github.com/snyk/vervet/v4@v4.27.2/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  	var 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  	initDestinationComponents(dst, src)
    92  	for k, v := range src.Components.Schemas {
    93  		if _, ok := dst.Components.Schemas[k]; !ok || replace {
    94  			dst.Components.Schemas[k] = v
    95  		}
    96  	}
    97  	for k, v := range src.Components.Parameters {
    98  		if _, ok := dst.Components.Parameters[k]; !ok || replace {
    99  			dst.Components.Parameters[k] = v
   100  		}
   101  	}
   102  	for k, v := range src.Components.Headers {
   103  		if _, ok := dst.Components.Headers[k]; !ok || replace {
   104  			dst.Components.Headers[k] = v
   105  		}
   106  	}
   107  	for k, v := range src.Components.RequestBodies {
   108  		if _, ok := dst.Components.RequestBodies[k]; !ok || replace {
   109  			dst.Components.RequestBodies[k] = v
   110  		}
   111  	}
   112  	for k, v := range src.Components.Responses {
   113  		if _, ok := dst.Components.Responses[k]; !ok || replace {
   114  			dst.Components.Responses[k] = v
   115  		}
   116  	}
   117  	for k, v := range src.Components.SecuritySchemes {
   118  		if _, ok := dst.Components.SecuritySchemes[k]; !ok || replace {
   119  			dst.Components.SecuritySchemes[k] = v
   120  		}
   121  	}
   122  	for k, v := range src.Components.Examples {
   123  		if _, ok := dst.Components.Examples[k]; !ok || replace {
   124  			dst.Components.Examples[k] = v
   125  		}
   126  	}
   127  	for k, v := range src.Components.Links {
   128  		if _, ok := dst.Components.Links[k]; !ok || replace {
   129  			dst.Components.Links[k] = v
   130  		}
   131  	}
   132  	for k, v := range src.Components.Callbacks {
   133  		if _, ok := dst.Components.Callbacks[k]; !ok || replace {
   134  			dst.Components.Callbacks[k] = v
   135  		}
   136  	}
   137  }
   138  
   139  func mergeExtensions(dst, src *openapi3.T, replace bool) {
   140  	if src.Extensions != nil && dst.Extensions == nil {
   141  		dst.Extensions = make(map[string]interface{}, len(src.Extensions))
   142  	}
   143  
   144  	for k, v := range src.Extensions {
   145  		// It's possible for specs to be merged from multiple stabilities and
   146  		// we don't want these different stability inputs to override
   147  		// the declared stability of the output we're building.
   148  		if k == ExtSnykApiStability {
   149  			continue
   150  		}
   151  
   152  		if _, ok := dst.Extensions[k]; !ok || replace {
   153  			dst.Extensions[k] = v
   154  		}
   155  	}
   156  }
   157  
   158  func mergeInfo(dst, src *openapi3.T, replace bool) {
   159  	if src.Info != nil && (dst.Info == nil || replace) {
   160  		dst.Info = src.Info
   161  	}
   162  }
   163  
   164  func mergePaths(dst, src *openapi3.T, replace bool) {
   165  	if src.Paths != nil && dst.Paths == nil {
   166  		dst.Paths = make(openapi3.Paths, len(src.Paths))
   167  	}
   168  	for k, v := range src.Paths {
   169  		if _, ok := dst.Paths[k]; !ok || replace {
   170  			dst.Paths[k] = v
   171  		}
   172  	}
   173  }
   174  
   175  func mergeSecurityRequirements(dst, src *openapi3.T, replace bool) {
   176  	if len(src.Security) > 0 && (len(dst.Security) == 0 || replace) {
   177  		dst.Security = src.Security
   178  	}
   179  }
   180  
   181  func mergeServers(dst, src *openapi3.T, replace bool) {
   182  	if len(src.Servers) > 0 && (len(dst.Security) == 0 || replace) {
   183  		dst.Servers = src.Servers
   184  	}
   185  }