Added tags to what-changed

This commit is contained in:
Dave Shanley
2022-09-29 07:46:21 -04:00
parent cd68570278
commit c47cc15cad
7 changed files with 478 additions and 5 deletions

View File

@@ -16,6 +16,19 @@ type Buildable[T any] interface {
*T *T
} }
// HasValueNode is implemented by NodeReference and ValueReference to return the yaml.Node backing the value.
type HasValueNode[T any] interface {
GetValueNode() *yaml.Node
*T
}
// HasValue is implemented by NodeReference and ValueReference to return the yaml.Node backing the value.
type HasValue[T any] interface {
GetValue() T
GetValueNode() *yaml.Node
*T
}
// NodeReference is a low-level container for holding a Value of type T, as well as references to // NodeReference is a low-level container for holding a Value of type T, as well as references to
// a key yaml.Node that points to the key node that contains the value node, and the value node that contains // a key yaml.Node that points to the key node that contains the value node, and the value node that contains
// the actual value. // the actual value.
@@ -71,6 +84,16 @@ func (n NodeReference[T]) Mutate(value T) NodeReference[T] {
return n return n
} }
// GetValueNode will return the yaml.Node containing the reference value node
func (n NodeReference[T]) GetValueNode() *yaml.Node {
return n.ValueNode
}
// GetValue will return the raw value of the node
func (n NodeReference[T]) GetValue() T {
return n.Value
}
// IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored) // IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored)
func (n ValueReference[T]) IsEmpty() bool { func (n ValueReference[T]) IsEmpty() bool {
return n.ValueNode == nil return n.ValueNode == nil
@@ -81,6 +104,16 @@ func (n ValueReference[T]) GenerateMapKey() string {
return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column) return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column)
} }
// GetValueNode will return the yaml.Node containing the reference value node
func (n ValueReference[T]) GetValueNode() *yaml.Node {
return n.ValueNode
}
// GetValue will return the raw value of the node
func (n ValueReference[T]) GetValue() T {
return n.Value
}
// IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored) // IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored)
func (n KeyReference[T]) IsEmpty() bool { func (n KeyReference[T]) IsEmpty() bool {
return n.KeyNode == nil return n.KeyNode == nil

View File

@@ -40,4 +40,5 @@ const (
AuthorizationCodeLabel = "authorizationCode" AuthorizationCodeLabel = "authorizationCode"
DescriptionLabel = "description" DescriptionLabel = "description"
URLLabel = "url" URLLabel = "url"
NameLabel = "name"
) )

View File

@@ -14,6 +14,14 @@ type ExternalDocChanges struct {
ExtensionChanges *ExtensionChanges ExtensionChanges *ExtensionChanges
} }
func (e *ExternalDocChanges) TotalChanges() int {
c := len(e.Changes)
if e.ExtensionChanges != nil {
c += len(e.ExtensionChanges.Changes)
}
return c
}
func CompareExternalDocs(l, r *lowbase.ExternalDoc) *ExternalDocChanges { func CompareExternalDocs(l, r *lowbase.ExternalDoc) *ExternalDocChanges {
var changes []*Change var changes []*Change
changeType := 0 changeType := 0

View File

@@ -38,6 +38,7 @@ x-testing: hiya!`
extChanges := CompareExternalDocs(&lDoc, &rDoc) extChanges := CompareExternalDocs(&lDoc, &rDoc)
assert.Len(t, extChanges.ExtensionChanges.Changes, 1) assert.Len(t, extChanges.ExtensionChanges.Changes, 1)
assert.Len(t, extChanges.Changes, 2) assert.Len(t, extChanges.Changes, 2)
assert.Equal(t, 3, extChanges.TotalChanges())
// validate property changes // validate property changes
urlChange := extChanges.Changes[0] urlChange := extChanges.Changes[0]

147
what-changed/tags.go Normal file
View File

@@ -0,0 +1,147 @@
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package what_changed
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"strings"
)
type TagChanges struct {
PropertyChanges
ExternalDocs *ExternalDocChanges
ExtensionChanges *ExtensionChanges
}
func (t *TagChanges) TotalChanges() int {
c := len(t.Changes)
if t.ExternalDocs != nil {
c += t.ExternalDocs.TotalChanges()
}
if t.ExtensionChanges != nil {
c += len(t.ExtensionChanges.Changes)
}
return c
}
func CompareTags(l, r []low.ValueReference[*lowbase.Tag]) *TagChanges {
tc := new(TagChanges)
// look at the original and then look through the new.
seenLeft := make(map[string]*low.ValueReference[*lowbase.Tag])
seenRight := make(map[string]*low.ValueReference[*lowbase.Tag])
for i := range l {
h := l[i]
seenLeft[strings.ToLower(l[i].Value.Name.Value)] = &h
}
for i := range r {
h := r[i]
seenRight[strings.ToLower(r[i].Value.Name.Value)] = &h
}
var changes []*Change
var changeType int
// check for removals, modifications and moves
for i := range seenLeft {
changeType = 0
if seenRight[i] == nil {
// deleted
changeType = ObjectRemoved
ctx := CreateContext(seenLeft[i].ValueNode, nil)
changes = append(changes, &Change{
Context: ctx,
ChangeType: changeType,
Property: i,
Original: fmt.Sprintf("%v", seenLeft[i].Value),
})
continue
}
// if the existing tag exists, let's check it.
if seenRight[i] != nil {
// check if name has moved
ctx := CreateContext(seenLeft[i].Value.Name.ValueNode, seenRight[i].Value.Name.ValueNode)
if ctx.HasChanged() {
changeType = Moved
changes = append(changes, &Change{
Context: ctx,
ChangeType: changeType,
Property: lowv3.NameLabel,
Original: seenLeft[i].Value.Name.Value,
New: seenRight[i].Value.Name.Value,
})
}
// check if description has been modified
if seenLeft[i].Value.Description.Value != seenRight[i].Value.Description.Value {
changeType = Modified
ctx = CreateContext(seenLeft[i].Value.Description.ValueNode, seenRight[i].Value.Description.ValueNode)
if ctx.HasChanged() {
changeType = ModifiedAndMoved
}
changes = append(changes, &Change{
Context: ctx,
ChangeType: changeType,
Property: lowv3.DescriptionLabel,
Original: seenLeft[i].Value.Description.Value,
New: seenRight[i].Value.Description.Value,
})
}
// check if description has moved
if seenLeft[i].Value.Description.Value == seenRight[i].Value.Description.Value {
ctx = CreateContext(seenLeft[i].Value.Description.ValueNode, seenRight[i].Value.Description.ValueNode)
if ctx.HasChanged() {
changeType = Moved
changes = append(changes, &Change{
Context: ctx,
ChangeType: changeType,
Property: lowv3.DescriptionLabel,
Original: seenLeft[i].Value.Description.Value,
New: seenRight[i].Value.Description.Value,
})
}
}
// compare extensions
var lExt, rExt map[low.KeyReference[string]]low.ValueReference[any]
if l != nil && len(seenLeft[i].Value.Extensions) > 0 {
lExt = seenLeft[i].Value.Extensions
}
if r != nil && len(seenRight[i].Value.Extensions) > 0 {
rExt = seenRight[i].Value.Extensions
}
tc.ExtensionChanges = CompareExtensions(lExt, rExt)
// compare external docs
tc.ExternalDocs = CompareExternalDocs(seenLeft[i].Value.ExternalDocs.Value,
seenRight[i].Value.ExternalDocs.Value)
}
}
// check for additions
for i := range seenRight {
if seenLeft[i] == nil {
// added
ctx := CreateContext(nil, seenRight[i].ValueNode)
changes = append(changes, &Change{
Context: ctx,
ChangeType: ObjectAdded,
Property: i,
New: fmt.Sprintf("%v", seenRight[i].Value),
})
}
}
if len(changes) <= 0 {
return nil
}
tc.Changes = changes
return tc
}

288
what-changed/tags_test.go Normal file
View File

@@ -0,0 +1,288 @@
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package what_changed
import (
"github.com/pb33f/libopenapi/datamodel"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCompareTags(t *testing.T) {
left := `openapi: 3.0.1
tags:
- name: a tag
description: a lovely tag
x-tag: something
externalDocs:
url: https://quobix.com
description: cool`
right := `openapi: 3.0.1
tags:
- name: a tag
description: a lovelier tag description
x-tag: something else
externalDocs:
url: https://pb33f.io
description: cooler`
// create document (which will create our correct tags low level structures)
lInfo, _ := datamodel.ExtractSpecInfo([]byte(left))
rInfo, _ := datamodel.ExtractSpecInfo([]byte(right))
lDoc, _ := lowv3.CreateDocument(lInfo)
rDoc, _ := lowv3.CreateDocument(rInfo)
// compare.
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate.
assert.Len(t, changes.Changes, 1)
assert.Len(t, changes.ExternalDocs.Changes, 2)
assert.Len(t, changes.ExtensionChanges.Changes, 1)
assert.Equal(t, 4, changes.TotalChanges())
descChange := changes.Changes[0]
assert.Equal(t, "a lovelier tag description", descChange.New)
assert.Equal(t, "a lovely tag", descChange.Original)
assert.Equal(t, Modified, descChange.ChangeType)
assert.False(t, descChange.Context.HasChanged())
}
func TestCompareTags_AddNewTag(t *testing.T) {
left := `openapi: 3.0.1
tags:
- name: a tag
description: a lovelier tag description
x-tag: something else
externalDocs:
url: https://pb33f.io
description: cooler`
right := `openapi: 3.0.1
tags:
- name: a tag
description: a lovelier tag description
x-tag: something else
externalDocs:
url: https://pb33f.io
description: cooler
- name: a new tag
description: a cool new tag`
// create document (which will create our correct tags low level structures)
lInfo, _ := datamodel.ExtractSpecInfo([]byte(left))
rInfo, _ := datamodel.ExtractSpecInfo([]byte(right))
lDoc, _ := lowv3.CreateDocument(lInfo)
rDoc, _ := lowv3.CreateDocument(rInfo)
// compare.
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate.
assert.Len(t, changes.Changes, 1)
assert.Equal(t, 1, changes.TotalChanges())
descChange := changes.Changes[0]
assert.Equal(t, ObjectAdded, descChange.ChangeType)
}
func TestCompareTags_AddDeleteTag(t *testing.T) {
left := `openapi: 3.0.1
tags:
- name: a tag
description: a lovelier tag description
x-tag: something else
externalDocs:
url: https://pb33f.io
description: cooler`
right := `openapi: 3.0.1
tags:
- name: a new tag
description: a cool new tag`
// create document (which will create our correct tags low level structures)
lInfo, _ := datamodel.ExtractSpecInfo([]byte(left))
rInfo, _ := datamodel.ExtractSpecInfo([]byte(right))
lDoc, _ := lowv3.CreateDocument(lInfo)
rDoc, _ := lowv3.CreateDocument(rInfo)
// compare.
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate.
assert.Len(t, changes.Changes, 2)
assert.Equal(t, 2, changes.TotalChanges())
assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType)
assert.Equal(t, ObjectAdded, changes.Changes[1].ChangeType)
}
func TestCompareTags_DescriptionMoved(t *testing.T) {
left := `openapi: 3.0.1
tags:
- description: a lovelier tag description
name: a tag
x-tag: something else
externalDocs:
url: https://pb33f.io
description: cooler`
right := `openapi: 3.0.1
tags:
- name: a tag
x-tag: something else
description: a lovelier tag description
externalDocs:
url: https://pb33f.io
description: cooler`
// create document (which will create our correct tags low level structures)
lInfo, _ := datamodel.ExtractSpecInfo([]byte(left))
rInfo, _ := datamodel.ExtractSpecInfo([]byte(right))
lDoc, _ := lowv3.CreateDocument(lInfo)
rDoc, _ := lowv3.CreateDocument(rInfo)
// compare.
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate.
assert.Len(t, changes.Changes, 2)
assert.Equal(t, 3, changes.TotalChanges())
nameChange := changes.Changes[0]
assert.Equal(t, Moved, nameChange.ChangeType)
assert.Equal(t, 4, nameChange.Context.OrigLine)
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, 5, descChange.Context.NewLine)
assert.True(t, descChange.Context.HasChanged())
}
func TestCompareTags_NameMoved(t *testing.T) {
left := `openapi: 3.0.1
tags:
- description: a lovelier tag description
name: a tag
x-tag: something else
externalDocs:
url: https://pb33f.io
description: cooler`
right := `openapi: 3.0.1
tags:
- description: a lovelier tag description
x-tag: something else
externalDocs:
url: https://pb33f.io
description: cooler
name: a tag`
// create document (which will create our correct tags low level structures)
lInfo, _ := datamodel.ExtractSpecInfo([]byte(left))
rInfo, _ := datamodel.ExtractSpecInfo([]byte(right))
lDoc, _ := lowv3.CreateDocument(lInfo)
rDoc, _ := lowv3.CreateDocument(rInfo)
// compare.
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate.
assert.Len(t, changes.Changes, 1)
assert.Equal(t, 2, changes.TotalChanges())
nameChange := changes.Changes[0]
assert.Equal(t, Moved, nameChange.ChangeType)
assert.Equal(t, 4, nameChange.Context.OrigLine)
assert.Equal(t, 8, nameChange.Context.NewLine)
assert.True(t, nameChange.Context.HasChanged())
}
func TestCompareTags_ModifiedAndMoved(t *testing.T) {
left := `openapi: 3.0.1
tags:
- description: a lovelier tag description
name: a tag
x-tag: something else
externalDocs:
url: https://pb33f.io
description: cooler`
right := `openapi: 3.0.1
tags:
- name: a tag
x-tag: something else
description: a different tag description
externalDocs:
url: https://pb33f.io
description: cooler`
// create document (which will create our correct tags low level structures)
lInfo, _ := datamodel.ExtractSpecInfo([]byte(left))
rInfo, _ := datamodel.ExtractSpecInfo([]byte(right))
lDoc, _ := lowv3.CreateDocument(lInfo)
rDoc, _ := lowv3.CreateDocument(rInfo)
// compare.
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate.
assert.Len(t, changes.Changes, 2)
assert.Equal(t, 3, changes.TotalChanges())
descChange := changes.Changes[1]
assert.Equal(t, ModifiedAndMoved, descChange.ChangeType)
assert.Equal(t, 3, descChange.Context.OrigLine)
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)
assert.True(t, descChange.Context.HasChanged())
}
func TestCompareTags_Identical(t *testing.T) {
left := `openapi: 3.0.1
tags:
- description: a lovelier tag description
name: a tag
x-tag: something else
externalDocs:
url: https://pb33f.io
description: cooler`
right := `openapi: 3.0.1
tags:
- description: a lovelier tag description
name: a tag
x-tag: something else
externalDocs:
url: https://pb33f.io
description: cooler`
// create document (which will create our correct tags low level structures)
lInfo, _ := datamodel.ExtractSpecInfo([]byte(left))
rInfo, _ := datamodel.ExtractSpecInfo([]byte(right))
lDoc, _ := lowv3.CreateDocument(lInfo)
rDoc, _ := lowv3.CreateDocument(rInfo)
// compare.
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate.
assert.Nil(t, changes)
}

View File

@@ -49,11 +49,6 @@ type PropertyChanges struct {
Changes []*Change Changes []*Change
} }
type TagChanges struct {
PropertyChanges
ExternalDocs *ExternalDocChanges
}
type Changes struct { type Changes struct {
TagChanges *TagChanges TagChanges *TagChanges
} }