github.com/snyk/vervet/v6@v6.2.4/merge.go (about) 1 package vervet 2 3 import ( 4 "errors" 5 "sort" 6 7 "github.com/getkin/kin-openapi/openapi3" 8 ) 9 10 // Merge adds the paths and components from a source OpenAPI document root, 11 // to a destination document root. 12 // 13 // TODO: This is a naive implementation that should be improved to detect and 14 // resolve conflicts better. For example, distinct resources might have 15 // localized references with the same URIs but different content. 16 // Content-addressible resource versions may further facilitate governance; 17 // this also would facilitate detecting and relocating such conflicts. 18 // 19 // TODO(next-release): 20 // - This function is suitable for overlay merging scenarios only. 21 // - Component merging should be removed. Use Collator for safe component 22 // merging. 23 func Merge(dst, src *openapi3.T, replace bool) error { 24 err := mergeComponents(dst, src, replace) 25 if err != nil { 26 return err 27 } 28 mergeExtensions(dst, src, replace) 29 mergeInfo(dst, src, replace) 30 mergeOpenAPIVersion(dst, src, replace) 31 mergePaths(dst, src, replace) 32 mergeSecurityRequirements(dst, src, replace) 33 mergeServers(dst, src, replace) 34 mergeTags(dst, src, replace) 35 36 return nil 37 } 38 39 func mergeOpenAPIVersion(dst, src *openapi3.T, replace bool) { 40 if dst.OpenAPI == "" || (src.OpenAPI != "" && replace) { 41 dst.OpenAPI = src.OpenAPI 42 } 43 } 44 45 func mergeTags(dst, src *openapi3.T, replace bool) { 46 m := map[string]*openapi3.Tag{} 47 for _, t := range dst.Tags { 48 m[t.Name] = t 49 } 50 for _, t := range src.Tags { 51 if _, ok := m[t.Name]; !ok || replace { 52 m[t.Name] = t 53 } 54 } 55 dst.Tags = openapi3.Tags{} 56 tagNames := []string{} 57 for tagName := range m { 58 tagNames = append(tagNames, tagName) 59 } 60 sort.Strings(tagNames) 61 for _, tagName := range tagNames { 62 dst.Tags = append(dst.Tags, m[tagName]) 63 } 64 } 65 66 func initDestinationComponents(dst, src *openapi3.T) { 67 if src.Components.Schemas != nil && dst.Components.Schemas == nil { 68 dst.Components.Schemas = make(map[string]*openapi3.SchemaRef) 69 } 70 if src.Components.Parameters != nil && dst.Components.Parameters == nil { 71 dst.Components.Parameters = make(map[string]*openapi3.ParameterRef) 72 } 73 if src.Components.Headers != nil && dst.Components.Headers == nil { 74 dst.Components.Headers = make(map[string]*openapi3.HeaderRef) 75 } 76 if src.Components.RequestBodies != nil && dst.Components.RequestBodies == nil { 77 dst.Components.RequestBodies = make(map[string]*openapi3.RequestBodyRef) 78 } 79 if src.Components.Responses != nil && dst.Components.Responses == nil { 80 dst.Components.Responses = make(map[string]*openapi3.ResponseRef) 81 } 82 if src.Components.SecuritySchemes != nil && dst.Components.SecuritySchemes == nil { 83 dst.Components.SecuritySchemes = make(map[string]*openapi3.SecuritySchemeRef) 84 } 85 if src.Components.Examples != nil && dst.Components.Examples == nil { 86 dst.Components.Examples = make(map[string]*openapi3.ExampleRef) 87 } 88 if src.Components.Links != nil && dst.Components.Links == nil { 89 dst.Components.Links = make(map[string]*openapi3.LinkRef) 90 } 91 if src.Components.Callbacks != nil && dst.Components.Callbacks == nil { 92 dst.Components.Callbacks = make(map[string]*openapi3.CallbackRef) 93 } 94 } 95 96 func mergeComponents(dst, src *openapi3.T, replace bool) error { 97 if src.Components == nil { 98 return nil 99 } 100 101 if dst.Components == nil { 102 dst.Components = &openapi3.Components{} 103 } 104 105 initDestinationComponents(dst, src) 106 107 err := mergeMap(dst.Components.Schemas, src.Components.Schemas, replace) 108 if err != nil { 109 return err 110 } 111 err = mergeMap(dst.Components.Parameters, src.Components.Parameters, replace) 112 if err != nil { 113 return err 114 } 115 err = mergeMap(dst.Components.Headers, src.Components.Headers, replace) 116 if err != nil { 117 return err 118 } 119 err = mergeMap(dst.Components.RequestBodies, src.Components.RequestBodies, replace) 120 if err != nil { 121 return err 122 } 123 err = mergeMap(dst.Components.Responses, src.Components.Responses, replace) 124 if err != nil { 125 return err 126 } 127 err = mergeMap(dst.Components.SecuritySchemes, src.Components.SecuritySchemes, replace) 128 if err != nil { 129 return err 130 } 131 err = mergeMap(dst.Components.Examples, src.Components.Examples, replace) 132 if err != nil { 133 return err 134 } 135 err = mergeMap(dst.Components.Links, src.Components.Links, replace) 136 if err != nil { 137 return err 138 } 139 return mergeMap(dst.Components.Callbacks, src.Components.Callbacks, replace) 140 } 141 142 func mergeMap[T any](dst, src map[string]T, replace bool) error { 143 for k, v := range src { 144 existing, exists := dst[k] 145 if exists && !replace && !componentsEqual(v, existing) { 146 return errors.New("conflicting component: " + k) 147 } 148 dst[k] = v 149 } 150 return nil 151 } 152 153 func mergeExtensions(dst, src *openapi3.T, replace bool) { 154 if src.Extensions != nil && dst.Extensions == nil { 155 dst.Extensions = make(map[string]interface{}, len(src.Extensions)) 156 } 157 158 for k, v := range src.Extensions { 159 // It's possible for specs to be merged from multiple stabilities and 160 // we don't want these different stability inputs to override 161 // the declared stability of the output we're building. 162 if k == ExtSnykApiStability { 163 continue 164 } 165 166 if _, ok := dst.Extensions[k]; !ok || replace { 167 dst.Extensions[k] = v 168 } 169 } 170 } 171 172 func mergeInfo(dst, src *openapi3.T, replace bool) { 173 if src.Info != nil && (dst.Info == nil || replace) { 174 dst.Info = src.Info 175 } 176 } 177 178 func mergePaths(dst, src *openapi3.T, replace bool) { 179 if src.Paths != nil && dst.Paths == nil { 180 dst.Paths = make(openapi3.Paths, len(src.Paths)) 181 } 182 for k, v := range src.Paths { 183 if _, ok := dst.Paths[k]; !ok || replace { 184 dst.Paths[k] = v 185 } 186 } 187 } 188 189 func mergeSecurityRequirements(dst, src *openapi3.T, replace bool) { 190 if len(src.Security) > 0 && (len(dst.Security) == 0 || replace) { 191 dst.Security = src.Security 192 } 193 } 194 195 func mergeServers(dst, src *openapi3.T, replace bool) { 196 if len(src.Servers) > 0 && (len(dst.Security) == 0 || replace) { 197 dst.Servers = src.Servers 198 } 199 }