Schema what-changed almost complete.

Perhaps the biggest and most complex of all the models to determine what has changed.
This commit is contained in:
Dave Shanley
2022-10-10 12:19:57 -04:00
parent b83e751aa7
commit 61beb3ea2a
7 changed files with 805 additions and 44 deletions

View File

@@ -37,8 +37,10 @@ func (d *Discriminator) FindMappingValue(key string) *low.ValueReference[string]
func (d *Discriminator) Hash() [32]byte { func (d *Discriminator) Hash() [32]byte {
// calculate a hash from every property. // calculate a hash from every property.
f := []string{d.PropertyName.Value} var f []string
if d.PropertyName.Value != "" {
f = append(f, d.PropertyName.Value)
}
propertyKeys := make([]string, 0, len(d.Mapping)) propertyKeys := make([]string, 0, len(d.Mapping))
for i := range d.Mapping { for i := range d.Mapping {
propertyKeys = append(propertyKeys, i.Value) propertyKeys = append(propertyKeys, i.Value)

View File

@@ -258,6 +258,11 @@ func (s *Schema) Hash() [32]byte {
d = append(d, fmt.Sprintf(x, itemsEntities[itemsKeys[k]].Hash())) d = append(d, fmt.Sprintf(x, itemsEntities[itemsKeys[k]].Hash()))
} }
} }
// add extensions to hash
for k := range s.Extensions {
d = append(d, fmt.Sprintf("%v-%x", k.Value, s.Extensions[k].Value))
}
return sha256.Sum256([]byte(strings.Join(d, "|"))) return sha256.Sum256([]byte(strings.Join(d, "|")))
} }
@@ -300,7 +305,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
s.extractExtensions(root) s.extractExtensions(root)
// determine schema type, singular (3.0) or multiple (3.1), use a variable value // determine schema type, singular (3.0) or multiple (3.1), use a variable value
_, typeLabel, typeValue := utils.FindKeyNodeFull(TypeLabel, root.Content) _, typeLabel, typeValue := utils.FindKeyNodeFullTop(TypeLabel, root.Content)
if typeValue != nil { if typeValue != nil {
if utils.IsNodeStringValue(typeValue) { if utils.IsNodeStringValue(typeValue) {
s.Type = low.NodeReference[SchemaDynamicValue[string, []low.ValueReference[string]]]{ s.Type = low.NodeReference[SchemaDynamicValue[string, []low.ValueReference[string]]]{
@@ -327,7 +332,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// determine exclusive minimum type, bool (3.0) or int (3.1) // determine exclusive minimum type, bool (3.0) or int (3.1)
_, exMinLabel, exMinValue := utils.FindKeyNodeFull(ExclusiveMinimumLabel, root.Content) _, exMinLabel, exMinValue := utils.FindKeyNodeFullTop(ExclusiveMinimumLabel, root.Content)
if exMinValue != nil { if exMinValue != nil {
if utils.IsNodeBoolValue(exMinValue) { if utils.IsNodeBoolValue(exMinValue) {
val, _ := strconv.ParseBool(exMinValue.Value) val, _ := strconv.ParseBool(exMinValue.Value)
@@ -348,7 +353,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// determine exclusive maximum type, bool (3.0) or int (3.1) // determine exclusive maximum type, bool (3.0) or int (3.1)
_, exMaxLabel, exMaxValue := utils.FindKeyNodeFull(ExclusiveMaximumLabel, root.Content) _, exMaxLabel, exMaxValue := utils.FindKeyNodeFullTop(ExclusiveMaximumLabel, root.Content)
if exMaxValue != nil { if exMaxValue != nil {
if utils.IsNodeBoolValue(exMaxValue) { if utils.IsNodeBoolValue(exMaxValue) {
val, _ := strconv.ParseBool(exMaxValue.Value) val, _ := strconv.ParseBool(exMaxValue.Value)
@@ -369,7 +374,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle schema reference type if set. (3.1) // handle schema reference type if set. (3.1)
_, schemaRefLabel, schemaRefNode := utils.FindKeyNodeFull(SchemaTypeLabel, root.Content) _, schemaRefLabel, schemaRefNode := utils.FindKeyNodeFullTop(SchemaTypeLabel, root.Content)
if schemaRefNode != nil { if schemaRefNode != nil {
s.SchemaTypeRef = low.NodeReference[string]{ s.SchemaTypeRef = low.NodeReference[string]{
Value: schemaRefNode.Value, KeyNode: schemaRefLabel, ValueNode: schemaRefLabel} Value: schemaRefNode.Value, KeyNode: schemaRefLabel, ValueNode: schemaRefLabel}
@@ -382,7 +387,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle examples if set.(3.1) // handle examples if set.(3.1)
_, expArrLabel, expArrNode := utils.FindKeyNodeFull(ExamplesLabel, root.Content) _, expArrLabel, expArrNode := utils.FindKeyNodeFullTop(ExamplesLabel, root.Content)
if expArrNode != nil { if expArrNode != nil {
if utils.IsNodeArray(expArrNode) { if utils.IsNodeArray(expArrNode) {
var examples []low.ValueReference[any] var examples []low.ValueReference[any]
@@ -397,7 +402,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
} }
_, addPLabel, addPNode := utils.FindKeyNodeFull(AdditionalPropertiesLabel, root.Content) _, addPLabel, addPNode := utils.FindKeyNodeFullTop(AdditionalPropertiesLabel, root.Content)
if addPNode != nil { if addPNode != nil {
if utils.IsNodeMap(addPNode) { if utils.IsNodeMap(addPNode) {
schema, serr := low.ExtractObjectRaw[*Schema](addPNode, idx) schema, serr := low.ExtractObjectRaw[*Schema](addPNode, idx)
@@ -414,7 +419,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle discriminator if set. // handle discriminator if set.
_, discLabel, discNode := utils.FindKeyNodeFull(DiscriminatorLabel, root.Content) _, discLabel, discNode := utils.FindKeyNodeFullTop(DiscriminatorLabel, root.Content)
if discNode != nil { if discNode != nil {
var discriminator Discriminator var discriminator Discriminator
_ = low.BuildModel(discNode, &discriminator) _ = low.BuildModel(discNode, &discriminator)
@@ -422,7 +427,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle externalDocs if set. // handle externalDocs if set.
_, extDocLabel, extDocNode := utils.FindKeyNodeFull(ExternalDocsLabel, root.Content) _, extDocLabel, extDocNode := utils.FindKeyNodeFullTop(ExternalDocsLabel, root.Content)
if extDocNode != nil { if extDocNode != nil {
var exDoc ExternalDoc var exDoc ExternalDoc
_ = low.BuildModel(extDocNode, &exDoc) _ = low.BuildModel(extDocNode, &exDoc)
@@ -431,7 +436,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle xml if set. // handle xml if set.
_, xmlLabel, xmlNode := utils.FindKeyNodeFull(XMLLabel, root.Content) _, xmlLabel, xmlNode := utils.FindKeyNodeFullTop(XMLLabel, root.Content)
if xmlNode != nil { if xmlNode != nil {
var xml XML var xml XML
_ = low.BuildModel(xmlNode, &xml) _ = low.BuildModel(xmlNode, &xml)
@@ -457,7 +462,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle properties // handle properties
_, propLabel, propsNode := utils.FindKeyNodeFull(PropertiesLabel, root.Content) _, propLabel, propsNode := utils.FindKeyNodeFullTop(PropertiesLabel, root.Content)
if propsNode != nil { if propsNode != nil {
propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]) propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*SchemaProxy])
var currentProp *yaml.Node var currentProp *yaml.Node
@@ -498,11 +503,11 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
var allOf, anyOf, oneOf, not, items []low.ValueReference[*SchemaProxy] var allOf, anyOf, oneOf, not, items []low.ValueReference[*SchemaProxy]
_, allOfLabel, allOfValue := utils.FindKeyNodeFull(AllOfLabel, root.Content) _, allOfLabel, allOfValue := utils.FindKeyNodeFullTop(AllOfLabel, root.Content)
_, anyOfLabel, anyOfValue := utils.FindKeyNodeFull(AnyOfLabel, root.Content) _, anyOfLabel, anyOfValue := utils.FindKeyNodeFullTop(AnyOfLabel, root.Content)
_, oneOfLabel, oneOfValue := utils.FindKeyNodeFull(OneOfLabel, root.Content) _, oneOfLabel, oneOfValue := utils.FindKeyNodeFullTop(OneOfLabel, root.Content)
_, notLabel, notValue := utils.FindKeyNodeFull(NotLabel, root.Content) _, notLabel, notValue := utils.FindKeyNodeFullTop(NotLabel, root.Content)
_, itemsLabel, itemsValue := utils.FindKeyNodeFull(ItemsLabel, root.Content) _, itemsLabel, itemsValue := utils.FindKeyNodeFullTop(ItemsLabel, root.Content)
errorChan := make(chan error) errorChan := make(chan error)
allOfChan := make(chan schemaProxyBuildResult) allOfChan := make(chan schemaProxyBuildResult)

View File

@@ -198,3 +198,7 @@ func GetCircularReferenceResult(node *yaml.Node, idx *index.SpecIndex) *index.Ci
} }
return nil return nil
} }
func HashToString(hash [32]byte) string {
return fmt.Sprintf("%x", hash)
}

View File

@@ -90,4 +90,6 @@ const (
DeprecatedLabel = "deprecated" DeprecatedLabel = "deprecated"
ExampleLabel = "example" ExampleLabel = "example"
RefLabel = "$ref" RefLabel = "$ref"
DiscriminatorLabel = "discriminator"
ExternalDocsLabel = "externalDocs"
) )

View File

@@ -240,7 +240,7 @@ func FindKeyNode(key string, nodes []*yaml.Node) (keyNode *yaml.Node, valueNode
return nil, nil return nil, nil
} }
// FindKeyNodeFull is an overloaded version of FindKeyNode. Thins version however returns keys, labels and values. // FindKeyNodeFull is an overloaded version of FindKeyNode. This version however returns keys, labels and values.
// generally different things are required from different node trees, so depending on what this function is looking at // generally different things are required from different node trees, so depending on what this function is looking at
// it will return different things. // it will return different things.
func FindKeyNodeFull(key string, nodes []*yaml.Node) (keyNode *yaml.Node, labelNode *yaml.Node, valueNode *yaml.Node) { func FindKeyNodeFull(key string, nodes []*yaml.Node) (keyNode *yaml.Node, labelNode *yaml.Node, valueNode *yaml.Node) {
@@ -267,6 +267,33 @@ func FindKeyNodeFull(key string, nodes []*yaml.Node) (keyNode *yaml.Node, labelN
return nil, nil, nil return nil, nil, nil
} }
// FindKeyNodeFullTop is an overloaded version of FindKeyNodeFull. This version only looks at the top
// level of the node and not the children.
func FindKeyNodeFullTop(key string, nodes []*yaml.Node) (keyNode *yaml.Node, labelNode *yaml.Node, valueNode *yaml.Node) {
for i := range nodes {
if i%2 == 0 && key == nodes[i].Value {
return nodes[i], nodes[i], nodes[i+1] // next node is what we need.
}
}
for q, v := range nodes {
if q%2 != 0 {
continue
}
if key == v.Value {
if IsNodeMap(v) {
if q+1 == len(v.Content) {
return v, v.Content[q], v.Content[q]
}
return v, v.Content[q], v.Content[q+1]
}
if IsNodeArray(v) {
return v, v.Content[q], v.Content[q]
}
}
}
return nil, nil, nil
}
type ExtensionNode struct { type ExtensionNode struct {
Key *yaml.Node Key *yaml.Node
Value *yaml.Node Value *yaml.Node

View File

@@ -8,6 +8,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"gopkg.in/yaml.v3"
"sort" "sort"
"sync" "sync"
) )
@@ -109,12 +110,6 @@ func (s *SchemaChanges) TotalBreakingChanges() int {
t += s.SchemaPropertyChanges[n].TotalBreakingChanges() t += s.SchemaPropertyChanges[n].TotalBreakingChanges()
} }
} }
if s.ExternalDocChanges != nil {
t += s.ExternalDocChanges.TotalBreakingChanges()
}
if s.ExtensionChanges != nil {
t += s.ExtensionChanges.TotalBreakingChanges()
}
return t return t
} }
@@ -525,26 +520,69 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges {
} }
} }
// Discriminator
if lSchema.Discriminator.Value != nil && rSchema.Discriminator.Value != nil {
// check if hash matches, if not then compare.
if lSchema.Discriminator.Value.Hash() != rSchema.Discriminator.Value.Hash() {
sc.DiscriminatorChanges = CompareDiscriminator(lSchema.Discriminator.Value, rSchema.Discriminator.Value)
}
}
// added Discriminator
if lSchema.Discriminator.Value == nil && rSchema.Discriminator.Value != nil {
CreateChange[*base.Schema](&changes, ObjectAdded, v3.DiscriminatorLabel,
nil, rSchema.Discriminator.ValueNode, true, nil, rSchema.Discriminator.Value)
}
// removed Discriminator
if lSchema.Discriminator.Value != nil && rSchema.Discriminator.Value == nil {
CreateChange[*base.Schema](&changes, ObjectRemoved, v3.DiscriminatorLabel,
lSchema.Discriminator.ValueNode, nil, true, lSchema.Discriminator.Value, nil)
}
// ExternalDocs
if lSchema.ExternalDocs.Value != nil && rSchema.ExternalDocs.Value != nil {
// check if hash matches, if not then compare.
if lSchema.ExternalDocs.Value.Hash() != rSchema.ExternalDocs.Value.Hash() {
sc.ExternalDocChanges = CompareExternalDocs(lSchema.ExternalDocs.Value, rSchema.ExternalDocs.Value)
}
}
// added ExternalDocs
if lSchema.ExternalDocs.Value == nil && rSchema.ExternalDocs.Value != nil {
CreateChange[*base.Schema](&changes, ObjectAdded, v3.ExternalDocsLabel,
nil, rSchema.ExternalDocs.ValueNode, false, nil, rSchema.ExternalDocs.Value)
}
// removed ExternalDocs
if lSchema.ExternalDocs.Value != nil && rSchema.ExternalDocs.Value == nil {
CreateChange[*base.Schema](&changes, ObjectRemoved, v3.ExternalDocsLabel,
lSchema.ExternalDocs.ValueNode, nil, false, lSchema.ExternalDocs.Value, nil)
}
// check extensions
sc.ExtensionChanges = CompareExtensions(lSchema.Extensions, rSchema.Extensions)
// check core properties // check core properties
CheckProperties(props) CheckProperties(props)
propChanges := make(map[string]*SchemaChanges) propChanges := make(map[string]*SchemaChanges)
lProps := make([]string, len(lSchema.Properties.Value)) var lProps []string
lEntities := make(map[string]*base.SchemaProxy) lEntities := make(map[string]*base.SchemaProxy)
rProps := make([]string, len(rSchema.Properties.Value)) lKeyNodes := make(map[string]*yaml.Node)
var rProps []string
rEntities := make(map[string]*base.SchemaProxy) rEntities := make(map[string]*base.SchemaProxy)
rKeyNodes := make(map[string]*yaml.Node)
for w := range lSchema.Properties.Value { for w := range lSchema.Properties.Value {
if !lSchema.Properties.Value[w].Value.IsSchemaReference() { if !lSchema.Properties.Value[w].Value.IsSchemaReference() {
lProps = append(lProps, w.Value) lProps = append(lProps, w.Value)
lEntities[w.Value] = lSchema.Properties.Value[w].Value lEntities[w.Value] = lSchema.Properties.Value[w].Value
lKeyNodes[w.Value] = w.KeyNode
} }
} }
for w := range rSchema.Properties.Value { for w := range rSchema.Properties.Value {
if !rSchema.Properties.Value[w].Value.IsSchemaReference() { if !rSchema.Properties.Value[w].Value.IsSchemaReference() {
rProps = append(rProps, w.Value) rProps = append(rProps, w.Value)
rEntities[w.Value] = rSchema.Properties.Value[w].Value rEntities[w.Value] = rSchema.Properties.Value[w].Value
rKeyNodes[w.Value] = w.KeyNode
} }
} }
sort.Strings(lProps) sort.Strings(lProps)
@@ -583,9 +621,10 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges {
// old removed, new added. // old removed, new added.
CreateChange[*base.Schema](&changes, ObjectAdded, v3.PropertiesLabel, CreateChange[*base.Schema](&changes, ObjectAdded, v3.PropertiesLabel,
nil, rEntities[rProps[w]].GetValueNode(), false, nil, rEntities[rProps[w]]) nil, rKeyNodes[rProps[w]], false, nil, rEntities[rProps[w]])
CreateChange[*base.Schema](&changes, ObjectRemoved, v3.PropertiesLabel, CreateChange[*base.Schema](&changes, ObjectRemoved, v3.PropertiesLabel,
lEntities[lProps[w]].GetValueNode(), nil, true, lEntities[lProps[w]], nil) lKeyNodes[lProps[w]], nil, true, lEntities[lProps[w]], nil)
} }
} }
@@ -600,7 +639,7 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges {
} }
if w >= len(rProps) { if w >= len(rProps) {
CreateChange[*base.Schema](&changes, ObjectRemoved, v3.PropertiesLabel, CreateChange[*base.Schema](&changes, ObjectRemoved, v3.PropertiesLabel,
lEntities[lProps[w]].GetValueNode(), nil, true, lEntities[lProps[w]], nil) lKeyNodes[lProps[w]], nil, true, lEntities[lProps[w]], nil)
} }
} }
} }
@@ -612,9 +651,9 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges {
totalProperties++ totalProperties++
go checkProperty(rProps[w], lEntities[lProps[w]], rEntities[rProps[w]], propChanges, doneChan) go checkProperty(rProps[w], lEntities[lProps[w]], rEntities[rProps[w]], propChanges, doneChan)
} }
if w >= len(rProps) { if w >= len(lProps) {
CreateChange[*base.Schema](&changes, ObjectAdded, v3.PropertiesLabel, CreateChange[*base.Schema](&changes, ObjectAdded, v3.PropertiesLabel,
nil, rEntities[rProps[w]].GetValueNode(), false, nil, rEntities[rProps[w]]) nil, rKeyNodes[rProps[w]], false, nil, rEntities[rProps[w]])
} }
} }
} }
@@ -634,8 +673,8 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges {
go extractSchemaChanges(lSchema.Items.Value, rSchema.Items.Value, v3.ItemsLabel, go extractSchemaChanges(lSchema.Items.Value, rSchema.Items.Value, v3.ItemsLabel,
&sc.ItemsChanges, &changes, doneChan) &sc.ItemsChanges, &changes, doneChan)
go extractSchemaChanges(lSchema.Not.Value, rSchema.Not.Value, v3.ItemsLabel, go extractSchemaChanges(lSchema.Not.Value, rSchema.Not.Value, v3.NotLabel,
&sc.ItemsChanges, &changes, doneChan) &sc.NotChanges, &changes, doneChan)
totalChecks := totalProperties + 5 totalChecks := totalProperties + 5
completedChecks := 0 completedChecks := 0
@@ -645,16 +684,12 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges {
completedChecks++ completedChecks++
} }
} }
} }
// done // done
sc.Changes = changes if changes != nil {
if sc.TotalChanges() <= 0 { sc.Changes = changes
return nil
} }
return sc return sc
} }
func extractSchemaChanges( func extractSchemaChanges(
@@ -696,11 +731,6 @@ func extractSchemaChanges(
} }
} }
if len(lKeys) <= 0 && len(rKeys) <= 0 {
done <- true
return
}
// sort slices so that like for like is all sequenced. // sort slices so that like for like is all sequenced.
sort.Strings(lKeys) sort.Strings(lKeys)
sort.Strings(rKeys) sort.Strings(rKeys)

