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  }