...

Source file src/github.com/go-openapi/analysis/mixin.go

Documentation: github.com/go-openapi/analysis

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package analysis
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  
    21  	"github.com/go-openapi/spec"
    22  )
    23  
    24  // Mixin modifies the primary swagger spec by adding the paths and
    25  // definitions from the mixin specs. Top level parameters and
    26  // responses from the mixins are also carried over. Operation id
    27  // collisions are avoided by appending "Mixin<N>" but only if
    28  // needed.
    29  //
    30  // The following parts of primary are subject to merge, filling empty details
    31  //   - Info
    32  //   - BasePath
    33  //   - Host
    34  //   - ExternalDocs
    35  //
    36  // Consider calling FixEmptyResponseDescriptions() on the modified primary
    37  // if you read them from storage and they are valid to start with.
    38  //
    39  // Entries in "paths", "definitions", "parameters" and "responses" are
    40  // added to the primary in the order of the given mixins. If the entry
    41  // already exists in primary it is skipped with a warning message.
    42  //
    43  // The count of skipped entries (from collisions) is returned so any
    44  // deviation from the number expected can flag a warning in your build
    45  // scripts. Carefully review the collisions before accepting them;
    46  // consider renaming things if possible.
    47  //
    48  // No key normalization takes place (paths, type defs,
    49  // etc). Ensure they are canonical if your downstream tools do
    50  // key normalization of any form.
    51  //
    52  // Merging schemes (http, https), and consumers/producers do not account for
    53  // collisions.
    54  func Mixin(primary *spec.Swagger, mixins ...*spec.Swagger) []string {
    55  	skipped := make([]string, 0, len(mixins))
    56  	opIDs := getOpIDs(primary)
    57  	initPrimary(primary)
    58  
    59  	for i, m := range mixins {
    60  		skipped = append(skipped, mergeSwaggerProps(primary, m)...)
    61  
    62  		skipped = append(skipped, mergeConsumes(primary, m)...)
    63  
    64  		skipped = append(skipped, mergeProduces(primary, m)...)
    65  
    66  		skipped = append(skipped, mergeTags(primary, m)...)
    67  
    68  		skipped = append(skipped, mergeSchemes(primary, m)...)
    69  
    70  		skipped = append(skipped, mergeSecurityDefinitions(primary, m)...)
    71  
    72  		skipped = append(skipped, mergeSecurityRequirements(primary, m)...)
    73  
    74  		skipped = append(skipped, mergeDefinitions(primary, m)...)
    75  
    76  		// merging paths requires a map of operationIDs to work with
    77  		skipped = append(skipped, mergePaths(primary, m, opIDs, i)...)
    78  
    79  		skipped = append(skipped, mergeParameters(primary, m)...)
    80  
    81  		skipped = append(skipped, mergeResponses(primary, m)...)
    82  	}
    83  
    84  	return skipped
    85  }
    86  
    87  // getOpIDs extracts all the paths.<path>.operationIds from the given
    88  // spec and returns them as the keys in a map with 'true' values.
    89  func getOpIDs(s *spec.Swagger) map[string]bool {
    90  	rv := make(map[string]bool)
    91  	if s.Paths == nil {
    92  		return rv
    93  	}
    94  
    95  	for _, v := range s.Paths.Paths {
    96  		piops := pathItemOps(v)
    97  
    98  		for _, op := range piops {
    99  			rv[op.ID] = true
   100  		}
   101  	}
   102  
   103  	return rv
   104  }
   105  
   106  func pathItemOps(p spec.PathItem) []*spec.Operation {
   107  	var rv []*spec.Operation
   108  	rv = appendOp(rv, p.Get)
   109  	rv = appendOp(rv, p.Put)
   110  	rv = appendOp(rv, p.Post)
   111  	rv = appendOp(rv, p.Delete)
   112  	rv = appendOp(rv, p.Head)
   113  	rv = appendOp(rv, p.Patch)
   114  
   115  	return rv
   116  }
   117  
   118  func appendOp(ops []*spec.Operation, op *spec.Operation) []*spec.Operation {
   119  	if op == nil {
   120  		return ops
   121  	}
   122  
   123  	return append(ops, op)
   124  }
   125  
   126  func mergeSecurityDefinitions(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {
   127  	for k, v := range m.SecurityDefinitions {
   128  		if _, exists := primary.SecurityDefinitions[k]; exists {
   129  			warn := fmt.Sprintf(
   130  				"SecurityDefinitions entry '%v' already exists in primary or higher priority mixin, skipping\n", k)
   131  			skipped = append(skipped, warn)
   132  
   133  			continue
   134  		}
   135  
   136  		primary.SecurityDefinitions[k] = v
   137  	}
   138  
   139  	return
   140  }
   141  
   142  func mergeSecurityRequirements(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {
   143  	for _, v := range m.Security {
   144  		found := false
   145  		for _, vv := range primary.Security {
   146  			if reflect.DeepEqual(v, vv) {
   147  				found = true
   148  
   149  				break
   150  			}
   151  		}
   152  
   153  		if found {
   154  			warn := fmt.Sprintf(
   155  				"Security requirement: '%v' already exists in primary or higher priority mixin, skipping\n", v)
   156  			skipped = append(skipped, warn)
   157  
   158  			continue
   159  		}
   160  		primary.Security = append(primary.Security, v)
   161  	}
   162  
   163  	return
   164  }
   165  
   166  func mergeDefinitions(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {
   167  	for k, v := range m.Definitions {
   168  		// assume name collisions represent IDENTICAL type. careful.
   169  		if _, exists := primary.Definitions[k]; exists {
   170  			warn := fmt.Sprintf(
   171  				"definitions entry '%v' already exists in primary or higher priority mixin, skipping\n", k)
   172  			skipped = append(skipped, warn)
   173  
   174  			continue
   175  		}
   176  		primary.Definitions[k] = v
   177  	}
   178  
   179  	return
   180  }
   181  
   182  func mergePaths(primary *spec.Swagger, m *spec.Swagger, opIDs map[string]bool, mixIndex int) (skipped []string) {
   183  	if m.Paths != nil {
   184  		for k, v := range m.Paths.Paths {
   185  			if _, exists := primary.Paths.Paths[k]; exists {
   186  				warn := fmt.Sprintf(
   187  					"paths entry '%v' already exists in primary or higher priority mixin, skipping\n", k)
   188  				skipped = append(skipped, warn)
   189  
   190  				continue
   191  			}
   192  
   193  			// Swagger requires that operationIds be
   194  			// unique within a spec. If we find a
   195  			// collision we append "Mixin0" to the
   196  			// operatoinId we are adding, where 0 is mixin
   197  			// index.  We assume that operationIds with
   198  			// all the proivded specs are already unique.
   199  			piops := pathItemOps(v)
   200  			for _, piop := range piops {
   201  				if opIDs[piop.ID] {
   202  					piop.ID = fmt.Sprintf("%v%v%v", piop.ID, "Mixin", mixIndex)
   203  				}
   204  				opIDs[piop.ID] = true
   205  			}
   206  			primary.Paths.Paths[k] = v
   207  		}
   208  	}
   209  
   210  	return
   211  }
   212  
   213  func mergeParameters(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {
   214  	for k, v := range m.Parameters {
   215  		// could try to rename on conflict but would
   216  		// have to fix $refs in the mixin. Complain
   217  		// for now
   218  		if _, exists := primary.Parameters[k]; exists {
   219  			warn := fmt.Sprintf(
   220  				"top level parameters entry '%v' already exists in primary or higher priority mixin, skipping\n", k)
   221  			skipped = append(skipped, warn)
   222  
   223  			continue
   224  		}
   225  		primary.Parameters[k] = v
   226  	}
   227  
   228  	return
   229  }
   230  
   231  func mergeResponses(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {
   232  	for k, v := range m.Responses {
   233  		// could try to rename on conflict but would
   234  		// have to fix $refs in the mixin. Complain
   235  		// for now
   236  		if _, exists := primary.Responses[k]; exists {
   237  			warn := fmt.Sprintf(
   238  				"top level responses entry '%v' already exists in primary or higher priority mixin, skipping\n", k)
   239  			skipped = append(skipped, warn)
   240  
   241  			continue
   242  		}
   243  		primary.Responses[k] = v
   244  	}
   245  
   246  	return skipped
   247  }
   248  
   249  func mergeConsumes(primary *spec.Swagger, m *spec.Swagger) []string {
   250  	for _, v := range m.Consumes {
   251  		found := false
   252  		for _, vv := range primary.Consumes {
   253  			if v == vv {
   254  				found = true
   255  
   256  				break
   257  			}
   258  		}
   259  
   260  		if found {
   261  			// no warning here: we just skip it
   262  			continue
   263  		}
   264  		primary.Consumes = append(primary.Consumes, v)
   265  	}
   266  
   267  	return []string{}
   268  }
   269  
   270  func mergeProduces(primary *spec.Swagger, m *spec.Swagger) []string {
   271  	for _, v := range m.Produces {
   272  		found := false
   273  		for _, vv := range primary.Produces {
   274  			if v == vv {
   275  				found = true
   276  
   277  				break
   278  			}
   279  		}
   280  
   281  		if found {
   282  			// no warning here: we just skip it
   283  			continue
   284  		}
   285  		primary.Produces = append(primary.Produces, v)
   286  	}
   287  
   288  	return []string{}
   289  }
   290  
   291  func mergeTags(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {
   292  	for _, v := range m.Tags {
   293  		found := false
   294  		for _, vv := range primary.Tags {
   295  			if v.Name == vv.Name {
   296  				found = true
   297  
   298  				break
   299  			}
   300  		}
   301  
   302  		if found {
   303  			warn := fmt.Sprintf(
   304  				"top level tags entry with name '%v' already exists in primary or higher priority mixin, skipping\n",
   305  				v.Name,
   306  			)
   307  			skipped = append(skipped, warn)
   308  
   309  			continue
   310  		}
   311  
   312  		primary.Tags = append(primary.Tags, v)
   313  	}
   314  
   315  	return
   316  }
   317  
   318  func mergeSchemes(primary *spec.Swagger, m *spec.Swagger) []string {
   319  	for _, v := range m.Schemes {
   320  		found := false
   321  		for _, vv := range primary.Schemes {
   322  			if v == vv {
   323  				found = true
   324  
   325  				break
   326  			}
   327  		}
   328  
   329  		if found {
   330  			// no warning here: we just skip it
   331  			continue
   332  		}
   333  		primary.Schemes = append(primary.Schemes, v)
   334  	}
   335  
   336  	return []string{}
   337  }
   338  
   339  func mergeSwaggerProps(primary *spec.Swagger, m *spec.Swagger) []string {
   340  	var skipped, skippedInfo, skippedDocs []string
   341  
   342  	primary.Extensions, skipped = mergeExtensions(primary.Extensions, m.Extensions)
   343  
   344  	// merging details in swagger top properties
   345  	if primary.Host == "" {
   346  		primary.Host = m.Host
   347  	}
   348  
   349  	if primary.BasePath == "" {
   350  		primary.BasePath = m.BasePath
   351  	}
   352  
   353  	if primary.Info == nil {
   354  		primary.Info = m.Info
   355  	} else if m.Info != nil {
   356  		skippedInfo = mergeInfo(primary.Info, m.Info)
   357  		skipped = append(skipped, skippedInfo...)
   358  	}
   359  
   360  	if primary.ExternalDocs == nil {
   361  		primary.ExternalDocs = m.ExternalDocs
   362  	} else if m != nil {
   363  		skippedDocs = mergeExternalDocs(primary.ExternalDocs, m.ExternalDocs)
   364  		skipped = append(skipped, skippedDocs...)
   365  	}
   366  
   367  	return skipped
   368  }
   369  
   370  //nolint:unparam
   371  func mergeExternalDocs(primary *spec.ExternalDocumentation, m *spec.ExternalDocumentation) []string {
   372  	if primary.Description == "" {
   373  		primary.Description = m.Description
   374  	}
   375  
   376  	if primary.URL == "" {
   377  		primary.URL = m.URL
   378  	}
   379  
   380  	return nil
   381  }
   382  
   383  func mergeInfo(primary *spec.Info, m *spec.Info) []string {
   384  	var sk, skipped []string
   385  
   386  	primary.Extensions, sk = mergeExtensions(primary.Extensions, m.Extensions)
   387  	skipped = append(skipped, sk...)
   388  
   389  	if primary.Description == "" {
   390  		primary.Description = m.Description
   391  	}
   392  
   393  	if primary.Title == "" {
   394  		primary.Description = m.Description
   395  	}
   396  
   397  	if primary.TermsOfService == "" {
   398  		primary.TermsOfService = m.TermsOfService
   399  	}
   400  
   401  	if primary.Version == "" {
   402  		primary.Version = m.Version
   403  	}
   404  
   405  	if primary.Contact == nil {
   406  		primary.Contact = m.Contact
   407  	} else if m.Contact != nil {
   408  		var csk []string
   409  		primary.Contact.Extensions, csk = mergeExtensions(primary.Contact.Extensions, m.Contact.Extensions)
   410  		skipped = append(skipped, csk...)
   411  
   412  		if primary.Contact.Name == "" {
   413  			primary.Contact.Name = m.Contact.Name
   414  		}
   415  
   416  		if primary.Contact.URL == "" {
   417  			primary.Contact.URL = m.Contact.URL
   418  		}
   419  
   420  		if primary.Contact.Email == "" {
   421  			primary.Contact.Email = m.Contact.Email
   422  		}
   423  	}
   424  
   425  	if primary.License == nil {
   426  		primary.License = m.License
   427  	} else if m.License != nil {
   428  		var lsk []string
   429  		primary.License.Extensions, lsk = mergeExtensions(primary.License.Extensions, m.License.Extensions)
   430  		skipped = append(skipped, lsk...)
   431  
   432  		if primary.License.Name == "" {
   433  			primary.License.Name = m.License.Name
   434  		}
   435  
   436  		if primary.License.URL == "" {
   437  			primary.License.URL = m.License.URL
   438  		}
   439  	}
   440  
   441  	return skipped
   442  }
   443  
   444  func mergeExtensions(primary spec.Extensions, m spec.Extensions) (result spec.Extensions, skipped []string) {
   445  	if primary == nil {
   446  		result = m
   447  
   448  		return
   449  	}
   450  
   451  	if m == nil {
   452  		result = primary
   453  
   454  		return
   455  	}
   456  
   457  	result = primary
   458  	for k, v := range m {
   459  		if _, found := primary[k]; found {
   460  			skipped = append(skipped, k)
   461  
   462  			continue
   463  		}
   464  
   465  		primary[k] = v
   466  	}
   467  
   468  	return
   469  }
   470  
   471  func initPrimary(primary *spec.Swagger) {
   472  	if primary.SecurityDefinitions == nil {
   473  		primary.SecurityDefinitions = make(map[string]*spec.SecurityScheme)
   474  	}
   475  
   476  	if primary.Security == nil {
   477  		primary.Security = make([]map[string][]string, 0, 10)
   478  	}
   479  
   480  	if primary.Produces == nil {
   481  		primary.Produces = make([]string, 0, 10)
   482  	}
   483  
   484  	if primary.Consumes == nil {
   485  		primary.Consumes = make([]string, 0, 10)
   486  	}
   487  
   488  	if primary.Tags == nil {
   489  		primary.Tags = make([]spec.Tag, 0, 10)
   490  	}
   491  
   492  	if primary.Schemes == nil {
   493  		primary.Schemes = make([]string, 0, 10)
   494  	}
   495  
   496  	if primary.Paths == nil {
   497  		primary.Paths = &spec.Paths{Paths: make(map[string]spec.PathItem)}
   498  	}
   499  
   500  	if primary.Paths.Paths == nil {
   501  		primary.Paths.Paths = make(map[string]spec.PathItem)
   502  	}
   503  
   504  	if primary.Definitions == nil {
   505  		primary.Definitions = make(spec.Definitions)
   506  	}
   507  
   508  	if primary.Parameters == nil {
   509  		primary.Parameters = make(map[string]spec.Parameter)
   510  	}
   511  
   512  	if primary.Responses == nil {
   513  		primary.Responses = make(map[string]spec.Response)
   514  	}
   515  }
   516  

View as plain text