Hardening model whilst testing what-changed feature.

Checking and sampling, and fixing bugs while working through testing.

Signed-off-by: Dave Shanley <dshanley@splunk.com>
This commit is contained in:
Dave Shanley
2022-11-18 14:11:19 -05:00
parent 9d8d6e36ea
commit a8a8b482d8
10 changed files with 668 additions and 634 deletions

View File

@@ -98,6 +98,7 @@ type OpenAPIParameter interface {
type SharedOperations interface { type SharedOperations interface {
//HasDescription //HasDescription
//HasExternalDocs //HasExternalDocs
GetOperationId() NodeReference[string]
GetExternalDocs() NodeReference[any] GetExternalDocs() NodeReference[any]
GetDescription() NodeReference[string] GetDescription() NodeReference[string]
GetTags() NodeReference[[]ValueReference[string]] GetTags() NodeReference[[]ValueReference[string]]

View File

@@ -124,7 +124,7 @@ func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
} }
_, ln, vn = utils.FindKeyNodeFull(ServersLabel, root.Content) _, ln, vn = utils.FindKeyNodeFullTop(ServersLabel, root.Content)
if vn != nil { if vn != nil {
if utils.IsNodeArray(vn) { if utils.IsNodeArray(vn) {
var servers []low.ValueReference[*Server] var servers []low.ValueReference[*Server]

View File

@@ -9,22 +9,25 @@ info:
email: buckaroo@pb33f.io email: buckaroo@pb33f.io
url: https://pb33f.io url: https://pb33f.io
license: license:
name: pb33f name: pb33f-internal
url: https://pb33f.io/made-up url: https://pb33f.io/made-up
version: "1.2" version: "1.2"
security: security:
- OAuthScheme: - OAuthScheme:
- read:burgers - read:burgers
- write:burgers - write:burgers
- read:books
tags: tags:
- name: HotDogs
description: a new type of burger, its a thin round one in a bun.
- name: "Burgers" - name: "Burgers"
description: "All kinds of yummy burgers." description: All kinds of yummy burgers, the very best in the world.
externalDocs: externalDocs:
description: "Find out more" description: "Find out more"
url: "https://pb33f.io" url: "https://pb33f.io"
x-internal-ting: somethingSpecial x-internal-ting: somethingSpecial
x-internal-tong: 1 x-internal-tong: 1
x-internal-tang: 1.2 x-internal-tang: 1.2.3
x-internal-tung: true x-internal-tung: true
x-internal-arr: x-internal-arr:
- one - one
@@ -42,10 +45,10 @@ tags:
url: "https://pb33f.io" url: "https://pb33f.io"
servers: servers:
- url: "{scheme}://api.pb33f.io" - url: "{scheme}://api.pb33f.io"
description: "this is our main API server, for all fun API things." description: "this is our main API server, for all fun API things. updated"
variables: variables:
scheme: scheme:
enum: [https, wss] enum: [https]
default: https default: https
description: this is a server variable for the scheme description: this is a server variable for the scheme
- url: "https://{domain}.{host}.com" - url: "https://{domain}.{host}.com"
@@ -58,15 +61,16 @@ servers:
default: "pb33f.io" default: "pb33f.io"
description: the default host for this API is 'pb33f.io' description: the default host for this API is 'pb33f.io'
paths: paths:
x-milky-milk: milky x-milky-milk: milky updated
/burgers: /burgers:
x-burger-meta: meaty x-burger-meta: meaty pop
post: post:
operationId: createBurger operationId: createBurgerChanged
tags: tags:
- "Burgers" - "Burgers"
summary: Create a new burger - "HotDogs"
description: A new burger for our menu, yummy yum yum. summary: Create a new burger the changed
description: A new burger for our menu
requestBody: requestBody:
$ref: '#/components/requestBodies/BurgerRequest' $ref: '#/components/requestBodies/BurgerRequest'
responses: responses:
@@ -74,7 +78,7 @@ paths:
headers: headers:
UseOil: UseOil:
$ref: '#/components/headers/UseOil' $ref: '#/components/headers/UseOil'
description: A tasty burger for you to eat. description: A tasty burger for you to eat. update
content: content:
application/json: application/json:
schema: schema:
@@ -83,11 +87,13 @@ paths:
quarterPounder: quarterPounder:
$ref: '#/components/examples/QuarterPounder' $ref: '#/components/examples/QuarterPounder'
filetOFish: filetOFish:
summary: a cripsy fish sammich filled with ocean goodness. summary: a cripsy fish sammich filled with ocean goodness. changed
value: value:
name: Filet-O-Fish name: Filet-O-Fish
numPatties: 1 numPatties: 22
links: links:
DuplicateLocateBurger:
$ref: '#/components/links/LocateBurger'
LocateBurger: LocateBurger:
$ref: '#/components/links/LocateBurger' $ref: '#/components/links/LocateBurger'
AnotherLocateBurger: AnotherLocateBurger:
@@ -103,24 +109,16 @@ paths:
summary: oh my goodness summary: oh my goodness
value: value:
message: something went terribly wrong my friend, no new burger for you. mate. message: something went terribly wrong my friend, no new burger for you. mate.
"422":
description: Unprocessable entity
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
unexpectedError:
summary: invalid request
value:
message: unable to accept this request, looks bad, missing something.
security: security:
- OAuthScheme: - OAuthScheme:
- read:burgers - read:burgers
- write:burgers - write:burgers
- read:books
servers: servers:
- url: https://pb33f.io - url: https://pb33f.io
description: this is an alternative server for this operation. description: this is an alternative server for this operation.
- url: https://pb33f.io/new
description: this is an alternative server, copy pasta.
/burgers/{burgerId}: /burgers/{burgerId}:
get: get:
callbacks: callbacks:

View File

@@ -15,7 +15,7 @@ type DocumentChanges struct {
PropertyChanges PropertyChanges
InfoChanges *InfoChanges InfoChanges *InfoChanges
PathsChanges *PathsChanges PathsChanges *PathsChanges
TagChanges *TagChanges TagChanges []*TagChanges
ExternalDocChanges *ExternalDocChanges ExternalDocChanges *ExternalDocChanges
WebhookChanges map[string]*PathItemChanges WebhookChanges map[string]*PathItemChanges
ServerChanges []*ServerChanges ServerChanges []*ServerChanges
@@ -32,8 +32,8 @@ func (d *DocumentChanges) TotalChanges() int {
if d.PathsChanges != nil { if d.PathsChanges != nil {
c += d.PathsChanges.TotalChanges() c += d.PathsChanges.TotalChanges()
} }
if d.TagChanges != nil { for k := range d.TagChanges {
c += d.TagChanges.TotalChanges() c += d.TagChanges[k].TotalChanges()
} }
if d.ExternalDocChanges != nil { if d.ExternalDocChanges != nil {
c += d.ExternalDocChanges.TotalChanges() c += d.ExternalDocChanges.TotalChanges()
@@ -64,8 +64,8 @@ func (d *DocumentChanges) TotalBreakingChanges() int {
if d.PathsChanges != nil { if d.PathsChanges != nil {
c += d.PathsChanges.TotalBreakingChanges() c += d.PathsChanges.TotalBreakingChanges()
} }
if d.TagChanges != nil { for k := range d.TagChanges {
c += d.TagChanges.TotalBreakingChanges() c += d.TagChanges[k].TotalBreakingChanges()
} }
if d.ExternalDocChanges != nil { if d.ExternalDocChanges != nil {
c += d.ExternalDocChanges.TotalBreakingChanges() c += d.ExternalDocChanges.TotalBreakingChanges()
@@ -213,7 +213,7 @@ func CompareDocuments(l, r any) *DocumentChanges {
} }
// compare servers // compare servers
if n := checkServers(lDoc.Servers, rDoc.Servers, &changes); n != nil { if n := checkServers(lDoc.Servers, rDoc.Servers); n != nil {
dc.ServerChanges = n dc.ServerChanges = n
} }

View File

@@ -16,13 +16,9 @@ import (
type OperationChanges struct { type OperationChanges struct {
PropertyChanges PropertyChanges
ExternalDocChanges *ExternalDocChanges ExternalDocChanges *ExternalDocChanges
ParameterChanges []*ParameterChanges ParameterChanges []*ParameterChanges
ResponsesChanges *ResponsesChanges ResponsesChanges *ResponsesChanges
// SecurityRequirementChanges are defined differently between v2 and v3. Both are defined
// as the same data type, however the objects are structured differently. A slice is the cleanest
// way to compose both models.
SecurityRequirementChanges []*SecurityRequirementChanges SecurityRequirementChanges []*SecurityRequirementChanges
// v3 // v3
@@ -105,6 +101,10 @@ func addSharedOperationProperties(left, right low.SharedOperations, changes *[]*
addPropertyCheck(&props, left.GetDeprecated().ValueNode, right.GetDeprecated().ValueNode, addPropertyCheck(&props, left.GetDeprecated().ValueNode, right.GetDeprecated().ValueNode,
left.GetDeprecated(), right.GetDeprecated(), changes, v3.DeprecatedLabel, false) left.GetDeprecated(), right.GetDeprecated(), changes, v3.DeprecatedLabel, false)
// operation id
addPropertyCheck(&props, left.GetOperationId().ValueNode, right.GetOperationId().ValueNode,
left.GetOperationId(), right.GetOperationId(), changes, v3.OperationIdLabel, true)
return props return props
} }
@@ -343,7 +343,7 @@ func CompareOperations(l, r any) *OperationChanges {
} }
// servers // servers
oc.ServerChanges = checkServers(lOperation.Servers, rOperation.Servers, &changes) oc.ServerChanges = checkServers(lOperation.Servers, rOperation.Servers)
oc.ExtensionChanges = CompareExtensions(lOperation.Extensions, rOperation.Extensions) oc.ExtensionChanges = CompareExtensions(lOperation.Extensions, rOperation.Extensions)
// todo: callbacks // todo: callbacks
@@ -353,8 +353,7 @@ func CompareOperations(l, r any) *OperationChanges {
return oc return oc
} }
func checkServers(lServers, rServers low.NodeReference[[]low.ValueReference[*v3.Server]], func checkServers(lServers, rServers low.NodeReference[[]low.ValueReference[*v3.Server]]) []*ServerChanges {
changes *[]*Change) []*ServerChanges {
var serverChanges []*ServerChanges var serverChanges []*ServerChanges
@@ -383,34 +382,58 @@ func checkServers(lServers, rServers low.NodeReference[[]low.ValueReference[*v3.
} }
for k := range lv { for k := range lv {
var changes []*Change
if _, ok := rv[k]; ok { if _, ok := rv[k]; ok {
if !low.AreEqual(lv[k].Value, rv[k].Value) { if !low.AreEqual(lv[k].Value, rv[k].Value) {
serverChanges = append(serverChanges, CompareServers(lv[k].Value, rv[k].Value)) serverChanges = append(serverChanges, CompareServers(lv[k].Value, rv[k].Value))
} }
continue continue
} }
CreateChange(changes, ObjectRemoved, v3.ServersLabel, lv[k].ValueNode.Value = lv[k].Value.URL.Value
CreateChange(&changes, ObjectRemoved, v3.ServersLabel,
lv[k].ValueNode, nil, true, lv[k].Value.URL.Value, lv[k].ValueNode, nil, true, lv[k].Value.URL.Value,
nil) nil)
sc := new(ServerChanges)
sc.Changes = changes
serverChanges = append(serverChanges, sc)
} }
for k := range rv { for k := range rv {
if _, ok := lv[k]; !ok { if _, ok := lv[k]; !ok {
CreateChange(changes, ObjectAdded, v3.ServersLabel,
rv[k].ValueNode, nil, false, rv[k].Value.URL.Value, var changes []*Change
nil) rv[k].ValueNode.Value = rv[k].Value.URL.Value
CreateChange(&changes, ObjectAdded, v3.ServersLabel,
nil, rv[k].ValueNode, false, nil,
rv[k].Value.URL.Value)
sc := new(ServerChanges)
sc.Changes = changes
serverChanges = append(serverChanges, sc)
} }
} }
} }
var changes []*Change
sc := new(ServerChanges)
if !lServers.IsEmpty() && rServers.IsEmpty() { if !lServers.IsEmpty() && rServers.IsEmpty() {
CreateChange(changes, PropertyRemoved, v3.ServersLabel, CreateChange(&changes, PropertyRemoved, v3.ServersLabel,
lServers.ValueNode, nil, true, lServers.ValueNode, lServers.ValueNode, nil, true, lServers.Value,
nil) nil)
} }
if lServers.IsEmpty() && !rServers.IsEmpty() { if lServers.IsEmpty() && !rServers.IsEmpty() {
CreateChange(changes, PropertyAdded, v3.ServersLabel, CreateChange(&changes, PropertyAdded, v3.ServersLabel,
nil, rServers.ValueNode, false, nil, nil, rServers.ValueNode, false, nil,
rServers.Value) rServers.Value)
} }
sc.Changes = changes
if len(changes) > 0 {
serverChanges = append(serverChanges, sc)
}
if len(serverChanges) <= 0 { if len(serverChanges) <= 0 {
return nil return nil
} }

File diff suppressed because it is too large Load Diff

View File

@@ -526,7 +526,7 @@ func compareOpenAPIPathItem(lPath, rPath *v3.PathItem, changes *[]*Change, pc *P
} }
// servers // servers
pc.ServerChanges = checkServers(lPath.Servers, rPath.Servers, changes) pc.ServerChanges = checkServers(lPath.Servers, rPath.Servers)
// parameters // parameters
if !lPath.Parameters.IsEmpty() && !rPath.Parameters.IsEmpty() { if !lPath.Parameters.IsEmpty() && !rPath.Parameters.IsEmpty() {

View File

@@ -7,7 +7,6 @@ import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/datamodel/low/v3"
"strings"
) )
// TagChanges represents changes made to the Tags object of an OpenAPI document. // TagChanges represents changes made to the Tags object of an OpenAPI document.
@@ -37,25 +36,28 @@ func (t *TagChanges) TotalBreakingChanges() int {
// CompareTags will compare a left (original) and a right (new) slice of ValueReference nodes for // 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 // any changes between them. If there are changes, a pointer to TagChanges is returned, if not then
// nil is returned instead. // nil is returned instead.
func CompareTags(l, r []low.ValueReference[*base.Tag]) *TagChanges { func CompareTags(l, r []low.ValueReference[*base.Tag]) []*TagChanges {
tc := new(TagChanges)
var tagResults []*TagChanges
// look at the original and then look through the new. // look at the original and then look through the new.
seenLeft := make(map[string]*low.ValueReference[*base.Tag]) seenLeft := make(map[string]*low.ValueReference[*base.Tag])
seenRight := make(map[string]*low.ValueReference[*base.Tag]) seenRight := make(map[string]*low.ValueReference[*base.Tag])
for i := range l { for i := range l {
h := l[i] h := l[i]
seenLeft[strings.ToLower(l[i].Value.Name.Value)] = &h seenLeft[l[i].Value.Name.Value] = &h
} }
for i := range r { for i := range r {
h := r[i] h := r[i]
seenRight[strings.ToLower(r[i].Value.Name.Value)] = &h seenRight[r[i].Value.Name.Value] = &h
} }
var changes []*Change //var changes []*Change
// check for removals, modifications and moves // check for removals, modifications and moves
for i := range seenLeft { for i := range seenLeft {
tc := new(TagChanges)
var changes []*Change
CheckForObjectAdditionOrRemoval[*base.Tag](seenLeft, seenRight, i, &changes, false, true) CheckForObjectAdditionOrRemoval[*base.Tag](seenLeft, seenRight, i, &changes, false, true)
@@ -104,20 +106,32 @@ func CompareTags(l, r []low.ValueReference[*base.Tag]) *TagChanges {
} }
// check extensions // check extensions
tc.ExtensionChanges = CheckExtensions(seenLeft[i].GetValue(), seenRight[i].GetValue()) tc.ExtensionChanges = CompareExtensions(seenLeft[i].Value.Extensions, seenRight[i].Value.Extensions)
tc.Changes = changes
if tc.TotalChanges() > 0 {
tagResults = append(tagResults, tc)
}
continue
} }
}
if len(changes) > 0 {
tc.Changes = changes
tagResults = append(tagResults, tc)
}
}
for i := range seenRight { for i := range seenRight {
if seenLeft[i] == nil { if seenLeft[i] == nil {
tc := new(TagChanges)
var changes []*Change
CreateChange(&changes, ObjectAdded, i, nil, seenRight[i].GetValueNode(), CreateChange(&changes, ObjectAdded, i, nil, seenRight[i].GetValueNode(),
false, nil, seenRight[i].GetValue()) false, nil, seenRight[i].GetValue())
tc.Changes = changes
tagResults = append(tagResults, tc)
} }
} }
tc.Changes = changes return tagResults
if tc.TotalChanges() <= 0 {
return nil
}
return tc
} }

View File

@@ -40,12 +40,12 @@ tags:
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value) changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate. // evaluate.
assert.Len(t, changes.Changes, 1) assert.Len(t, changes[0].Changes, 1)
assert.Len(t, changes.ExternalDocs.Changes, 2) assert.Len(t, changes[0].ExternalDocs.Changes, 2)
assert.Len(t, changes.ExtensionChanges.Changes, 1) assert.Len(t, changes[0].ExtensionChanges.Changes, 1)
assert.Equal(t, 4, changes.TotalChanges()) assert.Equal(t, 4, changes[0].TotalChanges())
descChange := changes.Changes[0] descChange := changes[0].Changes[0]
assert.Equal(t, "a lovelier tag description", descChange.New) assert.Equal(t, "a lovelier tag description", descChange.New)
assert.Equal(t, "a lovely tag", descChange.Original) assert.Equal(t, "a lovely tag", descChange.Original)
assert.Equal(t, Modified, descChange.ChangeType) assert.Equal(t, Modified, descChange.ChangeType)
@@ -84,10 +84,10 @@ tags:
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value) changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate. // evaluate.
assert.Len(t, changes.Changes, 1) assert.Len(t, changes[0].Changes, 1)
assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 1, changes[0].TotalChanges())
descChange := changes.Changes[0] descChange := changes[0].Changes[0]
assert.Equal(t, ObjectAdded, descChange.ChangeType) assert.Equal(t, ObjectAdded, descChange.ChangeType)
} }
@@ -117,12 +117,10 @@ tags:
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value) changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate. // evaluate.
assert.Len(t, changes.Changes, 2) assert.Len(t, changes, 2)
assert.Equal(t, 2, changes.TotalChanges()) assert.Equal(t, 1, changes[0].TotalChanges())
assert.Equal(t, 1, changes[1].TotalChanges())
assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType) assert.Equal(t, 1, changes[0].TotalBreakingChanges())
assert.Equal(t, ObjectAdded, changes.Changes[1].ChangeType)
assert.Equal(t, 1, changes.TotalBreakingChanges())
} }
func TestCompareTags_DescriptionMoved(t *testing.T) { func TestCompareTags_DescriptionMoved(t *testing.T) {
@@ -222,10 +220,10 @@ tags:
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value) changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate. // evaluate.
assert.Len(t, changes.Changes, 1) assert.Len(t, changes[0].Changes, 1)
assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 1, changes[0].TotalChanges())
descChange := changes.Changes[0] descChange := changes[0].Changes[0]
assert.Equal(t, Modified, descChange.ChangeType) assert.Equal(t, Modified, descChange.ChangeType)
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)
@@ -289,8 +287,8 @@ tags:
changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value) changes := CompareTags(lDoc.Tags.Value, rDoc.Tags.Value)
// evaluate. // evaluate.
assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 1, changes[0].TotalChanges())
assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType) assert.Equal(t, ObjectAdded, changes[0].Changes[0].ChangeType)
} }
@@ -317,7 +315,7 @@ tags:
changes := CompareTags(rDoc.Tags.Value, lDoc.Tags.Value) changes := CompareTags(rDoc.Tags.Value, lDoc.Tags.Value)
// evaluate. // evaluate.
assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 1, changes[0].TotalChanges())
assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType) assert.Equal(t, ObjectRemoved, changes[0].Changes[0].ChangeType)
} }

View File

@@ -22,8 +22,8 @@ func TestCompareOpenAPIDocuments(t *testing.T) {
modDoc, _ := v3.CreateDocument(infoMod) modDoc, _ := v3.CreateDocument(infoMod)
changes := CompareOpenAPIDocuments(origDoc, modDoc) changes := CompareOpenAPIDocuments(origDoc, modDoc)
assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 21, changes.TotalChanges())
assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, 3, changes.TotalBreakingChanges())
} }