github.com/thetreep/go-swagger@v0.0.0-20240223100711-35af64f14f01/cmd/swagger/commands/diff/checks.go (about)

     1  package diff
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/go-openapi/spec"
     8  )
     9  
    10  // CompareEnums returns added, deleted enum values
    11  func CompareEnums(left, right []interface{}) []TypeDiff {
    12  	diffs := []TypeDiff{}
    13  
    14  	leftStrs := []string{}
    15  	rightStrs := []string{}
    16  	for _, eachLeft := range left {
    17  		leftStrs = append(leftStrs, fmt.Sprintf("%v", eachLeft))
    18  	}
    19  	for _, eachRight := range right {
    20  		rightStrs = append(rightStrs, fmt.Sprintf("%v", eachRight))
    21  	}
    22  	added, deleted, _ := fromStringArray(leftStrs).DiffsTo(rightStrs)
    23  	if len(added) > 0 {
    24  		typeChange := strings.Join(added, ",")
    25  		diffs = append(diffs, TypeDiff{Change: AddedEnumValue, Description: typeChange})
    26  	}
    27  	if len(deleted) > 0 {
    28  		typeChange := strings.Join(deleted, ",")
    29  		diffs = append(diffs, TypeDiff{Change: DeletedEnumValue, Description: typeChange})
    30  	}
    31  
    32  	return diffs
    33  }
    34  
    35  // CompareProperties recursive property comparison
    36  func CompareProperties(location DifferenceLocation, schema1 *spec.Schema, schema2 *spec.Schema, getRefFn1 SchemaFromRefFn, getRefFn2 SchemaFromRefFn, cmp CompareSchemaFn) []SpecDifference {
    37  	propDiffs := []SpecDifference{}
    38  
    39  	if schema1.Properties == nil && schema2.Properties == nil {
    40  		return propDiffs
    41  	}
    42  
    43  	schema1Props := propertiesFor(schema1, getRefFn1)
    44  	schema2Props := propertiesFor(schema2, getRefFn2)
    45  
    46  	// find deleted and changed properties
    47  	for eachProp1Name, eachProp1 := range schema1Props {
    48  		eachProp1 := eachProp1
    49  		childLoc := addChildDiffNode(location, eachProp1Name, eachProp1.Schema)
    50  
    51  		if eachProp2, ok := schema2Props[eachProp1Name]; ok {
    52  			diffs := CheckToFromRequired(eachProp1.Required, eachProp2.Required)
    53  			if len(diffs) > 0 {
    54  				for _, diff := range diffs {
    55  					propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: diff.Change})
    56  				}
    57  			}
    58  			cmp(childLoc, eachProp1.Schema, eachProp2.Schema)
    59  		} else {
    60  			propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: DeletedProperty})
    61  		}
    62  	}
    63  
    64  	// find added properties
    65  	for eachProp2Name, eachProp2 := range schema2.Properties {
    66  		eachProp2 := eachProp2
    67  		if _, ok := schema1.Properties[eachProp2Name]; !ok {
    68  			childLoc := addChildDiffNode(location, eachProp2Name, &eachProp2)
    69  
    70  			analyzedProp2 := schema2Props[eachProp2Name]
    71  			if analyzedProp2.Required {
    72  				propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: AddedRequiredProperty})
    73  			} else {
    74  				propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: AddedProperty})
    75  			}
    76  		}
    77  	}
    78  	return propDiffs
    79  
    80  }
    81  
    82  // CompareFloatValues compares a float data item
    83  func CompareFloatValues(fieldName string, val1 *float64, val2 *float64, ifGreaterCode SpecChangeCode, ifLessCode SpecChangeCode) []TypeDiff {
    84  	diffs := []TypeDiff{}
    85  	if val1 != nil && val2 != nil {
    86  		if *val2 > *val1 {
    87  			diffs = append(diffs, TypeDiff{Change: ifGreaterCode, Description: fmt.Sprintf("%s %f->%f", fieldName, *val1, *val2)})
    88  		} else if *val2 < *val1 {
    89  			diffs = append(diffs, TypeDiff{Change: ifLessCode, Description: fmt.Sprintf("%s %f->%f", fieldName, *val1, *val2)})
    90  		}
    91  	} else {
    92  		if val1 != val2 {
    93  			if val1 != nil {
    94  				diffs = append(diffs, TypeDiff{Change: DeletedConstraint, Description: fmt.Sprintf("%s(%f)", fieldName, *val1)})
    95  			} else {
    96  				diffs = append(diffs, TypeDiff{Change: AddedConstraint, Description: fmt.Sprintf("%s(%f)", fieldName, *val2)})
    97  			}
    98  		}
    99  	}
   100  	return diffs
   101  }
   102  
   103  // CompareIntValues compares to int data items
   104  func CompareIntValues(fieldName string, val1 *int64, val2 *int64, ifGreaterCode SpecChangeCode, ifLessCode SpecChangeCode) []TypeDiff {
   105  	diffs := []TypeDiff{}
   106  	if val1 != nil && val2 != nil {
   107  		if *val2 > *val1 {
   108  			diffs = append(diffs, TypeDiff{Change: ifGreaterCode, Description: fmt.Sprintf("%s %d->%d", fieldName, *val1, *val2)})
   109  		} else if *val2 < *val1 {
   110  			diffs = append(diffs, TypeDiff{Change: ifLessCode, Description: fmt.Sprintf("%s %d->%d", fieldName, *val1, *val2)})
   111  		}
   112  	} else {
   113  		if val1 != val2 {
   114  			if val1 != nil {
   115  				diffs = append(diffs, TypeDiff{Change: DeletedConstraint, Description: fmt.Sprintf("%s(%d)", fieldName, *val1)})
   116  			} else {
   117  				diffs = append(diffs, TypeDiff{Change: AddedConstraint, Description: fmt.Sprintf("%s(%d)", fieldName, *val2)})
   118  			}
   119  		}
   120  	}
   121  	return diffs
   122  }
   123  
   124  // CheckToFromPrimitiveType check for diff to or from a primitive
   125  func CheckToFromPrimitiveType(diffs []TypeDiff, type1, type2 interface{}) []TypeDiff {
   126  
   127  	type1IsPrimitive := isPrimitive(type1)
   128  	type2IsPrimitive := isPrimitive(type2)
   129  
   130  	// Primitive to Obj or Obj to Primitive
   131  	if type1IsPrimitive != type2IsPrimitive {
   132  		typeStr1, isarray1 := getSchemaType(type1)
   133  		typeStr2, isarray2 := getSchemaType(type2)
   134  		return addTypeDiff(diffs, TypeDiff{Change: ChangedType, FromType: formatTypeString(typeStr1, isarray1), ToType: formatTypeString(typeStr2, isarray2)})
   135  	}
   136  
   137  	return diffs
   138  }
   139  
   140  // CheckRefChange has the property ref changed
   141  func CheckRefChange(diffs []TypeDiff, type1, type2 interface{}) (diffReturn []TypeDiff) {
   142  
   143  	diffReturn = diffs
   144  	if isRefType(type1) && isRefType(type2) {
   145  		// both refs but to different objects (TODO detect renamed object)
   146  		ref1 := definitionFromRef(getRef(type1))
   147  		ref2 := definitionFromRef(getRef(type2))
   148  		if ref1 != ref2 {
   149  			diffReturn = addTypeDiff(diffReturn, TypeDiff{Change: RefTargetChanged, FromType: getSchemaTypeStr(type1), ToType: getSchemaTypeStr(type2)})
   150  		}
   151  	} else if isRefType(type1) != isRefType(type2) {
   152  		diffReturn = addTypeDiff(diffReturn, TypeDiff{Change: ChangedType, FromType: getSchemaTypeStr(type1), ToType: getSchemaTypeStr(type2)})
   153  	}
   154  	return
   155  }
   156  
   157  // checkNumericTypeChanges checks for changes to or from a numeric type
   158  func checkNumericTypeChanges(diffs []TypeDiff, type1, type2 *spec.SchemaProps) []TypeDiff {
   159  	// Number
   160  	_, type1IsNumeric := numberWideness[type1.Type[0]]
   161  	_, type2IsNumeric := numberWideness[type2.Type[0]]
   162  
   163  	if type1IsNumeric && type2IsNumeric {
   164  		foundDiff := false
   165  		if type1.ExclusiveMaximum && !type2.ExclusiveMaximum {
   166  			diffs = addTypeDiff(diffs, TypeDiff{Change: WidenedType, Description: fmt.Sprintf("Exclusive Maximum Removed:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
   167  			foundDiff = true
   168  		}
   169  		if !type1.ExclusiveMaximum && type2.ExclusiveMaximum {
   170  			diffs = addTypeDiff(diffs, TypeDiff{Change: NarrowedType, Description: fmt.Sprintf("Exclusive Maximum Added:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
   171  			foundDiff = true
   172  		}
   173  		if type1.ExclusiveMinimum && !type2.ExclusiveMinimum {
   174  			diffs = addTypeDiff(diffs, TypeDiff{Change: WidenedType, Description: fmt.Sprintf("Exclusive Minimum Removed:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
   175  			foundDiff = true
   176  		}
   177  		if !type1.ExclusiveMinimum && type2.ExclusiveMinimum {
   178  			diffs = addTypeDiff(diffs, TypeDiff{Change: NarrowedType, Description: fmt.Sprintf("Exclusive Minimum Added:%v->%v", type1.ExclusiveMinimum, type2.ExclusiveMinimum)})
   179  			foundDiff = true
   180  		}
   181  		if !foundDiff {
   182  			maxDiffs := CompareFloatValues("Maximum", type1.Maximum, type2.Maximum, WidenedType, NarrowedType)
   183  			diffs = append(diffs, maxDiffs...)
   184  			minDiffs := CompareFloatValues("Minimum", type1.Minimum, type2.Minimum, NarrowedType, WidenedType)
   185  			diffs = append(diffs, minDiffs...)
   186  		}
   187  	}
   188  	return diffs
   189  }
   190  
   191  // CheckStringTypeChanges checks for changes to or from a string type
   192  func CheckStringTypeChanges(diffs []TypeDiff, type1, type2 *spec.SchemaProps) []TypeDiff {
   193  	// string changes
   194  	if type1.Type[0] == StringType &&
   195  		type2.Type[0] == StringType {
   196  		minLengthDiffs := CompareIntValues("MinLength", type1.MinLength, type2.MinLength, NarrowedType, WidenedType)
   197  		diffs = append(diffs, minLengthDiffs...)
   198  		maxLengthDiffs := CompareIntValues("MaxLength", type1.MinLength, type2.MinLength, WidenedType, NarrowedType)
   199  		diffs = append(diffs, maxLengthDiffs...)
   200  		if type1.Pattern != type2.Pattern {
   201  			diffs = addTypeDiff(diffs, TypeDiff{Change: ChangedType, Description: fmt.Sprintf("Pattern Changed:%s->%s", type1.Pattern, type2.Pattern)})
   202  		}
   203  		if type1.Type[0] == StringType {
   204  			if len(type1.Enum) > 0 {
   205  				enumDiffs := CompareEnums(type1.Enum, type2.Enum)
   206  				diffs = append(diffs, enumDiffs...)
   207  			}
   208  		}
   209  	}
   210  	return diffs
   211  }
   212  
   213  // CheckToFromRequired checks for changes to or from a required property
   214  func CheckToFromRequired(required1, required2 bool) (diffs []TypeDiff) {
   215  	if required1 != required2 {
   216  		code := ChangedOptionalToRequired
   217  		if required1 {
   218  			code = ChangedRequiredToOptional
   219  		}
   220  		diffs = addTypeDiff(diffs, TypeDiff{Change: code})
   221  	}
   222  	return diffs
   223  }
   224  
   225  const objType = "object"
   226  
   227  func getTypeHierarchyChange(type1, type2 string) TypeDiff {
   228  	fromType := type1
   229  	if fromType == "" {
   230  		fromType = objType
   231  	}
   232  	toType := type2
   233  	if toType == "" {
   234  		toType = objType
   235  	}
   236  	diffDescription := fmt.Sprintf("%s -> %s", fromType, toType)
   237  	if isStringType(type1) && !isStringType(type2) {
   238  		return TypeDiff{Change: NarrowedType, Description: diffDescription}
   239  	}
   240  	if !isStringType(type1) && isStringType(type2) {
   241  		return TypeDiff{Change: WidenedType, Description: diffDescription}
   242  	}
   243  	type1Wideness, type1IsNumeric := numberWideness[type1]
   244  	type2Wideness, type2IsNumeric := numberWideness[type2]
   245  	if type1IsNumeric && type2IsNumeric {
   246  		if type1Wideness == type2Wideness {
   247  			return TypeDiff{Change: ChangedToCompatibleType, Description: diffDescription}
   248  		}
   249  		if type1Wideness > type2Wideness {
   250  			return TypeDiff{Change: NarrowedType, Description: diffDescription}
   251  		}
   252  		if type1Wideness < type2Wideness {
   253  			return TypeDiff{Change: WidenedType, Description: diffDescription}
   254  		}
   255  	}
   256  	return TypeDiff{Change: ChangedType, Description: diffDescription}
   257  }
   258  
   259  func isRefType(item interface{}) bool {
   260  	switch s := item.(type) {
   261  	case spec.Refable:
   262  		return s.Ref.String() != ""
   263  	case *spec.Schema:
   264  		return s.Ref.String() != ""
   265  	case *spec.SchemaProps:
   266  		return s.Ref.String() != ""
   267  	case *spec.SimpleSchema:
   268  		return false
   269  	default:
   270  		return false
   271  	}
   272  }