github.com/snyk/vervet/v5@v5.11.1-0.20240202085829-ad4dd7fb6101/config/api.go (about) 1 package config 2 3 import ( 4 "fmt" 5 6 "github.com/bmatcuk/doublestar/v4" 7 ) 8 9 // APIs defines a named map of API instances. 10 type APIs map[string]*API 11 12 // An API defines how and where to build versioned OpenAPI documents from a 13 // source collection of individual resource specifications and additional 14 // overlay content to merge. 15 type API struct { 16 Name string `json:"-"` 17 Resources []*ResourceSet `json:"resources"` 18 Overlays []*Overlay `json:"overlays"` 19 Output *Output `json:"output"` 20 } 21 22 // A ResourceSet defines a set of versioned resources that adhere to the same 23 // standards. 24 // 25 // Versioned resources are expressed as individual OpenAPI documents in a 26 // directory structure: 27 // 28 // +-resource 29 // 30 // | 31 // +-2021-08-01 32 // | | 33 // | +-spec.yaml 34 // | +-<implementation code, etc. can go here> 35 // | 36 // +-2021-08-15 37 // | | 38 // | +-spec.yaml 39 // | +-<implementation code, etc. can go here> 40 // ... 41 // 42 // Each YYYY-mm-dd directory under a resource is a version. The spec.yaml 43 // in each version is a complete OpenAPI document describing the resource 44 // at that version. 45 type ResourceSet struct { 46 Description string `json:"description"` 47 Linter string `json:"linter"` 48 LinterOverrides map[string]Linters `json:"linter-overrides"` 49 Path string `json:"path"` 50 Excludes []string `json:"excludes"` 51 } 52 53 func (r *ResourceSet) validate() error { 54 for _, exclude := range r.Excludes { 55 if !doublestar.ValidatePattern(exclude) { 56 return fmt.Errorf("invalid exclude pattern %q", exclude) 57 } 58 } 59 return nil 60 } 61 62 // An Overlay defines additional OpenAPI documents to merge into the aggregate 63 // OpenAPI spec when compiling an API. These might include special endpoints 64 // that should be included in the aggregate API but are not versioned, or 65 // top-level descriptions of the API itself. 66 type Overlay struct { 67 Include string `json:"include"` 68 Inline string `json:"inline"` 69 } 70 71 // Output defines where the aggregate versioned OpenAPI specs should be created 72 // during compilation. 73 type Output struct { 74 Path string `json:"path,omitempty"` 75 Paths []string `json:"paths,omitempty"` 76 Linter string `json:"linter"` 77 } 78 79 // EffectivePaths returns a slice of effective configured output paths, whether 80 // a single or multiple output paths have been configured. 81 func (o *Output) ResolvePaths() []string { 82 if o.Path != "" { 83 return []string{o.Path} 84 } 85 return o.Paths 86 } 87 88 func (a APIs) init(p *Project) error { 89 if len(a) == 0 { 90 return fmt.Errorf("no apis defined") 91 } 92 // Referenced linters and generators all exist 93 for name, api := range a { 94 api.Name = name 95 if len(api.Resources) == 0 { 96 return fmt.Errorf("no resources defined (apis.%s.resources)", api.Name) 97 } 98 for rcIndex, resource := range api.Resources { 99 if resource.Linter != "" { 100 if _, ok := p.Linters[resource.Linter]; !ok { 101 return fmt.Errorf("linter %q not found (apis.%s.resources[%d].linter)", 102 resource.Linter, api.Name, rcIndex) 103 } 104 } 105 if err := resource.validate(); err != nil { 106 return fmt.Errorf("%w (apis.%s.resources[%d])", err, api.Name, rcIndex) 107 } 108 for rcName, versionMap := range resource.LinterOverrides { 109 for version, linter := range versionMap { 110 err := linter.validate() 111 if err != nil { 112 return fmt.Errorf("%w (apis.%s.resources[%d].linter-overrides.%s.%s)", 113 err, api.Name, rcIndex, rcName, version) 114 } 115 if linter.OpticCI != nil { 116 return fmt.Errorf("optic linter does not support overrides (apis.%s.resources[%d].linter-overrides.%s.%s)", 117 api.Name, rcIndex, rcName, version) 118 } 119 } 120 } 121 } 122 if api.Output != nil { 123 if len(api.Output.Paths) > 0 && api.Output.Path != "" { 124 return fmt.Errorf("output should specify one of 'path' or 'paths', not both (apis.%s.output)", 125 api.Name) 126 } 127 if api.Output.Linter != "" { 128 if linter, ok := p.Linters[api.Output.Linter]; !ok { 129 return fmt.Errorf("linter %q not found (apis.%s.output.linter)", 130 api.Output.Linter, api.Name) 131 } else if linter.OpticCI != nil { 132 return fmt.Errorf("optic linter does not yet support compiled specs (apis.%s.output.linter)", 133 api.Name) 134 } 135 } 136 } 137 } 138 return nil 139 }