mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-09 04:20:17 +00:00
Refactored what-changed core code to be ready for buildout.
The designs and patterns are set, the way forward for low-level models is clear. Documentation and cleanliness will now be a side by side process as the rest is built out. Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
189
what-changed/comparison_functions.go
Normal file
189
what-changed/comparison_functions.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
@@ -8,14 +8,19 @@ import (
|
|||||||
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
|
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 {
|
type ContactChanges struct {
|
||||||
PropertyChanges[*lowbase.Contact]
|
PropertyChanges[*lowbase.Contact]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TotalChanges represents the total number of changes that have occurred to a Contact object
|
||||||
func (c *ContactChanges) TotalChanges() int {
|
func (c *ContactChanges) TotalChanges() int {
|
||||||
return len(c.Changes)
|
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 {
|
func CompareContact(l, r *lowbase.Contact) *ContactChanges {
|
||||||
|
|
||||||
var changes []*Change[*lowbase.Contact]
|
var changes []*Change[*lowbase.Contact]
|
||||||
@@ -64,4 +69,3 @@ func CompareContact(l, r *lowbase.Contact) *ContactChanges {
|
|||||||
}
|
}
|
||||||
return dc
|
return dc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ExtensionChanges represents any changes to custom extensions defined for an OpenAPI object.
|
||||||
type ExtensionChanges struct {
|
type ExtensionChanges struct {
|
||||||
PropertyChanges[any]
|
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 {
|
func CompareExtensions(l, r map[low.KeyReference[string]]low.ValueReference[any]) *ExtensionChanges {
|
||||||
|
|
||||||
// look at the original and then look through the new.
|
// look at the original and then look through the new.
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ x-test: 1`
|
|||||||
|
|
||||||
assert.Len(t, extChanges.Changes, 1)
|
assert.Len(t, extChanges.Changes, 1)
|
||||||
assert.Equal(t, Moved, extChanges.Changes[0].ChangeType)
|
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.Equal(t, 1, extChanges.Changes[0].Context.NewLine)
|
||||||
assert.True(t, extChanges.Changes[0].Context.HasChanged())
|
assert.True(t, extChanges.Changes[0].Context.HasChanged())
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ x-test: 1`
|
|||||||
|
|
||||||
assert.Len(t, extChanges.Changes, 1)
|
assert.Len(t, extChanges.Changes, 1)
|
||||||
assert.Equal(t, ModifiedAndMoved, extChanges.Changes[0].ChangeType)
|
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].Context.NewLine)
|
||||||
assert.Equal(t, "1", extChanges.Changes[0].Original)
|
assert.Equal(t, "1", extChanges.Changes[0].Original)
|
||||||
assert.Equal(t, "2", extChanges.Changes[0].New)
|
assert.Equal(t, "2", extChanges.Changes[0].New)
|
||||||
@@ -97,7 +97,7 @@ x-test: 1`
|
|||||||
|
|
||||||
assert.Len(t, extChanges.Changes, 1)
|
assert.Len(t, extChanges.Changes, 1)
|
||||||
assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType)
|
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].Context.NewLine)
|
||||||
assert.Equal(t, "1", extChanges.Changes[0].Original)
|
assert.Equal(t, "1", extChanges.Changes[0].Original)
|
||||||
assert.True(t, extChanges.Changes[0].Context.HasChanged())
|
assert.True(t, extChanges.Changes[0].Context.HasChanged())
|
||||||
@@ -121,7 +121,7 @@ x-test: 1`
|
|||||||
|
|
||||||
assert.Len(t, extChanges.Changes, 1)
|
assert.Len(t, extChanges.Changes, 1)
|
||||||
assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType)
|
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, 2, extChanges.Changes[0].Context.NewLine)
|
||||||
assert.Equal(t, "1", extChanges.Changes[0].New)
|
assert.Equal(t, "1", extChanges.Changes[0].New)
|
||||||
assert.True(t, extChanges.Changes[0].Context.HasChanged())
|
assert.True(t, extChanges.Changes[0].Context.HasChanged())
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import (
|
|||||||
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
|
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ExternalDocChanges represents changes made to any ExternalDoc object from an OpenAPI document.
|
||||||
type ExternalDocChanges struct {
|
type ExternalDocChanges struct {
|
||||||
PropertyChanges[*lowbase.ExternalDoc]
|
PropertyChanges[*lowbase.ExternalDoc]
|
||||||
ExtensionChanges *ExtensionChanges
|
ExtensionChanges *ExtensionChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TotalChanges returns a count of everything that changed
|
||||||
func (e *ExternalDocChanges) TotalChanges() int {
|
func (e *ExternalDocChanges) TotalChanges() int {
|
||||||
c := len(e.Changes)
|
c := len(e.Changes)
|
||||||
if e.ExtensionChanges != nil {
|
if e.ExtensionChanges != nil {
|
||||||
@@ -21,6 +23,9 @@ func (e *ExternalDocChanges) TotalChanges() int {
|
|||||||
return c
|
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 {
|
func CompareExternalDocs(l, r *lowbase.ExternalDoc) *ExternalDocChanges {
|
||||||
var changes []*Change[*lowbase.ExternalDoc]
|
var changes []*Change[*lowbase.ExternalDoc]
|
||||||
var props []*PropertyCheck[*lowbase.ExternalDoc]
|
var props []*PropertyCheck[*lowbase.ExternalDoc]
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ x-testing: hiya!`
|
|||||||
assert.False(t, urlChange.Context.HasChanged())
|
assert.False(t, urlChange.Context.HasChanged())
|
||||||
assert.Equal(t, "https://pb33f.io", urlChange.Original)
|
assert.Equal(t, "https://pb33f.io", urlChange.Original)
|
||||||
assert.Equal(t, "https://quobix.com", urlChange.New)
|
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)
|
assert.Equal(t, lowv3.URLLabel, urlChange.Property)
|
||||||
|
|
||||||
descChange := extChanges.Changes[1]
|
descChange := extChanges.Changes[1]
|
||||||
@@ -54,8 +54,8 @@ x-testing: hiya!`
|
|||||||
assert.False(t, descChange.Context.HasChanged())
|
assert.False(t, descChange.Context.HasChanged())
|
||||||
assert.Equal(t, "this is another test", descChange.New)
|
assert.Equal(t, "this is another test", descChange.New)
|
||||||
assert.Equal(t, "this is a test", descChange.Original)
|
assert.Equal(t, "this is a test", descChange.Original)
|
||||||
assert.Equal(t, 2, descChange.Context.OrigLine)
|
assert.Equal(t, 2, descChange.Context.OriginalLine)
|
||||||
assert.Equal(t, 14, descChange.Context.OrigCol)
|
assert.Equal(t, 14, descChange.Context.OriginalColumn)
|
||||||
|
|
||||||
// validate extensions
|
// validate extensions
|
||||||
extChange := extChanges.ExtensionChanges.Changes[0]
|
extChange := extChanges.ExtensionChanges.Changes[0]
|
||||||
@@ -63,8 +63,8 @@ x-testing: hiya!`
|
|||||||
assert.False(t, extChange.Context.HasChanged())
|
assert.False(t, extChange.Context.HasChanged())
|
||||||
assert.Equal(t, "hiya!", extChange.New)
|
assert.Equal(t, "hiya!", extChange.New)
|
||||||
assert.Equal(t, "hello", extChange.Original)
|
assert.Equal(t, "hello", extChange.Original)
|
||||||
assert.Equal(t, 3, extChange.Context.OrigLine)
|
assert.Equal(t, 3, extChange.Context.OriginalLine)
|
||||||
assert.Equal(t, 12, extChange.Context.OrigCol)
|
assert.Equal(t, 12, extChange.Context.OriginalColumn)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ url: https://quobix.com`
|
|||||||
assert.True(t, urlChange.Context.HasChanged())
|
assert.True(t, urlChange.Context.HasChanged())
|
||||||
assert.Equal(t, "https://pb33f.io", urlChange.Original)
|
assert.Equal(t, "https://pb33f.io", urlChange.Original)
|
||||||
assert.Equal(t, "https://quobix.com", urlChange.New)
|
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, 3, urlChange.Context.NewLine)
|
||||||
assert.Equal(t, lowv3.URLLabel, urlChange.Property)
|
assert.Equal(t, lowv3.URLLabel, urlChange.Property)
|
||||||
|
|
||||||
@@ -110,10 +110,10 @@ url: https://quobix.com`
|
|||||||
assert.True(t, descChange.Context.HasChanged())
|
assert.True(t, descChange.Context.HasChanged())
|
||||||
assert.Equal(t, "this is another test", descChange.New)
|
assert.Equal(t, "this is another test", descChange.New)
|
||||||
assert.Equal(t, "this is a test", descChange.Original)
|
assert.Equal(t, "this is a test", descChange.Original)
|
||||||
assert.Equal(t, 2, descChange.Context.OrigLine)
|
assert.Equal(t, 2, descChange.Context.OriginalLine)
|
||||||
assert.Equal(t, 14, descChange.Context.OrigCol)
|
assert.Equal(t, 14, descChange.Context.OriginalColumn)
|
||||||
assert.Equal(t, 1, descChange.Context.NewLine)
|
assert.Equal(t, 1, descChange.Context.NewLine)
|
||||||
assert.Equal(t, 14, descChange.Context.NewCol)
|
assert.Equal(t, 14, descChange.Context.NewColumn)
|
||||||
|
|
||||||
// validate extensions
|
// validate extensions
|
||||||
extChange := extChanges.ExtensionChanges.Changes[0]
|
extChange := extChanges.ExtensionChanges.Changes[0]
|
||||||
@@ -121,10 +121,10 @@ url: https://quobix.com`
|
|||||||
assert.True(t, extChange.Context.HasChanged())
|
assert.True(t, extChange.Context.HasChanged())
|
||||||
assert.Equal(t, "hiya!", extChange.New)
|
assert.Equal(t, "hiya!", extChange.New)
|
||||||
assert.Equal(t, "hello", extChange.Original)
|
assert.Equal(t, "hello", extChange.Original)
|
||||||
assert.Equal(t, 3, extChange.Context.OrigLine)
|
assert.Equal(t, 3, extChange.Context.OriginalLine)
|
||||||
assert.Equal(t, 12, extChange.Context.OrigCol)
|
assert.Equal(t, 12, extChange.Context.OriginalColumn)
|
||||||
assert.Equal(t, 2, extChange.Context.NewLine)
|
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) {
|
func TestCompareExternalDocs_Identical(t *testing.T) {
|
||||||
|
|||||||
117
what-changed/models.go
Normal file
117
what-changed/models.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -10,12 +10,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TagChanges represents changes made to the Tags object of an OpenAPI document.
|
||||||
type TagChanges struct {
|
type TagChanges struct {
|
||||||
PropertyChanges[*lowbase.Tag]
|
PropertyChanges[*lowbase.Tag]
|
||||||
ExternalDocs *ExternalDocChanges
|
ExternalDocs *ExternalDocChanges
|
||||||
ExtensionChanges *ExtensionChanges
|
ExtensionChanges *ExtensionChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TotalChanges returns a count of everything that changed within tags.
|
||||||
func (t *TagChanges) TotalChanges() int {
|
func (t *TagChanges) TotalChanges() int {
|
||||||
c := len(t.Changes)
|
c := len(t.Changes)
|
||||||
if t.ExternalDocs != nil {
|
if t.ExternalDocs != nil {
|
||||||
@@ -27,6 +29,9 @@ func (t *TagChanges) TotalChanges() int {
|
|||||||
return c
|
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 {
|
func CompareTags(l, r []low.ValueReference[*lowbase.Tag]) *TagChanges {
|
||||||
tc := new(TagChanges)
|
tc := new(TagChanges)
|
||||||
|
|
||||||
|
|||||||
@@ -159,13 +159,13 @@ tags:
|
|||||||
|
|
||||||
nameChange := changes.Changes[0]
|
nameChange := changes.Changes[0]
|
||||||
assert.Equal(t, Moved, nameChange.ChangeType)
|
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.Equal(t, 3, nameChange.Context.NewLine)
|
||||||
assert.True(t, nameChange.Context.HasChanged())
|
assert.True(t, nameChange.Context.HasChanged())
|
||||||
|
|
||||||
descChange := changes.Changes[1]
|
descChange := changes.Changes[1]
|
||||||
assert.Equal(t, Moved, descChange.ChangeType)
|
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.Equal(t, 5, descChange.Context.NewLine)
|
||||||
assert.True(t, descChange.Context.HasChanged())
|
assert.True(t, descChange.Context.HasChanged())
|
||||||
|
|
||||||
@@ -206,7 +206,7 @@ tags:
|
|||||||
|
|
||||||
nameChange := changes.Changes[0]
|
nameChange := changes.Changes[0]
|
||||||
assert.Equal(t, Moved, nameChange.ChangeType)
|
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.Equal(t, 8, nameChange.Context.NewLine)
|
||||||
assert.True(t, nameChange.Context.HasChanged())
|
assert.True(t, nameChange.Context.HasChanged())
|
||||||
}
|
}
|
||||||
@@ -246,7 +246,7 @@ tags:
|
|||||||
|
|
||||||
descChange := changes.Changes[1]
|
descChange := changes.Changes[1]
|
||||||
assert.Equal(t, ModifiedAndMoved, descChange.ChangeType)
|
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, 5, descChange.Context.NewLine)
|
||||||
assert.Equal(t, "a lovelier tag description", descChange.Original)
|
assert.Equal(t, "a lovelier tag description", descChange.Original)
|
||||||
assert.Equal(t, "a different tag description", descChange.New)
|
assert.Equal(t, "a different tag description", descChange.New)
|
||||||
|
|||||||
@@ -3,197 +3,3 @@
|
|||||||
|
|
||||||
package what_changed
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user