mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-11 04:20:24 +00:00
Added discriminator checks and cleaned up some more patterns
Still quite early in the pattern finding process, needs another sweep to cache and fetch more pre-calculated change counts. Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
@@ -51,4 +51,5 @@ const (
|
|||||||
PrefixLabel = "prefix"
|
PrefixLabel = "prefix"
|
||||||
AttributeLabel = "attribute"
|
AttributeLabel = "attribute"
|
||||||
WrappedLabel = "wrapped"
|
WrappedLabel = "wrapped"
|
||||||
|
PropertyNameLabel = "propertyName"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -59,6 +59,27 @@ func CreateContext(l, r *yaml.Node) *ChangeContext {
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FlattenLowLevelMap[T any](
|
||||||
|
lowMap map[low.KeyReference[string]]low.ValueReference[T]) map[string]*low.ValueReference[T] {
|
||||||
|
flat := make(map[string]*low.ValueReference[T])
|
||||||
|
for i := range lowMap {
|
||||||
|
l := lowMap[i]
|
||||||
|
flat[i.Value] = &l
|
||||||
|
}
|
||||||
|
return flat
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountBreakingChanges counts the number of changes in a slice that are breaking
|
||||||
|
func CountBreakingChanges[T any](changes []*Change[T]) int {
|
||||||
|
b := 0
|
||||||
|
for i := range changes {
|
||||||
|
if changes[i].Breaking {
|
||||||
|
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.
|
||||||
// The label is the key to look for in the left and right maps.
|
// The label is the key to look for in the left and right maps.
|
||||||
//
|
//
|
||||||
@@ -69,36 +90,26 @@ func CreateContext(l, r *yaml.Node) *ChangeContext {
|
|||||||
func CheckForObjectAdditionOrRemoval[T any](l, r map[string]*low.ValueReference[T], label string, changes *[]*Change[T],
|
func CheckForObjectAdditionOrRemoval[T any](l, r map[string]*low.ValueReference[T], label string, changes *[]*Change[T],
|
||||||
breakingAdd, breakingRemove bool) {
|
breakingAdd, breakingRemove bool) {
|
||||||
var left, right T
|
var left, right T
|
||||||
if CheckObjectRemoved(l, r) {
|
if CheckSpecificObjectRemoved(l, r, label) {
|
||||||
left = l[label].GetValue()
|
left = l[label].GetValue()
|
||||||
CreateChange[T](changes, ObjectRemoved, label, l[label].GetValueNode(), nil,
|
CreateChange[T](changes, ObjectRemoved, label, l[label].GetValueNode(), nil,
|
||||||
breakingRemove, left, right)
|
breakingRemove, left, right)
|
||||||
}
|
}
|
||||||
if added, key := CheckObjectAdded(l, r); added {
|
if CheckSpecificObjectAdded(l, r, label) {
|
||||||
right = r[key].GetValue()
|
right = r[label].GetValue()
|
||||||
CreateChange[T](changes, ObjectAdded, label, nil, r[key].GetValueNode(),
|
CreateChange[T](changes, ObjectAdded, label, nil, r[label].GetValueNode(),
|
||||||
breakingAdd, left, right)
|
breakingAdd, left, right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckObjectRemoved returns true if a key/value in the left map is not present in the right.
|
// CheckSpecificObjectRemoved returns true if a specific value is not in both maps.
|
||||||
func CheckObjectRemoved[T any](l, r map[string]*T) bool {
|
func CheckSpecificObjectRemoved[T any](l, r map[string]*T, label string) bool {
|
||||||
for i := range l {
|
return l[label] != nil && r[label] == nil
|
||||||
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.
|
// CheckSpecificObjectAdded returns true if a specific value is not in both maps.
|
||||||
func CheckObjectAdded[T any](l, r map[string]*T) (bool, string) {
|
func CheckSpecificObjectAdded[T any](l, r map[string]*T, label string) bool {
|
||||||
for i := range r {
|
return l[label] == nil && r[label] != nil
|
||||||
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
|
// CheckProperties will iterate through a slice of PropertyCheck pointers of type T. The method is a convenience method
|
||||||
|
|||||||
79
what-changed/discriminator.go
Normal file
79
what-changed/discriminator.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiscriminatorChanges represents changes made to a Discriminator OpenAPI object
|
||||||
|
type DiscriminatorChanges struct {
|
||||||
|
PropertyChanges[*base.Discriminator]
|
||||||
|
MappingChanges []*Change[string]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalChanges returns a count of everything changed within the Discriminator object
|
||||||
|
func (d *DiscriminatorChanges) TotalChanges() int {
|
||||||
|
l := 0
|
||||||
|
if k := len(d.Changes); k > 0 {
|
||||||
|
l += k
|
||||||
|
}
|
||||||
|
if k := len(d.MappingChanges); k > 0 {
|
||||||
|
l += k
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompareDiscriminator(l, r *base.Discriminator) *DiscriminatorChanges {
|
||||||
|
dc := new(DiscriminatorChanges)
|
||||||
|
var changes []*Change[*base.Discriminator]
|
||||||
|
var props []*PropertyCheck[*base.Discriminator]
|
||||||
|
var mapping []*Change[string]
|
||||||
|
|
||||||
|
// Name (breaking change)
|
||||||
|
props = append(props, &PropertyCheck[*base.Discriminator]{
|
||||||
|
LeftNode: l.PropertyName.ValueNode,
|
||||||
|
RightNode: r.PropertyName.ValueNode,
|
||||||
|
Label: v3.PropertyNameLabel,
|
||||||
|
Changes: &changes,
|
||||||
|
Breaking: true,
|
||||||
|
Original: l,
|
||||||
|
New: r,
|
||||||
|
})
|
||||||
|
|
||||||
|
// check properties
|
||||||
|
CheckProperties(props)
|
||||||
|
|
||||||
|
// flatten maps
|
||||||
|
lMap := FlattenLowLevelMap[string](l.Mapping)
|
||||||
|
rMap := FlattenLowLevelMap[string](r.Mapping)
|
||||||
|
|
||||||
|
// check for removals, modifications and moves
|
||||||
|
for i := range lMap {
|
||||||
|
CheckForObjectAdditionOrRemoval[string](lMap, rMap, i, &mapping, false, true)
|
||||||
|
// if the existing tag exists, let's check it.
|
||||||
|
if rMap[i] != nil {
|
||||||
|
if lMap[i].Value != rMap[i].Value {
|
||||||
|
CreateChange[string](&mapping, Modified, i, lMap[i].GetValueNode(),
|
||||||
|
rMap[i].GetValueNode(), true, lMap[i].GetValue(), rMap[i].GetValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range rMap {
|
||||||
|
if lMap[i] == nil {
|
||||||
|
CreateChange[string](&mapping, ObjectAdded, i, nil,
|
||||||
|
rMap[i].GetValueNode(), false, nil, rMap[i].GetValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(changes) <= 0 && len(mapping) <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dc.Changes = changes
|
||||||
|
dc.MappingChanges = mapping
|
||||||
|
return dc
|
||||||
|
|
||||||
|
}
|
||||||
256
what-changed/discriminator_test.go
Normal file
256
what-changed/discriminator_test.go
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
// 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 TestCompareDiscriminator_PropertyNameChanged(t *testing.T) {
|
||||||
|
|
||||||
|
left := `propertyName: chicken`
|
||||||
|
|
||||||
|
right := `propertyName: nuggets`
|
||||||
|
|
||||||
|
var lNode, rNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||||
|
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||||
|
|
||||||
|
// create low level objects
|
||||||
|
var lDoc base.Discriminator
|
||||||
|
var rDoc base.Discriminator
|
||||||
|
_ = low.BuildModel(&lNode, &lDoc)
|
||||||
|
_ = low.BuildModel(&rNode, &rDoc)
|
||||||
|
|
||||||
|
// compare.
|
||||||
|
extChanges := CompareDiscriminator(&lDoc, &rDoc)
|
||||||
|
assert.Equal(t, 1, extChanges.TotalChanges())
|
||||||
|
assert.Equal(t, Modified, extChanges.Changes[0].ChangeType)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompareDiscriminator_PropertyNameRemoved(t *testing.T) {
|
||||||
|
|
||||||
|
left := `propertyName: chicken`
|
||||||
|
|
||||||
|
right := ``
|
||||||
|
|
||||||
|
var lNode, rNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||||
|
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||||
|
|
||||||
|
// create low level objects
|
||||||
|
var lDoc base.Discriminator
|
||||||
|
var rDoc base.Discriminator
|
||||||
|
_ = low.BuildModel(&lNode, &lDoc)
|
||||||
|
_ = low.BuildModel(&rNode, &rDoc)
|
||||||
|
|
||||||
|
// compare.
|
||||||
|
extChanges := CompareDiscriminator(&lDoc, &rDoc)
|
||||||
|
assert.Equal(t, 1, extChanges.TotalChanges())
|
||||||
|
assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompareDiscriminator_PropertyNameAdded(t *testing.T) {
|
||||||
|
|
||||||
|
left := ``
|
||||||
|
|
||||||
|
right := `propertyName: chicken`
|
||||||
|
|
||||||
|
var lNode, rNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||||
|
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||||
|
|
||||||
|
// create low level objects
|
||||||
|
var lDoc base.Discriminator
|
||||||
|
var rDoc base.Discriminator
|
||||||
|
_ = low.BuildModel(&lNode, &lDoc)
|
||||||
|
_ = low.BuildModel(&rNode, &rDoc)
|
||||||
|
|
||||||
|
// compare.
|
||||||
|
extChanges := CompareDiscriminator(&lDoc, &rDoc)
|
||||||
|
assert.Equal(t, 1, extChanges.TotalChanges())
|
||||||
|
assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompareDiscriminator_MappingAdded(t *testing.T) {
|
||||||
|
|
||||||
|
left := `propertyName: chicken`
|
||||||
|
|
||||||
|
right := `propertyName: chicken
|
||||||
|
mapping:
|
||||||
|
chuffing: puffing
|
||||||
|
hacking: coding`
|
||||||
|
|
||||||
|
var lNode, rNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||||
|
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||||
|
|
||||||
|
// create low level objects
|
||||||
|
var lDoc base.Discriminator
|
||||||
|
var rDoc base.Discriminator
|
||||||
|
_ = low.BuildModel(&lNode, &lDoc)
|
||||||
|
_ = low.BuildModel(&rNode, &rDoc)
|
||||||
|
|
||||||
|
// compare.
|
||||||
|
extChanges := CompareDiscriminator(&lDoc, &rDoc)
|
||||||
|
assert.Equal(t, 2, extChanges.TotalChanges())
|
||||||
|
assert.Equal(t, ObjectAdded, extChanges.MappingChanges[0].ChangeType)
|
||||||
|
assert.Equal(t, ObjectAdded, extChanges.MappingChanges[1].ChangeType)
|
||||||
|
assert.Equal(t, "chuffing", extChanges.MappingChanges[0].Property)
|
||||||
|
assert.Equal(t, "puffing", extChanges.MappingChanges[0].New)
|
||||||
|
assert.Equal(t, "hacking", extChanges.MappingChanges[1].Property)
|
||||||
|
assert.Equal(t, "coding", extChanges.MappingChanges[1].New)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompareDiscriminator_MappingRemoved(t *testing.T) {
|
||||||
|
|
||||||
|
left := `propertyName: chicken
|
||||||
|
mapping:
|
||||||
|
chuffing: puffing
|
||||||
|
hacking: coding`
|
||||||
|
|
||||||
|
right := `propertyName: chicken
|
||||||
|
mapping:
|
||||||
|
hacking: coding`
|
||||||
|
|
||||||
|
var lNode, rNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||||
|
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||||
|
|
||||||
|
// create low level objects
|
||||||
|
var lDoc base.Discriminator
|
||||||
|
var rDoc base.Discriminator
|
||||||
|
_ = low.BuildModel(&lNode, &lDoc)
|
||||||
|
_ = low.BuildModel(&rNode, &rDoc)
|
||||||
|
|
||||||
|
// compare.
|
||||||
|
extChanges := CompareDiscriminator(&lDoc, &rDoc)
|
||||||
|
assert.Equal(t, 1, extChanges.TotalChanges())
|
||||||
|
assert.Equal(t, ObjectRemoved, extChanges.MappingChanges[0].ChangeType)
|
||||||
|
assert.Equal(t, "chuffing", extChanges.MappingChanges[0].Property)
|
||||||
|
assert.Equal(t, "puffing", extChanges.MappingChanges[0].Original)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompareDiscriminator_SingleMappingAdded(t *testing.T) {
|
||||||
|
|
||||||
|
left := `propertyName: chicken
|
||||||
|
mapping:
|
||||||
|
chuffing: puffing`
|
||||||
|
|
||||||
|
right := `propertyName: chicken
|
||||||
|
mapping:
|
||||||
|
chuffing: puffing
|
||||||
|
hacking: coding`
|
||||||
|
|
||||||
|
var lNode, rNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||||
|
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||||
|
|
||||||
|
// create low level objects
|
||||||
|
var lDoc base.Discriminator
|
||||||
|
var rDoc base.Discriminator
|
||||||
|
_ = low.BuildModel(&lNode, &lDoc)
|
||||||
|
_ = low.BuildModel(&rNode, &rDoc)
|
||||||
|
|
||||||
|
// compare.
|
||||||
|
extChanges := CompareDiscriminator(&lDoc, &rDoc)
|
||||||
|
assert.Equal(t, 1, extChanges.TotalChanges())
|
||||||
|
assert.Equal(t, ObjectAdded, extChanges.MappingChanges[0].ChangeType)
|
||||||
|
assert.Equal(t, "hacking", extChanges.MappingChanges[0].Property)
|
||||||
|
assert.Equal(t, "coding", extChanges.MappingChanges[0].New)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompareDiscriminator_MultiMappingAdded(t *testing.T) {
|
||||||
|
|
||||||
|
left := `propertyName: chicken
|
||||||
|
mapping:
|
||||||
|
chuffing: puffing`
|
||||||
|
|
||||||
|
right := `propertyName: chicken
|
||||||
|
mapping:
|
||||||
|
chuffing: puffing
|
||||||
|
hacking: coding
|
||||||
|
singing: dancing`
|
||||||
|
|
||||||
|
var lNode, rNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||||
|
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||||
|
|
||||||
|
// create low level objects
|
||||||
|
var lDoc base.Discriminator
|
||||||
|
var rDoc base.Discriminator
|
||||||
|
_ = low.BuildModel(&lNode, &lDoc)
|
||||||
|
_ = low.BuildModel(&rNode, &rDoc)
|
||||||
|
|
||||||
|
// compare.
|
||||||
|
extChanges := CompareDiscriminator(&lDoc, &rDoc)
|
||||||
|
assert.Equal(t, 2, extChanges.TotalChanges())
|
||||||
|
assert.Equal(t, ObjectAdded, extChanges.MappingChanges[0].ChangeType)
|
||||||
|
assert.Equal(t, "hacking", extChanges.MappingChanges[0].Property)
|
||||||
|
assert.Equal(t, "coding", extChanges.MappingChanges[0].New)
|
||||||
|
assert.Equal(t, "singing", extChanges.MappingChanges[1].Property)
|
||||||
|
assert.Equal(t, "dancing", extChanges.MappingChanges[1].New)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompareDiscriminator_SingleMappingModified(t *testing.T) {
|
||||||
|
|
||||||
|
left := `propertyName: chicken
|
||||||
|
mapping:
|
||||||
|
chuffing: puffing`
|
||||||
|
|
||||||
|
right := `propertyName: chicken
|
||||||
|
mapping:
|
||||||
|
chuffing: herbs`
|
||||||
|
|
||||||
|
var lNode, rNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||||
|
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||||
|
|
||||||
|
// create low level objects
|
||||||
|
var lDoc base.Discriminator
|
||||||
|
var rDoc base.Discriminator
|
||||||
|
_ = low.BuildModel(&lNode, &lDoc)
|
||||||
|
_ = low.BuildModel(&rNode, &rDoc)
|
||||||
|
|
||||||
|
// compare.
|
||||||
|
extChanges := CompareDiscriminator(&lDoc, &rDoc)
|
||||||
|
assert.Equal(t, 1, extChanges.TotalChanges())
|
||||||
|
assert.Equal(t, Modified, extChanges.MappingChanges[0].ChangeType)
|
||||||
|
assert.Equal(t, "chuffing", extChanges.MappingChanges[0].Property)
|
||||||
|
assert.Equal(t, "herbs", extChanges.MappingChanges[0].New)
|
||||||
|
assert.Equal(t, "puffing", extChanges.MappingChanges[0].Original)
|
||||||
|
|
||||||
|
// should be a single breaking change
|
||||||
|
assert.Equal(t, 1, CountBreakingChanges(extChanges.MappingChanges))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompareDiscriminator_Identical(t *testing.T) {
|
||||||
|
|
||||||
|
left := `propertyName: chicken`
|
||||||
|
|
||||||
|
right := `propertyName: chicken`
|
||||||
|
|
||||||
|
var lNode, rNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||||
|
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||||
|
|
||||||
|
// create low level objects
|
||||||
|
var lDoc base.Discriminator
|
||||||
|
var rDoc base.Discriminator
|
||||||
|
_ = low.BuildModel(&lNode, &lDoc)
|
||||||
|
_ = low.BuildModel(&rNode, &rDoc)
|
||||||
|
|
||||||
|
// compare.
|
||||||
|
extChanges := CompareDiscriminator(&lDoc, &rDoc)
|
||||||
|
assert.Nil(t, extChanges)
|
||||||
|
}
|
||||||
@@ -94,6 +94,13 @@ func CompareTags(l, r []low.ValueReference[*base.Tag]) *TagChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := range seenRight {
|
||||||
|
if seenLeft[i] == nil {
|
||||||
|
CreateChange[*base.Tag](&changes, ObjectAdded, i, nil, seenRight[i].GetValueNode(),
|
||||||
|
false, nil, seenRight[i].GetValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(changes) <= 0 {
|
if len(changes) <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user