diff --git a/datamodel/low/base/xml.go b/datamodel/low/base/xml.go index 668c83d..d66d6a4 100644 --- a/datamodel/low/base/xml.go +++ b/datamodel/low/base/xml.go @@ -28,3 +28,7 @@ func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error { x.Extensions = low.ExtractExtensions(root) return nil } + +func (x *XML) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { + return x.Extensions +} diff --git a/datamodel/low/v3/constants.go b/datamodel/low/v3/constants.go index ee7db64..3da233b 100644 --- a/datamodel/low/v3/constants.go +++ b/datamodel/low/v3/constants.go @@ -47,4 +47,8 @@ const ( VersionLabel = "version" LicenseLabel = "license" ContactLabel = "contact" + NamespaceLabel = "namespace" + PrefixLabel = "prefix" + AttributeLabel = "attribute" + WrappedLabel = "wrapped" ) diff --git a/what-changed/tags.go b/what-changed/tags.go index 0efb4c6..ff61df6 100644 --- a/what-changed/tags.go +++ b/what-changed/tags.go @@ -84,12 +84,13 @@ func CompareTags(l, r []low.ValueReference[*base.Tag]) *TagChanges { // check properties CheckProperties(props) - // check extensions - tc.ExtensionChanges = CheckExtensions(seenLeft[i].GetValue(), seenRight[i].GetValue()) - // compare external docs tc.ExternalDocs = CompareExternalDocs(seenLeft[i].Value.ExternalDocs.Value, seenRight[i].Value.ExternalDocs.Value) + + // check extensions + tc.ExtensionChanges = CheckExtensions(seenLeft[i].GetValue(), seenRight[i].GetValue()) + } } diff --git a/what-changed/xml.go b/what-changed/xml.go new file mode 100644 index 0000000..67caf40 --- /dev/null +++ b/what-changed/xml.go @@ -0,0 +1,99 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package what_changed + +import ( + "github.com/pb33f/libopenapi/datamodel/low/base" + v3 "github.com/pb33f/libopenapi/datamodel/low/v3" +) + +// XMLChanges represents changes made to the XML object of an OpenAPI document. +type XMLChanges struct { + PropertyChanges[*base.XML] + ExtensionChanges *ExtensionChanges +} + +// TotalChanges returns a count of everything that was changed within an XML object. +func (x *XMLChanges) TotalChanges() int { + c := len(x.Changes) + if x.ExtensionChanges != nil { + c += len(x.ExtensionChanges.Changes) + } + return c +} + +// CompareXML will compare a left (original) and a right (new) XML instance, and check for +// any changes between them. If changes are found, the function returns a pointer to XMLChanges, +// otherwise, if nothing changed - it will return nil +func CompareXML(l, r *base.XML) *XMLChanges { + xc := new(XMLChanges) + var changes []*Change[*base.XML] + var props []*PropertyCheck[*base.XML] + + // Name (breaking change) + props = append(props, &PropertyCheck[*base.XML]{ + LeftNode: l.Name.ValueNode, + RightNode: r.Name.ValueNode, + Label: v3.NameLabel, + Changes: &changes, + Breaking: true, + Original: l, + New: r, + }) + + // Namespace (breaking change) + props = append(props, &PropertyCheck[*base.XML]{ + LeftNode: l.Namespace.ValueNode, + RightNode: r.Namespace.ValueNode, + Label: v3.NamespaceLabel, + Changes: &changes, + Breaking: true, + Original: l, + New: r, + }) + + // Prefix (breaking change) + props = append(props, &PropertyCheck[*base.XML]{ + LeftNode: l.Prefix.ValueNode, + RightNode: r.Prefix.ValueNode, + Label: v3.PrefixLabel, + Changes: &changes, + Breaking: true, + Original: l, + New: r, + }) + + // Attribute (breaking change) + props = append(props, &PropertyCheck[*base.XML]{ + LeftNode: l.Attribute.ValueNode, + RightNode: r.Attribute.ValueNode, + Label: v3.AttributeLabel, + Changes: &changes, + Breaking: true, + Original: l, + New: r, + }) + + // Wrapped (breaking change) + props = append(props, &PropertyCheck[*base.XML]{ + LeftNode: l.Wrapped.ValueNode, + RightNode: r.Wrapped.ValueNode, + Label: v3.WrappedLabel, + Changes: &changes, + Breaking: true, + Original: l, + New: r, + }) + + // check properties + CheckProperties(props) + + // check extensions + xc.ExtensionChanges = CheckExtensions(l, r) + xc.Changes = changes + if len(xc.Changes) <= 0 && xc.ExtensionChanges == nil { + return nil + } + return xc +} diff --git a/what-changed/xml_test.go b/what-changed/xml_test.go new file mode 100644 index 0000000..e7da3fb --- /dev/null +++ b/what-changed/xml_test.go @@ -0,0 +1,143 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package what_changed + +import ( + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/datamodel/low/base" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + "testing" +) + +func TestCompareXML_NameChanged(t *testing.T) { + + left := `name: xml thing +namespace: something +prefix: another +attribute: true +wrapped: true` + + right := `namespace: something +prefix: another +name: changed xml thing +attribute: true +wrapped: true` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc base.XML + var rDoc base.XML + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareXML(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) + +} + +func TestCompareXML_NameRemoved(t *testing.T) { + + left := `name: xml thing +namespace: something +prefix: another +attribute: true +wrapped: true` + + right := `wrapped: true +prefix: another +attribute: true +namespace: something` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc base.XML + var rDoc base.XML + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareXML(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) + +} + +func TestCompareXML_ExtensionAdded(t *testing.T) { + + left := `name: xml thing +namespace: something +prefix: another +attribute: true +wrapped: true` + + right := `name: xml thing +namespace: something +prefix: another +attribute: true +wrapped: true +x-coffee: time` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc base.XML + var rDoc base.XML + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareXML(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, ObjectAdded, extChanges.ExtensionChanges.Changes[0].ChangeType) + +} + +func TestCompareXML_Identical(t *testing.T) { + + left := `name: xml thing +namespace: something +prefix: another +attribute: true +wrapped: true` + + right := `name: xml thing +namespace: something +prefix: another +attribute: true +wrapped: true` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc base.XML + var rDoc base.XML + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareXML(&lDoc, &rDoc) + assert.Nil(t, extChanges) + +}