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 }