diff --git a/datamodel/low/v3/constants.go b/datamodel/low/v3/constants.go index 25d0c3f..5260539 100644 --- a/datamodel/low/v3/constants.go +++ b/datamodel/low/v3/constants.go @@ -110,4 +110,6 @@ const ( SchemeLabel = "scheme" OpenIdConnectUrlLabel = "openIdConnectUrl" ScopesLabel = "scopes" + OperationRefLabel = "operationRef" + OperationIdLabel = "operationId" ) diff --git a/what-changed/model/link.go b/what-changed/model/link.go new file mode 100644 index 0000000..51a8312 --- /dev/null +++ b/what-changed/model/link.go @@ -0,0 +1,142 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package model + +import ( + "github.com/pb33f/libopenapi/datamodel/low" + v3 "github.com/pb33f/libopenapi/datamodel/low/v3" +) + +type LinkChanges struct { + PropertyChanges + ExtensionChanges *ExtensionChanges + ServerChanges *ServerChanges +} + +func (l *LinkChanges) TotalChanges() int { + c := l.PropertyChanges.TotalChanges() + if l.ExtensionChanges != nil { + c += l.ExtensionChanges.TotalChanges() + } + if l.ServerChanges != nil { + c += l.ServerChanges.TotalChanges() + } + return c +} + +func (l *LinkChanges) TotalBreakingChanges() int { + c := l.PropertyChanges.TotalBreakingChanges() + if l.ServerChanges != nil { + c += l.ServerChanges.TotalBreakingChanges() + } + return c +} + +func CompareLinks(l, r *v3.Link) *LinkChanges { + if low.AreEqual(l, r) { + return nil + } + + var props []*PropertyCheck + var changes []*Change + + // operation ref + props = append(props, &PropertyCheck{ + LeftNode: l.OperationRef.ValueNode, + RightNode: r.OperationRef.ValueNode, + Label: v3.OperationRefLabel, + Changes: &changes, + Breaking: true, + Original: l, + New: r, + }) + + // operation id + props = append(props, &PropertyCheck{ + LeftNode: l.OperationId.ValueNode, + RightNode: r.OperationId.ValueNode, + Label: v3.OperationIdLabel, + Changes: &changes, + Breaking: true, + Original: l, + New: r, + }) + + // request body + props = append(props, &PropertyCheck{ + LeftNode: l.RequestBody.ValueNode, + RightNode: r.RequestBody.ValueNode, + Label: v3.RequestBodyLabel, + Changes: &changes, + Breaking: true, + Original: l, + New: r, + }) + + // description + props = append(props, &PropertyCheck{ + LeftNode: l.Description.ValueNode, + RightNode: r.Description.ValueNode, + Label: v3.DescriptionLabel, + Changes: &changes, + Breaking: false, + Original: l, + New: r, + }) + + CheckProperties(props) + lc := new(LinkChanges) + lc.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions) + + // server + if !l.Server.IsEmpty() && !r.Server.IsEmpty() { + if !low.AreEqual(l.Server.Value, r.Server.Value) { + lc.ServerChanges = CompareServers(l.Server.Value, r.Server.Value) + } + } + if !l.Server.IsEmpty() && r.Server.IsEmpty() { + CreateChange(&changes, PropertyRemoved, v3.ServerLabel, + l.Server.ValueNode, nil, true, + l.Server.Value, nil) + } + if l.Server.IsEmpty() && !r.Server.IsEmpty() { + CreateChange(&changes, PropertyAdded, v3.ServerLabel, + nil, r.Server.ValueNode, true, + nil, r.Server.Value) + } + + // parameters + lValues := make(map[string]low.ValueReference[string]) + rValues := make(map[string]low.ValueReference[string]) + for i := range l.Parameters.Value { + lValues[i.Value] = l.Parameters.Value[i] + } + for i := range r.Parameters.Value { + rValues[i.Value] = r.Parameters.Value[i] + } + for k := range lValues { + if _, ok := rValues[k]; !ok { + CreateChange(&changes, ObjectRemoved, v3.ParametersLabel, + lValues[k].ValueNode, nil, true, + k, nil) + continue + } + if lValues[k].Value != rValues[k].Value { + CreateChange(&changes, Modified, v3.ParametersLabel, + lValues[k].ValueNode, rValues[k].ValueNode, true, + k, k) + } + + } + for k := range rValues { + if _, ok := lValues[k]; !ok { + CreateChange(&changes, ObjectAdded, v3.ParametersLabel, + nil, rValues[k].ValueNode, true, + nil, k) + } + } + + lc.Changes = changes + return lc +} diff --git a/what-changed/model/link_test.go b/what-changed/model/link_test.go new file mode 100644 index 0000000..4788e07 --- /dev/null +++ b/what-changed/model/link_test.go @@ -0,0 +1,309 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package model + +import ( + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + "testing" +) + +func TestCompareLinks(t *testing.T) { + + left := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io +parameters: + nice: rice` + + right := left + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Link + var rDoc v3.Link + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareLinks(&lDoc, &rDoc) + assert.Nil(t, extChanges) + +} + +func TestCompareLinks_ModifyExtension(t *testing.T) { + + left := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io +parameters: + nice: rice +x-cake: tasty` + + right := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io +parameters: + nice: rice +x-cake: very tasty` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Link + var rDoc v3.Link + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareLinks(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, Modified, extChanges.ExtensionChanges.Changes[0].ChangeType) + +} + +func TestCompareLinks_ModifyServer(t *testing.T) { + + left := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io +parameters: + nice: rice` + + right := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io/changed +parameters: + nice: rice` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Link + var rDoc v3.Link + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareLinks(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, Modified, extChanges.ServerChanges.Changes[0].ChangeType) +} + +func TestCompareLinks_AddServer(t *testing.T) { + + left := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +parameters: + nice: rice` + + right := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io/changed +parameters: + nice: rice` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Link + var rDoc v3.Link + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareLinks(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) +} + +func TestCompareLinks_RemoveServer(t *testing.T) { + + left := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +parameters: + nice: rice` + + right := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io/changed +parameters: + nice: rice` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Link + var rDoc v3.Link + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareLinks(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) +} + +func TestCompareLinks_ModifyParam(t *testing.T) { + + left := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io +parameters: + nice: cake` + + right := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io +parameters: + nice: rice` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Link + var rDoc v3.Link + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareLinks(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) + assert.Equal(t, "nice", extChanges.Changes[0].NewObject) + assert.Equal(t, "cake", extChanges.Changes[0].Original) + assert.Equal(t, "rice", extChanges.Changes[0].New) +} + +func TestCompareLinks_AddParam(t *testing.T) { + + left := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io +parameters: + nice: cake` + + right := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io +parameters: + nice: cake + hot: pizza` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Link + var rDoc v3.Link + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareLinks(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) + assert.Equal(t, "hot", extChanges.Changes[0].NewObject) + assert.Equal(t, "pizza", extChanges.Changes[0].New) +} + +func TestCompareLinks_RemoveParam(t *testing.T) { + + left := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io +parameters: + nice: cake` + + right := `operationId: someOperation +requestBody: expression-says-what +description: a nice link +server: + url: https://pb33f.io +parameters: + nice: cake + hot: pizza` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Link + var rDoc v3.Link + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare. + extChanges := CompareLinks(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) + assert.Equal(t, "hot", extChanges.Changes[0].OriginalObject) + assert.Equal(t, "pizza", extChanges.Changes[0].Original) +}