github.com/rzurga/go-swagger@v0.28.1-0.20211109195225-5d1f453ffa3a/cmd/swagger/commands/diff/spec_analyser.go (about)

     1  package diff
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/go-openapi/spec"
     8  )
     9  
    10  // StringType For identifying string types
    11  const StringType = "string"
    12  
    13  // URLMethodResponse encapsulates these three elements to act as a map key
    14  type URLMethodResponse struct {
    15  	Path     string `json:"path"`
    16  	Method   string `json:"method"`
    17  	Response string `json:"response"`
    18  }
    19  
    20  // MarshalText - for serializing as a map key
    21  func (p URLMethod) MarshalText() (text []byte, err error) {
    22  	return []byte(fmt.Sprintf("%s %s", p.Path, p.Method)), nil
    23  }
    24  
    25  // URLMethods allows iteration of endpoints based on url and method
    26  type URLMethods map[URLMethod]*PathItemOp
    27  
    28  // SpecAnalyser contains all the differences for a Spec
    29  type SpecAnalyser struct {
    30  	Diffs                 SpecDifferences
    31  	urlMethods1           URLMethods
    32  	urlMethods2           URLMethods
    33  	Definitions1          spec.Definitions
    34  	Definitions2          spec.Definitions
    35  	ReferencedDefinitions map[string]bool
    36  }
    37  
    38  // NewSpecAnalyser returns an empty SpecDiffs
    39  func NewSpecAnalyser() *SpecAnalyser {
    40  	return &SpecAnalyser{
    41  		Diffs:                 SpecDifferences{},
    42  		ReferencedDefinitions: map[string]bool{},
    43  	}
    44  }
    45  
    46  // Analyse the differences in two specs
    47  func (sd *SpecAnalyser) Analyse(spec1, spec2 *spec.Swagger) error {
    48  	sd.Definitions1 = spec1.Definitions
    49  	sd.Definitions2 = spec2.Definitions
    50  	sd.urlMethods1 = getURLMethodsFor(spec1)
    51  	sd.urlMethods2 = getURLMethodsFor(spec2)
    52  
    53  	sd.analyseSpecMetadata(spec1, spec2)
    54  	sd.analyseEndpoints()
    55  	sd.analyseRequestParams()
    56  	sd.analyseEndpointData()
    57  	sd.analyseResponseParams()
    58  	sd.AnalyseDefinitions()
    59  
    60  	return nil
    61  }
    62  
    63  func (sd *SpecAnalyser) analyseSpecMetadata(spec1, spec2 *spec.Swagger) {
    64  	// breaking if it no longer consumes any formats
    65  	added, deleted, _ := fromStringArray(spec1.Consumes).DiffsTo(spec2.Consumes)
    66  
    67  	node := getNameOnlyDiffNode("Spec")
    68  	location := DifferenceLocation{Node: node}
    69  	consumesLoation := location.AddNode(getNameOnlyDiffNode("consumes"))
    70  
    71  	for _, eachAdded := range added {
    72  		sd.Diffs = sd.Diffs.addDiff(
    73  			SpecDifference{DifferenceLocation: consumesLoation, Code: AddedConsumesFormat, Compatibility: NonBreaking, DiffInfo: eachAdded})
    74  	}
    75  	for _, eachDeleted := range deleted {
    76  		sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: consumesLoation, Code: DeletedConsumesFormat, Compatibility: Breaking, DiffInfo: eachDeleted})
    77  	}
    78  
    79  	// // breaking if it no longer produces any formats
    80  	added, deleted, _ = fromStringArray(spec1.Produces).DiffsTo(spec2.Produces)
    81  	producesLocation := location.AddNode(getNameOnlyDiffNode("produces"))
    82  	for _, eachAdded := range added {
    83  		sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: producesLocation, Code: AddedProducesFormat, Compatibility: NonBreaking, DiffInfo: eachAdded})
    84  	}
    85  	for _, eachDeleted := range deleted {
    86  		sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: producesLocation, Code: DeletedProducesFormat, Compatibility: Breaking, DiffInfo: eachDeleted})
    87  	}
    88  
    89  	// // breaking if it no longer supports a scheme
    90  	added, deleted, _ = fromStringArray(spec1.Schemes).DiffsTo(spec2.Schemes)
    91  	schemesLocation := location.AddNode(getNameOnlyDiffNode("schemes"))
    92  
    93  	for _, eachAdded := range added {
    94  		sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: schemesLocation, Code: AddedSchemes, Compatibility: NonBreaking, DiffInfo: eachAdded})
    95  	}
    96  	for _, eachDeleted := range deleted {
    97  		sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: schemesLocation, Code: DeletedSchemes, Compatibility: Breaking, DiffInfo: eachDeleted})
    98  	}
    99  
   100  	// host should be able to change without any issues?
   101  	sd.analyseMetaDataProperty(spec1.Info.Description, spec2.Info.Description, ChangedDescripton, NonBreaking)
   102  
   103  	// // host should be able to change without any issues?
   104  	sd.analyseMetaDataProperty(spec1.Host, spec2.Host, ChangedHostURL, Breaking)
   105  	// sd.Host = compareStrings(spec1.Host, spec2.Host)
   106  
   107  	// // Base Path change will break non generated clients
   108  	sd.analyseMetaDataProperty(spec1.BasePath, spec2.BasePath, ChangedBasePath, Breaking)
   109  
   110  	// TODO: what to do about security?
   111  	// Missing security scheme will break a client
   112  	// Security            []map[string][]string  `json:"security,omitempty"`
   113  	// Tags                []Tag                  `json:"tags,omitempty"`
   114  	// ExternalDocs        *ExternalDocumentation `json:"externalDocs,omitempty"`
   115  }
   116  
   117  func (sd *SpecAnalyser) analyseEndpoints() {
   118  	sd.findDeletedEndpoints()
   119  	sd.findAddedEndpoints()
   120  }
   121  
   122  // AnalyseDefinitions check for changes to defintion objects not referenced in any endpoint
   123  func (sd *SpecAnalyser) AnalyseDefinitions() {
   124  	alreadyReferenced := map[string]bool{}
   125  	for k := range sd.ReferencedDefinitions {
   126  		alreadyReferenced[k] = true
   127  	}
   128  	location := DifferenceLocation{Node: &Node{Field: "Spec Definitions"}}
   129  	for name1, sch := range sd.Definitions1 {
   130  		schema1 := sch
   131  		if _, ok := alreadyReferenced[name1]; !ok {
   132  			childLocation := location.AddNode(&Node{Field: name1})
   133  			if schema2, ok := sd.Definitions2[name1]; ok {
   134  				sd.compareSchema(childLocation, &schema1, &schema2)
   135  			} else {
   136  				sd.addDiffs(childLocation, []TypeDiff{{Change: DeletedDefinition}})
   137  			}
   138  		}
   139  	}
   140  	for name2 := range sd.Definitions2 {
   141  		if _, ok := sd.Definitions1[name2]; !ok {
   142  			childLocation := location.AddNode(&Node{Field: name2})
   143  			sd.addDiffs(childLocation, []TypeDiff{{Change: AddedDefinition}})
   144  		}
   145  	}
   146  }
   147  
   148  func (sd *SpecAnalyser) analyseEndpointData() {
   149  
   150  	for URLMethod, op2 := range sd.urlMethods2 {
   151  		if op1, ok := sd.urlMethods1[URLMethod]; ok {
   152  			addedTags, deletedTags, _ := fromStringArray(op1.Operation.Tags).DiffsTo(op2.Operation.Tags)
   153  			location := DifferenceLocation{URL: URLMethod.Path, Method: URLMethod.Method}
   154  
   155  			for _, eachAddedTag := range addedTags {
   156  				sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: AddedTag, DiffInfo: fmt.Sprintf(`"%s"`, eachAddedTag)})
   157  			}
   158  			for _, eachDeletedTag := range deletedTags {
   159  				sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: DeletedTag, DiffInfo: fmt.Sprintf(`"%s"`, eachDeletedTag)})
   160  			}
   161  
   162  			sd.compareDescripton(location, op1.Operation.Description, op2.Operation.Description)
   163  
   164  		}
   165  	}
   166  }
   167  
   168  func (sd *SpecAnalyser) analyseRequestParams() {
   169  	locations := []string{"query", "path", "body", "header"}
   170  
   171  	for _, paramLocation := range locations {
   172  		rootNode := getNameOnlyDiffNode(strings.Title(paramLocation))
   173  		for URLMethod, op2 := range sd.urlMethods2 {
   174  			if op1, ok := sd.urlMethods1[URLMethod]; ok {
   175  
   176  				params1 := getParams(op1.ParentPathItem.Parameters, op1.Operation.Parameters, paramLocation)
   177  				params2 := getParams(op2.ParentPathItem.Parameters, op2.Operation.Parameters, paramLocation)
   178  
   179  				location := DifferenceLocation{URL: URLMethod.Path, Method: URLMethod.Method, Node: rootNode}
   180  
   181  				// detect deleted params
   182  				for paramName1, param1 := range params1 {
   183  					if _, ok := params2[paramName1]; !ok {
   184  						childLocation := location.AddNode(getSchemaDiffNode(paramName1, &param1.SimpleSchema))
   185  						code := DeletedOptionalParam
   186  						if param1.Required {
   187  							code = DeletedRequiredParam
   188  						}
   189  						sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: childLocation, Code: code})
   190  					}
   191  				}
   192  				// detect added changed params
   193  				for paramName2, param2 := range params2 {
   194  					// changed?
   195  					if param1, ok := params1[paramName2]; ok {
   196  						sd.compareParams(URLMethod, paramLocation, paramName2, param1, param2)
   197  					} else {
   198  						// Added
   199  						childLocation := location.AddNode(getSchemaDiffNode(paramName2, &param2.SimpleSchema))
   200  						code := AddedOptionalParam
   201  						if param2.Required {
   202  							code = AddedRequiredParam
   203  						}
   204  						sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: childLocation, Code: code})
   205  					}
   206  				}
   207  			}
   208  		}
   209  	}
   210  }
   211  
   212  func (sd *SpecAnalyser) analyseResponseParams() {
   213  	// Loop through url+methods in spec 2 - check deleted and changed
   214  	for eachURLMethodFrom2, op2 := range sd.urlMethods2 {
   215  
   216  		// present in both specs? Use key from spec 2 to lookup in spec 1
   217  		if op1, ok := sd.urlMethods1[eachURLMethodFrom2]; ok {
   218  			// compare responses for url and method
   219  			op1Responses := op1.Operation.Responses.StatusCodeResponses
   220  			op2Responses := op2.Operation.Responses.StatusCodeResponses
   221  
   222  			// deleted responses
   223  			for code1 := range op1Responses {
   224  				if _, ok := op2Responses[code1]; !ok {
   225  					location := DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code1, Node: getSchemaDiffNode("Body", op1Responses[code1].Schema)}
   226  					sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: DeletedResponse})
   227  				}
   228  			}
   229  			// Added updated Response Codes
   230  			for code2, op2Response := range op2Responses {
   231  
   232  				if op1Response, ok := op1Responses[code2]; ok {
   233  					op1Headers := op1Response.ResponseProps.Headers
   234  					headerRootNode := getNameOnlyDiffNode("Headers")
   235  
   236  					// Iterate Spec2 Headers looking for added and updated
   237  					location := DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code2, Node: headerRootNode}
   238  					for op2HeaderName, op2Header := range op2Response.ResponseProps.Headers {
   239  						if op1Header, ok := op1Headers[op2HeaderName]; ok {
   240  							diffs := sd.CompareProps(forHeader(op1Header), forHeader(op2Header))
   241  							sd.addDiffs(location, diffs)
   242  						} else {
   243  							sd.Diffs = sd.Diffs.addDiff(SpecDifference{
   244  								DifferenceLocation: location.AddNode(getSchemaDiffNode(op2HeaderName, &op2Header.SimpleSchema)),
   245  								Code:               AddedResponseHeader})
   246  						}
   247  					}
   248  					for op1HeaderName := range op1Response.ResponseProps.Headers {
   249  						if _, ok := op2Response.ResponseProps.Headers[op1HeaderName]; !ok {
   250  							op1Header := op1Response.ResponseProps.Headers[op1HeaderName]
   251  							sd.Diffs = sd.Diffs.addDiff(SpecDifference{
   252  								DifferenceLocation: location.AddNode(getSchemaDiffNode(op1HeaderName, &op1Header.SimpleSchema)),
   253  								Code:               DeletedResponseHeader})
   254  						}
   255  					}
   256  					schem := op1Response.Schema
   257  					node := getNameOnlyDiffNode("NoContent")
   258  					if schem != nil {
   259  						node = getSchemaDiffNode("Body", &schem.SchemaProps)
   260  					}
   261  					responseLocation := DifferenceLocation{URL: eachURLMethodFrom2.Path,
   262  						Method:   eachURLMethodFrom2.Method,
   263  						Response: code2,
   264  						Node:     node}
   265  					sd.compareDescripton(responseLocation, op1Response.Description, op2Response.Description)
   266  
   267  					if op1Response.Schema != nil {
   268  						sd.compareSchema(
   269  							DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code2, Node: getSchemaDiffNode("Body", op1Response.Schema)},
   270  							op1Response.Schema,
   271  							op2Response.Schema)
   272  					}
   273  				} else {
   274  					// op2Response
   275  					sd.Diffs = sd.Diffs.addDiff(SpecDifference{
   276  						DifferenceLocation: DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code2, Node: getSchemaDiffNode("Body", op2Response.Schema)},
   277  						Code:               AddedResponse})
   278  				}
   279  			}
   280  		}
   281  	}
   282  }
   283  
   284  func addTypeDiff(diffs []TypeDiff, diff TypeDiff) []TypeDiff {
   285  	if diff.Change != NoChangeDetected {
   286  		diffs = append(diffs, diff)
   287  	}
   288  	return diffs
   289  }
   290  
   291  // CompareProps computes type specific property diffs
   292  func (sd *SpecAnalyser) CompareProps(type1, type2 *spec.SchemaProps) []TypeDiff {
   293  
   294  	diffs := []TypeDiff{}
   295  
   296  	diffs = CheckToFromPrimitiveType(diffs, type1, type2)
   297  
   298  	if len(diffs) > 0 {
   299  		return diffs
   300  	}
   301  
   302  	if isArray(type1) {
   303  		maxItemDiffs := CompareIntValues("MaxItems", type1.MaxItems, type2.MaxItems, WidenedType, NarrowedType)
   304  		diffs = append(diffs, maxItemDiffs...)
   305  		minItemsDiff := CompareIntValues("MinItems", type1.MinItems, type2.MinItems, NarrowedType, WidenedType)
   306  		diffs = append(diffs, minItemsDiff...)
   307  	}
   308  
   309  	if len(diffs) > 0 {
   310  		return diffs
   311  	}
   312  
   313  	diffs = CheckRefChange(diffs, type1, type2)
   314  	if len(diffs) > 0 {
   315  		return diffs
   316  	}
   317  
   318  	if !(isPrimitiveType(type1.Type) && isPrimitiveType(type2.Type)) {
   319  		return diffs
   320  	}
   321  
   322  	// check primitive type hierarchy change eg string -> integer = NarrowedChange
   323  	if type1.Type[0] != type2.Type[0] ||
   324  		type1.Format != type2.Format {
   325  		diff := getTypeHierarchyChange(primitiveTypeString(type1.Type[0], type1.Format), primitiveTypeString(type2.Type[0], type2.Format))
   326  		diffs = addTypeDiff(diffs, diff)
   327  	}
   328  
   329  	diffs = CheckStringTypeChanges(diffs, type1, type2)
   330  
   331  	if len(diffs) > 0 {
   332  		return diffs
   333  	}
   334  
   335  	diffs = checkNumericTypeChanges(diffs, type1, type2)
   336  
   337  	if len(diffs) > 0 {
   338  		return diffs
   339  	}
   340  
   341  	return diffs
   342  }
   343  
   344  func (sd *SpecAnalyser) compareParams(urlMethod URLMethod, location string, name string, param1, param2 spec.Parameter) {
   345  	diffLocation := DifferenceLocation{URL: urlMethod.Path, Method: urlMethod.Method}
   346  
   347  	childLocation := diffLocation.AddNode(getNameOnlyDiffNode(strings.Title(location)))
   348  	paramLocation := diffLocation.AddNode(getNameOnlyDiffNode(name))
   349  	sd.compareDescripton(paramLocation, param1.Description, param2.Description)
   350  
   351  	if param1.Schema != nil && param2.Schema != nil {
   352  		if len(name) > 0 {
   353  			childLocation = childLocation.AddNode(getSchemaDiffNode(name, param2.Schema))
   354  		}
   355  
   356  		sd.compareSchema(childLocation, param1.Schema, param2.Schema)
   357  	}
   358  	diffs := sd.CompareProps(forParam(param1), forParam(param2))
   359  
   360  	childLocation = childLocation.AddNode(getSchemaDiffNode(name, &param2.SimpleSchema))
   361  	if len(diffs) > 0 {
   362  		sd.addDiffs(childLocation, diffs)
   363  	}
   364  
   365  	diffs = CheckToFromRequired(param1.Required, param2.Required)
   366  	if len(diffs) > 0 {
   367  		sd.addDiffs(childLocation, diffs)
   368  	}
   369  }
   370  
   371  func (sd *SpecAnalyser) addTypeDiff(location DifferenceLocation, diff *TypeDiff) {
   372  	diffCopy := diff
   373  	desc := diffCopy.Description
   374  	if len(desc) == 0 {
   375  		if diffCopy.FromType != diffCopy.ToType {
   376  			desc = fmt.Sprintf("%s -> %s", diffCopy.FromType, diffCopy.ToType)
   377  		}
   378  	}
   379  	sd.Diffs = sd.Diffs.addDiff(SpecDifference{
   380  		DifferenceLocation: location,
   381  		Code:               diffCopy.Change,
   382  		DiffInfo:           desc})
   383  }
   384  
   385  func (sd *SpecAnalyser) compareDescripton(location DifferenceLocation, desc1, desc2 string) {
   386  	if desc1 != desc2 {
   387  		code := ChangedDescripton
   388  		if len(desc1) > 0 {
   389  			code = DeletedDescripton
   390  		} else if len(desc2) > 0 {
   391  			code = AddedDescripton
   392  		}
   393  		sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: code})
   394  	}
   395  }
   396  
   397  func isPrimitiveType(item spec.StringOrArray) bool {
   398  	return len(item) > 0 && item[0] != ArrayType && item[0] != ObjectType
   399  }
   400  
   401  func isArrayType(item spec.StringOrArray) bool {
   402  	return len(item) > 0 && item[0] == ArrayType
   403  }
   404  func (sd *SpecAnalyser) getRefSchemaFromSpec1(ref spec.Ref) (*spec.Schema, string) {
   405  	return sd.schemaFromRef(ref, &sd.Definitions1)
   406  }
   407  
   408  func (sd *SpecAnalyser) getRefSchemaFromSpec2(ref spec.Ref) (*spec.Schema, string) {
   409  	return sd.schemaFromRef(ref, &sd.Definitions2)
   410  }
   411  
   412  // CompareSchemaFn Fn spec for comparing schemas
   413  type CompareSchemaFn func(location DifferenceLocation, schema1, schema2 *spec.Schema)
   414  
   415  func (sd *SpecAnalyser) compareSchema(location DifferenceLocation, schema1, schema2 *spec.Schema) {
   416  
   417  	refDiffs := []TypeDiff{}
   418  	refDiffs = CheckRefChange(refDiffs, schema1, schema2)
   419  	if len(refDiffs) > 0 {
   420  		for _, d := range refDiffs {
   421  			diff := d
   422  			sd.addTypeDiff(location, &diff)
   423  		}
   424  		return
   425  	}
   426  
   427  	if isRefType(schema1) {
   428  		schema1, _ = sd.schemaFromRef(getRef(schema1), &sd.Definitions1)
   429  	}
   430  	if isRefType(schema2) {
   431  		schema2, _ = sd.schemaFromRef(getRef(schema2), &sd.Definitions2)
   432  	}
   433  
   434  	sd.compareDescripton(location, schema1.Description, schema2.Description)
   435  
   436  	typeDiffs := sd.CompareProps(&schema1.SchemaProps, &schema2.SchemaProps)
   437  	if len(typeDiffs) > 0 {
   438  		sd.addDiffs(location, typeDiffs)
   439  		return
   440  	}
   441  
   442  	if isArray(schema1) {
   443  		sd.compareSchema(location, schema1.Items.Schema, schema2.Items.Schema)
   444  	}
   445  
   446  	diffs := CompareProperties(location, schema1, schema2, sd.getRefSchemaFromSpec1, sd.getRefSchemaFromSpec2, sd.compareSchema)
   447  	for _, diff := range diffs {
   448  		sd.Diffs = sd.Diffs.addDiff(diff)
   449  	}
   450  }
   451  
   452  func (sd *SpecAnalyser) addDiffs(location DifferenceLocation, diffs []TypeDiff) {
   453  	for _, e := range diffs {
   454  		eachTypeDiff := e
   455  		if eachTypeDiff.Change != NoChangeDetected {
   456  			sd.addTypeDiff(location, &eachTypeDiff)
   457  		}
   458  	}
   459  }
   460  
   461  func addChildDiffNode(location DifferenceLocation, propName string, propSchema *spec.Schema) DifferenceLocation {
   462  	newNode := location.Node
   463  	childNode := fromSchemaProps(propName, &propSchema.SchemaProps)
   464  	if newNode != nil {
   465  		newNode = newNode.Copy()
   466  		newNode.AddLeafNode(&childNode)
   467  	} else {
   468  		newNode = &childNode
   469  	}
   470  	return DifferenceLocation{
   471  		URL:      location.URL,
   472  		Method:   location.Method,
   473  		Response: location.Response,
   474  		Node:     newNode,
   475  	}
   476  }
   477  
   478  func fromSchemaProps(fieldName string, props *spec.SchemaProps) Node {
   479  	node := Node{}
   480  	node.TypeName, node.IsArray = getSchemaType(props)
   481  	node.Field = fieldName
   482  	return node
   483  }
   484  
   485  func (sd *SpecAnalyser) findAddedEndpoints() {
   486  	for URLMethod := range sd.urlMethods2 {
   487  		if _, ok := sd.urlMethods1[URLMethod]; !ok {
   488  			sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: DifferenceLocation{URL: URLMethod.Path, Method: URLMethod.Method}, Code: AddedEndpoint})
   489  		}
   490  	}
   491  }
   492  
   493  func (sd *SpecAnalyser) findDeletedEndpoints() {
   494  	for eachURLMethod, operation1 := range sd.urlMethods1 {
   495  		code := DeletedEndpoint
   496  		if (operation1.ParentPathItem.Options != nil && operation1.ParentPathItem.Options.Deprecated) ||
   497  			(operation1.Operation.Deprecated) {
   498  			code = DeletedDeprecatedEndpoint
   499  		}
   500  		if _, ok := sd.urlMethods2[eachURLMethod]; !ok {
   501  			sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: DifferenceLocation{URL: eachURLMethod.Path, Method: eachURLMethod.Method}, Code: code})
   502  		}
   503  	}
   504  }
   505  
   506  func (sd *SpecAnalyser) analyseMetaDataProperty(item1, item2 string, codeIfDiff SpecChangeCode, compatIfDiff Compatibility) {
   507  	if item1 != item2 {
   508  		diffSpec := fmt.Sprintf("%s -> %s", item1, item2)
   509  		sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: DifferenceLocation{Node: &Node{Field: "Spec Metadata"}}, Code: codeIfDiff, Compatibility: compatIfDiff, DiffInfo: diffSpec})
   510  	}
   511  }
   512  
   513  func (sd *SpecAnalyser) schemaFromRef(ref spec.Ref, defns *spec.Definitions) (actualSchema *spec.Schema, definitionName string) {
   514  	definitionName = definitionFromRef(ref)
   515  	foundSchema, ok := (*defns)[definitionName]
   516  	if !ok {
   517  		return nil, definitionName
   518  	}
   519  	sd.ReferencedDefinitions[definitionName] = true
   520  	actualSchema = &foundSchema
   521  	return
   522  
   523  }
   524  
   525  // PropertyDefn combines a property with its required-ness
   526  type PropertyDefn struct {
   527  	Schema   *spec.Schema
   528  	Required bool
   529  }
   530  
   531  // PropertyMap a unified map including all AllOf fields
   532  type PropertyMap map[string]PropertyDefn