Fixed small glitch in what-changed for checking nil values.

gofmt does not operate the same across mac and windows, this is wht the whole file got shuffled. odd.
This commit is contained in:
Dave Shanley
2022-11-27 12:50:20 -05:00
parent 0e0b99225d
commit d6ca1802f6

View File

@@ -4,82 +4,82 @@
package model package model
import ( import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strings" "strings"
"sync" "sync"
) )
const ( const (
HashPh = "%x" HashPh = "%x"
EMPTY_STR = "" EMPTY_STR = ""
) )
// CreateChange is a generic function that will create a Change of type T, populate all properties if set, and then // 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 // add a pointer to Change[T] in the slice of Change pointers provided
func CreateChange(changes *[]*Change, changeType int, property string, leftValueNode, rightValueNode *yaml.Node, func CreateChange(changes *[]*Change, changeType int, property string, leftValueNode, rightValueNode *yaml.Node,
breaking bool, originalObject, newObject any) *[]*Change { breaking bool, originalObject, newObject any) *[]*Change {
// create a new context for the left and right nodes. // create a new context for the left and right nodes.
ctx := CreateContext(leftValueNode, rightValueNode) ctx := CreateContext(leftValueNode, rightValueNode)
c := &Change{ c := &Change{
Context: ctx, Context: ctx,
ChangeType: changeType, ChangeType: changeType,
Property: property, Property: property,
Breaking: breaking, Breaking: breaking,
} }
// if the left is not nil, we have an original value // if the left is not nil, we have an original value
if leftValueNode != nil && leftValueNode.Value != "" { if leftValueNode != nil && leftValueNode.Value != "" {
c.Original = leftValueNode.Value c.Original = leftValueNode.Value
} }
// if the right is not nil, then we have a new value // if the right is not nil, then we have a new value
if rightValueNode != nil && rightValueNode.Value != "" { if rightValueNode != nil && rightValueNode.Value != "" {
c.New = rightValueNode.Value c.New = rightValueNode.Value
} }
// original and new objects // original and new objects
c.OriginalObject = originalObject c.OriginalObject = originalObject
c.NewObject = newObject c.NewObject = newObject
// add the change to supplied changes slice // add the change to supplied changes slice
*changes = append(*changes, c) *changes = append(*changes, c)
return changes return changes
} }
// CreateContext will return a pointer to a ChangeContext containing the original and new line and column numbers // CreateContext will return a pointer to a ChangeContext containing the original and new line and column numbers
// of the left and right value nodes. // of the left and right value nodes.
func CreateContext(l, r *yaml.Node) *ChangeContext { func CreateContext(l, r *yaml.Node) *ChangeContext {
ctx := new(ChangeContext) ctx := new(ChangeContext)
if l != nil { if l != nil {
ctx.OriginalLine = &l.Line ctx.OriginalLine = &l.Line
ctx.OriginalColumn = &l.Column ctx.OriginalColumn = &l.Column
} }
if r != nil { if r != nil {
ctx.NewLine = &r.Line ctx.NewLine = &r.Line
ctx.NewColumn = &r.Column ctx.NewColumn = &r.Column
} }
return ctx return ctx
} }
func FlattenLowLevelMap[T any]( func FlattenLowLevelMap[T any](
lowMap map[low.KeyReference[string]]low.ValueReference[T]) map[string]*low.ValueReference[T] { lowMap map[low.KeyReference[string]]low.ValueReference[T]) map[string]*low.ValueReference[T] {
flat := make(map[string]*low.ValueReference[T]) flat := make(map[string]*low.ValueReference[T])
for i := range lowMap { for i := range lowMap {
l := lowMap[i] l := lowMap[i]
flat[i.Value] = &l flat[i.Value] = &l
} }
return flat return flat
} }
// CountBreakingChanges counts the number of changes in a slice that are breaking // CountBreakingChanges counts the number of changes in a slice that are breaking
func CountBreakingChanges(changes []*Change) int { func CountBreakingChanges(changes []*Change) int {
b := 0 b := 0
for i := range changes { for i := range changes {
if changes[i].Breaking { if changes[i].Breaking {
b++ b++
} }
} }
return b return b
} }
// CheckForObjectAdditionOrRemoval will check for the addition or removal of an object from left and right maps. // CheckForObjectAdditionOrRemoval will check for the addition or removal of an object from left and right maps.
@@ -90,28 +90,28 @@ func CountBreakingChanges(changes []*Change) int {
// properties like descriptions, summaries and other non-binding values, so a breakingRemove value can be tuned for // properties like descriptions, summaries and other non-binding values, so a breakingRemove value can be tuned for
// these circumstances. // these circumstances.
func CheckForObjectAdditionOrRemoval[T any](l, r map[string]*low.ValueReference[T], label string, changes *[]*Change, func CheckForObjectAdditionOrRemoval[T any](l, r map[string]*low.ValueReference[T], label string, changes *[]*Change,
breakingAdd, breakingRemove bool) { breakingAdd, breakingRemove bool) {
var left, right T var left, right T
if CheckSpecificObjectRemoved(l, r, label) { if CheckSpecificObjectRemoved(l, r, label) {
left = l[label].GetValue() left = l[label].GetValue()
CreateChange(changes, ObjectRemoved, label, l[label].GetValueNode(), nil, CreateChange(changes, ObjectRemoved, label, l[label].GetValueNode(), nil,
breakingRemove, left, right) breakingRemove, left, nil)
} }
if CheckSpecificObjectAdded(l, r, label) { if CheckSpecificObjectAdded(l, r, label) {
right = r[label].GetValue() right = r[label].GetValue()
CreateChange(changes, ObjectAdded, label, nil, r[label].GetValueNode(), CreateChange(changes, ObjectAdded, label, nil, r[label].GetValueNode(),
breakingAdd, left, right) breakingAdd, nil, right)
} }
} }
// CheckSpecificObjectRemoved returns true if a specific value is not in both maps. // CheckSpecificObjectRemoved returns true if a specific value is not in both maps.
func CheckSpecificObjectRemoved[T any](l, r map[string]*T, label string) bool { func CheckSpecificObjectRemoved[T any](l, r map[string]*T, label string) bool {
return l[label] != nil && r[label] == nil return l[label] != nil && r[label] == nil
} }
// CheckSpecificObjectAdded returns true if a specific value is not in both maps. // CheckSpecificObjectAdded returns true if a specific value is not in both maps.
func CheckSpecificObjectAdded[T any](l, r map[string]*T, label string) bool { func CheckSpecificObjectAdded[T any](l, r map[string]*T, label string) bool {
return l[label] == nil && r[label] != nil return l[label] == nil && r[label] != nil
} }
// CheckProperties will iterate through a slice of PropertyCheck pointers of type T. The method is a convenience method // CheckProperties will iterate through a slice of PropertyCheck pointers of type T. The method is a convenience method
@@ -120,18 +120,18 @@ func CheckSpecificObjectAdded[T any](l, r map[string]*T, label string) bool {
// CheckForModification // CheckForModification
func CheckProperties(properties []*PropertyCheck) { func CheckProperties(properties []*PropertyCheck) {
// todo: make this async to really speed things up. // todo: make this async to really speed things up.
for _, n := range properties { for _, n := range properties {
CheckPropertyAdditionOrRemoval(n.LeftNode, n.RightNode, n.Label, n.Changes, n.Breaking, n.Original, n.New) 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) CheckForModification(n.LeftNode, n.RightNode, n.Label, n.Changes, n.Breaking, n.Original, n.New)
} }
} }
// CheckPropertyAdditionOrRemoval will run both CheckForRemoval (first) and CheckForAddition (second) // CheckPropertyAdditionOrRemoval will run both CheckForRemoval (first) and CheckForAddition (second)
func CheckPropertyAdditionOrRemoval[T any](l, r *yaml.Node, func CheckPropertyAdditionOrRemoval[T any](l, r *yaml.Node,
label string, changes *[]*Change, breaking bool, orig, new T) { label string, changes *[]*Change, breaking bool, orig, new T) {
CheckForRemoval[T](l, r, label, changes, breaking, orig, new) CheckForRemoval[T](l, r, label, changes, breaking, orig, new)
CheckForAddition[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 // CheckForRemoval will check left and right yaml.Node instances for changes. Anything that is found missing on the
@@ -141,13 +141,13 @@ func CheckPropertyAdditionOrRemoval[T any](l, r *yaml.Node,
// //
// The Change is then added to the slice of []Change[T] instances provided as a pointer. // 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, breaking bool, orig, new T) { func CheckForRemoval[T any](l, r *yaml.Node, label string, changes *[]*Change, breaking bool, orig, new T) {
if l != nil && l.Value != "" && (r == nil || r.Value == "") { if l != nil && l.Value != "" && (r == nil || r.Value == "") {
CreateChange(changes, PropertyRemoved, label, l, r, breaking, orig, new) CreateChange(changes, PropertyRemoved, label, l, r, breaking, orig, new)
return return
} }
if l != nil && r == nil { if l != nil && r == nil {
CreateChange(changes, PropertyRemoved, label, l, nil, breaking, orig, nil) CreateChange(changes, PropertyRemoved, label, l, nil, breaking, orig, nil)
} }
} }
// CheckForAddition will check left and right yaml.Node instances for changes. Anything that is found missing on the // CheckForAddition will check left and right yaml.Node instances for changes. Anything that is found missing on the
@@ -157,9 +157,9 @@ func CheckForRemoval[T any](l, r *yaml.Node, label string, changes *[]*Change, b
// //
// The Change is then added to the slice of []Change[T] instances provided as a pointer. // 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, breaking bool, orig, new T) { func CheckForAddition[T any](l, r *yaml.Node, label string, changes *[]*Change, breaking bool, orig, new T) {
if (l == nil || l.Value == "") && r != nil && r.Value != "" { if (l == nil || l.Value == "") && r != nil && r.Value != "" {
CreateChange(changes, PropertyAdded, label, l, r, breaking, orig, new) CreateChange(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 // CheckForModification will check left and right yaml.Node instances for changes. Anything that is found in both
@@ -169,31 +169,31 @@ func CheckForAddition[T any](l, r *yaml.Node, label string, changes *[]*Change,
// //
// The Change is then added to the slice of []Change[T] instances provided as a pointer. // 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, breaking bool, orig, new T) { func CheckForModification[T any](l, r *yaml.Node, label string, changes *[]*Change, breaking bool, orig, new T) {
if l != nil && l.Value != "" && r != nil && r.Value != "" && r.Value != l.Value && r.Tag == l.Tag { if l != nil && l.Value != "" && r != nil && r.Value != "" && r.Value != l.Value && r.Tag == l.Tag {
CreateChange(changes, Modified, label, l, r, breaking, orig, new) CreateChange(changes, Modified, label, l, r, breaking, orig, new)
return return
} }
// the values may have not changed, but the tag (node type) type may have // the values may have not changed, but the tag (node type) type may have
if l != nil && l.Value != "" && r != nil && r.Value != "" && r.Value != l.Value && r.Tag != l.Tag { if l != nil && l.Value != "" && r != nil && r.Value != "" && r.Value != l.Value && r.Tag != l.Tag {
CreateChange(changes, Modified, label, l, r, breaking, orig, new) CreateChange(changes, Modified, label, l, r, breaking, orig, new)
return return
} }
} }
// CheckMapForChanges checks a left and right low level map for any additions, subtractions or modifications to // CheckMapForChanges checks a left and right low level map for any additions, subtractions or modifications to
// values. The compareFunc argument should reference the correct comparison function for the generic type. // values. The compareFunc argument should reference the correct comparison function for the generic type.
func CheckMapForChanges[T any, R any](expLeft, expRight map[low.KeyReference[string]]low.ValueReference[T], func CheckMapForChanges[T any, R any](expLeft, expRight map[low.KeyReference[string]]low.ValueReference[T],
changes *[]*Change, label string, compareFunc func(l, r T) R) map[string]R { changes *[]*Change, label string, compareFunc func(l, r T) R) map[string]R {
return CheckMapForChangesWithComp(expLeft, expRight, changes, label, compareFunc, true) return CheckMapForChangesWithComp(expLeft, expRight, changes, label, compareFunc, true)
} }
func CheckMapForAdditionRemoval[T any](expLeft, expRight map[low.KeyReference[string]]low.ValueReference[T], func CheckMapForAdditionRemoval[T any](expLeft, expRight map[low.KeyReference[string]]low.ValueReference[T],
changes *[]*Change, label string) any { changes *[]*Change, label string) any {
// do nothing // do nothing
doNothing := func(l, r T) any { doNothing := func(l, r T) any {
return nil return nil
} }
return CheckMapForChangesWithComp(expLeft, expRight, changes, label, doNothing, false) return CheckMapForChangesWithComp(expLeft, expRight, changes, label, doNothing, false)
} }
//// CheckMapForAdditionRemoval checks a left and right low level map for any additions or subtractions, but not modifications //// CheckMapForAdditionRemoval checks a left and right low level map for any additions or subtractions, but not modifications
@@ -211,168 +211,168 @@ func CheckMapForAdditionRemoval[T any](expLeft, expRight map[low.KeyReference[st
// values. The compareFunc argument should reference the correct comparison function for the generic type. The compare // values. The compareFunc argument should reference the correct comparison function for the generic type. The compare
// bit determines if the comparison should be run or not. // bit determines if the comparison should be run or not.
func CheckMapForChangesWithComp[T any, R any](expLeft, expRight map[low.KeyReference[string]]low.ValueReference[T], func CheckMapForChangesWithComp[T any, R any](expLeft, expRight map[low.KeyReference[string]]low.ValueReference[T],
changes *[]*Change, label string, compareFunc func(l, r T) R, compare bool) map[string]R { changes *[]*Change, label string, compareFunc func(l, r T) R, compare bool) map[string]R {
// stop concurrent threads screwing up changes. // stop concurrent threads screwing up changes.
var chLock sync.Mutex var chLock sync.Mutex
lHashes := make(map[string]string) lHashes := make(map[string]string)
rHashes := make(map[string]string) rHashes := make(map[string]string)
lValues := make(map[string]low.ValueReference[T]) lValues := make(map[string]low.ValueReference[T])
rValues := make(map[string]low.ValueReference[T]) rValues := make(map[string]low.ValueReference[T])
for k := range expLeft { for k := range expLeft {
lHashes[k.Value] = low.GenerateHashString(expLeft[k].Value) lHashes[k.Value] = low.GenerateHashString(expLeft[k].Value)
lValues[k.Value] = expLeft[k] lValues[k.Value] = expLeft[k]
} }
for k := range expRight { for k := range expRight {
rHashes[k.Value] = low.GenerateHashString(expRight[k].Value) rHashes[k.Value] = low.GenerateHashString(expRight[k].Value)
rValues[k.Value] = expRight[k] rValues[k.Value] = expRight[k]
} }
expChanges := make(map[string]R) expChanges := make(map[string]R)
checkLeft := func(k string, doneChan chan bool, f, g map[string]string, p, h map[string]low.ValueReference[T]) { checkLeft := func(k string, doneChan chan bool, f, g map[string]string, p, h map[string]low.ValueReference[T]) {
rhash := g[k] rhash := g[k]
if rhash == "" { if rhash == "" {
if p[k].GetValueNode().Value == "" { if p[k].GetValueNode().Value == "" {
p[k].GetValueNode().Value = k p[k].GetValueNode().Value = k
} }
chLock.Lock() chLock.Lock()
CreateChange(changes, ObjectRemoved, label, CreateChange(changes, ObjectRemoved, label,
p[k].GetValueNode(), nil, true, p[k].GetValueNode(), nil, true,
p[k].GetValue(), nil) p[k].GetValue(), nil)
chLock.Unlock() chLock.Unlock()
doneChan <- true doneChan <- true
return return
} }
if f[k] == g[k] { if f[k] == g[k] {
doneChan <- true doneChan <- true
return return
} }
// run comparison. // run comparison.
if compare { if compare {
chLock.Lock() chLock.Lock()
expChanges[k] = compareFunc(p[k].Value, h[k].Value) expChanges[k] = compareFunc(p[k].Value, h[k].Value)
chLock.Unlock() chLock.Unlock()
} }
doneChan <- true doneChan <- true
} }
doneChan := make(chan bool) doneChan := make(chan bool)
count := 0 count := 0
// check left example hashes // check left example hashes
for k := range lHashes { for k := range lHashes {
count++ count++
go checkLeft(k, doneChan, lHashes, rHashes, lValues, rValues) go checkLeft(k, doneChan, lHashes, rHashes, lValues, rValues)
} }
//check right example hashes //check right example hashes
for k := range rHashes { for k := range rHashes {
count++ count++
go checkRightValue(k, doneChan, lHashes, rValues, changes, label, &chLock) go checkRightValue(k, doneChan, lHashes, rValues, changes, label, &chLock)
} }
// wait for all done signals. // wait for all done signals.
completed := 0 completed := 0
for completed < count { for completed < count {
select { select {
case <-doneChan: case <-doneChan:
completed++ completed++
} }
} }
return expChanges return expChanges
} }
func checkRightValue[T any](k string, doneChan chan bool, f map[string]string, p map[string]low.ValueReference[T], func checkRightValue[T any](k string, doneChan chan bool, f map[string]string, p map[string]low.ValueReference[T],
changes *[]*Change, label string, lock *sync.Mutex) { changes *[]*Change, label string, lock *sync.Mutex) {
lhash := f[k] lhash := f[k]
if lhash == "" { if lhash == "" {
if p[k].GetValueNode().Value == "" { if p[k].GetValueNode().Value == "" {
p[k].GetValueNode().Value = k // this is kinda dirty, but I don't want to duplicate code so sue me. p[k].GetValueNode().Value = k // this is kinda dirty, but I don't want to duplicate code so sue me.
} }
lock.Lock() lock.Lock()
CreateChange(changes, ObjectAdded, label, CreateChange(changes, ObjectAdded, label,
nil, p[k].GetValueNode(), false, nil, p[k].GetValueNode(), false,
nil, p[k].GetValue()) nil, p[k].GetValue())
lock.Unlock() lock.Unlock()
} }
doneChan <- true doneChan <- true
} }
// ExtractStringValueSliceChanges will compare two low level string slices for changes. // ExtractStringValueSliceChanges will compare two low level string slices for changes.
func ExtractStringValueSliceChanges(lParam, rParam []low.ValueReference[string], func ExtractStringValueSliceChanges(lParam, rParam []low.ValueReference[string],
changes *[]*Change, label string, breaking bool) { changes *[]*Change, label string, breaking bool) {
lKeys := make([]string, len(lParam)) lKeys := make([]string, len(lParam))
rKeys := make([]string, len(rParam)) rKeys := make([]string, len(rParam))
lValues := make(map[string]low.ValueReference[string]) lValues := make(map[string]low.ValueReference[string])
rValues := make(map[string]low.ValueReference[string]) rValues := make(map[string]low.ValueReference[string])
for i := range lParam { for i := range lParam {
lKeys[i] = strings.ToLower(lParam[i].Value) lKeys[i] = strings.ToLower(lParam[i].Value)
lValues[lKeys[i]] = lParam[i] lValues[lKeys[i]] = lParam[i]
} }
for i := range rParam { for i := range rParam {
rKeys[i] = strings.ToLower(rParam[i].Value) rKeys[i] = strings.ToLower(rParam[i].Value)
rValues[rKeys[i]] = rParam[i] rValues[rKeys[i]] = rParam[i]
} }
for i := range lValues { for i := range lValues {
if _, ok := rValues[i]; !ok { if _, ok := rValues[i]; !ok {
CreateChange(changes, PropertyRemoved, label, CreateChange(changes, PropertyRemoved, label,
lValues[i].ValueNode, lValues[i].ValueNode,
nil, nil,
breaking, breaking,
lValues[i].Value, lValues[i].Value,
nil) nil)
} }
} }
for i := range rValues { for i := range rValues {
if _, ok := lValues[i]; !ok { if _, ok := lValues[i]; !ok {
CreateChange(changes, PropertyAdded, label, CreateChange(changes, PropertyAdded, label,
nil, nil,
rValues[i].ValueNode, rValues[i].ValueNode,
false, false,
nil, nil,
rValues[i].Value) rValues[i].Value)
} }
} }
} }
// ExtractRawValueSliceChanges will compare two low level interface{} slices for changes. // ExtractRawValueSliceChanges will compare two low level interface{} slices for changes.
func ExtractRawValueSliceChanges(lParam, rParam []low.ValueReference[any], func ExtractRawValueSliceChanges(lParam, rParam []low.ValueReference[any],
changes *[]*Change, label string, breaking bool) { changes *[]*Change, label string, breaking bool) {
lKeys := make([]string, len(lParam)) lKeys := make([]string, len(lParam))
rKeys := make([]string, len(rParam)) rKeys := make([]string, len(rParam))
lValues := make(map[string]low.ValueReference[any]) lValues := make(map[string]low.ValueReference[any])
rValues := make(map[string]low.ValueReference[any]) rValues := make(map[string]low.ValueReference[any])
for i := range lParam { for i := range lParam {
lKeys[i] = strings.ToLower(fmt.Sprint(lParam[i].Value)) lKeys[i] = strings.ToLower(fmt.Sprint(lParam[i].Value))
lValues[lKeys[i]] = lParam[i] lValues[lKeys[i]] = lParam[i]
} }
for i := range rParam { for i := range rParam {
rKeys[i] = strings.ToLower(fmt.Sprint(rParam[i].Value)) rKeys[i] = strings.ToLower(fmt.Sprint(rParam[i].Value))
rValues[rKeys[i]] = rParam[i] rValues[rKeys[i]] = rParam[i]
} }
for i := range lValues { for i := range lValues {
if _, ok := rValues[i]; !ok { if _, ok := rValues[i]; !ok {
CreateChange(changes, PropertyRemoved, label, CreateChange(changes, PropertyRemoved, label,
lValues[i].ValueNode, lValues[i].ValueNode,
nil, nil,
breaking, breaking,
lValues[i].Value, lValues[i].Value,
nil) nil)
} }
} }
for i := range rValues { for i := range rValues {
if _, ok := lValues[i]; !ok { if _, ok := lValues[i]; !ok {
CreateChange(changes, PropertyAdded, label, CreateChange(changes, PropertyAdded, label,
nil, nil,
rValues[i].ValueNode, rValues[i].ValueNode,
false, false,
nil, nil,
rValues[i].Value) rValues[i].Value)
} }
} }
} }