Added component checking for swagger models.

components support now caters to v2, v3 coming shortly - but now it's time for a walk. this shit is exhausting my brain.
This commit is contained in:
Dave Shanley
2022-11-12 15:39:32 -05:00
parent 6694ad54d2
commit 1abc25a923
4 changed files with 655 additions and 4 deletions

View File

@@ -114,3 +114,12 @@ func (sp *SchemaProxy) GetSchemaReference() string {
func (sp *SchemaProxy) GetValueNode() *yaml.Node {
return sp.vn
}
// Hash will return a consistent SHA256 Hash of the SchemaProxy object (it will resolve it)
func (sp *SchemaProxy) Hash() [32]byte {
if sp.rendered != nil {
return sp.rendered.Hash()
} else {
return sp.Schema().Hash()
}
}

View File

@@ -4,10 +4,13 @@
package v2
import (
"crypto/sha256"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
"gopkg.in/yaml.v3"
"sort"
"strings"
)
// ParameterDefinitions is a low-level representation of a Swagger / OpenAPI 2 Parameters Definitions object.
@@ -110,6 +113,22 @@ func (d *Definitions) Build(root *yaml.Node, idx *index.SpecIndex) error {
return nil
}
// Hash will return a consistent SHA256 Hash of the Definitions object
func (d *Definitions) Hash() [32]byte {
var f []string
keys := make([]string, len(d.Schemas))
z := 0
for k := range d.Schemas {
keys[z] = k.Value
z++
}
sort.Strings(keys)
for k := range keys {
f = append(f, low.GenerateHashString(d.FindSchema(keys[k]).Value))
}
return sha256.Sum256([]byte(strings.Join(f, "|")))
}
// Build will extract all ParameterDefinitions into Parameter instances.
func (pd *ParameterDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) error {
errorChan := make(chan error)

View File

@@ -3,17 +3,141 @@
package model
import (
v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"reflect"
)
type ComponentsChanges struct {
PropertyChanges
SchemaChanges map[string]*SchemaChanges
ResponsesChanges map[string]*SchemaChanges
ResponsesChanges map[string]*ResponseChanges
ParameterChanges map[string]*ParameterChanges
ExamplesChanges map[string]*ExamplesChanges
RequestBodyChanges map[string]*RequestBodyChanges
HeaderChanges map[string]*HeaderChanges
SecuritySchemeChanges map[string]*SecuritySchemeChanges
LinkChanges map[string]*LinkChanges
// todo:
//CallbackChanges map[string]*CallbackChanges
ExtensionChanges *ExtensionChanges
CallbackChanges map[string]*CallbackChanges
ExtensionChanges *ExtensionChanges
}
func CompareComponents(l, r any) *ComponentsChanges {
var changes []*Change
cc := new(ComponentsChanges)
// Swagger Parameters
if reflect.TypeOf(&v2.ParameterDefinitions{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.ParameterDefinitions{}) == reflect.TypeOf(r) {
lDef := l.(*v2.ParameterDefinitions)
rDef := r.(*v2.ParameterDefinitions)
cc.ParameterChanges = CheckMapForChangesUntyped(lDef.Definitions, rDef.Definitions, &changes,
v3.ParametersLabel, CompareParameters)
}
// Swagger Responses
if reflect.TypeOf(&v2.ResponsesDefinitions{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.ResponsesDefinitions{}) == reflect.TypeOf(r) {
lDef := l.(*v2.ResponsesDefinitions)
rDef := r.(*v2.ResponsesDefinitions)
cc.ResponsesChanges = CheckMapForChangesUntyped(lDef.Definitions, rDef.Definitions, &changes,
v3.ResponsesLabel, CompareResponse)
}
// Swagger Schemas
if reflect.TypeOf(&v2.Definitions{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.Definitions{}) == reflect.TypeOf(r) {
lDef := l.(*v2.Definitions)
rDef := r.(*v2.Definitions)
cc.SchemaChanges = CheckMapForChanges(lDef.Schemas, rDef.Schemas, &changes,
v2.DefinitionsLabel, CompareSchemas)
}
// Swagger Security Definitions
if reflect.TypeOf(&v2.SecurityDefinitions{}) == reflect.TypeOf(l) &&
reflect.TypeOf(&v2.SecurityDefinitions{}) == reflect.TypeOf(r) {
lDef := l.(*v2.SecurityDefinitions)
rDef := r.(*v2.SecurityDefinitions)
cc.SecuritySchemeChanges = CheckMapForChangesUntyped(lDef.Definitions, rDef.Definitions, &changes,
v3.SecurityDefinitionLabel, CompareSecuritySchemes)
}
cc.Changes = changes
if cc.TotalChanges() <= 0 {
return nil
}
return cc
}
func (c *ComponentsChanges) TotalChanges() int {
v := c.PropertyChanges.TotalChanges()
for k := range c.SchemaChanges {
v += c.SchemaChanges[k].TotalChanges()
}
for k := range c.ResponsesChanges {
v += c.ResponsesChanges[k].TotalChanges()
}
for k := range c.ParameterChanges {
v += c.ParameterChanges[k].TotalChanges()
}
for k := range c.ExamplesChanges {
v += c.ExamplesChanges[k].TotalChanges()
}
for k := range c.RequestBodyChanges {
v += c.RequestBodyChanges[k].TotalChanges()
}
for k := range c.HeaderChanges {
v += c.HeaderChanges[k].TotalChanges()
}
for k := range c.SecuritySchemeChanges {
v += c.SecuritySchemeChanges[k].TotalChanges()
}
for k := range c.LinkChanges {
v += c.LinkChanges[k].TotalChanges()
}
for k := range c.CallbackChanges {
v += c.CallbackChanges[k].TotalChanges()
}
if c.ExtensionChanges != nil {
v += c.ExtensionChanges.TotalChanges()
}
return v
}
func (c *ComponentsChanges) TotalBreakingChanges() int {
v := c.PropertyChanges.TotalBreakingChanges()
for k := range c.SchemaChanges {
v += c.SchemaChanges[k].TotalBreakingChanges()
}
for k := range c.ResponsesChanges {
v += c.ResponsesChanges[k].TotalBreakingChanges()
}
for k := range c.ParameterChanges {
v += c.ParameterChanges[k].TotalBreakingChanges()
}
for k := range c.ExamplesChanges {
v += c.ExamplesChanges[k].TotalBreakingChanges()
}
for k := range c.RequestBodyChanges {
v += c.RequestBodyChanges[k].TotalBreakingChanges()
}
for k := range c.HeaderChanges {
v += c.HeaderChanges[k].TotalBreakingChanges()
}
for k := range c.SecuritySchemeChanges {
v += c.SecuritySchemeChanges[k].TotalBreakingChanges()
}
for k := range c.LinkChanges {
v += c.LinkChanges[k].TotalBreakingChanges()
}
for k := range c.CallbackChanges {
v += c.CallbackChanges[k].TotalBreakingChanges()
}
if c.ExtensionChanges != nil {
v += c.ExtensionChanges.TotalBreakingChanges()
}
return v
}

View File

@@ -2,3 +2,502 @@
// SPDX-License-Identifier: MIT
package model
import (
"github.com/pb33f/libopenapi/datamodel/low"
v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
v3low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func TestCompareComponents_Swagger_Definitions_Equal(t *testing.T) {
left := `thing1:
type: int
description: a thing
thing2:
type: string
description: another thing.`
right := `thing1:
type: int
description: a thing
thing2:
type: string
description: another thing.`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.Definitions
var rDoc v2.Definitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&lDoc, &rDoc)
assert.Nil(t, extChanges)
}
func TestCompareComponents_Swagger_Definitions_Modified(t *testing.T) {
left := `thing1:
type: int
description: a thing
thing2:
type: int
description: another thing.`
right := `thing1:
type: int
description: a thing
thing2:
type: string
description: another thing.`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.Definitions
var rDoc v2.Definitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&lDoc, &rDoc)
assert.Equal(t, 1, extChanges.TotalChanges())
assert.Equal(t, 1, extChanges.TotalBreakingChanges())
assert.Equal(t, 1, extChanges.SchemaChanges["thing2"].TotalChanges())
}
func TestCompareComponents_Swagger_Definitions_Added(t *testing.T) {
left := `thing1:
type: int
description: a thing
thing2:
type: string
description: another thing.`
right := `thing1:
type: int
description: a thing
thing2:
type: string
description: another thing.
thing3:
type: int
description: added a thing`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.Definitions
var rDoc v2.Definitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&lDoc, &rDoc)
assert.Equal(t, 1, extChanges.TotalChanges())
assert.Equal(t, 0, extChanges.TotalBreakingChanges())
assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType)
}
func TestCompareComponents_Swagger_Definitions_Removed(t *testing.T) {
left := `thing1:
type: int
description: a thing
thing2:
type: string
description: another thing.`
right := `thing1:
type: int
description: a thing
thing2:
type: string
description: another thing.
thing3:
type: int
description: added a thing`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.Definitions
var rDoc v2.Definitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&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, "thing3", extChanges.Changes[0].Original)
}
func TestCompareComponents_Swagger_Parameters_Equal(t *testing.T) {
left := `param1:
name: nap
param2:
name: sleep
param3:
name: snooze
`
right := `param1:
name: nap
param2:
name: sleep
param3:
name: snooze`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.ParameterDefinitions
var rDoc v2.ParameterDefinitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&lDoc, &rDoc)
assert.Nil(t, extChanges)
}
func TestCompareComponents_Swagger_Parameters_Modified(t *testing.T) {
left := `param1:
name: nap
param2:
name: sleep
param3:
name: snooze
`
right := `param1:
name: WIDE AWAKE
param2:
name: sleep
param3:
name: KINDA SNOOZ`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.ParameterDefinitions
var rDoc v2.ParameterDefinitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&lDoc, &rDoc)
assert.Equal(t, 2, extChanges.TotalChanges())
assert.Equal(t, 2, extChanges.TotalBreakingChanges())
assert.Equal(t, Modified, extChanges.ParameterChanges["param1"].Changes[0].ChangeType)
assert.Equal(t, "WIDE AWAKE", extChanges.ParameterChanges["param1"].Changes[0].New)
assert.Equal(t, "KINDA SNOOZ", extChanges.ParameterChanges["param3"].Changes[0].New)
assert.Equal(t, v3low.NameLabel, extChanges.ParameterChanges["param1"].Changes[0].Property)
}
func TestCompareComponents_Swagger_Parameters_Added(t *testing.T) {
left := `param1:
name: nap
param2:
name: sleep
param3:
name: snooze
`
right := `param1:
name: nap
param2:
name: sleep
param3:
name: snooze
param4:
name: I woke up!`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.ParameterDefinitions
var rDoc v2.ParameterDefinitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&lDoc, &rDoc)
assert.Equal(t, 1, extChanges.TotalChanges())
assert.Equal(t, 0, extChanges.TotalBreakingChanges())
assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType)
assert.Equal(t, "param4", extChanges.Changes[0].New)
}
func TestCompareComponents_Swagger_Parameters_Removed(t *testing.T) {
left := `param1:
name: nap
param2:
name: sleep
param3:
name: snooze
`
right := `param1:
name: nap
param2:
name: sleep
param3:
name: snooze
param4:
name: I woke up!`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.ParameterDefinitions
var rDoc v2.ParameterDefinitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&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, "param4", extChanges.Changes[0].Original)
}
func TestCompareComponents_Swagger_Responses_Equal(t *testing.T) {
left := `resp1:
description: hi!
resp2:
description: bye!
`
right := `resp1:
description: hi!
resp2:
description: bye!`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.ResponsesDefinitions
var rDoc v2.ResponsesDefinitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&lDoc, &rDoc)
assert.Nil(t, extChanges)
}
func TestCompareComponents_Swagger_Responses_Modified(t *testing.T) {
left := `resp1:
description: hi!
resp2:
description: bye!
`
right := `resp1:
description: hi!
resp2:
description: oh, so you want to change huh?`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.ResponsesDefinitions
var rDoc v2.ResponsesDefinitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&lDoc, &rDoc)
assert.Equal(t, 1, extChanges.TotalChanges())
assert.Equal(t, 0, extChanges.TotalBreakingChanges())
assert.Equal(t, 1, extChanges.ResponsesChanges["resp2"].TotalChanges())
assert.Equal(t, v3low.DescriptionLabel, extChanges.ResponsesChanges["resp2"].Changes[0].Property)
}
func TestCompareComponents_Swagger_Responses_Added(t *testing.T) {
left := `resp1:
description: hi!
resp2:
description: bye!
`
right := `resp1:
description: hi!
resp2:
description: bye!
resp3:
description: another response!`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.ResponsesDefinitions
var rDoc v2.ResponsesDefinitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&lDoc, &rDoc)
assert.Equal(t, 1, extChanges.TotalChanges())
assert.Equal(t, 0, extChanges.TotalBreakingChanges())
assert.Equal(t, "resp3", extChanges.Changes[0].New)
assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType)
assert.Equal(t, v2.ResponsesLabel, extChanges.Changes[0].Property)
}
func TestCompareComponents_Swagger_Responses_Removed(t *testing.T) {
left := `resp1:
description: hi!
resp2:
description: bye!
`
right := `resp1:
description: hi!
resp2:
description: bye!
resp3:
description: another response!`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.ResponsesDefinitions
var rDoc v2.ResponsesDefinitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&rDoc, &lDoc)
assert.Equal(t, 1, extChanges.TotalChanges())
assert.Equal(t, 1, extChanges.TotalBreakingChanges())
assert.Equal(t, "resp3", extChanges.Changes[0].Original)
assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType)
assert.Equal(t, v2.ResponsesLabel, extChanges.Changes[0].Property)
}
func TestCompareComponents_Swagger_SecurityDefinitions_Equal(t *testing.T) {
left := `scheme1:
description: hi!
scheme2:
description: bye!
`
right := `scheme1:
description: hi!
scheme2:
description: bye!`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.SecurityDefinitions
var rDoc v2.SecurityDefinitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&lDoc, &rDoc)
assert.Nil(t, extChanges)
}
func TestCompareComponents_Swagger_SecurityDefinitions_Modified(t *testing.T) {
left := `scheme1:
description: hi!
scheme2:
description: bye!
`
right := `scheme1:
description: hi!
scheme2:
description: bye! again!`
var lNode, rNode yaml.Node
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
// create low level objects
var lDoc v2.SecurityDefinitions
var rDoc v2.SecurityDefinitions
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(lNode.Content[0], nil)
_ = rDoc.Build(rNode.Content[0], nil)
// compare.
extChanges := CompareComponents(&lDoc, &rDoc)
assert.Equal(t, 1, extChanges.TotalChanges())
assert.Equal(t, 0, extChanges.TotalBreakingChanges())
assert.Equal(t, 1, extChanges.SecuritySchemeChanges["scheme2"].TotalChanges())
assert.Equal(t, v3low.DescriptionLabel, extChanges.SecuritySchemeChanges["scheme2"].Changes[0].Property)
}