diff --git a/datamodel/high/base/schema.go b/datamodel/high/base/schema.go index 6d64bf1..3733ca1 100644 --- a/datamodel/high/base/schema.go +++ b/datamodel/high/base/schema.go @@ -86,7 +86,7 @@ type Schema struct { MinProperties *int64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` Required []string `json:"required,omitempty" yaml:"required,omitempty"` Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` - AdditionalProperties any `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` + AdditionalProperties any `json:"additionalProperties,omitempty" yaml:"additionalProperties,renderZero,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` Default any `json:"default,omitempty" yaml:"default,omitempty"` Nullable *bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` @@ -300,6 +300,7 @@ func NewSchema(schema *base.Schema) *Schema { p := &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ ValueNode: sch.ValueNode, Value: sch.Value, + Reference: sch.GetReference(), }} bChan <- buildResult{idx: idx, s: p} diff --git a/datamodel/high/base/schema_test.go b/datamodel/high/base/schema_test.go index 4684cd6..5022200 100644 --- a/datamodel/high/base/schema_test.go +++ b/datamodel/high/base/schema_test.go @@ -1019,3 +1019,74 @@ func TestNewSchemaProxy_RenderSchemaCheckAdditionalPropertiesSliceMap(t *testing assert.Len(t, schemaBytes, 75) } +/* + + */ + +func TestNewSchemaProxy_RenderAdditionalPropertiesFalse(t *testing.T) { + testSpec := `additionalProperties: false` + + var compNode yaml.Node + _ = yaml.Unmarshal([]byte(testSpec), &compNode) + + sp := new(lowbase.SchemaProxy) + err := sp.Build(compNode.Content[0], nil) + assert.NoError(t, err) + + lowproxy := low.NodeReference[*lowbase.SchemaProxy]{ + Value: sp, + ValueNode: compNode.Content[0], + } + + schemaProxy := NewSchemaProxy(&lowproxy) + compiled := schemaProxy.Schema() + + // now render it out, it should be identical. + schemaBytes, _ := compiled.Render() + assert.Equal(t, testSpec, strings.TrimSpace(string(schemaBytes))) +} + +func TestNewSchemaProxy_RenderMultiplePoly(t *testing.T) { + idxYaml := `openapi: 3.1.0 +components: + schemas: + balance_transaction: + description: A balance transaction` + + testSpec := `properties: + bigBank: + type: object + properties: + failure_balance_transaction: + anyOf: + - maxLength: 5000 + type: string + - $ref: '#/components/schemas/balance_transaction' + x-expansionResources: + oneOf: + - $ref: '#/components/schemas/balance_transaction'` + + var compNode, idxNode yaml.Node + _ = yaml.Unmarshal([]byte(testSpec), &compNode) + _ = yaml.Unmarshal([]byte(idxYaml), &idxNode) + + idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig()) + + sp := new(lowbase.SchemaProxy) + + err := sp.Build(compNode.Content[0], idx) + assert.NoError(t, err) + + lowproxy := low.NodeReference[*lowbase.SchemaProxy]{ + Value: sp, + ValueNode: idxNode.Content[0], + } + + sch1 := SchemaProxy{schema: &lowproxy} + compiled := sch1.Schema() + + // now render it out, it should be identical. + schemaBytes, _ := compiled.Render() + assert.Equal(t, testSpec, strings.TrimSpace(string(schemaBytes))) +} + diff --git a/datamodel/high/base/security_requirement.go b/datamodel/high/base/security_requirement.go index 6b14b20..8e7006a 100644 --- a/datamodel/high/base/security_requirement.go +++ b/datamodel/high/base/security_requirement.go @@ -4,12 +4,14 @@ package base import ( - "github.com/pb33f/libopenapi/datamodel/high" + "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" + "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" + "sort" ) -// SecurityRequirement is a high-level representation of a Swagger / OpenAPI 2 SecurityRequirement object. +// SecurityRequirement is a high-level representation of a Swagger / OpenAPI 3 SecurityRequirement object. // // SecurityRequirement lists the required security schemes to execute this operation. The object can have multiple // security schemes declared in it which are all required (that is, there is a logical AND between the schemes). @@ -17,7 +19,7 @@ import ( // The name used for each property MUST correspond to a security scheme declared in the Security Definitions // - https://swagger.io/specification/v2/#securityDefinitionsObject type SecurityRequirement struct { - Requirements map[string][]string + Requirements map[string][]string `json:"-" yaml:"-"` low *base.SecurityRequirement } @@ -55,6 +57,69 @@ func (s *SecurityRequirement) Render() ([]byte, error) { // MarshalYAML will create a ready to render YAML representation of the SecurityRequirement object. func (s *SecurityRequirement) MarshalYAML() (interface{}, error) { - nb := high.NewNodeBuilder(s, s.low) - return nb.Render(), nil + + type req struct { + line int + key string + val []string + lowKey *low.KeyReference[string] + lowVal *low.ValueReference[[]low.ValueReference[string]] + } + + m := utils.CreateEmptyMapNode() + keys := make([]*req, len(s.Requirements)) + + i := 0 + + for k := range s.Requirements { + keys[i] = &req{key: k, val: s.Requirements[k]} + i++ + } + i = 0 + for k := range s.low.Requirements.Value { + if k.Value == keys[i].key { + gh := s.low.Requirements.Value[k] + keys[i].line = k.KeyNode.Line + keys[i].lowKey = &k + keys[i].lowVal = &gh + } + i++ + } + sort.Slice(keys, func(i, j int) bool { + return keys[i].line < keys[j].line + }) + + for k := range keys { + l := utils.CreateStringNode(keys[k].key) + l.Line = keys[k].line + + // for each key, extract all the values and order them. + type req struct { + line int + val string + } + + reqs := make([]*req, len(keys[k].val)) + for t := range keys[k].val { + reqs[t] = &req{val: keys[k].val[t], line: 9999 + t} + for _ = range keys[k].lowVal.Value[t].Value { + fh := keys[k].val[t] + df := keys[k].lowVal.Value[t].Value + if fh == df { + reqs[t].line = keys[k].lowVal.Value[t].ValueNode.Line + break + } + } + } + sort.Slice(reqs, func(i, j int) bool { + return reqs[i].line < reqs[j].line + }) + sn := utils.CreateEmptySequenceNode() + for z := range reqs { + sn.Content = append(sn.Content, utils.CreateStringNode(reqs[z].val)) + } + + m.Content = append(m.Content, l, sn) + } + return m, nil } diff --git a/datamodel/high/node_builder.go b/datamodel/high/node_builder.go index d15cecd..dda1113 100644 --- a/datamodel/high/node_builder.go +++ b/datamodel/high/node_builder.go @@ -16,10 +16,11 @@ import ( // NodeEntry represents a single node used by NodeBuilder. type NodeEntry struct { - Tag string - Key string - Value any - Line int + Tag string + Key string + Value any + Line int + RenderZero bool } // NodeBuilder is a structure used by libopenapi high-level objects, to render themselves back to YAML. @@ -30,13 +31,16 @@ type NodeBuilder struct { Low any } +const renderZero = "renderZero" + // NewNodeBuilder will create a new NodeBuilder instance, this is the only way to create a NodeBuilder. // The function accepts a high level object and a low level object (need to be siblings/same type). // // Using reflection, a map of every field in the high level object is created, ready to be rendered. func NewNodeBuilder(high any, low any) *NodeBuilder { // create a new node builder - nb := &NodeBuilder{High: high} + nb := new(NodeBuilder) + nb.High = high if low != nil { nb.Low = low } @@ -103,21 +107,27 @@ func (n *NodeBuilder) add(key string, i int) { field, _ := reflect.TypeOf(n.High).Elem().FieldByName(key) tag := string(field.Tag.Get("yaml")) tagName := strings.Split(tag, ",")[0] + if tag == "-" { return } + renderZeroVal := strings.Split(tag, ",")[1] + // extract the value of the field fieldValue := reflect.ValueOf(n.High).Elem().FieldByName(key) f := fieldValue.Interface() value := reflect.ValueOf(f) - if f == nil || value.IsZero() { + if renderZeroVal != renderZero && (f == nil || value.IsZero()) { return } // create a new node entry nodeEntry := &NodeEntry{Tag: tagName, Key: key} + if renderZeroVal == renderZero { + nodeEntry.RenderZero = true + } switch value.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -134,7 +144,7 @@ func (n *NodeBuilder) add(key string, i int) { nodeEntry.Value = f } } else { - if !value.IsNil() && !value.IsZero() { + if (renderZeroVal == renderZero) || (!value.IsNil() && !value.IsZero()) { nodeEntry.Value = f } } @@ -158,13 +168,30 @@ func (n *NodeBuilder) add(key string, i int) { fLow := lowFieldValue.Interface() value = reflect.ValueOf(fLow) switch value.Kind() { - case reflect.Map: - for _, ky := range value.MapKeys() { - if we, wok := ky.Interface().(low.HasKeyNode); wok { - nodeEntry.Line = we.GetKeyNode().Line - break + + case reflect.Slice: + l := value.Len() + lines := make([]int, l) + for g := 0; g < l; g++ { + qw := value.Index(g).Interface() + if we, wok := qw.(low.HasKeyNode); wok { + lines[g] = we.GetKeyNode().Line } } + sort.Ints(lines) + nodeEntry.Line = lines[0] // pick the lowest line number so this key is sorted in order. + break + case reflect.Map: + + l := value.Len() + lines := make([]int, l) + for q, ky := range value.MapKeys() { + if we, wok := ky.Interface().(low.HasKeyNode); wok { + lines[q] = we.GetKeyNode().Line + } + } + sort.Ints(lines) + nodeEntry.Line = lines[0] // pick the lowest line number, sort in order case reflect.Struct: y := value.Interface() @@ -202,6 +229,10 @@ func (n *NodeBuilder) renderReference() []*yaml.Node { // Render will render the NodeBuilder back to a YAML node, iterating over every NodeEntry defined func (n *NodeBuilder) Render() *yaml.Node { + if len(n.Nodes) == 0 { + return utils.CreateEmptyMapNode() + } + // order nodes by line number, retain original order m := utils.CreateEmptyMapNode() if fg, ok := n.Low.(low.IsReferenced); ok { @@ -223,7 +254,7 @@ func (n *NodeBuilder) Render() *yaml.Node { for i := range n.Nodes { node := n.Nodes[i] - n.AddYAMLNode(m, node.Tag, node.Key, node.Value, node.Line) + n.AddYAMLNode(m, node) } if len(m.Content) > 0 { return m @@ -234,35 +265,45 @@ func (n *NodeBuilder) Render() *yaml.Node { // AddYAMLNode will add a new *yaml.Node to the parent node, using the tag, key and value provided. // If the value is nil, then the node will not be added. This method is recursive, so it will dig down // into any non-scalar types. -func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, line int) *yaml.Node { - if value == nil { +func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Node { + if entry.Value == nil { return parent } // check the type - t := reflect.TypeOf(value) + t := reflect.TypeOf(entry.Value) var l *yaml.Node - if tag != "" { - l = utils.CreateStringNode(tag) + if entry.Tag != "" { + l = utils.CreateStringNode(entry.Tag) } + + value := entry.Value + line := entry.Line + key := entry.Key + var valueNode *yaml.Node switch t.Kind() { case reflect.String: val := value.(string) - if val == "" { + if val == "" && !entry.RenderZero { return parent } + valueNode = utils.CreateStringNode(val) valueNode.Line = line break case reflect.Bool: val := value.(bool) - if !val { + if !val && !entry.RenderZero { return parent } - valueNode = utils.CreateBoolNode("true") + if !val { + valueNode = utils.CreateBoolNode("false") + } else { + valueNode = utils.CreateBoolNode("true") + } valueNode.Line = line break @@ -371,7 +412,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, // build out each map node in original order. for _, cv := range orderedCollection { - n.AddYAMLNode(p, cv.Tag, cv.Key, cv.Value, cv.Line) + n.AddYAMLNode(p, cv) } if len(p.Content) > 0 { valueNode = p @@ -387,6 +428,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, var rawNode yaml.Node m := reflect.ValueOf(value) sl := utils.CreateEmptySequenceNode() + skip := false for i := 0; i < m.Len(); i++ { sqi := m.Index(i).Interface() @@ -407,10 +449,29 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, nodes[1] = utils.CreateStringNode(glu.GoLowUntyped().(low.IsReferenced).GetReference()) rt.Content = append(rt.Content, nodes...) sl.Content = append(sl.Content, rt) + skip = true + } else { + + // TODO: pick up here in the AM looks like we need to correctly handle + + if er, ko := sqi.(Renderable); ko { + rend, _ := er.(Renderable).MarshalYAML() + sl.Content = append(sl.Content, rend.(*yaml.Node)) + skip = true + } + + //} } } } + } else { + if er, ko := sqi.(Renderable); ko { + rend, _ := er.(Renderable).MarshalYAML() + sl.Content = append(sl.Content, rend.(*yaml.Node)) + skip = true + } + } } @@ -419,6 +480,9 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, valueNode = sl break } + if skip { + break + } err := rawNode.Encode(value) if err != nil { @@ -468,6 +532,11 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, if *b { valueNode = utils.CreateBoolNode("true") valueNode.Line = line + } else { + if entry.RenderZero { + valueNode = utils.CreateBoolNode("false") + valueNode.Line = line + } } } if b, bok := value.(*int64); bok { diff --git a/datamodel/high/node_builder_test.go b/datamodel/high/node_builder_test.go index aed5161..c1dc9d3 100644 --- a/datamodel/high/node_builder_test.go +++ b/datamodel/high/node_builder_test.go @@ -14,7 +14,7 @@ import ( ) type key struct { - Name string `yaml:"name"` + Name string `yaml:"name,omitempty"` ref bool refStr string ln int @@ -71,27 +71,27 @@ func (k key) MarshalYAML() (interface{}, error) { } type test1 struct { - Thing string `yaml:"thing"` - Thong int `yaml:"thong"` - Thrum int64 `yaml:"thrum"` - Thang float32 `yaml:"thang"` - Thung float64 `yaml:"thung"` - Thyme bool `yaml:"thyme"` - Thurm any `yaml:"thurm"` - Thugg *bool `yaml:"thugg"` - Thurr *int64 `yaml:"thurr"` - Thral *float64 `yaml:"thral"` - Tharg []string `yaml:"tharg"` - Type []string `yaml:"type"` - Throg []*key `yaml:"throg"` - Thrug map[string]string `yaml:"thrug"` - Thoom []map[string]string `yaml:"thoom"` - Thomp map[key]string `yaml:"thomp"` - Thump key `yaml:"thump"` - Thane key `yaml:"thane"` - Thunk key `yaml:"thunk"` - Thrim *key `yaml:"thrim"` - Thril map[string]*key `yaml:"thril"` + Thing string `yaml:"thing,omitempty"` + Thong int `yaml:"thong,omitempty"` + Thrum int64 `yaml:"thrum,omitempty"` + Thang float32 `yaml:"thang,omitempty"` + Thung float64 `yaml:"thung,omitempty"` + Thyme bool `yaml:"thyme,omitempty"` + Thurm any `yaml:"thurm,omitempty"` + Thugg *bool `yaml:"thugg,omitempty"` + Thurr *int64 `yaml:"thurr,omitempty"` + Thral *float64 `yaml:"thral,omitempty"` + Tharg []string `yaml:"tharg,omitempty"` + Type []string `yaml:"type,omitempty"` + Throg []*key `yaml:"throg,omitempty"` + Thrug map[string]string `yaml:"thrug,omitempty"` + Thoom []map[string]string `yaml:"thoom,omitempty"` + Thomp map[key]string `yaml:"thomp,omitempty"` + Thump key `yaml:"thump,omitempty"` + Thane key `yaml:"thane,omitempty"` + Thunk key `yaml:"thunk,omitempty"` + Thrim *key `yaml:"thrim,omitempty"` + Thril map[string]*key `yaml:"thril,omitempty"` Extensions map[string]any `yaml:"-"` ignoreMe string `yaml:"-"` IgnoreMe string `yaml:"-"` @@ -159,7 +159,7 @@ func TestNewNodeBuilder(t *testing.T) { ignoreMe: "I should never be seen!", Thing: "ding", Thong: 1, - Thurm: &test1{}, + Thurm: nil, Thrum: 1234567, Thang: 2.2, Thung: 3.33333, @@ -195,16 +195,12 @@ func TestNewNodeBuilder(t *testing.T) { }, } - nb := NewNodeBuilder(&t1, &t1) + nb := NewNodeBuilder(&t1, nil) node := nb.Render() data, _ := yaml.Marshal(node) - desired := `thrug: - chicken: nuggets -thomp: - meddy: princess -thing: ding + desired := `thing: ding thong: "1" thrum: "1234567" thang: 2.20 @@ -217,9 +213,13 @@ tharg: - chicken - nuggets type: chicken +thrug: + chicken: nuggets thoom: - maddy: champion - ember: naughty +thomp: + meddy: princess x-pizza: time` assert.Equal(t, desired, strings.TrimSpace(string(data))) @@ -306,22 +306,25 @@ func TestNewNodeBuilder_NoValue(t *testing.T) { Thing: "", } + nodeEnty := NodeEntry{} nb := NewNodeBuilder(&t1, &t1) - node := nb.AddYAMLNode(nil, "", "", nil, 0) + node := nb.AddYAMLNode(nil, &nodeEnty) assert.Nil(t, node) } func TestNewNodeBuilder_EmptyString(t *testing.T) { t1 := new(test1) + nodeEnty := NodeEntry{} nb := NewNodeBuilder(t1, t1) - node := nb.AddYAMLNode(nil, "", "", "", 0) + node := nb.AddYAMLNode(nil, &nodeEnty) assert.Nil(t, node) } func TestNewNodeBuilder_Bool(t *testing.T) { t1 := new(test1) nb := NewNodeBuilder(t1, t1) - node := nb.AddYAMLNode(nil, "", "", false, 0) + nodeEnty := NodeEntry{} + node := nb.AddYAMLNode(nil, &nodeEnty) assert.Nil(t, node) } @@ -329,7 +332,8 @@ func TestNewNodeBuilder_Int(t *testing.T) { t1 := new(test1) nb := NewNodeBuilder(t1, t1) p := utils.CreateEmptyMapNode() - node := nb.AddYAMLNode(p, "p", "p", 12, 0) + nodeEnty := NodeEntry{Tag: "p", Value: 12, Key: "p"} + node := nb.AddYAMLNode(p, &nodeEnty) assert.NotNil(t, node) assert.Len(t, node.Content, 2) assert.Equal(t, "12", node.Content[1].Value) @@ -339,7 +343,8 @@ func TestNewNodeBuilder_Int64(t *testing.T) { t1 := new(test1) nb := NewNodeBuilder(t1, t1) p := utils.CreateEmptyMapNode() - node := nb.AddYAMLNode(p, "p", "p", int64(234556), 0) + nodeEnty := NodeEntry{Tag: "p", Value: int64(234556), Key: "p"} + node := nb.AddYAMLNode(p, &nodeEnty) assert.NotNil(t, node) assert.Len(t, node.Content, 2) assert.Equal(t, "234556", node.Content[1].Value) @@ -349,7 +354,8 @@ func TestNewNodeBuilder_Float32(t *testing.T) { t1 := new(test1) nb := NewNodeBuilder(t1, t1) p := utils.CreateEmptyMapNode() - node := nb.AddYAMLNode(p, "p", "p", float32(1234.23), 0) + nodeEnty := NodeEntry{Tag: "p", Value: float32(1234.23), Key: "p"} + node := nb.AddYAMLNode(p, &nodeEnty) assert.NotNil(t, node) assert.Len(t, node.Content, 2) assert.Equal(t, "1234.23", node.Content[1].Value) @@ -359,7 +365,8 @@ func TestNewNodeBuilder_Float64(t *testing.T) { t1 := new(test1) nb := NewNodeBuilder(t1, t1) p := utils.CreateEmptyMapNode() - node := nb.AddYAMLNode(p, "p", "p", 1234.232323, 0) + nodeEnty := NodeEntry{Tag: "p", Value: 1234.232323, Key: "p"} + node := nb.AddYAMLNode(p, &nodeEnty) assert.NotNil(t, node) assert.Len(t, node.Content, 2) assert.Equal(t, "1234.232323", node.Content[1].Value) @@ -478,7 +485,8 @@ func TestNewNodeBuilder_MissingLabel(t *testing.T) { t1 := new(test1) nb := NewNodeBuilder(t1, t1) p := utils.CreateEmptyMapNode() - node := nb.AddYAMLNode(p, "", "p", 1234.232323, 0) + nodeEnty := NodeEntry{Value: 1234.232323, Key: "p"} + node := nb.AddYAMLNode(p, &nodeEnty) assert.NotNil(t, node) assert.Len(t, node.Content, 0) } diff --git a/datamodel/high/v3/document.go b/datamodel/high/v3/document.go index 72ab078..03fc57f 100644 --- a/datamodel/high/v3/document.go +++ b/datamodel/high/v3/document.go @@ -50,6 +50,7 @@ type Document struct { // an empty security requirement ({}) can be included in the array. // - https://spec.openapis.org/oas/v3.1.0#security-requirement-object Security []*base.SecurityRequirement `json:"security,omitempty" yaml:"security,omitempty"` + //Security []*base.SecurityRequirement `json:"-" yaml:"-"` // Tags is a slice of base.Tag instances defined by the specification // A list of tags used by the document with additional metadata. The order of the tags can be used to reflect on @@ -70,7 +71,7 @@ type Document struct { // The default value for the $schema keyword within Schema Objects contained within this OAS document. // This MUST be in the form of a URI. // - https://spec.openapis.org/oas/v3.1.0#schema-object - JsonSchemaDialect string `json:"$schema,omitempty" yaml:"$schema,omitempty"` + JsonSchemaDialect string `json:"jsonSchemaDialect,omitempty" yaml:"jsonSchemaDialect,omitempty"` // Webhooks is a 3.1+ property that is similar to callbacks, except, this defines incoming webhooks. // The incoming webhooks that MAY be received as part of this API and that the API consumer MAY choose to implement. diff --git a/datamodel/high/v3/request_body.go b/datamodel/high/v3/request_body.go index 3237868..44abeb5 100644 --- a/datamodel/high/v3/request_body.go +++ b/datamodel/high/v3/request_body.go @@ -14,7 +14,7 @@ import ( type RequestBody struct { Description string `json:"description,omitempty" yaml:"description,omitempty"` Content map[string]*MediaType `json:"content,omitempty" yaml:"content,omitempty"` - Required bool `json:"required,omitempty" yaml:"required,omitempty"` + Required *bool `json:"required,omitempty" yaml:"required,renderZero,omitempty"` Extensions map[string]any `json:"-" yaml:"-"` low *low.RequestBody } @@ -24,7 +24,9 @@ func NewRequestBody(rb *low.RequestBody) *RequestBody { r := new(RequestBody) r.low = rb r.Description = rb.Description.Value - r.Required = rb.Required.Value + if rb.Required.ValueNode != nil { + r.Required = &rb.Required.Value + } r.Extensions = high.ExtractExtensions(rb.Extensions) r.Content = ExtractContent(rb.Content.Value) return r diff --git a/datamodel/high/v3/request_body_test.go b/datamodel/high/v3/request_body_test.go index 5258f84..976f1cd 100644 --- a/datamodel/high/v3/request_body_test.go +++ b/datamodel/high/v3/request_body_test.go @@ -11,9 +11,10 @@ import ( func TestRequestBody_MarshalYAML(t *testing.T) { + rb := true req := &RequestBody{ Description: "beer", - Required: true, + Required: &rb, Extensions: map[string]interface{}{"x-high-gravity": "why not?"}, } @@ -26,3 +27,38 @@ x-high-gravity: why not?` assert.Equal(t, desired, strings.TrimSpace(string(rend))) } + +func TestRequestBody_MarshalNoRequired(t *testing.T) { + rb := false + req := &RequestBody{ + Description: "beer", + Required: &rb, + Extensions: map[string]interface{}{"x-high-gravity": "why not?"}, + } + + rend, _ := req.Render() + + desired := `description: beer +required: false +x-high-gravity: why not?` + + assert.Equal(t, desired, strings.TrimSpace(string(rend))) + +} + +func TestRequestBody_MarshalRequiredNil(t *testing.T) { + + req := &RequestBody{ + Description: "beer", + Extensions: map[string]interface{}{"x-high-gravity": "why not?"}, + } + + rend, _ := req.Render() + + desired := `description: beer +x-high-gravity: why not?` + + assert.Equal(t, desired, strings.TrimSpace(string(rend))) + +} + diff --git a/datamodel/low/base/schema_test.go b/datamodel/low/base/schema_test.go index 43ab907..e7b0863 100644 --- a/datamodel/low/base/schema_test.go +++ b/datamodel/low/base/schema_test.go @@ -1468,6 +1468,31 @@ func TestSchema_Hash_AdditionalPropsSlice(t *testing.T) { assert.Equal(t, lHash, rHash) } +func TestSchema_Hash_AdditionalPropsSliceNoMap(t *testing.T) { + left := `schema: + additionalProperties: + - hello` + + right := `schema: + additionalProperties: + - hello` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + lDoc, _ := ExtractSchema(lNode.Content[0], nil) + rDoc, _ := ExtractSchema(rNode.Content[0], nil) + + assert.NotNil(t, lDoc) + assert.NotNil(t, rDoc) + + lHash := lDoc.Value.Schema().Hash() + rHash := rDoc.Value.Schema().Hash() + + assert.Equal(t, lHash, rHash) +} + func TestSchema_Hash_NotEqual(t *testing.T) { left := `schema: title: an OK message - but different diff --git a/datamodel/low/extraction_functions_test.go b/datamodel/low/extraction_functions_test.go index 02b36ae..fac16a8 100644 --- a/datamodel/low/extraction_functions_test.go +++ b/datamodel/low/extraction_functions_test.go @@ -1536,3 +1536,27 @@ func TestGenerateHashString(t *testing.T) { GenerateHashString("hello")) } + +func TestSetReference(t *testing.T) { + + type testObj struct { + *Reference + } + + n := testObj{Reference: &Reference{}} + SetReference(&n, "#/pigeon/street") + + assert.Equal(t, "#/pigeon/street", n.GetReference()) + +} + +func TestSetReference_nil(t *testing.T) { + + type testObj struct { + *Reference + } + + n := testObj{Reference: &Reference{}} + SetReference(nil, "#/pigeon/street") + assert.NotEqual(t, "#/pigeon/street", n.GetReference()) +} diff --git a/datamodel/low/model_builder_test.go b/datamodel/low/model_builder_test.go index 3ba6f3d..ec9a79b 100644 --- a/datamodel/low/model_builder_test.go +++ b/datamodel/low/model_builder_test.go @@ -27,6 +27,7 @@ type hotdog struct { LotsOfUnknowns []NodeReference[any] Where map[string]NodeReference[any] There map[string]NodeReference[string] + AllTheThings NodeReference[map[KeyReference[string]]ValueReference[string]] } func TestBuildModel_Mismatch(t *testing.T) { @@ -99,7 +100,10 @@ where: bears: 200 there: oh: yeah - care: bear` + care: bear +allTheThings: + beer: isGood + cake: isNice` var rootNode yaml.Node mErr := yaml.Unmarshal([]byte(yml), &rootNode) @@ -129,6 +133,16 @@ there: assert.Len(t, hd.There, 2) assert.Equal(t, "bear", hd.There["care"].Value) assert.Equal(t, 324938249028.98234892374892374923874823974, hd.Mustard.Value) + + allTheThings := hd.AllTheThings.Value + for i := range allTheThings { + if i.Value == "beer" { + assert.Equal(t, "isGood", allTheThings[i].Value) + } + if i.Value == "cake" { + assert.Equal(t, "isNice", allTheThings[i].Value) + } + } assert.NoError(t, cErr) } diff --git a/datamodel/low/reference.go b/datamodel/low/reference.go index cc1ce3c..30b75d8 100644 --- a/datamodel/low/reference.go +++ b/datamodel/low/reference.go @@ -269,7 +269,9 @@ func (n ValueReference[T]) MarshalYAML() (interface{}, error) { nodes := make([]*yaml.Node, 2) nodes[0] = utils.CreateStringNode("$ref") nodes[1] = utils.CreateStringNode(n.Reference) - return nodes, nil + m := utils.CreateEmptyMapNode() + m.Content = nodes + return m, nil } var h yaml.Node e := n.ValueNode.Decode(&h) diff --git a/datamodel/low/reference_test.go b/datamodel/low/reference_test.go index 8fba042..11d433f 100644 --- a/datamodel/low/reference_test.go +++ b/datamodel/low/reference_test.go @@ -5,6 +5,9 @@ package low import ( "crypto/sha256" + "fmt" + "github.com/pb33f/libopenapi/utils" + "strings" "testing" "github.com/pb33f/libopenapi/index" @@ -558,3 +561,196 @@ func TestHashToString(t *testing.T) { HashToString(sha256.Sum256([]byte("12345")))) } + +func TestReference_IsReference(t *testing.T) { + ref := Reference{ + Reference: "#/components/schemas/SomeSchema", + } + assert.True(t, ref.IsReference()) + +} + +func TestNodeReference_NodeLineNumber(t *testing.T) { + + n := utils.CreateStringNode("pizza") + nr := NodeReference[string]{ + Value: "pizza", + ValueNode: n, + } + + n.Line = 3 + assert.Equal(t, 3, nr.NodeLineNumber()) +} + +func TestNodeReference_NodeLineNumberEmpty(t *testing.T) { + + nr := NodeReference[string]{ + Value: "pizza", + } + assert.Equal(t, 0, nr.NodeLineNumber()) +} + +func TestNodeReference_GetReference(t *testing.T) { + + nr := NodeReference[string]{ + Reference: "#/happy/sunday", + } + assert.Equal(t, "#/happy/sunday", nr.GetReference()) +} + +func TestNodeReference_SetReference(t *testing.T) { + + nr := NodeReference[string]{} + nr.SetReference("#/happy/sunday") +} + +func TestNodeReference_IsReference(t *testing.T) { + + nr := NodeReference[string]{ + ReferenceNode: true, + } + assert.True(t, nr.IsReference()) +} + +func TestNodeReference_GetKeyNode(t *testing.T) { + + nr := NodeReference[string]{ + KeyNode: utils.CreateStringNode("pizza"), + } + assert.Equal(t, "pizza", nr.GetKeyNode().Value) + +} + +func TestNodeReference_GetValueUntyped(t *testing.T) { + + type anything struct { + thing string + } + + nr := NodeReference[any]{ + Value: anything{thing: "ding"}, + } + + assert.Equal(t, "{ding}", fmt.Sprint(nr.GetValueUntyped())) +} + +func TestValueReference_NodeLineNumber(t *testing.T) { + + n := utils.CreateStringNode("pizza") + nr := ValueReference[string]{ + Value: "pizza", + ValueNode: n, + } + + n.Line = 3 + assert.Equal(t, 3, nr.NodeLineNumber()) +} + +func TestValueReference_NodeLineNumber_Nil(t *testing.T) { + + nr := ValueReference[string]{ + Value: "pizza", + } + + assert.Equal(t, 0, nr.NodeLineNumber()) +} + +func TestValueReference_GetReference(t *testing.T) { + + nr := ValueReference[string]{ + Reference: "#/happy/sunday", + } + assert.Equal(t, "#/happy/sunday", nr.GetReference()) +} + +func TestValueReference_SetReference(t *testing.T) { + + nr := ValueReference[string]{} + nr.SetReference("#/happy/sunday") +} + +func TestValueReference_GetValueUntyped(t *testing.T) { + + type anything struct { + thing string + } + + nr := ValueReference[any]{ + Value: anything{thing: "ding"}, + } + + assert.Equal(t, "{ding}", fmt.Sprint(nr.GetValueUntyped())) +} + +func TestValueReference_IsReference(t *testing.T) { + + nr := NodeReference[string]{ + ReferenceNode: true, + } + assert.True(t, nr.IsReference()) +} + +func TestValueReference_MarshalYAML_Ref(t *testing.T) { + + nr := ValueReference[string]{ + ReferenceNode: true, + Reference: "#/burgers/beer", + } + + data, _ := yaml.Marshal(nr) + assert.Equal(t, `$ref: '#/burgers/beer'`, strings.TrimSpace(string(data))) + +} + +func TestValueReference_MarshalYAML(t *testing.T) { + + v := map[string]interface{}{ + "beer": "burger", + "wine": "cheese", + } + + var enc yaml.Node + enc.Encode(&v) + + nr := ValueReference[any]{ + Value: v, + ValueNode: &enc, + } + + data, _ := yaml.Marshal(nr) + + expected := `beer: burger +wine: cheese` + + assert.Equal(t, expected, strings.TrimSpace(string(data))) +} + +func TestKeyReference_GetValueUntyped(t *testing.T) { + + type anything struct { + thing string + } + + nr := KeyReference[any]{ + Value: anything{thing: "ding"}, + } + + assert.Equal(t, "{ding}", fmt.Sprint(nr.GetValueUntyped())) +} + +func TestKeyReference_GetKeyNode(t *testing.T) { + kn := utils.CreateStringNode("pizza") + kn.Line = 3 + + nr := KeyReference[any]{ + KeyNode: kn, + } + + assert.Equal(t, 3, nr.GetKeyNode().Line) + assert.Equal(t, "pizza", nr.GetKeyNode().Value) +} + + + + + diff --git a/datamodel/low/v2/swagger_test.go b/datamodel/low/v2/swagger_test.go index 9c7bc37..a765985 100644 --- a/datamodel/low/v2/swagger_test.go +++ b/datamodel/low/v2/swagger_test.go @@ -20,7 +20,10 @@ func initTest() { data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml") info, _ := datamodel.ExtractSpecInfo(data) var err []error - doc, err = CreateDocument(info) + doc, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) wait := true for wait { select { @@ -38,7 +41,10 @@ func BenchmarkCreateDocument(b *testing.B) { data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml") info, _ := datamodel.ExtractSpecInfo(data) for i := 0; i < b.N; i++ { - doc, _ = CreateDocument(info) + doc, _ = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) } } diff --git a/datamodel/low/v3/constants.go b/datamodel/low/v3/constants.go index 6b91d14..ac2b666 100644 --- a/datamodel/low/v3/constants.go +++ b/datamodel/low/v3/constants.go @@ -11,28 +11,29 @@ const ( HeadersLabel = "headers" ExpressionLabel = "expression" InfoLabel = "info" - SwaggerLabel = "swagger" - ParametersLabel = "parameters" - RequestBodyLabel = "requestBody" - RequestBodiesLabel = "requestBodies" - ResponsesLabel = "responses" - CallbacksLabel = "callbacks" - ContentLabel = "content" - PathsLabel = "paths" - PathLabel = "path" - WebhooksLabel = "webhooks" - JSONSchemaDialectLabel = "jsonSchemaDialect" - GetLabel = "get" - PostLabel = "post" - PatchLabel = "patch" - PutLabel = "put" - DeleteLabel = "delete" - OptionsLabel = "options" - HeadLabel = "head" - TraceLabel = "trace" - LinksLabel = "links" - DefaultLabel = "default" - SecurityLabel = "security" + SwaggerLabel = "swagger" + ParametersLabel = "parameters" + RequestBodyLabel = "requestBody" + RequestBodiesLabel = "requestBodies" + ResponsesLabel = "responses" + CallbacksLabel = "callbacks" + ContentLabel = "content" + PathsLabel = "paths" + PathLabel = "path" + WebhooksLabel = "webhooks" + JSONSchemaDialectLabel = "jsonSchemaDialect" + JSONSchemaLabel = "$schema" + GetLabel = "get" + PostLabel = "post" + PatchLabel = "patch" + PutLabel = "put" + DeleteLabel = "delete" + OptionsLabel = "options" + HeadLabel = "head" + TraceLabel = "trace" + LinksLabel = "links" + DefaultLabel = "default" + SecurityLabel = "security" SecuritySchemesLabel = "securitySchemes" OAuthFlowsLabel = "flows" VariablesLabel = "variables" diff --git a/datamodel/low/v3/create_document_test.go b/datamodel/low/v3/create_document_test.go index 0a8b07c..5f4d405 100644 --- a/datamodel/low/v3/create_document_test.go +++ b/datamodel/low/v3/create_document_test.go @@ -19,6 +19,7 @@ func initTest() { data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml") info, _ := datamodel.ExtractSpecInfo(data) var err []error + // deprecated function test. doc, err = CreateDocument(info) if err != nil { panic("broken something") @@ -29,7 +30,10 @@ func BenchmarkCreateDocument(b *testing.B) { data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml") info, _ := datamodel.ExtractSpecInfo(data) for i := 0; i < b.N; i++ { - doc, _ = CreateDocument(info) + doc, _ = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) } } @@ -37,7 +41,10 @@ func BenchmarkCreateDocument_Circular(b *testing.B) { data, _ := ioutil.ReadFile("../../../test_specs/circular-tests.yaml") info, _ := datamodel.ExtractSpecInfo(data) for i := 0; i < b.N; i++ { - _, err := CreateDocument(info) + _, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) if err != nil { panic("this should not error") } @@ -51,7 +58,10 @@ func BenchmarkCreateDocument_k8s(b *testing.B) { for i := 0; i < b.N; i++ { - _, err := CreateDocument(info) + _, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) if err != nil { panic("this should not error") } @@ -62,7 +72,10 @@ func TestCircularReferenceError(t *testing.T) { data, _ := ioutil.ReadFile("../../../test_specs/circular-tests.yaml") info, _ := datamodel.ExtractSpecInfo(data) - circDoc, err := CreateDocument(info) + circDoc, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) assert.NotNil(t, circDoc) assert.Len(t, err, 3) } @@ -72,7 +85,10 @@ func BenchmarkCreateDocument_Stripe(b *testing.B) { info, _ := datamodel.ExtractSpecInfo(data) for i := 0; i < b.N; i++ { - _, err := CreateDocument(info) + _, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) if err != nil { panic("this should not error") } @@ -83,7 +99,10 @@ func BenchmarkCreateDocument_Petstore(b *testing.B) { data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json") info, _ := datamodel.ExtractSpecInfo(data) for i := 0; i < b.N; i++ { - _, err := CreateDocument(info) + _, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) if err != nil { panic("this should not error") } @@ -94,7 +113,11 @@ func TestCreateDocumentStripe(t *testing.T) { data, _ := ioutil.ReadFile("../../../test_specs/stripe.yaml") info, _ := datamodel.ExtractSpecInfo(data) - d, err := CreateDocument(info) + d, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + BasePath: "/here", + }) assert.Len(t, err, 3) assert.Equal(t, "3.0.0", d.Version.Value) @@ -137,7 +160,10 @@ func TestCreateDocument_WebHooks_Error(t *testing.T) { info, _ := datamodel.ExtractSpecInfo([]byte(yml)) var err []error - _, err = CreateDocument(info) + _, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) assert.Len(t, err, 1) } @@ -523,7 +549,10 @@ components: info, _ := datamodel.ExtractSpecInfo([]byte(yml)) var err []error - doc, err = CreateDocument(info) + doc, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) assert.Len(t, err, 0) ob := doc.Components.Value.FindSchema("bork").Value @@ -539,7 +568,10 @@ webhooks: info, _ := datamodel.ExtractSpecInfo([]byte(yml)) var err []error - doc, err = CreateDocument(info) + doc, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) assert.Len(t, err, 1) } @@ -552,7 +584,10 @@ components: info, _ := datamodel.ExtractSpecInfo([]byte(yml)) var err []error - _, err = CreateDocument(info) + _, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) assert.Len(t, err, 1) } @@ -565,7 +600,10 @@ paths: info, _ := datamodel.ExtractSpecInfo([]byte(yml)) var err []error - _, err = CreateDocument(info) + _, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) assert.Len(t, err, 1) } @@ -576,7 +614,10 @@ tags: info, _ := datamodel.ExtractSpecInfo([]byte(yml)) var err []error - _, err = CreateDocument(info) + _, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) assert.Len(t, err, 1) } @@ -587,7 +628,10 @@ security: info, _ := datamodel.ExtractSpecInfo([]byte(yml)) var err []error - _, err = CreateDocument(info) + _, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) assert.Len(t, err, 1) } @@ -598,7 +642,10 @@ externalDocs: info, _ := datamodel.ExtractSpecInfo([]byte(yml)) var err []error - _, err = CreateDocument(info) + _, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) assert.Len(t, err, 1) } @@ -612,7 +659,10 @@ func ExampleCreateDocument() { info, _ := datamodel.ExtractSpecInfo(petstoreBytes) // build low-level document model - document, errors := CreateDocument(info) + document, errors := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + }) // if something went wrong, a slice of errors is returned if len(errors) > 0 { diff --git a/document.go b/document.go index 437a15a..7ecfa57 100644 --- a/document.go +++ b/document.go @@ -70,7 +70,7 @@ type Document interface { // **IMPORTANT** This method only supports OpenAPI Documents. The Swagger model will not support mutations correctly // and will not update when called. This choice has been made because we don't want to continue supporting Swagger, // it's too old, so it should be motivation to upgrade to OpenAPI 3. - RenderAndReload() ([]byte, *Document, *DocumentModel[v3high.Document], []error) + RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], []error) // Serialize will re-render a Document back into a []byte slice. If any modifications have been made to the // underlying data model using low level APIs, then those changes will be reflected in the serialized output. @@ -156,7 +156,7 @@ func (d *document) Serialize() ([]byte, error) { } } -func (d *document) RenderAndReload() ([]byte, *Document, *DocumentModel[v3high.Document], []error) { +func (d *document) RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], []error) { if d.highSwaggerModel != nil && d.highOpenAPI3Model == nil { return nil, nil, nil, []error{errors.New("this method only supports OpenAPI 3 documents, not Swagger")} } @@ -166,15 +166,15 @@ func (d *document) RenderAndReload() ([]byte, *Document, *DocumentModel[v3high.D } newDoc, err := NewDocument(newBytes) if err != nil { - return newBytes, &newDoc, nil, []error{err} + return newBytes, newDoc, nil, []error{err} } // build the model. model, errs := newDoc.BuildV3Model() if errs != nil { - return newBytes, &newDoc, model, errs + return newBytes, newDoc, model, errs } // this document is now dead, long live the new document! - return newBytes, &newDoc, model, nil + return newBytes, newDoc, model, nil } func (d *document) BuildV2Model() (*DocumentModel[v2high.Swagger], []error) { @@ -278,14 +278,13 @@ func CompareDocuments(original, updated Document) (*model.DocumentChanges, []err } v3ModelRight, errs := updated.BuildV3Model() if len(errs) > 0 { - errors = errs + errors = append(errors, errs...) } if v3ModelLeft != nil && v3ModelRight != nil { return what_changed.CompareOpenAPIDocuments(v3ModelLeft.Model.GoLow(), v3ModelRight.Model.GoLow()), errors } else { - return nil, errs + return nil, errors } - } if original.GetSpecInfo().SpecType == utils.OpenApi2 && updated.GetSpecInfo().SpecType == utils.OpenApi2 { v2ModelLeft, errs := original.BuildV2Model() @@ -294,13 +293,13 @@ func CompareDocuments(original, updated Document) (*model.DocumentChanges, []err } v2ModelRight, errs := updated.BuildV2Model() if len(errs) > 0 { - errors = errs + errors = append(errors, errs...) } if v2ModelLeft != nil && v2ModelRight != nil { return what_changed.CompareSwaggerDocuments(v2ModelLeft.Model.GoLow(), v2ModelRight.Model.GoLow()), errors } else { - return nil, errs + return nil, errors } } - return nil, nil + return nil, []error{fmt.Errorf("unable to compare documents, one or both documents are not of the same version")} } diff --git a/document_examples_test.go b/document_examples_test.go new file mode 100644 index 0000000..29d9cf2 --- /dev/null +++ b/document_examples_test.go @@ -0,0 +1,601 @@ +// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package libopenapi + +import ( + "fmt" + "github.com/pb33f/libopenapi/datamodel" + "io/ioutil" + "net/url" + "strings" + "testing" + + "github.com/pb33f/libopenapi/datamodel/high" + low "github.com/pb33f/libopenapi/datamodel/low/base" + v3 "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/resolver" + "github.com/pb33f/libopenapi/utils" + "github.com/stretchr/testify/assert" +) + +func ExampleNewDocument_fromOpenAPI3Document() { + + // How to read in an OpenAPI 3 Specification, into a Document. + + // load an OpenAPI 3 specification from bytes + petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json") + + // create a new document from specification bytes + document, err := NewDocument(petstore) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // because we know this is a v3 spec, we can build a ready to go model from it. + v3Model, errors := document.BuildV3Model() + + // if anything went wrong when building the v3 model, a slice of errors will be returned + if len(errors) > 0 { + for i := range errors { + fmt.Printf("error: %e\n", errors[i]) + } + panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) + } + + // get a count of the number of paths and schemas. + paths := len(v3Model.Model.Paths.PathItems) + schemas := len(v3Model.Model.Components.Schemas) + + // print the number of paths and schemas in the document + fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) + // Output: There are 13 paths and 8 schemas in the document +} + +func ExampleNewDocument_fromWithDocumentConfigurationFailure() { + + // This example shows how to create a document that prevents the loading of external references/ + // from files or the network + + // load in the Digital Ocean OpenAPI specification + digitalOcean, _ := ioutil.ReadFile("test_specs/digitalocean.yaml") + + // create a DocumentConfiguration that prevents loading file and remote references + config := datamodel.DocumentConfiguration{ + AllowFileReferences: false, + AllowRemoteReferences: false, + } + + // create a new document from specification bytes + doc, err := NewDocumentWithConfiguration(digitalOcean, &config) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // only errors will be thrown, so just capture them and print the number of errors. + _, errors := doc.BuildV3Model() + + // if anything went wrong when building the v3 model, a slice of errors will be returned + if len(errors) > 0 { + fmt.Println("Error building Digital Ocean spec errors reported") + } + // Output: Error building Digital Ocean spec errors reported +} + +func ExampleNewDocument_fromWithDocumentConfigurationSuccess() { + + // This example shows how to create a document that prevents the loading of external references/ + // from files or the network + + // load in the Digital Ocean OpenAPI specification + digitalOcean, _ := ioutil.ReadFile("test_specs/digitalocean.yaml") + + // Digital Ocean needs a baseURL to be set, so we can resolve relative references. + baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification") + + // create a DocumentConfiguration that allows loading file and remote references, and sets the baseURL + // to somewhere that can resolve the relative references. + config := datamodel.DocumentConfiguration{ + AllowFileReferences: true, + AllowRemoteReferences: true, + BaseURL: baseURL, + } + + // create a new document from specification bytes + doc, err := NewDocumentWithConfiguration(digitalOcean, &config) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // only errors will be thrown, so just capture them and print the number of errors. + _, errors := doc.BuildV3Model() + + // if anything went wrong when building the v3 model, a slice of errors will be returned + if len(errors) > 0 { + fmt.Println("Error building Digital Ocean spec errors reported") + } else { + fmt.Println("Digital Ocean spec built successfully") + } + // Output: Digital Ocean spec built successfully +} + +func ExampleNewDocument_fromSwaggerDocument() { + + // How to read in a Swagger / OpenAPI 2 Specification, into a Document. + + // load a Swagger specification from bytes + petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json") + + // create a new document from specification bytes + document, err := NewDocument(petstore) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // because we know this is a v2 spec, we can build a ready to go model from it. + v2Model, errors := document.BuildV2Model() + + // if anything went wrong when building the v3 model, a slice of errors will be returned + if len(errors) > 0 { + for i := range errors { + fmt.Printf("error: %e\n", errors[i]) + } + panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) + } + + // get a count of the number of paths and schemas. + paths := len(v2Model.Model.Paths.PathItems) + schemas := len(v2Model.Model.Definitions.Definitions) + + // print the number of paths and schemas in the document + fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) + // Output: There are 14 paths and 6 schemas in the document +} + +func ExampleNewDocument_fromUnknownVersion() { + + // load an unknown version of an OpenAPI spec + petstore, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml") + + // create a new document from specification bytes + document, err := NewDocument(petstore) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + var paths, schemas int + var errors []error + + // We don't know which type of document this is, so we can use the spec info to inform us + if document.GetSpecInfo().SpecType == utils.OpenApi3 { + v3Model, errs := document.BuildV3Model() + if len(errs) > 0 { + errors = errs + } + if len(errors) <= 0 { + paths = len(v3Model.Model.Paths.PathItems) + schemas = len(v3Model.Model.Components.Schemas) + } + } + if document.GetSpecInfo().SpecType == utils.OpenApi2 { + v2Model, errs := document.BuildV2Model() + if len(errs) > 0 { + errors = errs + } + if len(errors) <= 0 { + paths = len(v2Model.Model.Paths.PathItems) + schemas = len(v2Model.Model.Definitions.Definitions) + } + } + + // if anything went wrong when building the model, report errors. + if len(errors) > 0 { + for i := range errors { + fmt.Printf("error: %e\n", errors[i]) + } + panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) + } + + // print the number of paths and schemas in the document + fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) + // Output: There are 5 paths and 6 schemas in the document +} + +func ExampleNewDocument_mutateValuesAndSerialize() { + + // How to mutate values in an OpenAPI Specification, without re-ordering original content. + + // create very small, and useless spec that does nothing useful, except showcase this feature. + spec := ` +openapi: 3.1.0 +info: + title: This is a title + contact: + name: Some Person + email: some@emailaddress.com + license: + url: http://some-place-on-the-internet.com/license +` + // create a new document from specification bytes + document, err := NewDocument([]byte(spec)) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // because we know this is a v3 spec, we can build a ready to go model from it. + v3Model, errors := document.BuildV3Model() + + // if anything went wrong when building the v3 model, a slice of errors will be returned + if len(errors) > 0 { + for i := range errors { + fmt.Printf("error: %e\n", errors[i]) + } + panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) + } + + // mutate the title, to do this we currently need to drop down to the low-level API. + v3Model.Model.GoLow().Info.Value.Title.Mutate("A new title for a useless spec") + + // mutate the email address in the contact object. + v3Model.Model.GoLow().Info.Value.Contact.Value.Email.Mutate("buckaroo@pb33f.io") + + // mutate the name in the contact object. + v3Model.Model.GoLow().Info.Value.Contact.Value.Name.Mutate("Buckaroo") + + // mutate the URL for the license object. + v3Model.Model.GoLow().Info.Value.License.Value.URL.Mutate("https://pb33f.io/license") + + // serialize the document back into the original YAML or JSON + mutatedSpec, serialError := document.Serialize() + + // if something went wrong serializing + if serialError != nil { + panic(fmt.Sprintf("cannot serialize document: %e", serialError)) + } + + // print our modified spec! + fmt.Println(string(mutatedSpec)) + // Output: openapi: 3.1.0 + //info: + // title: A new title for a useless spec + // contact: + // name: Buckaroo + // email: buckaroo@pb33f.io + // license: + // url: https://pb33f.io/license +} + +func ExampleCompareDocuments_openAPI() { + + // How to compare two different OpenAPI specifications. + + // load an original OpenAPI 3 specification from bytes + burgerShopOriginal, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml") + + // load an **updated** OpenAPI 3 specification from bytes + burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml") + + // create a new document from original specification bytes + originalDoc, err := NewDocument(burgerShopOriginal) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // create a new document from updated specification bytes + updatedDoc, err := NewDocument(burgerShopUpdated) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // Compare documents for all changes made + documentChanges, errs := CompareDocuments(originalDoc, updatedDoc) + + // If anything went wrong when building models for documents. + if len(errs) > 0 { + for i := range errs { + fmt.Printf("error: %e\n", errs[i]) + } + panic(fmt.Sprintf("cannot compare documents: %d errors reported", len(errs))) + } + + // Extract SchemaChanges from components changes. + schemaChanges := documentChanges.ComponentsChanges.SchemaChanges + + // Print out some interesting stats about the OpenAPI document changes. + fmt.Printf("There are %d changes, of which %d are breaking. %v schemas have changes.", + documentChanges.TotalChanges(), documentChanges.TotalBreakingChanges(), len(schemaChanges)) + //Output: There are 72 changes, of which 17 are breaking. 5 schemas have changes. + +} + +func ExampleCompareDocuments_swagger() { + + // How to compare two different Swagger specifications. + + // load an original OpenAPI 3 specification from bytes + petstoreOriginal, _ := ioutil.ReadFile("test_specs/petstorev2-complete.yaml") + + // load an **updated** OpenAPI 3 specification from bytes + petstoreUpdated, _ := ioutil.ReadFile("test_specs/petstorev2-complete-modified.yaml") + + // create a new document from original specification bytes + originalDoc, err := NewDocument(petstoreOriginal) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // create a new document from updated specification bytes + updatedDoc, err := NewDocument(petstoreUpdated) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // Compare documents for all changes made + documentChanges, errs := CompareDocuments(originalDoc, updatedDoc) + + // If anything went wrong when building models for documents. + if len(errs) > 0 { + for i := range errs { + fmt.Printf("error: %e\n", errs[i]) + } + panic(fmt.Sprintf("cannot compare documents: %d errors reported", len(errs))) + } + + // Extract SchemaChanges from components changes. + schemaChanges := documentChanges.ComponentsChanges.SchemaChanges + + // Print out some interesting stats about the Swagger document changes. + fmt.Printf("There are %d changes, of which %d are breaking. %v schemas have changes.", + documentChanges.TotalChanges(), documentChanges.TotalBreakingChanges(), len(schemaChanges)) + //Output: There are 52 changes, of which 27 are breaking. 5 schemas have changes. + +} + +func TestDocument_Paths_As_Array(t *testing.T) { + + // paths can now be wrapped in an array. + spec := `{ + "openapi": "3.1.0", + "paths": [ + "/": { + "get": {} + } + ] +} +` + // create a new document from specification bytes + doc, err := NewDocument([]byte(spec)) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + v3Model, _ := doc.BuildV3Model() + assert.NotNil(t, v3Model) +} + +// If you want to know more about circular references that have been found +// during the parsing/indexing/building of a document, you can capture the +// []errors thrown which are pointers to *resolver.ResolvingError +func ExampleNewDocument_infinite_circular_references() { + + // create a specification with an obvious and deliberate circular reference + spec := `openapi: "3.1" +components: + schemas: + One: + description: "test one" + properties: + things: + "$ref": "#/components/schemas/Two" + required: + - things + Two: + description: "test two" + properties: + testThing: + "$ref": "#/components/schemas/One" + required: + - testThing +` + // create a new document from specification bytes + doc, err := NewDocument([]byte(spec)) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + _, errs := doc.BuildV3Model() + + // extract resolving error + resolvingError := errs[0] + + // resolving error is a pointer to *resolver.ResolvingError + // which provides access to rich details about the error. + circularReference := resolvingError.(*resolver.ResolvingError).CircularReference + + // capture the journey with all details + var buf strings.Builder + for n := range circularReference.Journey { + + // add the full definition name to the journey. + buf.WriteString(circularReference.Journey[n].Definition) + if n < len(circularReference.Journey)-1 { + buf.WriteString(" -> ") + } + } + + // print out the journey and the loop point. + fmt.Printf("Journey: %s\n", buf.String()) + fmt.Printf("Loop Point: %s", circularReference.LoopPoint.Definition) + // Output: Journey: #/components/schemas/Two -> #/components/schemas/One -> #/components/schemas/Two + // Loop Point: #/components/schemas/Two +} + +// This tests checks that circular references which are _not_ marked as required pass correctly +func TestNewDocument_terminable_circular_references(t *testing.T) { + + // create a specification with an obvious and deliberate circular reference + spec := `openapi: "3.1" +components: + schemas: + One: + description: "test one" + properties: + things: + "$ref": "#/components/schemas/Two" + Two: + description: "test two" + properties: + testThing: + "$ref": "#/components/schemas/One" +` + // create a new document from specification bytes + doc, err := NewDocument([]byte(spec)) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + _, errs := doc.BuildV3Model() + + assert.Len(t, errs, 0) +} + +// If you're using complex types with OpenAPI Extensions, it's simple to unpack extensions into complex +// types using `high.UnpackExtensions()`. libopenapi retains the original raw data in the low model (not the high) +// which means unpacking them can be a little complex. +// +// This example demonstrates how to use the `UnpackExtensions` with custom OpenAPI extensions. +func ExampleNewDocument_unpacking_extensions() { + + // define an example struct representing a cake + type cake struct { + Candles int `yaml:"candles"` + Frosting string `yaml:"frosting"` + Some_Strange_Var_Name string `yaml:"someStrangeVarName"` + } + + // define a struct that holds a map of cake pointers. + type cakes struct { + Description string + Cakes map[string]*cake + } + + // define a struct representing a burger + type burger struct { + Sauce string + Patty string + } + + // define a struct that holds a map of cake pointers + type burgers struct { + Description string + Burgers map[string]*burger + } + + // create a specification with a schema and parameter that use complex custom cakes and burgers extensions. + spec := `openapi: "3.1" +components: + schemas: + SchemaOne: + description: "Some schema with custom complex extensions" + x-custom-cakes: + description: some cakes + cakes: + someCake: + candles: 10 + frosting: blue + someStrangeVarName: something + anotherCake: + candles: 1 + frosting: green + parameters: + ParameterOne: + description: "Some parameter also using complex extensions" + x-custom-burgers: + description: some burgers + burgers: + someBurger: + sauce: ketchup + patty: meat + anotherBurger: + sauce: mayo + patty: lamb` + // create a new document from specification bytes + doc, err := NewDocument([]byte(spec)) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // build a v3 model. + docModel, errs := doc.BuildV3Model() + + // if anything went wrong building, indexing and resolving the model, an error is thrown + if errs != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // get a reference to SchemaOne and ParameterOne + schemaOne := docModel.Model.Components.Schemas["SchemaOne"].Schema() + parameterOne := docModel.Model.Components.Parameters["ParameterOne"] + + // unpack schemaOne extensions into complex `cakes` type + schemaOneExtensions, schemaUnpackErrors := high.UnpackExtensions[cakes, *low.Schema](schemaOne) + if schemaUnpackErrors != nil { + panic(fmt.Sprintf("cannot unpack schema extensions: %e", err)) + } + + // unpack parameterOne into complex `burgers` type + parameterOneExtensions, paramUnpackErrors := high.UnpackExtensions[burgers, *v3.Parameter](parameterOne) + if paramUnpackErrors != nil { + panic(fmt.Sprintf("cannot unpack parameter extensions: %e", err)) + } + + // extract extension by name for schemaOne + customCakes := schemaOneExtensions["x-custom-cakes"] + + // extract extension by name for schemaOne + customBurgers := parameterOneExtensions["x-custom-burgers"] + + // print out schemaOne complex extension details. + fmt.Printf("schemaOne 'x-custom-cakes' (%s) has %d cakes, 'someCake' has %d candles and %s frosting\n", + customCakes.Description, + len(customCakes.Cakes), + customCakes.Cakes["someCake"].Candles, + customCakes.Cakes["someCake"].Frosting, + ) + + // print out parameterOne complex extension details. + fmt.Printf("parameterOne 'x-custom-burgers' (%s) has %d burgers, 'anotherBurger' has %s sauce and a %s patty\n", + customBurgers.Description, + len(customBurgers.Burgers), + customBurgers.Burgers["anotherBurger"].Sauce, + customBurgers.Burgers["anotherBurger"].Patty, + ) + + // Output: schemaOne 'x-custom-cakes' (some cakes) has 2 cakes, 'someCake' has 10 candles and blue frosting + //parameterOne 'x-custom-burgers' (some burgers) has 2 burgers, 'anotherBurger' has mayo sauce and a lamb patty +} diff --git a/document_test.go b/document_test.go index 7633fae..2557de7 100644 --- a/document_test.go +++ b/document_test.go @@ -1,23 +1,15 @@ // Copyright 2022 Princess B33f Heavy Industries / Dave Shanley // SPDX-License-Identifier: MIT - package libopenapi import ( "fmt" - "github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel/high/base" - "io/ioutil" - "net/url" - "strings" - "testing" - - "github.com/pb33f/libopenapi/datamodel/high" - low "github.com/pb33f/libopenapi/datamodel/low/base" - v3 "github.com/pb33f/libopenapi/datamodel/low/v3" - "github.com/pb33f/libopenapi/resolver" - "github.com/pb33f/libopenapi/utils" + "github.com/pb33f/libopenapi/what-changed/model" "github.com/stretchr/testify/assert" + "io/ioutil" + "os" + "testing" ) func TestLoadDocument_Simple_V2(t *testing.T) { @@ -158,6 +150,60 @@ info: assert.Equal(t, ymlModified, string(serial)) } +func TestDocument_RenderAndReload_ChangeCheck_Burgershop(t *testing.T) { + + bs, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml") + doc, _ := NewDocument(bs) + doc.BuildV3Model() + + rend, newDoc, _, _ := doc.RenderAndReload() + os.WriteFile("rendered-bs.yaml", rend, 0644) + + // compare documents + compReport, errs := CompareDocuments(doc, newDoc) + + // should noth be nil. + assert.Nil(t, errs) + assert.NotNil(t, rend) + assert.Nil(t, compReport) + +} + +func TestDocument_RenderAndReload_ChangeCheck_Stripe(t *testing.T) { + + bs, _ := ioutil.ReadFile("test_specs/stripe.yaml") + doc, _ := NewDocument(bs) + doc.BuildV3Model() + + b, newDoc, _, _ := doc.RenderAndReload() + + os.WriteFile("rendered-data.yaml", b, 0644) + + // compare documents + compReport, errs := CompareDocuments(doc, newDoc) + + // get flat list of changes. + flatChanges := compReport.GetAllChanges() + + // remove everything that is a description change (stripe has a lot of those from having 519 empty descriptions) + var filtered []*model.Change + for i := range flatChanges { + if flatChanges[i].Property != "description" { + filtered = append(filtered, flatChanges[i]) + } + } + + assert.Nil(t, errs) + tc := compReport.TotalChanges() + bc := compReport.TotalBreakingChanges() + assert.Equal(t, 0, bc) + assert.Equal(t, 519, tc) + + // there should be no other changes than the 519 descriptions. + assert.Equal(t, 0, len(filtered)) + +} + func TestDocument_RenderAndReload(t *testing.T) { // load an OpenAPI 3 specification from bytes @@ -209,6 +255,41 @@ func TestDocument_RenderAndReload(t *testing.T) { h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl) } +func TestDocument_RenderAndReload_Swagger(t *testing.T) { + petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json") + doc, _ := NewDocument(petstore) + doc.BuildV2Model() + doc.BuildV2Model() + _, _, _, e := doc.RenderAndReload() + assert.Len(t, e, 1) + assert.Equal(t, "this method only supports OpenAPI 3 documents, not Swagger", e[0].Error()) + +} + +func TestDocument_BuildModelPreBuild(t *testing.T) { + petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json") + doc, _ := NewDocument(petstore) + doc.BuildV3Model() + doc.BuildV3Model() + _, _, _, e := doc.RenderAndReload() + assert.Len(t, e, 0) +} + +func TestDocument_BuildModelCircular(t *testing.T) { + petstore, _ := ioutil.ReadFile("test_specs/circular-tests.yaml") + doc, _ := NewDocument(petstore) + m, e := doc.BuildV3Model() + assert.NotNil(t, m) + assert.Len(t, e, 3) +} + +func TestDocument_BuildModelBad(t *testing.T) { + petstore, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml") + doc, _ := NewDocument(petstore) + m, e := doc.BuildV3Model() + assert.Nil(t, m) + assert.Len(t, e, 9) +} func TestDocument_Serialize_JSON_Modified(t *testing.T) { @@ -267,585 +348,49 @@ paths: operation.GoLow().Parameters.Value[0].Reference) } -func ExampleNewDocument_fromOpenAPI3Document() { - - // How to read in an OpenAPI 3 Specification, into a Document. - - // load an OpenAPI 3 specification from bytes - petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json") - - // create a new document from specification bytes - document, err := NewDocument(petstore) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - // because we know this is a v3 spec, we can build a ready to go model from it. - v3Model, errors := document.BuildV3Model() - - // if anything went wrong when building the v3 model, a slice of errors will be returned - if len(errors) > 0 { - for i := range errors { - fmt.Printf("error: %e\n", errors[i]) - } - panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) - } - - // get a count of the number of paths and schemas. - paths := len(v3Model.Model.Paths.PathItems) - schemas := len(v3Model.Model.Components.Schemas) - - // print the number of paths and schemas in the document - fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) - // Output: There are 13 paths and 8 schemas in the document -} - -func ExampleNewDocument_fromWithDocumentConfigurationFailure() { - - // This example shows how to create a document that prevents the loading of external references/ - // from files or the network - - // load in the Digital Ocean OpenAPI specification - digitalOcean, _ := ioutil.ReadFile("test_specs/digitalocean.yaml") - - // create a DocumentConfiguration that prevents loading file and remote references - config := datamodel.DocumentConfiguration{ - AllowFileReferences: false, - AllowRemoteReferences: false, - } - - // create a new document from specification bytes - doc, err := NewDocumentWithConfiguration(digitalOcean, &config) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - // only errors will be thrown, so just capture them and print the number of errors. - _, errors := doc.BuildV3Model() - - // if anything went wrong when building the v3 model, a slice of errors will be returned - if len(errors) > 0 { - fmt.Println("Error building Digital Ocean spec errors reported") - } - // Output: Error building Digital Ocean spec errors reported -} - -func ExampleNewDocument_fromWithDocumentConfigurationSuccess() { - - // This example shows how to create a document that prevents the loading of external references/ - // from files or the network - - // load in the Digital Ocean OpenAPI specification - digitalOcean, _ := ioutil.ReadFile("test_specs/digitalocean.yaml") - - // Digital Ocean needs a baseURL to be set, so we can resolve relative references. - baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification") - - // create a DocumentConfiguration that allows loading file and remote references, and sets the baseURL - // to somewhere that can resolve the relative references. - config := datamodel.DocumentConfiguration{ - AllowFileReferences: true, - AllowRemoteReferences: true, - BaseURL: baseURL, - } - - // create a new document from specification bytes - doc, err := NewDocumentWithConfiguration(digitalOcean, &config) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - // only errors will be thrown, so just capture them and print the number of errors. - _, errors := doc.BuildV3Model() - - // if anything went wrong when building the v3 model, a slice of errors will be returned - if len(errors) > 0 { - fmt.Println("Error building Digital Ocean spec errors reported") - } else { - fmt.Println("Digital Ocean spec built successfully") - } - // Output: Digital Ocean spec built successfully -} - -func ExampleNewDocument_fromSwaggerDocument() { - - // How to read in a Swagger / OpenAPI 2 Specification, into a Document. - - // load a Swagger specification from bytes - petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json") - - // create a new document from specification bytes - document, err := NewDocument(petstore) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - // because we know this is a v2 spec, we can build a ready to go model from it. - v2Model, errors := document.BuildV2Model() - - // if anything went wrong when building the v3 model, a slice of errors will be returned - if len(errors) > 0 { - for i := range errors { - fmt.Printf("error: %e\n", errors[i]) - } - panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) - } - - // get a count of the number of paths and schemas. - paths := len(v2Model.Model.Paths.PathItems) - schemas := len(v2Model.Model.Definitions.Definitions) - - // print the number of paths and schemas in the document - fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) - // Output: There are 14 paths and 6 schemas in the document -} - -func ExampleNewDocument_fromUnknownVersion() { - - // load an unknown version of an OpenAPI spec - petstore, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml") - - // create a new document from specification bytes - document, err := NewDocument(petstore) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - var paths, schemas int - var errors []error - - // We don't know which type of document this is, so we can use the spec info to inform us - if document.GetSpecInfo().SpecType == utils.OpenApi3 { - v3Model, errs := document.BuildV3Model() - if len(errs) > 0 { - errors = errs - } - if len(errors) <= 0 { - paths = len(v3Model.Model.Paths.PathItems) - schemas = len(v3Model.Model.Components.Schemas) - } - } - if document.GetSpecInfo().SpecType == utils.OpenApi2 { - v2Model, errs := document.BuildV2Model() - if len(errs) > 0 { - errors = errs - } - if len(errors) <= 0 { - paths = len(v2Model.Model.Paths.PathItems) - schemas = len(v2Model.Model.Definitions.Definitions) - } - } - - // if anything went wrong when building the model, report errors. - if len(errors) > 0 { - for i := range errors { - fmt.Printf("error: %e\n", errors[i]) - } - panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) - } - - // print the number of paths and schemas in the document - fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) - // Output: There are 5 paths and 6 schemas in the document -} - -func ExampleNewDocument_mutateValuesAndSerialize() { - - // How to mutate values in an OpenAPI Specification, without re-ordering original content. - - // create very small, and useless spec that does nothing useful, except showcase this feature. - spec := ` -openapi: 3.1.0 -info: - title: This is a title - contact: - name: Some Person - email: some@emailaddress.com - license: - url: http://some-place-on-the-internet.com/license -` - // create a new document from specification bytes - document, err := NewDocument([]byte(spec)) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - // because we know this is a v3 spec, we can build a ready to go model from it. - v3Model, errors := document.BuildV3Model() - - // if anything went wrong when building the v3 model, a slice of errors will be returned - if len(errors) > 0 { - for i := range errors { - fmt.Printf("error: %e\n", errors[i]) - } - panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) - } - - // mutate the title, to do this we currently need to drop down to the low-level API. - v3Model.Model.GoLow().Info.Value.Title.Mutate("A new title for a useless spec") - - // mutate the email address in the contact object. - v3Model.Model.GoLow().Info.Value.Contact.Value.Email.Mutate("buckaroo@pb33f.io") - - // mutate the name in the contact object. - v3Model.Model.GoLow().Info.Value.Contact.Value.Name.Mutate("Buckaroo") - - // mutate the URL for the license object. - v3Model.Model.GoLow().Info.Value.License.Value.URL.Mutate("https://pb33f.io/license") - - // serialize the document back into the original YAML or JSON - mutatedSpec, serialError := document.Serialize() - - // if something went wrong serializing - if serialError != nil { - panic(fmt.Sprintf("cannot serialize document: %e", serialError)) - } - - // print our modified spec! - fmt.Println(string(mutatedSpec)) - // Output: openapi: 3.1.0 - //info: - // title: A new title for a useless spec - // contact: - // name: Buckaroo - // email: buckaroo@pb33f.io - // license: - // url: https://pb33f.io/license -} - -func ExampleCompareDocuments_openAPI() { - - // How to compare two different OpenAPI specifications. - - // load an original OpenAPI 3 specification from bytes - burgerShopOriginal, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml") - - // load an **updated** OpenAPI 3 specification from bytes +func TestDocument_BuildModel_CompareDocsV3_LeftError(t *testing.T) { + burgerShopOriginal, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml") burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml") + originalDoc, _ := NewDocument(burgerShopOriginal) + updatedDoc, _ := NewDocument(burgerShopUpdated) + changes, errors := CompareDocuments(originalDoc, updatedDoc) + assert.Len(t, errors, 9) + assert.Nil(t, changes) +} - // create a new document from original specification bytes - originalDoc, err := NewDocument(burgerShopOriginal) +func TestDocument_BuildModel_CompareDocsV3_RightError(t *testing.T) { - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - // create a new document from updated specification bytes - updatedDoc, err := NewDocument(burgerShopUpdated) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - // Compare documents for all changes made - documentChanges, errs := CompareDocuments(originalDoc, updatedDoc) - - // If anything went wrong when building models for documents. - if len(errs) > 0 { - for i := range errs { - fmt.Printf("error: %e\n", errs[i]) - } - panic(fmt.Sprintf("cannot compare documents: %d errors reported", len(errs))) - } - - // Extract SchemaChanges from components changes. - schemaChanges := documentChanges.ComponentsChanges.SchemaChanges - - // Print out some interesting stats about the OpenAPI document changes. - fmt.Printf("There are %d changes, of which %d are breaking. %v schemas have changes.", - documentChanges.TotalChanges(), documentChanges.TotalBreakingChanges(), len(schemaChanges)) - //Output: There are 72 changes, of which 17 are breaking. 5 schemas have changes. + burgerShopOriginal, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml") + burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml") + originalDoc, _ := NewDocument(burgerShopOriginal) + updatedDoc, _ := NewDocument(burgerShopUpdated) + changes, errors := CompareDocuments(updatedDoc, originalDoc) + assert.Len(t, errors, 9) + assert.Nil(t, changes) } -func ExampleCompareDocuments_swagger() { +func TestDocument_BuildModel_CompareDocsV2_Error(t *testing.T) { - // How to compare two different Swagger specifications. - - // load an original OpenAPI 3 specification from bytes - petstoreOriginal, _ := ioutil.ReadFile("test_specs/petstorev2-complete.yaml") - - // load an **updated** OpenAPI 3 specification from bytes - petstoreUpdated, _ := ioutil.ReadFile("test_specs/petstorev2-complete-modified.yaml") - - // create a new document from original specification bytes - originalDoc, err := NewDocument(petstoreOriginal) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - // create a new document from updated specification bytes - updatedDoc, err := NewDocument(petstoreUpdated) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - // Compare documents for all changes made - documentChanges, errs := CompareDocuments(originalDoc, updatedDoc) - - // If anything went wrong when building models for documents. - if len(errs) > 0 { - for i := range errs { - fmt.Printf("error: %e\n", errs[i]) - } - panic(fmt.Sprintf("cannot compare documents: %d errors reported", len(errs))) - } - - // Extract SchemaChanges from components changes. - schemaChanges := documentChanges.ComponentsChanges.SchemaChanges - - // Print out some interesting stats about the Swagger document changes. - fmt.Printf("There are %d changes, of which %d are breaking. %v schemas have changes.", - documentChanges.TotalChanges(), documentChanges.TotalBreakingChanges(), len(schemaChanges)) - //Output: There are 52 changes, of which 27 are breaking. 5 schemas have changes. + burgerShopOriginal, _ := ioutil.ReadFile("test_specs/petstorev2-badref.json") + burgerShopUpdated, _ := ioutil.ReadFile("test_specs/petstorev2-badref.json") + originalDoc, _ := NewDocument(burgerShopOriginal) + updatedDoc, _ := NewDocument(burgerShopUpdated) + changes, errors := CompareDocuments(updatedDoc, originalDoc) + assert.Len(t, errors, 2) + assert.Nil(t, changes) } -func TestDocument_Paths_As_Array(t *testing.T) { +func TestDocument_BuildModel_CompareDocsV2V3Mix_Error(t *testing.T) { - // paths can now be wrapped in an array. - spec := `{ - "openapi": "3.1.0", - "paths": [ - "/": { - "get": {} - } - ] -} -` - // create a new document from specification bytes - doc, err := NewDocument([]byte(spec)) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - v3Model, _ := doc.BuildV3Model() - assert.NotNil(t, v3Model) -} - -// If you want to know more about circular references that have been found -// during the parsing/indexing/building of a document, you can capture the -// []errors thrown which are pointers to *resolver.ResolvingError -func ExampleNewDocument_infinite_circular_references() { - - // create a specification with an obvious and deliberate circular reference - spec := `openapi: "3.1" -components: - schemas: - One: - description: "test one" - properties: - things: - "$ref": "#/components/schemas/Two" - required: - - things - Two: - description: "test two" - properties: - testThing: - "$ref": "#/components/schemas/One" - required: - - testThing -` - // create a new document from specification bytes - doc, err := NewDocument([]byte(spec)) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - _, errs := doc.BuildV3Model() - - // extract resolving error - resolvingError := errs[0] - - // resolving error is a pointer to *resolver.ResolvingError - // which provides access to rich details about the error. - circularReference := resolvingError.(*resolver.ResolvingError).CircularReference - - // capture the journey with all details - var buf strings.Builder - for n := range circularReference.Journey { - - // add the full definition name to the journey. - buf.WriteString(circularReference.Journey[n].Definition) - if n < len(circularReference.Journey)-1 { - buf.WriteString(" -> ") - } - } - - // print out the journey and the loop point. - fmt.Printf("Journey: %s\n", buf.String()) - fmt.Printf("Loop Point: %s", circularReference.LoopPoint.Definition) - // Output: Journey: #/components/schemas/Two -> #/components/schemas/One -> #/components/schemas/Two - // Loop Point: #/components/schemas/Two -} - -// This tests checks that circular references which are _not_ marked as required pass correctly -func TestNewDocument_terminable_circular_references(t *testing.T) { - - // create a specification with an obvious and deliberate circular reference - spec := `openapi: "3.1" -components: - schemas: - One: - description: "test one" - properties: - things: - "$ref": "#/components/schemas/Two" - Two: - description: "test two" - properties: - testThing: - "$ref": "#/components/schemas/One" -` - // create a new document from specification bytes - doc, err := NewDocument([]byte(spec)) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - _, errs := doc.BuildV3Model() - - assert.Len(t, errs, 0) -} - -// If you're using complex types with OpenAPI Extensions, it's simple to unpack extensions into complex -// types using `high.UnpackExtensions()`. libopenapi retains the original raw data in the low model (not the high) -// which means unpacking them can be a little complex. -// -// This example demonstrates how to use the `UnpackExtensions` with custom OpenAPI extensions. -func ExampleNewDocument_unpacking_extensions() { - - // define an example struct representing a cake - type cake struct { - Candles int `yaml:"candles"` - Frosting string `yaml:"frosting"` - Some_Strange_Var_Name string `yaml:"someStrangeVarName"` - } - - // define a struct that holds a map of cake pointers. - type cakes struct { - Description string - Cakes map[string]*cake - } - - // define a struct representing a burger - type burger struct { - Sauce string - Patty string - } - - // define a struct that holds a map of cake pointers - type burgers struct { - Description string - Burgers map[string]*burger - } - - // create a specification with a schema and parameter that use complex custom cakes and burgers extensions. - spec := `openapi: "3.1" -components: - schemas: - SchemaOne: - description: "Some schema with custom complex extensions" - x-custom-cakes: - description: some cakes - cakes: - someCake: - candles: 10 - frosting: blue - someStrangeVarName: something - anotherCake: - candles: 1 - frosting: green - parameters: - ParameterOne: - description: "Some parameter also using complex extensions" - x-custom-burgers: - description: some burgers - burgers: - someBurger: - sauce: ketchup - patty: meat - anotherBurger: - sauce: mayo - patty: lamb` - // create a new document from specification bytes - doc, err := NewDocument([]byte(spec)) - - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - // build a v3 model. - docModel, errs := doc.BuildV3Model() - - // if anything went wrong building, indexing and resolving the model, an error is thrown - if errs != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } - - // get a reference to SchemaOne and ParameterOne - schemaOne := docModel.Model.Components.Schemas["SchemaOne"].Schema() - parameterOne := docModel.Model.Components.Parameters["ParameterOne"] - - // unpack schemaOne extensions into complex `cakes` type - schemaOneExtensions, schemaUnpackErrors := high.UnpackExtensions[cakes, *low.Schema](schemaOne) - if schemaUnpackErrors != nil { - panic(fmt.Sprintf("cannot unpack schema extensions: %e", err)) - } - - // unpack parameterOne into complex `burgers` type - parameterOneExtensions, paramUnpackErrors := high.UnpackExtensions[burgers, *v3.Parameter](parameterOne) - if paramUnpackErrors != nil { - panic(fmt.Sprintf("cannot unpack parameter extensions: %e", err)) - } - - // extract extension by name for schemaOne - customCakes := schemaOneExtensions["x-custom-cakes"] - - // extract extension by name for schemaOne - customBurgers := parameterOneExtensions["x-custom-burgers"] - - // print out schemaOne complex extension details. - fmt.Printf("schemaOne 'x-custom-cakes' (%s) has %d cakes, 'someCake' has %d candles and %s frosting\n", - customCakes.Description, - len(customCakes.Cakes), - customCakes.Cakes["someCake"].Candles, - customCakes.Cakes["someCake"].Frosting, - ) - - // print out parameterOne complex extension details. - fmt.Printf("parameterOne 'x-custom-burgers' (%s) has %d burgers, 'anotherBurger' has %s sauce and a %s patty\n", - customBurgers.Description, - len(customBurgers.Burgers), - customBurgers.Burgers["anotherBurger"].Sauce, - customBurgers.Burgers["anotherBurger"].Patty, - ) - - // Output: schemaOne 'x-custom-cakes' (some cakes) has 2 cakes, 'someCake' has 10 candles and blue frosting - //parameterOne 'x-custom-burgers' (some burgers) has 2 burgers, 'anotherBurger' has mayo sauce and a lamb patty + burgerShopOriginal, _ := ioutil.ReadFile("test_specs/petstorev2.json") + burgerShopUpdated, _ := ioutil.ReadFile("test_specs/petstorev3.json") + originalDoc, _ := NewDocument(burgerShopOriginal) + updatedDoc, _ := NewDocument(burgerShopUpdated) + changes, errors := CompareDocuments(updatedDoc, originalDoc) + assert.Len(t, errors, 1) + assert.Nil(t, changes) } diff --git a/resolver/resolver.go b/resolver/resolver.go index 01d63f4..8f06462 100644 --- a/resolver/resolver.go +++ b/resolver/resolver.go @@ -72,10 +72,10 @@ func (resolver *Resolver) GetPolymorphicCircularErrors() []*index.CircularRefere if !resolver.circularReferences[i].IsInfiniteLoop { continue } - - if resolver.circularReferences[i].IsPolymorphicResult { - res = append(res, resolver.circularReferences[i]) + if !resolver.circularReferences[i].IsPolymorphicResult { + continue } + res = append(res, resolver.circularReferences[i]) } return res } diff --git a/resolver/resolver_test.go b/resolver/resolver_test.go index 84b7b9f..6e03594 100644 --- a/resolver/resolver_test.go +++ b/resolver/resolver_test.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io/ioutil" + "net/url" "testing" "github.com/pb33f/libopenapi/index" @@ -62,6 +63,31 @@ func TestResolver_CheckForCircularReferences(t *testing.T) { assert.NoError(t, err) } +func TestResolver_CheckForCircularReferences_DigitalOcean(t *testing.T) { + circular, _ := ioutil.ReadFile("../test_specs/digitalocean.yaml") + var rootNode yaml.Node + yaml.Unmarshal(circular, &rootNode) + + baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification") + + index := index.NewSpecIndexWithConfig(&rootNode, &index.SpecIndexConfig{ + AllowRemoteLookup: true, + AllowFileLookup: true, + BaseURL: baseURL, + }) + + resolver := NewResolver(index) + assert.NotNil(t, resolver) + + circ := resolver.CheckForCircularReferences() + assert.Len(t, circ, 0) + assert.Len(t, resolver.GetResolvingErrors(), 0) + assert.Len(t, resolver.GetCircularErrors(), 0) + + _, err := yaml.Marshal(resolver.resolvedRoot) + assert.NoError(t, err) +} + func TestResolver_CircularReferencesRequiredValid(t *testing.T) { circular, _ := ioutil.ReadFile("../test_specs/swagger-valid-recursive-model.yaml") var rootNode yaml.Node @@ -170,6 +196,37 @@ components: assert.Len(t, circ, 0) } +func TestResolver_ResolveComponents_PolyCircRef(t *testing.T) { + yml := `openapi: 3.1.0 +components: + schemas: + cheese: + description: cheese + anyOf: + - $ref: '#/components/schemas/crackers' + crackers: + description: crackers + anyOf: + - $ref: '#/components/schemas/cheese' + tea: + description: tea` + + var rootNode yaml.Node + yaml.Unmarshal([]byte(yml), &rootNode) + + index := index.NewSpecIndex(&rootNode) + + resolver := NewResolver(index) + assert.NotNil(t, resolver) + + _ = resolver.CheckForCircularReferences() + resolver.circularReferences[0].IsInfiniteLoop = true // override + assert.Len(t, index.GetCircularReferences(), 1) + assert.Len(t, resolver.GetPolymorphicCircularErrors(), 1) + assert.Equal(t, 2, index.GetCircularReferences()[0].LoopIndex) + +} + func TestResolver_ResolveComponents_Missing(t *testing.T) { yml := `paths: /hey: diff --git a/test_specs/petstorev2-badref.json b/test_specs/petstorev2-badref.json new file mode 100644 index 0000000..fc314aa --- /dev/null +++ b/test_specs/petstorev2-badref.json @@ -0,0 +1,1061 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + "version": "1.0.6", + "title": "Swagger Petstore", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "parameters": { + "simpleParam": { + "in": "query", + "name": "simple", + "type": "string" + } + }, + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + ], + "schemes": [ + "https", + "http" + ], + "paths": { + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "additionalMetadata", + "in": "formData", + "description": "Additional data to pass to server", + "required": false, + "type": "string" + }, + { + "name": "file", + "in": "formData", + "description": "file to upload", + "required": false, + "type": "file" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/ApiResponseNotHere" + } + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet": { + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "", + "operationId": "addPet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/PetGoneHome" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "", + "operationId": "updatePet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "type": "array", + "items": { + "type": "string", + "enum": [ + "available", + "pending", + "sold" + ], + "default": "available" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/PettyChange" + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Petrofied" + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "deprecated": true + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Pet" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "consumes": [ + "application/x-www-form-urlencoded" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "name", + "in": "formData", + "description": "Updated name of the pet", + "required": false, + "type": "string" + }, + { + "name": "status", + "in": "formData", + "description": "Updated status of the pet", + "required": false, + "type": "string" + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "api_key", + "in": "header", + "required": false, + "type": "string" + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "", + "operationId": "placeOrder", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "order placed for purchasing the pet", + "required": true, + "schema": { + "$ref": "#/definitions/OrderMeABeer" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid Order" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + "operationId": "getOrderById", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "type": "integer", + "maximum": 10, + "minimum": 1, + "format": "int64" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Ordering" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + "operationId": "deleteOrder", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "type": "integer", + "minimum": 1, + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/user/createWithArray": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithArrayInput", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithListInput", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/User" + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Updated user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be updated", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "body", + "description": "Updated user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "400": { + "description": "Invalid user supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "type": "string" + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": true, + "type": "string" + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Expires-After": { + "type": "string", + "format": "date-time", + "description": "date in UTC when token expires" + }, + "X-Rate-Limit": { + "type": "integer", + "format": "int32", + "description": "calls per hour allowed by the user" + } + }, + "schema": { + "type": "string" + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Created user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + }, + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", + "flow": "implicit", + "scopes": { + "read:pets": "read your pets", + "write:pets": "modify pets in your account" + } + } + }, + "definitions": { + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "Pet": { + "type": "object", + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "$ref": "#/definitions/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "xml": { + "name": "tag" + }, + "$ref": "#/definitions/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "Order" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + } + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + } +} \ No newline at end of file diff --git a/test_specs/stripe.yaml b/test_specs/stripe.yaml index 0edc6ae..e118e75 100644 --- a/test_specs/stripe.yaml +++ b/test_specs/stripe.yaml @@ -20721,6 +20721,7 @@ components: type: object x-expandableFields: [] refund: + description: |- description: |- `Refund` objects allow you to refund a charge that has previously been created but not yet refunded. Funds will be refunded to the credit or debit card that diff --git a/utils/nodes.go b/utils/nodes.go index dbe4dc0..fd8d06c 100644 --- a/utils/nodes.go +++ b/utils/nodes.go @@ -5,6 +5,15 @@ package utils import "gopkg.in/yaml.v3" +func CreateRefNode(ref string) *yaml.Node { + m := CreateEmptyMapNode() + nodes := make([]*yaml.Node, 2) + nodes[0] = CreateStringNode("$ref") + nodes[1] = CreateStringNode(ref) + m.Content = nodes + return m +} + func CreateEmptyMapNode() *yaml.Node { n := &yaml.Node{ Kind: yaml.MappingNode, diff --git a/utils/nodes_test.go b/utils/nodes_test.go new file mode 100644 index 0000000..81d5ef9 --- /dev/null +++ b/utils/nodes_test.go @@ -0,0 +1,49 @@ +// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package utils + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestCreateBoolNode(t *testing.T) { + b := CreateBoolNode("true") + assert.Equal(t, "!!bool", b.Tag) + assert.Equal(t, "true", b.Value) +} + +func TestCreateEmptyMapNode(t *testing.T) { + m := CreateEmptyMapNode() + assert.Equal(t, "!!map", m.Tag) + assert.Len(t, m.Content, 0) +} + +func TestCreateEmptySequenceNode(t *testing.T) { + s := CreateEmptySequenceNode() + assert.Equal(t, "!!seq", s.Tag) + assert.Len(t, s.Content, 0) +} + +func TestCreateFloatNode(t *testing.T) { + f := CreateFloatNode("3.14") + assert.Equal(t, "!!float", f.Tag) + assert.Equal(t, "3.14", f.Value) +} + +func TestCreateIntNode(t *testing.T) { + i := CreateIntNode("42") + assert.Equal(t, "!!int", i.Tag) + assert.Equal(t, "42", i.Value) +} + +func TestCreateRefNode(t *testing.T) { + r := CreateRefNode("#/components/schemas/MySchema") + assert.Equal(t, "!!map", r.Tag) + assert.Len(t, r.Content, 2) + assert.Equal(t, "!!str", r.Content[0].Tag) + assert.Equal(t, "$ref", r.Content[0].Value) + assert.Equal(t, "!!str", r.Content[1].Tag) + assert.Equal(t, "#/components/schemas/MySchema", r.Content[1].Value) +} diff --git a/what-changed/model/callback.go b/what-changed/model/callback.go index fd92936..04dbea0 100644 --- a/what-changed/model/callback.go +++ b/what-changed/model/callback.go @@ -27,6 +27,19 @@ func (c *CallbackChanges) TotalChanges() int { return d } +// GetAllChanges returns a slice of all changes made between Callback objects +func (c *CallbackChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, c.Changes...) + for k := range c.ExpressionChanges { + changes = append(changes, c.ExpressionChanges[k].GetAllChanges()...) + } + if c.ExtensionChanges != nil { + changes = append(changes, c.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalBreakingChanges returns a total count of all changes made between Callback objects func (c *CallbackChanges) TotalBreakingChanges() int { d := c.PropertyChanges.TotalBreakingChanges() diff --git a/what-changed/model/callback_test.go b/what-changed/model/callback_test.go index 70a5ed4..acaf399 100644 --- a/what-changed/model/callback_test.go +++ b/what-changed/model/callback_test.go @@ -87,6 +87,7 @@ func TestCompareCallback_Add(t *testing.T) { // compare. extChanges := CompareCallback(&lDoc, &rDoc) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalChanges()) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) @@ -138,6 +139,7 @@ func TestCompareCallback_Modify(t *testing.T) { // compare. extChanges := CompareCallback(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.ExpressionChanges["{$request.query.queryUrl}"].Changes[0].ChangeType) assert.Equal(t, v3.GetLabel, extChanges.ExpressionChanges["{$request.query.queryUrl}"].Changes[0].Property) @@ -187,6 +189,7 @@ func TestCompareCallback_Remove(t *testing.T) { // compare. extChanges := CompareCallback(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, "slippers", extChanges.Changes[0].Property) diff --git a/what-changed/model/change_types.go b/what-changed/model/change_types.go index f0b8743..dceb9a2 100644 --- a/what-changed/model/change_types.go +++ b/what-changed/model/change_types.go @@ -50,15 +50,15 @@ func (c *ChangeContext) HasChanged() bool { if c.NewLine != nil && c.OriginalLine != nil && *c.NewLine != *c.OriginalLine { return true } - if c.NewColumn != nil && c.OriginalColumn != nil && *c.NewColumn != *c.OriginalColumn { - return true - } + //if c.NewColumn != nil && c.OriginalColumn != nil && *c.NewColumn != *c.OriginalColumn { + // return true + //} if (c.NewLine == nil && c.OriginalLine != nil) || (c.NewLine != nil && c.OriginalLine == nil) { return true } - if (c.NewColumn == nil && c.OriginalColumn != nil) || (c.NewColumn != nil && c.OriginalColumn == nil) { - return true - } + //if (c.NewColumn == nil && c.OriginalColumn != nil) || (c.NewColumn != nil && c.OriginalColumn == nil) { + // return true + //} return false } diff --git a/what-changed/model/components.go b/what-changed/model/components.go index b240219..220e4e5 100644 --- a/what-changed/model/components.go +++ b/what-changed/model/components.go @@ -235,6 +235,22 @@ func runComparison[T any, R any](l, r map[low.KeyReference[string]]low.ValueRefe } } +// GetAllChanges returns a slice of all changes made between Callback objects +func (c *ComponentsChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, c.Changes...) + for k := range c.SchemaChanges { + changes = append(changes, c.SchemaChanges[k].GetAllChanges()...) + } + for k := range c.SecuritySchemeChanges { + changes = append(changes, c.SecuritySchemeChanges[k].GetAllChanges()...) + } + if c.ExtensionChanges != nil { + changes = append(changes, c.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns total changes for all Components and Definitions func (c *ComponentsChanges) TotalChanges() int { v := c.PropertyChanges.TotalChanges() diff --git a/what-changed/model/components_test.go b/what-changed/model/components_test.go index 27bdaa4..a94b3d8 100644 --- a/what-changed/model/components_test.go +++ b/what-changed/model/components_test.go @@ -75,6 +75,7 @@ thing2: // compare. extChanges := CompareComponents(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -113,6 +114,7 @@ thing3: // compare. extChanges := CompareComponents(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) @@ -152,6 +154,7 @@ thing3: // compare. extChanges := CompareComponents(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, "thing3", extChanges.Changes[0].Original) @@ -190,6 +193,7 @@ param4: // compare. extChanges := CompareComponents(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, "param4", extChanges.Changes[0].New) @@ -228,6 +232,7 @@ param4: // compare. extChanges := CompareComponents(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, "param4", extChanges.Changes[0].Original) @@ -263,6 +268,7 @@ resp3: extChanges := CompareComponents(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, "resp3", extChanges.Changes[0].New) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) @@ -299,6 +305,7 @@ resp3: extChanges := CompareComponents(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, "resp3", extChanges.Changes[0].Original) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) @@ -330,6 +337,7 @@ scheme2: // compare. extChanges := CompareComponents(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, 1, extChanges.SecuritySchemeChanges["scheme1"].TotalChanges()) assert.Equal(t, v3.DescriptionLabel, extChanges.SecuritySchemeChanges["scheme1"].Changes[0].Property) @@ -1088,4 +1096,5 @@ func TestCompareComponents_OpenAPI_Extensions_Modified(t *testing.T) { // compare. extChanges := CompareComponents(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) } diff --git a/what-changed/model/contact.go b/what-changed/model/contact.go index 3bcb99b..317b83d 100644 --- a/what-changed/model/contact.go +++ b/what-changed/model/contact.go @@ -13,6 +13,11 @@ type ContactChanges struct { *PropertyChanges } +// GetAllChanges returns a slice of all changes made between Callback objects +func (c *ContactChanges) GetAllChanges() []*Change { + return c.Changes +} + // TotalChanges represents the total number of changes that have occurred to a Contact object func (c *ContactChanges) TotalChanges() int { return c.PropertyChanges.TotalChanges() diff --git a/what-changed/model/contact_test.go b/what-changed/model/contact_test.go index 9ef751d..c8521eb 100644 --- a/what-changed/model/contact_test.go +++ b/what-changed/model/contact_test.go @@ -33,6 +33,7 @@ url: https://pb33f.io` // compare. extChanges := CompareContact(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } diff --git a/what-changed/model/discriminator.go b/what-changed/model/discriminator.go index a522ba7..3351490 100644 --- a/what-changed/model/discriminator.go +++ b/what-changed/model/discriminator.go @@ -26,6 +26,16 @@ func (d *DiscriminatorChanges) TotalChanges() int { return l } +// GetAllChanges returns a slice of all changes made between Callback objects +func (c *DiscriminatorChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, c.Changes...) + if c.MappingChanges != nil { + changes = append(changes, c.MappingChanges...) + } + return changes +} + // TotalBreakingChanges returns the number of breaking changes made by the Discriminator func (d *DiscriminatorChanges) TotalBreakingChanges() int { return d.PropertyChanges.TotalBreakingChanges() + CountBreakingChanges(d.MappingChanges) diff --git a/what-changed/model/discriminator_test.go b/what-changed/model/discriminator_test.go index 41ff96b..085007d 100644 --- a/what-changed/model/discriminator_test.go +++ b/what-changed/model/discriminator_test.go @@ -30,6 +30,7 @@ func TestCompareDiscriminator_PropertyNameChanged(t *testing.T) { // compare. extChanges := CompareDiscriminator(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) } @@ -56,6 +57,7 @@ propertyName: chicken` // compare. extChanges := CompareDiscriminator(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) } @@ -81,6 +83,7 @@ propertyName: chicken` // compare. extChanges := CompareDiscriminator(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -106,6 +109,7 @@ mapping: // compare. extChanges := CompareDiscriminator(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) for _, k := range extChanges.MappingChanges { assert.Equal(t, ObjectAdded, k.ChangeType) diff --git a/what-changed/model/document.go b/what-changed/model/document.go index e72aba9..3135b42 100644 --- a/what-changed/model/document.go +++ b/what-changed/model/document.go @@ -66,6 +66,40 @@ func (d *DocumentChanges) TotalChanges() int { return c } +// GetAllChanges returns a slice of all changes made between Document objects +func (d *DocumentChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, d.Changes...) + if d.InfoChanges != nil { + changes = append(changes, d.InfoChanges.GetAllChanges()...) + } + if d.PathsChanges != nil { + changes = append(changes, d.PathsChanges.GetAllChanges()...) + } + for k := range d.TagChanges { + changes = append(changes, d.TagChanges[k].GetAllChanges()...) + } + if d.ExternalDocChanges != nil { + changes = append(changes, d.ExternalDocChanges.GetAllChanges()...) + } + for k := range d.WebhookChanges { + changes = append(changes, d.WebhookChanges[k].GetAllChanges()...) + } + for k := range d.ServerChanges { + changes = append(changes, d.ServerChanges[k].GetAllChanges()...) + } + for k := range d.SecurityRequirementChanges { + changes = append(changes, d.SecurityRequirementChanges[k].GetAllChanges()...) + } + if d.ComponentsChanges != nil { + changes = append(changes, d.ComponentsChanges.GetAllChanges()...) + } + if d.ExtensionChanges != nil { + changes = append(changes, d.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalBreakingChanges returns a total count of all breaking changes made in the Document func (d *DocumentChanges) TotalBreakingChanges() int { c := d.PropertyChanges.TotalBreakingChanges() diff --git a/what-changed/model/document_test.go b/what-changed/model/document_test.go index d0a7da2..960a231 100644 --- a/what-changed/model/document_test.go +++ b/what-changed/model/document_test.go @@ -85,6 +85,7 @@ produces: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 10, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 10) assert.Equal(t, 6, extChanges.TotalBreakingChanges()) } @@ -113,6 +114,7 @@ produces: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 3) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -141,6 +143,7 @@ basePath: /api` // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 3) assert.Equal(t, 3, extChanges.TotalBreakingChanges()) } @@ -171,6 +174,7 @@ info: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 3) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, 3, extChanges.InfoChanges.TotalChanges()) } @@ -196,6 +200,7 @@ info: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, v3.InfoLabel, extChanges.Changes[0].Property) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) @@ -222,6 +227,7 @@ info: // compare. extChanges := CompareDocuments(rDoc, lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, v3.InfoLabel, extChanges.Changes[0].Property) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) @@ -248,6 +254,7 @@ externalDocs: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, 2, extChanges.ExternalDocChanges.TotalChanges()) } @@ -270,6 +277,7 @@ externalDocs: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, v3.ExternalDocsLabel, extChanges.Changes[0].Property) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) @@ -294,6 +302,7 @@ externalDocs: // compare. extChanges := CompareDocuments(rDoc, lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, v3.ExternalDocsLabel, extChanges.Changes[0].Property) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) @@ -367,6 +376,7 @@ security: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) } @@ -399,6 +409,7 @@ definitions: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) } @@ -459,6 +470,7 @@ securityDefinitions: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -527,6 +539,7 @@ parameters: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -594,6 +607,7 @@ responses: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -657,6 +671,7 @@ paths: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 3) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -689,6 +704,7 @@ paths: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) } @@ -748,6 +764,7 @@ tags: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 3) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -801,6 +818,7 @@ jsonSchemaDialect: https://pb33f.io/schema/changed` extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 3) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) } @@ -829,6 +847,7 @@ components: extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -858,6 +877,7 @@ components: extChanges := CompareDocuments(rDoc, lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) } @@ -897,6 +917,7 @@ paths: extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -969,6 +990,7 @@ components: extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -1000,6 +1022,7 @@ servers: extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 3) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -1034,6 +1057,7 @@ components: extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -1072,6 +1096,7 @@ webhooks: extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -1114,6 +1139,7 @@ paths: // compare. extChanges := CompareDocuments(lDoc, rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } diff --git a/what-changed/model/encoding.go b/what-changed/model/encoding.go index a6f3417..f467305 100644 --- a/what-changed/model/encoding.go +++ b/what-changed/model/encoding.go @@ -13,6 +13,16 @@ type EncodingChanges struct { HeaderChanges map[string]*HeaderChanges `json:"headers,omitempty" yaml:"headers,omitempty"` } +// GetAllChanges returns a slice of all changes made between Encoding objects +func (e *EncodingChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, e.Changes...) + for k := range e.HeaderChanges { + changes = append(changes, e.HeaderChanges[k].GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes made between two Encoding objects func (e *EncodingChanges) TotalChanges() int { c := e.PropertyChanges.TotalChanges() diff --git a/what-changed/model/encoding_test.go b/what-changed/model/encoding_test.go index 919bbcd..6a1b036 100644 --- a/what-changed/model/encoding_test.go +++ b/what-changed/model/encoding_test.go @@ -80,6 +80,7 @@ allowReserved: true` extChanges := CompareEncoding(&lDoc, &rDoc) assert.NotNil(t, extChanges) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) } @@ -114,6 +115,7 @@ allowReserved: true` extChanges := CompareEncoding(&lDoc, &rDoc) assert.NotNil(t, extChanges) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.HeadersLabel, extChanges.Changes[0].Property) @@ -149,6 +151,7 @@ allowReserved: true` extChanges := CompareEncoding(&rDoc, &lDoc) assert.NotNil(t, extChanges) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } diff --git a/what-changed/model/example.go b/what-changed/model/example.go index de4e20d..cf0839c 100644 --- a/what-changed/model/example.go +++ b/what-changed/model/example.go @@ -21,6 +21,16 @@ type ExampleChanges struct { ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` } +// GetAllChanges returns a slice of all changes made between Example objects +func (e *ExampleChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, e.Changes...) + if e.ExtensionChanges != nil { + changes = append(changes, e.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes made to Example func (e *ExampleChanges) TotalChanges() int { l := e.PropertyChanges.TotalChanges() diff --git a/what-changed/model/example_test.go b/what-changed/model/example_test.go index c2889fa..a07b695 100644 --- a/what-changed/model/example_test.go +++ b/what-changed/model/example_test.go @@ -34,6 +34,7 @@ func TestCompareExamples_SummaryModified(t *testing.T) { extChanges := CompareExamples(&lDoc, &rDoc) assert.Equal(t, extChanges.TotalChanges(), 1) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.SummaryLabel, extChanges.Changes[0].Property) @@ -66,6 +67,7 @@ func TestCompareExamples_Map(t *testing.T) { extChanges := CompareExamples(&lDoc, &rDoc) assert.Equal(t, extChanges.TotalChanges(), 2) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) } @@ -94,6 +96,7 @@ func TestCompareExamples_MapAdded(t *testing.T) { extChanges := CompareExamples(&lDoc, &rDoc) assert.Equal(t, extChanges.TotalChanges(), 1) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -147,6 +150,7 @@ description: cure all` extChanges := CompareExamples(&lDoc, &rDoc) assert.Equal(t, extChanges.TotalChanges(), 1) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.DescriptionLabel, extChanges.Changes[0].Property) assert.Equal(t, "cure all", extChanges.Changes[0].New) @@ -173,6 +177,7 @@ x-herbs: cure all` extChanges := CompareExamples(&lDoc, &rDoc) assert.Equal(t, extChanges.TotalChanges(), 1) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, ObjectAdded, extChanges.ExtensionChanges.Changes[0].ChangeType) assert.Equal(t, "x-herbs", extChanges.ExtensionChanges.Changes[0].Property) assert.Equal(t, "cure all", extChanges.ExtensionChanges.Changes[0].New) diff --git a/what-changed/model/examples.go b/what-changed/model/examples.go index 1b08d8d..ecd6f94 100644 --- a/what-changed/model/examples.go +++ b/what-changed/model/examples.go @@ -13,6 +13,11 @@ type ExamplesChanges struct { *PropertyChanges } +// GetAllChanges returns a slice of all changes made between Examples objects +func (a *ExamplesChanges) GetAllChanges() []*Change { + return a.Changes +} + // TotalChanges represents the total number of changes made between Example instances. func (a *ExamplesChanges) TotalChanges() int { return a.PropertyChanges.TotalChanges() diff --git a/what-changed/model/examples_test.go b/what-changed/model/examples_test.go index 03b3bee..ba81643 100644 --- a/what-changed/model/examples_test.go +++ b/what-changed/model/examples_test.go @@ -31,6 +31,7 @@ func TestCompareExamplesV2(t *testing.T) { extChanges := CompareExamplesV2(&lDoc, &rDoc) assert.Equal(t, extChanges.TotalChanges(), 1) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.SummaryLabel, extChanges.Changes[0].Property) @@ -58,6 +59,7 @@ yummy: coffee` extChanges := CompareExamplesV2(&lDoc, &rDoc) assert.Equal(t, extChanges.TotalChanges(), 1) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) } @@ -82,6 +84,7 @@ yummy: coffee` extChanges := CompareExamplesV2(&rDoc, &lDoc) assert.Equal(t, extChanges.TotalChanges(), 1) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) } diff --git a/what-changed/model/extensions.go b/what-changed/model/extensions.go index fdabc86..a01d878 100644 --- a/what-changed/model/extensions.go +++ b/what-changed/model/extensions.go @@ -13,6 +13,11 @@ type ExtensionChanges struct { *PropertyChanges } +// GetAllChanges returns a slice of all changes made between Extension objects +func (e *ExtensionChanges) GetAllChanges() []*Change { + return e.Changes +} + // TotalChanges returns the total number of object extensions that were made. func (e *ExtensionChanges) TotalChanges() int { return e.PropertyChanges.TotalChanges() diff --git a/what-changed/model/extensions_test.go b/what-changed/model/extensions_test.go index ac09322..601f735 100644 --- a/what-changed/model/extensions_test.go +++ b/what-changed/model/extensions_test.go @@ -25,6 +25,7 @@ func TestCompareExtensions(t *testing.T) { extChanges := CompareExtensions(lExt, rExt) assert.Equal(t, extChanges.TotalChanges(), 1) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) assert.Equal(t, "1", extChanges.Changes[0].Original) assert.Equal(t, "2", extChanges.Changes[0].New) @@ -49,6 +50,7 @@ x-test: 1` extChanges := CompareExtensions(lExt, rExt) assert.Len(t, extChanges.Changes, 1) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, 2, *extChanges.Changes[0].Context.OriginalLine) assert.Nil(t, extChanges.Changes[0].Context.NewLine) @@ -73,6 +75,7 @@ x-test: 1` extChanges := CompareExtensions(lExt, rExt) assert.Len(t, extChanges.Changes, 1) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Nil(t, extChanges.Changes[0].Context.OriginalLine) assert.Equal(t, 2, *extChanges.Changes[0].Context.NewLine) diff --git a/what-changed/model/external_docs.go b/what-changed/model/external_docs.go index b999ac6..47b1bd8 100644 --- a/what-changed/model/external_docs.go +++ b/what-changed/model/external_docs.go @@ -14,6 +14,16 @@ type ExternalDocChanges struct { ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` } +// GetAllChanges returns a slice of all changes made between Example objects +func (e *ExternalDocChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, e.Changes...) + if e.ExtensionChanges != nil { + changes = append(changes, e.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns a count of everything that changed func (e *ExternalDocChanges) TotalChanges() int { c := e.PropertyChanges.TotalChanges() diff --git a/what-changed/model/external_docs_test.go b/what-changed/model/external_docs_test.go index 1b3eaf1..00abbee 100644 --- a/what-changed/model/external_docs_test.go +++ b/what-changed/model/external_docs_test.go @@ -38,6 +38,7 @@ x-testing: hiya!` extChanges := CompareExternalDocs(&lDoc, &rDoc) assert.Len(t, extChanges.ExtensionChanges.Changes, 1) assert.Len(t, extChanges.Changes, 2) + assert.Len(t, extChanges.GetAllChanges(), 3) assert.Equal(t, 3, extChanges.TotalChanges()) // validate property changes @@ -94,6 +95,7 @@ url: https://quobix.com` extChanges := CompareExternalDocs(&lDoc, &rDoc) assert.Len(t, extChanges.ExtensionChanges.Changes, 1) assert.Len(t, extChanges.Changes, 2) + assert.Len(t, extChanges.GetAllChanges(), 3) // validate property changes urlChange := extChanges.Changes[0] @@ -169,6 +171,7 @@ x-testing: hello` // compare. extChanges := CompareExternalDocs(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -194,6 +197,7 @@ url: https://pb33f.io` // compare. extChanges := CompareExternalDocs(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -219,6 +223,7 @@ description: something` // compare. extChanges := CompareExternalDocs(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) } @@ -244,5 +249,6 @@ url: https://pb33f.io` // compare extChanges := CompareExternalDocs(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) } diff --git a/what-changed/model/header.go b/what-changed/model/header.go index 34943e3..10a9592 100644 --- a/what-changed/model/header.go +++ b/what-changed/model/header.go @@ -23,6 +23,28 @@ type HeaderChanges struct { ItemsChanges *ItemsChanges `json:"items,omitempty" yaml:"items,omitempty"` } +// GetAllChanges returns a slice of all changes made between Header objects +func (h *HeaderChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, h.Changes...) + for k := range h.ExamplesChanges { + changes = append(changes, h.ExamplesChanges[k].GetAllChanges()...) + } + for k := range h.ContentChanges { + changes = append(changes, h.ContentChanges[k].GetAllChanges()...) + } + if h.ExtensionChanges != nil { + changes = append(changes, h.ExtensionChanges.GetAllChanges()...) + } + if h.SchemaChanges != nil { + changes = append(changes, h.SchemaChanges.GetAllChanges()...) + } + if h.ItemsChanges != nil { + changes = append(changes, h.ItemsChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes made between two Header objects. func (h *HeaderChanges) TotalChanges() int { c := h.PropertyChanges.TotalChanges() diff --git a/what-changed/model/header_test.go b/what-changed/model/header_test.go index 37afe3c..4d071b8 100644 --- a/what-changed/model/header_test.go +++ b/what-changed/model/header_test.go @@ -123,6 +123,7 @@ x-beer: really yummy` extChanges := CompareHeadersV2(&lDoc, &rDoc) assert.NotNil(t, extChanges) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -167,6 +168,7 @@ x-beer: yummy` assert.NotNil(t, extChanges) assert.Equal(t, 1, extChanges.TotalChanges()) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) } @@ -211,6 +213,7 @@ x-beer: yummy` assert.NotNil(t, extChanges) assert.Equal(t, 1, extChanges.TotalChanges()) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) } @@ -301,6 +304,7 @@ x-beer: yummy` extChanges := CompareHeadersV3(&lDoc, &rDoc) assert.NotNil(t, extChanges) assert.Equal(t, 5, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 5) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } diff --git a/what-changed/model/info.go b/what-changed/model/info.go index 8f0314e..98021f6 100644 --- a/what-changed/model/info.go +++ b/what-changed/model/info.go @@ -15,6 +15,19 @@ type InfoChanges struct { LicenseChanges *LicenseChanges `json:"license,omitempty" yaml:"license,omitempty"` } +// GetAllChanges returns a slice of all changes made between Info objects +func (i *InfoChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, i.Changes...) + if i.ContactChanges != nil { + changes = append(changes, i.ContactChanges.GetAllChanges()...) + } + if i.LicenseChanges != nil { + changes = append(changes, i.LicenseChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges represents the total number of changes made to an Info object. func (i *InfoChanges) TotalChanges() int { t := i.PropertyChanges.TotalChanges() diff --git a/what-changed/model/info_test.go b/what-changed/model/info_test.go index 702476e..9d42892 100644 --- a/what-changed/model/info_test.go +++ b/what-changed/model/info_test.go @@ -48,6 +48,7 @@ license: // compare. extChanges := CompareInfo(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.DescriptionLabel, extChanges.Changes[0].Property) } @@ -88,6 +89,7 @@ license: // compare. extChanges := CompareInfo(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.TitleLabel, extChanges.Changes[0].Property) } @@ -127,6 +129,7 @@ license: // compare. extChanges := CompareInfo(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.VersionLabel, extChanges.Changes[0].Property) } @@ -164,6 +167,7 @@ contact: // compare. extChanges := CompareInfo(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.LicenseLabel, extChanges.Changes[0].Property) } @@ -201,6 +205,7 @@ license: // compare. extChanges := CompareInfo(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.LicenseLabel, extChanges.Changes[0].Property) } @@ -240,6 +245,7 @@ license: // compare. extChanges := CompareInfo(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, Modified, extChanges.LicenseChanges.Changes[0].ChangeType) assert.Equal(t, v3.NameLabel, extChanges.LicenseChanges.Changes[0].Property) } @@ -276,6 +282,7 @@ license: // compare. extChanges := CompareInfo(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.ContactLabel, extChanges.Changes[0].Property) } @@ -312,6 +319,7 @@ license: // compare. extChanges := CompareInfo(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.ContactLabel, extChanges.Changes[0].Property) } @@ -351,6 +359,7 @@ license: // compare. extChanges := CompareInfo(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, Modified, extChanges.ContactChanges.Changes[0].ChangeType) assert.Equal(t, v3.NameLabel, extChanges.ContactChanges.Changes[0].Property) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) diff --git a/what-changed/model/items.go b/what-changed/model/items.go index 5cc158e..e0da83a 100644 --- a/what-changed/model/items.go +++ b/what-changed/model/items.go @@ -15,6 +15,16 @@ type ItemsChanges struct { ItemsChanges *ItemsChanges `json:"items,omitempty" yaml:"items,omitempty"` } +// GetAllChanges returns a slice of all changes made between Items objects +func (i *ItemsChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, i.Changes...) + if i.ItemsChanges != nil { + changes = append(changes, i.ItemsChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes found between two Items objects // This is a recursive function because Items can contain Items. Be careful! func (i *ItemsChanges) TotalChanges() int { diff --git a/what-changed/model/items_test.go b/what-changed/model/items_test.go index 3a478b0..0ea8d1a 100644 --- a/what-changed/model/items_test.go +++ b/what-changed/model/items_test.go @@ -34,6 +34,7 @@ func TestCompareItems(t *testing.T) { changes := CompareItems(&lDoc, &rDoc) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.TypeLabel, changes.Changes[0].Property) } @@ -64,6 +65,7 @@ items: changes := CompareItems(&lDoc, &rDoc) assert.NotNil(t, changes) assert.Equal(t, 2, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 2) assert.Equal(t, 2, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.ItemsChanges.TotalChanges()) assert.Equal(t, v3.TypeLabel, changes.Changes[0].Property) @@ -93,6 +95,7 @@ items: changes := CompareItems(&lDoc, &rDoc) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.ItemsLabel, changes.Changes[0].Property) assert.Equal(t, PropertyAdded, changes.Changes[0].ChangeType) @@ -122,6 +125,7 @@ items: changes := CompareItems(&rDoc, &lDoc) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.ItemsLabel, changes.Changes[0].Property) assert.Equal(t, PropertyRemoved, changes.Changes[0].ChangeType) diff --git a/what-changed/model/license.go b/what-changed/model/license.go index c33dc54..6ffbc87 100644 --- a/what-changed/model/license.go +++ b/what-changed/model/license.go @@ -13,6 +13,11 @@ type LicenseChanges struct { *PropertyChanges } +// GetAllChanges returns a slice of all changes made between License objects +func (l *LicenseChanges) GetAllChanges() []*Change { + return l.Changes +} + // TotalChanges represents the total number of changes made to a License instance. func (l *LicenseChanges) TotalChanges() int { return l.PropertyChanges.TotalChanges() diff --git a/what-changed/model/license_test.go b/what-changed/model/license_test.go index 4145491..bf2abe0 100644 --- a/what-changed/model/license_test.go +++ b/what-changed/model/license_test.go @@ -33,6 +33,7 @@ url: https://pb33f.io` // compare. extChanges := CompareLicense(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) @@ -60,6 +61,7 @@ url: https://pb33f.io` // compare. extChanges := CompareLicense(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) } @@ -86,6 +88,7 @@ name: buckaroo` // compare. extChanges := CompareLicense(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -112,6 +115,7 @@ name: buckaroo` // compare. extChanges := CompareLicense(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -137,6 +141,7 @@ func TestCompareLicense_URLModified(t *testing.T) { // compare. extChanges := CompareLicense(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) } @@ -163,6 +168,7 @@ url: https://pb33f.io` // compare. extChanges := CompareLicense(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) assert.Equal(t, PropertyRemoved, extChanges.Changes[1].ChangeType) } diff --git a/what-changed/model/link.go b/what-changed/model/link.go index 68aeb0c..478a70e 100644 --- a/what-changed/model/link.go +++ b/what-changed/model/link.go @@ -15,6 +15,19 @@ type LinkChanges struct { ServerChanges *ServerChanges `json:"server,omitempty" yaml:"server,omitempty"` } +// GetAllChanges returns a slice of all changes made between Link objects +func (l *LinkChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, l.Changes...) + if l.ServerChanges != nil { + changes = append(changes, l.ServerChanges.GetAllChanges()...) + } + if l.ExtensionChanges != nil { + changes = append(changes, l.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total changes made between OpenAPI Link objects func (l *LinkChanges) TotalChanges() int { c := l.PropertyChanges.TotalChanges() diff --git a/what-changed/model/link_test.go b/what-changed/model/link_test.go index 5d15b27..ea83354 100644 --- a/what-changed/model/link_test.go +++ b/what-changed/model/link_test.go @@ -76,6 +76,7 @@ x-cake: very tasty` // compare. extChanges := CompareLinks(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.ExtensionChanges.Changes[0].ChangeType) @@ -114,6 +115,7 @@ parameters: // compare. extChanges := CompareLinks(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.ServerChanges.Changes[0].ChangeType) } @@ -149,6 +151,7 @@ parameters: // compare. extChanges := CompareLinks(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -184,6 +187,7 @@ parameters: // compare. extChanges := CompareLinks(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) } @@ -221,6 +225,7 @@ parameters: // compare. extChanges := CompareLinks(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) assert.Equal(t, "nice", extChanges.Changes[0].NewObject) @@ -262,6 +267,7 @@ parameters: // compare. extChanges := CompareLinks(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, "hot", extChanges.Changes[0].NewObject) @@ -302,6 +308,7 @@ parameters: // compare. extChanges := CompareLinks(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, "hot", extChanges.Changes[0].OriginalObject) diff --git a/what-changed/model/media_type.go b/what-changed/model/media_type.go index 328874b..796d1a7 100644 --- a/what-changed/model/media_type.go +++ b/what-changed/model/media_type.go @@ -19,6 +19,25 @@ type MediaTypeChanges struct { EncodingChanges map[string]*EncodingChanges `json:"encoding,omitempty" yaml:"encoding,omitempty"` } +// GetAllChanges returns a slice of all changes made between MediaType objects +func (m *MediaTypeChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, m.Changes...) + if m.SchemaChanges != nil { + changes = append(changes, m.SchemaChanges.GetAllChanges()...) + } + for k := range m.ExampleChanges { + changes = append(changes, m.ExampleChanges[k].GetAllChanges()...) + } + for k := range m.EncodingChanges { + changes = append(changes, m.EncodingChanges[k].GetAllChanges()...) + } + if m.ExtensionChanges != nil { + changes = append(changes, m.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes between two MediaType instances. func (m *MediaTypeChanges) TotalChanges() int { c := m.PropertyChanges.TotalChanges() diff --git a/what-changed/model/media_type_test.go b/what-changed/model/media_type_test.go index d4a8402..e92effe 100644 --- a/what-changed/model/media_type_test.go +++ b/what-changed/model/media_type_test.go @@ -84,6 +84,7 @@ encoding: extChanges := CompareMediaTypes(&lDoc, &rDoc) assert.NotNil(t, extChanges) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.ExampleLabel, extChanges.Changes[0].Property) @@ -118,6 +119,7 @@ example: extChanges := CompareMediaTypes(&lDoc, &rDoc) assert.NotNil(t, extChanges) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.ExampleLabel, extChanges.Changes[0].Property) @@ -150,6 +152,7 @@ example: extChanges := CompareMediaTypes(&lDoc, &rDoc) assert.NotNil(t, extChanges) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.ExampleLabel, extChanges.Changes[0].Property) @@ -182,6 +185,7 @@ example: extChanges := CompareMediaTypes(&rDoc, &lDoc) assert.NotNil(t, extChanges) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.ExampleLabel, extChanges.Changes[0].Property) @@ -221,6 +225,7 @@ encoding: extChanges := CompareMediaTypes(&lDoc, &rDoc) assert.NotNil(t, extChanges) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.SchemaLabel, extChanges.Changes[0].Property) @@ -260,6 +265,7 @@ encoding: extChanges := CompareMediaTypes(&rDoc, &lDoc) assert.NotNil(t, extChanges) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.SchemaLabel, extChanges.Changes[0].Property) @@ -305,5 +311,6 @@ x-tea: cup` extChanges := CompareMediaTypes(&lDoc, &rDoc) assert.NotNil(t, extChanges) assert.Equal(t, 5, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 5) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) } diff --git a/what-changed/model/oauth_flows.go b/what-changed/model/oauth_flows.go index c4196f9..3045a3b 100644 --- a/what-changed/model/oauth_flows.go +++ b/what-changed/model/oauth_flows.go @@ -18,6 +18,28 @@ type OAuthFlowsChanges struct { ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` } +// GetAllChanges returns a slice of all changes made between OAuthFlows objects +func (o *OAuthFlowsChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, o.Changes...) + if o.ImplicitChanges != nil { + changes = append(changes, o.ImplicitChanges.GetAllChanges()...) + } + if o.PasswordChanges != nil { + changes = append(changes, o.PasswordChanges.GetAllChanges()...) + } + if o.ClientCredentialsChanges != nil { + changes = append(changes, o.ClientCredentialsChanges.GetAllChanges()...) + } + if o.AuthorizationCodeChanges != nil { + changes = append(changes, o.AuthorizationCodeChanges.GetAllChanges()...) + } + if o.ExtensionChanges != nil { + changes = append(changes, o.ImplicitChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the number of changes made between two OAuthFlows instances. func (o *OAuthFlowsChanges) TotalChanges() int { c := o.PropertyChanges.TotalChanges() @@ -137,6 +159,16 @@ type OAuthFlowChanges struct { ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` } +// GetAllChanges returns a slice of all changes made between OAuthFlow objects +func (o *OAuthFlowChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, o.Changes...) + if o.ExtensionChanges != nil { + changes = append(changes, o.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes made between two OAuthFlow objects func (o *OAuthFlowChanges) TotalChanges() int { c := o.PropertyChanges.TotalChanges() diff --git a/what-changed/model/oauth_flows_test.go b/what-changed/model/oauth_flows_test.go index 637de71..e25b898 100644 --- a/what-changed/model/oauth_flows_test.go +++ b/what-changed/model/oauth_flows_test.go @@ -74,6 +74,7 @@ x-burgers: crispy` // compare extChanges := CompareOAuthFlow(&lDoc, &rDoc) assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 3) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) } @@ -109,6 +110,7 @@ x-burgers: nice` // compare extChanges := CompareOAuthFlow(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, "taff", extChanges.Changes[0].New) assert.Equal(t, "tiff", extChanges.Changes[0].NewObject) @@ -146,6 +148,7 @@ x-burgers: nice` // compare extChanges := CompareOAuthFlow(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, "taff", extChanges.Changes[0].Original) assert.Equal(t, "tiff", extChanges.Changes[0].OriginalObject) @@ -182,6 +185,7 @@ x-burgers: nice` // compare extChanges := CompareOAuthFlow(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, "raff", extChanges.Changes[0].New) assert.Equal(t, "raff", extChanges.Changes[0].NewObject) @@ -255,6 +259,7 @@ x-coke: cola` // compare extChanges := CompareOAuthFlows(&lDoc, &rDoc) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -286,6 +291,7 @@ x-coke: cola` // compare extChanges := CompareOAuthFlows(&rDoc, &lDoc) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 4, extChanges.TotalBreakingChanges()) } @@ -325,5 +331,6 @@ x-coke: cherry` // compare extChanges := CompareOAuthFlows(&lDoc, &rDoc) assert.Equal(t, 5, extChanges.TotalChanges()) - assert.Equal(t, 4, extChanges.TotalBreakingChanges()) + assert.Len(t, extChanges.GetAllChanges(), 5) + assert.Equal(t, 4, extChanges.TotalBreakingChanges()) } diff --git a/what-changed/model/operation.go b/what-changed/model/operation.go index f14ee88..db73e9a 100644 --- a/what-changed/model/operation.go +++ b/what-changed/model/operation.go @@ -30,6 +30,37 @@ type OperationChanges struct { CallbackChanges map[string]*CallbackChanges `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` } +// GetAllChanges returns a slice of all changes made between Operation objects +func (o *OperationChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, o.Changes...) + if o.ExternalDocChanges != nil { + changes = append(changes, o.ExternalDocChanges.GetAllChanges()...) + } + for k := range o.ParameterChanges { + changes = append(changes, o.ParameterChanges[k].GetAllChanges()...) + } + if o.ResponsesChanges != nil { + changes = append(changes, o.ResponsesChanges.GetAllChanges()...) + } + for k := range o.SecurityRequirementChanges { + changes = append(changes, o.SecurityRequirementChanges[k].GetAllChanges()...) + } + if o.RequestBodyChanges != nil { + changes = append(changes, o.RequestBodyChanges.GetAllChanges()...) + } + for k := range o.ServerChanges { + changes = append(changes, o.ServerChanges[k].GetAllChanges()...) + } + for k := range o.CallbackChanges { + changes = append(changes, o.CallbackChanges[k].GetAllChanges()...) + } + if o.ExtensionChanges != nil { + changes = append(changes, o.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes made between two Swagger or OpenAPI Operation objects. func (o *OperationChanges) TotalChanges() int { c := o.PropertyChanges.TotalChanges() diff --git a/what-changed/model/operation_test.go b/what-changed/model/operation_test.go index b653988..39cdba1 100644 --- a/what-changed/model/operation_test.go +++ b/what-changed/model/operation_test.go @@ -102,6 +102,7 @@ parameters: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -152,6 +153,7 @@ parameters: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, "parameters", extChanges.Changes[0].Property) @@ -205,6 +207,7 @@ parameters: // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, "parameters", extChanges.Changes[0].Property) @@ -264,6 +267,7 @@ parameters: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) } @@ -321,6 +325,7 @@ parameters: // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, "jummy", extChanges.Changes[0].Original) @@ -378,6 +383,7 @@ parameters: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, PropertyAdded, extChanges.Changes[1].ChangeType) @@ -435,6 +441,7 @@ parameters: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.TagsLabel, extChanges.Changes[0].Property) @@ -471,6 +478,7 @@ schemes: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 6, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 6) assert.Equal(t, 3, extChanges.TotalBreakingChanges()) } @@ -504,6 +512,7 @@ responses: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.ResponsesChanges.ResponseChanges["200"].Changes[0].ChangeType) assert.Equal(t, Modified, extChanges.ExternalDocChanges.Changes[0].ChangeType) @@ -545,6 +554,7 @@ responses: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -574,6 +584,7 @@ responses: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, PropertyAdded, extChanges.Changes[1].ChangeType) @@ -605,6 +616,7 @@ responses: // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, PropertyRemoved, extChanges.Changes[1].ChangeType) @@ -640,6 +652,7 @@ security: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.SecurityRequirementChanges[0].Changes[0].ChangeType) assert.Equal(t, "crap", extChanges.SecurityRequirementChanges[0].Changes[0].New) @@ -676,6 +689,7 @@ security: // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.SecurityRequirementChanges[0].Changes[0].ChangeType) assert.Equal(t, "crap", extChanges.SecurityRequirementChanges[0].Changes[0].Original) @@ -714,6 +728,7 @@ security: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, "thongs", extChanges.Changes[0].New) @@ -752,6 +767,7 @@ security: // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, "thongs", extChanges.Changes[0].Original) @@ -831,6 +847,7 @@ func TestCompareOperations_V3_ModifyParam(t *testing.T) { // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.ParameterChanges[0].Changes[0].ChangeType) } @@ -864,6 +881,7 @@ func TestCompareOperations_V3_AddParam(t *testing.T) { // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) } @@ -897,6 +915,7 @@ func TestCompareOperations_V3_RemoveParam(t *testing.T) { // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) } @@ -926,6 +945,7 @@ parameters: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) } @@ -955,6 +975,7 @@ parameters: // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -985,6 +1006,7 @@ func TestCompareOperations_V3_ModifyServers(t *testing.T) { // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.ServerChanges[0].Changes[0].ChangeType) } @@ -1018,6 +1040,7 @@ func TestCompareOperations_V3_ModifyCallback(t *testing.T) { // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges. CallbackChanges["myCallback"]. @@ -1058,6 +1081,7 @@ func TestCompareOperations_V3_AddCallback(t *testing.T) { // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) } @@ -1092,6 +1116,7 @@ callbacks: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -1126,6 +1151,7 @@ callbacks: // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) } @@ -1163,6 +1189,7 @@ func TestCompareOperations_V3_RemoveCallback(t *testing.T) { // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) } @@ -1191,6 +1218,7 @@ func TestCompareOperations_V3_AddServer(t *testing.T) { // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.ServerChanges[0].Changes[0].ChangeType) } @@ -1219,6 +1247,7 @@ func TestCompareOperations_V3_RemoveServer(t *testing.T) { // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.ServerChanges[0].Changes[0].ChangeType) } @@ -1247,6 +1276,7 @@ servers: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.ServerChanges[0].Changes[0].ChangeType) } @@ -1275,6 +1305,7 @@ servers: // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.ServerChanges[0].Changes[0].ChangeType) } @@ -1307,6 +1338,7 @@ security: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.SecurityRequirementChanges[0].Changes[0].ChangeType) } @@ -1335,6 +1367,7 @@ security: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Empty(t, extChanges.SecurityRequirementChanges) } @@ -1363,6 +1396,7 @@ security: []` // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Empty(t, extChanges.SecurityRequirementChanges) } @@ -1390,6 +1424,7 @@ func TestCompareOperations_V3_ModifyRequestBody(t *testing.T) { // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.RequestBodyChanges.Changes[0].ChangeType) } @@ -1417,6 +1452,7 @@ requestBody: // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -1442,6 +1478,7 @@ func TestCompareOperations_V3_ModifyExtension(t *testing.T) { // compare. extChanges := CompareOperations(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.ExtensionChanges.Changes[0].ChangeType) } @@ -1469,6 +1506,7 @@ requestBody: // compare. extChanges := CompareOperations(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) } diff --git a/what-changed/model/parameter.go b/what-changed/model/parameter.go index 135fa33..b926884 100644 --- a/what-changed/model/parameter.go +++ b/what-changed/model/parameter.go @@ -26,6 +26,28 @@ type ParameterChanges struct { ContentChanges map[string]*MediaTypeChanges `json:"content,omitempty" yaml:"content,omitempty"` } +// GetAllChanges returns a slice of all changes made between Parameter objects +func (p *ParameterChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, p.Changes...) + if p.SchemaChanges != nil { + changes = append(changes, p.SchemaChanges.GetAllChanges()...) + } + for i := range p.ExamplesChanges { + changes = append(changes, p.ExamplesChanges[i].GetAllChanges()...) + } + if p.ItemsChanges != nil { + changes = append(changes, p.ItemsChanges.GetAllChanges()...) + } + if p.ExtensionChanges != nil { + changes = append(changes, p.ExtensionChanges.GetAllChanges()...) + } + for i := range p.ContentChanges { + changes = append(changes, p.ContentChanges[i].GetAllChanges()...) + } + return changes +} + // TotalChanges returns a count of everything that changed func (p *ParameterChanges) TotalChanges() int { c := p.PropertyChanges.TotalChanges() diff --git a/what-changed/model/parameter_test.go b/what-changed/model/parameter_test.go index 4649110..2a78108 100644 --- a/what-changed/model/parameter_test.go +++ b/what-changed/model/parameter_test.go @@ -78,6 +78,7 @@ func TestCompareParameters_V3_Schema(t *testing.T) { // compare. extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, 1, extChanges.SchemaChanges.TotalChanges()) @@ -105,6 +106,7 @@ schema: // compare. extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) @@ -132,6 +134,7 @@ schema: // compare. extChanges := CompareParameters(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) @@ -157,6 +160,7 @@ func TestCompareParameters_V3_Extensions(t *testing.T) { // compare. extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, 1, extChanges.ExtensionChanges.TotalChanges()) @@ -183,6 +187,7 @@ func TestCompareParameters_V3_ExampleChange(t *testing.T) { // compare. extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -229,6 +234,7 @@ example: a string` // compare extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -254,6 +260,7 @@ example: a string` // compare extChanges := CompareParameters(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) } @@ -282,6 +289,7 @@ func TestCompareParameters_V3_ExamplesChanged(t *testing.T) { // compare extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.ExamplesChanges["anExample"].Changes[0].ChangeType) } @@ -313,6 +321,7 @@ func TestCompareParameters_V3_ExamplesAdded(t *testing.T) { // compare extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) } @@ -344,6 +353,7 @@ func TestCompareParameters_V3_ExamplesRemoved(t *testing.T) { // compare extChanges := CompareParameters(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) } @@ -375,6 +385,7 @@ func TestCompareParameters_V3_ContentChanged(t *testing.T) { // compare extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.ContentChanges["application/json"].SchemaChanges.Changes[0].ChangeType) @@ -410,6 +421,7 @@ func TestCompareParameters_V3_ContentAdded(t *testing.T) { // compare extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) } @@ -434,6 +446,7 @@ func TestCompareParameters_V2_DefaultChange(t *testing.T) { // compare. extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -458,6 +471,7 @@ default: wat?` // compare. extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -483,6 +497,7 @@ func TestCompareParameters_V2_EnumChange(t *testing.T) { // compare. extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -533,6 +548,7 @@ example: a string` // compare extChanges := CompareParameters(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) } @@ -603,6 +619,7 @@ func TestCompareParameters_V2_ItemsChange(t *testing.T) { // compare. extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.ItemsChanges.Changes[0].ChangeType) @@ -630,6 +647,7 @@ items: // compare. extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) } @@ -656,6 +674,7 @@ items: // compare. extChanges := CompareParameters(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) } @@ -680,6 +699,7 @@ func TestCompareParameters_V2_Extensions(t *testing.T) { // compare. extChanges := CompareParameters(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, 1, extChanges.ExtensionChanges.TotalChanges()) } diff --git a/what-changed/model/path_item.go b/what-changed/model/path_item.go index 5724227..06dd3d4 100644 --- a/what-changed/model/path_item.go +++ b/what-changed/model/path_item.go @@ -27,6 +27,46 @@ type PathItemChanges struct { ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` } +// GetAllChanges returns a slice of all changes made between PathItem objects +func (p *PathItemChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, p.Changes...) + if p.GetChanges != nil { + changes = append(changes, p.GetChanges.GetAllChanges()...) + } + if p.PutChanges != nil { + changes = append(changes, p.PutChanges.GetAllChanges()...) + } + if p.PostChanges != nil { + changes = append(changes, p.PostChanges.GetAllChanges()...) + } + if p.DeleteChanges != nil { + changes = append(changes, p.DeleteChanges.GetAllChanges()...) + } + if p.OptionsChanges != nil { + changes = append(changes, p.OptionsChanges.GetAllChanges()...) + } + if p.HeadChanges != nil { + changes = append(changes, p.HeadChanges.GetAllChanges()...) + } + if p.PatchChanges != nil { + changes = append(changes, p.PatchChanges.GetAllChanges()...) + } + if p.TraceChanges != nil { + changes = append(changes, p.TraceChanges.GetAllChanges()...) + } + for i := range p.ServerChanges { + changes = append(changes, p.ServerChanges[i].GetAllChanges()...) + } + for i := range p.ParameterChanges { + changes = append(changes, p.ParameterChanges[i].GetAllChanges()...) + } + if p.ExtensionChanges != nil { + changes = append(changes, p.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes found between two Swagger or OpenAPI PathItems func (p *PathItemChanges) TotalChanges() int { c := p.PropertyChanges.TotalChanges() diff --git a/what-changed/model/path_item_test.go b/what-changed/model/path_item_test.go index 3710f50..d7bbf36 100644 --- a/what-changed/model/path_item_test.go +++ b/what-changed/model/path_item_test.go @@ -105,6 +105,7 @@ x-thing: ding-a-ling` // compare. extChanges := ComparePathItems(&lDoc, &rDoc) assert.Equal(t, 8, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 8) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } diff --git a/what-changed/model/paths.go b/what-changed/model/paths.go index 19a1a64..2c54d14 100644 --- a/what-changed/model/paths.go +++ b/what-changed/model/paths.go @@ -18,6 +18,19 @@ type PathsChanges struct { ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` } +// GetAllChanges returns a slice of all changes made between Paths objects +func (p *PathsChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, p.Changes...) + for k := range p.PathItemsChanges { + changes = append(changes, p.PathItemsChanges[k].GetAllChanges()...) + } + if p.ExtensionChanges != nil { + changes = append(changes, p.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes between two Swagger or OpenAPI Paths Objects func (p *PathsChanges) TotalChanges() int { c := p.PropertyChanges.TotalChanges() diff --git a/what-changed/model/paths_test.go b/what-changed/model/paths_test.go index f74ee8c..c0e471d 100644 --- a/what-changed/model/paths_test.go +++ b/what-changed/model/paths_test.go @@ -84,6 +84,7 @@ x-windows: washed // compare. extChanges := ComparePaths(&lDoc, &rDoc) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -123,6 +124,7 @@ x-windows: dirty // compare. extChanges := ComparePaths(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, "/crispy/chips", extChanges.Changes[0].New) @@ -164,6 +166,7 @@ x-windows: dirty // compare. extChanges := ComparePaths(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, "/crispy/chips", extChanges.Changes[0].Original) @@ -240,6 +243,7 @@ x-windows: washed // compare. extChanges := ComparePaths(&lDoc, &rDoc) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -286,6 +290,7 @@ x-windows: dirty` // compare. extChanges := ComparePaths(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, "/mushy/peas", extChanges.Changes[0].New) @@ -334,6 +339,7 @@ x-windows: dirty` // compare. extChanges := ComparePaths(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, "/mushy/peas", extChanges.Changes[0].Original) diff --git a/what-changed/model/request_body.go b/what-changed/model/request_body.go index 08c16bd..1f209fa 100644 --- a/what-changed/model/request_body.go +++ b/what-changed/model/request_body.go @@ -15,6 +15,19 @@ type RequestBodyChanges struct { ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` } +// GetAllChanges returns a slice of all changes made between RequestBody objects +func (rb *RequestBodyChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, rb.Changes...) + for k := range rb.ContentChanges { + changes = append(changes, rb.ContentChanges[k].GetAllChanges()...) + } + if rb.ExtensionChanges != nil { + changes = append(changes, rb.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes found between two OpenAPI RequestBody objects func (rb *RequestBodyChanges) TotalChanges() int { c := rb.PropertyChanges.TotalChanges() diff --git a/what-changed/model/request_body_test.go b/what-changed/model/request_body_test.go index 0a681e1..cd85d8b 100644 --- a/what-changed/model/request_body_test.go +++ b/what-changed/model/request_body_test.go @@ -78,5 +78,6 @@ content: extChanges := CompareRequestBodies(&lDoc, &rDoc) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) } diff --git a/what-changed/model/response.go b/what-changed/model/response.go index afc73c1..d031fb9 100644 --- a/what-changed/model/response.go +++ b/what-changed/model/response.go @@ -26,6 +26,34 @@ type ResponseChanges struct { ServerChanges *ServerChanges `json:"server,omitempty" yaml:"server,omitempty"` } +// GetAllChanges returns a slice of all changes made between RequestBody objects +func (r *ResponseChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, r.Changes...) + if r.ExtensionChanges != nil { + changes = append(changes, r.ExtensionChanges.GetAllChanges()...) + } + if r.SchemaChanges != nil { + changes = append(changes, r.SchemaChanges.GetAllChanges()...) + } + if r.ExamplesChanges != nil { + changes = append(changes, r.ExamplesChanges.GetAllChanges()...) + } + if r.ServerChanges != nil { + changes = append(changes, r.ServerChanges.GetAllChanges()...) + } + for k := range r.HeadersChanges { + changes = append(changes, r.HeadersChanges[k].GetAllChanges()...) + } + for k := range r.ContentChanges { + changes = append(changes, r.ContentChanges[k].GetAllChanges()...) + } + for k := range r.LinkChanges { + changes = append(changes, r.LinkChanges[k].GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes found between two Swagger or OpenAPI Response Objects func (r *ResponseChanges) TotalChanges() int { c := r.PropertyChanges.TotalChanges() @@ -38,6 +66,9 @@ func (r *ResponseChanges) TotalChanges() int { if r.ExamplesChanges != nil { c += r.ExamplesChanges.TotalChanges() } + if r.ServerChanges != nil { + c += r.ServerChanges.TotalChanges() + } for k := range r.HeadersChanges { c += r.HeadersChanges[k].TotalChanges() } @@ -57,6 +88,9 @@ func (r *ResponseChanges) TotalBreakingChanges() int { if r.SchemaChanges != nil { c += r.SchemaChanges.TotalBreakingChanges() } + if r.ServerChanges != nil { + c += r.ServerChanges.TotalBreakingChanges() + } for k := range r.HeadersChanges { c += r.HeadersChanges[k].TotalBreakingChanges() } diff --git a/what-changed/model/response_test.go b/what-changed/model/response_test.go index c458608..fb5ac0b 100644 --- a/what-changed/model/response_test.go +++ b/what-changed/model/response_test.go @@ -77,6 +77,7 @@ x-toot: poot` extChanges := CompareResponse(&lDoc, &rDoc) assert.Equal(t, 5, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 5) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -110,6 +111,7 @@ examples: extChanges := CompareResponse(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -143,6 +145,7 @@ examples: extChanges := CompareResponse(&rDoc, &lDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -223,5 +226,6 @@ x-toot: pooty` extChanges := CompareResponse(&lDoc, &rDoc) assert.Equal(t, 5, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 5) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) } diff --git a/what-changed/model/responses.go b/what-changed/model/responses.go index 73c19bd..03c8812 100644 --- a/what-changed/model/responses.go +++ b/what-changed/model/responses.go @@ -18,6 +18,22 @@ type ResponsesChanges struct { ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` } +// GetAllChanges returns a slice of all changes made between Responses objects +func (r *ResponsesChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, r.Changes...) + for k := range r.ResponseChanges { + changes = append(changes, r.ResponseChanges[k].GetAllChanges()...) + } + if r.DefaultChanges != nil { + changes = append(changes, r.DefaultChanges.GetAllChanges()...) + } + if r.ExtensionChanges != nil { + changes = append(changes, r.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total number of changes found between two Swagger or OpenAPI Responses objects func (r *ResponsesChanges) TotalChanges() int { c := r.PropertyChanges.TotalChanges() diff --git a/what-changed/model/responses_test.go b/what-changed/model/responses_test.go index e62c251..d56edb7 100644 --- a/what-changed/model/responses_test.go +++ b/what-changed/model/responses_test.go @@ -81,6 +81,7 @@ x-ting: tang` extChanges := CompareResponses(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.ResponseChanges["404"].SchemaChanges.Changes[0].ChangeType) assert.Equal(t, Modified, extChanges.ResponseChanges["200"].SchemaChanges.Changes[0].ChangeType) @@ -121,6 +122,7 @@ x-apple: pie` extChanges := CompareResponses(&rDoc, &lDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.ResponseChanges["200"].Changes[0].ChangeType) } @@ -156,6 +158,7 @@ func TestCompareResponses_V2_RemoveSchema(t *testing.T) { extChanges := CompareResponses(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.ResponseChanges["200"].Changes[0].ChangeType) } @@ -189,6 +192,7 @@ default: extChanges := CompareResponses(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) } @@ -222,6 +226,7 @@ default: extChanges := CompareResponses(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) } @@ -259,6 +264,7 @@ default: extChanges := CompareResponses(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -322,6 +328,7 @@ x-coffee: yum extChanges := CompareResponses(&lDoc, &rDoc) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -355,6 +362,7 @@ default: extChanges := CompareResponses(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, v3.DefaultLabel, extChanges.Changes[0].Property) } @@ -389,6 +397,7 @@ default: extChanges := CompareResponses(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, v3.DefaultLabel, extChanges.Changes[0].Property) } @@ -425,6 +434,7 @@ default: extChanges := CompareResponses(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, v3.DescriptionLabel, extChanges.DefaultChanges.Changes[0].Property) } @@ -457,5 +467,6 @@ func TestCompareResponses_V3_AddRemoveMediaType(t *testing.T) { extChanges := CompareResponses(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } diff --git a/what-changed/model/schema.go b/what-changed/model/schema.go index 2f78eab..a24d723 100644 --- a/what-changed/model/schema.go +++ b/what-changed/model/schema.go @@ -45,6 +45,94 @@ type SchemaChanges struct { PatternPropertiesChanges map[string]*SchemaChanges `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"` } +// GetAllChanges returns a slice of all changes made between Responses objects +func (s *SchemaChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, s.Changes...) + if s.DiscriminatorChanges != nil { + changes = append(changes, s.DiscriminatorChanges.GetAllChanges()...) + } + if len(s.AllOfChanges) > 0 { + for n := range s.AllOfChanges { + if s.AllOfChanges[n] != nil { + changes = append(changes, s.AllOfChanges[n].GetAllChanges()...) + } + } + } + if len(s.AnyOfChanges) > 0 { + for n := range s.AnyOfChanges { + if s.AnyOfChanges[n] != nil { + changes = append(changes, s.AnyOfChanges[n].GetAllChanges()...) + } + } + } + if len(s.OneOfChanges) > 0 { + for n := range s.OneOfChanges { + if s.OneOfChanges[n] != nil { + changes = append(changes, s.OneOfChanges[n].GetAllChanges()...) + } + } + } + if s.NotChanges != nil { + changes = append(changes, s.NotChanges.GetAllChanges()...) + } + if s.ItemsChanges != nil { + changes = append(changes, s.ItemsChanges.GetAllChanges()...) + } + if s.IfChanges != nil { + changes = append(changes, s.IfChanges.GetAllChanges()...) + } + if s.ElseChanges != nil { + changes = append(changes, s.ElseChanges.GetAllChanges()...) + } + if s.ThenChanges != nil { + changes = append(changes, s.ThenChanges.GetAllChanges()...) + } + if s.PropertyNamesChanges != nil { + changes = append(changes, s.PropertyNamesChanges.GetAllChanges()...) + } + if s.ContainsChanges != nil { + changes = append(changes, s.ContainsChanges.GetAllChanges()...) + } + if s.UnevaluatedItemsChanges != nil { + changes = append(changes, s.UnevaluatedItemsChanges.GetAllChanges()...) + } + if s.UnevaluatedPropertiesChanges != nil { + changes = append(changes, s.UnevaluatedPropertiesChanges.GetAllChanges()...) + } + if s.SchemaPropertyChanges != nil { + for n := range s.SchemaPropertyChanges { + if s.SchemaPropertyChanges[n] != nil { + changes = append(changes, s.SchemaPropertyChanges[n].GetAllChanges()...) + } + } + } + if s.DependentSchemasChanges != nil { + for n := range s.DependentSchemasChanges { + if s.DependentSchemasChanges[n] != nil { + changes = append(changes, s.DependentSchemasChanges[n].GetAllChanges()...) + } + } + } + if s.PatternPropertiesChanges != nil { + for n := range s.PatternPropertiesChanges { + if s.PatternPropertiesChanges[n] != nil { + changes = append(changes, s.PatternPropertiesChanges[n].GetAllChanges()...) + } + } + } + if s.ExternalDocChanges != nil { + changes = append(changes, s.ExternalDocChanges.GetAllChanges()...) + } + if s.XMLChanges != nil { + changes = append(changes, s.XMLChanges.GetAllChanges()...) + } + if s.ExtensionChanges != nil { + changes = append(changes, s.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns a count of the total number of changes made to this schema and all sub-schemas func (s *SchemaChanges) TotalChanges() int { t := s.PropertyChanges.TotalChanges() @@ -58,7 +146,9 @@ func (s *SchemaChanges) TotalChanges() int { } if len(s.AnyOfChanges) > 0 { for n := range s.AnyOfChanges { - t += s.AnyOfChanges[n].TotalChanges() + if s.AnyOfChanges[n] != nil { + t += s.AnyOfChanges[n].TotalChanges() + } } } if len(s.OneOfChanges) > 0 { @@ -95,7 +185,9 @@ func (s *SchemaChanges) TotalChanges() int { } if s.SchemaPropertyChanges != nil { for n := range s.SchemaPropertyChanges { - t += s.SchemaPropertyChanges[n].TotalChanges() + if s.SchemaPropertyChanges[n] != nil { + t += s.SchemaPropertyChanges[n].TotalChanges() + } } } if s.DependentSchemasChanges != nil { @@ -302,7 +394,10 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges { } else { sc.PropertyChanges = NewPropertyChanges(nil) } - return sc + if sc.TotalChanges() > 0 { + return sc + } + return nil } func checkSchemaXML(lSchema *base.Schema, rSchema *base.Schema, changes *[]*Change, sc *SchemaChanges) { diff --git a/what-changed/model/schema_test.go b/what-changed/model/schema_test.go index 546f4a5..8d4a668 100644 --- a/what-changed/model/schema_test.go +++ b/what-changed/model/schema_test.go @@ -43,6 +43,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, Modified, changes.Changes[0].ChangeType) assert.Equal(t, "an OK message Changed", changes.Changes[0].New) @@ -71,6 +72,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, changes.Changes[0].ChangeType) assert.Equal(t, "a thing", changes.Changes[0].New) assert.Equal(t, v3.DescriptionLabel, changes.Changes[0].Property) @@ -99,6 +101,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, PropertyRemoved, changes.Changes[0].ChangeType) assert.Equal(t, "a thing", changes.Changes[0].Original) assert.Equal(t, v3.DescriptionLabel, changes.Changes[0].Property) @@ -125,6 +128,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType) } @@ -149,6 +153,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType) } @@ -238,6 +243,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, Modified, changes.Changes[0].ChangeType) assert.Equal(t, "#/components/schemas/Yo", changes.Changes[0].New) } @@ -268,6 +274,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, Modified, changes.Changes[0].ChangeType) assert.Equal(t, v3.RefLabel, changes.Changes[0].Property) assert.Equal(t, "#/components/schemas/Yo", changes.Changes[0].Original) @@ -300,6 +307,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, Modified, changes.Changes[0].ChangeType) assert.Equal(t, v3.RefLabel, changes.Changes[0].Property) assert.Equal(t, "#/components/schemas/Yo", changes.Changes[0].New) @@ -389,6 +397,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, changes.Changes[0].ChangeType) assert.Equal(t, "two", changes.Changes[0].New) assert.Equal(t, v3.RequiredLabel, changes.Changes[0].Property) @@ -419,6 +428,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, PropertyRemoved, changes.Changes[0].ChangeType) assert.Equal(t, "two", changes.Changes[0].Original) assert.Equal(t, v3.RequiredLabel, changes.Changes[0].Property) @@ -446,6 +456,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, PropertyAdded, changes.Changes[0].ChangeType) assert.Equal(t, "d", changes.Changes[0].New) assert.Equal(t, v3.EnumLabel, changes.Changes[0].Property) @@ -473,6 +484,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, PropertyRemoved, changes.Changes[0].ChangeType) assert.Equal(t, "d", changes.Changes[0].Original) assert.Equal(t, v3.EnumLabel, changes.Changes[0].Property) @@ -506,6 +518,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType) assert.Equal(t, "propB", changes.Changes[0].New) assert.Equal(t, v3.PropertiesLabel, changes.Changes[0].Property) @@ -539,6 +552,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType) assert.Equal(t, "propB", changes.Changes[0].Original) assert.Equal(t, v3.PropertiesLabel, changes.Changes[0].Property) @@ -568,6 +582,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.IfChanges.PropertyChanges.TotalChanges()) } @@ -596,6 +611,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.IfLabel, changes.Changes[0].Property) } @@ -624,6 +640,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.IfLabel, changes.Changes[0].Property) } @@ -652,6 +669,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.ElseChanges.PropertyChanges.TotalChanges()) } @@ -680,6 +698,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.ElseLabel, changes.Changes[0].Property) } @@ -708,6 +727,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.ElseLabel, changes.Changes[0].Property) } @@ -736,6 +756,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.ThenChanges.PropertyChanges.TotalChanges()) } @@ -764,6 +785,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.ThenLabel, changes.Changes[0].Property) } @@ -792,6 +814,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.ThenLabel, changes.Changes[0].Property) } @@ -822,6 +845,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.DependentSchemasChanges["schemaOne"].PropertyChanges.TotalChanges()) } @@ -852,6 +876,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.PatternPropertiesChanges["schemaOne"].PropertyChanges.TotalChanges()) } @@ -880,6 +905,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.PropertyNamesChanges.PropertyChanges.TotalChanges()) } @@ -908,6 +934,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.PropertyNamesLabel, changes.Changes[0].Property) } @@ -936,6 +963,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.PropertyNamesLabel, changes.Changes[0].Property) } @@ -964,6 +992,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.ContainsChanges.PropertyChanges.TotalChanges()) } @@ -992,6 +1021,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.ContainsLabel, changes.Changes[0].Property) } @@ -1020,6 +1050,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.ContainsLabel, changes.Changes[0].Property) } @@ -1048,6 +1079,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.UnevaluatedPropertiesChanges.PropertyChanges.TotalChanges()) } @@ -1076,6 +1108,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.UnevaluatedPropertiesLabel, changes.Changes[0].Property) } @@ -1104,6 +1137,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.UnevaluatedPropertiesLabel, changes.Changes[0].Property) } @@ -1132,6 +1166,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.UnevaluatedItemsChanges.PropertyChanges.TotalChanges()) } @@ -1160,6 +1195,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.UnevaluatedItemsLabel, changes.Changes[0].Property) } @@ -1188,6 +1224,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.UnevaluatedItemsLabel, changes.Changes[0].Property) } @@ -1213,6 +1250,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 1, changes.TotalBreakingChanges()) } @@ -1239,6 +1277,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 1, changes.TotalBreakingChanges()) } @@ -1265,6 +1304,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 1, changes.TotalBreakingChanges()) } @@ -1292,6 +1332,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 1, changes.TotalBreakingChanges()) } @@ -1320,6 +1361,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) } @@ -1349,6 +1391,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, Modified, changes.SchemaPropertyChanges["propA"].Changes[0].ChangeType) assert.Equal(t, "string", changes.SchemaPropertyChanges["propA"].Changes[0].New) @@ -1381,6 +1424,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 2, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 2) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType) assert.Equal(t, "propN", changes.Changes[0].New) @@ -1415,6 +1459,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 2, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 2) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType) assert.Equal(t, v3.AnyOfLabel, changes.Changes[0].Property) @@ -1448,6 +1493,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 2, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 2) assert.Equal(t, 2, changes.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType) assert.Equal(t, v3.AnyOfLabel, changes.Changes[0].Property) @@ -1480,6 +1526,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, Modified, changes.AnyOfChanges[0].Changes[0].ChangeType) assert.Equal(t, "string", changes.AnyOfChanges[0].Changes[0].New) @@ -1512,6 +1559,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 2, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 2) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType) assert.Equal(t, v3.OneOfLabel, changes.Changes[0].Property) @@ -1545,6 +1593,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 2, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 2) assert.Equal(t, 2, changes.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType) assert.Equal(t, v3.AllOfLabel, changes.Changes[0].Property) @@ -1579,6 +1628,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.TypeLabel, changes.ItemsChanges.Changes[0].Property) assert.Equal(t, Modified, changes.ItemsChanges.Changes[0].ChangeType) @@ -1612,6 +1662,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.TypeLabel, changes.ItemsChanges.Changes[0].Property) assert.Equal(t, Modified, changes.ItemsChanges.Changes[0].ChangeType) @@ -1645,6 +1696,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.TypeLabel, changes.NotChanges.Changes[0].Property) assert.Equal(t, Modified, changes.NotChanges.Changes[0].ChangeType) @@ -1678,6 +1730,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.PropertyNameLabel, changes.DiscriminatorChanges.Changes[0].Property) assert.Equal(t, Modified, changes.DiscriminatorChanges.Changes[0].ChangeType) @@ -1709,6 +1762,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.DiscriminatorLabel, changes.Changes[0].Property) assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType) @@ -1741,6 +1795,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.DiscriminatorLabel, changes.Changes[0].Property) assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType) @@ -1775,6 +1830,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, v3.URLLabel, changes.ExternalDocChanges.Changes[0].Property) assert.Equal(t, Modified, changes.ExternalDocChanges.Changes[0].ChangeType) @@ -1806,6 +1862,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, v3.ExternalDocsLabel, changes.Changes[0].Property) assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType) @@ -1838,6 +1895,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, v3.ExternalDocsLabel, changes.Changes[0].Property) assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType) @@ -1869,6 +1927,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, "x-melody", changes.ExtensionChanges.Changes[0].Property) assert.Equal(t, ObjectAdded, changes.ExtensionChanges.Changes[0].ChangeType) @@ -1927,6 +1986,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, v3.ExampleLabel, changes.Changes[0].Property) assert.Equal(t, PropertyAdded, changes.Changes[0].ChangeType) @@ -1956,6 +2016,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, v3.ExampleLabel, changes.Changes[0].Property) assert.Equal(t, PropertyRemoved, changes.Changes[0].ChangeType) @@ -1988,6 +2049,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, v3.ExamplesLabel, changes.Changes[0].Property) assert.Equal(t, Modified, changes.Changes[0].ChangeType) @@ -2019,6 +2081,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, v3.ExamplesLabel, changes.Changes[0].Property) assert.Equal(t, ObjectAdded, changes.Changes[0].ChangeType) @@ -2052,6 +2115,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 2, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 2) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, v3.ExamplesLabel, changes.Changes[0].Property) assert.Equal(t, Modified, changes.Changes[0].ChangeType) @@ -2085,6 +2149,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, v3.ExamplesLabel, changes.Changes[0].Property) assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType) @@ -2118,6 +2183,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 2, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 2) assert.Equal(t, 0, changes.TotalBreakingChanges()) assert.Equal(t, v3.ExamplesLabel, changes.Changes[0].Property) assert.Equal(t, Modified, changes.Changes[0].ChangeType) @@ -2151,6 +2217,7 @@ components: changes := CompareSchemas(lSchemaProxy, rSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.NameLabel, changes.XMLChanges.Changes[0].Property) assert.Equal(t, Modified, changes.XMLChanges.Changes[0].ChangeType) @@ -2210,6 +2277,7 @@ components: changes := CompareSchemas(rSchemaProxy, lSchemaProxy) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, v3.XMLLabel, changes.Changes[0].Property) assert.Equal(t, ObjectRemoved, changes.Changes[0].ChangeType) @@ -2252,6 +2320,7 @@ components: changes := CompareDocuments(leftDoc, rightDoc) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) } @@ -2281,6 +2350,7 @@ components: changes := CompareDocuments(leftDoc, rightDoc) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 0, changes.TotalBreakingChanges()) } @@ -2314,6 +2384,7 @@ components: changes := CompareDocuments(leftDoc, rightDoc) assert.NotNil(t, changes) assert.Equal(t, 1, changes.TotalChanges()) + assert.Len(t, changes.GetAllChanges(), 1) assert.Equal(t, 1, changes.TotalBreakingChanges()) } diff --git a/what-changed/model/scopes.go b/what-changed/model/scopes.go index 81fa8aa..b960846 100644 --- a/what-changed/model/scopes.go +++ b/what-changed/model/scopes.go @@ -15,6 +15,16 @@ type ScopesChanges struct { ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` } +// GetAllChanges returns a slice of all changes made between Scopes objects +func (s *ScopesChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, s.Changes...) + if s.ExtensionChanges != nil { + changes = append(changes, s.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns the total changes found between two Swagger Scopes objects. func (s *ScopesChanges) TotalChanges() int { c := s.PropertyChanges.TotalChanges() diff --git a/what-changed/model/scopes_test.go b/what-changed/model/scopes_test.go index 10ec1bd..5537e9d 100644 --- a/what-changed/model/scopes_test.go +++ b/what-changed/model/scopes_test.go @@ -61,6 +61,7 @@ x-nugget: chicken` // compare. extChanges := CompareScopes(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) assert.Equal(t, "pie", extChanges.Changes[0].New) @@ -89,6 +90,7 @@ x-nugget: chicken` // compare. extChanges := CompareScopes(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, "sky", extChanges.Changes[0].New) @@ -118,6 +120,7 @@ x-nugget: soup` // compare. extChanges := CompareScopes(&rDoc, &lDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, "sky", extChanges.Changes[0].Original) diff --git a/what-changed/model/security_requirement.go b/what-changed/model/security_requirement.go index e8ef64e..06f392e 100644 --- a/what-changed/model/security_requirement.go +++ b/what-changed/model/security_requirement.go @@ -15,6 +15,11 @@ type SecurityRequirementChanges struct { *PropertyChanges } +// GetAllChanges returns a slice of all changes made between SecurityRequirement objects +func (s *SecurityRequirementChanges) GetAllChanges() []*Change { + return s.Changes +} + // TotalChanges returns the total number of changes between two SecurityRequirement Objects. func (s *SecurityRequirementChanges) TotalChanges() int { return s.PropertyChanges.TotalChanges() diff --git a/what-changed/model/security_requirement_test.go b/what-changed/model/security_requirement_test.go index ff79fd7..86b8d68 100644 --- a/what-changed/model/security_requirement_test.go +++ b/what-changed/model/security_requirement_test.go @@ -69,6 +69,7 @@ biscuit: // compare extChanges := CompareSecurityRequirement(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, "biscuit", extChanges.Changes[0].NewObject) @@ -101,6 +102,7 @@ biscuit: // compare extChanges := CompareSecurityRequirement(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) } @@ -133,6 +135,7 @@ milk: // compare extChanges := CompareSecurityRequirement(&lDoc, &rDoc) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, ObjectRemoved, extChanges.Changes[1].ChangeType) diff --git a/what-changed/model/security_scheme.go b/what-changed/model/security_scheme.go index a1ee04b..218f926 100644 --- a/what-changed/model/security_scheme.go +++ b/what-changed/model/security_scheme.go @@ -22,6 +22,22 @@ type SecuritySchemeChanges struct { ScopesChanges *ScopesChanges `json:"scopes,omitempty" yaml:"scopes,omitempty"` } +// GetAllChanges returns a slice of all changes made between SecurityRequirement objects +func (ss *SecuritySchemeChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, ss.Changes...) + if ss.OAuthFlowChanges != nil { + changes = append(changes, ss.OAuthFlowChanges.GetAllChanges()...) + } + if ss.ScopesChanges != nil { + changes = append(changes, ss.ScopesChanges.GetAllChanges()...) + } + if ss.ExtensionChanges != nil { + changes = append(changes, ss.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges represents total changes found between two Swagger or OpenAPI SecurityScheme instances. func (ss *SecuritySchemeChanges) TotalChanges() int { c := ss.PropertyChanges.TotalChanges() diff --git a/what-changed/model/security_scheme_test.go b/what-changed/model/security_scheme_test.go index 185a1f3..9685e9c 100644 --- a/what-changed/model/security_scheme_test.go +++ b/what-changed/model/security_scheme_test.go @@ -72,6 +72,7 @@ x-beer: very tasty` // compare extChanges := CompareSecuritySchemes(&lDoc, &rDoc) assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 4) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) assert.Equal(t, Modified, extChanges.Changes[1].ChangeType) @@ -103,6 +104,7 @@ scopes: // compare extChanges := CompareSecuritySchemes(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.ScopesLabel, extChanges.Changes[0].Property) @@ -132,6 +134,7 @@ scopes: // compare extChanges := CompareSecuritySchemes(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, v3.ScopesLabel, extChanges.Changes[0].Property) @@ -161,6 +164,7 @@ func TestCompareSecuritySchemes_v2_ModifyScope(t *testing.T) { // compare extChanges := CompareSecuritySchemes(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.ScopesChanges.Changes[0].ChangeType) assert.Equal(t, v3.ScopesLabel, extChanges.ScopesChanges.Changes[0].Property) @@ -226,6 +230,7 @@ x-beer: cool` // compare extChanges := CompareSecuritySchemes(&lDoc, &rDoc) assert.Equal(t, 5, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 5) assert.Equal(t, 2, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) assert.Equal(t, Modified, extChanges.Changes[1].ChangeType) @@ -258,6 +263,7 @@ flows: // compare extChanges := CompareSecuritySchemes(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) } @@ -286,6 +292,7 @@ flows: // compare extChanges := CompareSecuritySchemes(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) } @@ -317,6 +324,7 @@ flows: // compare extChanges := CompareSecuritySchemes(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, Modified, extChanges.OAuthFlowChanges.ImplicitChanges.Changes[0].ChangeType) } diff --git a/what-changed/model/server.go b/what-changed/model/server.go index 9360c74..8266dde 100644 --- a/what-changed/model/server.go +++ b/what-changed/model/server.go @@ -14,6 +14,16 @@ type ServerChanges struct { ServerVariableChanges map[string]*ServerVariableChanges `json:"serverVariables,omitempty" yaml:"serverVariables,omitempty"` } +// GetAllChanges returns a slice of all changes made between SecurityRequirement objects +func (s *ServerChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, s.Changes...) + for k := range s.ServerVariableChanges { + changes = append(changes, s.ServerVariableChanges[k].GetAllChanges()...) + } + return changes +} + // TotalChanges returns total changes found between two OpenAPI Server Objects func (s *ServerChanges) TotalChanges() int { c := s.PropertyChanges.TotalChanges() diff --git a/what-changed/model/server_test.go b/what-changed/model/server_test.go index 9b88ac6..9bff2e0 100644 --- a/what-changed/model/server_test.go +++ b/what-changed/model/server_test.go @@ -83,6 +83,7 @@ variables: // compare. extChanges := CompareServers(&lDoc, &rDoc) assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Equal(t, 2, extChanges.TotalBreakingChanges()) } @@ -120,6 +121,7 @@ variables: // compare. extChanges := CompareServers(&lDoc, &rDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) assert.Equal(t, ObjectAdded, extChanges.ServerVariableChanges["thing"].Changes[0].ChangeType) @@ -159,6 +161,7 @@ variables: // compare. extChanges := CompareServers(&rDoc, &lDoc) assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 2) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, ObjectRemoved, extChanges.ServerVariableChanges["thing"].Changes[0].ChangeType) diff --git a/what-changed/model/server_variable.go b/what-changed/model/server_variable.go index 32d1db6..74a2b7e 100644 --- a/what-changed/model/server_variable.go +++ b/what-changed/model/server_variable.go @@ -13,6 +13,11 @@ type ServerVariableChanges struct { *PropertyChanges } +// GetAllChanges returns a slice of all changes made between SecurityRequirement objects +func (s *ServerVariableChanges) GetAllChanges() []*Change { + return s.Changes +} + // CompareServerVariables compares a left and right OpenAPI ServerVariable object for changes. // If anything is found, returns a pointer to a ServerVariableChanges instance, otherwise returns nil. func CompareServerVariables(l, r *v3.ServerVariable) *ServerVariableChanges { diff --git a/what-changed/model/server_variable_test.go b/what-changed/model/server_variable_test.go index 7aff95a..063cc81 100644 --- a/what-changed/model/server_variable_test.go +++ b/what-changed/model/server_variable_test.go @@ -67,6 +67,7 @@ enum: // compare. extChanges := CompareServerVariables(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) @@ -100,6 +101,7 @@ enum: // compare. extChanges := CompareServerVariables(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) } @@ -129,6 +131,7 @@ enum: // compare. extChanges := CompareServerVariables(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } @@ -159,6 +162,7 @@ enum: // compare. extChanges := CompareServerVariables(&rDoc, &lDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) assert.Equal(t, PropertyAdded, extChanges.Changes[0].ChangeType) } diff --git a/what-changed/model/tags.go b/what-changed/model/tags.go index 1d80b74..106c9c3 100644 --- a/what-changed/model/tags.go +++ b/what-changed/model/tags.go @@ -16,6 +16,19 @@ type TagChanges struct { ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` } +// GetAllChanges returns a slice of all changes made between Tag objects +func (t *TagChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, t.Changes...) + if t.ExternalDocs != nil { + changes = append(changes, t.ExternalDocs.GetAllChanges()...) + } + if t.ExtensionChanges != nil { + changes = append(changes, t.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns a count of everything that changed within tags. func (t *TagChanges) TotalChanges() int { c := t.PropertyChanges.TotalChanges() diff --git a/what-changed/model/tags_test.go b/what-changed/model/tags_test.go index f479bba..b7162aa 100644 --- a/what-changed/model/tags_test.go +++ b/what-changed/model/tags_test.go @@ -44,6 +44,7 @@ tags: assert.Len(t, changes[0].ExternalDocs.Changes, 2) assert.Len(t, changes[0].ExtensionChanges.Changes, 1) assert.Equal(t, 4, changes[0].TotalChanges()) + assert.Len(t, changes[0].GetAllChanges(), 4) descChange := changes[0].Changes[0] assert.Equal(t, "a lovelier tag description", descChange.New) @@ -86,6 +87,7 @@ tags: // evaluate. assert.Len(t, changes[0].Changes, 1) assert.Equal(t, 1, changes[0].TotalChanges()) + assert.Len(t, changes[0].GetAllChanges(), 1) descChange := changes[0].Changes[0] assert.Equal(t, ObjectAdded, descChange.ChangeType) @@ -119,6 +121,7 @@ tags: // evaluate. assert.Len(t, changes, 2) assert.Equal(t, 1, changes[0].TotalChanges()) + assert.Len(t, changes[0].GetAllChanges(), 1) assert.Equal(t, 1, changes[1].TotalChanges()) assert.Equal(t, 1, changes[0].TotalBreakingChanges()) } @@ -222,6 +225,7 @@ tags: // evaluate. assert.Len(t, changes[0].Changes, 1) assert.Equal(t, 1, changes[0].TotalChanges()) + assert.Len(t, changes[0].GetAllChanges(), 1) descChange := changes[0].Changes[0] assert.Equal(t, Modified, descChange.ChangeType) @@ -288,6 +292,7 @@ tags: // evaluate. assert.Equal(t, 1, changes[0].TotalChanges()) + assert.Len(t, changes[0].GetAllChanges(), 1) assert.Equal(t, ObjectAdded, changes[0].Changes[0].ChangeType) } @@ -316,6 +321,7 @@ tags: // evaluate. assert.Equal(t, 1, changes[0].TotalChanges()) + assert.Len(t, changes[0].GetAllChanges(), 1) assert.Equal(t, ObjectRemoved, changes[0].Changes[0].ChangeType) } diff --git a/what-changed/model/xml.go b/what-changed/model/xml.go index b9df84a..624e930 100644 --- a/what-changed/model/xml.go +++ b/what-changed/model/xml.go @@ -14,6 +14,16 @@ type XMLChanges struct { ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` } +// GetAllChanges returns a slice of all changes made between XML objects +func (x *XMLChanges) GetAllChanges() []*Change { + var changes []*Change + changes = append(changes, x.Changes...) + if x.ExtensionChanges != nil { + changes = append(changes, x.ExtensionChanges.GetAllChanges()...) + } + return changes +} + // TotalChanges returns a count of everything that was changed within an XML object. func (x *XMLChanges) TotalChanges() int { c := x.PropertyChanges.TotalChanges() diff --git a/what-changed/model/xml_test.go b/what-changed/model/xml_test.go index 6445138..9a5e2e5 100644 --- a/what-changed/model/xml_test.go +++ b/what-changed/model/xml_test.go @@ -40,6 +40,7 @@ wrapped: true` // compare. extChanges := CompareXML(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) } @@ -72,6 +73,7 @@ namespace: something` // compare. extChanges := CompareXML(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, PropertyRemoved, extChanges.Changes[0].ChangeType) assert.Equal(t, 1, extChanges.TotalBreakingChanges()) @@ -107,6 +109,7 @@ x-coffee: time` // compare. extChanges := CompareXML(&lDoc, &rDoc) assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Len(t, extChanges.GetAllChanges(), 1) assert.Equal(t, ObjectAdded, extChanges.ExtensionChanges.Changes[0].ChangeType) } diff --git a/what-changed/what_changed.go b/what-changed/what_changed.go index d076a7c..ad94cf3 100644 --- a/what-changed/what_changed.go +++ b/what-changed/what_changed.go @@ -19,6 +19,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/low/v2" "github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/what-changed/model" + "reflect" ) // CompareOpenAPIDocuments will compare left (original) and right (updated) OpenAPI 3+ documents and extract every change @@ -34,3 +35,23 @@ func CompareOpenAPIDocuments(original, updated *v3.Document) *model.DocumentChan func CompareSwaggerDocuments(original, updated *v2.Swagger) *model.DocumentChanges { return model.CompareDocuments(original, updated) } + +func ExtractFlatChanges(changes *model.DocumentChanges) []*model.Change { + return extractChanges(changes) +} + +func extractChanges(anything any) []*model.Change { + + vo := reflect.ValueOf(anything) + switch vo.Kind() { + case reflect.Ptr: + extractChanges(vo.Elem().Interface()) + case reflect.Slice: + + panic("slice not supported") + + case reflect.Struct: + extractChanges(vo.Elem().Interface()) + } + return nil +} \ No newline at end of file