fix: continued moving everything to orderedmaps plus cleaned up most the tests

This commit is contained in:
Tristan Cartledge
2023-12-01 17:37:07 +00:00
parent 0f3d0cb28f
commit a4ad09aab3
169 changed files with 3435 additions and 3764 deletions

View File

@@ -21,7 +21,7 @@ import (
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object
type Discriminator struct {
PropertyName string `json:"propertyName,omitempty" yaml:"propertyName,omitempty"`
Mapping orderedmap.Map[string, string] `json:"mapping,omitempty" yaml:"mapping,omitempty"`
Mapping *orderedmap.Map[string, string] `json:"mapping,omitempty" yaml:"mapping,omitempty"`
low *low.Discriminator
}

View File

@@ -127,7 +127,7 @@ func TestDynamicValue_MarshalYAMLInline(t *testing.T) {
// convert node into yaml
bits, _ := yaml.Marshal(rend)
assert.Equal(t, "properties:\n rice:\n $ref: '#/components/schemas/rice'", strings.TrimSpace(string(bits)))
assert.Equal(t, "properties:\n rice:\n type: array\n items:\n type: string", strings.TrimSpace(string(bits)))
}
func TestDynamicValue_MarshalYAMLInline_Error(t *testing.T) {

View File

@@ -17,9 +17,9 @@ import (
type Example struct {
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Value any `json:"value,omitempty" yaml:"value,omitempty"`
Value *yaml.Node `json:"value,omitempty" yaml:"value,omitempty"`
ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Example
}
@@ -58,7 +58,7 @@ func (e *Example) MarshalYAML() (interface{}, error) {
// ExtractExamples will convert a low-level example map, into a high level one that is simple to navigate.
// no fidelity is lost, everything is still available via GoLow()
func ExtractExamples(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Example]]) orderedmap.Map[string, *Example] {
func ExtractExamples(elements *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Example]]) *orderedmap.Map[string, *Example] {
extracted := orderedmap.New[string, *Example]()
for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() {
extracted.Set(pair.Key().Value, NewExample(pair.Value().Value))

View File

@@ -17,7 +17,6 @@ import (
)
func TestNewExample(t *testing.T) {
var cNode yaml.Node
yml := `summary: an example
@@ -37,18 +36,23 @@ x-hack: code`
// build high
highExample := NewExample(&lowExample)
var xHack string
_ = highExample.Extensions.GetOrZero("x-hack").Decode(&xHack)
var example string
_ = highExample.Value.Decode(&example)
assert.Equal(t, "an example", highExample.Summary)
assert.Equal(t, "something more", highExample.Description)
assert.Equal(t, "https://pb33f.io", highExample.ExternalValue)
assert.Equal(t, "code", highExample.Extensions["x-hack"])
assert.Equal(t, "a thing", highExample.Value)
assert.Equal(t, "code", xHack)
assert.Equal(t, "a thing", example)
assert.Equal(t, 4, highExample.GoLow().ExternalValue.ValueNode.Line)
assert.NotNil(t, highExample.GoLowUntyped())
// render the example as YAML
rendered, _ := highExample.Render()
assert.Equal(t, strings.TrimSpace(string(rendered)), yml)
assert.Equal(t, yml, strings.TrimSpace(string(rendered)))
}
func TestExtractExamples(t *testing.T) {
@@ -71,11 +75,9 @@ func TestExtractExamples(t *testing.T) {
)
assert.Equal(t, "herbs", ExtractExamples(examplesMap).GetOrZero("green").Summary)
}
func ExampleNewExample() {
// create some example yaml (or can be JSON, it does not matter)
yml := `summary: something interesting
description: something more interesting with detail
@@ -98,5 +100,4 @@ x-hack: code`
fmt.Print(highExample.ExternalValue)
// Output: https://pb33f.io
}

View File

@@ -6,6 +6,7 @@ package base
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
@@ -18,7 +19,7 @@ import (
type ExternalDoc struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.ExternalDoc
}
@@ -46,7 +47,7 @@ func (e *ExternalDoc) GoLowUntyped() any {
return e.low
}
func (e *ExternalDoc) GetExtensions() map[string]any {
func (e *ExternalDoc) GetExtensions() *orderedmap.Map[string, *yaml.Node] {
return e.Extensions
}

View File

@@ -5,17 +5,17 @@ package base
import (
"context"
"fmt"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"strings"
"testing"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestNewExternalDoc(t *testing.T) {
var cNode yaml.Node
yml := `description: hack code
@@ -31,22 +31,23 @@ x-hack: code`
highExt := NewExternalDoc(&lowExt)
var xHack string
_ = highExt.Extensions.GetOrZero("x-hack").Decode(&xHack)
assert.Equal(t, "hack code", highExt.Description)
assert.Equal(t, "https://pb33f.io", highExt.URL)
assert.Equal(t, "code", highExt.Extensions["x-hack"])
assert.Equal(t, "code", xHack)
wentLow := highExt.GoLow()
assert.Equal(t, 2, wentLow.URL.ValueNode.Line)
assert.Len(t, highExt.GetExtensions(), 1)
assert.Equal(t, 1, orderedmap.Len(highExt.GetExtensions()))
// render the high-level object as YAML
rendered, _ := highExt.Render()
assert.Equal(t, strings.TrimSpace(string(rendered)), yml)
}
func ExampleNewExternalDoc() {
func TestExampleNewExternalDoc(t *testing.T) {
// create a new external documentation spec reference
// this can be YAML or JSON.
yml := `description: hack code docs
@@ -67,7 +68,8 @@ x-hack: code`
// create new high-level ExternalDoc
highExt := NewExternalDoc(&lowExt)
// print out a extension
fmt.Print(highExt.Extensions["x-hack"])
// Output: code
var xHack string
_ = highExt.Extensions.GetOrZero("x-hack").Decode(&xHack)
assert.Equal(t, "code", xHack)
}

View File

@@ -6,6 +6,7 @@ package base
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
@@ -24,7 +25,7 @@ type Info struct {
Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"`
License *License `json:"license,omitempty" yaml:"license,omitempty"`
Version string `json:"version,omitempty" yaml:"version,omitempty"`
Extensions map[string]any
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Info
}
@@ -53,7 +54,7 @@ func NewInfo(info *low.Info) *Info {
if !info.Version.IsEmpty() {
i.Version = info.Version.Value
}
if len(info.Extensions) > 0 {
if orderedmap.Len(info.Extensions) > 0 {
i.Extensions = high.ExtractExtensions(info.Extensions)
}
return i

View File

@@ -10,6 +10,8 @@ import (
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
@@ -37,6 +39,9 @@ x-cli-name: chicken cli`
highInfo := NewInfo(&lowInfo)
var xCliName string
_ = highInfo.Extensions.GetOrZero("x-cli-name").Decode(&xCliName)
assert.Equal(t, "chicken", highInfo.Title)
assert.Equal(t, "a chicken nugget", highInfo.Summary)
assert.Equal(t, "nugget", highInfo.Description)
@@ -45,7 +50,7 @@ x-cli-name: chicken cli`
assert.Equal(t, "pb33f", highInfo.License.Name)
assert.Equal(t, "https://pb33f.io", highInfo.License.URL)
assert.Equal(t, "99.99", highInfo.Version)
assert.Equal(t, "chicken cli", highInfo.Extensions["x-cli-name"])
assert.Equal(t, "chicken cli", xCliName)
wentLow := highInfo.GoLow()
assert.Equal(t, 10, wentLow.Version.ValueNode.Line)
@@ -109,13 +114,12 @@ url: https://opensource.org/licenses/MIT`
}
func TestInfo_Render(t *testing.T) {
ext := make(map[string]any)
ext["x-pizza"] = "pepperoni"
ext["x-cake"] = &License{
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-pizza", utils.CreateStringNode("pepperoni"))
ext.Set("x-cake", utils.CreateYamlNode(&License{
Name: "someone",
URL: "nowhere",
}
}))
highI := &Info{
Title: "hey",
Description: "there you",
@@ -146,6 +150,9 @@ func TestInfo_Render(t *testing.T) {
// build high
highInfo := NewInfo(&lowInfo)
var xPizza string
_ = highInfo.Extensions.GetOrZero("x-pizza").Decode(&xPizza)
assert.Equal(t, "hey", highInfo.Title)
assert.Equal(t, "there you", highInfo.Description)
assert.Equal(t, "have you got any money", highInfo.TermsOfService)
@@ -154,12 +161,11 @@ func TestInfo_Render(t *testing.T) {
assert.Equal(t, "MIT", highInfo.License.Name)
assert.Equal(t, "https://opensource.org/licenses/MIT", highInfo.License.URL)
assert.Equal(t, "1.2.3", highInfo.Version)
assert.Equal(t, "pepperoni", highInfo.Extensions["x-pizza"])
assert.Equal(t, "pepperoni", xPizza)
assert.NotNil(t, highInfo.GoLowUntyped())
}
func TestInfo_RenderOrder(t *testing.T) {
yml := `title: hey
description: there you
termsOfService: have you got any money
@@ -187,6 +193,9 @@ x-cake:
// build high
highInfo := NewInfo(&lowInfo)
var xPizza string
_ = highInfo.Extensions.GetOrZero("x-pizza").Decode(&xPizza)
assert.Equal(t, "hey", highInfo.Title)
assert.Equal(t, "there you", highInfo.Description)
assert.Equal(t, "have you got any money", highInfo.TermsOfService)
@@ -195,7 +204,7 @@ x-cake:
assert.Equal(t, "MIT", highInfo.License.Name)
assert.Equal(t, "https://opensource.org/licenses/MIT", highInfo.License.URL)
assert.Equal(t, "1.2.3", highInfo.Version)
assert.Equal(t, "pepperoni", highInfo.Extensions["x-pizza"])
assert.Equal(t, "pepperoni", xPizza)
// marshal high back to yaml, should be the same as the original, in same order.
bytes, _ := highInfo.Render()

View File

@@ -45,7 +45,7 @@ type Schema struct {
Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
// in 3.1 examples can be an array (which is recommended)
Examples []any `json:"examples,omitempty" yaml:"examples,omitempty"`
Examples []*yaml.Node `json:"examples,omitempty" yaml:"examples,omitempty"`
// in 3.1 prefixItems provides tuple validation support.
PrefixItems []*SchemaProxy `json:"prefixItems,omitempty" yaml:"prefixItems,omitempty"`
@@ -57,8 +57,8 @@ type Schema struct {
If *SchemaProxy `json:"if,omitempty" yaml:"if,omitempty"`
Else *SchemaProxy `json:"else,omitempty" yaml:"else,omitempty"`
Then *SchemaProxy `json:"then,omitempty" yaml:"then,omitempty"`
DependentSchemas orderedmap.Map[string, *SchemaProxy] `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"`
PatternProperties orderedmap.Map[string, *SchemaProxy] `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"`
DependentSchemas *orderedmap.Map[string, *SchemaProxy] `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"`
PatternProperties *orderedmap.Map[string, *SchemaProxy] `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"`
PropertyNames *SchemaProxy `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"`
UnevaluatedItems *SchemaProxy `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"`
@@ -74,7 +74,7 @@ type Schema struct {
// Compatible with all versions
Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"`
Properties orderedmap.Map[string, *SchemaProxy] `json:"properties,omitempty" yaml:"properties,omitempty"`
Properties *orderedmap.Map[string, *SchemaProxy] `json:"properties,omitempty" yaml:"properties,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
Maximum *float64 `json:"maximum,renderZero,omitempty" yaml:"maximum,renderZero,omitempty"`
@@ -89,19 +89,19 @@ type Schema struct {
MaxProperties *int64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
MinProperties *int64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"`
Enum []*yaml.Node `json:"enum,omitempty" yaml:"enum,omitempty"`
AdditionalProperties *DynamicValue[*SchemaProxy, bool] `json:"additionalProperties,renderZero,omitempty" yaml:"additionalProperties,renderZero,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Default any `json:"default,omitempty" yaml:"default,renderZero,omitempty"`
Const any `json:"const,omitempty" yaml:"const,renderZero,omitempty"`
Default *yaml.Node `json:"default,omitempty" yaml:"default,renderZero,omitempty"`
Const *yaml.Node `json:"const,omitempty" yaml:"const,renderZero,omitempty"`
Nullable *bool `json:"nullable,omitempty" yaml:"nullable,omitempty"`
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30
WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30
ReadOnly *bool `json:"readOnly,renderZero,omitempty" yaml:"readOnly,renderZero,omitempty"` // https://github.com/pb33f/libopenapi/issues/30
WriteOnly *bool `json:"writeOnly,renderZero,omitempty" yaml:"writeOnly,renderZero,omitempty"` // https://github.com/pb33f/libopenapi/issues/30
XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"`
ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Example *yaml.Node `json:"example,omitempty" yaml:"example,omitempty"`
Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *base.Schema
// Parent Proxy refers back to the low level SchemaProxy that is proxying this schema.
@@ -266,17 +266,17 @@ func NewSchema(schema *base.Schema) *Schema {
s.Nullable = &schema.Nullable.Value
}
if !schema.ReadOnly.IsEmpty() {
s.ReadOnly = schema.ReadOnly.Value
s.ReadOnly = &schema.ReadOnly.Value
}
if !schema.WriteOnly.IsEmpty() {
s.WriteOnly = schema.WriteOnly.Value
s.WriteOnly = &schema.WriteOnly.Value
}
if !schema.Deprecated.IsEmpty() {
s.Deprecated = &schema.Deprecated.Value
}
s.Example = schema.Example.Value
if len(schema.Examples.Value) > 0 {
examples := make([]any, len(schema.Examples.Value))
examples := make([]*yaml.Node, len(schema.Examples.Value))
for i := 0; i < len(schema.Examples.Value); i++ {
examples[i] = schema.Examples.Value[i].Value
}
@@ -298,11 +298,11 @@ func NewSchema(schema *base.Schema) *Schema {
}
s.Required = req
var enum []any
if !schema.Anchor.IsEmpty() {
s.Anchor = schema.Anchor.Value
}
var enum []*yaml.Node
for i := range schema.Enum.Value {
enum = append(enum, schema.Enum.Value[i].Value)
}
@@ -322,11 +322,13 @@ func NewSchema(schema *base.Schema) *Schema {
// for every item, build schema async
buildSchema := func(sch lowmodel.ValueReference[*base.SchemaProxy], idx int, bChan chan buildResult) {
p := NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: sch.ValueNode,
n := &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: sch.GetValueNode(),
Value: sch.Value,
Reference: sch.GetReference(),
})
}
n.SetReference(sch.GetReference(), sch.GetValueNode())
p := NewSchemaProxy(n)
bChan <- buildResult{idx: idx, s: p}
}
@@ -353,7 +355,7 @@ func NewSchema(schema *base.Schema) *Schema {
// props async
buildProps := func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*base.SchemaProxy],
props orderedmap.Map[string, *SchemaProxy], sw int,
props *orderedmap.Map[string, *SchemaProxy], sw int,
) {
props.Set(k.Value, NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
Value: v.Value,

View File

@@ -4,13 +4,14 @@
package base
import (
"sync"
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sync"
)
// SchemaProxy exists as a stub that will create a Schema once (and only once) the Schema() method is called. An
@@ -98,11 +99,15 @@ func (sp *SchemaProxy) Schema() *Schema {
// IsReference returns true if the SchemaProxy is a reference to another Schema.
func (sp *SchemaProxy) IsReference() bool {
if sp == nil {
return false
}
if sp.refStr != "" {
return true
}
if sp.schema != nil {
return sp.schema.Value.IsSchemaReference()
return sp.schema.Value.IsReference()
}
return false
}
@@ -112,7 +117,14 @@ func (sp *SchemaProxy) GetReference() string {
if sp.refStr != "" {
return sp.refStr
}
return sp.schema.Value.GetSchemaReference()
return sp.schema.GetValue().GetReference()
}
func (sp *SchemaProxy) GetReferenceNode() *yaml.Node {
if sp.refStr != "" {
return nil
}
return sp.schema.GetValue().GetReferenceNode()
}
// GetReferenceOrigin returns a pointer to the index.NodeOrigin of the $ref if this SchemaProxy is a reference to another Schema.
@@ -171,12 +183,13 @@ func (sp *SchemaProxy) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil
} else {
refNode := sp.GetReferenceNode()
if refNode != nil {
return refNode, nil
}
// do not build out a reference, just marshal the reference.
mp := utils.CreateEmptyMapNode()
mp.Content = append(mp.Content,
utils.CreateStringNode("$ref"),
utils.CreateStringNode(sp.GetReference()))
return mp, nil
return utils.CreateRefNode(sp.GetReference()), nil
}
}

View File

@@ -306,8 +306,8 @@ $anchor: anchor`
assert.Equal(t, "string", compiled.PropertyNames.Schema().Type[0])
assert.Equal(t, "boolean", compiled.UnevaluatedItems.Schema().Type[0])
assert.Equal(t, "integer", compiled.UnevaluatedProperties.A.Schema().Type[0])
assert.True(t, compiled.ReadOnly)
assert.True(t, compiled.WriteOnly)
assert.True(t, *compiled.ReadOnly)
assert.True(t, *compiled.WriteOnly)
assert.True(t, *compiled.Deprecated)
assert.True(t, *compiled.Nullable)
assert.Equal(t, "anchor", compiled.Anchor)
@@ -548,7 +548,7 @@ func TestSchemaProxy_GoLow(t *testing.T) {
sp := NewSchemaProxy(&lowRef)
assert.Equal(t, lowProxy, sp.GoLow())
assert.Equal(t, ref, sp.GoLow().GetSchemaReference())
assert.Equal(t, ref, sp.GoLow().GetReference())
assert.Equal(t, ref, sp.GoLow().GetReference())
spNil := NewSchemaProxy(nil)
@@ -703,7 +703,14 @@ examples:
`
highSchema := getHighSchema(t, yml)
assert.Equal(t, []any{int64(5), int64(10)}, highSchema.Examples)
examples := []any{}
for _, ex := range highSchema.Examples {
var v int64
assert.NoError(t, ex.Decode(&v))
examples = append(examples, v)
}
assert.Equal(t, []any{int64(5), int64(10)}, examples)
}
func ExampleNewSchema() {
@@ -1123,7 +1130,7 @@ components:
// now render it out, it should be identical.
schemaBytes, _ := compiled.RenderInline()
assert.Len(t, schemaBytes, 585)
assert.Equal(t, "properties:\n bigBank:\n type: object\n properties:\n failure_balance_transaction:\n allOf:\n - type: object\n properties:\n name:\n type: string\n price:\n type: number\n anyOf:\n - description: A balance transaction\n anyOf:\n - maxLength: 5000\n type: string\n - description: A balance transaction\n", string(schemaBytes))
}
func TestUnevaluatedPropertiesBoolean_True(t *testing.T) {

View File

@@ -21,7 +21,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 orderedmap.Map[string, []string] `json:"-" yaml:"-"`
Requirements *orderedmap.Map[string, []string] `json:"-" yaml:"-"`
low *base.SecurityRequirement
}
@@ -59,7 +59,6 @@ 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) {
type req struct {
line int
key string

View File

@@ -6,6 +6,7 @@ package base
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
@@ -19,7 +20,7 @@ type Tag struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Extensions map[string]any
Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Tag
}

View File

@@ -16,7 +16,6 @@ import (
)
func TestNewTag(t *testing.T) {
var cNode yaml.Node
yml := `name: chicken
@@ -33,10 +32,13 @@ x-hack: code`
highTag := NewTag(&lowTag)
var xHack string
_ = highTag.Extensions.GetOrZero("x-hack").Decode(&xHack)
assert.Equal(t, "chicken", highTag.Name)
assert.Equal(t, "nuggets", highTag.Description)
assert.Equal(t, "https://pb33f.io", highTag.ExternalDocs.URL)
assert.Equal(t, "code", highTag.Extensions["x-hack"])
assert.Equal(t, "code", xHack)
wentLow := highTag.GoLow()
assert.Equal(t, 5, wentLow.FindExtension("x-hack").ValueNode.Line)
@@ -45,11 +47,9 @@ x-hack: code`
// render the tag as YAML
highTagBytes, _ := highTag.Render()
assert.Equal(t, strings.TrimSpace(string(highTagBytes)), yml)
}
func TestTag_RenderInline(t *testing.T) {
tag := &Tag{
Name: "cake",
}

View File

@@ -6,6 +6,7 @@ package base
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
@@ -25,7 +26,7 @@ type XML struct {
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"`
Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"`
Extensions map[string]any
Extensions *orderedmap.Map[string, *yaml.Node]
low *low.XML
}

View File

@@ -11,27 +11,18 @@ import (
"strings"
"unicode"
"github.com/pb33f/libopenapi/datamodel/high/nodes"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
// NodeEntry represents a single node used by NodeBuilder.
type NodeEntry struct {
Tag string
Key string
Value any
StringValue string
Line int
Style yaml.Style
RenderZero bool
}
// NodeBuilder is a structure used by libopenapi high-level objects, to render themselves back to YAML.
// this allows high-level objects to be 'mutable' because all changes will be rendered out.
type NodeBuilder struct {
Version float32
Nodes []*NodeEntry
Nodes []*nodes.NodeEntry
High any
Low any
Resolve bool // If set to true, all references will be rendered inline
@@ -62,7 +53,6 @@ func NewNodeBuilder(high any, low any) *NodeBuilder {
}
func (n *NodeBuilder) add(key string, i int) {
// only operate on exported fields.
if unicode.IsLower(rune(key[0])) {
return
@@ -71,38 +61,38 @@ func (n *NodeBuilder) add(key string, i int) {
// if the key is 'Extensions' then we need to extract the keys from the map
// and add them to the node builder.
if key == "Extensions" {
extensions := reflect.ValueOf(n.High).Elem().FieldByName(key)
for b, e := range extensions.MapKeys() {
v := extensions.MapIndex(e)
extKey := e.String()
extValue := v.Interface()
nodeEntry := &NodeEntry{Tag: extKey, Key: extKey, Value: extValue, Line: 9999 + b}
ev := reflect.ValueOf(n.High).Elem().FieldByName(key).Interface()
var extensions *orderedmap.Map[string, *yaml.Node]
if ev != nil {
extensions = ev.(*orderedmap.Map[string, *yaml.Node])
}
var lowExtensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
if n.Low != nil && !reflect.ValueOf(n.Low).IsZero() {
fieldValue := reflect.ValueOf(n.Low).Elem().FieldByName("Extensions")
f := fieldValue.Interface()
value := reflect.ValueOf(f)
switch value.Kind() {
case reflect.Map:
if j, ok := n.Low.(low.HasExtensionsUntyped); ok {
originalExtensions := j.GetExtensions()
u := 0
for k := range originalExtensions {
if k.Value == extKey {
if originalExtensions[k].ValueNode.Line != 0 {
nodeEntry.Style = originalExtensions[k].ValueNode.Style
nodeEntry.Line = originalExtensions[k].ValueNode.Line + u
} else {
nodeEntry.Line = 999999 + b + u
lowExtensions = j.GetExtensions()
}
}
u++
j := 0
if lowExtensions != nil {
// If we have low extensions get the original lowest line number so we end up in the same place
for pair := orderedmap.First(lowExtensions); pair != nil; pair = pair.Next() {
if j == 0 || pair.Key().KeyNode.Line < j {
j = pair.Key().KeyNode.Line
}
}
}
for pair := orderedmap.First(extensions); pair != nil; pair = pair.Next() {
nodeEntry := &nodes.NodeEntry{Tag: pair.Key(), Key: pair.Key(), Value: pair.Value(), Line: j}
if lowExtensions != nil {
lowItem := low.FindItemInOrderedMap(pair.Key(), lowExtensions)
nodeEntry.LowValue = lowItem
}
n.Nodes = append(n.Nodes, nodeEntry)
j++
}
// done, extensions are handled separately.
return
@@ -119,11 +109,11 @@ func (n *NodeBuilder) add(key string, i int) {
var renderZeroFlag, omitemptyFlag bool
tagParts := strings.Split(tag, ",")
for i = 1; i < len(tagParts); i++ {
if tagParts[i] == renderZero {
for _, part := range tagParts {
if part == renderZero {
renderZeroFlag = true
}
if tagParts[i] == "omitempty" {
if part == "omitempty" {
omitemptyFlag = true
}
}
@@ -133,7 +123,9 @@ func (n *NodeBuilder) add(key string, i int) {
f := fieldValue.Interface()
value := reflect.ValueOf(f)
var isZero bool
if zeroer, ok := f.(yaml.IsZeroer); ok && zeroer.IsZero() {
if (value.Kind() == reflect.Interface || value.Kind() == reflect.Ptr) && value.IsNil() {
isZero = true
} else if zeroer, ok := f.(yaml.IsZeroer); ok && zeroer.IsZero() {
isZero = true
} else if f == nil || value.IsZero() {
isZero = true
@@ -146,7 +138,7 @@ func (n *NodeBuilder) add(key string, i int) {
}
// create a new node entry
nodeEntry := &NodeEntry{Tag: tagName, Key: key}
nodeEntry := &nodes.NodeEntry{Tag: tagName, Key: key}
nodeEntry.RenderZero = renderZeroFlag
switch value.Kind() {
case reflect.Float64, reflect.Float32:
@@ -192,39 +184,24 @@ func (n *NodeBuilder) add(key string, i int) {
fLow := lowFieldValue.Interface()
value = reflect.ValueOf(fLow)
type lineStyle struct {
line int
style yaml.Style
}
nodeEntry.LowValue = fLow
switch value.Kind() {
case reflect.Slice:
l := value.Len()
lines := make([]lineStyle, l)
lines := make([]int, l)
for g := 0; g < l; g++ {
qw := value.Index(g).Interface()
if we, wok := qw.(low.HasKeyNode); wok {
lines[g] = lineStyle{we.GetKeyNode().Line, we.GetKeyNode().Style}
lines[g] = we.GetKeyNode().Line
}
}
sort.Slice(lines, func(i, j int) bool {
return lines[i].line < lines[j].line
return lines[i] < lines[j]
})
nodeEntry.Line = lines[0].line // pick the lowest line number so this key is sorted in order.
nodeEntry.Style = lines[0].style
break
nodeEntry.Line = lines[0]
case reflect.Map:
l := value.Len()
line := make([]int, l)
for q, ky := range value.MapKeys() {
if we, wok := ky.Interface().(low.HasKeyNode); wok {
line[q] = we.GetKeyNode().Line
}
}
sort.Ints(line)
nodeEntry.Line = line[0]
panic("only ordered maps are supported")
case reflect.Struct:
y := value.Interface()
nodeEntry.Line = 9999 + i
@@ -232,13 +209,11 @@ func (n *NodeBuilder) add(key string, i int) {
if nb.IsReference() {
if jk, kj := y.(low.HasKeyNode); kj {
nodeEntry.Line = jk.GetKeyNode().Line
nodeEntry.Style = jk.GetKeyNode().Style
break
}
}
if nb.GetValueNode() != nil {
nodeEntry.Line = nb.GetValueNode().Line
nodeEntry.Style = nb.GetValueNode().Style
}
}
default:
@@ -252,12 +227,13 @@ func (n *NodeBuilder) add(key string, i int) {
}
}
func (n *NodeBuilder) renderReference() []*yaml.Node {
fg := n.Low.(low.IsReferenced)
nodes := make([]*yaml.Node, 2)
nodes[0] = utils.CreateStringNode("$ref")
nodes[1] = utils.CreateStringNode(fg.GetReference())
return nodes
func (n *NodeBuilder) renderReference(fg low.IsReferenced) *yaml.Node {
origNode := fg.GetReferenceNode()
if origNode == nil {
return utils.CreateRefNode(fg.GetReference())
}
return origNode
}
// Render will render the NodeBuilder back to a YAML node, iterating over every NodeEntry defined
@@ -272,8 +248,7 @@ func (n *NodeBuilder) Render() *yaml.Node {
g := reflect.ValueOf(fg)
if !g.IsNil() {
if fg.IsReference() && !n.Resolve {
m.Content = append(m.Content, n.renderReference()...)
return m
return n.renderReference(n.Low.(low.IsReferenced))
}
}
}
@@ -295,7 +270,7 @@ 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, entry *NodeEntry) *yaml.Node {
func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *nodes.NodeEntry) *yaml.Node {
if entry.Value == nil {
return parent
}
@@ -305,11 +280,11 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
var l *yaml.Node
if entry.Tag != "" {
l = utils.CreateStringNode(entry.Tag)
l.Style = entry.KeyStyle
}
value := entry.Value
line := entry.Line
key := entry.Key
var valueNode *yaml.Node
switch t.Kind() {
@@ -318,9 +293,15 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
val := value.(string)
valueNode = utils.CreateStringNode(val)
valueNode.Line = line
valueNode.Style = entry.Style
break
if entry.LowValue != nil {
if vnut, ok := entry.LowValue.(low.HasValueNodeUntyped); ok {
vn := vnut.GetValueNode()
if vn != nil {
valueNode.Style = vn.Style
}
}
}
case reflect.Bool:
val := value.(bool)
if !val {
@@ -329,26 +310,18 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
valueNode = utils.CreateBoolNode("true")
}
valueNode.Line = line
break
case reflect.Int:
val := strconv.Itoa(value.(int))
valueNode = utils.CreateIntNode(val)
valueNode.Line = line
break
case reflect.Int64:
val := strconv.FormatInt(value.(int64), 10)
valueNode = utils.CreateIntNode(val)
valueNode.Line = line
break
case reflect.Float32:
val := strconv.FormatFloat(float64(value.(float32)), 'f', 2, 64)
valueNode = utils.CreateFloatNode(val)
valueNode.Line = line
break
case reflect.Float64:
precision := -1
if entry.StringValue != "" && strings.Contains(entry.StringValue, ".") {
@@ -357,87 +330,8 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
val := strconv.FormatFloat(value.(float64), 'f', precision, 64)
valueNode = utils.CreateFloatNode(val)
valueNode.Line = line
break
case reflect.Map:
// the keys will be rendered randomly, if we don't find out the original line
// number of the tag.
var orderedCollection []*NodeEntry
m := reflect.ValueOf(value)
for g, k := range m.MapKeys() {
var x string
// extract key
yu := k.Interface()
if o, ok := yu.(low.HasKeyNode); ok {
x = o.GetKeyNode().Value
} else {
x = k.String()
}
// go low and pull out the line number.
lowProps := reflect.ValueOf(n.Low)
if n.Low != nil && !lowProps.IsZero() && !lowProps.IsNil() {
gu := lowProps.Elem()
gi := gu.FieldByName(key)
jl := reflect.ValueOf(gi)
if !jl.IsZero() && gi.Interface() != nil {
gh := gi.Interface()
// extract low level key line number
if pr, ok := gh.(low.HasValueUnTyped); ok {
fg := reflect.ValueOf(pr.GetValueUntyped())
found := false
found, orderedCollection = n.extractLowMapKeys(fg, x, found, orderedCollection, m, k)
if found != true {
// this is something new, add it.
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999 + g,
Value: m.MapIndex(k).Interface(),
})
}
} else {
// this is a map, but it may be wrapped still.
bj := reflect.ValueOf(gh)
orderedCollection = n.extractLowMapKeysWrapped(bj, x, orderedCollection, g)
}
} else {
// this is a map, without any low level details available (probably an extension map).
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999 + g,
Value: m.MapIndex(k).Interface(),
})
}
} else {
// this is a map, without any low level details available (probably an extension map).
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999 + g,
Value: m.MapIndex(k).Interface(),
})
}
}
// sort the slice by line number to ensure everything is rendered in order.
sort.Slice(orderedCollection, func(i, j int) bool {
return orderedCollection[i].Line < orderedCollection[j].Line
})
// create an empty map.
p := utils.CreateEmptyMapNode()
// build out each map node in original order.
for _, cv := range orderedCollection {
n.AddYAMLNode(p, cv)
}
if len(p.Content) > 0 {
valueNode = p
}
panic("only ordered maps are supported")
case reflect.Slice:
@@ -456,8 +350,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
if ut != nil && r.GetReference() != "" &&
ut.(low.IsReferenced).IsReference() {
if !n.Resolve {
refNode := utils.CreateRefNode(glu.GoLowUntyped().(low.IsReferenced).GetReference())
sl.Content = append(sl.Content, refNode)
sl.Content = append(sl.Content, n.renderReference(glu.GoLowUntyped().(low.IsReferenced)))
skip = true
} else {
skip = false
@@ -472,13 +365,13 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
if er, ko := sqi.(Renderable); ko {
var rend interface{}
if !n.Resolve {
rend, _ = er.(Renderable).MarshalYAML()
rend, _ = er.MarshalYAML()
} else {
// try and render inline, if we can, otherwise treat as normal.
if _, ko := er.(RenderableInline); ko {
rend, _ = er.(RenderableInline).MarshalYAMLInline()
} else {
rend, _ = er.(Renderable).MarshalYAML()
rend, _ = er.MarshalYAML()
}
}
// check if this is a pointer or not.
@@ -505,6 +398,17 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
if err != nil {
return parent
} else {
if entry.LowValue != nil {
if vnut, ok := entry.LowValue.(low.HasValueNodeUntyped); ok {
vn := vnut.GetValueNode()
if vn.Kind == yaml.SequenceNode {
for i := range vn.Content {
rawNode.Content[i].Style = vn.Content[i].Style
}
}
}
}
valueNode = &rawNode
}
@@ -524,18 +428,29 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
return parent
case reflect.Ptr:
if r, ok := value.(Renderable); ok {
if m, ok := value.(orderedmap.MapToYamlNoder); ok {
l := entry.LowValue
if l == nil {
if gl, ok := value.(GoesLowUntyped); ok && gl.GoLowUntyped() != nil {
l = gl.GoLowUntyped()
}
}
p := m.ToYamlNode(n, l)
if len(p.Content) > 0 {
valueNode = p
}
} else if r, ok := value.(Renderable); ok {
if gl, lg := value.(GoesLowUntyped); lg {
if gl.GoLowUntyped() != nil {
ut := reflect.ValueOf(gl.GoLowUntyped())
lut := gl.GoLowUntyped()
if lut != nil {
lr := lut.(low.IsReferenced)
ut := reflect.ValueOf(lr)
if !ut.IsNil() {
if gl.GoLowUntyped().(low.IsReferenced).IsReference() {
if lut.(low.IsReferenced).IsReference() {
if !n.Resolve {
// TODO: use renderReference here.
rvn := utils.CreateEmptyMapNode()
rvn.Content = append(rvn.Content, utils.CreateStringNode("$ref"))
rvn.Content = append(rvn.Content, utils.CreateStringNode(gl.GoLowUntyped().(low.IsReferenced).GetReference()))
valueNode = rvn
valueNode = n.renderReference(lut.(low.IsReferenced))
break
}
}
@@ -621,64 +536,6 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
return parent
}
func (n *NodeBuilder) extractLowMapKeysWrapped(iu reflect.Value, x string, orderedCollection []*NodeEntry, g int) []*NodeEntry {
for _, ky := range iu.MapKeys() {
ty := ky.Interface()
if ere, eok := ty.(low.HasKeyNode); eok {
er := ere.GetKeyNode().Value
if er == x {
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: ky.Interface().(low.HasKeyNode).GetKeyNode().Line,
Value: iu.MapIndex(ky).Interface(),
})
}
} else {
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: 9999 + g,
Value: iu.MapIndex(ky).Interface(),
})
}
}
return orderedCollection
}
func (n *NodeBuilder) extractLowMapKeys(fg reflect.Value, x string, found bool, orderedCollection []*NodeEntry, m reflect.Value, k reflect.Value) (bool, []*NodeEntry) {
if fg.IsValid() && !fg.IsZero() {
for j, ky := range fg.MapKeys() {
hu := ky.Interface()
if we, wok := hu.(low.HasKeyNode); wok {
er := we.GetKeyNode().Value
if er == x {
found = true
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: x,
Key: x,
Line: we.GetKeyNode().Line,
Value: m.MapIndex(k).Interface(),
})
}
} else {
uu := ky.Interface()
if uu == x {
// this is a map, without any low level details available
found = true
orderedCollection = append(orderedCollection, &NodeEntry{
Tag: uu.(string),
Key: uu.(string),
Line: 9999 + j,
Value: m.MapIndex(k).Interface(),
})
}
}
}
}
return found, orderedCollection
}
// Renderable is an interface that can be implemented by types that provide a custom MarshalYAML method.
type Renderable interface {
MarshalYAML() (interface{}, error)

View File

@@ -4,82 +4,57 @@
package high
import (
"strings"
"testing"
"github.com/pb33f/libopenapi/datamodel/high/nodes"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"reflect"
"strings"
"testing"
)
type key struct {
Name string `yaml:"name,omitempty"`
type valueReferenceStruct struct {
ref bool
refStr string
ln int
nilval bool
v any
kn *yaml.Node
low.IsReferenced `yaml:"-"`
Value string `yaml:"value,omitempty"`
}
func (k key) GetKeyNode() *yaml.Node {
if k.kn != nil {
return k.kn
}
kn := utils.CreateStringNode("meddy")
kn.Line = k.ln
return kn
func (r valueReferenceStruct) IsReference() bool {
return r.ref
}
func (k key) GetValueUntyped() any {
return k.v
func (r valueReferenceStruct) GetReference() string {
return r.refStr
}
func (k key) GetValueNode() *yaml.Node {
return k.GetValueNodeUntyped()
func (r *valueReferenceStruct) SetReference(ref string, _ *yaml.Node) {
r.refStr = ref
}
func (k key) GetValueNodeUntyped() *yaml.Node {
if k.nilval {
func (r *valueReferenceStruct) GetReferenceNode() *yaml.Node {
return nil
}
kn := utils.CreateStringNode("maddy")
kn.Line = k.ln
return kn
}
func (k key) IsReference() bool {
return k.ref
}
func (k key) GetReference() string {
return k.refStr
}
func (k key) SetReference(ref string) {
k.refStr = ref
}
func (k key) GoLowUntyped() any {
return &k
}
func (k key) MarshalYAML() (interface{}, error) {
func (r valueReferenceStruct) MarshalYAML() (interface{}, error) {
return utils.CreateStringNode("pizza"), nil
}
func (k key) MarshalYAMLInline() (interface{}, error) {
func (r valueReferenceStruct) MarshalYAMLInline() (interface{}, error) {
return utils.CreateStringNode("pizza-inline!"), nil
}
func (r valueReferenceStruct) GoLowUntyped() any {
return &r
}
type plug struct {
Name []string `yaml:"name,omitempty"`
}
type test1 struct {
Thrig map[string]*plug `yaml:"thrig,omitempty"`
Thrig *orderedmap.Map[string, *plug] `yaml:"thrig,omitempty"`
Thing string `yaml:"thing,omitempty"`
Thong int `yaml:"thong,omitempty"`
Thrum int64 `yaml:"thrum,omitempty"`
@@ -93,80 +68,83 @@ type test1 struct {
Throo *float64 `yaml:"throo,renderZero,omitempty"`
Tharg []string `yaml:"tharg,omitempty"`
Type []string `yaml:"type,omitempty"`
Throg []*key `yaml:"throg,omitempty"`
Throg []*valueReferenceStruct `yaml:"throg,omitempty"`
Thrat []interface{} `yaml:"thrat,omitempty"`
Thrag []map[string][]string `yaml:"thrag,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:"-"`
Thrag []*orderedmap.Map[string, []string] `yaml:"thrag,omitempty"`
Thrug *orderedmap.Map[string, string] `yaml:"thrug,omitempty"`
Thoom []*orderedmap.Map[string, string] `yaml:"thoom,omitempty"`
Thomp *orderedmap.Map[low.KeyReference[string], string] `yaml:"thomp,omitempty"`
Thump valueReferenceStruct `yaml:"thump,omitempty"`
Thane valueReferenceStruct `yaml:"thane,omitempty"`
Thunk valueReferenceStruct `yaml:"thunk,omitempty"`
Thrim *valueReferenceStruct `yaml:"thrim,omitempty"`
Thril *orderedmap.Map[string, *valueReferenceStruct] `yaml:"thril,omitempty"`
Extensions *orderedmap.Map[string, *yaml.Node] `yaml:"-"`
ignoreMe string `yaml:"-"`
IgnoreMe string `yaml:"-"`
}
func (te *test1) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (te *test1) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
g := orderedmap.New[low.KeyReference[string], low.ValueReference[*yaml.Node]]()
g := make(map[low.KeyReference[string]]low.ValueReference[any])
i := 0
for pair := orderedmap.First(te.Extensions); pair != nil; pair = pair.Next() {
kn := utils.CreateStringNode(pair.Key())
kn.Line = 999999 + i // weighted to the bottom.
for i := range te.Extensions {
f := reflect.TypeOf(te.Extensions[i])
switch f.Kind() {
case reflect.String:
vn := utils.CreateStringNode(te.Extensions[i].(string))
vn.Line = 999999 // weighted to the bottom.
g[low.KeyReference[string]{
Value: i,
KeyNode: vn,
}] = low.ValueReference[any]{
ValueNode: vn,
Value: te.Extensions[i].(string),
}
case reflect.Map:
kn := utils.CreateStringNode(i)
var vn yaml.Node
_ = vn.Decode(te.Extensions[i])
kn.Line = 999999 // weighted to the bottom.
g[low.KeyReference[string]{
Value: i,
g.Set(low.KeyReference[string]{
Value: pair.Key(),
KeyNode: kn,
}] = low.ValueReference[any]{
ValueNode: &vn,
Value: te.Extensions[i],
}
}
}, low.ValueReference[*yaml.Node]{
ValueNode: pair.Value(),
Value: pair.Value(),
})
i++
}
return g
}
func (te *test1) MarshalYAML() (interface{}, error) {
panic("MarshalYAML")
nb := NewNodeBuilder(te, te)
return nb.Render(), nil
}
func (te *test1) GetKeyNode() *yaml.Node {
panic("GetKeyNode")
kn := utils.CreateStringNode("meddy")
kn.Line = 20
return kn
}
func (te *test1) GoesLowUntyped() any {
panic("GoesLowUntyped")
return te
}
func TestNewNodeBuilder(t *testing.T) {
b := true
c := int64(12345)
d := 1234.1234
thoom1 := orderedmap.New[string, string]()
thoom1.Set("maddy", "champion")
thoom2 := orderedmap.New[string, string]()
thoom2.Set("ember", "naughty")
thomp := orderedmap.New[low.KeyReference[string], string]()
thomp.Set(low.KeyReference[string]{
Value: "meddy",
KeyNode: utils.CreateStringNode("meddy"),
}, "princess")
thrug := orderedmap.New[string, string]()
thrug.Set("chicken", "nuggets")
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-pizza", utils.CreateStringNode("time"))
t1 := test1{
ignoreMe: "I should never be seen!",
Thing: "ding",
@@ -181,30 +159,18 @@ func TestNewNodeBuilder(t *testing.T) {
Thral: &d,
Tharg: []string{"chicken", "nuggets"},
Type: []string{"chicken"},
Thoom: []map[string]string{
{
"maddy": "champion",
Thoom: []*orderedmap.Map[string, string]{
thoom1,
thoom2,
},
{
"ember": "naughty",
},
},
Thomp: map[key]string{
{ln: 1}: "princess",
},
Thane: key{ // this is going to be ignored, needs to be a ValueReference
ln: 2,
ref: true,
refStr: "ripples",
},
Thrug: map[string]string{
"chicken": "nuggets",
},
Thump: key{Name: "I will be ignored", ln: 3},
Thunk: key{ln: 4, nilval: true},
Extensions: map[string]any{
"x-pizza": "time",
Thomp: thomp,
Thane: valueReferenceStruct{ // this is going to be ignored, needs to be a ValueReference
Value: "ripples",
},
Thrug: thrug,
Thump: valueReferenceStruct{Value: "I will be ignored"},
Thunk: valueReferenceStruct{},
Extensions: ext,
}
nb := NewNodeBuilder(&t1, nil)
@@ -235,11 +201,9 @@ thomp:
x-pizza: time`
assert.Equal(t, desired, strings.TrimSpace(string(data)))
}
func TestNewNodeBuilder_Type(t *testing.T) {
t1 := test1{
Type: []string{"chicken", "soup"},
}
@@ -257,15 +221,12 @@ func TestNewNodeBuilder_Type(t *testing.T) {
}
func TestNewNodeBuilder_IsReferenced(t *testing.T) {
t1 := key{
Name: "cotton",
ref: true,
refStr: "#/my/heart",
ln: 2,
t1 := &low.ValueReference[string]{
Value: "cotton",
}
t1.SetReference("#/my/heart", nil)
nb := NewNodeBuilder(&t1, &t1)
nb := NewNodeBuilder(t1, t1)
node := nb.Render()
data, _ := yaml.Marshal(node)
@@ -276,13 +237,13 @@ func TestNewNodeBuilder_IsReferenced(t *testing.T) {
}
func TestNewNodeBuilder_Extensions(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-pizza", utils.CreateStringNode("time"))
ext.Set("x-money", utils.CreateStringNode("time"))
t1 := test1{
Thing: "ding",
Extensions: map[string]any{
"x-pizza": "time",
"x-money": "time",
},
Extensions: ext,
Thong: 1,
}
@@ -294,13 +255,13 @@ func TestNewNodeBuilder_Extensions(t *testing.T) {
}
func TestNewNodeBuilder_LowValueNode(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-pizza", utils.CreateStringNode("time"))
ext.Set("x-money", utils.CreateStringNode("time"))
t1 := test1{
Thing: "ding",
Extensions: map[string]any{
"x-pizza": "time",
"x-money": "time",
},
Extensions: ext,
Thong: 1,
}
@@ -313,12 +274,11 @@ func TestNewNodeBuilder_LowValueNode(t *testing.T) {
}
func TestNewNodeBuilder_NoValue(t *testing.T) {
t1 := test1{
Thing: "",
}
nodeEnty := NodeEntry{}
nodeEnty := nodes.NodeEntry{}
nb := NewNodeBuilder(&t1, &t1)
node := nb.AddYAMLNode(nil, &nodeEnty)
assert.Nil(t, node)
@@ -326,7 +286,7 @@ func TestNewNodeBuilder_NoValue(t *testing.T) {
func TestNewNodeBuilder_EmptyString(t *testing.T) {
t1 := new(test1)
nodeEnty := NodeEntry{}
nodeEnty := nodes.NodeEntry{}
nb := NewNodeBuilder(t1, t1)
node := nb.AddYAMLNode(nil, &nodeEnty)
assert.Nil(t, node)
@@ -334,7 +294,7 @@ func TestNewNodeBuilder_EmptyString(t *testing.T) {
func TestNewNodeBuilder_EmptyStringRenderZero(t *testing.T) {
t1 := new(test1)
nodeEnty := NodeEntry{RenderZero: true, Value: ""}
nodeEnty := nodes.NodeEntry{RenderZero: true, Value: ""}
nb := NewNodeBuilder(t1, t1)
m := utils.CreateEmptyMapNode()
node := nb.AddYAMLNode(m, &nodeEnty)
@@ -344,7 +304,7 @@ func TestNewNodeBuilder_EmptyStringRenderZero(t *testing.T) {
func TestNewNodeBuilder_Bool(t *testing.T) {
t1 := new(test1)
nb := NewNodeBuilder(t1, t1)
nodeEnty := NodeEntry{}
nodeEnty := nodes.NodeEntry{}
node := nb.AddYAMLNode(nil, &nodeEnty)
assert.Nil(t, node)
}
@@ -364,7 +324,7 @@ func TestNewNodeBuilder_Int(t *testing.T) {
t1 := new(test1)
nb := NewNodeBuilder(t1, t1)
p := utils.CreateEmptyMapNode()
nodeEnty := NodeEntry{Tag: "p", Value: 12, Key: "p"}
nodeEnty := nodes.NodeEntry{Tag: "p", Value: 12, Key: "p"}
node := nb.AddYAMLNode(p, &nodeEnty)
assert.NotNil(t, node)
assert.Len(t, node.Content, 2)
@@ -375,7 +335,7 @@ func TestNewNodeBuilder_Int64(t *testing.T) {
t1 := new(test1)
nb := NewNodeBuilder(t1, t1)
p := utils.CreateEmptyMapNode()
nodeEnty := NodeEntry{Tag: "p", Value: int64(234556), Key: "p"}
nodeEnty := nodes.NodeEntry{Tag: "p", Value: int64(234556), Key: "p"}
node := nb.AddYAMLNode(p, &nodeEnty)
assert.NotNil(t, node)
assert.Len(t, node.Content, 2)
@@ -386,7 +346,7 @@ func TestNewNodeBuilder_Float32(t *testing.T) {
t1 := new(test1)
nb := NewNodeBuilder(t1, t1)
p := utils.CreateEmptyMapNode()
nodeEnty := NodeEntry{Tag: "p", Value: float32(1234.23), Key: "p"}
nodeEnty := nodes.NodeEntry{Tag: "p", Value: float32(1234.23), Key: "p"}
node := nb.AddYAMLNode(p, &nodeEnty)
assert.NotNil(t, node)
assert.Len(t, node.Content, 2)
@@ -397,7 +357,7 @@ func TestNewNodeBuilder_Float64(t *testing.T) {
t1 := new(test1)
nb := NewNodeBuilder(t1, t1)
p := utils.CreateEmptyMapNode()
nodeEnty := NodeEntry{Tag: "p", Value: 1234.232323, Key: "p", StringValue: "1234.232323"}
nodeEnty := nodes.NodeEntry{Tag: "p", Value: 1234.232323, Key: "p", StringValue: "1234.232323"}
node := nb.AddYAMLNode(p, &nodeEnty)
assert.NotNil(t, node)
assert.Len(t, node.Content, 2)
@@ -410,30 +370,33 @@ func TestNewNodeBuilder_EmptyNode(t *testing.T) {
nb.Nodes = nil
m := nb.Render()
assert.Len(t, m.Content, 0)
}
func TestNewNodeBuilder_MapKeyHasValue(t *testing.T) {
thrug := orderedmap.New[string, string]()
thrug.Set("dump", "trump")
t1 := test1{
Thrug: map[string]string{
"dump": "trump",
},
Thrug: thrug,
}
type test1low struct {
Thrug key `yaml:"thrug"`
Thrug *orderedmap.Map[*low.KeyReference[string], *low.ValueReference[string]] `yaml:"thrug"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
thrugLow := orderedmap.New[*low.KeyReference[string], *low.ValueReference[string]]()
thrugLow.Set(&low.KeyReference[string]{
Value: "dump",
KeyNode: utils.CreateStringNode("dump"),
}, &low.ValueReference[string]{
Value: "trump",
ValueNode: utils.CreateStringNode("trump"),
})
t2 := test1low{
Thrug: key{
v: map[string]string{
"dump": "trump",
},
ln: 2,
},
Thrug: thrugLow,
}
nb := NewNodeBuilder(&t1, &t2)
@@ -448,31 +411,30 @@ func TestNewNodeBuilder_MapKeyHasValue(t *testing.T) {
}
func TestNewNodeBuilder_MapKeyHasValueThatHasValue(t *testing.T) {
thomp := orderedmap.New[low.KeyReference[string], string]()
thomp.Set(low.KeyReference[string]{Value: "meddy", KeyNode: utils.CreateStringNode("meddy")}, "princess")
t1 := test1{
Thomp: map[key]string{
{v: "who"}: "princess",
},
Thomp: thomp,
}
type test1low struct {
Thomp key `yaml:"thomp"`
Thomp low.ValueReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]] `yaml:"thomp"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
valueMap := orderedmap.New[low.KeyReference[string], low.ValueReference[string]]()
valueMap.Set(low.KeyReference[string]{
Value: "ice",
KeyNode: utils.CreateStringNode("ice"),
}, low.ValueReference[string]{
Value: "princess",
})
t2 := test1low{
Thomp: key{
v: map[key]string{
{
v: key{
v: "ice",
kn: utils.CreateStringNode("limes"),
},
kn: utils.CreateStringNode("chimes"),
ln: 6}: "princess",
},
ln: 2,
Thomp: low.ValueReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]]{
Value: valueMap,
},
}
@@ -488,23 +450,27 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValue(t *testing.T) {
}
func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatch(t *testing.T) {
thomp := orderedmap.New[low.KeyReference[string], string]()
thomp.Set(low.KeyReference[string]{Value: "meddy", KeyNode: utils.CreateStringNode("meddy")}, "princess")
t1 := test1{
Thomp: map[key]string{
{v: "who"}: "princess",
},
Thomp: thomp,
}
type test1low struct {
Thomp low.NodeReference[map[key]string] `yaml:"thomp"`
Thomp low.NodeReference[*orderedmap.Map[low.KeyReference[string], string]] `yaml:"thomp"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
g := low.NodeReference[map[key]string]{
Value: map[key]string{
{v: "my", kn: utils.CreateStringNode("limes")}: "princess",
},
valMap := orderedmap.New[low.KeyReference[string], string]()
valMap.Set(low.KeyReference[string]{
Value: "meddy",
KeyNode: utils.CreateStringNode("meddy"),
}, "princess")
g := low.NodeReference[*orderedmap.Map[low.KeyReference[string], string]]{
Value: valMap,
}
t2 := test1low{
@@ -522,93 +488,26 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatch(t *testing.T) {
assert.Equal(t, desired, strings.TrimSpace(string(data)))
}
func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatchKeyNode(t *testing.T) {
t1 := test1{
Thomp: map[key]string{
{v: "who"}: "princess",
},
}
type test1low struct {
Thomp low.NodeReference[map[key]string] `yaml:"thomp"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
g := low.NodeReference[map[key]string]{
Value: map[key]string{
{v: "my", kn: utils.CreateStringNode("limes")}: "princess",
},
}
t2 := test1low{
Thomp: g,
}
nb := NewNodeBuilder(&t1, &t2)
node := nb.Render()
data, _ := yaml.Marshal(node)
desired := `thomp:
meddy: princess`
assert.Equal(t, desired, strings.TrimSpace(string(data)))
}
func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatch_NoWrap(t *testing.T) {
t1 := test1{
Thomp: map[key]string{
{v: "who"}: "princess",
},
}
type test1low struct {
Thomp map[key]string `yaml:"thomp"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
t2 := test1low{
Thomp: map[key]string{
{v: "my", kn: utils.CreateStringNode("meddy")}: "princess",
},
}
nb := NewNodeBuilder(&t1, &t2)
node := nb.Render()
data, _ := yaml.Marshal(node)
desired := `thomp:
meddy: princess`
assert.Equal(t, desired, strings.TrimSpace(string(data)))
}
func TestNewNodeBuilder_MissingLabel(t *testing.T) {
t1 := new(test1)
nb := NewNodeBuilder(t1, t1)
p := utils.CreateEmptyMapNode()
nodeEnty := NodeEntry{Value: 1234.232323, Key: "p"}
nodeEnty := nodes.NodeEntry{Value: 1234.232323, Key: "p"}
node := nb.AddYAMLNode(p, &nodeEnty)
assert.NotNil(t, node)
assert.Len(t, node.Content, 0)
}
func TestNewNodeBuilder_ExtensionMap(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
pizza := orderedmap.New[string, string]()
pizza.Set("dump", "trump")
ext.Set("x-pizza", utils.CreateYamlNode(pizza))
ext.Set("x-money", utils.CreateStringNode("time"))
t1 := test1{
Thing: "ding",
Extensions: map[string]any{
"x-pizza": map[string]string{
"dump": "trump",
},
"x-money": "time",
},
Extensions: ext,
Thong: 1,
}
@@ -621,20 +520,21 @@ func TestNewNodeBuilder_ExtensionMap(t *testing.T) {
}
func TestNewNodeBuilder_MapKeyHasValueThatHasValueMismatch(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
pizza := orderedmap.New[string, string]()
pizza.Set("dump", "trump")
ext.Set("x-pizza", utils.CreateYamlNode(pizza))
cake := orderedmap.New[string, string]()
cake.Set("maga", "nomore")
ext.Set("x-cake", utils.CreateYamlNode(cake))
thril := orderedmap.New[string, *valueReferenceStruct]()
thril.Set("princess", &valueReferenceStruct{Value: "who"})
thril.Set("heavy", &valueReferenceStruct{Value: "who"})
t1 := test1{
Extensions: map[string]any{
"x-pizza": map[string]string{
"dump": "trump",
},
"x-cake": map[string]string{
"maga": "nomore",
},
},
Thril: map[string]*key{
"princess": {v: "who", Name: "beef", ln: 2},
"heavy": {v: "who", Name: "industries", ln: 3},
},
Extensions: ext,
Thril: thril,
}
nb := NewNodeBuilder(&t1, nil)
@@ -642,13 +542,18 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValueMismatch(t *testing.T) {
data, _ := yaml.Marshal(node)
assert.Len(t, data, 94)
assert.Equal(t, `thril:
princess: pizza
heavy: pizza
x-pizza:
dump: trump
x-cake:
maga: nomore`, strings.TrimSpace(string(data)))
}
func TestNewNodeBuilder_SliceRef(t *testing.T) {
c := key{ref: true, refStr: "#/red/robin/yummmmm", Name: "milky"}
ty := []*key{&c}
c := valueReferenceStruct{ref: true, refStr: "#/red/robin/yummmmm", Value: "milky"}
ty := []*valueReferenceStruct{&c}
t1 := test1{
Throg: ty,
}
@@ -665,9 +570,8 @@ func TestNewNodeBuilder_SliceRef(t *testing.T) {
}
func TestNewNodeBuilder_SliceRef_Inline(t *testing.T) {
c := key{ref: true, refStr: "#/red/robin/yummmmm", Name: "milky"}
ty := []*key{&c}
c := valueReferenceStruct{Value: "milky"}
ty := []*valueReferenceStruct{&c}
t1 := test1{
Throg: ty,
}
@@ -684,22 +588,19 @@ func TestNewNodeBuilder_SliceRef_Inline(t *testing.T) {
assert.Equal(t, desired, strings.TrimSpace(string(data)))
}
type testRender struct {
}
type testRender struct{}
func (t testRender) MarshalYAML() (interface{}, error) {
return utils.CreateStringNode("testy!"), nil
}
type testRenderRawNode struct {
}
type testRenderRawNode struct{}
func (t testRenderRawNode) MarshalYAML() (interface{}, error) {
return yaml.Node{Kind: yaml.ScalarNode, Value: "zesty!"}, nil
}
func TestNewNodeBuilder_SliceRef_Inline_NotCompatible(t *testing.T) {
ty := []interface{}{testRender{}}
t1 := test1{
Thrat: ty,
@@ -718,7 +619,6 @@ func TestNewNodeBuilder_SliceRef_Inline_NotCompatible(t *testing.T) {
}
func TestNewNodeBuilder_SliceRef_Inline_NotCompatible_NotPointer(t *testing.T) {
ty := []interface{}{testRenderRawNode{}}
t1 := test1{
Thrat: ty,
@@ -737,7 +637,6 @@ func TestNewNodeBuilder_SliceRef_Inline_NotCompatible_NotPointer(t *testing.T) {
}
func TestNewNodeBuilder_PointerRef_Inline_NotCompatible_RawNode(t *testing.T) {
ty := testRenderRawNode{}
t1 := test1{
Thurm: &ty,
@@ -755,8 +654,7 @@ func TestNewNodeBuilder_PointerRef_Inline_NotCompatible_RawNode(t *testing.T) {
}
func TestNewNodeBuilder_PointerRef_Inline_NotCompatible(t *testing.T) {
ty := key{}
ty := valueReferenceStruct{}
t1 := test1{
Thurm: &ty,
}
@@ -773,9 +671,8 @@ func TestNewNodeBuilder_PointerRef_Inline_NotCompatible(t *testing.T) {
}
func TestNewNodeBuilder_SliceNoRef(t *testing.T) {
c := key{ref: false, Name: "milky"}
ty := []*key{&c}
c := valueReferenceStruct{Value: "milky"}
ty := []*valueReferenceStruct{&c}
t1 := test1{
Throg: ty,
}
@@ -792,7 +689,6 @@ func TestNewNodeBuilder_SliceNoRef(t *testing.T) {
}
func TestNewNodeBuilder_TestStructAny(t *testing.T) {
t1 := test1{
Thurm: low.ValueReference[any]{
ValueNode: utils.CreateStringNode("beer"),
@@ -808,8 +704,8 @@ func TestNewNodeBuilder_TestStructAny(t *testing.T) {
assert.Equal(t, desired, strings.TrimSpace(string(data)))
}
func TestNewNodeBuilder_TestStructString(t *testing.T) {
func TestNewNodeBuilder_TestStructString(t *testing.T) {
t1 := test1{
Thurm: low.ValueReference[string]{
ValueNode: utils.CreateStringNode("beer"),
@@ -827,12 +723,11 @@ func TestNewNodeBuilder_TestStructString(t *testing.T) {
}
func TestNewNodeBuilder_TestStructPointer(t *testing.T) {
t1 := test1{
Thrim: &key{
Thrim: &valueReferenceStruct{
ref: true,
refStr: "#/cash/money",
Name: "pizza",
Value: "pizza",
},
}
@@ -848,17 +743,17 @@ func TestNewNodeBuilder_TestStructPointer(t *testing.T) {
}
func TestNewNodeBuilder_TestStructRef(t *testing.T) {
fkn := utils.CreateStringNode("pizzaBurgers")
fkn.Line = 22
t1 := test1{
Thurm: low.NodeReference[string]{
Reference: "#/cash/money",
ReferenceNode: true,
thurm := low.NodeReference[string]{
KeyNode: fkn,
ValueNode: fkn,
},
}
thurm.SetReference("#/cash/money", nil)
t1 := test1{
Thurm: thurm,
}
nb := NewNodeBuilder(&t1, &t1)
@@ -872,7 +767,6 @@ func TestNewNodeBuilder_TestStructRef(t *testing.T) {
}
func TestNewNodeBuilder_TestStructDefaultEncode(t *testing.T) {
f := 1
t1 := test1{
Thurm: &f,
@@ -889,9 +783,10 @@ func TestNewNodeBuilder_TestStructDefaultEncode(t *testing.T) {
}
func TestNewNodeBuilder_TestSliceMapSliceStruct(t *testing.T) {
a := []map[string][]string{
{"pizza": {"beer", "wine"}},
pizza := orderedmap.New[string, []string]()
pizza.Set("pizza", []string{"beer", "wine"})
a := []*orderedmap.Map[string, []string]{
pizza,
}
t1 := test1{
@@ -912,7 +807,6 @@ func TestNewNodeBuilder_TestSliceMapSliceStruct(t *testing.T) {
}
func TestNewNodeBuilder_TestRenderZero(t *testing.T) {
f := false
t1 := test1{
Thugg: &f,
@@ -929,7 +823,6 @@ func TestNewNodeBuilder_TestRenderZero(t *testing.T) {
}
func TestNewNodeBuilder_TestRenderZero_Float(t *testing.T) {
f := 0.0
t1 := test1{
Throo: &f,
@@ -946,7 +839,6 @@ func TestNewNodeBuilder_TestRenderZero_Float(t *testing.T) {
}
func TestNewNodeBuilder_TestRenderZero_Float_NotZero(t *testing.T) {
f := 0.12
t1 := test1{
Throo: &f,
@@ -963,11 +855,11 @@ func TestNewNodeBuilder_TestRenderZero_Float_NotZero(t *testing.T) {
}
func TestNewNodeBuilder_TestRenderServerVariableSimulation(t *testing.T) {
thrig := orderedmap.New[string, *plug]()
thrig.Set("pork", &plug{Name: []string{"gammon", "bacon"}})
t1 := test1{
Thrig: map[string]*plug{
"pork": {Name: []string{"gammon", "bacon"}},
},
Thrig: thrig,
}
nb := NewNodeBuilder(&t1, &t1)
@@ -985,22 +877,21 @@ func TestNewNodeBuilder_TestRenderServerVariableSimulation(t *testing.T) {
}
func TestNewNodeBuilder_ShouldHaveNotDoneTestsLikeThisOhWell(t *testing.T) {
m := orderedmap.New[low.KeyReference[string], low.ValueReference[*valueReferenceStruct]]()
m := make(map[low.KeyReference[string]]low.ValueReference[*key])
m[low.KeyReference[string]{
m.Set(low.KeyReference[string]{
KeyNode: utils.CreateStringNode("pizza"),
Value: "pizza",
}] = low.ValueReference[*key]{
}, low.ValueReference[*valueReferenceStruct]{
ValueNode: utils.CreateStringNode("beer"),
Value: &key{},
}
Value: &valueReferenceStruct{},
})
d := make(map[string]*key)
d["pizza"] = &key{}
d := orderedmap.New[string, *valueReferenceStruct]()
d.Set("pizza", &valueReferenceStruct{})
type t1low struct {
Thril low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*key]]
Thril low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*valueReferenceStruct]]]
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
@@ -1010,7 +901,7 @@ func TestNewNodeBuilder_ShouldHaveNotDoneTestsLikeThisOhWell(t *testing.T) {
}
t2 := t1low{
Thril: low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*key]]{
Thril: low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*valueReferenceStruct]]]{
Value: m,
ValueNode: utils.CreateStringNode("beer"),
},

View File

@@ -0,0 +1,16 @@
package nodes
import "gopkg.in/yaml.v3"
// NodeEntry represents a single node used by NodeBuilder.
type NodeEntry struct {
Tag string
Key string
Value any
StringValue string
Line int
KeyStyle yaml.Style
// ValueStyle yaml.Style
RenderZero bool
LowValue any
}

View File

@@ -15,12 +15,13 @@ package high
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
// GoesLow is used to represent any high-level model. All high level models meet this interface and can be used to
// extract low-level models from any high-level model.
type GoesLow[T any] interface {
// GoLow returns the low-level object that was used to create the high-level object. This allows consumers
// to dive-down into the plumbing API at any point in the model.
GoLow() T
@@ -29,18 +30,17 @@ type GoesLow[T any] interface {
// GoesLowUntyped is used to represent any high-level model. All high level models meet this interface and can be used to
// extract low-level models from any high-level model.
type GoesLowUntyped interface {
// GoLowUntyped returns the low-level object that was used to create the high-level object. This allows consumers
// to dive-down into the plumbing API at any point in the model.
GoLowUntyped() any
}
// ExtractExtensions is a convenience method for converting low-level extension definitions, to a high level map[string]any
// ExtractExtensions is a convenience method for converting low-level extension definitions, to a high level *orderedmap.Map[string, *yaml.Node]
// definition that is easier to consume in applications.
func ExtractExtensions(extensions map[low.KeyReference[string]]low.ValueReference[any]) map[string]any {
extracted := make(map[string]any)
for k, v := range extensions {
extracted[k.Value] = v.Value
func ExtractExtensions(extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]) *orderedmap.Map[string, *yaml.Node] {
extracted := orderedmap.New[string, *yaml.Node]()
for pair := orderedmap.First(extensions); pair != nil; pair = pair.Next() {
extracted.Set(pair.Key().Value, pair.Value().Value)
}
return extracted
}
@@ -61,18 +61,18 @@ func ExtractExtensions(extensions map[low.KeyReference[string]]low.ValueReferenc
//
// schema := schemaProxy.Schema() // any high-level object that has
// extensions, err := UnpackExtensions[MyComplexType, low.Schema](schema)
func UnpackExtensions[T any, R low.HasExtensions[T]](low GoesLow[R]) (map[string]*T, error) {
m := make(map[string]*T)
func UnpackExtensions[T any, R low.HasExtensions[T]](low GoesLow[R]) (*orderedmap.Map[string, *T], error) {
m := orderedmap.New[string, *T]()
ext := low.GoLow().GetExtensions()
for i := range ext {
key := i.Value
for pair := orderedmap.First(ext); pair != nil; pair = pair.Next() {
key := pair.Key().Value
g := new(T)
valueNode := ext[i].ValueNode
valueNode := pair.Value().ValueNode
err := valueNode.Decode(g)
if err != nil {
return nil, err
}
m[key] = g
m.Set(key, g)
}
return m, nil
}

View File

@@ -4,21 +4,30 @@
package high
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func TestExtractExtensions(t *testing.T) {
n := make(map[low.KeyReference[string]]low.ValueReference[any])
n[low.KeyReference[string]{
n := orderedmap.New[low.KeyReference[string], low.ValueReference[*yaml.Node]]()
n.Set(low.KeyReference[string]{
Value: "pb33f",
}] = low.ValueReference[any]{
Value: "new cowboy in town",
}
}, low.ValueReference[*yaml.Node]{
Value: utils.CreateStringNode("new cowboy in town"),
})
ext := ExtractExtensions(n)
assert.Equal(t, "new cowboy in town", ext["pb33f"])
var pb33f string
err := ext.GetOrZero("pb33f").Decode(&pb33f)
require.NoError(t, err)
assert.Equal(t, "new cowboy in town", pb33f)
}
type textExtension struct {
@@ -35,15 +44,14 @@ func (p *parent) GoLow() *child {
}
type child struct {
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
}
func (c *child) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (c *child) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return c.Extensions
}
func TestUnpackExtensions(t *testing.T) {
var resultA, resultB yaml.Node
ymlA := `
@@ -59,18 +67,18 @@ power: 2`
err = yaml.Unmarshal([]byte(ymlB), &resultB)
assert.NoError(t, err)
n := make(map[low.KeyReference[string]]low.ValueReference[any])
n[low.KeyReference[string]{
n := orderedmap.New[low.KeyReference[string], low.ValueReference[*yaml.Node]]()
n.Set(low.KeyReference[string]{
Value: "x-rancher-a",
}] = low.ValueReference[any]{
}, low.ValueReference[*yaml.Node]{
ValueNode: resultA.Content[0],
}
})
n[low.KeyReference[string]{
n.Set(low.KeyReference[string]{
Value: "x-rancher-b",
}] = low.ValueReference[any]{
}, low.ValueReference[*yaml.Node]{
ValueNode: resultB.Content[0],
}
})
c := new(child)
c.Extensions = n
@@ -81,14 +89,13 @@ power: 2`
res, err := UnpackExtensions[textExtension, *child](p)
assert.NoError(t, err)
assert.NotEmpty(t, res)
assert.Equal(t, "buckaroo", res["x-rancher-a"].Cowboy)
assert.Equal(t, 100, res["x-rancher-a"].Power)
assert.Equal(t, "frogman", res["x-rancher-b"].Cowboy)
assert.Equal(t, 2, res["x-rancher-b"].Power)
assert.Equal(t, "buckaroo", res.GetOrZero("x-rancher-a").Cowboy)
assert.Equal(t, 100, res.GetOrZero("x-rancher-a").Power)
assert.Equal(t, "frogman", res.GetOrZero("x-rancher-b").Cowboy)
assert.Equal(t, 2, res.GetOrZero("x-rancher-b").Power)
}
func TestUnpackExtensions_Fail(t *testing.T) {
var resultA, resultB yaml.Node
ymlA := `
@@ -105,18 +112,18 @@ power: hello`
err = yaml.Unmarshal([]byte(ymlB), &resultB)
assert.NoError(t, err)
n := make(map[low.KeyReference[string]]low.ValueReference[any])
n[low.KeyReference[string]{
n := orderedmap.New[low.KeyReference[string], low.ValueReference[*yaml.Node]]()
n.Set(low.KeyReference[string]{
Value: "x-rancher-a",
}] = low.ValueReference[any]{
}, low.ValueReference[*yaml.Node]{
ValueNode: resultA.Content[0],
}
})
n[low.KeyReference[string]{
n.Set(low.KeyReference[string]{
Value: "x-rancher-b",
}] = low.ValueReference[any]{
}, low.ValueReference[*yaml.Node]{
ValueNode: resultB.Content[0],
}
})
c := new(child)
c.Extensions = n

View File

@@ -4,6 +4,7 @@
package v2
import (
"github.com/pb33f/libopenapi/datamodel"
highbase "github.com/pb33f/libopenapi/datamodel/high/base"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
@@ -17,7 +18,7 @@ import (
// arrays or models.
// - https://swagger.io/specification/v2/#definitionsObject
type Definitions struct {
Definitions orderedmap.Map[string, *highbase.SchemaProxy]
Definitions *orderedmap.Map[string, *highbase.SchemaProxy]
low *low.Definitions
}
@@ -38,7 +39,7 @@ func NewDefinitions(definitions *low.Definitions) *Definitions {
defs.Set(value.key, value.result)
return nil
}
_ = orderedmap.TranslateMapParallel(definitions.Schemas, translateFunc, resultFunc)
_ = datamodel.TranslateMapParallel(definitions.Schemas, translateFunc, resultFunc)
rd.Definitions = defs
return rd
}

View File

@@ -6,13 +6,14 @@ package v2
import (
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
// Example represents a high-level Swagger / OpenAPI 2 Example object, backed by a low level one.
// Allows sharing examples for operation responses
// - https://swagger.io/specification/v2/#exampleObject
type Example struct {
Values orderedmap.Map[string, any]
Values *orderedmap.Map[string, *yaml.Node]
low *low.Examples
}
@@ -21,7 +22,7 @@ func NewExample(examples *low.Examples) *Example {
e := new(Example)
e.low = examples
if orderedmap.Len(examples.Values) > 0 {
values := orderedmap.New[string, any]()
values := orderedmap.New[string, *yaml.Node]()
for pair := orderedmap.First(examples.Values); pair != nil; pair = pair.Next() {
values.Set(pair.Key().Value, pair.Value().Value)
}

View File

@@ -6,6 +6,8 @@ package v2
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
// Header Represents a high-level Swagger / OpenAPI 2 Header object, backed by a low-level one.
@@ -30,7 +32,7 @@ type Header struct {
UniqueItems bool
Enum []any
MultipleOf int
Extensions map[string]any
Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Header
}

View File

@@ -5,6 +5,7 @@ package v2
import (
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"gopkg.in/yaml.v3"
)
// Items is a high-level representation of a Swagger / OpenAPI 2 Items object, backed by a low level one.
@@ -16,7 +17,7 @@ type Items struct {
Format string
CollectionFormat string
Items *Items
Default any
Default *yaml.Node
Maximum int
ExclusiveMaximum bool
Minimum int
@@ -27,7 +28,7 @@ type Items struct {
MaxItems int
MinItems int
UniqueItems bool
Enum []any
Enum []*yaml.Node
MultipleOf int
low *low.Items
}
@@ -82,7 +83,7 @@ func NewItems(items *low.Items) *Items {
i.UniqueItems = items.UniqueItems.Value
}
if !items.Enum.IsEmpty() {
var enums []any
var enums []*yaml.Node
for e := range items.Enum.Value {
enums = append(enums, items.Enum.Value[e].Value)
}

View File

@@ -7,6 +7,8 @@ import (
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/high/base"
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
// Operation represents a high-level Swagger / OpenAPI 2 Operation object, backed by a low-level one.
@@ -25,7 +27,7 @@ type Operation struct {
Schemes []string
Deprecated bool
Security []*base.SecurityRequirement
Extensions map[string]any
Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Operation
}

View File

@@ -7,6 +7,8 @@ import (
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/high/base"
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
// Parameter represents a high-level Swagger / OpenAPI 2 Parameter object, backed by a low-level one.
@@ -61,7 +63,7 @@ type Parameter struct {
Schema *base.SchemaProxy
Items *Items
CollectionFormat string
Default any
Default *yaml.Node
Maximum *int
ExclusiveMaximum *bool
Minimum *int
@@ -72,9 +74,9 @@ type Parameter struct {
MaxItems *int
MinItems *int
UniqueItems *bool
Enum []any
Enum []*yaml.Node
MultipleOf *int
Extensions map[string]any
Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Parameter
}
@@ -147,7 +149,7 @@ func NewParameter(parameter *low.Parameter) *Parameter {
p.UniqueItems = &parameter.UniqueItems.Value
}
if !parameter.Enum.IsEmpty() {
var enums []any
var enums []*yaml.Node
for e := range parameter.Enum.Value {
enums = append(enums, parameter.Enum.Value[e].Value)
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"github.com/pb33f/libopenapi/datamodel"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
@@ -16,7 +17,7 @@ import (
// referenced to the ones defined here. It does not define global operation parameters
// - https://swagger.io/specification/v2/#parametersDefinitionsObject
type ParameterDefinitions struct {
Definitions orderedmap.Map[string, *Parameter]
Definitions *orderedmap.Map[string, *Parameter]
low *low.ParameterDefinitions
}
@@ -36,7 +37,7 @@ func NewParametersDefinitions(parametersDefinitions *low.ParameterDefinitions) *
params.Set(value.key, value.result)
return nil
}
_ = orderedmap.TranslateMapParallel(parametersDefinitions.Definitions, translateFunc, resultFunc)
_ = datamodel.TranslateMapParallel(parametersDefinitions.Definitions, translateFunc, resultFunc)
pd.Definitions = params
return pd
}

View File

@@ -4,10 +4,15 @@
package v2
import (
"reflect"
"slices"
"sync"
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/datamodel/low"
lowV2 "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
// PathItem represents a high-level Swagger / OpenAPI 2 PathItem object backed by a low-level one.
@@ -26,12 +31,12 @@ type PathItem struct {
Head *Operation
Patch *Operation
Parameters []*Parameter
Extensions map[string]any
low *low.PathItem
Extensions *orderedmap.Map[string, *yaml.Node]
low *lowV2.PathItem
}
// NewPathItem will create a new high-level PathItem from a low-level one. All paths are built out asynchronously.
func NewPathItem(pathItem *low.PathItem) *PathItem {
func NewPathItem(pathItem *lowV2.PathItem) *PathItem {
p := new(PathItem)
p.low = pathItem
p.Extensions = high.ExtractExtensions(pathItem.Extensions)
@@ -42,7 +47,7 @@ func NewPathItem(pathItem *low.PathItem) *PathItem {
}
p.Parameters = params
}
var buildOperation = func(method string, op *low.Operation) *Operation {
buildOperation := func(method string, op *lowV2.Operation) *Operation {
return NewOperation(op)
}
@@ -50,49 +55,49 @@ func NewPathItem(pathItem *low.PathItem) *PathItem {
if !pathItem.Get.IsEmpty() {
wg.Add(1)
go func() {
p.Get = buildOperation(low.GetLabel, pathItem.Get.Value)
p.Get = buildOperation(lowV2.GetLabel, pathItem.Get.Value)
wg.Done()
}()
}
if !pathItem.Put.IsEmpty() {
wg.Add(1)
go func() {
p.Put = buildOperation(low.PutLabel, pathItem.Put.Value)
p.Put = buildOperation(lowV2.PutLabel, pathItem.Put.Value)
wg.Done()
}()
}
if !pathItem.Post.IsEmpty() {
wg.Add(1)
go func() {
p.Post = buildOperation(low.PostLabel, pathItem.Post.Value)
p.Post = buildOperation(lowV2.PostLabel, pathItem.Post.Value)
wg.Done()
}()
}
if !pathItem.Patch.IsEmpty() {
wg.Add(1)
go func() {
p.Patch = buildOperation(low.PatchLabel, pathItem.Patch.Value)
p.Patch = buildOperation(lowV2.PatchLabel, pathItem.Patch.Value)
wg.Done()
}()
}
if !pathItem.Delete.IsEmpty() {
wg.Add(1)
go func() {
p.Delete = buildOperation(low.DeleteLabel, pathItem.Delete.Value)
p.Delete = buildOperation(lowV2.DeleteLabel, pathItem.Delete.Value)
wg.Done()
}()
}
if !pathItem.Head.IsEmpty() {
wg.Add(1)
go func() {
p.Head = buildOperation(low.HeadLabel, pathItem.Head.Value)
p.Head = buildOperation(lowV2.HeadLabel, pathItem.Head.Value)
wg.Done()
}()
}
if !pathItem.Options.IsEmpty() {
wg.Add(1)
go func() {
p.Options = buildOperation(low.OptionsLabel, pathItem.Options.Value)
p.Options = buildOperation(lowV2.OptionsLabel, pathItem.Options.Value)
wg.Done()
}()
}
@@ -101,32 +106,65 @@ func NewPathItem(pathItem *low.PathItem) *PathItem {
}
// GoLow returns the low-level PathItem used to create the high-level one.
func (p *PathItem) GoLow() *low.PathItem {
func (p *PathItem) GoLow() *lowV2.PathItem {
return p.low
}
func (p *PathItem) GetOperations() map[string]*Operation {
o := make(map[string]*Operation)
func (p *PathItem) GetOperations() *orderedmap.Map[string, *Operation] {
o := orderedmap.New[string, *Operation]()
// TODO: this is a bit of a hack, but it works for now. We might just want to actually pull the data out of the document as a map and split it into the individual operations
type op struct {
name string
op *Operation
line int
}
getLine := func(field string, idx int) int {
if p.GoLow() == nil {
return idx
}
l, ok := reflect.ValueOf(p.GoLow()).Elem().FieldByName(field).Interface().(low.NodeReference[*lowV2.Operation])
if !ok || l.GetKeyNode() == nil {
return idx
}
return l.GetKeyNode().Line
}
ops := []op{}
if p.Get != nil {
o[low.GetLabel] = p.Get
ops = append(ops, op{name: lowV2.GetLabel, op: p.Get, line: getLine("Get", -7)})
}
if p.Put != nil {
o[low.PutLabel] = p.Put
ops = append(ops, op{name: lowV2.PutLabel, op: p.Put, line: getLine("Put", -6)})
}
if p.Post != nil {
o[low.PostLabel] = p.Post
ops = append(ops, op{name: lowV2.PostLabel, op: p.Post, line: getLine("Post", -5)})
}
if p.Delete != nil {
o[low.DeleteLabel] = p.Delete
ops = append(ops, op{name: lowV2.DeleteLabel, op: p.Delete, line: getLine("Delete", -4)})
}
if p.Options != nil {
o[low.OptionsLabel] = p.Options
ops = append(ops, op{name: lowV2.OptionsLabel, op: p.Options, line: getLine("Options", -3)})
}
if p.Head != nil {
o[low.HeadLabel] = p.Head
ops = append(ops, op{name: lowV2.HeadLabel, op: p.Head, line: getLine("Head", -2)})
}
if p.Patch != nil {
o[low.PatchLabel] = p.Patch
ops = append(ops, op{name: lowV2.PatchLabel, op: p.Patch, line: getLine("Patch", -1)})
}
slices.SortStableFunc(ops, func(a op, b op) int {
return a.line - b.line
})
for _, op := range ops {
o.Set(op.name, op.op)
}
return o
}

View File

@@ -5,16 +5,17 @@ package v2
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func TestPathItem_GetOperations(t *testing.T) {
yml := `get:
description: get
put:
@@ -41,5 +42,5 @@ options:
r := NewPathItem(&n)
assert.Len(t, r.GetOperations(), 7)
assert.Equal(t, 7, orderedmap.Len(r.GetOperations()))
}

View File

@@ -9,12 +9,13 @@ import (
"github.com/pb33f/libopenapi/datamodel/low"
v2low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
// Paths represents a high-level Swagger / OpenAPI Paths object, backed by a low-level one.
type Paths struct {
PathItems orderedmap.Map[string, *PathItem]
Extensions map[string]any
PathItems *orderedmap.Map[string, *PathItem]
Extensions *orderedmap.Map[string, *yaml.Node]
low *v2low.Paths
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/high/base"
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
// Response is a representation of a high-level Swagger / OpenAPI 2 Response object, backed by a low-level one.
@@ -16,9 +17,9 @@ import (
type Response struct {
Description string
Schema *base.SchemaProxy
Headers orderedmap.Map[string, *Header]
Headers *orderedmap.Map[string, *Header]
Examples *Example
Extensions map[string]any
Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Response
}

View File

@@ -4,17 +4,19 @@
package v2
import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
// Responses is a high-level representation of a Swagger / OpenAPI 2 Responses object, backed by a low level one.
type Responses struct {
Codes orderedmap.Map[string, *Response]
Codes *orderedmap.Map[string, *Response]
Default *Response
Extensions map[string]any
Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Responses
}
@@ -40,7 +42,7 @@ func NewResponses(responses *low.Responses) *Responses {
resp.Set(value.key, value.result)
return nil
}
_ = orderedmap.TranslateMapParallel(responses.Codes, translateFunc, resultFunc)
_ = datamodel.TranslateMapParallel(responses.Codes, translateFunc, resultFunc)
r.Codes = resp
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"github.com/pb33f/libopenapi/datamodel"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
@@ -16,7 +17,7 @@ import (
// referenced to the ones defined here. It does not define global operation responses
// - https://swagger.io/specification/v2/#responsesDefinitionsObject
type ResponsesDefinitions struct {
Definitions orderedmap.Map[string, *Response]
Definitions *orderedmap.Map[string, *Response]
low *low.ResponsesDefinitions
}
@@ -36,7 +37,7 @@ func NewResponsesDefinitions(responsesDefinitions *low.ResponsesDefinitions) *Re
return nil
}
_ = orderedmap.TranslateMapParallel(responsesDefinitions.Definitions, translateFunc, resultFunc)
_ = datamodel.TranslateMapParallel(responsesDefinitions.Definitions, translateFunc, resultFunc)
rd.Definitions = responses
return rd
}

View File

@@ -13,7 +13,7 @@ import (
// Scopes lists the available scopes for an OAuth2 security scheme.
// - https://swagger.io/specification/v2/#scopesObject
type Scopes struct {
Values orderedmap.Map[string, string]
Values *orderedmap.Map[string, string]
low *low.Scopes
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"github.com/pb33f/libopenapi/datamodel"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
@@ -16,7 +17,7 @@ import (
// schemes on the operations and only serves to provide the relevant details for each scheme
// - https://swagger.io/specification/v2/#securityDefinitionsObject
type SecurityDefinitions struct {
Definitions orderedmap.Map[string, *SecurityScheme]
Definitions *orderedmap.Map[string, *SecurityScheme]
low *low.SecurityDefinitions
}
@@ -35,12 +36,8 @@ func NewSecurityDefinitions(definitions *low.SecurityDefinitions) *SecurityDefin
schemes.Set(value.key, value.result)
return nil
}
_ = orderedmap.TranslateMapParallel(definitions.Definitions, translateFunc, resultFunc)
_ = datamodel.TranslateMapParallel(definitions.Definitions, translateFunc, resultFunc)
// schemes := make(map[string]*SecurityScheme)
// for k := range definitions.Definitions {
// schemes[k.Value] = NewSecurityScheme(definitions.Definitions[k].Value)
// }
sd.Definitions = schemes
return sd
}

View File

@@ -6,6 +6,8 @@ package v2
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
// SecurityScheme is a high-level representation of a Swagger / OpenAPI 2 SecurityScheme object
@@ -24,7 +26,7 @@ type SecurityScheme struct {
AuthorizationUrl string
TokenUrl string
Scopes *Scopes
Extensions map[string]any
Extensions *orderedmap.Map[string, *yaml.Node]
low *low.SecurityScheme
}

View File

@@ -15,11 +15,12 @@ import (
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/high/base"
low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
// Swagger represents a high-level Swagger / OpenAPI 2 document. An instance of Swagger is the root of the specification.
type Swagger struct {
// Swagger is the version of Swagger / OpenAPI being used, extracted from the 'swagger: 2.x' definition.
Swagger string
@@ -90,7 +91,7 @@ type Swagger struct {
ExternalDocs *base.ExternalDoc
// Extensions contains all custom extensions defined for the top-level document.
Extensions map[string]any
Extensions *orderedmap.Map[string, *yaml.Node]
low *low.Swagger
}

View File

@@ -5,13 +5,12 @@ package v2
import (
"os"
"testing"
"github.com/pb33f/libopenapi/datamodel"
v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"testing"
)
var doc *v2.Swagger
@@ -42,8 +41,12 @@ func BenchmarkNewDocument(b *testing.B) {
func TestNewSwaggerDocument_Base(t *testing.T) {
initTest()
highDoc := NewSwaggerDocument(doc)
var xPet bool
_ = highDoc.Extensions.GetOrZero("x-pet").Decode(&xPet)
assert.Equal(t, "2.0", highDoc.Swagger)
assert.True(t, highDoc.Extensions["x-pet"].(bool))
assert.True(t, xPet)
assert.Equal(t, "petstore.swagger.io", highDoc.Host)
assert.Equal(t, "/v2", highDoc.BasePath)
assert.Len(t, highDoc.Schemes, 2)
@@ -56,7 +59,6 @@ func TestNewSwaggerDocument_Base(t *testing.T) {
wentLow := highDoc.GoLow()
assert.Equal(t, 16, wentLow.Host.ValueNode.Line)
assert.Equal(t, 7, wentLow.Host.ValueNode.Column)
}
func TestNewSwaggerDocument_Info(t *testing.T) {
@@ -82,11 +84,15 @@ func TestNewSwaggerDocument_Parameters(t *testing.T) {
initTest()
highDoc := NewSwaggerDocument(doc)
params := highDoc.Parameters
var xChicken string
_ = params.Definitions.GetOrZero("simpleParam").Extensions.GetOrZero("x-chicken").Decode(&xChicken)
assert.Equal(t, 1, orderedmap.Len(params.Definitions))
assert.Equal(t, "query", params.Definitions.GetOrZero("simpleParam").In)
assert.Equal(t, "simple", params.Definitions.GetOrZero("simpleParam").Name)
assert.Equal(t, "string", params.Definitions.GetOrZero("simpleParam").Type)
assert.Equal(t, "nuggets", params.Definitions.GetOrZero("simpleParam").Extensions["x-chicken"])
assert.Equal(t, "nuggets", xChicken)
wentLow := params.GoLow()
assert.Equal(t, 20, wentLow.FindParameter("simpleParam").ValueNode.Line)
@@ -95,7 +101,6 @@ func TestNewSwaggerDocument_Parameters(t *testing.T) {
wentLower := params.Definitions.GetOrZero("simpleParam").GoLow()
assert.Equal(t, 21, wentLower.Name.ValueNode.Line)
assert.Equal(t, 11, wentLower.Name.ValueNode.Column)
}
func TestNewSwaggerDocument_Security(t *testing.T) {
@@ -107,7 +112,6 @@ func TestNewSwaggerDocument_Security(t *testing.T) {
wentLow := highDoc.Security[0].GoLow()
assert.Equal(t, 25, wentLow.Requirements.ValueNode.Line)
assert.Equal(t, 5, wentLow.Requirements.ValueNode.Column)
}
func TestNewSwaggerDocument_Definitions_Security(t *testing.T) {
@@ -140,22 +144,32 @@ func TestNewSwaggerDocument_Definitions_Responses(t *testing.T) {
assert.Equal(t, 2, orderedmap.Len(highDoc.Responses.Definitions))
defs := highDoc.Responses.Definitions
assert.Equal(t, "morning", defs.GetOrZero("200").Extensions["x-coffee"])
var xCoffee string
_ = defs.GetOrZero("200").Extensions.GetOrZero("x-coffee").Decode(&xCoffee)
assert.Equal(t, "morning", xCoffee)
assert.Equal(t, "OK", defs.GetOrZero("200").Description)
assert.Equal(t, "a generic API response object",
defs.GetOrZero("200").Schema.Schema().Description)
assert.Equal(t, 3, orderedmap.Len(defs.GetOrZero("200").Examples.Values))
exp := defs.GetOrZero("200").Examples.Values.GetOrZero("application/json")
assert.Len(t, exp.(map[string]interface{}), 2)
assert.Equal(t, "two", exp.(map[string]interface{})["one"])
var appJson map[string]interface{}
_ = defs.GetOrZero("200").Examples.Values.GetOrZero("application/json").Decode(&appJson)
exp = defs.GetOrZero("200").Examples.Values.GetOrZero("text/xml")
assert.Len(t, exp.([]interface{}), 3)
assert.Equal(t, "two", exp.([]interface{})[1])
assert.Len(t, appJson, 2)
assert.Equal(t, "two", appJson["one"])
exp = defs.GetOrZero("200").Examples.Values.GetOrZero("text/plain")
assert.Equal(t, "something else.", exp)
var textXml []interface{}
_ = defs.GetOrZero("200").Examples.Values.GetOrZero("text/xml").Decode(&textXml)
assert.Len(t, textXml, 3)
assert.Equal(t, "two", textXml[1])
var textPlain string
_ = defs.GetOrZero("200").Examples.Values.GetOrZero("text/plain").Decode(&textPlain)
assert.Equal(t, "something else.", textPlain)
expWentLow := defs.GetOrZero("200").Examples.GoLow()
assert.Equal(t, 702, expWentLow.FindExample("application/json").ValueNode.Line)
@@ -168,10 +182,13 @@ func TestNewSwaggerDocument_Definitions_Responses(t *testing.T) {
assert.Len(t, y.Enum, 2)
x := y.Items
var def string
_ = x.Default.Decode(&def)
assert.Equal(t, "something", x.Format)
assert.Equal(t, "array", x.Type)
assert.Equal(t, "csv", x.CollectionFormat)
assert.Equal(t, "cake", x.Default)
assert.Equal(t, "cake", def)
assert.Equal(t, 10, x.Maximum)
assert.Equal(t, 1, x.Minimum)
assert.True(t, x.ExclusiveMaximum)
@@ -198,7 +215,6 @@ func TestNewSwaggerDocument_Definitions(t *testing.T) {
wentLow := highDoc.Definitions.GoLow()
assert.Equal(t, 848, wentLow.FindSchema("User").ValueNode.Line)
}
func TestNewSwaggerDocument_Paths(t *testing.T) {
@@ -207,7 +223,14 @@ func TestNewSwaggerDocument_Paths(t *testing.T) {
assert.Equal(t, 15, orderedmap.Len(highDoc.Paths.PathItems))
upload := highDoc.Paths.PathItems.GetOrZero("/pet/{petId}/uploadImage")
assert.Equal(t, "man", upload.Extensions["x-potato"])
var xPotato string
_ = upload.Extensions.GetOrZero("x-potato").Decode(&xPotato)
var paramEnum0 string
_ = upload.Post.Parameters[0].Enum[0].Decode(&paramEnum0)
assert.Equal(t, "man", xPotato)
assert.Nil(t, upload.Get)
assert.Nil(t, upload.Put)
assert.Nil(t, upload.Patch)
@@ -238,8 +261,11 @@ func TestNewSwaggerDocument_Paths(t *testing.T) {
assert.Equal(t, 20, *upload.Post.Parameters[0].MaxItems)
assert.True(t, *upload.Post.Parameters[0].UniqueItems)
assert.Len(t, upload.Post.Parameters[0].Enum, 2)
assert.Equal(t, "hello", upload.Post.Parameters[0].Enum[0])
def := upload.Post.Parameters[0].Default.(map[string]interface{})
assert.Equal(t, "hello", paramEnum0)
var def map[string]any
_ = upload.Post.Parameters[0].Default.Decode(&def)
assert.Equal(t, "here", def["something"])
assert.Equal(t, "https://pb33f.io", upload.Post.ExternalDocs.URL)
@@ -257,11 +283,9 @@ func TestNewSwaggerDocument_Paths(t *testing.T) {
wentLowest := upload.Post.GoLow()
assert.Equal(t, 55, wentLowest.Tags.KeyNode.Line)
}
func TestNewSwaggerDocument_Responses(t *testing.T) {
initTest()
highDoc := NewSwaggerDocument(doc)
upload := highDoc.Paths.PathItems.GetOrZero("/pet/{petId}/uploadImage").Post
@@ -278,5 +302,4 @@ func TestNewSwaggerDocument_Responses(t *testing.T) {
wentLower := OK.GoLow()
assert.Equal(t, 107, wentLower.Schema.KeyNode.Line)
assert.Equal(t, 11, wentLower.Schema.KeyNode.Column)
}

View File

@@ -21,8 +21,8 @@ import (
// that identifies a URL to use for the callback operation.
// - https://spec.openapis.org/oas/v3.1.0#callback-object
type Callback struct {
Expression orderedmap.Map[string, *PathItem] `json:"-" yaml:"-"`
Extensions map[string]any `json:"-" yaml:"-"`
Expression *orderedmap.Map[string, *PathItem] `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Callback
}
@@ -31,13 +31,11 @@ func NewCallback(lowCallback *low.Callback) *Callback {
n := new(Callback)
n.low = lowCallback
n.Expression = orderedmap.New[string, *PathItem]()
for pair := orderedmap.First(lowCallback.Expression.Value); pair != nil; pair = pair.Next() {
for pair := orderedmap.First(lowCallback.Expression); pair != nil; pair = pair.Next() {
n.Expression.Set(pair.Key().Value, NewPathItem(pair.Value().Value))
}
n.Extensions = make(map[string]any)
for k, v := range lowCallback.Extensions {
n.Extensions[k.Value] = v.Value
}
n.Extensions = high.ExtractExtensions(lowCallback.Extensions)
return n
}
@@ -51,36 +49,50 @@ func (c *Callback) GoLowUntyped() any {
return c.low
}
// Render will return a YAML representation of the Callback object as a byte slice.
// Render will return a YAML representation of the Paths object as a byte slice.
func (c *Callback) Render() ([]byte, error) {
return yaml.Marshal(c)
}
// MarshalYAML will create a ready to render YAML representation of the Callback object.
func (c *Callback) RenderInline() ([]byte, error) {
d, _ := c.MarshalYAMLInline()
return yaml.Marshal(d)
}
// MarshalYAML will create a ready to render YAML representation of the Paths object.
func (c *Callback) MarshalYAML() (interface{}, error) {
// map keys correctly.
m := utils.CreateEmptyMapNode()
type cbItem struct {
cb *PathItem
exp string
type pathItem struct {
pi *PathItem
path string
line int
ext *yaml.Node
style yaml.Style
rendered *yaml.Node
}
var mapped []*cbItem
var mapped []*pathItem
for pair := orderedmap.First(c.Expression); pair != nil; pair = pair.Next() {
ln := 999 // default to a high value to weight new content to the bottom.
k := pair.Key()
pi := pair.Value()
ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if c.low != nil {
for lPair := orderedmap.First(c.low.Expression.Value); lPair != nil; lPair = lPair.Next() {
if lPair.Key().Value == pair.Key() {
ln = lPair.Key().KeyNode.Line
}
}
}
mapped = append(mapped, &cbItem{pair.Value(), pair.Key(), ln, nil})
lpi := c.low.FindExpression(k)
if lpi != nil {
ln = lpi.ValueNode.Line
}
for pair := orderedmap.First(c.low.Expression); pair != nil; pair = pair.Next() {
if pair.Key().Value == k {
style = pair.Key().KeyNode.Style
break
}
}
}
mapped = append(mapped, &pathItem{pi, k, ln, style, nil})
}
// extract extensions
nb := high.NewNodeBuilder(c, c.low)
extNode := nb.Render()
if extNode != nil && extNode.Content != nil {
@@ -90,23 +102,101 @@ func (c *Callback) MarshalYAML() (interface{}, error) {
label = extNode.Content[u].Value
continue
}
mapped = append(mapped, &cbItem{nil, label,
extNode.Content[u].Line, extNode.Content[u]})
mapped = append(mapped, &pathItem{
nil, label,
extNode.Content[u].Line, 0, extNode.Content[u],
})
}
}
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for j := range mapped {
if mapped[j].cb != nil {
rendered, _ := mapped[j].cb.MarshalYAML()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].exp))
for _, mp := range mapped {
if mp.pi != nil {
rendered, _ := mp.pi.MarshalYAML()
kn := utils.CreateStringNode(mp.path)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mapped[j].ext != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].exp))
m.Content = append(m.Content, mapped[j].ext)
if mp.rendered != nil {
m.Content = append(m.Content, utils.CreateStringNode(mp.path))
m.Content = append(m.Content, mp.rendered)
}
}
return m, nil
}
func (c *Callback) MarshalYAMLInline() (interface{}, error) {
// map keys correctly.
m := utils.CreateEmptyMapNode()
type pathItem struct {
pi *PathItem
path string
line int
style yaml.Style
rendered *yaml.Node
}
var mapped []*pathItem
for pair := orderedmap.First(c.Expression); pair != nil; pair = pair.Next() {
k := pair.Key()
pi := pair.Value()
ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if c.low != nil {
lpi := c.low.FindExpression(k)
if lpi != nil {
ln = lpi.ValueNode.Line
}
for pair := orderedmap.First(c.low.Expression); pair != nil; pair = pair.Next() {
if pair.Key().Value == k {
style = pair.Key().KeyNode.Style
break
}
}
}
mapped = append(mapped, &pathItem{pi, k, ln, style, nil})
}
nb := high.NewNodeBuilder(c, c.low)
nb.Resolve = true
extNode := nb.Render()
if extNode != nil && extNode.Content != nil {
var label string
for u := range extNode.Content {
if u%2 == 0 {
label = extNode.Content[u].Value
continue
}
mapped = append(mapped, &pathItem{
nil, label,
extNode.Content[u].Line, 0, extNode.Content[u],
})
}
}
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for _, mp := range mapped {
if mp.pi != nil {
rendered, _ := mp.pi.MarshalYAMLInline()
kn := utils.CreateStringNode(mp.path)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mp.rendered != nil {
m.Content = append(m.Content, utils.CreateStringNode(mp.path))
m.Content = append(m.Content, mp.rendered)
}
}

View File

@@ -12,11 +12,14 @@ import (
v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestCallback_MarshalYAML(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("why not?"))
cb := &Callback{
Expression: orderedmap.ToOrderedMap(map[string]*PathItem{
@@ -31,9 +34,7 @@ func TestCallback_MarshalYAML(t *testing.T) {
},
},
}),
Extensions: map[string]any{
"x-burgers": "why not?",
},
Extensions: ext,
}
rend, _ := cb.Render()
@@ -43,7 +44,10 @@ func TestCallback_MarshalYAML(t *testing.T) {
// mutate
cb.Expression.GetOrZero("https://pb33f.io").Get.OperationId = "blim-blam"
cb.Extensions = map[string]interface{}{"x-burgers": "yes please!"}
ext = orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("yes please!"))
cb.Extensions = ext
rend, _ = cb.Render()
// there is no way to determine order in brand new maps, so we have to check length.
@@ -72,7 +76,10 @@ func TestCallback_MarshalYAML(t *testing.T) {
r := NewCallback(&n)
assert.Equal(t, "please", r.Extensions["x-break-everything"])
var xBreakEverything string
_ = r.Extensions.GetOrZero("x-break-everything").Decode(&xBreakEverything)
assert.Equal(t, "please", xBreakEverything)
rend, _ = r.Render()
assert.Equal(t, k, strings.TrimSpace(string(rend)))

View File

@@ -22,16 +22,16 @@ import (
// will have no effect on the API unless they are explicitly referenced from properties outside the components object.
// - https://spec.openapis.org/oas/v3.1.0#components-object
type Components struct {
Schemas orderedmap.Map[string, *highbase.SchemaProxy] `json:"schemas,omitempty" yaml:"schemas,omitempty"`
Responses orderedmap.Map[string, *Response] `json:"responses,omitempty" yaml:"responses,omitempty"`
Parameters orderedmap.Map[string, *Parameter] `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Examples orderedmap.Map[string, *highbase.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
RequestBodies orderedmap.Map[string, *RequestBody] `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
Headers orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"`
SecuritySchemes orderedmap.Map[string, *SecurityScheme] `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
Links orderedmap.Map[string, *Link] `json:"links,omitempty" yaml:"links,omitempty"`
Callbacks orderedmap.Map[string, *Callback] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Schemas *orderedmap.Map[string, *highbase.SchemaProxy] `json:"schemas,omitempty" yaml:"schemas,omitempty"`
Responses *orderedmap.Map[string, *Response] `json:"responses,omitempty" yaml:"responses,omitempty"`
Parameters *orderedmap.Map[string, *Parameter] `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Examples *orderedmap.Map[string, *highbase.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
RequestBodies *orderedmap.Map[string, *RequestBody] `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
Headers *orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"`
SecuritySchemes *orderedmap.Map[string, *SecurityScheme] `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
Links *orderedmap.Map[string, *Link] `json:"links,omitempty" yaml:"links,omitempty"`
Callbacks *orderedmap.Map[string, *Callback] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Components
}
@@ -41,7 +41,7 @@ type Components struct {
func NewComponents(comp *low.Components) *Components {
c := new(Components)
c.low = comp
if len(comp.Extensions) > 0 {
if orderedmap.Len(comp.Extensions) > 0 {
c.Extensions = high.ExtractExtensions(comp.Extensions)
}
cbMap := orderedmap.New[string, *Callback]()
@@ -114,7 +114,7 @@ type componentResult[T any] struct {
}
// buildComponent builds component structs from low level structs.
func buildComponent[IN any, OUT any](inMap orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[IN]], outMap orderedmap.Map[string, OUT], translateItem func(IN) OUT) {
func buildComponent[IN any, OUT any](inMap *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[IN]], outMap *orderedmap.Map[string, OUT], translateItem func(IN) OUT) {
translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[IN]]) (componentResult[OUT], error) {
return componentResult[OUT]{key: pair.Key().Value, res: translateItem(pair.Value().Value)}, nil
}
@@ -126,7 +126,7 @@ func buildComponent[IN any, OUT any](inMap orderedmap.Map[lowmodel.KeyReference[
}
// buildSchema builds a schema from low level structs.
func buildSchema(inMap orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*base.SchemaProxy]], outMap orderedmap.Map[string, *highbase.SchemaProxy]) {
func buildSchema(inMap *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*base.SchemaProxy]], outMap *orderedmap.Map[string, *highbase.SchemaProxy]) {
translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*base.SchemaProxy]]) (componentResult[*highbase.SchemaProxy], error) {
value := pair.Value()
var sch *highbase.SchemaProxy

View File

@@ -23,7 +23,6 @@ import (
// Document represents a high-level OpenAPI 3 document (both 3.0 & 3.1). A Document is the root of the specification.
type Document struct {
// Version is the version of OpenAPI being used, extracted from the 'openapi: x.x.x' definition.
// This is not a standard property of the OpenAPI model, it's a convenience mechanism only.
Version string `json:"openapi,omitempty" yaml:"openapi,omitempty"`
@@ -54,7 +53,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:"-"`
// 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
@@ -69,7 +68,7 @@ type Document struct {
ExternalDocs *base.ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
// Extensions contains all custom extensions defined for the top-level document.
Extensions map[string]any `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
// JsonSchemaDialect is a 3.1+ property that sets the dialect to use for validating *base.Schema definitions
// The default value for the $schema keyword within Schema Objects contained within this OAS document.
@@ -83,7 +82,7 @@ type Document struct {
// for example by an out-of-band registration. The key name is a unique string to refer to each webhook,
// while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider
// and the expected responses. An example is available.
Webhooks orderedmap.Map[string, *PathItem] `json:"webhooks,omitempty" yaml:"webhooks,omitempty"`
Webhooks *orderedmap.Map[string, *PathItem] `json:"webhooks,omitempty" yaml:"webhooks,omitempty"`
// Index is a reference to the *index.SpecIndex that was created for the document and used
// as a guide when building out the Document. Ideal if further processing is required on the model and
@@ -122,7 +121,7 @@ func NewDocument(document *low.Document) *Document {
if !document.ExternalDocs.IsEmpty() {
d.ExternalDocs = base.NewExternalDoc(document.ExternalDocs.Value)
}
if len(document.Extensions) > 0 {
if orderedmap.Len(document.Extensions) > 0 {
d.Extensions = high.ExtractExtensions(document.Extensions)
}
if !document.Components.IsEmpty() {

View File

@@ -51,7 +51,11 @@ func BenchmarkNewDocument(b *testing.B) {
func TestNewDocument_Extensions(t *testing.T) {
initTest()
h := NewDocument(lowDoc)
assert.Equal(t, "darkside", h.Extensions["x-something-something"])
var xSomethingSomething string
_ = h.Extensions.GetOrZero("x-something-something").Decode(&xSomethingSomething)
assert.Equal(t, "darkside", xSomethingSomething)
}
func TestNewDocument_ExternalDocs(t *testing.T) {
@@ -133,15 +137,28 @@ func TestNewDocument_Servers(t *testing.T) {
func TestNewDocument_Tags(t *testing.T) {
initTest()
h := NewDocument(lowDoc)
var xInternalTing string
_ = h.Tags[0].Extensions.GetOrZero("x-internal-ting").Decode(&xInternalTing)
var xInternalTong int64
_ = h.Tags[0].Extensions.GetOrZero("x-internal-tong").Decode(&xInternalTong)
var xInternalTang float64
_ = h.Tags[0].Extensions.GetOrZero("x-internal-tang").Decode(&xInternalTang)
assert.Len(t, h.Tags, 2)
assert.Equal(t, "Burgers", h.Tags[0].Name)
assert.Equal(t, "All kinds of yummy burgers.", h.Tags[0].Description)
assert.Equal(t, "Find out more", h.Tags[0].ExternalDocs.Description)
assert.Equal(t, "https://pb33f.io", h.Tags[0].ExternalDocs.URL)
assert.Equal(t, "somethingSpecial", h.Tags[0].Extensions["x-internal-ting"])
assert.Equal(t, int64(1), h.Tags[0].Extensions["x-internal-tong"])
assert.Equal(t, 1.2, h.Tags[0].Extensions["x-internal-tang"])
assert.True(t, h.Tags[0].Extensions["x-internal-tung"].(bool))
assert.Equal(t, "somethingSpecial", xInternalTing)
assert.Equal(t, int64(1), xInternalTong)
assert.Equal(t, 1.2, xInternalTang)
var tung bool
_ = h.Tags[0].Extensions.GetOrZero("x-internal-tung").Decode(&tung)
assert.True(t, tung)
wentLow := h.Tags[1].GoLow()
assert.Equal(t, 39, wentLow.Description.KeyNode.Line)
@@ -191,7 +208,10 @@ func TestNewDocument_Components_Callbacks(t *testing.T) {
h.Components.Callbacks.GetOrZero("BurgerCallback").GoLow().FindExpression("{$request.query.queryUrl}").ValueNode.Column,
)
assert.Equal(t, "please", h.Components.Callbacks.GetOrZero("BurgerCallback").Extensions["x-break-everything"])
var xBreakEverything string
_ = h.Components.Callbacks.GetOrZero("BurgerCallback").Extensions.GetOrZero("x-break-everything").Decode(&xBreakEverything)
assert.Equal(t, "please", xBreakEverything)
for pair := orderedmap.First(h.Components.GoLow().Callbacks.Value); pair != nil; pair = pair.Next() {
if pair.Key().Value == "BurgerCallback" {
@@ -209,7 +229,9 @@ func TestNewDocument_Components_Schemas(t *testing.T) {
goLow := h.Components.GoLow()
a := h.Components.Schemas.GetOrZero("Error")
abcd := a.Schema().Properties.GetOrZero("message").Schema().Example
var abcd string
_ = a.Schema().Properties.GetOrZero("message").Schema().Example.Decode(&abcd)
assert.Equal(t, "No such burger as 'Big-Whopper'", abcd)
assert.Equal(t, 433, goLow.Schemas.KeyNode.Line)
assert.Equal(t, 3, goLow.Schemas.KeyNode.Column)
@@ -218,13 +240,21 @@ func TestNewDocument_Components_Schemas(t *testing.T) {
b := h.Components.Schemas.GetOrZero("Burger")
assert.Len(t, b.Schema().Required, 2)
assert.Equal(t, "golden slices of happy fun joy", b.Schema().Properties.GetOrZero("fries").Schema().Description)
assert.Equal(t, int64(2), b.Schema().Properties.GetOrZero("numPatties").Schema().Example)
var numPattiesExample int64
_ = b.Schema().Properties.GetOrZero("numPatties").Schema().Example.Decode(&numPattiesExample)
assert.Equal(t, int64(2), numPattiesExample)
assert.Equal(t, 448, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Line)
assert.Equal(t, 7, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Column)
assert.Equal(t, 450, b.Schema().GoLow().FindProperty("name").ValueNode.Line)
f := h.Components.Schemas.GetOrZero("Fries")
assert.Equal(t, "salt", f.Schema().Properties.GetOrZero("seasoning").Schema().Items.A.Schema().Example)
var seasoningExample string
_ = f.Schema().Properties.GetOrZero("seasoning").Schema().Items.A.Schema().Example.Decode(&seasoningExample)
assert.Equal(t, "salt", seasoningExample)
assert.Len(t, f.Schema().Properties.GetOrZero("favoriteDrink").Schema().Properties.GetOrZero("drinkType").Schema().Enum, 1)
d := h.Components.Schemas.GetOrZero("Drink")
@@ -240,7 +270,11 @@ func TestNewDocument_Components_Schemas(t *testing.T) {
assert.Equal(t, 523, pl.Schema().XML.GoLow().Name.ValueNode.Line)
ext := h.Components.Extensions
assert.Equal(t, "loud", ext["x-screaming-baby"])
var xScreamingBaby string
_ = ext.GetOrZero("x-screaming-baby").Decode(&xScreamingBaby)
assert.Equal(t, "loud", xScreamingBaby)
}
func TestNewDocument_Components_Headers(t *testing.T) {
@@ -319,8 +353,12 @@ func TestNewDocument_Components_Parameters(t *testing.T) {
assert.Equal(t, "burgerHeader", bh.Name)
assert.Equal(t, 392, bh.GoLow().Name.KeyNode.Line)
assert.Equal(t, 2, orderedmap.Len(bh.Schema.Schema().Properties))
assert.Equal(t, "big-mac", bh.Example)
assert.True(t, bh.Required)
var example string
_ = bh.Example.Decode(&example)
assert.Equal(t, "big-mac", example)
assert.True(t, *bh.Required)
assert.Equal(
t,
"this is a header",
@@ -341,8 +379,12 @@ func TestNewDocument_Paths(t *testing.T) {
func testBurgerShop(t *testing.T, h *Document, checkLines bool) {
burgersOp := h.Paths.PathItems.GetOrZero("/burgers")
assert.Len(t, burgersOp.GetOperations(), 1)
assert.Equal(t, "meaty", burgersOp.Extensions["x-burger-meta"])
assert.Equal(t, 1, burgersOp.GetOperations().Len())
var xBurgerMeta string
_ = burgersOp.Extensions.GetOrZero("x-burger-meta").Decode(&xBurgerMeta)
assert.Equal(t, "meaty", xBurgerMeta)
assert.Nil(t, burgersOp.Get)
assert.Nil(t, burgersOp.Put)
assert.Nil(t, burgersOp.Patch)

View File

@@ -15,7 +15,7 @@ import (
// - https://spec.openapis.org/oas/v3.1.0#encoding-object
type Encoding struct {
ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"`
Headers orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"`
Headers *orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
@@ -28,7 +28,9 @@ func NewEncoding(encoding *low.Encoding) *Encoding {
e.low = encoding
e.ContentType = encoding.ContentType.Value
e.Style = encoding.Style.Value
if !encoding.Explode.IsEmpty() {
e.Explode = &encoding.Explode.Value
}
e.AllowReserved = encoding.AllowReserved.Value
e.Headers = ExtractHeaders(encoding.Headers.Value)
return e
@@ -56,7 +58,7 @@ func (e *Encoding) MarshalYAML() (interface{}, error) {
}
// ExtractEncoding converts hard to navigate low-level plumbing Encoding definitions, into a high-level simple map
func ExtractEncoding(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Encoding]]) orderedmap.Map[string, *Encoding] {
func ExtractEncoding(elements *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Encoding]]) *orderedmap.Map[string, *Encoding] {
extracted := orderedmap.New[string, *Encoding]()
for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() {
extracted.Set(pair.Key().Value, NewEncoding(pair.Value().Value))

View File

@@ -24,10 +24,10 @@ type Header struct {
Explode bool `json:"explode,omitempty" yaml:"explode,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
Schema *highbase.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Examples orderedmap.Map[string, *highbase.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Example *yaml.Node `json:"example,omitempty" yaml:"example,omitempty"`
Examples *orderedmap.Map[string, *highbase.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
Content *orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Header
}
@@ -52,6 +52,7 @@ func NewHeader(header *low.Header) *Header {
h.Content = ExtractContent(header.Content.Value)
h.Example = header.Example.Value
h.Examples = highbase.ExtractExamples(header.Examples.Value)
h.Extensions = high.ExtractExtensions(header.Extensions)
return h
}
@@ -66,7 +67,7 @@ func (h *Header) GoLowUntyped() any {
}
// ExtractHeaders will extract a hard to navigate low-level Header map, into simple high-level one.
func ExtractHeaders(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Header]]) orderedmap.Map[string, *Header] {
func ExtractHeaders(elements *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Header]]) *orderedmap.Map[string, *Header] {
extracted := orderedmap.New[string, *Header]()
for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() {
extracted.Set(pair.Key().Value, NewHeader(pair.Value().Value))

View File

@@ -9,10 +9,14 @@ import (
"github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestHeader_MarshalYAML(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("why not?"))
header := &Header{
Description: "A header",
@@ -22,11 +26,11 @@ func TestHeader_MarshalYAML(t *testing.T) {
Style: "simple",
Explode: true,
AllowReserved: true,
Example: "example",
Example: utils.CreateStringNode("example"),
Examples: orderedmap.ToOrderedMap(map[string]*base.Example{
"example": {Value: "example"},
"example": {Value: utils.CreateStringNode("example")},
}),
Extensions: map[string]interface{}{"x-burgers": "why not?"},
Extensions: ext,
}
rend, _ := header.Render()
@@ -45,5 +49,4 @@ examples:
x-burgers: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
}

View File

@@ -25,11 +25,11 @@ import (
type Link struct {
OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
OperationId string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Parameters orderedmap.Map[string, string] `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Parameters *orderedmap.Map[string, string] `json:"parameters,omitempty" yaml:"parameters,omitempty"`
RequestBody string `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Server *Server `json:"server,omitempty" yaml:"server,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Link
}

View File

@@ -4,6 +4,7 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/high/base"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
@@ -19,9 +20,9 @@ import (
type MediaType struct {
Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Examples orderedmap.Map[string, *base.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
Encoding orderedmap.Map[string, *Encoding] `json:"encoding,omitempty" yaml:"encoding,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Examples *orderedmap.Map[string, *base.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
Encoding *orderedmap.Map[string, *Encoding] `json:"encoding,omitempty" yaml:"encoding,omitempty"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.MediaType
}
@@ -73,7 +74,7 @@ func (m *MediaType) MarshalYAMLInline() (interface{}, error) {
// ExtractContent takes in a complex and hard to navigate low-level content map, and converts it in to a much simpler
// and easier to navigate high-level one.
func ExtractContent(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.MediaType]]) orderedmap.Map[string, *MediaType] {
func ExtractContent(elements *orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.MediaType]]) *orderedmap.Map[string, *MediaType] {
extracted := orderedmap.New[string, *MediaType]()
translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.MediaType]]) (asyncResult[*MediaType], error) {
return asyncResult[*MediaType]{
@@ -85,6 +86,6 @@ func ExtractContent(elements orderedmap.Map[lowmodel.KeyReference[string], lowmo
extracted.Set(value.key, value.result)
return nil
}
_ = orderedmap.TranslateMapParallel(elements, translateFunc, resultFunc)
_ = datamodel.TranslateMapParallel(elements, translateFunc, resultFunc)
return extracted
}

View File

@@ -35,8 +35,7 @@ func TestMediaType_MarshalYAMLInline(t *testing.T) {
yml, _ := mt.Render()
// the rendered output should be a ref to the media type.
op := `schema:
$ref: '#/components/schemas/Pet'`
op := `schema: {"$ref": "#/components/schemas/Pet"}`
assert.Equal(t, op, strings.TrimSpace(string(yml)))
@@ -45,30 +44,30 @@ func TestMediaType_MarshalYAMLInline(t *testing.T) {
op = `schema:
required:
- name
- photoUrls
- "name"
- "photoUrls"
type: "object"
properties:
id:
"id":
type: "integer"
format: "int64"
example: 10
name:
"name":
type: "string"
example: "doggie"
category:
"category":
type: "object"
properties:
id:
"id":
type: "integer"
format: "int64"
example: 1
name:
"name":
type: "string"
example: "Dogs"
xml:
name: "category"
photoUrls:
"photoUrls":
type: "array"
xml:
wrapped: true
@@ -76,27 +75,27 @@ func TestMediaType_MarshalYAMLInline(t *testing.T) {
type: "string"
xml:
name: "photoUrl"
tags:
"tags":
type: "array"
xml:
wrapped: true
items:
type: "object"
properties:
id:
"id":
type: "integer"
format: "int64"
name:
"name":
type: "string"
xml:
name: "tag"
status:
"status":
type: "string"
description: "pet status in the store"
enum:
- available
- pending
- sold
- "available"
- "pending"
- "sold"
xml:
name: "pet"
example: testing a nice mutation`
@@ -104,7 +103,6 @@ example: testing a nice mutation`
yml, _ = mt.RenderInline()
assert.Equal(t, op, strings.TrimSpace(string(yml)))
}
func TestMediaType_MarshalYAML(t *testing.T) {
@@ -125,22 +123,19 @@ func TestMediaType_MarshalYAML(t *testing.T) {
yml, _ := mt.Render()
// the rendered output should be a ref to the media type.
op := `schema:
$ref: '#/components/schemas/Pet'`
op := `schema: {"$ref": "#/components/schemas/Pet"}`
assert.Equal(t, op, strings.TrimSpace(string(yml)))
// modify the media type to have an example
mt.Example = "testing a nice mutation"
op = `schema:
$ref: '#/components/schemas/Pet'
op = `schema: {"$ref": "#/components/schemas/Pet"}
example: testing a nice mutation`
yml, _ = mt.Render()
assert.Equal(t, op, strings.TrimSpace(string(yml)))
}
func TestMediaType_Examples(t *testing.T) {

View File

@@ -16,8 +16,8 @@ type OAuthFlow struct {
AuthorizationUrl string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenUrl string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
RefreshUrl string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"`
Scopes orderedmap.Map[string, string] `json:"scopes,omitempty" yaml:"scopes,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Scopes *orderedmap.Map[string, string] `json:"scopes,omitempty" yaml:"scopes,omitempty"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.OAuthFlow
}
@@ -29,7 +29,7 @@ func NewOAuthFlow(flow *low.OAuthFlow) *OAuthFlow {
o.AuthorizationUrl = flow.AuthorizationUrl.Value
o.RefreshUrl = flow.RefreshUrl.Value
scopes := orderedmap.New[string, string]()
for pair := flow.Scopes.Value.First(); pair != nil; pair = pair.Next() {
for pair := orderedmap.First(flow.Scopes.Value); pair != nil; pair = pair.Next() {
scopes.Set(pair.Key().Value, pair.Value().Value)
}
o.Scopes = scopes

View File

@@ -8,7 +8,9 @@ import (
"testing"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestOAuthFlow_MarshalYAML(t *testing.T) {
@@ -37,7 +39,9 @@ scopes:
// mutate
oflow.Scopes = nil
oflow.Extensions = map[string]interface{}{"x-burgers": "why not?"}
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("why not?"))
oflow.Extensions = ext
desired = `authorizationUrl: https://pb33f.io
tokenUrl: https://pb33f.io/token

View File

@@ -6,6 +6,7 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
@@ -16,7 +17,7 @@ type OAuthFlows struct {
Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"`
AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty" yaml:"authorizationCode,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.OAuthFlows
}

View File

@@ -25,11 +25,11 @@ type Operation struct {
Parameters []*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
RequestBody *RequestBody `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
Responses *Responses `json:"responses,omitempty" yaml:"responses,omitempty"`
Callbacks orderedmap.Map[string, *Callback] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Callbacks *orderedmap.Map[string, *Callback] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Security []*base.SecurityRequirement `json:"security,omitempty" yaml:"security,omitempty"`
Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Operation
}
@@ -45,7 +45,9 @@ func NewOperation(operation *low.Operation) *Operation {
}
o.Tags = tags
o.Summary = operation.Summary.Value
if !operation.Deprecated.IsEmpty() {
o.Deprecated = &operation.Deprecated.Value
}
o.Description = operation.Description.Value
if !operation.ExternalDocs.IsEmpty() {
o.ExternalDocs = base.NewExternalDoc(operation.ExternalDocs.Value)

View File

@@ -50,13 +50,13 @@ callbacks:
assert.Equal(t, "https://pb33f.io", r.ExternalDocs.URL)
assert.Equal(t, 1, r.GoLow().ExternalDocs.KeyNode.Line)
assert.Contains(t, r.Callbacks, "testCallback")
assert.Contains(t, r.Callbacks.GetOrZero("testCallback").Expression, "{$request.body#/callbackUrl}")
assert.NotNil(t, r.Callbacks.GetOrZero("testCallback"))
assert.NotNil(t, r.Callbacks.GetOrZero("testCallback").Expression.GetOrZero("{$request.body#/callbackUrl}"))
assert.Equal(t, 3, r.GoLow().Callbacks.KeyNode.Line)
}
func TestOperation_MarshalYAML(t *testing.T) {
op := &Operation{
Tags: []string{"test"},
Summary: "nice",
@@ -90,11 +90,9 @@ requestBody:
description: dice`
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
}
func TestOperation_MarshalYAMLInline(t *testing.T) {
op := &Operation{
Tags: []string{"test"},
Summary: "nice",
@@ -128,7 +126,6 @@ requestBody:
description: dice`
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
}
func TestOperation_EmptySecurity(t *testing.T) {
@@ -147,7 +144,6 @@ security: []`
assert.NotNil(t, r.Security)
assert.Len(t, r.Security, 0)
}
func TestOperation_NoSecurity(t *testing.T) {
@@ -164,5 +160,4 @@ func TestOperation_NoSecurity(t *testing.T) {
r := NewOperation(&n)
assert.Nil(t, r.Security)
}

View File

@@ -19,17 +19,17 @@ type Parameter struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Required *bool `json:"required,renderZero,omitempty" yaml:"required,renderZero,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
Explode *bool `json:"explode,renderZero,omitempty" yaml:"explode,renderZero,omitempty"`
AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Examples orderedmap.Map[string, *base.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Example *yaml.Node `json:"example,omitempty" yaml:"example,omitempty"`
Examples *orderedmap.Map[string, *base.Example] `json:"examples,omitempty" yaml:"examples,omitempty"`
Content *orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Parameter
}
@@ -50,7 +50,9 @@ func NewParameter(param *low.Parameter) *Parameter {
if !param.Schema.IsEmpty() {
p.Schema = base.NewSchemaProxy(&param.Schema)
}
p.Required = param.Required.Value
if !param.Required.IsEmpty() {
p.Required = &param.Required.Value
}
p.Example = param.Example.Value
p.Examples = base.ExtractExamples(param.Examples.Value)
p.Content = ExtractContent(param.Content.Value)

View File

@@ -9,10 +9,14 @@ import (
"github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestParameter_MarshalYAML(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("why not?"))
explode := true
param := Parameter{
@@ -23,11 +27,11 @@ func TestParameter_MarshalYAML(t *testing.T) {
Style: "simple",
Explode: &explode,
AllowReserved: true,
Example: "example",
Example: utils.CreateStringNode("example"),
Examples: orderedmap.ToOrderedMap(map[string]*base.Example{
"example": {Value: "example"},
"example": {Value: utils.CreateStringNode("example")},
}),
Extensions: map[string]interface{}{"x-burgers": "why not?"},
Extensions: ext,
}
rend, _ := param.Render()
@@ -49,6 +53,8 @@ x-burgers: why not?`
}
func TestParameter_MarshalYAMLInline(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-burgers", utils.CreateStringNode("why not?"))
explode := true
param := Parameter{
@@ -59,11 +65,11 @@ func TestParameter_MarshalYAMLInline(t *testing.T) {
Style: "simple",
Explode: &explode,
AllowReserved: true,
Example: "example",
Example: utils.CreateStringNode("example"),
Examples: orderedmap.ToOrderedMap(map[string]*base.Example{
"example": {Value: "example"},
"example": {Value: utils.CreateStringNode("example")},
}),
Extensions: map[string]interface{}{"x-burgers": "why not?"},
Extensions: ext,
}
rend, _ := param.RenderInline()
@@ -85,7 +91,6 @@ x-burgers: why not?`
}
func TestParameter_IsExploded(t *testing.T) {
explode := true
param := Parameter{
Explode: &explode,
@@ -106,7 +111,6 @@ func TestParameter_IsExploded(t *testing.T) {
}
func TestParameter_IsDefaultFormEncoding(t *testing.T) {
param := Parameter{}
assert.True(t, param.IsDefaultFormEncoding())
@@ -133,7 +137,6 @@ func TestParameter_IsDefaultFormEncoding(t *testing.T) {
}
func TestParameter_IsDefaultHeaderEncoding(t *testing.T) {
param := Parameter{}
assert.True(t, param.IsDefaultHeaderEncoding())
@@ -163,8 +166,6 @@ func TestParameter_IsDefaultHeaderEncoding(t *testing.T) {
}
func TestParameter_IsDefaultPathEncoding(t *testing.T) {
param := Parameter{}
assert.True(t, param.IsDefaultPathEncoding())
}

View File

@@ -4,8 +4,13 @@
package v3
import (
"reflect"
"slices"
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/datamodel/low"
lowV3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
@@ -39,12 +44,12 @@ type PathItem struct {
Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"`
Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"`
Parameters []*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
low *low.PathItem
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *lowV3.PathItem
}
// NewPathItem creates a new high-level PathItem instance from a low-level one.
func NewPathItem(pathItem *low.PathItem) *PathItem {
func NewPathItem(pathItem *lowV3.PathItem) *PathItem {
pi := new(PathItem)
pi.low = pathItem
pi.Description = pathItem.Description.Value
@@ -62,7 +67,7 @@ func NewPathItem(pathItem *low.PathItem) *PathItem {
op *Operation
}
opChan := make(chan opResult)
var buildOperation = func(method int, op *low.Operation, c chan opResult) {
buildOperation := func(method int, op *lowV3.Operation, c chan opResult) {
if op == nil {
c <- opResult{method: method, op: nil}
return
@@ -120,7 +125,7 @@ func NewPathItem(pathItem *low.PathItem) *PathItem {
}
// GoLow returns the low level instance of PathItem, used to build the high-level one.
func (p *PathItem) GoLow() *low.PathItem {
func (p *PathItem) GoLow() *lowV3.PathItem {
return p.low
}
@@ -129,32 +134,65 @@ func (p *PathItem) GoLowUntyped() any {
return p.low
}
func (p *PathItem) GetOperations() map[string]*Operation {
o := make(map[string]*Operation)
func (p *PathItem) GetOperations() *orderedmap.Map[string, *Operation] {
o := orderedmap.New[string, *Operation]()
// TODO: this is a bit of a hack, but it works for now. We might just want to actually pull the data out of the document as a map and split it into the individual operations
type op struct {
name string
op *Operation
line int
}
getLine := func(field string, idx int) int {
if p.GoLow() == nil {
return idx
}
l, ok := reflect.ValueOf(p.GoLow()).Elem().FieldByName(field).Interface().(low.NodeReference[*lowV3.Operation])
if !ok || l.GetKeyNode() == nil {
return idx
}
return l.GetKeyNode().Line
}
ops := []op{}
if p.Get != nil {
o[low.GetLabel] = p.Get
ops = append(ops, op{name: lowV3.GetLabel, op: p.Get, line: getLine("Get", -8)})
}
if p.Put != nil {
o[low.PutLabel] = p.Put
ops = append(ops, op{name: lowV3.PutLabel, op: p.Put, line: getLine("Put", -7)})
}
if p.Post != nil {
o[low.PostLabel] = p.Post
ops = append(ops, op{name: lowV3.PostLabel, op: p.Post, line: getLine("Post", -6)})
}
if p.Delete != nil {
o[low.DeleteLabel] = p.Delete
ops = append(ops, op{name: lowV3.DeleteLabel, op: p.Delete, line: getLine("Delete", -5)})
}
if p.Options != nil {
o[low.OptionsLabel] = p.Options
ops = append(ops, op{name: lowV3.OptionsLabel, op: p.Options, line: getLine("Options", -4)})
}
if p.Head != nil {
o[low.HeadLabel] = p.Head
ops = append(ops, op{name: lowV3.HeadLabel, op: p.Head, line: getLine("Head", -3)})
}
if p.Patch != nil {
o[low.PatchLabel] = p.Patch
ops = append(ops, op{name: lowV3.PatchLabel, op: p.Patch, line: getLine("Patch", -2)})
}
if p.Trace != nil {
o[low.TraceLabel] = p.Trace
ops = append(ops, op{name: lowV3.TraceLabel, op: p.Trace, line: getLine("Trace", -1)})
}
slices.SortStableFunc(ops, func(a op, b op) int {
return a.line - b.line
})
for _, op := range ops {
o.Set(op.name, op.op)
}
return o
}

View File

@@ -67,11 +67,19 @@ trace:
r := NewPathItem(&n)
assert.Len(t, r.GetOperations(), 8)
assert.Equal(t, 8, r.GetOperations().Len())
// test that the operations are in the correct order
expectedOrder := []string{"get", "put", "post", "patch", "delete", "head", "options", "trace"}
i := 0
for pair := r.GetOperations().First(); pair != nil; pair = pair.Next() {
assert.Equal(t, expectedOrder[i], pair.Value().Description)
i++
}
}
func TestPathItem_MarshalYAML(t *testing.T) {
pi := &PathItem{
Description: "a path item",
Summary: "It's a test, don't worry about it, Jim",
@@ -112,7 +120,6 @@ parameters:
}
func TestPathItem_MarshalYAMLInline(t *testing.T) {
pi := &PathItem{
Description: "a path item",
Summary: "It's a test, don't worry about it, Jim",

View File

@@ -22,8 +22,8 @@ import (
// constraints.
// - https://spec.openapis.org/oas/v3.1.0#paths-object
type Paths struct {
PathItems orderedmap.Map[string, *PathItem] `json:"-" yaml:"-"`
Extensions map[string]any `json:"-" yaml:"-"`
PathItems *orderedmap.Map[string, *PathItem] `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *v3low.Paths
}
@@ -81,6 +81,7 @@ func (p *Paths) MarshalYAML() (interface{}, error) {
pi *PathItem
path string
line int
style yaml.Style
rendered *yaml.Node
}
var mapped []*pathItem
@@ -89,13 +90,21 @@ func (p *Paths) MarshalYAML() (interface{}, error) {
k := pair.Key()
pi := pair.Value()
ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if p.low != nil {
lpi := p.low.FindPath(k)
if lpi != nil {
ln = lpi.ValueNode.Line
}
for pair := orderedmap.First(p.low.PathItems); pair != nil; pair = pair.Next() {
if pair.Key().Value == k {
style = pair.Key().KeyNode.Style
break
}
mapped = append(mapped, &pathItem{pi, k, ln, nil})
}
}
mapped = append(mapped, &pathItem{pi, k, ln, style, nil})
}
nb := high.NewNodeBuilder(p, p.low)
@@ -107,23 +116,29 @@ func (p *Paths) MarshalYAML() (interface{}, error) {
label = extNode.Content[u].Value
continue
}
mapped = append(mapped, &pathItem{nil, label,
extNode.Content[u].Line, extNode.Content[u]})
mapped = append(mapped, &pathItem{
nil, label,
extNode.Content[u].Line, 0, extNode.Content[u],
})
}
}
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for j := range mapped {
if mapped[j].pi != nil {
rendered, _ := mapped[j].pi.MarshalYAML()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].path))
for _, mp := range mapped {
if mp.pi != nil {
rendered, _ := mp.pi.MarshalYAML()
kn := utils.CreateStringNode(mp.path)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mapped[j].rendered != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].path))
m.Content = append(m.Content, mapped[j].rendered)
if mp.rendered != nil {
m.Content = append(m.Content, utils.CreateStringNode(mp.path))
m.Content = append(m.Content, mp.rendered)
}
}
@@ -137,6 +152,7 @@ func (p *Paths) MarshalYAMLInline() (interface{}, error) {
pi *PathItem
path string
line int
style yaml.Style
rendered *yaml.Node
}
var mapped []*pathItem
@@ -145,13 +161,21 @@ func (p *Paths) MarshalYAMLInline() (interface{}, error) {
k := pair.Key()
pi := pair.Value()
ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if p.low != nil {
lpi := p.low.FindPath(k)
if lpi != nil {
ln = lpi.ValueNode.Line
}
for pair := orderedmap.First(p.low.PathItems); pair != nil; pair = pair.Next() {
if pair.Key().Value == k {
style = pair.Key().KeyNode.Style
break
}
mapped = append(mapped, &pathItem{pi, k, ln, nil})
}
}
mapped = append(mapped, &pathItem{pi, k, ln, style, nil})
}
nb := high.NewNodeBuilder(p, p.low)
@@ -164,23 +188,29 @@ func (p *Paths) MarshalYAMLInline() (interface{}, error) {
label = extNode.Content[u].Value
continue
}
mapped = append(mapped, &pathItem{nil, label,
extNode.Content[u].Line, extNode.Content[u]})
mapped = append(mapped, &pathItem{
nil, label,
extNode.Content[u].Line, 0, extNode.Content[u],
})
}
}
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for j := range mapped {
if mapped[j].pi != nil {
rendered, _ := mapped[j].pi.MarshalYAMLInline()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].path))
for _, mp := range mapped {
if mp.pi != nil {
rendered, _ := mp.pi.MarshalYAMLInline()
kn := utils.CreateStringNode(mp.path)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mapped[j].rendered != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].path))
m.Content = append(m.Content, mapped[j].rendered)
if mp.rendered != nil {
m.Content = append(m.Content, utils.CreateStringNode(mp.path))
m.Content = append(m.Content, mp.rendered)
}
}

View File

@@ -14,9 +14,9 @@ import (
// - https://spec.openapis.org/oas/v3.1.0#request-body-object
type RequestBody struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Content *orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Required *bool `json:"required,omitempty" yaml:"required,renderZero,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.RequestBody
}
@@ -25,7 +25,7 @@ func NewRequestBody(rb *low.RequestBody) *RequestBody {
r := new(RequestBody)
r.low = rb
r.Description = rb.Description.Value
if rb.Required.ValueNode != nil {
if !rb.Required.IsEmpty() {
r.Required = &rb.Required.Value
}
r.Extensions = high.ExtractExtensions(rb.Extensions)

View File

@@ -7,16 +7,21 @@ import (
"strings"
"testing"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestRequestBody_MarshalYAML(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-high-gravity", utils.CreateStringNode("why not?"))
rb := true
req := &RequestBody{
Description: "beer",
Required: &rb,
Extensions: map[string]interface{}{"x-high-gravity": "why not?"},
Extensions: ext,
}
rend, _ := req.Render()
@@ -26,16 +31,17 @@ required: true
x-high-gravity: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
}
func TestRequestBody_MarshalYAMLInline(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-high-gravity", utils.CreateStringNode("why not?"))
rb := true
req := &RequestBody{
Description: "beer",
Required: &rb,
Extensions: map[string]interface{}{"x-high-gravity": "why not?"},
Extensions: ext,
}
rend, _ := req.RenderInline()
@@ -45,15 +51,17 @@ required: true
x-high-gravity: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
}
func TestRequestBody_MarshalNoRequired(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-high-gravity", utils.CreateStringNode("why not?"))
rb := false
req := &RequestBody{
Description: "beer",
Required: &rb,
Extensions: map[string]interface{}{"x-high-gravity": "why not?"},
Extensions: ext,
}
rend, _ := req.Render()
@@ -63,14 +71,15 @@ required: false
x-high-gravity: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
}
func TestRequestBody_MarshalRequiredNil(t *testing.T) {
ext := orderedmap.New[string, *yaml.Node]()
ext.Set("x-high-gravity", utils.CreateStringNode("why not?"))
req := &RequestBody{
Description: "beer",
Extensions: map[string]interface{}{"x-high-gravity": "why not?"},
Extensions: ext,
}
rend, _ := req.Render()
@@ -79,5 +88,4 @@ func TestRequestBody_MarshalRequiredNil(t *testing.T) {
x-high-gravity: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
}

View File

@@ -17,10 +17,10 @@ import (
// - https://spec.openapis.org/oas/v3.1.0#response-object
type Response struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Headers orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"`
Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Links orderedmap.Map[string, *Link] `json:"links,omitempty" yaml:"links,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Headers *orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"`
Content *orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"`
Links *orderedmap.Map[string, *Link] `json:"links,omitempty" yaml:"links,omitempty"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Response
}

View File

@@ -21,7 +21,6 @@ import (
// with hard coded line and column numbers in them, changing the spec above the bottom will
// create pointless test changes. So here is a standalone test. you know... for science.
func TestNewResponse(t *testing.T) {
yml := `description: this is a response
headers:
someHeader:
@@ -46,14 +45,16 @@ links:
assert.Equal(t, 1, orderedmap.Len(r.Headers))
assert.Equal(t, 1, orderedmap.Len(r.Content))
assert.Equal(t, "pizza!", r.Extensions["x-pizza-man"])
var xPizzaMan string
_ = r.Extensions.GetOrZero("x-pizza-man").Decode(&xPizzaMan)
assert.Equal(t, "pizza!", xPizzaMan)
assert.Equal(t, 1, orderedmap.Len(r.Links))
assert.Equal(t, 1, r.GoLow().Description.KeyNode.Line)
}
func TestResponse_MarshalYAML(t *testing.T) {
yml := `description: this is a response
headers:
someHeader:
@@ -77,11 +78,9 @@ links:
rend, _ := r.Render()
assert.Equal(t, yml, strings.TrimSpace(string(rend)))
}
func TestResponse_MarshalYAMLInline(t *testing.T) {
yml := `description: this is a response
headers:
someHeader:
@@ -105,5 +104,4 @@ links:
rend, _ := r.RenderInline()
assert.Equal(t, yml, strings.TrimSpace(string(rend)))
}

View File

@@ -31,9 +31,9 @@ import (
// be the response for a successful operation call.
// - https://spec.openapis.org/oas/v3.1.0#responses-object
type Responses struct {
Codes orderedmap.Map[string, *Response] `json:"-" yaml:"-"`
Codes *orderedmap.Map[string, *Response] `json:"-" yaml:"-"`
Default *Response `json:"default,omitempty" yaml:"default,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Responses
}
@@ -97,19 +97,22 @@ func (r *Responses) MarshalYAML() (interface{}, error) {
code string
line int
ext *yaml.Node
style yaml.Style
}
var mapped []*responseItem
for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() {
ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if r.low != nil {
for lPair := orderedmap.First(r.low.Codes); lPair != nil; lPair = lPair.Next() {
if lPair.Key().Value == pair.Key() {
ln = lPair.Key().KeyNode.Line
style = lPair.Key().KeyNode.Style
}
}
}
mapped = append(mapped, &responseItem{pair.Value(), pair.Key(), ln, nil})
mapped = append(mapped, &responseItem{pair.Value(), pair.Key(), ln, nil, style})
}
// extract extensions
@@ -124,7 +127,7 @@ func (r *Responses) MarshalYAML() (interface{}, error) {
}
mapped = append(mapped, &responseItem{
nil, label,
extNode.Content[u].Line, extNode.Content[u],
extNode.Content[u].Line, extNode.Content[u], 0,
})
}
}
@@ -132,15 +135,19 @@ func (r *Responses) MarshalYAML() (interface{}, error) {
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for j := range mapped {
if mapped[j].resp != nil {
rendered, _ := mapped[j].resp.MarshalYAML()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
for _, mp := range mapped {
if mp.resp != nil {
rendered, _ := mp.resp.MarshalYAML()
kn := utils.CreateStringNode(mp.code)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mapped[j].ext != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, mapped[j].ext)
if mp.ext != nil {
m.Content = append(m.Content, utils.CreateStringNode(mp.code))
m.Content = append(m.Content, mp.ext)
}
}
@@ -155,19 +162,22 @@ func (r *Responses) MarshalYAMLInline() (interface{}, error) {
code string
line int
ext *yaml.Node
style yaml.Style
}
var mapped []*responseItem
for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() {
ln := 9999 // default to a high value to weight new content to the bottom.
var style yaml.Style
if r.low != nil {
for lPair := orderedmap.First(r.low.Codes); lPair != nil; lPair = lPair.Next() {
if lPair.Key().Value == pair.Key() {
ln = lPair.Key().KeyNode.Line
style = lPair.Key().KeyNode.Style
}
}
}
mapped = append(mapped, &responseItem{pair.Value(), pair.Key(), ln, nil})
mapped = append(mapped, &responseItem{pair.Value(), pair.Key(), ln, nil, style})
}
// extract extensions
@@ -183,7 +193,7 @@ func (r *Responses) MarshalYAMLInline() (interface{}, error) {
}
mapped = append(mapped, &responseItem{
nil, label,
extNode.Content[u].Line, extNode.Content[u],
extNode.Content[u].Line, extNode.Content[u], 0,
})
}
}
@@ -191,16 +201,20 @@ func (r *Responses) MarshalYAMLInline() (interface{}, error) {
sort.Slice(mapped, func(i, j int) bool {
return mapped[i].line < mapped[j].line
})
for j := range mapped {
if mapped[j].resp != nil {
rendered, _ := mapped[j].resp.MarshalYAMLInline()
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
for _, mp := range mapped {
if mp.resp != nil {
rendered, _ := mp.resp.MarshalYAMLInline()
kn := utils.CreateStringNode(mp.code)
kn.Style = mp.style
m.Content = append(m.Content, kn)
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mapped[j].ext != nil {
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, mapped[j].ext)
if mp.ext != nil {
m.Content = append(m.Content, utils.CreateStringNode(mp.code))
m.Content = append(m.Content, mp.ext)
}
}

View File

@@ -6,6 +6,7 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
@@ -28,7 +29,7 @@ type SecurityScheme struct {
BearerFormat string `json:"bearerFormat,omitempty" yaml:"bearerFormat,omitempty"`
Flows *OAuthFlows `json:"flows,omitempty" yaml:"flows,omitempty"`
OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty" yaml:"openIdConnectUrl,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.SecurityScheme
}

View File

@@ -15,8 +15,8 @@ import (
type Server struct {
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Variables orderedmap.Map[string, *ServerVariable] `json:"variables,omitempty" yaml:"variables,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Variables *orderedmap.Map[string, *ServerVariable] `json:"variables,omitempty" yaml:"variables,omitempty"`
Extensions *orderedmap.Map[string, *yaml.Node] `json:"-" yaml:"-"`
low *low.Server
}

View File

@@ -22,13 +22,13 @@ import (
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object
type Discriminator struct {
PropertyName low.NodeReference[string]
Mapping low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]]
Mapping low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]]
low.Reference
}
// FindMappingValue will return a ValueReference containing the string mapping value
func (d *Discriminator) FindMappingValue(key string) *low.ValueReference[string] {
for pair := d.Mapping.Value.First(); pair != nil; pair = pair.Next() {
for pair := orderedmap.First(d.Mapping.Value); pair != nil; pair = pair.Next() {
if pair.Key().Value == key {
v := pair.Value()
return &v
@@ -45,7 +45,7 @@ func (d *Discriminator) Hash() [32]byte {
f = append(f, d.PropertyName.Value)
}
for pair := orderedmap.First(d.Mapping.Value); pair != nil; pair = pair.Next() {
for pair := orderedmap.First(orderedmap.SortAlpha(d.Mapping.Value)); pair != nil; pair = pair.Next() {
f = append(f, pair.Value().Value)
}

View File

@@ -7,12 +7,11 @@ import (
"context"
"crypto/sha256"
"fmt"
"sort"
"strconv"
"strings"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
@@ -23,15 +22,15 @@ import (
type Example struct {
Summary low.NodeReference[string]
Description low.NodeReference[string]
Value low.NodeReference[any]
Value low.NodeReference[*yaml.Node]
ExternalValue low.NodeReference[string]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
*low.Reference
}
// FindExtension returns a ValueReference containing the extension value, if found.
func (ex *Example) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, ex.Extensions)
func (ex *Example) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInOrderedMap[*yaml.Node](ext, ex.Extensions)
}
// Hash will return a consistent SHA256 Hash of the Discriminator object
@@ -43,21 +42,15 @@ func (ex *Example) Hash() [32]byte {
if ex.Description.Value != "" {
f = append(f, ex.Description.Value)
}
if ex.Value.Value != "" {
if ex.Value.Value != nil && !ex.Value.Value.IsZero() {
// this could be anything!
f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(ex.Value.Value)))))
b, _ := yaml.Marshal(ex.Value.Value)
f = append(f, fmt.Sprintf("%x", sha256.Sum256(b)))
}
if ex.ExternalValue.Value != "" {
f = append(f, ex.ExternalValue.Value)
}
keys := make([]string, len(ex.Extensions))
z := 0
for k := range ex.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(ex.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
f = append(f, low.HashExtensions(ex.Extensions)...)
return sha256.Sum256([]byte(strings.Join(f, "|")))
}
@@ -70,32 +63,8 @@ func (ex *Example) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecInd
_, ln, vn := utils.FindKeyNodeFull(ValueLabel, root.Content)
if vn != nil {
var n map[string]interface{}
err := vn.Decode(&n)
if err != nil {
// if not a map, then try an array
var k []interface{}
err = vn.Decode(&k)
if err != nil {
// lets just default to interface
var j interface{}
_ = vn.Decode(&j)
ex.Value = low.NodeReference[any]{
Value: j,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
ex.Value = low.NodeReference[any]{
Value: k,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
ex.Value = low.NodeReference[any]{
Value: n,
ex.Value = low.NodeReference[*yaml.Node]{
Value: vn,
KeyNode: ln,
ValueNode: vn,
}
@@ -105,33 +74,6 @@ func (ex *Example) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecInd
}
// GetExtensions will return Example extensions to satisfy the HasExtensions interface.
func (ex *Example) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (ex *Example) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return ex.Extensions
}
// ExtractExampleValue will extract a primitive example value (if possible), or just the raw Value property if not.
func ExtractExampleValue(exp *yaml.Node) any {
if utils.IsNodeBoolValue(exp) {
v, _ := strconv.ParseBool(exp.Value)
return v
}
if utils.IsNodeIntValue(exp) {
v, _ := strconv.ParseInt(exp.Value, 10, 64)
return v
}
if utils.IsNodeFloatValue(exp) {
v, _ := strconv.ParseFloat(exp.Value, 64)
return v
}
if utils.IsNodeMap(exp) {
var m map[string]interface{}
_ = exp.Decode(&m)
return m
}
if utils.IsNodeArray(exp) {
var m []interface{}
_ = exp.Decode(&m)
return m
}
return exp.Value
}

View File

@@ -5,15 +5,17 @@ package base
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"testing"
)
func TestExample_Build_Success_NoValue(t *testing.T) {
yml := `summary: hot
description: cakes
x-cake: hot`
@@ -32,13 +34,13 @@ x-cake: hot`
assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value)
assert.Nil(t, n.Value.Value)
ext := n.FindExtension("x-cake")
assert.NotNil(t, ext)
assert.Equal(t, "hot", ext.Value)
var xCake string
_ = n.FindExtension("x-cake").Value.Decode(&xCake)
assert.Equal(t, "hot", xCake)
}
func TestExample_Build_Success_Simple(t *testing.T) {
yml := `summary: hot
description: cakes
value: a string example
@@ -57,14 +59,17 @@ x-cake: hot`
assert.NoError(t, err)
assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value)
assert.Equal(t, "a string example", n.Value.Value)
ext := n.FindExtension("x-cake")
assert.NotNil(t, ext)
assert.Equal(t, "hot", ext.Value)
var example string
err = n.Value.Value.Decode(&example)
assert.Equal(t, "a string example", example)
var xCake string
_ = n.FindExtension("x-cake").Value.Decode(&xCake)
assert.Equal(t, "hot", xCake)
}
func TestExample_Build_Success_Object(t *testing.T) {
yml := `summary: hot
description: cakes
value:
@@ -85,17 +90,15 @@ value:
assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value)
if v, ok := n.Value.Value.(map[string]interface{}); ok {
assert.Equal(t, "oven", v["pizza"])
assert.Equal(t, "pizza", v["yummy"])
} else {
assert.Fail(t, "failed to decode correctly.")
}
var m map[string]interface{}
err = n.Value.Value.Decode(&m)
require.NoError(t, err)
assert.Equal(t, "oven", m["pizza"])
assert.Equal(t, "pizza", m["yummy"])
}
func TestExample_Build_Success_Array(t *testing.T) {
yml := `summary: hot
description: cakes
value:
@@ -116,16 +119,15 @@ value:
assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value)
if v, ok := n.Value.Value.([]interface{}); ok {
assert.Equal(t, "wow", v[0])
assert.Equal(t, "such array", v[1])
} else {
assert.Fail(t, "failed to decode correctly.")
}
var a []any
err = n.Value.Value.Decode(&a)
require.NoError(t, err)
assert.Equal(t, "wow", a[0])
assert.Equal(t, "such array", a[1])
}
func TestExample_Build_Success_MergeNode(t *testing.T) {
yml := `x-things: &things
summary: hot
description: cakes
@@ -148,71 +150,15 @@ func TestExample_Build_Success_MergeNode(t *testing.T) {
assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value)
if v, ok := n.Value.Value.([]interface{}); ok {
assert.Equal(t, "wow", v[0])
assert.Equal(t, "such array", v[1])
} else {
assert.Fail(t, "failed to decode correctly.")
}
var a []any
err = n.Value.GetValue().Decode(&a)
require.NoError(t, err)
}
func TestExample_ExtractExampleValue_Map(t *testing.T) {
yml := `hot:
summer: nights
pizza: oven`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
val := ExtractExampleValue(idxNode.Content[0])
if v, ok := val.(map[string]interface{}); ok {
if r, rok := v["hot"].(map[string]interface{}); rok {
assert.Equal(t, "nights", r["summer"])
assert.Equal(t, "oven", r["pizza"])
} else {
assert.Fail(t, "failed to decode correctly.")
}
} else {
assert.Fail(t, "failed to decode correctly.")
}
}
func TestExample_ExtractExampleValue_Slice(t *testing.T) {
yml := `- hot:
summer: nights
- hotter:
pizza: oven`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
val := ExtractExampleValue(idxNode.Content[0])
if v, ok := val.([]interface{}); ok {
for w := range v {
if r, rok := v[w].(map[string]interface{}); rok {
for k := range r {
if k == "hotter" {
assert.Equal(t, "oven", r[k].(map[string]interface{})["pizza"])
}
if k == "hot" {
assert.Equal(t, "nights", r[k].(map[string]interface{})["summer"])
}
}
} else {
assert.Fail(t, "failed to decode correctly.")
}
}
} else {
assert.Fail(t, "failed to decode correctly.")
}
assert.Equal(t, "wow", a[0])
assert.Equal(t, "such array", a[1])
}
func TestExample_Hash(t *testing.T) {
left := `summary: hot
description: cakes
x-burger: nice
@@ -242,13 +188,5 @@ x-burger: nice`
_ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil)
assert.Equal(t, lDoc.Hash(), rDoc.Hash())
assert.Len(t, lDoc.GetExtensions(), 1)
}
func TestExtractExampleValue(t *testing.T) {
assert.True(t, ExtractExampleValue(&yaml.Node{Tag: "!!bool", Value: "true"}).(bool))
assert.Equal(t, int64(10), ExtractExampleValue(&yaml.Node{Tag: "!!int", Value: "10"}).(int64))
assert.Equal(t, 33.2, ExtractExampleValue(&yaml.Node{Tag: "!!float", Value: "33.2"}).(float64))
assert.Equal(t, "WHAT A NICE COW", ExtractExampleValue(&yaml.Node{Tag: "!!str", Value: "WHAT A NICE COW"}))
assert.Equal(t, 1, orderedmap.Len(lDoc.GetExtensions()))
}

View File

@@ -6,13 +6,13 @@ package base
import (
"context"
"crypto/sha256"
"fmt"
"strings"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
"strings"
)
// ExternalDoc represents a low-level External Documentation object as defined by OpenAPI 2 and 3
@@ -24,13 +24,13 @@ import (
type ExternalDoc struct {
Description low.NodeReference[string]
URL low.NodeReference[string]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
*low.Reference
}
// FindExtension returns a ValueReference containing the extension value, if found.
func (ex *ExternalDoc) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, ex.Extensions)
func (ex *ExternalDoc) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInOrderedMap[*yaml.Node](ext, ex.Extensions)
}
// Build will extract extensions from the ExternalDoc instance.
@@ -43,7 +43,7 @@ func (ex *ExternalDoc) Build(_ context.Context, _, root *yaml.Node, idx *index.S
}
// GetExtensions returns all ExternalDoc extensions and satisfies the low.HasExtensions interface.
func (ex *ExternalDoc) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (ex *ExternalDoc) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return ex.Extensions
}
@@ -53,13 +53,6 @@ func (ex *ExternalDoc) Hash() [32]byte {
ex.Description.Value,
ex.URL.Value,
}
keys := make([]string, len(ex.Extensions))
z := 0
for k := range ex.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(ex.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
f = append(f, low.HashExtensions(ex.Extensions)...)
return sha256.Sum256([]byte(strings.Join(f, "|")))
}

View File

@@ -5,15 +5,16 @@ package base
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func TestExternalDoc_FindExtension(t *testing.T) {
yml := `x-fish: cake`
var idxNode yaml.Node
@@ -26,12 +27,14 @@ func TestExternalDoc_FindExtension(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "cake", n.FindExtension("x-fish").Value)
var xFish string
_ = n.FindExtension("x-fish").Value.Decode(&xFish)
assert.Equal(t, "cake", xFish)
}
func TestExternalDoc_Build(t *testing.T) {
yml := `url: https://pb33f.io
description: the ranch
x-b33f: princess`
@@ -49,14 +52,13 @@ x-b33f: princess`
assert.NoError(t, err)
assert.Equal(t, "https://pb33f.io", n.URL.Value)
assert.Equal(t, "the ranch", n.Description.Value)
ext := n.FindExtension("x-b33f")
assert.NotNil(t, ext)
assert.Equal(t, "princess", ext.Value)
var xB33f string
_ = n.FindExtension("x-b33f").Value.Decode(&xB33f)
assert.Equal(t, "princess", xB33f)
}
func TestExternalDoc_Hash(t *testing.T) {
left := `url: https://pb33f.io
description: the ranch
x-b33f: princess`
@@ -78,5 +80,5 @@ description: the ranch`
_ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil)
assert.Equal(t, lDoc.Hash(), rDoc.Hash())
assert.Len(t, lDoc.GetExtensions(), 1)
assert.Equal(t, 1, orderedmap.Len(lDoc.GetExtensions()))
}

View File

@@ -6,11 +6,11 @@ package base
import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/utils"
"sort"
"strings"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"gopkg.in/yaml.v3"
@@ -31,17 +31,17 @@ type Info struct {
Contact low.NodeReference[*Contact]
License low.NodeReference[*License]
Version low.NodeReference[string]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
*low.Reference
}
// FindExtension attempts to locate an extension with the supplied key
func (i *Info) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap(ext, i.Extensions)
func (i *Info) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInOrderedMap(ext, i.Extensions)
}
// GetExtensions returns all extensions for Info
func (i *Info) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (i *Info) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return i.Extensions
}
@@ -87,13 +87,6 @@ func (i *Info) Hash() [32]byte {
if !i.Version.IsEmpty() {
f = append(f, i.Version.Value)
}
keys := make([]string, len(i.Extensions))
z := 0
for k := range i.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(i.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
f = append(f, low.HashExtensions(i.Extensions)...)
return sha256.Sum256([]byte(strings.Join(f, "|")))
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
@@ -54,10 +55,11 @@ x-cli-name: pizza cli`
assert.Equal(t, "magic", lic.Name.Value)
assert.Equal(t, "https://pb33f.io/license", lic.URL.Value)
cliName := n.FindExtension("x-cli-name")
assert.NotNil(t, cliName)
assert.Equal(t, "pizza cli", cliName.Value)
assert.Len(t, n.GetExtensions(), 1)
var xCliName string
_ = n.FindExtension("x-cli-name").Value.Decode(&xCliName)
assert.Equal(t, "pizza cli", xCliName)
assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
}
func TestContact_Build(t *testing.T) {

View File

@@ -82,7 +82,7 @@ type Schema struct {
Discriminator low.NodeReference[*Discriminator]
// in 3.1 examples can be an array (which is recommended)
Examples low.NodeReference[[]low.ValueReference[any]]
Examples low.NodeReference[[]low.ValueReference[*yaml.Node]]
// in 3.1 PrefixItems provides tuple validation using prefixItems.
PrefixItems low.NodeReference[[]low.ValueReference[*SchemaProxy]]
// in 3.1 Contains is used by arrays and points to a Schema.
@@ -97,8 +97,8 @@ type Schema struct {
If low.NodeReference[*SchemaProxy]
Else low.NodeReference[*SchemaProxy]
Then low.NodeReference[*SchemaProxy]
DependentSchemas low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]
PatternProperties low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]
DependentSchemas low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]
PatternProperties low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]
PropertyNames low.NodeReference[*SchemaProxy]
UnevaluatedItems low.NodeReference[*SchemaProxy]
UnevaluatedProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]
@@ -119,23 +119,23 @@ type Schema struct {
MaxProperties low.NodeReference[int64]
MinProperties low.NodeReference[int64]
Required low.NodeReference[[]low.ValueReference[string]]
Enum low.NodeReference[[]low.ValueReference[any]]
Enum low.NodeReference[[]low.ValueReference[*yaml.Node]]
Not low.NodeReference[*SchemaProxy]
Properties low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]
Properties low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]
AdditionalProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]
Description low.NodeReference[string]
ContentEncoding low.NodeReference[string]
ContentMediaType low.NodeReference[string]
Default low.NodeReference[any]
Const low.NodeReference[any]
Default low.NodeReference[*yaml.Node]
Const low.NodeReference[*yaml.Node]
Nullable low.NodeReference[bool]
ReadOnly low.NodeReference[bool]
WriteOnly low.NodeReference[bool]
XML low.NodeReference[*XML]
ExternalDocs low.NodeReference[*ExternalDoc]
Example low.NodeReference[any]
Example low.NodeReference[*yaml.Node]
Deprecated low.NodeReference[bool]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
// Parent Proxy refers back to the low level SchemaProxy that is proxying this schema.
ParentProxy *SchemaProxy
@@ -255,23 +255,13 @@ func (s *Schema) Hash() [32]byte {
keys = make([]string, len(s.Enum.Value))
for i := range s.Enum.Value {
keys[i] = fmt.Sprint(s.Enum.Value[i].Value)
keys[i] = low.ValueToString(s.Enum.Value[i].Value)
}
sort.Strings(keys)
d = append(d, keys...)
for i := range s.Enum.Value {
d = append(d, fmt.Sprint(s.Enum.Value[i].Value))
}
propKeys := make([]string, orderedmap.Len(s.Properties.Value))
z := 0
for pair := orderedmap.First(s.Properties.Value); pair != nil; pair = pair.Next() {
propKeys[z] = pair.Key().Value
z++
}
sort.Strings(propKeys)
for k := range propKeys {
d = append(d, low.GenerateHashString(s.FindProperty(propKeys[k]).Value))
for pair := orderedmap.First(orderedmap.SortAlpha(s.Properties.Value)); pair != nil; pair = pair.Next() {
d = append(d, fmt.Sprintf("%s-%s", pair.Key().Value, low.GenerateHashString(pair.Value().Value)))
}
if s.XML.Value != nil {
d = append(d, low.GenerateHashString(s.XML.Value))
@@ -287,7 +277,7 @@ func (s *Schema) Hash() [32]byte {
if len(s.OneOf.Value) > 0 {
oneOfKeys := make([]string, len(s.OneOf.Value))
oneOfEntities := make(map[string]*SchemaProxy)
z = 0
z := 0
for i := range s.OneOf.Value {
g := s.OneOf.Value[i].Value
r := low.GenerateHashString(g)
@@ -305,7 +295,7 @@ func (s *Schema) Hash() [32]byte {
if len(s.AllOf.Value) > 0 {
allOfKeys := make([]string, len(s.AllOf.Value))
allOfEntities := make(map[string]*SchemaProxy)
z = 0
z := 0
for i := range s.AllOf.Value {
g := s.AllOf.Value[i].Value
r := low.GenerateHashString(g)
@@ -323,7 +313,7 @@ func (s *Schema) Hash() [32]byte {
if len(s.AnyOf.Value) > 0 {
anyOfKeys := make([]string, len(s.AnyOf.Value))
anyOfEntities := make(map[string]*SchemaProxy)
z = 0
z := 0
for i := range s.AnyOf.Value {
g := s.AnyOf.Value[i].Value
r := low.GenerateHashString(g)
@@ -372,32 +362,18 @@ func (s *Schema) Hash() [32]byte {
d = append(d, fmt.Sprint(s.Anchor.Value))
}
depSchemasKeys := make([]string, orderedmap.Len(s.DependentSchemas.Value))
z = 0
for pair := orderedmap.First(s.DependentSchemas.Value); pair != nil; pair = pair.Next() {
depSchemasKeys[z] = pair.Key().Value
z++
}
sort.Strings(depSchemasKeys)
for k := range depSchemasKeys {
d = append(d, low.GenerateHashString(s.FindDependentSchema(depSchemasKeys[k]).Value))
for pair := orderedmap.First(orderedmap.SortAlpha(s.DependentSchemas.Value)); pair != nil; pair = pair.Next() {
d = append(d, fmt.Sprintf("%s-%s", pair.Key().Value, low.GenerateHashString(pair.Value().Value)))
}
patternPropsKeys := make([]string, orderedmap.Len(s.PatternProperties.Value))
z = 0
for pair := orderedmap.First(s.PatternProperties.Value); pair != nil; pair = pair.Next() {
patternPropsKeys[z] = pair.Key().Value
z++
}
sort.Strings(patternPropsKeys)
for k := range patternPropsKeys {
d = append(d, low.GenerateHashString(s.FindPatternProperty(patternPropsKeys[k]).Value))
for pair := orderedmap.First(orderedmap.SortAlpha(s.PatternProperties.Value)); pair != nil; pair = pair.Next() {
d = append(d, fmt.Sprintf("%s-%s", pair.Key().Value, low.GenerateHashString(pair.Value().Value)))
}
if len(s.PrefixItems.Value) > 0 {
itemsKeys := make([]string, len(s.PrefixItems.Value))
itemsEntities := make(map[string]*SchemaProxy)
z = 0
z := 0
for i := range s.PrefixItems.Value {
g := s.PrefixItems.Value[i].Value
r := low.GenerateHashString(g)
@@ -411,15 +387,7 @@ func (s *Schema) Hash() [32]byte {
}
}
// add extensions to hash
keys = make([]string, len(s.Extensions))
z = 0
for k := range s.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(s.Extensions[k].Value))))
z++
}
sort.Strings(keys)
d = append(d, keys...)
d = append(d, low.HashExtensions(s.Extensions)...)
if s.Example.Value != nil {
d = append(d, low.GenerateHashString(s.Example.Value))
}
@@ -435,12 +403,9 @@ func (s *Schema) Hash() [32]byte {
d = append(d, fmt.Sprint(s.MaxContains.Value))
}
if !s.Examples.IsEmpty() {
var xph []string
for w := range s.Examples.Value {
xph = append(xph, low.GenerateHashString(s.Examples.Value[w].Value))
for _, ex := range s.Examples.Value {
d = append(d, low.GenerateHashString(ex.Value))
}
sort.Strings(xph)
d = append(d, strings.Join(xph, "|"))
}
return sha256.Sum256([]byte(strings.Join(d, "|")))
}
@@ -464,7 +429,7 @@ func (s *Schema) FindPatternProperty(name string) *low.ValueReference[*SchemaPro
}
// GetExtensions returns all extensions for Schema
func (s *Schema) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (s *Schema) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return s.Extensions
}
@@ -649,20 +614,20 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
}
// handle example if set. (3.0)
_, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content)
_, expLabel, expNode := utils.FindKeyNodeFullTop(ExampleLabel, root.Content)
if expNode != nil {
s.Example = low.NodeReference[any]{Value: ExtractExampleValue(expNode), KeyNode: expLabel, ValueNode: expNode}
s.Example = low.NodeReference[*yaml.Node]{Value: expNode, KeyNode: expLabel, ValueNode: expNode}
}
// handle examples if set.(3.1)
_, expArrLabel, expArrNode := utils.FindKeyNodeFullTop(ExamplesLabel, root.Content)
if expArrNode != nil {
if utils.IsNodeArray(expArrNode) {
var examples []low.ValueReference[any]
var examples []low.ValueReference[*yaml.Node]
for i := range expArrNode.Content {
examples = append(examples, low.ValueReference[any]{Value: ExtractExampleValue(expArrNode.Content[i]), ValueNode: expArrNode.Content[i]})
examples = append(examples, low.ValueReference[*yaml.Node]{Value: expArrNode.Content[i], ValueNode: expArrNode.Content[i]})
}
s.Examples = low.NodeReference[[]low.ValueReference[any]]{
s.Examples = low.NodeReference[[]low.ValueReference[*yaml.Node]]{
Value: examples,
ValueNode: expArrNode,
KeyNode: expArrLabel,
@@ -1035,7 +1000,7 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
return nil
}
func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex, label string) (*low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]], error) {
func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex, label string) (*low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]], error) {
_, propLabel, propsNode := utils.FindKeyNodeFullTop(label, root.Content)
if propsNode != nil {
propertyMap := orderedmap.New[low.KeyReference[string], low.ValueReference[*SchemaProxy]]()
@@ -1047,12 +1012,12 @@ func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex
}
// check our prop isn't reference
isRef := false
refString := ""
var refNode *yaml.Node
if h, _, l := utils.IsNodeRefValue(prop); h {
ref, _, _, _ := low.LocateRefNodeWithContext(ctx, prop, idx)
if ref != nil {
isRef = true
refNode = prop
prop = ref
refString = l
} else {
@@ -1061,16 +1026,19 @@ func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex
}
}
sp := &SchemaProxy{ctx: ctx, kn: currentProp, vn: prop, idx: idx}
sp.SetReference(refString, refNode)
propertyMap.Set(low.KeyReference[string]{
KeyNode: currentProp,
Value: currentProp.Value,
}, low.ValueReference[*SchemaProxy]{
Value: &SchemaProxy{ctx: ctx, kn: currentProp, vn: prop, idx: idx, isReference: isRef, referenceLookup: refString},
Value: sp,
ValueNode: prop,
})
}
return &low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]{
return &low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]{
Value: propertyMap,
KeyNode: propLabel,
ValueNode: propsNode,
@@ -1112,7 +1080,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
syncChan := make(chan buildResult)
// build out a SchemaProxy for every sub-schema.
build := func(pctx context.Context, kn *yaml.Node, vn *yaml.Node, schemaIdx int, c chan buildResult,
build := func(pctx context.Context, kn, vn *yaml.Node, rf *yaml.Node, schemaIdx int, c chan buildResult,
isRef bool, refLocation string,
) {
// a proxy design works best here. polymorphism, pretty much guarantees that a sub-schema can
@@ -1127,8 +1095,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
sp.idx = idx
sp.ctx = pctx
if isRef {
sp.referenceLookup = refLocation
sp.isReference = true
sp.SetReference(refLocation, rf)
}
res := &low.ValueReference[*SchemaProxy]{
Value: sp,
@@ -1142,6 +1109,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
isRef := false
refLocation := ""
var refNode *yaml.Node
foundCtx := ctx
if utils.IsNodeMap(valueNode) {
h := false
@@ -1149,6 +1117,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
isRef = true
ref, _, _, fctx := low.LocateRefNodeWithContext(ctx, valueNode, idx)
if ref != nil {
refNode = valueNode
valueNode = ref
foundCtx = fctx
} else {
@@ -1159,7 +1128,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
// this only runs once, however to keep things consistent, it makes sense to use the same async method
// that arrays will use.
go build(foundCtx, labelNode, valueNode, -1, syncChan, isRef, refLocation)
go build(foundCtx, labelNode, valueNode, refNode, -1, syncChan, isRef, refLocation)
select {
case r := <-syncChan:
schemas <- schemaProxyBuildResult{
@@ -1181,6 +1150,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
isRef = true
ref, _, _, fctx := low.LocateRefNodeWithContext(ctx, vn, idx)
if ref != nil {
refNode = vn
vn = ref
foundCtx = fctx
} else {
@@ -1191,7 +1161,7 @@ func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, label
}
}
refBuilds++
go build(foundCtx, vn, vn, i, syncChan, isRef, refLocation)
go build(foundCtx, vn, vn, refNode, i, syncChan, isRef, refLocation)
}
completedBuilds := 0
@@ -1225,12 +1195,11 @@ func ExtractSchema(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (
var schLabel, schNode *yaml.Node
errStr := "schema build failed: reference '%s' cannot be found at line %d, col %d"
isRef := false
refLocation := ""
var refNode *yaml.Node
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
isRef = true
ref, fIdx, _, nCtx := low.LocateRefNodeWithContext(ctx, root, idx)
if ref != nil {
schNode = ref
@@ -1250,9 +1219,9 @@ func ExtractSchema(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (
if schNode != nil {
h := false
if h, _, refLocation = utils.IsNodeRefValue(schNode); h {
isRef = true
ref, foundIdx, _, nCtx := low.LocateRefNodeWithContext(ctx, schNode, idx)
if ref != nil {
refNode = schNode
schNode = ref
if foundIdx != nil {
// TODO: check on this
@@ -1273,11 +1242,16 @@ func ExtractSchema(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (
if schNode != nil {
// check if schema has already been built.
schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: idx, ctx: ctx, isReference: isRef, referenceLookup: refLocation}
return &low.NodeReference[*SchemaProxy]{
Value: schema, KeyNode: schLabel, ValueNode: schNode, ReferenceNode: isRef,
Reference: refLocation,
}, nil
schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: idx, ctx: ctx}
schema.SetReference(refLocation, refNode)
n := &low.NodeReference[*SchemaProxy]{
Value: schema,
KeyNode: schLabel,
ValueNode: schNode,
}
n.SetReference(refLocation, refNode)
return n, nil
}
return nil, nil
}

View File

@@ -6,6 +6,8 @@ package base
import (
"context"
"crypto/sha256"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
@@ -44,13 +46,12 @@ import (
// it's not actually JSONSchema until 3.1, so lots of times a bad schema will break parsing. Errors are only found
// when a schema is needed, so the rest of the document is parsed and ready to use.
type SchemaProxy struct {
low.Reference
kn *yaml.Node
vn *yaml.Node
idx *index.SpecIndex
rendered *Schema
buildError error
isReference bool // Is the schema underneath originally a $ref?
referenceLookup string // If the schema is a $ref, what's its name?
ctx context.Context
}
@@ -62,8 +63,7 @@ func (sp *SchemaProxy) Build(ctx context.Context, key, value *yaml.Node, idx *in
sp.idx = idx
sp.ctx = ctx
if rf, _, r := utils.IsNodeRefValue(value); rf {
sp.isReference = true
sp.referenceLookup = r
sp.SetReference(r, value)
}
return nil
}
@@ -101,36 +101,6 @@ func (sp *SchemaProxy) GetBuildError() error {
return sp.buildError
}
// IsSchemaReference returns true if the Schema that this SchemaProxy represents, is actually a reference to
// a Schema contained within Components or Definitions. There is no difference in the mechanism used to resolve the
// Schema when calling Schema(), however if we want to know if this schema was originally a reference, we won't
// be able to determine that from the model, without this bit.
func (sp *SchemaProxy) IsSchemaReference() bool {
return sp.isReference
}
// IsReference is an alias for IsSchemaReference() except it's compatible wih the IsReferenced interface type.
func (sp *SchemaProxy) IsReference() bool {
return sp.IsSchemaReference()
}
// GetReference is an alias for GetSchemaReference() except it's compatible wih the IsReferenced interface type.
func (sp *SchemaProxy) GetReference() string {
return sp.GetSchemaReference()
}
// SetReference will set the reference lookup for this SchemaProxy.
func (sp *SchemaProxy) SetReference(ref string) {
sp.referenceLookup = ref
}
// GetSchemaReference will return the lookup defined by the $ref that this schema points to. If the schema
// is inline, and not a reference, then this method returns an empty string. Only useful when combined with
// IsSchemaReference()
func (sp *SchemaProxy) GetSchemaReference() string {
return sp.referenceLookup
}
func (sp *SchemaProxy) GetSchemaReferenceLocation() *index.NodeOrigin {
if sp.idx != nil {
origin := sp.idx.FindNodeOrigin(sp.vn)
@@ -158,11 +128,11 @@ func (sp *SchemaProxy) GetValueNode() *yaml.Node {
// Hash will return a consistent SHA256 Hash of the SchemaProxy object (it will resolve it)
func (sp *SchemaProxy) Hash() [32]byte {
if sp.rendered != nil {
if !sp.isReference {
if !sp.IsReference() {
return sp.rendered.Hash()
}
} else {
if !sp.isReference {
if !sp.IsReference() {
// only resolve this proxy if it's not a ref.
sch := sp.Schema()
sp.rendered = sch
@@ -170,5 +140,5 @@ func (sp *SchemaProxy) Hash() [32]byte {
}
}
// hash reference value only, do not resolve!
return sha256.Sum256([]byte(sp.referenceLookup))
return sha256.Sum256([]byte(sp.GetReference()))
}

View File

@@ -5,15 +5,16 @@ package base
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func TestSchemaProxy_Build(t *testing.T) {
yml := `x-windows: washed
description: something`
@@ -24,29 +25,25 @@ description: something`
err := sch.Build(context.Background(), &idxNode, idxNode.Content[0], nil)
assert.NoError(t, err)
assert.Equal(t, "db2a35dd6fb3d9481d0682571b9d687616bb2a34c1887f7863f0b2e769ca7b23",
assert.Equal(t, "e20c009d370944d177c0b46e8fa29e15fadc3a6f9cca6bb251ff9e120265fc96",
low.GenerateHashString(&sch))
assert.Equal(t, "something", sch.Schema().Description.Value)
assert.Empty(t, sch.GetSchemaReference())
assert.Equal(t, "something", sch.Schema().Description.GetValue())
assert.Empty(t, sch.GetReference())
assert.NotNil(t, sch.GetKeyNode())
assert.NotNil(t, sch.GetValueNode())
assert.False(t, sch.IsSchemaReference())
assert.False(t, sch.IsReference())
assert.Empty(t, sch.GetReference())
sch.SetReference("coffee")
sch.SetReference("coffee", nil)
assert.Equal(t, "coffee", sch.GetReference())
// already rendered, should spit out the same
assert.Equal(t, "db2a35dd6fb3d9481d0682571b9d687616bb2a34c1887f7863f0b2e769ca7b23",
assert.Equal(t, "37290d74ac4d186e3a8e5785d259d2ec04fac91ae28092e7620ec8bc99e830aa",
low.GenerateHashString(&sch))
assert.Len(t, sch.Schema().GetExtensions(), 1)
assert.Equal(t, 1, orderedmap.Len(sch.Schema().GetExtensions()))
}
func TestSchemaProxy_Build_CheckRef(t *testing.T) {
yml := `$ref: wat`
var sch SchemaProxy
@@ -55,14 +52,13 @@ func TestSchemaProxy_Build_CheckRef(t *testing.T) {
err := sch.Build(context.Background(), nil, idxNode.Content[0], nil)
assert.NoError(t, err)
assert.True(t, sch.IsSchemaReference())
assert.Equal(t, "wat", sch.GetSchemaReference())
assert.True(t, sch.IsReference())
assert.Equal(t, "wat", sch.GetReference())
assert.Equal(t, "f00a787f7492a95e165b470702f4fe9373583fbdc025b2c8bdf0262cc48fcff4",
low.GenerateHashString(&sch))
}
func TestSchemaProxy_Build_HashInline(t *testing.T) {
yml := `type: int`
var sch SchemaProxy
@@ -71,14 +67,13 @@ func TestSchemaProxy_Build_HashInline(t *testing.T) {
err := sch.Build(context.Background(), nil, idxNode.Content[0], nil)
assert.NoError(t, err)
assert.False(t, sch.IsSchemaReference())
assert.False(t, sch.IsReference())
assert.NotNil(t, sch.Schema())
assert.Equal(t, "6da88c34ba124c41f977db66a4fc5c1a951708d285c81bb0d47c3206f4c27ca8",
low.GenerateHashString(&sch))
}
func TestSchemaProxy_Build_UsingMergeNodes(t *testing.T) {
yml := `
x-common-definitions:
life_cycle_types: &life_cycle_types_def
@@ -95,11 +90,9 @@ x-common-definitions:
assert.NoError(t, err)
assert.Len(t, sch.Schema().Enum.Value, 3)
assert.Equal(t, "The type of life cycle", sch.Schema().Description.Value)
}
func TestSchemaProxy_GetSchemaReferenceLocation(t *testing.T) {
yml := `type: object
properties:
name:
@@ -159,5 +152,4 @@ properties:
err = schC.Build(context.Background(), nil, idxNodeA.Content[0], nil)
origin = schC.GetSchemaReferenceLocation()
assert.Nil(t, origin)
}

View File

@@ -2,6 +2,8 @@ package base
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
@@ -9,7 +11,6 @@ import (
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func test_get_schema_blob() string {
@@ -192,8 +193,8 @@ func Test_Schema(t *testing.T) {
assert.Equal(t, "an xml namespace", j.XML.Value.Namespace.Value)
assert.Equal(t, "a prefix", j.XML.Value.Prefix.Value)
assert.Equal(t, true, j.XML.Value.Attribute.Value)
assert.Len(t, j.XML.Value.Extensions, 1)
assert.Len(t, j.XML.Value.GetExtensions(), 1)
assert.Equal(t, 1, orderedmap.Len(j.XML.Value.Extensions))
assert.Equal(t, 1, orderedmap.Len(j.XML.Value.GetExtensions()))
assert.NotNil(t, v.Value.Schema().AdditionalProperties.Value)
@@ -213,12 +214,20 @@ func Test_Schema(t *testing.T) {
io := v.Value.Schema()
assert.Equal(t, "allOfA description", io.Description.Value)
assert.Equal(t, "allOfAExp", io.Example.Value)
var ioExample string
_ = io.Example.GetValueNode().Decode(&ioExample)
assert.Equal(t, "allOfAExp", ioExample)
qw := f.FindProperty("allOfB").Value.Schema()
assert.NotNil(t, v)
assert.Equal(t, "allOfB description", qw.Description.Value)
assert.Equal(t, "allOfBExp", qw.Example.Value)
var qwExample string
_ = qw.Example.GetValueNode().Decode(&qwExample)
assert.Equal(t, "allOfBExp", qwExample)
// check polymorphic values anyOf
assert.Equal(t, "an anyOf thing", sch.AnyOf.Value[0].Value.Schema().Description.Value)
@@ -227,12 +236,18 @@ func Test_Schema(t *testing.T) {
v = sch.AnyOf.Value[0].Value.Schema().FindProperty("anyOfA")
assert.NotNil(t, v)
assert.Equal(t, "anyOfA description", v.Value.Schema().Description.Value)
assert.Equal(t, "anyOfAExp", v.Value.Schema().Example.Value)
var vSchemaExample string
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "anyOfAExp", vSchemaExample)
v = sch.AnyOf.Value[0].Value.Schema().FindProperty("anyOfB")
assert.NotNil(t, v)
assert.Equal(t, "anyOfB description", v.Value.Schema().Description.Value)
assert.Equal(t, "anyOfBExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "anyOfBExp", vSchemaExample)
// check polymorphic values oneOf
assert.Equal(t, "a oneof thing", sch.OneOf.Value[0].Value.Schema().Description.Value)
@@ -241,12 +256,16 @@ func Test_Schema(t *testing.T) {
v = sch.OneOf.Value[0].Value.Schema().FindProperty("oneOfA")
assert.NotNil(t, v)
assert.Equal(t, "oneOfA description", v.Value.Schema().Description.Value)
assert.Equal(t, "oneOfAExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "oneOfAExp", vSchemaExample)
v = sch.OneOf.Value[0].Value.Schema().FindProperty("oneOfB")
assert.NotNil(t, v)
assert.Equal(t, "oneOfB description", v.Value.Schema().Description.Value)
assert.Equal(t, "oneOfBExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "oneOfBExp", vSchemaExample)
// check values NOT
assert.Equal(t, "a not thing", sch.Not.Value.Schema().Description.Value)
@@ -255,12 +274,16 @@ func Test_Schema(t *testing.T) {
v = sch.Not.Value.Schema().FindProperty("notA")
assert.NotNil(t, v)
assert.Equal(t, "notA description", v.Value.Schema().Description.Value)
assert.Equal(t, "notAExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "notAExp", vSchemaExample)
v = sch.Not.Value.Schema().FindProperty("notB")
assert.NotNil(t, v)
assert.Equal(t, "notB description", v.Value.Schema().Description.Value)
assert.Equal(t, "notBExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "notBExp", vSchemaExample)
// check values Items
assert.Equal(t, "an items thing", sch.Items.Value.A.Schema().Description.Value)
@@ -269,12 +292,16 @@ func Test_Schema(t *testing.T) {
v = sch.Items.Value.A.Schema().FindProperty("itemsA")
assert.NotNil(t, v)
assert.Equal(t, "itemsA description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsAExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "itemsAExp", vSchemaExample)
v = sch.Items.Value.A.Schema().FindProperty("itemsB")
assert.NotNil(t, v)
assert.Equal(t, "itemsB description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsBExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "itemsBExp", vSchemaExample)
// check values PrefixItems
assert.Equal(t, "an items thing", sch.PrefixItems.Value[0].Value.Schema().Description.Value)
@@ -283,17 +310,21 @@ func Test_Schema(t *testing.T) {
v = sch.PrefixItems.Value[0].Value.Schema().FindProperty("itemsA")
assert.NotNil(t, v)
assert.Equal(t, "itemsA description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsAExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValueNode().Decode(&vSchemaExample)
assert.Equal(t, "itemsAExp", vSchemaExample)
v = sch.PrefixItems.Value[0].Value.Schema().FindProperty("itemsB")
assert.NotNil(t, v)
assert.Equal(t, "itemsB description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsBExp", v.Value.Schema().Example.Value)
_ = v.GetValue().Schema().Example.GetValue().Decode(&vSchemaExample)
assert.Equal(t, "itemsBExp", vSchemaExample)
// check discriminator
assert.NotNil(t, sch.Discriminator.Value)
assert.Equal(t, "athing", sch.Discriminator.Value.PropertyName.Value)
assert.Len(t, sch.Discriminator.Value.Mapping.Value, 2)
assert.Equal(t, 2, sch.Discriminator.GetValue().Mapping.GetValue().Len())
mv := sch.Discriminator.Value.FindMappingValue("log")
assert.Equal(t, "cat", mv.Value)
mv = sch.Discriminator.Value.FindMappingValue("pizza")
@@ -429,12 +460,20 @@ const: tasty`
assert.Equal(t, float64(12), sch.ExclusiveMinimum.Value.B)
assert.Equal(t, float64(13), sch.ExclusiveMaximum.Value.B)
assert.Len(t, sch.Examples.Value, 1)
assert.Equal(t, "testing", sch.Examples.Value[0].Value)
var example0 string
_ = sch.Examples.GetValue()[0].GetValue().Decode(&example0)
assert.Equal(t, "testing", example0)
assert.Equal(t, "fish64", sch.ContentEncoding.Value)
assert.Equal(t, "fish/paste", sch.ContentMediaType.Value)
assert.True(t, sch.Items.Value.IsB())
assert.True(t, sch.Items.Value.B)
assert.Equal(t, "tasty", sch.Const.Value)
var schConst string
_ = sch.Const.GetValue().Decode(&schConst)
assert.Equal(t, "tasty", schConst)
}
func TestSchema_Build_PropsLookup(t *testing.T) {
@@ -986,7 +1025,11 @@ schema:
assert.NoError(t, err)
assert.NotNil(t, res.Value)
sch := res.Value.Schema()
assert.Equal(t, 5, sch.Default.Value)
var def int
_ = sch.Default.GetValueNode().Decode(&def)
assert.Equal(t, 5, def)
}
func TestExtractSchema_ConstPrimitive(t *testing.T) {
@@ -1002,7 +1045,11 @@ schema:
assert.NoError(t, err)
assert.NotNil(t, res.Value)
sch := res.Value.Schema()
assert.Equal(t, 5, sch.Const.Value)
var cnst int
_ = sch.Const.GetValueNode().Decode(&cnst)
assert.Equal(t, 5, cnst)
}
func TestExtractSchema_Ref(t *testing.T) {
@@ -1785,7 +1832,6 @@ components:
res, e := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Nil(t, res)
assert.Equal(t, "schema build failed: reference '[empty]' cannot be found at line 2, col 9", e.Error())
}
func TestSchema_EmptyRef(t *testing.T) {
@@ -1814,5 +1860,4 @@ components:
res, e := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Nil(t, res)
assert.Equal(t, "schema build failed: reference '[empty]' cannot be found at line 1, col 7", e.Error())
}

View File

@@ -26,7 +26,7 @@ import (
// - https://swagger.io/specification/v2/#securityDefinitionsObject
// - https://swagger.io/specification/#security-requirement-object
type SecurityRequirement struct {
Requirements low.ValueReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]]
Requirements low.ValueReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]]
*low.Reference
}
@@ -61,7 +61,7 @@ func (s *SecurityRequirement) Build(_ context.Context, _, root *yaml.Node, _ *in
},
)
}
s.Requirements = low.ValueReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]]{
s.Requirements = low.ValueReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]]{
Value: valueMap,
ValueNode: root,
}
@@ -91,22 +91,14 @@ func (s *SecurityRequirement) GetKeys() []string {
// Hash will return a consistent SHA256 Hash of the SecurityRequirement object
func (s *SecurityRequirement) Hash() [32]byte {
var f []string
values := make(map[string][]string, orderedmap.Len(s.Requirements.Value))
var valKeys []string
for pair := orderedmap.First(s.Requirements.Value); pair != nil; pair = pair.Next() {
for pair := orderedmap.First(orderedmap.SortAlpha(s.Requirements.Value)); pair != nil; pair = pair.Next() {
var vals []string
for y := range pair.Value().Value {
vals = append(vals, pair.Value().Value[y].Value)
}
sort.Strings(vals)
valKeys = append(valKeys, pair.Key().Value)
if len(vals) > 0 {
values[pair.Key().Value] = vals
}
}
sort.Strings(valKeys)
for val := range valKeys {
f = append(f, fmt.Sprintf("%s-%s", valKeys[val], strings.Join(values[valKeys[val]], "|")))
f = append(f, fmt.Sprintf("%s-%s", pair.Key().Value, strings.Join(vals, "|")))
}
return sha256.Sum256([]byte(strings.Join(f, "|")))
}

View File

@@ -6,13 +6,13 @@ package base
import (
"context"
"crypto/sha256"
"fmt"
"strings"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
"strings"
)
// Tag represents a low-level Tag instance that is backed by a low-level one.
@@ -25,13 +25,13 @@ type Tag struct {
Name low.NodeReference[string]
Description low.NodeReference[string]
ExternalDocs low.NodeReference[*ExternalDoc]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
*low.Reference
}
// FindExtension returns a ValueReference containing the extension value, if found.
func (t *Tag) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, t.Extensions)
func (t *Tag) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInOrderedMap(ext, t.Extensions)
}
// Build will extract extensions and external docs for the Tag.
@@ -48,7 +48,7 @@ func (t *Tag) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecInde
}
// GetExtensions returns all Tag extensions and satisfies the low.HasExtensions interface.
func (t *Tag) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (t *Tag) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return t.Extensions
}
@@ -64,13 +64,6 @@ func (t *Tag) Hash() [32]byte {
if !t.ExternalDocs.IsEmpty() {
f = append(f, low.GenerateHashString(t.ExternalDocs.Value))
}
keys := make([]string, len(t.Extensions))
z := 0
for k := range t.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(t.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
f = append(f, low.HashExtensions(t.Extensions)...)
return sha256.Sum256([]byte(strings.Join(f, "|")))
}

View File

@@ -5,15 +5,16 @@ package base
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func TestTag_Build(t *testing.T) {
yml := `name: a tag
description: a description
externalDocs:
@@ -33,13 +34,15 @@ x-coffee: tasty`
assert.Equal(t, "a tag", n.Name.Value)
assert.Equal(t, "a description", n.Description.Value)
assert.Equal(t, "https://pb33f.io", n.ExternalDocs.Value.URL.Value)
assert.Equal(t, "tasty", n.FindExtension("x-coffee").Value)
assert.Len(t, n.GetExtensions(), 1)
var xCoffee string
_ = n.FindExtension("x-coffee").GetValue().Decode(&xCoffee)
assert.Equal(t, "tasty", xCoffee)
assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
}
func TestTag_Build_Error(t *testing.T) {
yml := `name: a tag
description: a description
externalDocs:
@@ -58,7 +61,6 @@ externalDocs:
}
func TestTag_Hash(t *testing.T) {
left := `name: melody
description: my princess
externalDocs:
@@ -84,5 +86,4 @@ x-b33f: princess`
_ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil)
assert.Equal(t, lDoc.Hash(), rDoc.Hash())
}

View File

@@ -3,12 +3,13 @@ package base
import (
"crypto/sha256"
"fmt"
"strings"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
"strings"
)
// XML represents a low-level representation of an XML object defined by all versions of OpenAPI.
@@ -26,7 +27,7 @@ type XML struct {
Prefix low.NodeReference[string]
Attribute low.NodeReference[bool]
Wrapped low.NodeReference[bool]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
*low.Reference
}
@@ -40,7 +41,7 @@ func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error {
}
// GetExtensions returns all Tag extensions and satisfies the low.HasExtensions interface.
func (x *XML) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (x *XML) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return x.Extensions
}
@@ -62,13 +63,6 @@ func (x *XML) Hash() [32]byte {
if !x.Wrapped.IsEmpty() {
f = append(f, fmt.Sprint(x.Wrapped.Value))
}
keys := make([]string, len(x.Extensions))
z := 0
for k := range x.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(x.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
f = append(f, low.HashExtensions(x.Extensions)...)
return sha256.Sum256([]byte(strings.Join(f, "|")))
}

View File

@@ -7,37 +7,24 @@ import (
"context"
"crypto/sha256"
"fmt"
"net/url"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3"
"net/url"
"path/filepath"
)
// FindItemInMap accepts a string key and a collection of KeyReference[string] and ValueReference[T]. Every
// KeyReference will have its value checked against the string key and if there is a match, it will be returned.
func FindItemInMap[T any](item string, collection map[KeyReference[string]]ValueReference[T]) *ValueReference[T] {
for n, o := range collection {
if n.Value == item {
return &o
}
if strings.EqualFold(item, n.Value) {
return &o
}
}
return nil
}
// FindItemInOrderedMap accepts a string key and a collection of KeyReference[string] and ValueReference[T].
// Every KeyReference will have its value checked against the string key and if there is a match, it will be
// returned.
func FindItemInOrderedMap[T any](item string, collection orderedmap.Map[KeyReference[string], ValueReference[T]]) *ValueReference[T] {
func FindItemInOrderedMap[T any](item string, collection *orderedmap.Map[KeyReference[string], ValueReference[T]]) *ValueReference[T] {
for pair := orderedmap.First(collection); pair != nil; pair = pair.Next() {
n := pair.Key()
if n.Value == item {
@@ -50,6 +37,18 @@ func FindItemInOrderedMap[T any](item string, collection orderedmap.Map[KeyRefer
return nil
}
// HashExtensions will generate a hash from the low representation of extensions.
func HashExtensions(ext *orderedmap.Map[KeyReference[string], ValueReference[*yaml.Node]]) []string {
f := []string{}
for pair := orderedmap.First(orderedmap.SortAlpha(ext)); pair != nil; pair = pair.Next() {
b, _ := yaml.Marshal(pair.Value().GetValue())
f = append(f, fmt.Sprintf("%s-%x", pair.Key().Value, sha256.Sum256([]byte(b))))
}
return f
}
// helper function to generate a list of all the things an index should be searched for.
func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.Reference {
return []func() map[string]*index.Reference{
@@ -68,7 +67,6 @@ func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.Re
}
func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, *index.SpecIndex, error, context.Context) {
if rf, _, rv := utils.IsNodeRefValue(root); rf {
if rv == "" {
@@ -112,9 +110,7 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S
explodedRefValue := strings.Split(rv, "#")
if len(explodedRefValue) == 2 {
if !strings.HasPrefix(explodedRefValue[0], "http") {
if !filepath.IsAbs(explodedRefValue[0]) {
if strings.HasPrefix(specPath, "http") {
u, _ := url.Parse(specPath)
p := ""
@@ -137,7 +133,6 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S
}
rv = fmt.Sprintf("%s#%s", abs, explodedRefValue[1])
} else {
// check for a config baseURL and use that if it exists.
if idx.GetConfig().BaseURL != nil {
@@ -154,11 +149,8 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S
}
}
} else {
if !strings.HasPrefix(explodedRefValue[0], "http") {
if !filepath.IsAbs(explodedRefValue[0]) {
if strings.HasPrefix(specPath, "http") {
u, _ := url.Parse(specPath)
p := filepath.Dir(u.Path)
@@ -173,7 +165,6 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S
rv = abs
} else {
// check for a config baseURL and use that if it exists.
if idx.GetConfig().BaseURL != nil {
u := *idx.GetConfig().BaseURL
@@ -211,7 +202,6 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S
rv, root.Line, root.Column), ctx
}
return nil, idx, nil, ctx
}
// LocateRefNode will perform a complete lookup for a $ref node. This function searches the entire index for
@@ -227,10 +217,12 @@ func ExtractObjectRaw[T Buildable[N], N any](ctx context.Context, key, root *yam
var circError error
var isReference bool
var referenceValue string
var refNode *yaml.Node
root = utils.NodeAlias(root)
if h, _, rv := utils.IsNodeRefValue(root); h {
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, root, idx)
if ref != nil {
refNode = root
root = ref
isReference = true
referenceValue = rv
@@ -257,7 +249,7 @@ func ExtractObjectRaw[T Buildable[N], N any](ctx context.Context, key, root *yam
// if this is a reference, keep track of the reference in the value
if isReference {
SetReference(n, referenceValue)
SetReference(n, referenceValue, refNode)
}
// do we want to throw an error as well if circular error reporting is on?
@@ -274,10 +266,12 @@ func ExtractObject[T Buildable[N], N any](ctx context.Context, label string, roo
var circError error
var isReference bool
var referenceValue string
var refNode *yaml.Node
root = utils.NodeAlias(root)
if rf, rl, refVal := utils.IsNodeRefValue(root); rf {
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, root, idx)
if ref != nil {
refNode = root
vn = ref
ln = rl
isReference = true
@@ -298,6 +292,7 @@ func ExtractObject[T Buildable[N], N any](ctx context.Context, label string, roo
if h, _, rVal := utils.IsNodeRefValue(vn); h {
ref, fIdx, lerr, nCtx := LocateRefNodeWithContext(ctx, vn, idx)
if ref != nil {
refNode = vn
vn = ref
if fIdx != nil {
idx = fIdx
@@ -331,16 +326,15 @@ func ExtractObject[T Buildable[N], N any](ctx context.Context, label string, roo
// if this is a reference, keep track of the reference in the value
if isReference {
SetReference(n, referenceValue)
SetReference(n, referenceValue, refNode)
}
res := NodeReference[T]{
Value: n,
KeyNode: ln,
ValueNode: vn,
ReferenceNode: isReference,
Reference: referenceValue,
}
res.SetReference(referenceValue, refNode)
// do we want to throw an error as well if circular error reporting is on?
if circError != nil && !idx.AllowCircularReferenceResolving() {
@@ -349,12 +343,13 @@ func ExtractObject[T Buildable[N], N any](ctx context.Context, label string, roo
return res, nil
}
func SetReference(obj any, ref string) {
func SetReference(obj any, ref string, refNode *yaml.Node) {
if obj == nil {
return
}
if r, ok := obj.(IsReferenced); ok {
r.SetReference(ref)
if r, ok := obj.(SetReferencer); ok {
r.SetReference(ref, refNode)
}
}
@@ -429,9 +424,12 @@ func ExtractArray[T Buildable[N], N any](ctx context.Context, label string, root
foundCtx := ctx
foundIndex := idx
var refNode *yaml.Node
if rf, _, rv := utils.IsNodeRefValue(node); rf {
refg, fIdx, err, nCtx := LocateRefEnd(ctx, node, idx, 0)
if refg != nil {
refNode = node
node = refg
localReferenceValue = rv
foundIndex = fIdx
@@ -457,15 +455,16 @@ func ExtractArray[T Buildable[N], N any](ctx context.Context, label string, root
}
if localReferenceValue != "" {
SetReference(n, localReferenceValue)
SetReference(n, localReferenceValue, refNode)
}
items = append(items, ValueReference[T]{
v := ValueReference[T]{
Value: n,
ValueNode: node,
ReferenceNode: localReferenceValue != "",
Reference: localReferenceValue,
})
}
v.SetReference(localReferenceValue, refNode)
items = append(items, v)
}
}
// include circular errors?
@@ -475,23 +474,6 @@ func ExtractArray[T Buildable[N], N any](ctx context.Context, label string, root
return items, ln, vn, nil
}
// ExtractExample will extract a value supplied as an example into a NodeReference. Value can be anything.
// the node value is untyped, so casting will be required when trying to use it.
func ExtractExample(expNode, expLabel *yaml.Node) NodeReference[any] {
ref := NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode}
if utils.IsNodeMap(expNode) {
var decoded map[string]interface{}
_ = expNode.Decode(&decoded)
ref.Value = decoded
}
if utils.IsNodeArray(expNode) {
var decoded []interface{}
_ = expNode.Decode(&decoded)
ref.Value = decoded
}
return ref
}
// ExtractMapNoLookupExtensions will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'NoLookup' part
// refers to the fact that there is no key supplied as part of the extraction, there is no lookup performed and the
// root yaml.Node pointer is used directly. Pass a true bit to includeExtensions to include extension keys in the map.
@@ -502,7 +484,7 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
root *yaml.Node,
idx *index.SpecIndex,
includeExtensions bool,
) (orderedmap.Map[KeyReference[string], ValueReference[PT]], error) {
) (*orderedmap.Map[KeyReference[string], ValueReference[PT]], error) {
valueMap := orderedmap.New[KeyReference[string], ValueReference[PT]]()
var circError error
if utils.IsNodeMap(root) {
@@ -540,10 +522,12 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
var isReference bool
var referenceValue string
var refNode *yaml.Node
// if value is a reference, we have to look it up in the index!
if h, _, rv := utils.IsNodeRefValue(node); h {
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, node, idx)
if ref != nil {
refNode = node
node = ref
isReference = true
referenceValue = rv
@@ -570,19 +554,21 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
return nil, berr
}
if isReference {
SetReference(n, referenceValue)
SetReference(n, referenceValue, refNode)
}
if currentKey != nil {
v := ValueReference[PT]{
Value: n,
ValueNode: node,
}
v.SetReference(referenceValue, refNode)
valueMap.Set(
KeyReference[string]{
Value: currentKey.Value,
KeyNode: currentKey,
},
ValueReference[PT]{
Value: n,
ValueNode: node,
Reference: referenceValue,
},
v,
)
}
}
@@ -591,7 +577,6 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
return valueMap, circError
}
return valueMap, nil
}
// ExtractMapNoLookup will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'NoLookup' part
@@ -603,7 +588,7 @@ func ExtractMapNoLookup[PT Buildable[N], N any](
ctx context.Context,
root *yaml.Node,
idx *index.SpecIndex,
) (orderedmap.Map[KeyReference[string], ValueReference[PT]], error) {
) (*orderedmap.Map[KeyReference[string], ValueReference[PT]], error) {
return ExtractMapNoLookupExtensions[PT, N](ctx, root, idx, false)
}
@@ -624,7 +609,186 @@ func ExtractMapExtensions[PT Buildable[N], N any](
root *yaml.Node,
idx *index.SpecIndex,
extensions bool,
) (orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) {
) (*orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) {
var labelNode, valueNode *yaml.Node
var circError error
root = utils.NodeAlias(root)
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref, fIdx, err, fCtx := LocateRefNodeWithContext(ctx, root, idx)
if ref != nil {
valueNode = ref
labelNode = rl
ctx = fCtx
idx = fIdx
if err != nil {
circError = err
}
} else {
return nil, labelNode, valueNode, fmt.Errorf("map build failed: reference cannot be found: %s",
root.Content[1].Value)
}
} else {
_, labelNode, valueNode = utils.FindKeyNodeFull(label, root.Content)
valueNode = utils.NodeAlias(valueNode)
if valueNode != nil {
if h, _, _ := utils.IsNodeRefValue(valueNode); h {
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, valueNode, idx)
if ref != nil {
valueNode = ref
idx = fIdx
ctx = nCtx
if err != nil {
circError = err
}
} else {
if err != nil {
return nil, labelNode, valueNode, fmt.Errorf("map build failed: reference cannot be found: %s",
err.Error())
}
}
}
}
}
if valueNode != nil {
valueMap := orderedmap.New[KeyReference[string], ValueReference[PT]]()
type buildInput struct {
label *yaml.Node
value *yaml.Node
}
in := make(chan buildInput)
out := make(chan mappingResult[PT])
done := make(chan struct{})
var wg sync.WaitGroup
wg.Add(2) // input and output goroutines.
// TranslatePipeline input.
go func() {
defer func() {
close(in)
wg.Done()
}()
var currentLabelNode *yaml.Node
for i, en := range valueNode.Content {
if !extensions {
if strings.HasPrefix(en.Value, "x-") {
continue // yo, don't pay any attention to extensions, not here anyway.
}
}
en = utils.NodeAlias(en)
if i%2 == 0 {
currentLabelNode = en
continue
}
select {
case in <- buildInput{
label: currentLabelNode,
value: en,
}:
case <-done:
return
}
}
}()
// TranslatePipeline output.
go func() {
for {
result, ok := <-out
if !ok {
break
}
valueMap.Set(result.k, result.v)
}
close(done)
wg.Done()
}()
translateFunc := func(input buildInput) (mappingResult[PT], error) {
foundIndex := idx
foundContext := ctx
en := input.value
var refNode *yaml.Node
var referenceValue string
// check our valueNode isn't a reference still.
if h, _, refVal := utils.IsNodeRefValue(en); h {
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, en, idx)
if ref != nil {
refNode = en
en = ref
referenceValue = refVal
if fIdx != nil {
foundIndex = fIdx
}
foundContext = nCtx
if err != nil {
circError = err
}
} else {
if err != nil {
return mappingResult[PT]{}, fmt.Errorf("flat map build failed: reference cannot be found: %s",
err.Error())
}
}
}
var n PT = new(N)
en = utils.NodeAlias(en)
_ = BuildModel(en, n)
err := n.Build(foundContext, input.label, en, foundIndex)
if err != nil {
return mappingResult[PT]{}, err
}
if referenceValue != "" {
SetReference(n, referenceValue, refNode)
}
v := ValueReference[PT]{
Value: n,
ValueNode: en,
}
v.SetReference(referenceValue, refNode)
return mappingResult[PT]{
k: KeyReference[string]{
KeyNode: input.label,
Value: input.label.Value,
},
v: v,
}, nil
}
err := datamodel.TranslatePipeline[buildInput, mappingResult[PT]](in, out, translateFunc)
wg.Wait()
if err != nil {
return nil, labelNode, valueNode, err
}
if circError != nil && !idx.AllowCircularReferenceResolving() {
return valueMap, labelNode, valueNode, circError
}
return valueMap, labelNode, valueNode, nil
}
return nil, labelNode, valueNode, nil
}
// ExtractMapExtensions will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'label' is
// used to locate the node to be extracted from the root node supplied. Supply a bit to decide if extensions should
// be included or not. required in some use cases.
//
// The second return value is the yaml.Node found for the 'label' and the third return value is the yaml.Node
// found for the value extracted from the label node.
func ExtractMapExtensionsOld[PT Buildable[N], N any](
ctx context.Context,
label string,
root *yaml.Node,
idx *index.SpecIndex,
extensions bool,
) (*orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) {
var referenceValue string
var labelNode, valueNode *yaml.Node
var circError error
@@ -687,7 +851,7 @@ func ExtractMapExtensions[PT Buildable[N], N any](
}
if ref != "" {
SetReference(n, ref)
SetReference(n, ref, nil)
}
c <- mappingResult[PT]{
@@ -698,7 +862,7 @@ func ExtractMapExtensions[PT Buildable[N], N any](
v: ValueReference[PT]{
Value: n,
ValueNode: value,
Reference: ref,
// Reference: ref,
},
}
}
@@ -773,7 +937,7 @@ func ExtractMap[PT Buildable[N], N any](
label string,
root *yaml.Node,
idx *index.SpecIndex,
) (orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) {
) (*orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) {
return ExtractMapExtensions[PT, N](ctx, label, root, idx, false)
}
@@ -781,7 +945,7 @@ func ExtractMap[PT Buildable[N], N any](
//
// Maps
//
// map[string]interface{} for maps
// *orderedmap.Map[string, *yaml.Node] for maps
//
// Slices
//
@@ -790,54 +954,15 @@ func ExtractMap[PT Buildable[N], N any](
// int, float, bool, string
//
// int64, float64, bool, string
func ExtractExtensions(root *yaml.Node) map[KeyReference[string]]ValueReference[any] {
func ExtractExtensions(root *yaml.Node) *orderedmap.Map[KeyReference[string], ValueReference[*yaml.Node]] {
root = utils.NodeAlias(root)
extensions := utils.FindExtensionNodes(root.Content)
extensionMap := make(map[KeyReference[string]]ValueReference[any])
extensionMap := orderedmap.New[KeyReference[string], ValueReference[*yaml.Node]]()
for _, ext := range extensions {
if utils.IsNodeMap(ext.Value) {
var v interface{}
_ = ext.Value.Decode(&v)
extensionMap[KeyReference[string]{
extensionMap.Set(KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: v, ValueNode: ext.Value}
}
if utils.IsNodeStringValue(ext.Value) {
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: ext.Value.Value, ValueNode: ext.Value}
}
if utils.IsNodeFloatValue(ext.Value) {
fv, _ := strconv.ParseFloat(ext.Value.Value, 64)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: fv, ValueNode: ext.Value}
}
if utils.IsNodeIntValue(ext.Value) {
iv, _ := strconv.ParseInt(ext.Value.Value, 10, 64)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: iv, ValueNode: ext.Value}
}
if utils.IsNodeBoolValue(ext.Value) {
bv, _ := strconv.ParseBool(ext.Value.Value)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: bv, ValueNode: ext.Value}
}
if utils.IsNodeArray(ext.Value) {
var v []interface{}
_ = ext.Value.Decode(&v)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: v, ValueNode: ext.Value}
}
}, ValueReference[*yaml.Node]{Value: ext.Value, ValueNode: ext.Value})
}
return extensionMap
}
@@ -869,6 +994,10 @@ func GenerateHashString(v any) string {
return fmt.Sprintf(HASH, h.Hash())
}
}
if n, ok := v.(*yaml.Node); ok {
b, _ := yaml.Marshal(n)
return fmt.Sprintf(HASH, sha256.Sum256(b))
}
// if we get here, we're a primitive, check if we're a pointer and de-point
if reflect.TypeOf(v).Kind() == reflect.Ptr {
v = reflect.ValueOf(v).Elem().Interface()
@@ -876,6 +1005,15 @@ func GenerateHashString(v any) string {
return fmt.Sprintf(HASH, sha256.Sum256([]byte(fmt.Sprint(v))))
}
func ValueToString(v any) string {
if n, ok := v.(*yaml.Node); ok {
b, _ := yaml.Marshal(n)
return string(b)
}
return fmt.Sprint(v)
}
// LocateRefEnd will perform a complete lookup for a $ref node. This function searches the entire index for
// the reference being supplied. If there is a match found, the reference *yaml.Node is returned.
// the function operates recursively and will keep iterating through references until it finds a non-reference

View File

@@ -7,51 +7,52 @@ import (
"context"
"crypto/sha256"
"fmt"
"golang.org/x/sync/syncmap"
"gopkg.in/yaml.v3"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"golang.org/x/sync/syncmap"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFindItemInMap(t *testing.T) {
v := make(map[KeyReference[string]]ValueReference[string])
v[KeyReference[string]{
func TestFindItemInOrderedMap(t *testing.T) {
v := orderedmap.New[KeyReference[string], ValueReference[string]]()
v.Set(KeyReference[string]{
Value: "pizza",
}] = ValueReference[string]{
}, ValueReference[string]{
Value: "pie",
}
assert.Equal(t, "pie", FindItemInMap("pizza", v).Value)
})
assert.Equal(t, "pie", FindItemInOrderedMap("pizza", v).Value)
}
func TestFindItemInMap_WrongCase(t *testing.T) {
v := make(map[KeyReference[string]]ValueReference[string])
v[KeyReference[string]{
func TestFindItemInOrderedMap_WrongCase(t *testing.T) {
v := orderedmap.New[KeyReference[string], ValueReference[string]]()
v.Set(KeyReference[string]{
Value: "pizza",
}] = ValueReference[string]{
}, ValueReference[string]{
Value: "pie",
}
assert.Equal(t, "pie", FindItemInMap("PIZZA", v).Value)
})
assert.Equal(t, "pie", FindItemInOrderedMap("PIZZA", v).Value)
}
func TestFindItemInMap_Error(t *testing.T) {
v := make(map[KeyReference[string]]ValueReference[string])
v[KeyReference[string]{
func TestFindItemInOrderedMap_Error(t *testing.T) {
v := orderedmap.New[KeyReference[string], ValueReference[string]]()
v.Set(KeyReference[string]{
Value: "pizza",
}] = ValueReference[string]{
}, ValueReference[string]{
Value: "pie",
}
assert.Nil(t, FindItemInMap("nuggets", v))
})
assert.Nil(t, FindItemInOrderedMap("nuggets", v))
}
func TestLocateRefNode(t *testing.T) {
yml := `components:
schemas:
cake:
@@ -69,11 +70,9 @@ func TestLocateRefNode(t *testing.T) {
located, _, _ := LocateRefNode(cNode.Content[0], idx)
assert.NotNil(t, located)
}
func TestLocateRefNode_BadNode(t *testing.T) {
yml := `components:
schemas:
cake:
@@ -94,11 +93,9 @@ func TestLocateRefNode_BadNode(t *testing.T) {
// should both be empty.
assert.Nil(t, located)
assert.Nil(t, err)
}
func TestLocateRefNode_Path(t *testing.T) {
yml := `paths:
/burger/time:
description: hello`
@@ -115,11 +112,9 @@ func TestLocateRefNode_Path(t *testing.T) {
located, _, _ := LocateRefNode(cNode.Content[0], idx)
assert.NotNil(t, located)
}
func TestLocateRefNode_Path_NotFound(t *testing.T) {
yml := `paths:
/burger/time:
description: hello`
@@ -137,7 +132,6 @@ func TestLocateRefNode_Path_NotFound(t *testing.T) {
located, _, err := LocateRefNode(cNode.Content[0], idx)
assert.Nil(t, located)
assert.Error(t, err)
}
type pizza struct {
@@ -149,7 +143,6 @@ func (p *pizza) Build(_ context.Context, _, _ *yaml.Node, _ *index.SpecIndex) er
}
func TestExtractObject(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -173,7 +166,6 @@ func TestExtractObject(t *testing.T) {
}
func TestExtractObject_Ref(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -197,7 +189,6 @@ func TestExtractObject_Ref(t *testing.T) {
}
func TestExtractObject_DoubleRef(t *testing.T) {
yml := `components:
schemas:
cake:
@@ -282,7 +273,6 @@ func TestExtractObject_DoubleRef_Circular_Fail(t *testing.T) {
}
func TestExtractObject_DoubleRef_Circular_Direct(t *testing.T) {
yml := `components:
schemas:
loopy:
@@ -312,7 +302,6 @@ func TestExtractObject_DoubleRef_Circular_Direct(t *testing.T) {
}
func TestExtractObject_DoubleRef_Circular_Direct_Fail(t *testing.T) {
yml := `components:
schemas:
loopy:
@@ -338,7 +327,6 @@ func TestExtractObject_DoubleRef_Circular_Direct_Fail(t *testing.T) {
_, err := ExtractObject[*pizza](context.Background(), "tags", cNode.Content[0], idx)
assert.Error(t, err)
}
type test_borked struct {
@@ -374,7 +362,6 @@ func (t *test_Good) Build(_ context.Context, _, root *yaml.Node, idx *index.Spec
}
func TestExtractObject_BadLowLevelModel(t *testing.T) {
yml := `components:
schemas:
hey:`
@@ -391,11 +378,9 @@ func TestExtractObject_BadLowLevelModel(t *testing.T) {
_, err := ExtractObject[*test_noGood](context.Background(), "thing", &cNode, idx)
assert.Error(t, err)
}
func TestExtractObject_BadBuild(t *testing.T) {
yml := `components:
schemas:
hey:`
@@ -412,11 +397,9 @@ func TestExtractObject_BadBuild(t *testing.T) {
_, err := ExtractObject[*test_almostGood](context.Background(), "thing", &cNode, idx)
assert.Error(t, err)
}
func TestExtractObject_BadLabel(t *testing.T) {
yml := `components:
schemas:
hey:`
@@ -434,11 +417,9 @@ func TestExtractObject_BadLabel(t *testing.T) {
res, err := ExtractObject[*test_almostGood](context.Background(), "ding", &cNode, idx)
assert.Nil(t, res.Value)
assert.NoError(t, err)
}
func TestExtractObject_PathIsCircular(t *testing.T) {
// first we need an index.
yml := `paths:
'/something/here':
@@ -467,11 +448,9 @@ func TestExtractObject_PathIsCircular(t *testing.T) {
res, err := ExtractObject[*test_Good](context.Background(), "thing", &rootNode, idx)
assert.NotNil(t, res.Value)
assert.Error(t, err) // circular error would have been thrown.
}
func TestExtractObject_PathIsCircular_IgnoreErrors(t *testing.T) {
// first we need an index.
yml := `paths:
'/something/here':
@@ -503,11 +482,9 @@ func TestExtractObject_PathIsCircular_IgnoreErrors(t *testing.T) {
res, err := ExtractObject[*test_Good](context.Background(), "thing", &rootNode, idx)
assert.NotNil(t, res.Value)
assert.NoError(t, err) // circular error would have been thrown, but we're ignoring them.
}
func TestExtractObjectRaw(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -530,7 +507,6 @@ func TestExtractObjectRaw(t *testing.T) {
}
func TestExtractObjectRaw_With_Ref(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -555,7 +531,6 @@ func TestExtractObjectRaw_With_Ref(t *testing.T) {
}
func TestExtractObjectRaw_Ref_Circular(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -579,11 +554,9 @@ func TestExtractObjectRaw_Ref_Circular(t *testing.T) {
tag, err, _, _ := ExtractObjectRaw[*pizza](context.Background(), nil, cNode.Content[0], idx)
assert.Error(t, err)
assert.NotNil(t, tag)
}
func TestExtractObjectRaw_RefBroken(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -601,11 +574,9 @@ func TestExtractObjectRaw_RefBroken(t *testing.T) {
tag, err, _, _ := ExtractObjectRaw[*pizza](context.Background(), nil, cNode.Content[0], idx)
assert.Error(t, err)
assert.Nil(t, tag)
}
func TestExtractObjectRaw_Ref_NonBuildable(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -622,11 +593,9 @@ func TestExtractObjectRaw_Ref_NonBuildable(t *testing.T) {
_, err, _, _ := ExtractObjectRaw[*test_noGood](context.Background(), nil, cNode.Content[0], idx)
assert.Error(t, err)
}
func TestExtractObjectRaw_Ref_AlmostBuildable(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -643,11 +612,9 @@ func TestExtractObjectRaw_Ref_AlmostBuildable(t *testing.T) {
_, err, _, _ := ExtractObjectRaw[*test_almostGood](context.Background(), nil, cNode.Content[0], idx)
assert.Error(t, err)
}
func TestExtractArray(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -675,7 +642,6 @@ func TestExtractArray(t *testing.T) {
}
func TestExtractArray_Ref(t *testing.T) {
yml := `components:
schemas:
things:
@@ -702,7 +668,6 @@ func TestExtractArray_Ref(t *testing.T) {
}
func TestExtractArray_Ref_Unbuildable(t *testing.T) {
yml := `components:
schemas:
things:
@@ -726,7 +691,6 @@ func TestExtractArray_Ref_Unbuildable(t *testing.T) {
}
func TestExtractArray_Ref_Circular(t *testing.T) {
yml := `components:
schemas:
thongs:
@@ -754,7 +718,6 @@ func TestExtractArray_Ref_Circular(t *testing.T) {
}
func TestExtractArray_Ref_Bad(t *testing.T) {
yml := `components:
schemas:
thongs:
@@ -782,7 +745,6 @@ func TestExtractArray_Ref_Bad(t *testing.T) {
}
func TestExtractArray_Ref_Nested(t *testing.T) {
yml := `components:
schemas:
thongs:
@@ -811,7 +773,6 @@ func TestExtractArray_Ref_Nested(t *testing.T) {
}
func TestExtractArray_Ref_Nested_Circular(t *testing.T) {
yml := `components:
schemas:
thongs:
@@ -840,7 +801,6 @@ func TestExtractArray_Ref_Nested_Circular(t *testing.T) {
}
func TestExtractArray_Ref_Nested_BadRef(t *testing.T) {
yml := `components:
schemas:
thongs:
@@ -867,7 +827,6 @@ func TestExtractArray_Ref_Nested_BadRef(t *testing.T) {
}
func TestExtractArray_Ref_Nested_CircularFlat(t *testing.T) {
yml := `components:
schemas:
thongs:
@@ -896,7 +855,6 @@ func TestExtractArray_Ref_Nested_CircularFlat(t *testing.T) {
}
func TestExtractArray_BadBuild(t *testing.T) {
yml := `components:
schemas:
thongs:`
@@ -918,7 +876,6 @@ func TestExtractArray_BadBuild(t *testing.T) {
}
func TestExtractArray_BadRefPropsTupe(t *testing.T) {
yml := `components:
parameters:
cakes:
@@ -940,45 +897,7 @@ func TestExtractArray_BadRefPropsTupe(t *testing.T) {
assert.Len(t, things, 0)
}
func TestExtractExample_String(t *testing.T) {
yml := `hi`
var e yaml.Node
_ = yaml.Unmarshal([]byte(yml), &e)
exp := ExtractExample(e.Content[0], e.Content[0])
assert.NotNil(t, exp.Value)
assert.Equal(t, "hi", exp.Value)
}
func TestExtractExample_Map(t *testing.T) {
yml := `one: two`
var e yaml.Node
_ = yaml.Unmarshal([]byte(yml), &e)
exp := ExtractExample(e.Content[0], e.Content[0])
assert.NotNil(t, exp.Value)
if n, ok := exp.Value.(map[string]interface{}); ok {
assert.Equal(t, "two", n["one"])
} else {
panic("example unpacked incorrectly.")
}
}
func TestExtractExample_Array(t *testing.T) {
yml := `- hello`
var e yaml.Node
_ = yaml.Unmarshal([]byte(yml), &e)
exp := ExtractExample(e.Content[0], e.Content[0])
assert.NotNil(t, exp.Value)
if n, ok := exp.Value.([]interface{}); ok {
assert.Equal(t, "hello", n[0])
} else {
panic("example unpacked incorrectly.")
}
}
func TestExtractMapFlatNoLookup(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
@@ -997,11 +916,9 @@ one:
things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, 1, orderedmap.Len(things))
}
func TestExtractMap_NoLookupWithExtensions(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
@@ -1031,7 +948,6 @@ one:
}
func TestExtractMap_NoLookupWithExtensions_UsingMerge(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
@@ -1053,11 +969,9 @@ one:
things, err := ExtractMapNoLookupExtensions[*test_Good](context.Background(), cNode.Content[0], idx, true)
assert.NoError(t, err)
assert.Equal(t, 4, orderedmap.Len(things))
}
func TestExtractMap_NoLookupWithoutExtensions(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
@@ -1083,7 +997,6 @@ one:
}
func TestExtractMap_WithExtensions(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
@@ -1105,7 +1018,6 @@ one:
}
func TestExtractMap_WithoutExtensions(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
@@ -1127,7 +1039,6 @@ one:
}
func TestExtractMapFlatNoLookup_Ref(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -1149,11 +1060,9 @@ one:
things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, 1, orderedmap.Len(things))
}
func TestExtractMapFlatNoLookup_Ref_Bad(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -1175,11 +1084,9 @@ one:
things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx)
assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things))
}
func TestExtractMapFlatNoLookup_Ref_Circular(t *testing.T) {
yml := `components:
schemas:
thongs:
@@ -1207,11 +1114,9 @@ one:
things, err := ExtractMapNoLookup[*test_Good](context.Background(), cNode.Content[0], idx)
assert.Error(t, err)
assert.Equal(t, 1, orderedmap.Len(things))
}
func TestExtractMapFlatNoLookup_Ref_BadBuild(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -1233,11 +1138,9 @@ hello:
things, err := ExtractMapNoLookup[*test_noGood](context.Background(), cNode.Content[0], idx)
assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things))
}
func TestExtractMapFlatNoLookup_Ref_AlmostBuild(t *testing.T) {
yml := `components:
schemas:
pizza:
@@ -1259,11 +1162,9 @@ one:
things, err := ExtractMapNoLookup[*test_almostGood](context.Background(), cNode.Content[0], idx)
assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things))
}
func TestExtractMapFlat(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
@@ -1282,11 +1183,9 @@ one:
things, _, _, err := ExtractMap[*test_Good](context.Background(), "one", cNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, 1, orderedmap.Len(things))
}
func TestExtractMapFlat_Ref(t *testing.T) {
yml := `components:
schemas:
stank:
@@ -1313,11 +1212,9 @@ one:
for pair := orderedmap.First(things); pair != nil; pair = pair.Next() {
assert.Equal(t, 99, pair.Value().Value.AlmostWork.Value)
}
}
func TestExtractMapFlat_DoubleRef(t *testing.T) {
yml := `components:
schemas:
stank:
@@ -1346,7 +1243,6 @@ func TestExtractMapFlat_DoubleRef(t *testing.T) {
}
func TestExtractMapFlat_DoubleRef_Error(t *testing.T) {
yml := `components:
schemas:
stank:
@@ -1369,11 +1265,9 @@ func TestExtractMapFlat_DoubleRef_Error(t *testing.T) {
things, _, _, err := ExtractMap[*test_almostGood](context.Background(), "one", cNode.Content[0], idx)
assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things))
}
func TestExtractMapFlat_DoubleRef_Error_NotFound(t *testing.T) {
yml := `components:
schemas:
stank:
@@ -1396,11 +1290,9 @@ func TestExtractMapFlat_DoubleRef_Error_NotFound(t *testing.T) {
things, _, _, err := ExtractMap[*test_almostGood](context.Background(), "one", cNode.Content[0], idx)
assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things))
}
func TestExtractMapFlat_DoubleRef_Circles(t *testing.T) {
yml := `components:
schemas:
stonk:
@@ -1428,11 +1320,9 @@ func TestExtractMapFlat_DoubleRef_Circles(t *testing.T) {
things, _, _, err := ExtractMap[*test_Good](context.Background(), "one", cNode.Content[0], idx)
assert.Error(t, err)
assert.Equal(t, 1, orderedmap.Len(things))
}
func TestExtractMapFlat_Ref_Error(t *testing.T) {
yml := `components:
schemas:
stank:
@@ -1455,11 +1345,9 @@ func TestExtractMapFlat_Ref_Error(t *testing.T) {
things, _, _, err := ExtractMap[*test_almostGood](context.Background(), "one", cNode.Content[0], idx)
assert.Error(t, err)
assert.Zero(t, orderedmap.Len(things))
}
func TestExtractMapFlat_Ref_Circ_Error(t *testing.T) {
yml := `components:
schemas:
stink:
@@ -1488,7 +1376,6 @@ func TestExtractMapFlat_Ref_Circ_Error(t *testing.T) {
}
func TestExtractMapFlat_Ref_Nested_Circ_Error(t *testing.T) {
yml := `components:
schemas:
stink:
@@ -1518,7 +1405,6 @@ func TestExtractMapFlat_Ref_Nested_Circ_Error(t *testing.T) {
}
func TestExtractMapFlat_Ref_Nested_Error(t *testing.T) {
yml := `components:
schemas:
stink:
@@ -1544,7 +1430,6 @@ func TestExtractMapFlat_Ref_Nested_Error(t *testing.T) {
}
func TestExtractMapFlat_BadKey_Ref_Nested_Error(t *testing.T) {
yml := `components:
schemas:
stink:
@@ -1570,7 +1455,6 @@ func TestExtractMapFlat_BadKey_Ref_Nested_Error(t *testing.T) {
}
func TestExtractMapFlat_Ref_Bad(t *testing.T) {
yml := `components:
schemas:
stink:
@@ -1599,7 +1483,6 @@ func TestExtractMapFlat_Ref_Bad(t *testing.T) {
}
func TestExtractExtensions(t *testing.T) {
yml := `x-bing: ding
x-bong: 1
x-ling: true
@@ -1612,25 +1495,27 @@ x-tacos: [1,2,3]`
_ = yaml.Unmarshal([]byte(yml), &idxNode)
r := ExtractExtensions(idxNode.Content[0])
assert.Len(t, r, 6)
for i := range r {
switch i.Value {
assert.Equal(t, 6, orderedmap.Len(r))
for pair := orderedmap.First(r); pair != nil; pair = pair.Next() {
var v any
_ = pair.Value().Value.Decode(&v)
switch pair.Key().Value {
case "x-bing":
assert.Equal(t, "ding", r[i].Value)
assert.Equal(t, "ding", v)
case "x-bong":
assert.Equal(t, int64(1), r[i].Value)
assert.Equal(t, 1, v)
case "x-ling":
assert.Equal(t, true, r[i].Value)
assert.Equal(t, true, v)
case "x-long":
assert.Equal(t, 0.99, r[i].Value)
assert.Equal(t, 0.99, v)
case "x-fish":
if a, ok := r[i].Value.(map[string]interface{}); ok {
assert.Equal(t, "yeah", a["woo"])
} else {
panic("should not fail casting")
}
var m map[string]any
err := pair.Value().Value.Decode(&m)
require.NoError(t, err)
assert.Equal(t, "yeah", m["woo"])
case "x-tacos":
assert.Len(t, r[i].Value, 3)
assert.Len(t, v, 3)
}
}
}
@@ -1650,8 +1535,8 @@ func (f test_fresh) Hash() [32]byte {
}
return sha256.Sum256([]byte(strings.Join(data, "|")))
}
func TestAreEqual(t *testing.T) {
func TestAreEqual(t *testing.T) {
var hey *test_fresh
assert.True(t, AreEqual(test_fresh{val: "hello"}, test_fresh{val: "hello"}))
@@ -1664,7 +1549,6 @@ func TestAreEqual(t *testing.T) {
}
func TestGenerateHashString(t *testing.T) {
assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
GenerateHashString(test_fresh{val: "hello"}))
@@ -1676,46 +1560,39 @@ func TestGenerateHashString(t *testing.T) {
assert.Equal(t, "",
GenerateHashString(nil))
}
func TestGenerateHashString_Pointer(t *testing.T) {
val := true
assert.Equal(t, "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b",
GenerateHashString(test_fresh{thang: &val}))
assert.Equal(t, "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b",
GenerateHashString(&val))
}
func TestSetReference(t *testing.T) {
type testObj struct {
*Reference
}
n := testObj{Reference: &Reference{}}
SetReference(&n, "#/pigeon/street")
SetReference(&n, "#/pigeon/street", nil)
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")
SetReference(nil, "#/pigeon/street", nil)
assert.NotEqual(t, "#/pigeon/street", n.GetReference())
}
func TestLocateRefNode_CurrentPathKey_HttpLink(t *testing.T) {
no := yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
@@ -1741,7 +1618,6 @@ func TestLocateRefNode_CurrentPathKey_HttpLink(t *testing.T) {
}
func TestLocateRefNode_CurrentPathKey_HttpLink_Local(t *testing.T) {
no := yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
@@ -1767,7 +1643,6 @@ func TestLocateRefNode_CurrentPathKey_HttpLink_Local(t *testing.T) {
}
func TestLocateRefNode_CurrentPathKey_HttpLink_RemoteCtx(t *testing.T) {
no := yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
@@ -1792,7 +1667,6 @@ func TestLocateRefNode_CurrentPathKey_HttpLink_RemoteCtx(t *testing.T) {
}
func TestLocateRefNode_CurrentPathKey_HttpLink_RemoteCtx_WithPath(t *testing.T) {
no := yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
@@ -1817,7 +1691,6 @@ func TestLocateRefNode_CurrentPathKey_HttpLink_RemoteCtx_WithPath(t *testing.T)
}
func TestLocateRefNode_CurrentPathKey_Path_Link(t *testing.T) {
no := yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
@@ -1842,7 +1715,6 @@ func TestLocateRefNode_CurrentPathKey_Path_Link(t *testing.T) {
}
func TestLocateRefNode_CurrentPathKey_Path_URL(t *testing.T) {
no := yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
@@ -1869,7 +1741,6 @@ func TestLocateRefNode_CurrentPathKey_Path_URL(t *testing.T) {
}
func TestLocateRefNode_CurrentPathKey_DeeperPath_URL(t *testing.T) {
no := yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
@@ -1896,7 +1767,6 @@ func TestLocateRefNode_CurrentPathKey_DeeperPath_URL(t *testing.T) {
}
func TestLocateRefNode_NoExplode(t *testing.T) {
no := yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
@@ -1923,7 +1793,6 @@ func TestLocateRefNode_NoExplode(t *testing.T) {
}
func TestLocateRefNode_NoExplode_HTTP(t *testing.T) {
no := yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
@@ -1951,7 +1820,6 @@ func TestLocateRefNode_NoExplode_HTTP(t *testing.T) {
}
func TestLocateRefNode_NoExplode_NoSpecPath(t *testing.T) {
no := yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
@@ -1979,7 +1847,6 @@ func TestLocateRefNode_NoExplode_NoSpecPath(t *testing.T) {
}
func TestLocateRefNode_DoARealLookup(t *testing.T) {
no := yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{
@@ -2020,7 +1887,6 @@ func TestLocateRefNode_DoARealLookup(t *testing.T) {
}
func TestLocateRefEndNoRef_NoName(t *testing.T) {
r := &yaml.Node{Content: []*yaml.Node{{Kind: yaml.ScalarNode, Value: "$ref"}, {Kind: yaml.ScalarNode, Value: ""}}}
n, i, e, c := LocateRefEnd(nil, r, nil, 0)
assert.Nil(t, n)
@@ -2030,7 +1896,6 @@ func TestLocateRefEndNoRef_NoName(t *testing.T) {
}
func TestLocateRefEndNoRef(t *testing.T) {
r := &yaml.Node{Content: []*yaml.Node{{Kind: yaml.ScalarNode, Value: "$ref"}, {Kind: yaml.ScalarNode, Value: "cake"}}}
n, i, e, c := LocateRefEnd(context.Background(), r, index.NewSpecIndexWithConfig(r, index.CreateClosedAPIIndexConfig()), 0)
assert.Nil(t, n)
@@ -2049,7 +1914,6 @@ func TestLocateRefEnd_TooDeep(t *testing.T) {
}
func TestLocateRefEnd_Loop(t *testing.T) {
yml, _ := os.ReadFile("../../test_specs/first.yaml")
var bsn yaml.Node
_ = yaml.Unmarshal(yml, &bsn)
@@ -2094,7 +1958,6 @@ func TestLocateRefEnd_Loop(t *testing.T) {
}
func TestLocateRefEnd_Loop_WithResolve(t *testing.T) {
yml, _ := os.ReadFile("../../test_specs/first.yaml")
var bsn yaml.Node
_ = yaml.Unmarshal(yml, &bsn)
@@ -2139,7 +2002,6 @@ func TestLocateRefEnd_Loop_WithResolve(t *testing.T) {
}
func TestLocateRefEnd_Empty(t *testing.T) {
yml, _ := os.ReadFile("../../test_specs/first.yaml")
var bsn yaml.Node
_ = yaml.Unmarshal(yml, &bsn)
@@ -2184,7 +2046,6 @@ func TestLocateRefEnd_Empty(t *testing.T) {
}
func TestArray_NotRefNotArray(t *testing.T) {
yml := ``
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
@@ -2201,5 +2062,4 @@ func TestArray_NotRefNotArray(t *testing.T) {
assert.Error(t, err)
assert.Equal(t, err.Error(), "array build failed, input is not an array, line 2, column 3")
assert.Len(t, things, 0)
}

View File

@@ -78,72 +78,63 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er
switch field.Type() {
case reflect.TypeOf(map[string]NodeReference[any]{}):
case reflect.TypeOf(orderedmap.New[string, NodeReference[*yaml.Node]]()):
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := make(map[string]NodeReference[any])
items := orderedmap.New[string, NodeReference[*yaml.Node]]()
var currentLabel string
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
currentLabel = sliceItem.Value
continue
}
var decoded map[string]interface{}
// I cannot think of a way to make this error out by this point.
_ = sliceItem.Decode(&decoded)
items[currentLabel] = NodeReference[any]{
Value: decoded,
items.Set(currentLabel, NodeReference[*yaml.Node]{
Value: sliceItem,
ValueNode: sliceItem,
KeyNode: valueNode,
}
})
}
field.Set(reflect.ValueOf(items))
}
}
case reflect.TypeOf(map[string]NodeReference[string]{}):
case reflect.TypeOf(orderedmap.New[string, NodeReference[string]]()):
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := make(map[string]NodeReference[string])
items := orderedmap.New[string, NodeReference[string]]()
var currentLabel string
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
currentLabel = sliceItem.Value
continue
}
items[currentLabel] = NodeReference[string]{
items.Set(currentLabel, NodeReference[string]{
Value: fmt.Sprintf("%v", sliceItem.Value),
ValueNode: sliceItem,
KeyNode: valueNode,
}
})
}
field.Set(reflect.ValueOf(items))
}
}
case reflect.TypeOf(NodeReference[any]{}):
case reflect.TypeOf(NodeReference[*yaml.Node]{}):
var decoded interface{}
_ = valueNode.Decode(&decoded)
if field.CanSet() {
or := NodeReference[any]{Value: decoded, ValueNode: valueNode, KeyNode: keyNode}
or := NodeReference[*yaml.Node]{Value: valueNode, ValueNode: valueNode, KeyNode: keyNode}
field.Set(reflect.ValueOf(or))
}
case reflect.TypeOf([]NodeReference[any]{}):
case reflect.TypeOf([]NodeReference[*yaml.Node]{}):
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []NodeReference[any]
var items []NodeReference[*yaml.Node]
for _, sliceItem := range valueNode.Content {
var decoded map[string]interface{}
err := sliceItem.Decode(&decoded)
if err != nil {
return err
}
items = append(items, NodeReference[any]{
Value: decoded,
items = append(items, NodeReference[*yaml.Node]{
Value: sliceItem,
ValueNode: sliceItem,
KeyNode: valueNode,
})
@@ -341,56 +332,8 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er
}
// helper for unpacking string maps.
case reflect.TypeOf(map[KeyReference[string]]ValueReference[string]{}):
case reflect.TypeOf(orderedmap.New[KeyReference[string], ValueReference[string]]()):
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := make(map[KeyReference[string]]ValueReference[string])
var cf *yaml.Node
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
cf = sliceItem
continue
}
items[KeyReference[string]{
Value: cf.Value,
KeyNode: cf,
}] = ValueReference[string]{
Value: sliceItem.Value,
ValueNode: sliceItem,
}
}
field.Set(reflect.ValueOf(items))
}
}
case reflect.TypeOf(KeyReference[map[KeyReference[string]]ValueReference[string]]{}):
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := make(map[KeyReference[string]]ValueReference[string])
var cf *yaml.Node
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
cf = sliceItem
continue
}
items[KeyReference[string]{
Value: cf.Value,
KeyNode: cf,
}] = ValueReference[string]{
Value: sliceItem.Value,
ValueNode: sliceItem,
}
}
ref := KeyReference[map[KeyReference[string]]ValueReference[string]]{
Value: items,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(ref))
}
}
case reflect.TypeOf(NodeReference[orderedmap.Map[KeyReference[string], ValueReference[string]]]{}):
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := orderedmap.New[KeyReference[string], ValueReference[string]]()
@@ -408,7 +351,55 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er
ValueNode: sliceItem,
})
}
ref := NodeReference[orderedmap.Map[KeyReference[string], ValueReference[string]]]{
field.Set(reflect.ValueOf(items))
}
}
case reflect.TypeOf(KeyReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]{}):
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := orderedmap.New[KeyReference[string], ValueReference[string]]()
var cf *yaml.Node
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
cf = sliceItem
continue
}
items.Set(KeyReference[string]{
Value: cf.Value,
KeyNode: cf,
}, ValueReference[string]{
Value: sliceItem.Value,
ValueNode: sliceItem,
})
}
ref := KeyReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]{
Value: items,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(ref))
}
}
case reflect.TypeOf(NodeReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]{}):
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := orderedmap.New[KeyReference[string], ValueReference[string]]()
var cf *yaml.Node
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
cf = sliceItem
continue
}
items.Set(KeyReference[string]{
Value: cf.Value,
KeyNode: cf,
}, ValueReference[string]{
Value: sliceItem.Value,
ValueNode: sliceItem,
})
}
ref := NodeReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]{
Value: items,
KeyNode: keyNode,
ValueNode: valueNode,
@@ -436,34 +427,18 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er
}
}
case reflect.TypeOf(NodeReference[[]ValueReference[any]]{}):
case reflect.TypeOf(NodeReference[[]ValueReference[*yaml.Node]]{}):
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []ValueReference[any]
var items []ValueReference[*yaml.Node]
for _, sliceItem := range valueNode.Content {
var val any
if utils.IsNodeIntValue(sliceItem) || utils.IsNodeFloatValue(sliceItem) {
if utils.IsNodeIntValue(sliceItem) {
val, _ = strconv.ParseInt(sliceItem.Value, 10, 64)
} else {
val, _ = strconv.ParseFloat(sliceItem.Value, 64)
}
}
if utils.IsNodeBoolValue(sliceItem) {
val, _ = strconv.ParseBool(sliceItem.Value)
}
if utils.IsNodeStringValue(sliceItem) {
val = sliceItem.Value
}
items = append(items, ValueReference[any]{
Value: val,
items = append(items, ValueReference[*yaml.Node]{
Value: sliceItem,
ValueNode: sliceItem,
})
}
n := NodeReference[[]ValueReference[any]]{
n := NodeReference[[]ValueReference[*yaml.Node]]{
Value: items,
KeyNode: keyNode,
ValueNode: valueNode,

View File

@@ -25,11 +25,11 @@ type hotdog struct {
Temps []NodeReference[int]
HighTemps []NodeReference[int64]
Buns []NodeReference[bool]
UnknownElements NodeReference[any]
LotsOfUnknowns []NodeReference[any]
Where map[string]NodeReference[any]
There map[string]NodeReference[string]
AllTheThings NodeReference[orderedmap.Map[KeyReference[string], ValueReference[string]]]
UnknownElements NodeReference[*yaml.Node]
LotsOfUnknowns []NodeReference[*yaml.Node]
Where *orderedmap.Map[string, NodeReference[*yaml.Node]]
There *orderedmap.Map[string, NodeReference[string]]
AllTheThings NodeReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]
}
func TestBuildModel_Mismatch(t *testing.T) {
@@ -126,11 +126,15 @@ allTheThings:
assert.Equal(t, int64(7392837462032342), hd.MaxTempHigh.Value)
assert.Equal(t, 2, hd.Temps[1].Value)
assert.Equal(t, 27, hd.Temps[1].ValueNode.Line)
assert.Len(t, hd.UnknownElements.Value, 2)
var unknownElements map[string]any
_ = hd.UnknownElements.Value.Decode(&unknownElements)
assert.Len(t, unknownElements, 2)
assert.Len(t, hd.LotsOfUnknowns, 3)
assert.Len(t, hd.Where, 2)
assert.Len(t, hd.There, 2)
assert.Equal(t, "bear", hd.There["care"].Value)
assert.Equal(t, 2, orderedmap.Len(hd.Where))
assert.Equal(t, 2, orderedmap.Len(hd.There))
assert.Equal(t, "bear", hd.There.GetOrZero("care").Value)
assert.Equal(t, 324938249028.98234892374892374923874823974, hd.Mustard.Value)
allTheThings := hd.AllTheThings.Value
@@ -201,27 +205,9 @@ thing: yeah`
assert.Equal(t, "yeah", ins.Thing.Value)
}
func TestSetField_NodeRefAny_Error(t *testing.T) {
type internal struct {
Thing []NodeReference[any]
}
yml := `thing:
- 999
- false`
ins := new(internal)
var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins)
assert.Error(t, try)
}
func TestSetField_MapHelperWrapped(t *testing.T) {
type internal struct {
Thing KeyReference[map[KeyReference[string]]ValueReference[string]]
Thing KeyReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]
}
yml := `thing:
@@ -236,12 +222,12 @@ func TestSetField_MapHelperWrapped(t *testing.T) {
try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try)
assert.Len(t, ins.Thing.Value, 3)
assert.Equal(t, 3, orderedmap.Len(ins.Thing.Value))
}
func TestSetField_MapHelper(t *testing.T) {
type internal struct {
Thing map[KeyReference[string]]ValueReference[string]
Thing *orderedmap.Map[KeyReference[string], ValueReference[string]]
}
yml := `thing:
@@ -256,7 +242,7 @@ func TestSetField_MapHelper(t *testing.T) {
try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try)
assert.Len(t, ins.Thing, 3)
assert.Equal(t, 3, orderedmap.Len(ins.Thing))
}
func TestSetField_ArrayHelper(t *testing.T) {
@@ -281,7 +267,7 @@ func TestSetField_ArrayHelper(t *testing.T) {
func TestSetField_Enum_Helper(t *testing.T) {
type internal struct {
Thing NodeReference[[]ValueReference[any]]
Thing NodeReference[[]ValueReference[*yaml.Node]]
}
yml := `thing:
@@ -324,7 +310,7 @@ func TestSetField_Default_Helper(t *testing.T) {
func TestHandleSlicesOfInts(t *testing.T) {
type internal struct {
Thing NodeReference[[]ValueReference[any]]
Thing NodeReference[[]ValueReference[*yaml.Node]]
}
yml := `thing:
@@ -338,13 +324,20 @@ func TestHandleSlicesOfInts(t *testing.T) {
try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try)
assert.Equal(t, int64(5), ins.Thing.Value[0].Value)
assert.Equal(t, 1.234, ins.Thing.Value[1].Value)
var thing0 int64
_ = ins.Thing.GetValue()[0].Value.Decode(&thing0)
var thing1 float64
_ = ins.Thing.GetValue()[1].Value.Decode(&thing1)
assert.Equal(t, int64(5), thing0)
assert.Equal(t, 1.234, thing1)
}
func TestHandleSlicesOfBools(t *testing.T) {
type internal struct {
Thing NodeReference[[]ValueReference[any]]
Thing NodeReference[[]ValueReference[*yaml.Node]]
}
yml := `thing:
@@ -357,9 +350,16 @@ func TestHandleSlicesOfBools(t *testing.T) {
assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins)
var thing0 bool
_ = ins.Thing.GetValue()[0].Value.Decode(&thing0)
var thing1 bool
_ = ins.Thing.GetValue()[1].Value.Decode(&thing1)
assert.NoError(t, try)
assert.Equal(t, true, ins.Thing.Value[0].Value)
assert.Equal(t, false, ins.Thing.Value[1].Value)
assert.Equal(t, true, thing0)
assert.Equal(t, false, thing1)
}
func TestSetField_Ignore(t *testing.T) {
@@ -387,7 +387,7 @@ func TestSetField_Ignore(t *testing.T) {
func TestBuildModelAsync(t *testing.T) {
type internal struct {
Thing KeyReference[map[KeyReference[string]]ValueReference[string]]
Thing KeyReference[*orderedmap.Map[KeyReference[string], ValueReference[string]]]
}
yml := `thing:
@@ -405,28 +405,5 @@ func TestBuildModelAsync(t *testing.T) {
wg.Add(1)
BuildModelAsync(rootNode.Content[0], ins, &wg, &errors)
wg.Wait()
assert.Len(t, ins.Thing.Value, 3)
}
func TestBuildModelAsync_Error(t *testing.T) {
type internal struct {
Thing []NodeReference[any]
}
yml := `thing:
- 999
- false`
ins := new(internal)
var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr)
var wg sync.WaitGroup
var errors []error
wg.Add(1)
BuildModelAsync(rootNode.Content[0], ins, &wg, &errors)
wg.Wait()
assert.Len(t, errors, 1)
assert.Len(t, ins.Thing, 0)
assert.Equal(t, 3, orderedmap.Len(ins.Thing.Value))
}

View File

@@ -3,6 +3,11 @@
package low
import (
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
)
type SharedParameters interface {
HasDescription
Hash() [32]byte
@@ -30,7 +35,7 @@ type SwaggerParameter interface {
GetType() *NodeReference[string]
GetFormat() *NodeReference[string]
GetCollectionFormat() *NodeReference[string]
GetDefault() *NodeReference[any]
GetDefault() *NodeReference[*yaml.Node]
GetMaximum() *NodeReference[int]
GetExclusiveMaximum() *NodeReference[bool]
GetMinimum() *NodeReference[int]
@@ -41,7 +46,7 @@ type SwaggerParameter interface {
GetMaxItems() *NodeReference[int]
GetMinItems() *NodeReference[int]
GetUniqueItems() *NodeReference[bool]
GetEnum() *NodeReference[[]ValueReference[any]]
GetEnum() *NodeReference[[]ValueReference[*yaml.Node]]
GetMultipleOf() *NodeReference[int]
}
@@ -51,7 +56,7 @@ type SwaggerHeader interface {
GetType() *NodeReference[string]
GetFormat() *NodeReference[string]
GetCollectionFormat() *NodeReference[string]
GetDefault() *NodeReference[any]
GetDefault() *NodeReference[*yaml.Node]
GetMaximum() *NodeReference[int]
GetExclusiveMaximum() *NodeReference[bool]
GetMinimum() *NodeReference[int]
@@ -62,7 +67,7 @@ type SwaggerHeader interface {
GetMaxItems() *NodeReference[int]
GetMinItems() *NodeReference[int]
GetUniqueItems() *NodeReference[bool]
GetEnum() *NodeReference[[]ValueReference[any]]
GetEnum() *NodeReference[[]ValueReference[*yaml.Node]]
GetMultipleOf() *NodeReference[int]
GetItems() *NodeReference[any] // requires cast.
}
@@ -74,7 +79,7 @@ type OpenAPIHeader interface {
GetStyle() *NodeReference[string]
GetAllowReserved() *NodeReference[bool]
GetExplode() *NodeReference[bool]
GetExample() *NodeReference[any]
GetExample() *NodeReference[*yaml.Node]
GetRequired() *NodeReference[bool]
GetAllowEmptyValue() *NodeReference[bool]
GetSchema() *NodeReference[any] // requires cast.
@@ -88,13 +93,12 @@ type OpenAPIParameter interface {
GetStyle() *NodeReference[string]
GetAllowReserved() *NodeReference[bool]
GetExplode() *NodeReference[bool]
GetExample() *NodeReference[any]
GetExample() *NodeReference[*yaml.Node]
GetExamples() *NodeReference[any] // requires cast.
GetContent() *NodeReference[any] // requires cast.
}
//TODO: this needs to be fixed, move returns to pointers.
// TODO: this needs to be fixed, move returns to pointers.
type SharedOperations interface {
GetOperationId() NodeReference[string]
GetExternalDocs() NodeReference[any]
@@ -102,7 +106,7 @@ type SharedOperations interface {
GetTags() NodeReference[[]ValueReference[string]]
GetSummary() NodeReference[string]
GetDeprecated() NodeReference[bool]
GetExtensions() map[KeyReference[string]]ValueReference[any]
GetExtensions() *orderedmap.Map[KeyReference[string], ValueReference[*yaml.Node]]
GetResponses() NodeReference[any] // requires cast.
GetParameters() NodeReference[any] // requires cast.
GetSecurity() NodeReference[any] // requires cast.
@@ -117,6 +121,6 @@ type SwaggerOperations interface {
type OpenAPIOperations interface {
SharedOperations
GetCallbacks() NodeReference[map[KeyReference[string]]ValueReference[any]] // requires cast
GetCallbacks() NodeReference[*orderedmap.Map[KeyReference[string], ValueReference[any]]] // requires cast
GetServers() NodeReference[any] // requires cast.
}

View File

@@ -3,7 +3,9 @@ package low
import (
"context"
"fmt"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
@@ -13,25 +15,35 @@ const (
)
type Reference struct {
Reference string `json:"-" yaml:"-"`
refNode *yaml.Node
reference string
}
func (r *Reference) GetReference() string {
return r.Reference
func (r Reference) GetReference() string {
return r.reference
}
func (r *Reference) IsReference() bool {
return r.Reference != ""
func (r Reference) IsReference() bool {
return r.reference != ""
}
func (r *Reference) SetReference(ref string) {
r.Reference = ref
func (r Reference) GetReferenceNode() *yaml.Node {
return r.refNode
}
func (r *Reference) SetReference(ref string, node *yaml.Node) {
r.reference = ref
r.refNode = node
}
type IsReferenced interface {
IsReference() bool
GetReference() string
SetReference(string)
GetReferenceNode() *yaml.Node
}
type SetReferencer interface {
SetReference(ref string, node *yaml.Node)
}
// Buildable is an interface for any struct that can be 'built out'. This means that a struct can accept
@@ -63,16 +75,14 @@ type Hashable interface {
// HasExtensions is implemented by any object that exposes extensions
type HasExtensions[T any] interface {
// GetExtensions returns generic low level extensions
GetExtensions() map[KeyReference[string]]ValueReference[any]
GetExtensions() *orderedmap.Map[KeyReference[string], ValueReference[*yaml.Node]]
}
// HasExtensionsUntyped is implemented by any object that exposes extensions
type HasExtensionsUntyped interface {
// GetExtensions returns generic low level extensions
GetExtensions() map[KeyReference[string]]ValueReference[any]
GetExtensions() *orderedmap.Map[KeyReference[string], ValueReference[*yaml.Node]]
}
// HasValue is implemented by NodeReference and ValueReference to return the yaml.Node backing the value.
@@ -98,6 +108,7 @@ type HasKeyNode interface {
// a key yaml.Node that points to the key node that contains the value node, and the value node that contains
// the actual value.
type NodeReference[T any] struct {
Reference
// The value being referenced
Value T
@@ -108,19 +119,14 @@ type NodeReference[T any] struct {
// The yaml.Node that is the key, that contains the value.
KeyNode *yaml.Node
// Is this value actually a reference in the original tree?
ReferenceNode bool
// If HasReference is true, then Reference contains the original $ref value.
Reference string
Context context.Context
}
var _ HasValueNodeUntyped = &NodeReference[any]{}
// KeyReference is a low-level container for key nodes holding a Value of type T. A KeyNode is a pointer to the
// yaml.Node that holds a key to a value.
type KeyReference[T any] struct {
// The value being referenced.
Value T
@@ -131,18 +137,13 @@ type KeyReference[T any] struct {
// ValueReference is a low-level container for value nodes that hold a Value of type T. A ValueNode is a pointer
// to the yaml.Node that holds the value.
type ValueReference[T any] struct {
Reference
// The value being referenced.
Value T
// The yaml.Node that holds the referenced value
ValueNode *yaml.Node
// Is this value actually a reference in the original tree?
ReferenceNode bool
// If HasReference is true, then Reference contains the original $ref value.
Reference string
}
// IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored)
@@ -158,32 +159,6 @@ func (n NodeReference[T]) NodeLineNumber() int {
}
}
func (n NodeReference[T]) GetReference() string {
return n.Reference
}
func (n NodeReference[T]) SetReference(ref string) {
n.Reference = ref
}
// IsReference will return true if the key node contains a $ref key.
func (n NodeReference[T]) IsReference() bool {
if n.ReferenceNode {
return true
}
if n.KeyNode != nil {
for k := range n.KeyNode.Content {
if k%2 == 0 {
if n.KeyNode.Content[k].Value == "$ref" {
n.ReferenceNode = true
return true
}
}
}
}
return false
}
// GenerateMapKey will return a string based on the line and column number of the node, e.g. 33:56 for line 33, col 56.
func (n NodeReference[T]) GenerateMapKey() string {
return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column)
@@ -251,36 +226,19 @@ func (n ValueReference[T]) GetValueUntyped() any {
return n.Value
}
func (n ValueReference[T]) GetReference() string {
return n.Reference
}
func (n ValueReference[T]) SetReference(ref string) {
n.Reference = ref
}
// IsReference will return true if the key node contains a $ref
func (n ValueReference[T]) IsReference() bool {
if n.Reference != "" {
return true
}
return false
}
func (n ValueReference[T]) MarshalYAML() (interface{}, error) {
if n.IsReference() {
nodes := make([]*yaml.Node, 2)
nodes[0] = utils.CreateStringNode("$ref")
nodes[1] = utils.CreateStringNode(n.Reference)
m := utils.CreateEmptyMapNode()
m.Content = nodes
return m, nil
return n.GetReferenceNode(), nil
}
var h yaml.Node
e := n.ValueNode.Decode(&h)
return h, e
}
func (n KeyReference[T]) MarshalYAML() (interface{}, error) {
return n.KeyNode, nil
}
// IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored)
func (n KeyReference[T]) IsEmpty() bool {
return n.KeyNode == nil

View File

@@ -6,10 +6,11 @@ package low
import (
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/utils"
"strings"
"testing"
"github.com/pb33f/libopenapi/utils"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
@@ -99,7 +100,6 @@ func TestKeyReference_GenerateMapKey(t *testing.T) {
}
func TestIsCircular_LookupFromJourney(t *testing.T) {
yml := `components:
schemas:
Something:
@@ -136,7 +136,6 @@ func TestIsCircular_LookupFromJourney(t *testing.T) {
}
func TestIsCircular_LookupFromJourney_Optional(t *testing.T) {
yml := `components:
schemas:
Something:
@@ -237,7 +236,6 @@ func TestIsCircular_LookupFromLoopPoint_Optional(t *testing.T) {
}
func TestIsCircular_FromRefLookup(t *testing.T) {
yml := `components:
schemas:
NotCircle:
@@ -558,21 +556,17 @@ func TestGetCircularReferenceResult_NothingFound(t *testing.T) {
func TestHashToString(t *testing.T) {
assert.Equal(t, "5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5",
HashToString(sha256.Sum256([]byte("12345"))))
}
func TestReference_IsReference(t *testing.T) {
ref := Reference{
Reference: "#/components/schemas/SomeSchema",
}
ref := Reference{}
ref.SetReference("#/components/schemas/SomeSchema", nil)
assert.True(t, ref.IsReference())
}
func TestNodeReference_NodeLineNumber(t *testing.T) {
n := utils.CreateStringNode("pizza")
nr := NodeReference[string]{
nr := &NodeReference[string]{
Value: "pizza",
ValueNode: n,
}
@@ -582,51 +576,36 @@ func TestNodeReference_NodeLineNumber(t *testing.T) {
}
func TestNodeReference_NodeLineNumberEmpty(t *testing.T) {
nr := NodeReference[string]{
nr := &NodeReference[string]{
Value: "pizza",
}
assert.Equal(t, 0, nr.NodeLineNumber())
}
func TestNodeReference_GetReference(t *testing.T) {
nr := NodeReference[string]{
Reference: "#/happy/sunday",
}
nr := &NodeReference[string]{}
nr.SetReference("#/happy/sunday", nil)
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())
nr := &NodeReference[string]{}
nr.SetReference("#/happy/sunday", nil)
}
func TestNodeReference_GetKeyNode(t *testing.T) {
nr := NodeReference[string]{
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]{
nr := &NodeReference[any]{
Value: anything{thing: "ding"},
}
@@ -634,7 +613,6 @@ func TestNodeReference_GetValueUntyped(t *testing.T) {
}
func TestValueReference_NodeLineNumber(t *testing.T) {
n := utils.CreateStringNode("pizza")
nr := ValueReference[string]{
Value: "pizza",
@@ -646,7 +624,6 @@ func TestValueReference_NodeLineNumber(t *testing.T) {
}
func TestValueReference_NodeLineNumber_Nil(t *testing.T) {
nr := ValueReference[string]{
Value: "pizza",
}
@@ -655,21 +632,12 @@ func TestValueReference_NodeLineNumber_Nil(t *testing.T) {
}
func TestValueReference_GetReference(t *testing.T) {
nr := ValueReference[string]{
Reference: "#/happy/sunday",
}
nr := ValueReference[string]{}
nr.SetReference("#/happy/sunday", nil)
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
}
@@ -681,28 +649,15 @@ func TestValueReference_GetValueUntyped(t *testing.T) {
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",
}
nr := ValueReference[string]{}
nr.SetReference("#/burgers/beer", nil)
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",
@@ -725,7 +680,6 @@ wine: cheese`
}
func TestKeyReference_GetValueUntyped(t *testing.T) {
type anything struct {
thing string
}

View File

@@ -6,9 +6,10 @@ package v2
import (
"context"
"crypto/sha256"
"sort"
"strings"
"sync"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
@@ -23,7 +24,7 @@ import (
// referenced to the ones defined here. It does not define global operation parameters
// - https://swagger.io/specification/v2/#parametersDefinitionsObject
type ParameterDefinitions struct {
Definitions orderedmap.Map[low.KeyReference[string], low.ValueReference[*Parameter]]
Definitions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*Parameter]]
}
// ResponsesDefinitions is a low-level representation of a Swagger / OpenAPI 2 Responses Definitions object.
@@ -32,7 +33,7 @@ type ParameterDefinitions struct {
// referenced to the ones defined here. It does not define global operation responses
// - https://swagger.io/specification/v2/#responsesDefinitionsObject
type ResponsesDefinitions struct {
Definitions orderedmap.Map[low.KeyReference[string], low.ValueReference[*Response]]
Definitions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*Response]]
}
// SecurityDefinitions is a low-level representation of a Swagger / OpenAPI 2 Security Definitions object.
@@ -41,7 +42,7 @@ type ResponsesDefinitions struct {
// schemes on the operations and only serves to provide the relevant details for each scheme
// - https://swagger.io/specification/v2/#securityDefinitionsObject
type SecurityDefinitions struct {
Definitions orderedmap.Map[low.KeyReference[string], low.ValueReference[*SecurityScheme]]
Definitions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*SecurityScheme]]
}
// Definitions is a low-level representation of a Swagger / OpenAPI 2 Definitions object
@@ -50,7 +51,7 @@ type SecurityDefinitions struct {
// arrays or models.
// - https://swagger.io/specification/v2/#definitionsObject
type Definitions struct {
Schemas orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]]
Schemas *orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]]
}
// FindSchema will attempt to locate a base.SchemaProxy instance using a name.
@@ -77,46 +78,79 @@ func (s *SecurityDefinitions) FindSecurityDefinition(securityDef string) *low.Va
func (d *Definitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
// TODO: Refactor with orderedmap.TranslatePipeline.
errorChan := make(chan error)
resultChan := make(chan definitionResult[*base.SchemaProxy])
var defLabel *yaml.Node
totalDefinitions := 0
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*base.SchemaProxy], e chan error) {
type buildInput struct {
label *yaml.Node
value *yaml.Node
}
results := orderedmap.New[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]]()
in := make(chan buildInput)
out := make(chan definitionResult[*base.SchemaProxy])
done := make(chan struct{})
var wg sync.WaitGroup
wg.Add(2) // input and output goroutines.
obj, err, _, rv := low.ExtractObjectRaw[*base.SchemaProxy](ctx, label, value, idx)
if err != nil {
e <- err
}
r <- definitionResult[*base.SchemaProxy]{k: label, v: low.ValueReference[*base.SchemaProxy]{
Value: obj, ValueNode: value, Reference: rv,
}}
}
for i := range root.Content {
// TranslatePipeline input.
go func() {
defer func() {
close(in)
wg.Done()
}()
var label *yaml.Node
for i, value := range root.Content {
if i%2 == 0 {
defLabel = root.Content[i]
label = value
continue
}
totalDefinitions++
go buildFunc(defLabel, root.Content[i], idx, resultChan, errorChan)
select {
case in <- buildInput{
label: label,
value: value,
}:
case <-done:
return
}
}
}()
// TranslatePipeline output.
go func() {
for {
result, ok := <-out
if !ok {
break
}
completedDefs := 0
results := orderedmap.New[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]]()
for completedDefs < totalDefinitions {
select {
case err := <-errorChan:
return err
case sch := <-resultChan:
completedDefs++
key := low.KeyReference[string]{
Value: sch.k.Value,
KeyNode: sch.k,
Value: result.k.Value,
KeyNode: result.k,
}
results.Set(key, sch.v)
results.Set(key, result.v)
}
close(done)
wg.Done()
}()
translateFunc := func(value buildInput) (definitionResult[*base.SchemaProxy], error) {
obj, err, _, rv := low.ExtractObjectRaw[*base.SchemaProxy](ctx, value.label, value.value, idx)
if err != nil {
return definitionResult[*base.SchemaProxy]{}, err
}
v := low.ValueReference[*base.SchemaProxy]{
Value: obj, ValueNode: value.value,
}
v.SetReference(rv, value.value)
return definitionResult[*base.SchemaProxy]{k: value.label, v: v}, nil
}
err := datamodel.TranslatePipeline[buildInput, definitionResult[*base.SchemaProxy]](in, out, translateFunc)
wg.Wait()
if err != nil {
return err
}
d.Schemas = results
return nil
}
@@ -124,15 +158,8 @@ func (d *Definitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.
// Hash will return a consistent SHA256 Hash of the Definitions object
func (d *Definitions) Hash() [32]byte {
var f []string
keys := make([]string, orderedmap.Len(d.Schemas))
z := 0
for pair := orderedmap.First(d.Schemas); pair != nil; pair = pair.Next() {
keys[z] = pair.Key().Value
z++
}
sort.Strings(keys)
for k := range keys {
f = append(f, low.GenerateHashString(d.FindSchema(keys[k]).Value))
for pair := orderedmap.First(orderedmap.SortAlpha(d.Schemas)); pair != nil; pair = pair.Next() {
f = append(f, low.GenerateHashString(d.FindSchema(pair.Key().Value).Value))
}
return sha256.Sum256([]byte(strings.Join(f, "|")))
}
@@ -143,15 +170,21 @@ func (pd *ParameterDefinitions) Build(ctx context.Context, _, root *yaml.Node, i
resultChan := make(chan definitionResult[*Parameter])
var defLabel *yaml.Node
totalDefinitions := 0
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*Parameter], e chan error) {
buildFunc := func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*Parameter], e chan error,
) {
obj, err, _, rv := low.ExtractObjectRaw[*Parameter](ctx, label, value, idx)
if err != nil {
e <- err
}
r <- definitionResult[*Parameter]{k: label, v: low.ValueReference[*Parameter]{Value: obj,
ValueNode: value, Reference: rv}}
v := low.ValueReference[*Parameter]{
Value: obj,
ValueNode: value,
}
v.SetReference(rv, value)
r <- definitionResult[*Parameter]{k: label, v: v}
}
for i := range root.Content {
if i%2 == 0 {
@@ -193,15 +226,21 @@ func (r *ResponsesDefinitions) Build(ctx context.Context, _, root *yaml.Node, id
resultChan := make(chan definitionResult[*Response])
var defLabel *yaml.Node
totalDefinitions := 0
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*Response], e chan error) {
buildFunc := func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*Response], e chan error,
) {
obj, err, _, rv := low.ExtractObjectRaw[*Response](ctx, label, value, idx)
if err != nil {
e <- err
}
r <- definitionResult[*Response]{k: label, v: low.ValueReference[*Response]{Value: obj,
ValueNode: value, Reference: rv}}
v := low.ValueReference[*Response]{
Value: obj,
ValueNode: value,
}
v.SetReference(rv, value)
r <- definitionResult[*Response]{k: label, v: v}
}
for i := range root.Content {
if i%2 == 0 {
@@ -238,16 +277,20 @@ func (s *SecurityDefinitions) Build(ctx context.Context, _, root *yaml.Node, idx
var defLabel *yaml.Node
totalDefinitions := 0
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*SecurityScheme], e chan error) {
buildFunc := func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*SecurityScheme], e chan error,
) {
obj, err, _, rv := low.ExtractObjectRaw[*SecurityScheme](ctx, label, value, idx)
if err != nil {
e <- err
}
r <- definitionResult[*SecurityScheme]{k: label, v: low.ValueReference[*SecurityScheme]{
Value: obj, ValueNode: value, Reference: rv,
}}
v := low.ValueReference[*SecurityScheme]{
Value: obj, ValueNode: value,
}
v.SetReference(rv, value)
r <- definitionResult[*SecurityScheme]{k: label, v: v}
}
for i := range root.Content {

View File

@@ -6,8 +6,6 @@ package v2
import (
"context"
"crypto/sha256"
"fmt"
"sort"
"strings"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -21,12 +19,12 @@ import (
// Allows sharing examples for operation responses
// - https://swagger.io/specification/v2/#exampleObject
type Examples struct {
Values orderedmap.Map[low.KeyReference[string], low.ValueReference[any]]
Values *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
}
// FindExample attempts to locate an example value, using a key label.
func (e *Examples) FindExample(name string) *low.ValueReference[any] {
return low.FindItemInOrderedMap[any](name, e.Values)
func (e *Examples) FindExample(name string) *low.ValueReference[*yaml.Node] {
return low.FindItemInOrderedMap(name, e.Values)
}
// Build will extract all examples and will attempt to unmarshal content into a map or slice based on type.
@@ -34,54 +32,21 @@ func (e *Examples) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecInd
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
var keyNode, currNode *yaml.Node
var err error
e.Values = orderedmap.New[low.KeyReference[string], low.ValueReference[any]]()
e.Values = orderedmap.New[low.KeyReference[string], low.ValueReference[*yaml.Node]]()
for i := range root.Content {
if i%2 == 0 {
keyNode = root.Content[i]
continue
}
currNode = root.Content[i]
var n map[string]interface{}
err = currNode.Decode(&n)
if err != nil {
var k []interface{}
err = currNode.Decode(&k)
if err != nil {
// lets just default to interface
var j interface{}
_ = currNode.Decode(&j)
e.Values.Set(
low.KeyReference[string]{
Value: keyNode.Value,
KeyNode: keyNode,
},
low.ValueReference[any]{
Value: j,
ValueNode: currNode,
},
)
continue
}
e.Values.Set(
low.KeyReference[string]{
Value: keyNode.Value,
KeyNode: keyNode,
},
low.ValueReference[any]{
Value: k,
ValueNode: currNode,
},
)
continue
}
e.Values.Set(
low.KeyReference[string]{
Value: keyNode.Value,
KeyNode: keyNode,
},
low.ValueReference[any]{
Value: n,
low.ValueReference[*yaml.Node]{
Value: currNode,
ValueNode: currNode,
},
)
@@ -92,15 +57,8 @@ func (e *Examples) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecInd
// Hash will return a consistent SHA256 Hash of the Examples object
func (e *Examples) Hash() [32]byte {
var f []string
keys := make([]string, orderedmap.Len(e.Values))
z := 0
for pair := orderedmap.First(e.Values); pair != nil; pair = pair.Next() {
keys[z] = pair.Key().Value
z++
}
sort.Strings(keys)
for k := range keys {
f = append(f, fmt.Sprintf("%v", e.FindExample(keys[k]).Value))
for pair := orderedmap.First(orderedmap.SortAlpha(e.Values)); pair != nil; pair = pair.Next() {
f = append(f, low.GenerateHashString(pair.Value().Value))
}
return sha256.Sum256([]byte(strings.Join(f, "|")))
}

View File

@@ -7,12 +7,14 @@ import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
"strings"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
// Header Represents a low-level Swagger / OpenAPI 2 Header object.
@@ -25,7 +27,7 @@ type Header struct {
Description low.NodeReference[string]
Items low.NodeReference[*Items]
CollectionFormat low.NodeReference[string]
Default low.NodeReference[any]
Default low.NodeReference[*yaml.Node]
Maximum low.NodeReference[int]
ExclusiveMaximum low.NodeReference[bool]
Minimum low.NodeReference[int]
@@ -36,18 +38,18 @@ type Header struct {
MaxItems low.NodeReference[int]
MinItems low.NodeReference[int]
UniqueItems low.NodeReference[bool]
Enum low.NodeReference[[]low.ValueReference[any]]
Enum low.NodeReference[[]low.ValueReference[*yaml.Node]]
MultipleOf low.NodeReference[int]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
}
// FindExtension will attempt to locate an extension value using a name lookup.
func (h *Header) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, h.Extensions)
func (h *Header) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInOrderedMap(ext, h.Extensions)
}
// GetExtensions returns all Header extensions and satisfies the low.HasExtensions interface.
func (h *Header) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (h *Header) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return h.Extensions
}
@@ -64,37 +66,14 @@ func (h *Header) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecI
_, ln, vn := utils.FindKeyNodeFull(DefaultLabel, root.Content)
if vn != nil {
var n map[string]interface{}
err = vn.Decode(&n)
if err != nil {
// if not a map, then try an array
var k []interface{}
err = vn.Decode(&k)
if err != nil {
// lets just default to interface
var j interface{}
_ = vn.Decode(&j)
h.Default = low.NodeReference[any]{
Value: j,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
h.Default = low.NodeReference[any]{
Value: k,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
h.Default = low.NodeReference[any]{
Value: n,
h.Default = low.NodeReference[*yaml.Node]{
Value: vn,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
return nil
}
@@ -113,8 +92,8 @@ func (h *Header) Hash() [32]byte {
if h.CollectionFormat.Value != "" {
f = append(f, h.CollectionFormat.Value)
}
if h.Default.Value != "" {
f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(h.Default.Value)))))
if h.Default.Value != nil && !h.Default.Value.IsZero() {
f = append(f, low.GenerateHashString(h.Default.Value))
}
f = append(f, fmt.Sprint(h.Maximum.Value))
f = append(f, fmt.Sprint(h.Minimum.Value))
@@ -129,24 +108,17 @@ func (h *Header) Hash() [32]byte {
if h.Pattern.Value != "" {
f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(h.Pattern.Value)))))
}
f = append(f, low.HashExtensions(h.Extensions)...)
keys := make([]string, len(h.Extensions))
keys := make([]string, len(h.Enum.Value))
z := 0
for k := range h.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(h.Extensions[k].Value))))
for k := range h.Enum.Value {
keys[z] = low.ValueToString(h.Enum.Value[k].Value)
z++
}
sort.Strings(keys)
f = append(f, keys...)
keys = make([]string, len(h.Enum.Value))
z = 0
for k := range h.Enum.Value {
keys[z] = fmt.Sprint(h.Enum.Value[k].Value)
z++
}
sort.Strings(keys)
f = append(f, keys...)
if h.Items.Value != nil {
f = append(f, low.GenerateHashString(h.Items.Value))
}
@@ -158,12 +130,15 @@ func (h *Header) Hash() [32]byte {
func (h *Header) GetType() *low.NodeReference[string] {
return &h.Type
}
func (h *Header) GetDescription() *low.NodeReference[string] {
return &h.Description
}
func (h *Header) GetFormat() *low.NodeReference[string] {
return &h.Format
}
func (h *Header) GetItems() *low.NodeReference[any] {
i := low.NodeReference[any]{
KeyNode: h.Items.KeyNode,
@@ -172,45 +147,59 @@ func (h *Header) GetItems() *low.NodeReference[any] {
}
return &i
}
func (h *Header) GetCollectionFormat() *low.NodeReference[string] {
return &h.CollectionFormat
}
func (h *Header) GetDefault() *low.NodeReference[any] {
func (h *Header) GetDefault() *low.NodeReference[*yaml.Node] {
return &h.Default
}
func (h *Header) GetMaximum() *low.NodeReference[int] {
return &h.Maximum
}
func (h *Header) GetExclusiveMaximum() *low.NodeReference[bool] {
return &h.ExclusiveMaximum
}
func (h *Header) GetMinimum() *low.NodeReference[int] {
return &h.Minimum
}
func (h *Header) GetExclusiveMinimum() *low.NodeReference[bool] {
return &h.ExclusiveMinimum
}
func (h *Header) GetMaxLength() *low.NodeReference[int] {
return &h.MaxLength
}
func (h *Header) GetMinLength() *low.NodeReference[int] {
return &h.MinLength
}
func (h *Header) GetPattern() *low.NodeReference[string] {
return &h.Pattern
}
func (h *Header) GetMaxItems() *low.NodeReference[int] {
return &h.MaxItems
}
func (h *Header) GetMinItems() *low.NodeReference[int] {
return &h.MinItems
}
func (h *Header) GetUniqueItems() *low.NodeReference[bool] {
return &h.UniqueItems
}
func (h *Header) GetEnum() *low.NodeReference[[]low.ValueReference[any]] {
func (h *Header) GetEnum() *low.NodeReference[[]low.ValueReference[*yaml.Node]] {
return &h.Enum
}
func (h *Header) GetMultipleOf() *low.NodeReference[int] {
return &h.MultipleOf
}

View File

@@ -5,15 +5,16 @@ package v2
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func TestHeader_Build(t *testing.T) {
yml := `items:
$ref: break`
@@ -28,11 +29,9 @@ func TestHeader_Build(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestHeader_DefaultAsSlice(t *testing.T) {
yml := `x-ext: thing
default:
- why
@@ -48,12 +47,15 @@ default:
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NotNil(t, n.Default.Value)
assert.Len(t, n.Default.Value, 3)
assert.Len(t, n.GetExtensions(), 1)
var def []string
_ = n.Default.GetValue().Decode(&def)
assert.Len(t, def, 3)
assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
}
func TestHeader_DefaultAsObject(t *testing.T) {
yml := `default:
lets:
create:
@@ -72,7 +74,6 @@ func TestHeader_DefaultAsObject(t *testing.T) {
}
func TestHeader_NoDefault(t *testing.T) {
yml := `minimum: 12`
var idxNode yaml.Node
@@ -87,7 +88,6 @@ func TestHeader_NoDefault(t *testing.T) {
}
func TestHeader_Hash_n_Grab(t *testing.T) {
yml := `description: head
type: string
format: left
@@ -160,7 +160,11 @@ pattern: wow
assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "nice", n.GetCollectionFormat().Value)
assert.Equal(t, "shut that door!", n.GetDefault().Value)
var def string
_ = n.GetDefault().Value.Decode(&def)
assert.Equal(t, "shut that door!", def)
assert.Equal(t, 10, n.GetMaximum().Value)
assert.Equal(t, 1, n.GetMinimum().Value)
assert.True(t, n.GetExclusiveMinimum().Value)
@@ -174,6 +178,8 @@ pattern: wow
assert.Equal(t, "wow", n.GetPattern().Value)
assert.Equal(t, "int", n.GetItems().Value.(*Items).Type.Value)
assert.Len(t, n.GetEnum().Value, 2)
assert.Equal(t, "large", n.FindExtension("x-belly").Value)
var xBelly string
_ = n.FindExtension("x-belly").GetValue().Decode(&xBelly)
assert.Equal(t, "large", xBelly)
}

View File

@@ -7,12 +7,14 @@ import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
"strings"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
// Items is a low-level representation of a Swagger / OpenAPI 2 Items object.
@@ -25,7 +27,7 @@ type Items struct {
Format low.NodeReference[string]
CollectionFormat low.NodeReference[string]
Items low.NodeReference[*Items]
Default low.NodeReference[any]
Default low.NodeReference[*yaml.Node]
Maximum low.NodeReference[int]
ExclusiveMaximum low.NodeReference[bool]
Minimum low.NodeReference[int]
@@ -36,18 +38,18 @@ type Items struct {
MaxItems low.NodeReference[int]
MinItems low.NodeReference[int]
UniqueItems low.NodeReference[bool]
Enum low.NodeReference[[]low.ValueReference[any]]
Enum low.NodeReference[[]low.ValueReference[*yaml.Node]]
MultipleOf low.NodeReference[int]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
}
// FindExtension will attempt to locate an extension value using a name lookup.
func (i *Items) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, i.Extensions)
func (i *Items) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInOrderedMap(ext, i.Extensions)
}
// GetExtensions returns all Items extensions and satisfies the low.HasExtensions interface.
func (i *Items) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (i *Items) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return i.Extensions
}
@@ -63,8 +65,8 @@ func (i *Items) Hash() [32]byte {
if i.CollectionFormat.Value != "" {
f = append(f, i.CollectionFormat.Value)
}
if i.Default.Value != "" {
f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(i.Default.Value)))))
if i.Default.Value != nil && !i.Default.Value.IsZero() {
f = append(f, low.GenerateHashString(i.Default.Value))
}
f = append(f, fmt.Sprint(i.Maximum.Value))
f = append(f, fmt.Sprint(i.Minimum.Value))
@@ -82,7 +84,7 @@ func (i *Items) Hash() [32]byte {
keys := make([]string, len(i.Enum.Value))
z := 0
for k := range i.Enum.Value {
keys[z] = fmt.Sprint(i.Enum.Value[k].Value)
keys[z] = low.ValueToString(i.Enum.Value[k].Value)
z++
}
sort.Strings(keys)
@@ -91,14 +93,7 @@ func (i *Items) Hash() [32]byte {
if i.Items.Value != nil {
f = append(f, low.GenerateHashString(i.Items.Value))
}
keys = make([]string, len(i.Extensions))
z = 0
for k := range i.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(i.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
f = append(f, low.HashExtensions(i.Extensions)...)
return sha256.Sum256([]byte(strings.Join(f, "|")))
}
@@ -115,32 +110,8 @@ func (i *Items) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIn
_, ln, vn := utils.FindKeyNodeFull(DefaultLabel, root.Content)
if vn != nil {
var n map[string]interface{}
err := vn.Decode(&n)
if err != nil {
// if not a map, then try an array
var k []interface{}
err = vn.Decode(&k)
if err != nil {
// lets just default to interface
var j interface{}
_ = vn.Decode(&j)
i.Default = low.NodeReference[any]{
Value: j,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
i.Default = low.NodeReference[any]{
Value: k,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
i.Default = low.NodeReference[any]{
Value: n,
i.Default = low.NodeReference[*yaml.Node]{
Value: vn,
KeyNode: ln,
ValueNode: vn,
}
@@ -154,9 +125,11 @@ func (i *Items) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIn
func (i *Items) GetType() *low.NodeReference[string] {
return &i.Type
}
func (i *Items) GetFormat() *low.NodeReference[string] {
return &i.Format
}
func (i *Items) GetItems() *low.NodeReference[any] {
k := low.NodeReference[any]{
KeyNode: i.Items.KeyNode,
@@ -165,48 +138,63 @@ func (i *Items) GetItems() *low.NodeReference[any] {
}
return &k
}
func (i *Items) GetCollectionFormat() *low.NodeReference[string] {
return &i.CollectionFormat
}
func (i *Items) GetDescription() *low.NodeReference[string] {
return nil // not implemented, but required to align with header contract
}
func (i *Items) GetDefault() *low.NodeReference[any] {
func (i *Items) GetDefault() *low.NodeReference[*yaml.Node] {
return &i.Default
}
func (i *Items) GetMaximum() *low.NodeReference[int] {
return &i.Maximum
}
func (i *Items) GetExclusiveMaximum() *low.NodeReference[bool] {
return &i.ExclusiveMaximum
}
func (i *Items) GetMinimum() *low.NodeReference[int] {
return &i.Minimum
}
func (i *Items) GetExclusiveMinimum() *low.NodeReference[bool] {
return &i.ExclusiveMinimum
}
func (i *Items) GetMaxLength() *low.NodeReference[int] {
return &i.MaxLength
}
func (i *Items) GetMinLength() *low.NodeReference[int] {
return &i.MinLength
}
func (i *Items) GetPattern() *low.NodeReference[string] {
return &i.Pattern
}
func (i *Items) GetMaxItems() *low.NodeReference[int] {
return &i.MaxItems
}
func (i *Items) GetMinItems() *low.NodeReference[int] {
return &i.MinItems
}
func (i *Items) GetUniqueItems() *low.NodeReference[bool] {
return &i.UniqueItems
}
func (i *Items) GetEnum() *low.NodeReference[[]low.ValueReference[any]] {
func (i *Items) GetEnum() *low.NodeReference[[]low.ValueReference[*yaml.Node]] {
return &i.Enum
}
func (i *Items) GetMultipleOf() *low.NodeReference[int] {
return &i.MultipleOf
}

View File

@@ -5,15 +5,16 @@ package v2
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func TestItems_Build(t *testing.T) {
yml := `items:
$ref: break`
@@ -31,7 +32,6 @@ func TestItems_Build(t *testing.T) {
}
func TestItems_DefaultAsSlice(t *testing.T) {
yml := `x-thing: thing
default:
- pizza
@@ -45,12 +45,14 @@ default:
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value, 2)
assert.Len(t, n.GetExtensions(), 1)
var def []string
_ = n.Default.Value.Decode(&def)
assert.Len(t, def, 2)
assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
}
func TestItems_DefaultAsMap(t *testing.T) {
yml := `default:
hot: pizza
tasty: beer`
@@ -63,12 +65,13 @@ func TestItems_DefaultAsMap(t *testing.T) {
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value, 2)
var def map[string]string
_ = n.Default.GetValue().Decode(&def)
assert.Len(t, def, 2)
}
func TestItems_Hash_n_Grab(t *testing.T) {
yml := `type: string
format: left
collectionFormat: nice
@@ -138,7 +141,10 @@ pattern: wow
assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "nice", n.GetCollectionFormat().Value)
assert.Equal(t, "shut that door!", n.GetDefault().Value)
var def string
_ = n.GetDefault().Value.Decode(&def)
assert.Equal(t, "shut that door!", def)
assert.Equal(t, 10, n.GetMaximum().Value)
assert.Equal(t, 1, n.GetMinimum().Value)
assert.True(t, n.GetExclusiveMinimum().Value)
@@ -152,7 +158,8 @@ pattern: wow
assert.Equal(t, "wow", n.GetPattern().Value)
assert.Equal(t, "int", n.GetItems().Value.(*Items).Type.Value)
assert.Len(t, n.GetEnum().Value, 2)
assert.Equal(t, "large", n.FindExtension("x-belly").Value)
assert.Nil(t, n.GetDescription())
var xBelly string
_ = n.FindExtension("x-belly").GetValue().Decode(&xBelly)
assert.Equal(t, "large", xBelly)
}

View File

@@ -7,13 +7,15 @@ import (
"context"
"crypto/sha256"
"fmt"
"sort"
"strings"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
"strings"
)
// Operation represents a low-level Swagger / OpenAPI 2 Operation object.
@@ -33,7 +35,7 @@ type Operation struct {
Schemes low.NodeReference[[]low.ValueReference[string]]
Deprecated low.NodeReference[bool]
Security low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
}
// Build will extract external docs, extensions, parameters, responses and security requirements.
@@ -150,14 +152,7 @@ func (o *Operation) Hash() [32]byte {
}
sort.Strings(keys)
f = append(f, keys...)
keys = make([]string, len(o.Extensions))
z := 0
for k := range o.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(o.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
f = append(f, low.HashExtensions(o.Extensions)...)
return sha256.Sum256([]byte(strings.Join(f, "|")))
}
@@ -185,7 +180,7 @@ func (o *Operation) GetOperationId() low.NodeReference[string] {
func (o *Operation) GetDeprecated() low.NodeReference[bool] {
return o.Deprecated
}
func (o *Operation) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (o *Operation) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return o.Extensions
}
func (o *Operation) GetResponses() low.NodeReference[any] {

View File

@@ -16,7 +16,6 @@ import (
)
func TestOperation_Build_ExternalDocs(t *testing.T) {
yml := `externalDocs:
$ref: break`
@@ -31,11 +30,9 @@ func TestOperation_Build_ExternalDocs(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestOperation_Build_Params(t *testing.T) {
yml := `parameters:
$ref: break`
@@ -50,11 +47,9 @@ func TestOperation_Build_Params(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestOperation_Build_Responses(t *testing.T) {
yml := `responses:
$ref: break`
@@ -69,11 +64,9 @@ func TestOperation_Build_Responses(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestOperation_Build_Security(t *testing.T) {
yml := `security:
$ref: break`
@@ -88,11 +81,9 @@ func TestOperation_Build_Security(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestOperation_Hash_n_Grab(t *testing.T) {
yml := `tags:
- nice
- hat
@@ -185,5 +176,5 @@ security:
assert.True(t, n.GetDeprecated().Value)
assert.Equal(t, 1, orderedmap.Len(n.GetResponses().Value.(*Responses).Codes))
assert.Len(t, n.GetSecurity().Value, 1)
assert.Len(t, n.GetExtensions(), 1)
assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
}

View File

@@ -7,13 +7,15 @@ import (
"context"
"crypto/sha256"
"fmt"
"sort"
"strings"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
"strings"
)
// Parameter represents a low-level Swagger / OpenAPI 2 Parameter object.
@@ -68,7 +70,7 @@ type Parameter struct {
Schema low.NodeReference[*base.SchemaProxy]
Items low.NodeReference[*Items]
CollectionFormat low.NodeReference[string]
Default low.NodeReference[any]
Default low.NodeReference[*yaml.Node]
Maximum low.NodeReference[int]
ExclusiveMaximum low.NodeReference[bool]
Minimum low.NodeReference[int]
@@ -79,18 +81,18 @@ type Parameter struct {
MaxItems low.NodeReference[int]
MinItems low.NodeReference[int]
UniqueItems low.NodeReference[bool]
Enum low.NodeReference[[]low.ValueReference[any]]
Enum low.NodeReference[[]low.ValueReference[*yaml.Node]]
MultipleOf low.NodeReference[int]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
}
// FindExtension attempts to locate a extension value given a name.
func (p *Parameter) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, p.Extensions)
func (p *Parameter) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInOrderedMap(ext, p.Extensions)
}
// GetExtensions returns all Parameter extensions and satisfies the low.HasExtensions interface.
func (p *Parameter) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (p *Parameter) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return p.Extensions
}
@@ -114,30 +116,8 @@ func (p *Parameter) Build(ctx context.Context, _, root *yaml.Node, idx *index.Sp
_, ln, vn := utils.FindKeyNodeFull(DefaultLabel, root.Content)
if vn != nil {
var n map[string]interface{}
err := vn.Decode(&n)
if err != nil {
var k []interface{}
err = vn.Decode(&k)
if err != nil {
var j interface{}
_ = vn.Decode(&j)
p.Default = low.NodeReference[any]{
Value: j,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
p.Default = low.NodeReference[any]{
Value: k,
KeyNode: ln,
ValueNode: vn,
}
return nil
}
p.Default = low.NodeReference[any]{
Value: n,
p.Default = low.NodeReference[*yaml.Node]{
Value: vn,
KeyNode: ln,
ValueNode: vn,
}
@@ -172,8 +152,8 @@ func (p *Parameter) Hash() [32]byte {
if p.CollectionFormat.Value != "" {
f = append(f, p.CollectionFormat.Value)
}
if p.Default.Value != "" {
f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(p.Default.Value)))))
if p.Default.Value != nil && !p.Default.Value.IsZero() {
f = append(f, low.GenerateHashString(p.Default.Value))
}
f = append(f, fmt.Sprint(p.Maximum.Value))
f = append(f, fmt.Sprint(p.Minimum.Value))
@@ -192,20 +172,13 @@ func (p *Parameter) Hash() [32]byte {
keys := make([]string, len(p.Enum.Value))
z := 0
for k := range p.Enum.Value {
keys[z] = fmt.Sprint(p.Enum.Value[k].Value)
keys[z] = low.ValueToString(p.Enum.Value[k].Value)
z++
}
sort.Strings(keys)
f = append(f, keys...)
keys = make([]string, len(p.Extensions))
z = 0
for k := range p.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(p.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
f = append(f, low.HashExtensions(p.Extensions)...)
if p.Items.Value != nil {
f = append(f, fmt.Sprintf("%x", p.Items.Value.Hash()))
}
@@ -217,21 +190,27 @@ func (p *Parameter) Hash() [32]byte {
func (p *Parameter) GetName() *low.NodeReference[string] {
return &p.Name
}
func (p *Parameter) GetIn() *low.NodeReference[string] {
return &p.In
}
func (p *Parameter) GetType() *low.NodeReference[string] {
return &p.Type
}
func (p *Parameter) GetDescription() *low.NodeReference[string] {
return &p.Description
}
func (p *Parameter) GetRequired() *low.NodeReference[bool] {
return &p.Required
}
func (p *Parameter) GetAllowEmptyValue() *low.NodeReference[bool] {
return &p.AllowEmptyValue
}
func (p *Parameter) GetSchema() *low.NodeReference[any] {
i := low.NodeReference[any]{
KeyNode: p.Schema.KeyNode,
@@ -240,9 +219,11 @@ func (p *Parameter) GetSchema() *low.NodeReference[any] {
}
return &i
}
func (p *Parameter) GetFormat() *low.NodeReference[string] {
return &p.Format
}
func (p *Parameter) GetItems() *low.NodeReference[any] {
i := low.NodeReference[any]{
KeyNode: p.Items.KeyNode,
@@ -251,45 +232,59 @@ func (p *Parameter) GetItems() *low.NodeReference[any] {
}
return &i
}
func (p *Parameter) GetCollectionFormat() *low.NodeReference[string] {
return &p.CollectionFormat
}
func (p *Parameter) GetDefault() *low.NodeReference[any] {
func (p *Parameter) GetDefault() *low.NodeReference[*yaml.Node] {
return &p.Default
}
func (p *Parameter) GetMaximum() *low.NodeReference[int] {
return &p.Maximum
}
func (p *Parameter) GetExclusiveMaximum() *low.NodeReference[bool] {
return &p.ExclusiveMaximum
}
func (p *Parameter) GetMinimum() *low.NodeReference[int] {
return &p.Minimum
}
func (p *Parameter) GetExclusiveMinimum() *low.NodeReference[bool] {
return &p.ExclusiveMinimum
}
func (p *Parameter) GetMaxLength() *low.NodeReference[int] {
return &p.MaxLength
}
func (p *Parameter) GetMinLength() *low.NodeReference[int] {
return &p.MinLength
}
func (p *Parameter) GetPattern() *low.NodeReference[string] {
return &p.Pattern
}
func (p *Parameter) GetMaxItems() *low.NodeReference[int] {
return &p.MaxItems
}
func (p *Parameter) GetMinItems() *low.NodeReference[int] {
return &p.MinItems
}
func (p *Parameter) GetUniqueItems() *low.NodeReference[bool] {
return &p.UniqueItems
}
func (p *Parameter) GetEnum() *low.NodeReference[[]low.ValueReference[any]] {
func (p *Parameter) GetEnum() *low.NodeReference[[]low.ValueReference[*yaml.Node]] {
return &p.Enum
}
func (p *Parameter) GetMultipleOf() *low.NodeReference[int] {
return &p.MultipleOf
}

View File

@@ -5,16 +5,17 @@ package v2
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func TestParameter_Build(t *testing.T) {
yml := `$ref: break`
var idxNode yaml.Node
@@ -28,11 +29,9 @@ func TestParameter_Build(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestParameter_Build_Items(t *testing.T) {
yml := `items:
$ref: break`
@@ -47,11 +46,9 @@ func TestParameter_Build_Items(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestParameter_DefaultSlice(t *testing.T) {
yml := `default:
- things
- junk
@@ -65,11 +62,14 @@ func TestParameter_DefaultSlice(t *testing.T) {
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value.([]any), 3)
var a []any
_ = n.Default.Value.Decode(&a)
assert.Len(t, a, 3)
}
func TestParameter_DefaultMap(t *testing.T) {
yml := `default:
things: junk
stuff: more junk`
@@ -82,11 +82,14 @@ func TestParameter_DefaultMap(t *testing.T) {
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value.(map[string]any), 2)
var m map[string]any
_ = n.Default.Value.Decode(&m)
assert.Len(t, m, 2)
}
func TestParameter_NoDefaultNoError(t *testing.T) {
yml := `name: pizza-pie`
var idxNode yaml.Node
@@ -101,7 +104,6 @@ func TestParameter_NoDefaultNoError(t *testing.T) {
}
func TestParameter_Hash_n_Grab(t *testing.T) {
yml := `name: mcmuffin
in: my-belly
description: tasty!
@@ -185,7 +187,10 @@ allowEmptyValue: true
assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "nice", n.GetCollectionFormat().Value)
assert.Equal(t, "shut that door!", n.GetDefault().Value)
var def string
_ = n.GetDefault().Value.Decode(&def)
assert.Equal(t, "shut that door!", def)
assert.Equal(t, 10, n.GetMaximum().Value)
assert.Equal(t, 1, n.GetMinimum().Value)
assert.True(t, n.GetExclusiveMinimum().Value)
@@ -199,7 +204,10 @@ allowEmptyValue: true
assert.Equal(t, "wow", n.GetPattern().Value)
assert.Equal(t, "int", n.GetItems().Value.(*Items).Type.Value)
assert.Len(t, n.GetEnum().Value, 2)
assert.Equal(t, "large", n.FindExtension("x-belly").Value)
var xBelly string
_ = n.FindExtension("x-belly").Value.Decode(&xBelly)
assert.Equal(t, "large", xBelly)
assert.Equal(t, "tasty!", n.GetDescription().Value)
assert.Equal(t, "mcmuffin", n.GetName().Value)
assert.Equal(t, "my-belly", n.GetIn().Value)
@@ -208,6 +216,5 @@ allowEmptyValue: true
assert.Equal(t, "int", v.Value.A) // A is v2
assert.True(t, n.GetRequired().Value)
assert.True(t, n.GetAllowEmptyValue().Value)
assert.Len(t, n.GetExtensions(), 1)
assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
@@ -34,16 +35,16 @@ type PathItem struct {
Head low.NodeReference[*Operation]
Patch low.NodeReference[*Operation]
Parameters low.NodeReference[[]low.ValueReference[*Parameter]]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
}
// FindExtension will attempt to locate an extension given a name.
func (p *PathItem) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, p.Extensions)
func (p *PathItem) FindExtension(ext string) *low.ValueReference[*yaml.Node] {
return low.FindItemInOrderedMap(ext, p.Extensions)
}
// GetExtensions returns all PathItem extensions and satisfies the low.HasExtensions interface.
func (p *PathItem) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
func (p *PathItem) GetExtensions() *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] {
return p.Extensions
}
@@ -153,8 +154,8 @@ func (p *PathItem) Build(ctx context.Context, _, root *yaml.Node, idx *index.Spe
}
}
//all operations have been superficially built,
//now we need to build out the operation, we will do this asynchronously for speed.
// all operations have been superficially built,
// now we need to build out the operation, we will do this asynchronously for speed.
opBuildChan := make(chan bool)
opErrorChan := make(chan error)
@@ -223,13 +224,6 @@ func (p *PathItem) Hash() [32]byte {
}
sort.Strings(keys)
f = append(f, keys...)
keys = make([]string, len(p.Extensions))
z := 0
for k := range p.Extensions {
keys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(p.Extensions[k].Value))))
z++
}
sort.Strings(keys)
f = append(f, keys...)
f = append(f, low.HashExtensions(p.Extensions)...)
return sha256.Sum256([]byte(strings.Join(f, "|")))
}

View File

@@ -5,15 +5,16 @@ package v2
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func TestPathItem_Build_Params(t *testing.T) {
yml := `parameters:
$ref: break`
@@ -28,11 +29,9 @@ func TestPathItem_Build_Params(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestPathItem_Build_MethodFail(t *testing.T) {
yml := `post:
$ref: break`
@@ -47,11 +46,9 @@ func TestPathItem_Build_MethodFail(t *testing.T) {
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestPathItem_Hash(t *testing.T) {
yml := `get:
description: get me up
put:
@@ -108,6 +105,5 @@ parameters:
// hash
assert.Equal(t, n.Hash(), n2.Hash())
assert.Len(t, n.GetExtensions(), 1)
assert.Equal(t, 1, orderedmap.Len(n.GetExtensions()))
}

Some files were not shown because too many files have changed in this diff Show More