diff --git a/datamodel/high/base/schema.go b/datamodel/high/base/schema.go index 76e13d3..15f7bdc 100644 --- a/datamodel/high/base/schema.go +++ b/datamodel/high/base/schema.go @@ -26,12 +26,12 @@ type DynamicValue[A any, B any] struct { } // IsA will return true if the 'A' or left value is set. (OpenAPI 3) -func (s DynamicValue[A, B]) IsA() bool { +func (s *DynamicValue[A, B]) IsA() bool { return s.N == 0 } // IsB will return true if the 'B' or right value is set (OpenAPI 3.1) -func (s DynamicValue[A, B]) IsB() bool { +func (s *DynamicValue[A, B]) IsB() bool { return s.N == 1 } diff --git a/datamodel/high/base/schema_test.go b/datamodel/high/base/schema_test.go index ae8fda6..4acdca6 100644 --- a/datamodel/high/base/schema_test.go +++ b/datamodel/high/base/schema_test.go @@ -14,6 +14,12 @@ import ( "gopkg.in/yaml.v3" ) +func TestDynamicValue_IsA(t *testing.T) { + dv := &DynamicValue[int, bool]{N: 0, A: 23} + assert.True(t, dv.IsA()) + assert.False(t, dv.IsB()) +} + func TestNewSchemaProxy(t *testing.T) { // check proxy @@ -541,6 +547,16 @@ exclusiveMaximum: 5 assert.EqualValues(t, value, highSchema.ExclusiveMaximum.B) } +func TestSchema_Items_Boolean(t *testing.T) { + yml := ` +type: number +items: true +` + highSchema := getHighSchema(t, yml) + + assert.True(t, highSchema.Items.B) +} + func TestSchemaExamples(t *testing.T) { yml := ` type: number diff --git a/datamodel/low/base/schema.go b/datamodel/low/base/schema.go index ad45f32..df0fe41 100644 --- a/datamodel/low/base/schema.go +++ b/datamodel/low/base/schema.go @@ -684,17 +684,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { } } - /* - If low.NodeReference[*SchemaProxy] - Else low.NodeReference[*SchemaProxy] - Then low.NodeReference[*SchemaProxy] - PropertyNames low.NodeReference[*SchemaProxy] - UnevaluatedItems low.NodeReference[*SchemaProxy] - UnevaluatedProperties low.NodeReference[*SchemaProxy] - */ - var allOf, anyOf, oneOf, prefixItems []low.ValueReference[*SchemaProxy] - var items, not, contains, sif, selse, sthen, propertyNames, unevalItems, unevalProperties low.ValueReference[*SchemaProxy] _, allOfLabel, allOfValue := utils.FindKeyNodeFullTop(AllOfLabel, root.Content) diff --git a/datamodel/low/base/schema_test.go b/datamodel/low/base/schema_test.go index 28d2f47..8409a42 100644 --- a/datamodel/low/base/schema_test.go +++ b/datamodel/low/base/schema_test.go @@ -361,6 +361,7 @@ exclusiveMinimum: 12 exclusiveMaximum: 13 contentEncoding: fish64 contentMediaType: fish/paste +items: true examples: - testing` @@ -387,6 +388,8 @@ examples: assert.Equal(t, "testing", sch.Examples.Value[0].Value) assert.Equal(t, "fish64", sch.ContentEncoding.Value) assert.Equal(t, "fish/paste", sch.ContentMediaType.Value) + assert.True(t, sch.Items.Value.IsB()) + assert.True(t, sch.Items.Value.B) } @@ -563,6 +566,60 @@ properties: } +func TestSchema_Build_DependentSchemas_Fail(t *testing.T) { + + yml := `components: + schemas: + Something: + description: this is something + type: string` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(yml), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml = `type: object +dependentSchemas: + aValue: + $ref: '#/bork'` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + var n Schema + err := n.Build(idxNode.Content[0], idx) + assert.Error(t, err) + +} + +func TestSchema_Build_PatternProperties_Fail(t *testing.T) { + + yml := `components: + schemas: + Something: + description: this is something + type: string` + + var iNode yaml.Node + mErr := yaml.Unmarshal([]byte(yml), &iNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&iNode) + + yml = `type: object +patternProperties: + aValue: + $ref: '#/bork'` + + var idxNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &idxNode) + + var n Schema + err := n.Build(idxNode.Content[0], idx) + assert.Error(t, err) + +} + func Test_Schema_Polymorphism_Array_Ref(t *testing.T) { yml := `components: @@ -1465,6 +1522,9 @@ func TestSchema_Hash_NotEqual(t *testing.T) { left := `schema: title: an OK message - but different + items: true + minContains: 3 + maxContains: 22 properties: propA: title: a proxy property @@ -1472,6 +1532,9 @@ func TestSchema_Hash_NotEqual(t *testing.T) { right := `schema: title: an OK message + items: false + minContains: 2 + maxContains: 10 properties: propA: title: a proxy property diff --git a/what-changed/model/parameter.go b/what-changed/model/parameter.go index c0dc998..135fa33 100644 --- a/what-changed/model/parameter.go +++ b/what-changed/model/parameter.go @@ -192,7 +192,7 @@ func addCommonParameterProperties(left, right low.SharedParameters, changes *[]* return props } -// CompareParametersV3 is amn OpenAPI type safe proxy for CompareParameters +// CompareParametersV3 is an OpenAPI type safe proxy for CompareParameters func CompareParametersV3(l, r *v3.Parameter) *ParameterChanges { return CompareParameters(l, r) } @@ -306,9 +306,6 @@ func CompareParameters(l, r any) *ParameterChanges { pc.PropertyChanges = NewPropertyChanges(changes) pc.ExtensionChanges = CompareExtensions(lext, rext) - if pc.TotalChanges() <= 0 { - return nil - } return pc } diff --git a/what-changed/model/parameter_test.go b/what-changed/model/parameter_test.go index 94a868f..4649110 100644 --- a/what-changed/model/parameter_test.go +++ b/what-changed/model/parameter_test.go @@ -52,7 +52,7 @@ func TestCompareParameters_V3(t *testing.T) { _ = rDoc.Build(rNode.Content[0], nil) // compare. - extChanges := CompareParameters(&lDoc, &rDoc) + extChanges := CompareParametersV3(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) } diff --git a/what-changed/model/schema.go b/what-changed/model/schema.go index 2fe4420..2f78eab 100644 --- a/what-changed/model/schema.go +++ b/what-changed/model/schema.go @@ -268,7 +268,14 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges { // now for the confusing part, there is also a schema's 'properties' property to parse. // inception, eat your heart out. doneChan := make(chan bool) - totalProperties := checkPropertiesPropertyOfASchema(lSchema, rSchema, &changes, sc, doneChan) + props, totalProperties := checkMappedSchemaOfASchema(lSchema.Properties.Value, rSchema.Properties.Value, &changes, doneChan) + sc.SchemaPropertyChanges = props + + deps, depsTotal := checkMappedSchemaOfASchema(lSchema.DependentSchemas.Value, rSchema.DependentSchemas.Value, &changes, doneChan) + sc.DependentSchemasChanges = deps + + patterns, patternsTotal := checkMappedSchemaOfASchema(lSchema.PatternProperties.Value, rSchema.PatternProperties.Value, &changes, doneChan) + sc.PatternPropertiesChanges = patterns // check polymorphic and multi-values async for speed. go extractSchemaChanges(lSchema.OneOf.Value, rSchema.OneOf.Value, v3.OneOfLabel, @@ -280,13 +287,7 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges { go extractSchemaChanges(lSchema.AnyOf.Value, rSchema.AnyOf.Value, v3.AnyOfLabel, &sc.AnyOfChanges, &changes, doneChan) - //go extractSchemaChanges(lSchema.Items.Value, rSchema.Items.Value, v3.ItemsLabel, - // &sc.ItemsChanges, &changes, doneChan) - // - //go extractSchemaChanges(lSchema.Not.Value, rSchema.Not.Value, v3.NotLabel, - // &sc.NotChanges, &changes, doneChan) - - totalChecks := totalProperties + 3 + totalChecks := totalProperties + depsTotal + patternsTotal + 3 completedChecks := 0 for completedChecks < totalChecks { select { @@ -324,12 +325,11 @@ func checkSchemaXML(lSchema *base.Schema, rSchema *base.Schema, changes *[]*Chan } } -func checkPropertiesPropertyOfASchema( - lSchema *base.Schema, - rSchema *base.Schema, +func checkMappedSchemaOfASchema( + lSchema, + rSchema map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy], changes *[]*Change, - sc *SchemaChanges, - doneChan chan bool) int { + doneChan chan bool) (map[string]*SchemaChanges, int) { propChanges := make(map[string]*SchemaChanges) @@ -340,19 +340,24 @@ func checkPropertiesPropertyOfASchema( rEntities := make(map[string]*base.SchemaProxy) rKeyNodes := make(map[string]*yaml.Node) - for w := range lSchema.Properties.Value { + for w := range lSchema { lProps = append(lProps, w.Value) - lEntities[w.Value] = lSchema.Properties.Value[w].Value + lEntities[w.Value] = lSchema[w].Value lKeyNodes[w.Value] = w.KeyNode } - for w := range rSchema.Properties.Value { + for w := range rSchema { rProps = append(rProps, w.Value) - rEntities[w.Value] = rSchema.Properties.Value[w].Value + rEntities[w.Value] = rSchema[w].Value rKeyNodes[w.Value] = w.KeyNode } sort.Strings(lProps) sort.Strings(rProps) + totalProperties := buildProperty(lProps, rProps, lEntities, rEntities, propChanges, doneChan, changes, rKeyNodes, lKeyNodes) + return propChanges, totalProperties +} +func buildProperty(lProps, rProps []string, lEntities, rEntities map[string]*base.SchemaProxy, + propChanges map[string]*SchemaChanges, doneChan chan bool, changes *[]*Change, rKeyNodes, lKeyNodes map[string]*yaml.Node) int { var propLock sync.Mutex checkProperty := func(key string, lp, rp *base.SchemaProxy, propChanges map[string]*SchemaChanges, done chan bool) { if lp != nil && rp != nil { @@ -421,8 +426,6 @@ func checkPropertiesPropertyOfASchema( } } } - - sc.SchemaPropertyChanges = propChanges return totalProperties } diff --git a/what-changed/model/schema_test.go b/what-changed/model/schema_test.go index c0f3e4f..546f4a5 100644 --- a/what-changed/model/schema_test.go +++ b/what-changed/model/schema_test.go @@ -544,6 +544,318 @@ components: assert.Equal(t, v3.PropertiesLabel, changes.Changes[0].Property) } +func TestCompareSchemas_If(t *testing.T) { + left := `openapi: 3.1 +components: + schemas: + OK: + if: + type: string` + + right := `openapi: 3.1 +components: + schemas: + OK: + if: + type: int` + + leftDoc, rightDoc := test_BuildDoc(left, right) + + // extract left reference schema and non reference schema. + lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value + rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value + + changes := CompareSchemas(lSchemaProxy, rSchemaProxy) + assert.NotNil(t, changes) + assert.Equal(t, 1, changes.TotalChanges()) + assert.Equal(t, 1, changes.TotalBreakingChanges()) + assert.Equal(t, 1, changes.IfChanges.PropertyChanges.TotalChanges()) +} + +func TestCompareSchemas_If_Added(t *testing.T) { + left := `openapi: 3.1 +components: + schemas: + OK: + type: string` + + right := `openapi: 3.1 +components: + schemas: + OK: + type: string + if: + type: int` + + leftDoc, rightDoc := test_BuildDoc(left, right) + + // extract left reference schema and non reference schema. + lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value + rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value + + changes := CompareSchemas(lSchemaProxy, rSchemaProxy) + assert.NotNil(t, changes) + assert.Equal(t, 1, changes.TotalChanges()) + assert.Equal(t, 1, changes.TotalBreakingChanges()) + assert.Equal(t, v3.IfLabel, changes.Changes[0].Property) +} + +func TestCompareSchemas_If_Removed(t *testing.T) { + left := `openapi: 3.1 +components: + schemas: + OK: + type: string` + + right := `openapi: 3.1 +components: + schemas: + OK: + type: string + if: + type: int` + + leftDoc, rightDoc := test_BuildDoc(left, right) + + // extract left reference schema and non reference schema. + lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value + rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value + + changes := CompareSchemas(rSchemaProxy, lSchemaProxy) + assert.NotNil(t, changes) + assert.Equal(t, 1, changes.TotalChanges()) + assert.Equal(t, 1, changes.TotalBreakingChanges()) + assert.Equal(t, v3.IfLabel, changes.Changes[0].Property) +} + +func TestCompareSchemas_Else(t *testing.T) { + left := `openapi: 3.1 +components: + schemas: + OK: + else: + type: string` + + right := `openapi: 3.1 +components: + schemas: + OK: + else: + type: int` + + leftDoc, rightDoc := test_BuildDoc(left, right) + + // extract left reference schema and non reference schema. + lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value + rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value + + changes := CompareSchemas(lSchemaProxy, rSchemaProxy) + assert.NotNil(t, changes) + assert.Equal(t, 1, changes.TotalChanges()) + assert.Equal(t, 1, changes.TotalBreakingChanges()) + assert.Equal(t, 1, changes.ElseChanges.PropertyChanges.TotalChanges()) +} + +func TestCompareSchemas_Else_Added(t *testing.T) { + left := `openapi: 3.1 +components: + schemas: + OK: + type: string` + + right := `openapi: 3.1 +components: + schemas: + OK: + type: string + else: + type: int` + + leftDoc, rightDoc := test_BuildDoc(left, right) + + // extract left reference schema and non reference schema. + lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value + rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value + + changes := CompareSchemas(lSchemaProxy, rSchemaProxy) + assert.NotNil(t, changes) + assert.Equal(t, 1, changes.TotalChanges()) + assert.Equal(t, 1, changes.TotalBreakingChanges()) + assert.Equal(t, v3.ElseLabel, changes.Changes[0].Property) +} + +func TestCompareSchemas_Else_Removed(t *testing.T) { + left := `openapi: 3.1 +components: + schemas: + OK: + type: string` + + right := `openapi: 3.1 +components: + schemas: + OK: + type: string + else: + type: int` + + leftDoc, rightDoc := test_BuildDoc(left, right) + + // extract left reference schema and non reference schema. + lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value + rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value + + changes := CompareSchemas(rSchemaProxy, lSchemaProxy) + assert.NotNil(t, changes) + assert.Equal(t, 1, changes.TotalChanges()) + assert.Equal(t, 1, changes.TotalBreakingChanges()) + assert.Equal(t, v3.ElseLabel, changes.Changes[0].Property) +} + +func TestCompareSchemas_Then(t *testing.T) { + left := `openapi: 3.1 +components: + schemas: + OK: + then: + type: string` + + right := `openapi: 3.1 +components: + schemas: + OK: + then: + type: int` + + leftDoc, rightDoc := test_BuildDoc(left, right) + + // extract left reference schema and non reference schema. + lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value + rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value + + changes := CompareSchemas(lSchemaProxy, rSchemaProxy) + assert.NotNil(t, changes) + assert.Equal(t, 1, changes.TotalChanges()) + assert.Equal(t, 1, changes.TotalBreakingChanges()) + assert.Equal(t, 1, changes.ThenChanges.PropertyChanges.TotalChanges()) +} + +func TestCompareSchemas_Then_Added(t *testing.T) { + left := `openapi: 3.1 +components: + schemas: + OK: + type: string` + + right := `openapi: 3.1 +components: + schemas: + OK: + type: string + then: + type: int` + + leftDoc, rightDoc := test_BuildDoc(left, right) + + // extract left reference schema and non reference schema. + lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value + rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value + + changes := CompareSchemas(lSchemaProxy, rSchemaProxy) + assert.NotNil(t, changes) + assert.Equal(t, 1, changes.TotalChanges()) + assert.Equal(t, 1, changes.TotalBreakingChanges()) + assert.Equal(t, v3.ThenLabel, changes.Changes[0].Property) +} + +func TestCompareSchemas_Then_Removed(t *testing.T) { + left := `openapi: 3.1 +components: + schemas: + OK: + type: string` + + right := `openapi: 3.1 +components: + schemas: + OK: + type: string + then: + type: int` + + leftDoc, rightDoc := test_BuildDoc(left, right) + + // extract left reference schema and non reference schema. + lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value + rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value + + changes := CompareSchemas(rSchemaProxy, lSchemaProxy) + assert.NotNil(t, changes) + assert.Equal(t, 1, changes.TotalChanges()) + assert.Equal(t, 1, changes.TotalBreakingChanges()) + assert.Equal(t, v3.ThenLabel, changes.Changes[0].Property) +} + +func TestCompareSchemas_DependentSchemas(t *testing.T) { + left := `openapi: 3.1 +components: + schemas: + OK: + dependentSchemas: + schemaOne: + type: int` + + right := `openapi: 3.1 +components: + schemas: + OK: + dependentSchemas: + schemaOne: + type: string` + + leftDoc, rightDoc := test_BuildDoc(left, right) + + // extract left reference schema and non reference schema. + lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value + rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value + + changes := CompareSchemas(lSchemaProxy, rSchemaProxy) + assert.NotNil(t, changes) + assert.Equal(t, 1, changes.TotalChanges()) + assert.Equal(t, 1, changes.TotalBreakingChanges()) + assert.Equal(t, 1, changes.DependentSchemasChanges["schemaOne"].PropertyChanges.TotalChanges()) +} + +func TestCompareSchemas_PatternProperties(t *testing.T) { + left := `openapi: 3.1 +components: + schemas: + OK: + patternProperties: + schemaOne: + type: int` + + right := `openapi: 3.1 +components: + schemas: + OK: + patternProperties: + schemaOne: + type: string` + + leftDoc, rightDoc := test_BuildDoc(left, right) + + // extract left reference schema and non reference schema. + lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value + rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value + + changes := CompareSchemas(lSchemaProxy, rSchemaProxy) + assert.NotNil(t, changes) + assert.Equal(t, 1, changes.TotalChanges()) + assert.Equal(t, 1, changes.TotalBreakingChanges()) + assert.Equal(t, 1, changes.PatternPropertiesChanges["schemaOne"].PropertyChanges.TotalChanges()) +} + func TestCompareSchemas_PropertyNames(t *testing.T) { left := `openapi: 3.1 components: