Skip to content

Commit ba327cf

Browse files
committed
Fixed panic for ref combinations
Increased converage to >90% (more to do) Refactored check code to improve readability and testability Signed-off-by: Jason Wraxall <wjase@yahoo.com.au>
1 parent 61d9213 commit ba327cf

13 files changed

+1081
-399
lines changed

diff/checks.go

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package diff
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/go-openapi/spec"
8+
)
9+
10+
func getRef(item interface{}) spec.Ref {
11+
switch s := item.(type) {
12+
case *spec.Refable:
13+
return s.Ref
14+
case *spec.Schema:
15+
return s.Ref
16+
case *spec.SchemaProps:
17+
return s.Ref
18+
default:
19+
return spec.Ref{}
20+
}
21+
}
22+
23+
// CheckToFromArrayType check for changes to or from an Array type
24+
func CheckToFromArrayType(diffs []TypeDiff, type1, type2 interface{}) []TypeDiff {
25+
// Single to Array or Array to Single
26+
typString1, isArray1 := getSchemaType(type1)
27+
typString2, isArray2 := getSchemaType(type2)
28+
29+
if isArray1 != isArray2 {
30+
return addTypeDiff(diffs, TypeDiff{Change: ChangedType, FromType: formatTypeString(typString1, isArray1), ToType: formatTypeString(typString2, isArray2)})
31+
}
32+
33+
return diffs
34+
}
35+
36+
// CheckToFromPrimitiveType check for diff to or from a primitive
37+
func CheckToFromPrimitiveType(diffs []TypeDiff, type1, type2 interface{}) []TypeDiff {
38+
39+
type1IsPrimitive := isPrimitive(type1)
40+
type2IsPrimitive := isPrimitive(type2)
41+
42+
// Primitive to Obj or Obj to Primitive
43+
if type1IsPrimitive != type2IsPrimitive {
44+
typeStr1, isarray1 := getSchemaType(type1)
45+
typeStr2, isarray2 := getSchemaType(type2)
46+
return addTypeDiff(diffs, TypeDiff{Change: ChangedType, FromType: formatTypeString(typeStr1, isarray1), ToType: formatTypeString(typeStr2, isarray2)})
47+
}
48+
49+
return diffs
50+
}
51+
52+
// CheckRefChange has the property ref changed
53+
func CheckRefChange(diffs []TypeDiff, type1, type2 interface{}) (diffReturn []TypeDiff) {
54+
55+
diffReturn = diffs
56+
if isRefType(type1) && isRefType(type2) {
57+
// both refs but to different objects (TODO detect renamed object)
58+
ref1 := definitionFromRef(getRef(type1))
59+
ref2 := definitionFromRef(getRef(type2))
60+
if ref1 != ref2 {
61+
diffReturn = addTypeDiff(diffReturn, TypeDiff{Change: RefTargetChanged, FromType: getSchemaTypeStr(type1), ToType: getSchemaTypeStr(type2)})
62+
}
63+
} else {
64+
if isRefType(type1) != isRefType(type2) {
65+
diffReturn = addTypeDiff(diffReturn, TypeDiff{Change: ChangedType, FromType: getSchemaTypeStr(type1), ToType: getSchemaTypeStr(type2)})
66+
}
67+
}
68+
return
69+
}
70+
71+
func compareEnums(left, right []interface{}) []TypeDiff {
72+
diffs := []TypeDiff{}
73+
74+
leftStrs := []string{}
75+
rightStrs := []string{}
76+
for _, eachLeft := range left {
77+
leftStrs = append(leftStrs, fmt.Sprintf("%v", eachLeft))
78+
}
79+
for _, eachRight := range right {
80+
rightStrs = append(rightStrs, fmt.Sprintf("%v", eachRight))
81+
}
82+
added, deleted, _ := fromStringArray(leftStrs).DiffsTo(rightStrs)
83+
if len(added) > 0 {
84+
typeChange := strings.Join(added, ",")
85+
diffs = append(diffs, TypeDiff{Change: AddedEnumValue, Description: typeChange})
86+
}
87+
if len(deleted) > 0 {
88+
typeChange := strings.Join(deleted, ",")
89+
diffs = append(diffs, TypeDiff{Change: DeletedEnumValue, Description: typeChange})
90+
}
91+
92+
return diffs
93+
}
94+
95+
// checkNumericTypeChanges checks for changes to or from a numeric type
96+
func checkNumericTypeChanges(diffs []TypeDiff, type1, type2 *spec.SchemaProps) []TypeDiff {
97+
// Number
98+
_, type1IsNumeric := numberWideness[type1.Type[0]]
99+
_, type2IsNumeric := numberWideness[type2.Type[0]]
100+
101+
if type1IsNumeric && type2IsNumeric {
102+
foundDiff := false
103+
if type1.ExclusiveMaximum && !type2.ExclusiveMaximum {
104+
diffs = addTypeDiff(diffs, TypeDiff{Change: WidenedType, Description: fmt.Sprintf("Exclusive Maximum Removed:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
105+
foundDiff = true
106+
}
107+
if !type1.ExclusiveMaximum && type2.ExclusiveMaximum {
108+
diffs = addTypeDiff(diffs, TypeDiff{Change: NarrowedType, Description: fmt.Sprintf("Exclusive Maximum Added:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
109+
foundDiff = true
110+
}
111+
if type1.ExclusiveMinimum && !type2.ExclusiveMinimum {
112+
diffs = addTypeDiff(diffs, TypeDiff{Change: WidenedType, Description: fmt.Sprintf("Exclusive Minimum Removed:%v->%v", type1.ExclusiveMaximum, type2.ExclusiveMaximum)})
113+
foundDiff = true
114+
}
115+
if !type1.ExclusiveMinimum && type2.ExclusiveMinimum {
116+
diffs = addTypeDiff(diffs, TypeDiff{Change: NarrowedType, Description: fmt.Sprintf("Exclusive Minimum Added:%v->%v", type1.ExclusiveMinimum, type2.ExclusiveMinimum)})
117+
foundDiff = true
118+
}
119+
if !foundDiff {
120+
diffs = addTypeDiff(diffs, compareFloatValues("Maximum", type1.Maximum, type2.Maximum, WidenedType, NarrowedType))
121+
diffs = addTypeDiff(diffs, compareFloatValues("Minimum", type1.Minimum, type2.Minimum, NarrowedType, WidenedType))
122+
}
123+
}
124+
return diffs
125+
}
126+
127+
// CheckStringTypeChanges checks for changes to or from a string type
128+
func CheckStringTypeChanges(diffs []TypeDiff, type1, type2 *spec.SchemaProps) []TypeDiff {
129+
// string changes
130+
if type1.Type[0] == StringType &&
131+
type2.Type[0] == StringType {
132+
diffs = addTypeDiff(diffs, compareIntValues("MinLength", type1.MinLength, type2.MinLength, NarrowedType, WidenedType))
133+
diffs = addTypeDiff(diffs, compareIntValues("MaxLength", type1.MinLength, type2.MinLength, WidenedType, NarrowedType))
134+
if type1.Pattern != type2.Pattern {
135+
diffs = addTypeDiff(diffs, TypeDiff{Change: ChangedType, Description: fmt.Sprintf("Pattern Changed:%s->%s", type1.Pattern, type2.Pattern)})
136+
}
137+
if type1.Type[0] == StringType {
138+
if len(type1.Enum) > 0 {
139+
enumDiffs := compareEnums(type1.Enum, type2.Enum)
140+
diffs = append(diffs, enumDiffs...)
141+
}
142+
}
143+
}
144+
return diffs
145+
}
146+
147+
// CheckToFromRequired checks for changes to or from a required property
148+
func CheckToFromRequired(required1, required2 bool) (diffs []TypeDiff) {
149+
if required1 != required2 {
150+
code := AddedRequiredProperty
151+
if required1 {
152+
code = ChangedRequiredToOptional
153+
}
154+
diffs = addTypeDiff(diffs, TypeDiff{Change: code})
155+
}
156+
return diffs
157+
}
158+
159+
func compareProperties(location DifferenceLocation, schema1 *spec.Schema, schema2 *spec.Schema, getRefFn1 SchemaFromRefFn, getRefFn2 SchemaFromRefFn, cmp CompareSchemaFn) []SpecDifference {
160+
propDiffs := []SpecDifference{}
161+
162+
requiredProps2 := sliceToStrMap(schema2.Required)
163+
requiredProps1 := sliceToStrMap(schema1.Required)
164+
schema1Props := propertiesFor(schema1, getRefFn1)
165+
schema2Props := propertiesFor(schema2, getRefFn2)
166+
// find deleted and changed properties
167+
168+
for eachProp1Name, eachProp1 := range schema1Props {
169+
eachProp1 := eachProp1
170+
_, required1 := requiredProps1[eachProp1Name]
171+
_, required2 := requiredProps2[eachProp1Name]
172+
childLoc := addChildDiffNode(location, eachProp1Name, &eachProp1)
173+
174+
if eachProp2, ok := schema2Props[eachProp1Name]; ok {
175+
diffs := CheckToFromRequired(required1, required2)
176+
if len(diffs) > 0 {
177+
for _, diff := range diffs {
178+
propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: diff.Change})
179+
}
180+
}
181+
cmp(childLoc, &eachProp1, &eachProp2)
182+
} else {
183+
propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: DeletedProperty})
184+
}
185+
}
186+
187+
// find added properties
188+
for eachProp2Name, eachProp2 := range schema2.Properties {
189+
if _, ok := schema1.Properties[eachProp2Name]; !ok {
190+
childLoc := addChildDiffNode(location, eachProp2Name, &eachProp2)
191+
propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: AddedProperty})
192+
}
193+
}
194+
return propDiffs
195+
196+
}
197+
198+
// SchemaFromRefFn define this to get a schema for a ref
199+
type SchemaFromRefFn func(spec.Ref) (*spec.Schema, string)
200+
201+
func propertiesFor(schema *spec.Schema, getRefFn SchemaFromRefFn) PropertyMap {
202+
schemaFromRef, _ := getRefFn(schema.Ref)
203+
if schemaFromRef != nil {
204+
schema = schemaFromRef
205+
}
206+
props := PropertyMap{}
207+
208+
if schema.Properties != nil {
209+
for name, prop := range schema.Properties {
210+
props[name] = prop
211+
}
212+
}
213+
for _, eachAllOf := range schema.AllOf {
214+
eachAllOf := eachAllOf
215+
eachAllOfActual, _ := getRefFn(eachAllOf.SchemaProps.Ref)
216+
if eachAllOfActual == nil {
217+
eachAllOfActual = &eachAllOf
218+
}
219+
for name, prop := range eachAllOfActual.Properties {
220+
props[name] = prop
221+
}
222+
}
223+
return props
224+
}
225+
226+
func isRefType(item interface{}) bool {
227+
switch s := item.(type) {
228+
case spec.Refable:
229+
return s.Ref.String() != ""
230+
case *spec.Schema:
231+
return s.Ref.String() != ""
232+
case *spec.SchemaProps:
233+
return s.Ref.String() != ""
234+
case *spec.SimpleSchema:
235+
return false
236+
default:
237+
return false
238+
}
239+
}

0 commit comments

Comments
 (0)