View File

@@ -5,6 +5,7 @@ package what_changed
import ( import (
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -286,3 +287,693 @@ func TestCompareSchemas_Identical(t *testing.T) {
changes := CompareSchemas(rSchemaProxy, lSchemaProxy) changes := CompareSchemas(rSchemaProxy, lSchemaProxy)
assert.Nil(t, changes) assert.Nil(t, changes)
} }
func TestCompareSchemas_RequiredAdded(t *testing.T) {
left := `components:
schemas:
OK:
title: an OK message
description: a thing
required:
- one`
right := `components:
schemas:
OK:
title: an OK message
description: a thing
required:
- one
- two`
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.Len(t, changes.Changes, 1)
assert.Equal(t, PropertyAdded, changes.Changes[0].ChangeType)
assert.Equal(t, "two", changes.Changes[0].New)
assert.Equal(t, v3.RequiredLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_RequiredRemoved(t *testing.T) {
left := `components:
schemas:
OK:
required:
- one`
right := `components:
schemas:
OK:
required:
- one
- two`
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.Len(t, changes.Changes, 1)
assert.Equal(t, PropertyRemoved, changes.Changes[0].ChangeType)
assert.Equal(t, "two", changes.Changes[0].Original)
assert.Equal(t, v3.RequiredLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_EnumAdded(t *testing.T) {
left := `components:
schemas:
OK:
enum: [a,b,c]`
right := `components:
schemas:
OK:
enum: [a,b,c,d]`
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.Len(t, changes.Changes, 1)
assert.Equal(t, PropertyAdded, changes.Changes[0].ChangeType)
assert.Equal(t, "d", changes.Changes[0].New)
assert.Equal(t, v3.EnumLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_EnumRemoved(t *testing.T) {
left := `components:
schemas:
OK:
enum: [a,b,c]`
right := `components:
schemas:
OK:
enum: [a,b,c,d]`
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.Len(t, changes.Changes, 1)
assert.Equal(t, PropertyRemoved, changes.Changes[0].ChangeType)
assert.Equal(t, "d", changes.Changes[0].Original)
assert.Equal(t, v3.EnumLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_PropertyAdded(t *testing.T) {
left := `components:
schemas:
OK:
properties:
propA:
type: int`
right := `components:
schemas:
OK:
properties:
propB:
type: string
propA:
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.Len(t, changes.Changes, 1)
assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType)
assert.Equal(t, "propB", changes.Changes[0].New)
assert.Equal(t, v3.PropertiesLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_PropertyRemoved(t *testing.T) {
left := `components:
schemas:
OK:
properties:
propA:
type: int`
right := `components:
schemas:
OK:
properties:
propB:
type: string
propA:
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.Len(t, changes.Changes, 1)
assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType)
assert.Equal(t, "propB", changes.Changes[0].Original)
assert.Equal(t, v3.PropertiesLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_PropertyChanged(t *testing.T) {
left := `components:
schemas:
OK:
properties:
propA:
type: int`
right := `components:
schemas:
OK:
properties:
propA:
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, Modified, changes.SchemaPropertyChanges["propA"].Changes[0].ChangeType)
assert.Equal(t, "string", changes.SchemaPropertyChanges["propA"].Changes[0].New)
assert.Equal(t, "int", changes.SchemaPropertyChanges["propA"].Changes[0].Original)
}
func TestCompareSchemas_PropertySwap(t *testing.T) {
left := `components:
schemas:
OK:
properties:
propA:
type: int`
right := `components:
schemas:
OK:
properties:
propN:
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, 2, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType)
assert.Equal(t, "propN", changes.Changes[0].New)
assert.Equal(t, v3.PropertiesLabel, changes.Changes[0].Property)
assert.Equal(t, ObjectRemoved, changes.Changes[1].ChangeType)
assert.Equal(t, "propA", changes.Changes[1].Original)
assert.Equal(t, v3.PropertiesLabel, changes.Changes[1].Property)
}
func TestCompareSchemas_AnyOfModifyAndAddItem(t *testing.T) {
left := `components:
schemas:
OK:
anyOf:
- type: bool`
right := `components:
schemas:
OK:
anyOf:
- type: string
- 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, 2, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType)
assert.Equal(t, v3.AnyOfLabel, changes.Changes[0].Property)
assert.Equal(t, Modified, changes.AnyOfChanges[0].Changes[0].ChangeType)
assert.Equal(t, "string", changes.AnyOfChanges[0].Changes[0].New)
assert.Equal(t, "bool", changes.AnyOfChanges[0].Changes[0].Original)
}
func TestCompareSchemas_AnyOfModifyAndRemoveItem(t *testing.T) {
left := `components:
schemas:
OK:
anyOf:
- type: bool`
right := `components:
schemas:
OK:
anyOf:
- type: string
- 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, 2, changes.TotalChanges())
assert.Equal(t, 2, changes.TotalBreakingChanges())
assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType)
assert.Equal(t, v3.AnyOfLabel, changes.Changes[0].Property)
assert.Equal(t, Modified, changes.AnyOfChanges[0].Changes[0].ChangeType)
assert.Equal(t, "bool", changes.AnyOfChanges[0].Changes[0].New)
assert.Equal(t, "string", changes.AnyOfChanges[0].Changes[0].Original)
}
func TestCompareSchemas_AnyOfModified(t *testing.T) {
left := `components:
schemas:
OK:
anyOf:
- type: bool`
right := `components:
schemas:
OK:
anyOf:
- 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, Modified, changes.AnyOfChanges[0].Changes[0].ChangeType)
assert.Equal(t, "string", changes.AnyOfChanges[0].Changes[0].New)
assert.Equal(t, "bool", changes.AnyOfChanges[0].Changes[0].Original)
}
func TestCompareSchemas_OneOfModifyAndAddItem(t *testing.T) {
left := `components:
schemas:
OK:
oneOf:
- type: bool`
right := `components:
schemas:
OK:
oneOf:
- type: string
- 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, 2, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType)
assert.Equal(t, v3.OneOfLabel, changes.Changes[0].Property)
assert.Equal(t, Modified, changes.OneOfChanges[0].Changes[0].ChangeType)
assert.Equal(t, "string", changes.OneOfChanges[0].Changes[0].New)
assert.Equal(t, "bool", changes.OneOfChanges[0].Changes[0].Original)
}
func TestCompareSchemas_AllOfModifyAndAddItem(t *testing.T) {
left := `components:
schemas:
OK:
allOf:
- type: bool`
right := `components:
schemas:
OK:
allOf:
- type: string
- 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, 2, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType)
assert.Equal(t, v3.AllOfLabel, changes.Changes[0].Property)
assert.Equal(t, Modified, changes.AllOfChanges[0].Changes[0].ChangeType)
assert.Equal(t, "string", changes.AllOfChanges[0].Changes[0].New)
assert.Equal(t, "bool", changes.AllOfChanges[0].Changes[0].Original)
}
func TestCompareSchemas_ItemsModifyAndAddItem(t *testing.T) {
left := `components:
schemas:
OK:
type: string
items:
type: bool`
right := `components:
schemas:
OK:
type: string
items:
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, v3.TypeLabel, changes.ItemsChanges[0].Changes[0].Property)
assert.Equal(t, Modified, changes.ItemsChanges[0].Changes[0].ChangeType)
assert.Equal(t, "string", changes.ItemsChanges[0].Changes[0].New)
assert.Equal(t, "bool", changes.ItemsChanges[0].Changes[0].Original)
}
func TestCompareSchemas_ItemsModifyAndAddItemArray(t *testing.T) {
left := `components:
schemas:
OK:
type: string
items:
- type: bool`
right := `components:
schemas:
OK:
type: string
items:
- 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, v3.TypeLabel, changes.ItemsChanges[0].Changes[0].Property)
assert.Equal(t, Modified, changes.ItemsChanges[0].Changes[0].ChangeType)
assert.Equal(t, "string", changes.ItemsChanges[0].Changes[0].New)
assert.Equal(t, "bool", changes.ItemsChanges[0].Changes[0].Original)
}
func TestCompareSchemas_NotModifyAndAddItem(t *testing.T) {
left := `components:
schemas:
OK:
type: string
not:
type: bool`
right := `components:
schemas:
OK:
type: string
not:
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, v3.TypeLabel, changes.NotChanges[0].Changes[0].Property)
assert.Equal(t, Modified, changes.NotChanges[0].Changes[0].ChangeType)
assert.Equal(t, "string", changes.NotChanges[0].Changes[0].New)
assert.Equal(t, "bool", changes.NotChanges[0].Changes[0].Original)
}
func TestCompareSchemas_DiscriminatorChange(t *testing.T) {
left := `components:
schemas:
OK:
type: string
discriminator:
propertyName: melody`
right := `components:
schemas:
OK:
type: string
discriminator:
propertyName: maddox`
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.PropertyNameLabel, changes.DiscriminatorChanges.Changes[0].Property)
assert.Equal(t, Modified, changes.DiscriminatorChanges.Changes[0].ChangeType)
assert.Equal(t, "maddox", changes.DiscriminatorChanges.Changes[0].New)
assert.Equal(t, "melody", changes.DiscriminatorChanges.Changes[0].Original)
}
func TestCompareSchemas_DiscriminatorAdd(t *testing.T) {
left := `components:
schemas:
OK:
type: string`
right := `components:
schemas:
OK:
type: string
discriminator:
propertyName: maddox`
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.DiscriminatorLabel, changes.Changes[0].Property)
assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType)
assert.Equal(t, "0e563831440581c713657dd857a0ec3af1bd7308a43bd3cae9184f61d61b288f",
low.HashToString(changes.Changes[0].NewObject.(*base.Discriminator).Hash()))
}
func TestCompareSchemas_DiscriminatorRemove(t *testing.T) {
left := `components:
schemas:
OK:
type: string`
right := `components:
schemas:
OK:
type: string
discriminator:
propertyName: maddox`
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.DiscriminatorLabel, changes.Changes[0].Property)
assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType)
assert.Equal(t, "0e563831440581c713657dd857a0ec3af1bd7308a43bd3cae9184f61d61b288f",
low.HashToString(changes.Changes[0].OriginalObject.(*base.Discriminator).Hash()))
}
func TestCompareSchemas_ExternalDocsChange(t *testing.T) {
left := `components:
schemas:
OK:
type: string
externalDocs:
url: https://pb33f.io`
right := `components:
schemas:
OK:
type: string
externalDocs:
url: https://pb33f.io/new`
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, 0, changes.TotalBreakingChanges())
assert.Equal(t, v3.URLLabel, changes.ExternalDocChanges.Changes[0].Property)
assert.Equal(t, Modified, changes.ExternalDocChanges.Changes[0].ChangeType)
assert.Equal(t, "https://pb33f.io/new", changes.ExternalDocChanges.Changes[0].New)
assert.Equal(t, "https://pb33f.io", changes.ExternalDocChanges.Changes[0].Original)
}
func TestCompareSchemas_ExternalDocsAdd(t *testing.T) {
left := `components:
schemas:
OK:
type: string`
right := `components:
schemas:
OK:
type: string
externalDocs:
url: https://pb33f.io`
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, 0, changes.TotalBreakingChanges())
assert.Equal(t, v3.ExternalDocsLabel, changes.Changes[0].Property)
assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType)
assert.Equal(t, "2b7adf30f2ea3a7617ccf429a099617a9c03e8b5f3a23a89dba4b90f760010d7",
low.HashToString(changes.Changes[0].NewObject.(*base.ExternalDoc).Hash()))
}
func TestCompareSchemas_ExternalDocsRemove(t *testing.T) {
left := `components:
schemas:
OK:
type: string`
right := `components:
schemas:
OK:
type: string
externalDocs:
url: https://pb33f.io`
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, 0, changes.TotalBreakingChanges())
assert.Equal(t, v3.ExternalDocsLabel, changes.Changes[0].Property)
assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType)
assert.Equal(t, "2b7adf30f2ea3a7617ccf429a099617a9c03e8b5f3a23a89dba4b90f760010d7",
low.HashToString(changes.Changes[0].OriginalObject.(*base.ExternalDoc).Hash()))
}
func TestCompareSchemas_AddExtension(t *testing.T) {
left := `components:
schemas:
OK:
type: string`
right := `components:
schemas:
OK:
type: string
x-melody: song`
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, 0, changes.TotalBreakingChanges())
assert.Equal(t, "x-melody", changes.ExtensionChanges.Changes[0].Property)
assert.Equal(t, ObjectAdded, changes.ExtensionChanges.Changes[0].ChangeType)
assert.Equal(t, "song", changes.ExtensionChanges.Changes[0].New)
}