diff --git a/what-changed/comparison_functions.go b/what-changed/comparison_functions.go new file mode 100644 index 0000000..b159a00 --- /dev/null +++ b/what-changed/comparison_functions.go @@ -0,0 +1,189 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package what_changed + +import ( + "github.com/pb33f/libopenapi/datamodel/low" + "gopkg.in/yaml.v3" +) + +// CreateChange is a generic function that will create a Change of type T, populate all properties if set, and then +// add a pointer to Change[T] in the slice of Change pointers provided +func CreateChange[T any](changes *[]*Change[T], changeType int, property string, leftValueNode, rightValueNode *yaml.Node, + breaking bool, originalObject, newObject T) *[]*Change[T] { + + // create a new context for the left and right nodes. + ctx := CreateContext(leftValueNode, rightValueNode) + c := &Change[T]{ + Context: ctx, + ChangeType: changeType, + Property: property, + Breaking: breaking, + } + // if the left is not nil, we have an original value + if leftValueNode != nil && leftValueNode.Value != "" { + c.Original = leftValueNode.Value + } + // if the right is not nil, then we have a new value + if rightValueNode != nil && rightValueNode.Value != "" { + c.New = rightValueNode.Value + } + // original and new objects + c.OriginalObject = originalObject + c.NewObject = newObject + + // add the change to supplied changes slice + *changes = append(*changes, c) + return changes +} + +// CreateContext will return a pointer to a ChangeContext containing the original and new line and column numbers +// of the left and right value nodes. +func CreateContext(l, r *yaml.Node) *ChangeContext { + ctx := new(ChangeContext) + if l != nil { + ctx.OriginalLine = l.Line + ctx.OriginalColumn = l.Column + } else { + ctx.OriginalLine = -1 + ctx.OriginalColumn = -1 + } + if r != nil { + ctx.NewLine = r.Line + ctx.NewColumn = r.Column + } else { + ctx.NewLine = -1 + ctx.NewColumn = -1 + } + return ctx +} + +// CheckForObjectAdditionOrRemoval will check for the addition or removal of an object from left and right maps. +// The label is the key to look for in the left and right maps. +// +// To determine this a breaking change for an addition then set breakingAdd to true (however I can't think of many +// scenarios that adding things should break anything). Removals are generally breaking, except for non contract +// properties like descriptions, summaries and other non-binding values, so a breakingRemove value can be tuned for +// these circumstances. +func CheckForObjectAdditionOrRemoval[T any](l, r map[string]*low.ValueReference[T], label string, changes *[]*Change[T], + breakingAdd, breakingRemove bool) { + var left, right T + if CheckObjectRemoved(l, r) { + left = l[label].GetValue() + CreateChange[T](changes, ObjectRemoved, label, l[label].GetValueNode(), nil, + breakingRemove, left, right) + } + if added, key := CheckObjectAdded(l, r); added { + right = r[key].GetValue() + CreateChange[T](changes, ObjectAdded, label, nil, r[key].GetValueNode(), + breakingAdd, left, right) + } +} + +// CheckObjectRemoved returns true if a key/value in the left map is not present in the right. +func CheckObjectRemoved[T any](l, r map[string]*T) bool { + for i := range l { + if r[i] == nil { + return true + } + } + return false +} + +// CheckObjectAdded returns true if a key/value in the right map is not present in the left. +func CheckObjectAdded[T any](l, r map[string]*T) (bool, string) { + for i := range r { + if l[i] == nil { + return true, i + } + } + return false, "" +} + +// CheckProperties will iterate through a slice of PropertyCheck pointers of type T. The method is a convenience method +// for running checks on the following methods in order: +// CheckPropertyAdditionOrRemoval +// CheckForModification +// CheckForMove +func CheckProperties[T any](properties []*PropertyCheck[T]) { + for _, n := range properties { + CheckPropertyAdditionOrRemoval(n.LeftNode, n.RightNode, n.Label, n.Changes, n.Breaking, n.Original, n.New) + CheckForModification(n.LeftNode, n.RightNode, n.Label, n.Changes, n.Breaking, n.Original, n.New) + CheckForMove(n.LeftNode, n.RightNode, n.Label, n.Changes, n.Breaking, n.Original, n.New) + } +} + +// CheckPropertyAdditionOrRemoval will run both CheckForRemoval (first) and CheckForAddition (second) +func CheckPropertyAdditionOrRemoval[T any](l, r *yaml.Node, + label string, changes *[]*Change[T], breaking bool, orig, new T) { + CheckForRemoval[T](l, r, label, changes, breaking, orig, new) + CheckForAddition[T](l, r, label, changes, breaking, orig, new) +} + +// CheckForRemoval will check left and right yaml.Node instances for changes. Anything that is found missing on the +// right, but present on the left, is considered a removal. A new Change[T] will be created with the type +// +// PropertyRemoved +// +// The Change is then added to the slice of []Change[T] instances provided as a pointer. +func CheckForRemoval[T any](l, r *yaml.Node, label string, changes *[]*Change[T], breaking bool, orig, new T) { + if l != nil && l.Value != "" && (r == nil || r.Value == "") { + CreateChange[T](changes, PropertyRemoved, label, l, r, breaking, orig, new) + } +} + +// CheckForAddition will check left and right yaml.Node instances for changes. Anything that is found missing on the +// left, but present on the left, is considered an addition. A new Change[T] will be created with the type +// +// PropertyAdded +// +// The Change is then added to the slice of []Change[T] instances provided as a pointer. +func CheckForAddition[T any](l, r *yaml.Node, label string, changes *[]*Change[T], breaking bool, orig, new T) { + if (l == nil || l.Value == "") && r != nil && r.Value != "" { + CreateChange[T](changes, PropertyAdded, label, l, r, breaking, orig, new) + } +} + +// CheckForModification will check left and right yaml.Node instances for changes. Anything that is found in both +// sides, but vary in value is considered a modification. CheckForModification will also check if the position of the +// value has changed in the source documents. +// +// If there is no change to position, but in value the function adds a change type of Modified, if there is both a change +// in value and a change in position, then it will be set to ModifiedAndMoved +// +// The Change is then added to the slice of []Change[T] instances provided as a pointer. +func CheckForModification[T any](l, r *yaml.Node, label string, changes *[]*Change[T], breaking bool, orig, new T) { + if l != nil && l.Value != "" && r != nil && r.Value != "" && r.Value != l.Value { + changeType := Modified + ctx := CreateContext(l, r) + if ctx.HasChanged() { + changeType = ModifiedAndMoved + } + CreateChange[T](changes, changeType, label, l, r, breaking, orig, new) + } +} + +// CheckForMove will check if left and right yaml.Node instances have moved position, but not changed in value. A change +// of type Moved is created and added to changes. +func CheckForMove[T any](l, r *yaml.Node, label string, changes *[]*Change[T], breaking bool, orig, new T) { + if l != nil && l.Value != "" && r != nil && r.Value != "" && r.Value == l.Value { // everything is equal + ctx := CreateContext(l, r) + if ctx.HasChanged() { + CreateChange[T](changes, Moved, label, l, r, breaking, orig, new) + } + } +} + +// CheckExtensions is a helper method to un-pack a left and right model that contains extensions. Once unpacked +// the extensions are compared and returns a pointer to ExtensionChanges. If nothing changed, nil is returned. +func CheckExtensions[T low.HasExtensions[T]](l, r T) *ExtensionChanges { + var lExt, rExt map[low.KeyReference[string]]low.ValueReference[any] + if len(l.GetExtensions()) > 0 { + lExt = l.GetExtensions() + } + if len(r.GetExtensions()) > 0 { + rExt = r.GetExtensions() + } + return CompareExtensions(lExt, rExt) +} diff --git a/what-changed/contact.go b/what-changed/contact.go index 08e45fc..2071edb 100644 --- a/what-changed/contact.go +++ b/what-changed/contact.go @@ -8,14 +8,19 @@ import ( lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3" ) +// ContactChanges Represent changes to a Contact object that is a child of Info, part of an OpenAPI document. type ContactChanges struct { PropertyChanges[*lowbase.Contact] } +// TotalChanges represents the total number of changes that have occurred to a Contact object func (c *ContactChanges) TotalChanges() int { return len(c.Changes) } +// CompareContact will check a left (original) and right (new) Contact object for any changes. If there +// were any, a pointer to a ContactChanges object is returned, otherwise if nothing changed - the function +// returns nil. func CompareContact(l, r *lowbase.Contact) *ContactChanges { var changes []*Change[*lowbase.Contact] @@ -64,4 +69,3 @@ func CompareContact(l, r *lowbase.Contact) *ContactChanges { } return dc } - diff --git a/what-changed/extensions.go b/what-changed/extensions.go index a0e57ce..f2c010f 100644 --- a/what-changed/extensions.go +++ b/what-changed/extensions.go @@ -8,10 +8,17 @@ import ( "strings" ) +// ExtensionChanges represents any changes to custom extensions defined for an OpenAPI object. type ExtensionChanges struct { PropertyChanges[any] } +// CompareExtensions will compare a left and right map of Key/ValueReference models for any changes to +// anything. This function does not try and cast the value of an extension to perform checks, it +// will perform a basic value check. +// +// A current limitation relates to extensions being objects and a property of the object changes, +// there is currently no support for knowing anything changed - so it is ignored. func CompareExtensions(l, r map[low.KeyReference[string]]low.ValueReference[any]) *ExtensionChanges { // look at the original and then look through the new. diff --git a/what-changed/extensions_test.go b/what-changed/extensions_test.go index a068e53..a0fa289 100644 --- a/what-changed/extensions_test.go +++ b/what-changed/extensions_test.go @@ -49,7 +49,7 @@ x-test: 1` assert.Len(t, extChanges.Changes, 1) assert.Equal(t, Moved, extChanges.Changes[0].ChangeType) - assert.Equal(t, 2, extChanges.Changes[0].Context.OrigLine) + assert.Equal(t, 2, extChanges.Changes[0].Context.OriginalLine) assert.Equal(t, 1, extChanges.Changes[0].Context.NewLine) assert.True(t, extChanges.Changes[0].Context.HasChanged()) } @@ -72,7 +72,7 @@ x-test: 1` assert.Len(t, extChanges.Changes, 1) assert.Equal(t, ModifiedAndMoved, extChanges.Changes[0].ChangeType) - assert.Equal(t, 2, extChanges.Changes[0].Context.OrigLine) + assert.Equal(t, 2, extChanges.Changes[0].Context.OriginalLine) assert.Equal(t, 1, extChanges.Changes[0].Context.NewLine) assert.Equal(t, "1", extChanges.Changes[0].Original) assert.Equal(t, "2", extChanges.Changes[0].New) @@ -97,7 +97,7 @@ x-test: 1` assert.Len(t, extChanges.Changes, 1) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) - assert.Equal(t, 2, extChanges.Changes[0].Context.OrigLine) + assert.Equal(t, 2, extChanges.Changes[0].Context.OriginalLine) assert.Equal(t, -1, extChanges.Changes[0].Context.NewLine) assert.Equal(t, "1", extChanges.Changes[0].Original) assert.True(t, extChanges.Changes[0].Context.HasChanged()) @@ -121,7 +121,7 @@ x-test: 1` assert.Len(t, extChanges.Changes, 1) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) - assert.Equal(t, -1, extChanges.Changes[0].Context.OrigLine) + assert.Equal(t, -1, extChanges.Changes[0].Context.OriginalLine) assert.Equal(t, 2, extChanges.Changes[0].Context.NewLine) assert.Equal(t, "1", extChanges.Changes[0].New) assert.True(t, extChanges.Changes[0].Context.HasChanged()) diff --git a/what-changed/external_docs.go b/what-changed/external_docs.go index 41184c4..fcbe489 100644 --- a/what-changed/external_docs.go +++ b/what-changed/external_docs.go @@ -8,11 +8,13 @@ import ( lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3" ) +// ExternalDocChanges represents changes made to any ExternalDoc object from an OpenAPI document. type ExternalDocChanges struct { PropertyChanges[*lowbase.ExternalDoc] ExtensionChanges *ExtensionChanges } +// TotalChanges returns a count of everything that changed func (e *ExternalDocChanges) TotalChanges() int { c := len(e.Changes) if e.ExtensionChanges != nil { @@ -21,6 +23,9 @@ func (e *ExternalDocChanges) TotalChanges() int { return c } +// CompareExternalDocs will compare a left (original) and a right (new) slice of ValueReference +// nodes for any changes between them. If there are changes, then a pointer to ExternalDocChanges +// is returned, otherwise if nothing changed - then nil is returned. func CompareExternalDocs(l, r *lowbase.ExternalDoc) *ExternalDocChanges { var changes []*Change[*lowbase.ExternalDoc] var props []*PropertyCheck[*lowbase.ExternalDoc] diff --git a/what-changed/external_docs_test.go b/what-changed/external_docs_test.go index 08f1c3d..abed83e 100644 --- a/what-changed/external_docs_test.go +++ b/what-changed/external_docs_test.go @@ -46,7 +46,7 @@ x-testing: hiya!` assert.False(t, urlChange.Context.HasChanged()) assert.Equal(t, "https://pb33f.io", urlChange.Original) assert.Equal(t, "https://quobix.com", urlChange.New) - assert.Equal(t, 1, urlChange.Context.OrigLine) + assert.Equal(t, 1, urlChange.Context.OriginalLine) assert.Equal(t, lowv3.URLLabel, urlChange.Property) descChange := extChanges.Changes[1] @@ -54,8 +54,8 @@ x-testing: hiya!` assert.False(t, descChange.Context.HasChanged()) assert.Equal(t, "this is another test", descChange.New) assert.Equal(t, "this is a test", descChange.Original) - assert.Equal(t, 2, descChange.Context.OrigLine) - assert.Equal(t, 14, descChange.Context.OrigCol) + assert.Equal(t, 2, descChange.Context.OriginalLine) + assert.Equal(t, 14, descChange.Context.OriginalColumn) // validate extensions extChange := extChanges.ExtensionChanges.Changes[0] @@ -63,8 +63,8 @@ x-testing: hiya!` assert.False(t, extChange.Context.HasChanged()) assert.Equal(t, "hiya!", extChange.New) assert.Equal(t, "hello", extChange.Original) - assert.Equal(t, 3, extChange.Context.OrigLine) - assert.Equal(t, 12, extChange.Context.OrigCol) + assert.Equal(t, 3, extChange.Context.OriginalLine) + assert.Equal(t, 12, extChange.Context.OriginalColumn) } @@ -101,7 +101,7 @@ url: https://quobix.com` assert.True(t, urlChange.Context.HasChanged()) assert.Equal(t, "https://pb33f.io", urlChange.Original) assert.Equal(t, "https://quobix.com", urlChange.New) - assert.Equal(t, 1, urlChange.Context.OrigLine) + assert.Equal(t, 1, urlChange.Context.OriginalLine) assert.Equal(t, 3, urlChange.Context.NewLine) assert.Equal(t, lowv3.URLLabel, urlChange.Property) @@ -110,10 +110,10 @@ url: https://quobix.com` assert.True(t, descChange.Context.HasChanged()) assert.Equal(t, "this is another test", descChange.New) assert.Equal(t, "this is a test", descChange.Original) - assert.Equal(t, 2, descChange.Context.OrigLine) - assert.Equal(t, 14, descChange.Context.OrigCol) + assert.Equal(t, 2, descChange.Context.OriginalLine) + assert.Equal(t, 14, descChange.Context.OriginalColumn) assert.Equal(t, 1, descChange.Context.NewLine) - assert.Equal(t, 14, descChange.Context.NewCol) + assert.Equal(t, 14, descChange.Context.NewColumn) // validate extensions extChange := extChanges.ExtensionChanges.Changes[0] @@ -121,10 +121,10 @@ url: https://quobix.com` assert.True(t, extChange.Context.HasChanged()) assert.Equal(t, "hiya!", extChange.New) assert.Equal(t, "hello", extChange.Original) - assert.Equal(t, 3, extChange.Context.OrigLine) - assert.Equal(t, 12, extChange.Context.OrigCol) + assert.Equal(t, 3, extChange.Context.OriginalLine) + assert.Equal(t, 12, extChange.Context.OriginalColumn) assert.Equal(t, 2, extChange.Context.NewLine) - assert.Equal(t, 12, extChange.Context.NewCol) + assert.Equal(t, 12, extChange.Context.NewColumn) } func TestCompareExternalDocs_Identical(t *testing.T) { diff --git a/what-changed/models.go b/what-changed/models.go new file mode 100644 index 0000000..cd1ece1 --- /dev/null +++ b/what-changed/models.go @@ -0,0 +1,117 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package what_changed + +import "gopkg.in/yaml.v3" + +// Definitions of the possible changes between two items +const ( + + // Modified means that was a modification of a value was made + Modified = iota + 1 + + // PropertyAdded means that a new property to an object was added + PropertyAdded + + // ObjectAdded means that a new object was added + ObjectAdded + + // ObjectRemoved means that an object was removed + ObjectRemoved + + // PropertyRemoved means that a property of an object was removed + PropertyRemoved + + // Moved means that a property or an object was moved, but unchanged (line/columns changed) + Moved + + // ModifiedAndMoved means that a property was modified, and it was also moved. + ModifiedAndMoved +) + +// WhatChanged is a summary object that contains a high level summary of everything changed. +type WhatChanged struct { + Added int + Removed int + ModifiedAndMoved int + Modified int + Moved int + TotalChanges int + Changes *Changes +} + +// ChangeContext holds a reference to the line and column positions of original and new change. +type ChangeContext struct { + OriginalLine int + OriginalColumn int + NewLine int + NewColumn int +} + +// HasChanged determines if the line and column numbers of the original and new values have changed. +func (c *ChangeContext) HasChanged() bool { + return c.NewLine != c.OriginalLine || c.NewColumn != c.OriginalColumn +} + +// Change represents a change between two different elements inside an OpenAPI specification. +type Change[T any] struct { + + // Context represents the lines and column numbers of the original and new changes. + Context *ChangeContext + + // ChangeType represents the type of change that occurred. stored as an integer, defined by constants above. + ChangeType int + + // Property is the property name key being changed. + Property string + + // Original is the original value represented as a string. + Original string + + // New is the new value represented as a string. + New string + + // Breaking determines if the change is a breaking one or not. + Breaking bool + + // OriginalObject represents the original object that was changed. + OriginalObject T + + // NewObject represents the new object that has been modified. + NewObject T +} + +// PropertyChanges holds a slice of Change[T] change pointers +type PropertyChanges[T any] struct { + Changes []*Change[T] +} + +// PropertyCheck is used by functions to check the state of left and right values. +type PropertyCheck[T any] struct { + + // Original is the property we're checking on the left + Original T + + // New is s the property we're checking on the right + New T + + // Label is the identifier we're looking for on the left and right hand sides + Label string + + // LeftNode is the yaml.Node pointer that holds the original node structure of the value + LeftNode *yaml.Node + + // RightNode is the yaml.Node pointer that holds the new node structure of the value + RightNode *yaml.Node + + // Breaking determines if the check is a breaking change (modifications or removals etc.) + Breaking bool + + // Changes represents a pointer to the slice to contain all changes found. + Changes *[]*Change[T] +} + +type Changes struct { + TagChanges *TagChanges +} diff --git a/what-changed/tags.go b/what-changed/tags.go index 36b0f4b..7e5a642 100644 --- a/what-changed/tags.go +++ b/what-changed/tags.go @@ -10,12 +10,14 @@ import ( "strings" ) +// TagChanges represents changes made to the Tags object of an OpenAPI document. type TagChanges struct { PropertyChanges[*lowbase.Tag] ExternalDocs *ExternalDocChanges ExtensionChanges *ExtensionChanges } +// TotalChanges returns a count of everything that changed within tags. func (t *TagChanges) TotalChanges() int { c := len(t.Changes) if t.ExternalDocs != nil { @@ -27,6 +29,9 @@ func (t *TagChanges) TotalChanges() int { return c } +// CompareTags will compare a left (original) and a right (new) slice of ValueReference nodes for +// any changes between them. If there are changes, a pointer to TagChanges is returned, if not then +// nil is returned instead. func CompareTags(l, r []low.ValueReference[*lowbase.Tag]) *TagChanges { tc := new(TagChanges) diff --git a/what-changed/tags_test.go b/what-changed/tags_test.go index 47f5d76..372189b 100644 --- a/what-changed/tags_test.go +++ b/what-changed/tags_test.go @@ -159,13 +159,13 @@ tags: nameChange := changes.Changes[0] assert.Equal(t, Moved, nameChange.ChangeType) - assert.Equal(t, 4, nameChange.Context.OrigLine) + assert.Equal(t, 4, nameChange.Context.OriginalLine) assert.Equal(t, 3, nameChange.Context.NewLine) assert.True(t, nameChange.Context.HasChanged()) descChange := changes.Changes[1] assert.Equal(t, Moved, descChange.ChangeType) - assert.Equal(t, 3, descChange.Context.OrigLine) + assert.Equal(t, 3, descChange.Context.OriginalLine) assert.Equal(t, 5, descChange.Context.NewLine) assert.True(t, descChange.Context.HasChanged()) @@ -206,7 +206,7 @@ tags: nameChange := changes.Changes[0] assert.Equal(t, Moved, nameChange.ChangeType) - assert.Equal(t, 4, nameChange.Context.OrigLine) + assert.Equal(t, 4, nameChange.Context.OriginalLine) assert.Equal(t, 8, nameChange.Context.NewLine) assert.True(t, nameChange.Context.HasChanged()) } @@ -246,7 +246,7 @@ tags: descChange := changes.Changes[1] assert.Equal(t, ModifiedAndMoved, descChange.ChangeType) - assert.Equal(t, 3, descChange.Context.OrigLine) + assert.Equal(t, 3, descChange.Context.OriginalLine) assert.Equal(t, 5, descChange.Context.NewLine) assert.Equal(t, "a lovelier tag description", descChange.Original) assert.Equal(t, "a different tag description", descChange.New) diff --git a/what-changed/what_changed.go b/what-changed/what_changed.go index ad42d46..6e78fbd 100644 --- a/what-changed/what_changed.go +++ b/what-changed/what_changed.go @@ -3,197 +3,3 @@ package what_changed -import ( - "github.com/pb33f/libopenapi/datamodel/low" - "gopkg.in/yaml.v3" -) - -const ( - Modified = iota + 1 - PropertyAdded - ObjectAdded - ObjectRemoved - PropertyRemoved - Moved - ModifiedAndMoved -) - -type WhatChanged struct { - Added int - Removed int - Modified int - Moved int - TotalChanges int - Changes *Changes -} - -type ChangeContext struct { - OrigLine int - OrigCol int - NewLine int - NewCol int -} - -func (c *ChangeContext) HasChanged() bool { - return c.NewLine != c.OrigLine || c.NewCol != c.OrigCol -} - -type Change[T any] struct { - Context *ChangeContext - ChangeType int - Property string - Original string - New string - Breaking bool - OriginalObject T - NewObject T -} - -type PropertyChanges[T any] struct { - Changes []*Change[T] -} - -type Changes struct { - TagChanges *TagChanges -} - -func CreateChange[T any](changes *[]*Change[T], changeType int, property string, leftValueNode, rightValueNode *yaml.Node, - breaking bool, originalObject, newObject T) *[]*Change[T] { - - ctx := CreateContext(leftValueNode, rightValueNode) - c := &Change[T]{ - Context: ctx, - ChangeType: changeType, - Property: property, - Breaking: breaking, - } - if leftValueNode != nil && leftValueNode.Value != "" { - c.Original = leftValueNode.Value - } - if rightValueNode != nil && rightValueNode.Value != "" { - c.New = rightValueNode.Value - } - c.OriginalObject = originalObject - c.NewObject = newObject - *changes = append(*changes, c) - return changes -} - -func CreateContext(l, r *yaml.Node) *ChangeContext { - ctx := new(ChangeContext) - if l != nil { - ctx.OrigLine = l.Line - ctx.OrigCol = l.Column - } else { - ctx.OrigLine = -1 - ctx.OrigCol = -1 - } - if r != nil { - ctx.NewLine = r.Line - ctx.NewCol = r.Column - } else { - ctx.NewLine = -1 - ctx.NewCol = -1 - } - return ctx -} - -type PropertyCheck[T any] struct { - Original T - New T - Label string - LeftNode *yaml.Node - RightNode *yaml.Node - Breaking bool - Changes *[]*Change[T] -} - -func CheckForObjectAdditionOrRemoval[T any](l, r map[string]*low.ValueReference[T], label string, changes *[]*Change[T], - breakingAdd, breakingRemove bool) { - var left, right T - if CheckObjectRemoved(l, r) { - left = l[label].GetValue() - CreateChange[T](changes, ObjectRemoved, label, l[label].GetValueNode(), nil, - breakingRemove, left, right) - } - if added, key := CheckObjectAdded(l, r); added { - right = r[key].GetValue() - CreateChange[T](changes, ObjectAdded, label, nil, r[key].GetValueNode(), - breakingAdd, left, right) - } -} - -func CheckObjectRemoved[T any](l, r map[string]*T) bool { - for i := range l { - if r[i] == nil { - return true - } - } - return false -} - -func CheckObjectAdded[T any](l, r map[string]*T) (bool, string) { - for i := range r { - if l[i] == nil { - return true, i - } - } - return false, "" -} - -func CheckProperties[T any](properties []*PropertyCheck[T]) { - for _, n := range properties { - CheckPropertyAdditionOrRemoval(n.LeftNode, n.RightNode, n.Label, n.Changes, n.Breaking, n.Original, n.New) - CheckForModification(n.LeftNode, n.RightNode, n.Label, n.Changes, n.Breaking, n.Original, n.New) - CheckForMove(n.LeftNode, n.RightNode, n.Label, n.Changes, n.Breaking, n.Original, n.New) - } -} - -func CheckPropertyAdditionOrRemoval[T any](l, r *yaml.Node, - label string, changes *[]*Change[T], breaking bool, orig, new T) { - CheckForRemoval[T](l, r, label, changes, breaking, orig, new) - CheckForAddition[T](l, r, label, changes, breaking, orig, new) -} - -func CheckForRemoval[T any](l, r *yaml.Node, label string, changes *[]*Change[T], breaking bool, orig, new T) { - if l != nil && l.Value != "" && (r == nil || r.Value == "") { - CreateChange[T](changes, PropertyRemoved, label, l, r, breaking, orig, new) - } -} - -func CheckForAddition[T any](l, r *yaml.Node, label string, changes *[]*Change[T], breaking bool, orig, new T) { - if (l == nil || l.Value == "") && r != nil && r.Value != "" { - CreateChange[T](changes, PropertyAdded, label, l, r, breaking, orig, new) - } -} - -func CheckForModification[T any](l, r *yaml.Node, label string, changes *[]*Change[T], breaking bool, orig, new T) { - if l != nil && l.Value != "" && r != nil && r.Value != "" && r.Value != l.Value { - changeType := Modified - ctx := CreateContext(l, r) - if ctx.HasChanged() { - changeType = ModifiedAndMoved - } - CreateChange[T](changes, changeType, label, l, r, breaking, orig, new) - } -} - -func CheckForMove[T any](l, r *yaml.Node, label string, changes *[]*Change[T], breaking bool, orig, new T) { - if l != nil && l.Value != "" && r != nil && r.Value != "" && r.Value == l.Value { // everything is equal - ctx := CreateContext(l, r) - if ctx.HasChanged() { - CreateChange[T](changes, Moved, label, l, r, breaking, orig, new) - } - } -} - -func CheckExtensions[T low.HasExtensions[T]](l, r T) *ExtensionChanges { - var lExt, rExt map[low.KeyReference[string]]low.ValueReference[any] - if len(l.GetExtensions()) > 0 { - lExt = l.GetExtensions() - } - if len(r.GetExtensions()) > 0 { - rExt = r.GetExtensions() - } - return CompareExtensions(lExt, rExt) -}