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