Added support for unevaluatedProperties as Schema and bool #118

Also ran `gofmt` across the entire project. Things need cleaning up.

Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
Dave Shanley
2023-06-15 08:58:20 -04:00
committed by quobix
parent 148822fa2a
commit c3cf5f1e38
142 changed files with 11751 additions and 11577 deletions

View File

@@ -32,14 +32,17 @@ const (
) )
// OpenAPI3SchemaData is an embedded version of the OpenAPI 3 Schema // OpenAPI3SchemaData is an embedded version of the OpenAPI 3 Schema
//
//go:embed schemas/oas3-schema.json //go:embed schemas/oas3-schema.json
var OpenAPI3SchemaData string // embedded OAS3 schema var OpenAPI3SchemaData string // embedded OAS3 schema
// OpenAPI31SchemaData is an embedded version of the OpenAPI 3.1 Schema // OpenAPI31SchemaData is an embedded version of the OpenAPI 3.1 Schema
//
//go:embed schemas/oas31-schema.json //go:embed schemas/oas31-schema.json
var OpenAPI31SchemaData string // embedded OAS31 schema var OpenAPI31SchemaData string // embedded OAS31 schema
// OpenAPI2SchemaData is an embedded version of the OpenAPI 2 (Swagger) Schema // OpenAPI2SchemaData is an embedded version of the OpenAPI 2 (Swagger) Schema
//
//go:embed schemas/swagger2-schema.json //go:embed schemas/swagger2-schema.json
var OpenAPI2SchemaData string // embedded OAS3 schema var OpenAPI2SchemaData string // embedded OAS3 schema

View File

@@ -4,18 +4,18 @@
package datamodel package datamodel
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
) )
func TestNewClosedDocumentConfiguration(t *testing.T) { func TestNewClosedDocumentConfiguration(t *testing.T) {
cfg := NewClosedDocumentConfiguration() cfg := NewClosedDocumentConfiguration()
assert.False(t, cfg.AllowRemoteReferences) assert.False(t, cfg.AllowRemoteReferences)
assert.False(t, cfg.AllowFileReferences) assert.False(t, cfg.AllowFileReferences)
} }
func TestNewOpenDocumentConfiguration(t *testing.T) { func TestNewOpenDocumentConfiguration(t *testing.T) {
cfg := NewOpenDocumentConfiguration() cfg := NewOpenDocumentConfiguration()
assert.True(t, cfg.AllowRemoteReferences) assert.True(t, cfg.AllowRemoteReferences)
assert.True(t, cfg.AllowFileReferences) assert.True(t, cfg.AllowFileReferences)
} }

View File

@@ -9,4 +9,3 @@
// beats, particularly when polymorphism is used. By re-using the same superset Schema across versions, we can ensure // beats, particularly when polymorphism is used. By re-using the same superset Schema across versions, we can ensure
// that all the latest features are collected, without damaging backwards compatibility. // that all the latest features are collected, without damaging backwards compatibility.
package base package base

View File

@@ -10,8 +10,9 @@ import (
) )
// Contact represents a high-level representation of the Contact definitions found at // Contact represents a high-level representation of the Contact definitions found at
// v2 - https://swagger.io/specification/v2/#contactObject //
// v3 - https://spec.openapis.org/oas/v3.1.0#contact-object // v2 - https://swagger.io/specification/v2/#contactObject
// v3 - https://spec.openapis.org/oas/v3.1.0#contact-object
type Contact struct { type Contact struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"` URL string `json:"url,omitempty" yaml:"url,omitempty"`
@@ -47,6 +48,3 @@ func (c *Contact) MarshalYAML() (interface{}, error) {
nb := low2.NewNodeBuilder(c, c.low) nb := low2.NewNodeBuilder(c, c.low)
return nb.Render(), nil return nb.Render(), nil
} }

View File

@@ -16,7 +16,8 @@ import (
// which is used to inform the consumer of the document of an alternative schema based on the value associated with it. // which is used to inform the consumer of the document of an alternative schema based on the value associated with it.
// //
// When using the discriminator, inline schemas will not be considered. // When using the discriminator, inline schemas will not be considered.
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object //
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object
type Discriminator struct { type Discriminator struct {
PropertyName string `json:"propertyName,omitempty" yaml:"propertyName,omitempty"` PropertyName string `json:"propertyName,omitempty" yaml:"propertyName,omitempty"`
Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"` Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"`

View File

@@ -44,7 +44,6 @@ func (d *DynamicValue[A, B]) RenderInline() ([]byte, error) {
return yaml.Marshal(d) return yaml.Marshal(d)
} }
// MarshalYAML will create a ready to render YAML representation of the DynamicValue object. // MarshalYAML will create a ready to render YAML representation of the DynamicValue object.
func (d *DynamicValue[A, B]) MarshalYAML() (interface{}, error) { func (d *DynamicValue[A, B]) MarshalYAML() (interface{}, error) {
// this is a custom renderer, we can't use the NodeBuilder out of the gate. // this is a custom renderer, we can't use the NodeBuilder out of the gate.

View File

@@ -11,7 +11,8 @@ import (
) )
// Example represents a high-level Example object as defined by OpenAPI 3+ // Example represents a high-level Example object as defined by OpenAPI 3+
// v3 - https://spec.openapis.org/oas/v3.1.0#example-object //
// v3 - https://spec.openapis.org/oas/v3.1.0#example-object
type Example struct { type Example struct {
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`

View File

@@ -12,8 +12,9 @@ import (
// ExternalDoc represents a high-level External Documentation object as defined by OpenAPI 2 and 3 // ExternalDoc represents a high-level External Documentation object as defined by OpenAPI 2 and 3
// //
// Allows referencing an external resource for extended documentation. // Allows referencing an external resource for extended documentation.
// v2 - https://swagger.io/specification/v2/#externalDocumentationObject //
// v3 - https://spec.openapis.org/oas/v3.1.0#external-documentation-object // v2 - https://swagger.io/specification/v2/#externalDocumentationObject
// v3 - https://spec.openapis.org/oas/v3.1.0#external-documentation-object
type ExternalDoc struct { type ExternalDoc struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"` URL string `json:"url,omitempty" yaml:"url,omitempty"`

View File

@@ -200,5 +200,3 @@ x-cake:
bytes, _ := highInfo.Render() bytes, _ := highInfo.Render()
assert.Len(t, bytes, 275) assert.Len(t, bytes, 275)
} }

View File

@@ -4,56 +4,56 @@
package base package base
import ( import (
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base" lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing" "testing"
) )
func TestLicense_Render(t *testing.T) { func TestLicense_Render(t *testing.T) {
highL := &License{Name: "MIT", URL: "https://pb33f.io"} highL := &License{Name: "MIT", URL: "https://pb33f.io"}
dat, _ := highL.Render() dat, _ := highL.Render()
// unmarshal yaml into a *yaml.Node instance // unmarshal yaml into a *yaml.Node instance
var cNode yaml.Node var cNode yaml.Node
_ = yaml.Unmarshal(dat, &cNode) _ = yaml.Unmarshal(dat, &cNode)
// build low // build low
var lowLicense lowbase.License var lowLicense lowbase.License
_ = lowmodel.BuildModel(cNode.Content[0], &lowLicense) _ = lowmodel.BuildModel(cNode.Content[0], &lowLicense)
// build high // build high
highLicense := NewLicense(&lowLicense) highLicense := NewLicense(&lowLicense)
assert.Equal(t, "MIT", highLicense.Name) assert.Equal(t, "MIT", highLicense.Name)
assert.Equal(t, "https://pb33f.io", highLicense.URL) assert.Equal(t, "https://pb33f.io", highLicense.URL)
} }
func TestLicense_RenderEqual(t *testing.T) { func TestLicense_RenderEqual(t *testing.T) {
yml := `name: MIT yml := `name: MIT
url: https://pb33f.io/not-real url: https://pb33f.io/not-real
` `
// unmarshal yaml into a *yaml.Node instance // unmarshal yaml into a *yaml.Node instance
var cNode yaml.Node var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode) _ = yaml.Unmarshal([]byte(yml), &cNode)
// build low // build low
var lowLicense lowbase.License var lowLicense lowbase.License
_ = lowmodel.BuildModel(cNode.Content[0], &lowLicense) _ = lowmodel.BuildModel(cNode.Content[0], &lowLicense)
_ = lowLicense.Build(cNode.Content[0], nil) _ = lowLicense.Build(cNode.Content[0], nil)
// build high // build high
highLicense := NewLicense(&lowLicense) highLicense := NewLicense(&lowLicense)
assert.Equal(t, "MIT", highLicense.Name) assert.Equal(t, "MIT", highLicense.Name)
assert.Equal(t, "https://pb33f.io/not-real", highLicense.URL) assert.Equal(t, "https://pb33f.io/not-real", highLicense.URL)
// re-render and ensure everything is in the same order as before. // re-render and ensure everything is in the same order as before.
bytes, _ := highLicense.Render() bytes, _ := highLicense.Render()
assert.Equal(t, yml, string(bytes)) assert.Equal(t, yml, string(bytes))
} }

View File

@@ -10,8 +10,9 @@ import (
) )
// License is a high-level representation of a License object as defined by OpenAPI 2 and OpenAPI 3 // License is a high-level representation of a License object as defined by OpenAPI 2 and OpenAPI 3
// v2 - https://swagger.io/specification/v2/#licenseObject //
// v3 - https://spec.openapis.org/oas/v3.1.0#license-object // v2 - https://swagger.io/specification/v2/#licenseObject
// v3 - https://spec.openapis.org/oas/v3.1.0#license-object
type License struct { type License struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"` URL string `json:"url,omitempty" yaml:"url,omitempty"`

View File

@@ -4,12 +4,12 @@
package base package base
import ( import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sync" "sync"
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
) )
// Schema represents a JSON Schema that support Swagger, OpenAPI 3 and OpenAPI 3.1 // Schema represents a JSON Schema that support Swagger, OpenAPI 3 and OpenAPI 3.1
@@ -22,8 +22,8 @@ import (
// - v3 schema: https://swagger.io/specification/#schema-object // - v3 schema: https://swagger.io/specification/#schema-object
// - v3.1 schema: https://spec.openapis.org/oas/v3.1.0#schema-object // - v3.1 schema: https://spec.openapis.org/oas/v3.1.0#schema-object
type Schema struct { type Schema struct {
// 3.1 only, used to define a dialect for this schema, label is '$schema'. // 3.1 only, used to define a dialect for this schema, label is '$schema'.
SchemaTypeRef string `json:"$schema,omitempty" yaml:"$schema,omitempty"` SchemaTypeRef string `json:"$schema,omitempty" yaml:"$schema,omitempty"`
// In versions 2 and 3.0, this ExclusiveMaximum can only be a boolean. // In versions 2 and 3.0, this ExclusiveMaximum can only be a boolean.
// In version 3.1, ExclusiveMaximum is a number. // In version 3.1, ExclusiveMaximum is a number.
@@ -33,76 +33,79 @@ type Schema struct {
// In version 3.1, ExclusiveMinimum is a number. // In version 3.1, ExclusiveMinimum is a number.
ExclusiveMinimum *DynamicValue[bool, float64] `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` ExclusiveMinimum *DynamicValue[bool, float64] `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
// In versions 2 and 3.0, this Type is a single value, so array will only ever have one value // In versions 2 and 3.0, this Type is a single value, so array will only ever have one value
// in version 3.1, Type can be multiple values // in version 3.1, Type can be multiple values
Type []string `json:"type,omitempty" yaml:"type,omitempty"` Type []string `json:"type,omitempty" yaml:"type,omitempty"`
// Schemas are resolved on demand using a SchemaProxy // Schemas are resolved on demand using a SchemaProxy
AllOf []*SchemaProxy `json:"allOf,omitempty" yaml:"allOf,omitempty"` AllOf []*SchemaProxy `json:"allOf,omitempty" yaml:"allOf,omitempty"`
// Polymorphic Schemas are only available in version 3+ // Polymorphic Schemas are only available in version 3+
OneOf []*SchemaProxy `json:"oneOf,omitempty" yaml:"oneOf,omitempty"` OneOf []*SchemaProxy `json:"oneOf,omitempty" yaml:"oneOf,omitempty"`
AnyOf []*SchemaProxy `json:"anyOf,omitempty" yaml:"anyOf,omitempty"` AnyOf []*SchemaProxy `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
// in 3.1 examples can be an array (which is recommended) // in 3.1 examples can be an array (which is recommended)
Examples []any `json:"examples,omitempty" yaml:"examples,omitempty"` Examples []any `json:"examples,omitempty" yaml:"examples,omitempty"`
// in 3.1 prefixItems provides tuple validation support. // in 3.1 prefixItems provides tuple validation support.
PrefixItems []*SchemaProxy `json:"prefixItems,omitempty" yaml:"prefixItems,omitempty"` PrefixItems []*SchemaProxy `json:"prefixItems,omitempty" yaml:"prefixItems,omitempty"`
// 3.1 Specific properties // 3.1 Specific properties
Contains *SchemaProxy `json:"contains,omitempty" yaml:"contains,omitempty"` Contains *SchemaProxy `json:"contains,omitempty" yaml:"contains,omitempty"`
MinContains *int64 `json:"minContains,omitempty" yaml:"minContains,omitempty"` MinContains *int64 `json:"minContains,omitempty" yaml:"minContains,omitempty"`
MaxContains *int64 `json:"maxContains,omitempty" yaml:"maxContains,omitempty"` MaxContains *int64 `json:"maxContains,omitempty" yaml:"maxContains,omitempty"`
If *SchemaProxy `json:"if,omitempty" yaml:"if,omitempty"` If *SchemaProxy `json:"if,omitempty" yaml:"if,omitempty"`
Else *SchemaProxy `json:"else,omitempty" yaml:"else,omitempty"` Else *SchemaProxy `json:"else,omitempty" yaml:"else,omitempty"`
Then *SchemaProxy `json:"then,omitempty" yaml:"then,omitempty"` Then *SchemaProxy `json:"then,omitempty" yaml:"then,omitempty"`
DependentSchemas map[string]*SchemaProxy `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"` DependentSchemas map[string]*SchemaProxy `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"`
PatternProperties map[string]*SchemaProxy `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"` PatternProperties map[string]*SchemaProxy `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"`
PropertyNames *SchemaProxy `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"` PropertyNames *SchemaProxy `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"`
UnevaluatedItems *SchemaProxy `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"` UnevaluatedItems *SchemaProxy `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"`
UnevaluatedProperties *SchemaProxy `json:"unevaluatedProperties,omitempty" yaml:"unevaluatedProperties,omitempty"`
// in 3.1 Items can be a Schema or a boolean // in 3.1 UnevaluatedProperties can be a Schema or a boolean
Items *DynamicValue[*SchemaProxy, bool] `json:"items,omitempty" yaml:"items,omitempty"` // https://github.com/pb33f/libopenapi/issues/118
UnevaluatedProperties *DynamicValue[*SchemaProxy, *bool] `json:"unevaluatedProperties,omitempty" yaml:"unevaluatedProperties,omitempty"`
// 3.1 only, part of the JSON Schema spec provides a way to identify a subschema // in 3.1 Items can be a Schema or a boolean
Anchor string `json:"$anchor,omitempty" yaml:"$anchor,omitempty"` Items *DynamicValue[*SchemaProxy, bool] `json:"items,omitempty" yaml:"items,omitempty"`
// Compatible with all versions // 3.1 only, part of the JSON Schema spec provides a way to identify a subschema
Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"` Anchor string `json:"$anchor,omitempty" yaml:"$anchor,omitempty"`
Properties 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,omitempty" yaml:"maximum,omitempty"`
Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
MaxLength *int64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
MinLength *int64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
MaxItems *int64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
MinItems *int64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
UniqueItems *bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
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"`
AdditionalProperties any `json:"additionalProperties,omitempty" yaml:"additionalProperties,renderZero,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Default any `json:"default,omitempty" yaml:"default,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
XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"`
ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
low *base.Schema
// Parent Proxy refers back to the low level SchemaProxy that is proxying this schema. // Compatible with all versions
ParentProxy *SchemaProxy `json:"-" yaml:"-"` Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"`
Properties 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,omitempty" yaml:"maximum,omitempty"`
Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
MaxLength *int64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
MinLength *int64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
MaxItems *int64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
MinItems *int64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
UniqueItems *bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
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"`
AdditionalProperties any `json:"additionalProperties,omitempty" yaml:"additionalProperties,renderZero,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Default any `json:"default,omitempty" yaml:"default,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
XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"`
ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
low *base.Schema
// Parent Proxy refers back to the low level SchemaProxy that is proxying this schema.
ParentProxy *SchemaProxy `json:"-" yaml:"-"`
} }
// NewSchema will create a new high-level schema from a low-level one. // NewSchema will create a new high-level schema from a low-level one.
@@ -164,302 +167,318 @@ func NewSchema(schema *base.Schema) *Schema {
s.MinProperties = &schema.MinProperties.Value s.MinProperties = &schema.MinProperties.Value
} }
if !schema.MaxContains.IsEmpty() { if !schema.MaxContains.IsEmpty() {
s.MaxContains = &schema.MaxContains.Value s.MaxContains = &schema.MaxContains.Value
} }
if !schema.MinContains.IsEmpty() { if !schema.MinContains.IsEmpty() {
s.MinContains = &schema.MinContains.Value s.MinContains = &schema.MinContains.Value
} }
if !schema.UniqueItems.IsEmpty() { if !schema.UniqueItems.IsEmpty() {
s.UniqueItems = &schema.UniqueItems.Value s.UniqueItems = &schema.UniqueItems.Value
} }
if !schema.Contains.IsEmpty() { if !schema.Contains.IsEmpty() {
s.Contains = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ s.Contains = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Contains.ValueNode, ValueNode: schema.Contains.ValueNode,
Value: schema.Contains.Value, Value: schema.Contains.Value,
}} }}
} }
if !schema.If.IsEmpty() { if !schema.If.IsEmpty() {
s.If = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ s.If = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.If.ValueNode, ValueNode: schema.If.ValueNode,
Value: schema.If.Value, Value: schema.If.Value,
}} }}
} }
if !schema.Else.IsEmpty() { if !schema.Else.IsEmpty() {
s.Else = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ s.Else = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Else.ValueNode, ValueNode: schema.Else.ValueNode,
Value: schema.Else.Value, Value: schema.Else.Value,
}} }}
} }
if !schema.Then.IsEmpty() { if !schema.Then.IsEmpty() {
s.Then = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ s.Then = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Then.ValueNode, ValueNode: schema.Then.ValueNode,
Value: schema.Then.Value, Value: schema.Then.Value,
}} }}
} }
if !schema.PropertyNames.IsEmpty() { if !schema.PropertyNames.IsEmpty() {
s.PropertyNames = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ s.PropertyNames = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.PropertyNames.ValueNode, ValueNode: schema.PropertyNames.ValueNode,
Value: schema.PropertyNames.Value, Value: schema.PropertyNames.Value,
}} }}
} }
if !schema.UnevaluatedItems.IsEmpty() { if !schema.UnevaluatedItems.IsEmpty() {
s.UnevaluatedItems = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ s.UnevaluatedItems = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.UnevaluatedItems.ValueNode, ValueNode: schema.UnevaluatedItems.ValueNode,
Value: schema.UnevaluatedItems.Value, Value: schema.UnevaluatedItems.Value,
}} }}
} }
if !schema.UnevaluatedProperties.IsEmpty() { // check if unevaluated properties is a schema
s.UnevaluatedProperties = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ if !schema.UnevaluatedProperties.IsEmpty() && schema.UnevaluatedProperties.Value.IsA() {
ValueNode: schema.UnevaluatedProperties.ValueNode, s.UnevaluatedProperties = &DynamicValue[*SchemaProxy, *bool]{
Value: schema.UnevaluatedProperties.Value, A: &SchemaProxy{
}} schema: &lowmodel.NodeReference[*base.SchemaProxy]{
} ValueNode: schema.UnevaluatedProperties.ValueNode,
Value: schema.UnevaluatedProperties.Value.A,
},
},
}
}
s.Pattern = schema.Pattern.Value // check if unevaluated properties is a bool
s.Format = schema.Format.Value if !schema.UnevaluatedProperties.IsEmpty() && schema.UnevaluatedProperties.Value.IsB() {
s.UnevaluatedProperties = &DynamicValue[*SchemaProxy, *bool]{
B: schema.UnevaluatedProperties.Value.B,
}
}
// 3.0 spec is a single value if !schema.UnevaluatedProperties.IsEmpty() {
if !schema.Type.IsEmpty() && schema.Type.Value.IsA() {
s.Type = []string{schema.Type.Value.A}
}
// 3.1 spec may have multiple values
if !schema.Type.IsEmpty() && schema.Type.Value.IsB() {
for i := range schema.Type.Value.B {
s.Type = append(s.Type, schema.Type.Value.B[i].Value)
}
}
if schema.AdditionalProperties.Value != nil {
if addPropSchema, ok := schema.AdditionalProperties.Value.(*base.SchemaProxy); ok {
s.AdditionalProperties = NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
KeyNode: schema.AdditionalProperties.KeyNode,
ValueNode: schema.AdditionalProperties.ValueNode,
Value: addPropSchema,
})
} else {
// TODO: check for slice and map types and unpack correctly.
s.AdditionalProperties = schema.AdditionalProperties.Value }
}
}
s.Description = schema.Description.Value
s.Default = schema.Default.Value
if !schema.Nullable.IsEmpty() {
s.Nullable = &schema.Nullable.Value
}
if !schema.ReadOnly.IsEmpty() {
s.ReadOnly = schema.ReadOnly.Value
}
if !schema.WriteOnly.IsEmpty() {
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))
for i := 0; i < len(schema.Examples.Value); i++ {
examples[i] = schema.Examples.Value[i].Value
}
s.Examples = examples
}
s.Extensions = high.ExtractExtensions(schema.Extensions)
if !schema.Discriminator.IsEmpty() {
s.Discriminator = NewDiscriminator(schema.Discriminator.Value)
}
if !schema.XML.IsEmpty() {
s.XML = NewXML(schema.XML.Value)
}
if !schema.ExternalDocs.IsEmpty() {
s.ExternalDocs = NewExternalDoc(schema.ExternalDocs.Value)
}
var req []string
for i := range schema.Required.Value {
req = append(req, schema.Required.Value[i].Value)
}
s.Required = req
var enum []any s.Pattern = schema.Pattern.Value
if !schema.Anchor.IsEmpty() { s.Format = schema.Format.Value
s.Anchor = schema.Anchor.Value
}
// TODO: check this behavior. // 3.0 spec is a single value
for i := range schema.Enum.Value { if !schema.Type.IsEmpty() && schema.Type.Value.IsA() {
enum = append(enum, schema.Enum.Value[i].Value) s.Type = []string{schema.Type.Value.A}
} }
s.Enum = enum // 3.1 spec may have multiple values
if !schema.Type.IsEmpty() && schema.Type.Value.IsB() {
for i := range schema.Type.Value.B {
s.Type = append(s.Type, schema.Type.Value.B[i].Value)
}
}
if schema.AdditionalProperties.Value != nil {
if addPropSchema, ok := schema.AdditionalProperties.Value.(*base.SchemaProxy); ok {
s.AdditionalProperties = NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
KeyNode: schema.AdditionalProperties.KeyNode,
ValueNode: schema.AdditionalProperties.ValueNode,
Value: addPropSchema,
})
} else {
// TODO: check for slice and map types and unpack correctly.
// async work. s.AdditionalProperties = schema.AdditionalProperties.Value
// any polymorphic properties need to be handled in their own threads }
// any properties each need to be processed in their own thread. }
// we go as fast as we can. s.Description = schema.Description.Value
polyCompletedChan := make(chan bool) s.Default = schema.Default.Value
propsChan := make(chan bool) if !schema.Nullable.IsEmpty() {
errChan := make(chan error) s.Nullable = &schema.Nullable.Value
}
if !schema.ReadOnly.IsEmpty() {
s.ReadOnly = schema.ReadOnly.Value
}
if !schema.WriteOnly.IsEmpty() {
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))
for i := 0; i < len(schema.Examples.Value); i++ {
examples[i] = schema.Examples.Value[i].Value
}
s.Examples = examples
}
s.Extensions = high.ExtractExtensions(schema.Extensions)
if !schema.Discriminator.IsEmpty() {
s.Discriminator = NewDiscriminator(schema.Discriminator.Value)
}
if !schema.XML.IsEmpty() {
s.XML = NewXML(schema.XML.Value)
}
if !schema.ExternalDocs.IsEmpty() {
s.ExternalDocs = NewExternalDoc(schema.ExternalDocs.Value)
}
var req []string
for i := range schema.Required.Value {
req = append(req, schema.Required.Value[i].Value)
}
s.Required = req
type buildResult struct { var enum []any
idx int if !schema.Anchor.IsEmpty() {
s *SchemaProxy s.Anchor = schema.Anchor.Value
} }
// for every item, build schema async // TODO: check this behavior.
buildSchema := func(sch lowmodel.ValueReference[*base.SchemaProxy], idx int, bChan chan buildResult) { for i := range schema.Enum.Value {
p := &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ enum = append(enum, schema.Enum.Value[i].Value)
ValueNode: sch.ValueNode, }
Value: sch.Value, s.Enum = enum
Reference: sch.GetReference(),
}}
bChan <- buildResult{idx: idx, s: p} // async work.
} // any polymorphic properties need to be handled in their own threads
// any properties each need to be processed in their own thread.
// we go as fast as we can.
polyCompletedChan := make(chan bool)
propsChan := make(chan bool)
errChan := make(chan error)
// schema async type buildResult struct {
buildOutSchemas := func(schemas []lowmodel.ValueReference[*base.SchemaProxy], items *[]*SchemaProxy, idx int
doneChan chan bool, e chan error, s *SchemaProxy
) { }
bChan := make(chan buildResult)
totalSchemas := len(schemas)
for i := range schemas {
go buildSchema(schemas[i], i, bChan)
}
j := 0
for j < totalSchemas {
select {
case r := <-bChan:
j++
(*items)[r.idx] = r.s
}
}
doneChan <- true
}
// props async // for every item, build schema async
var plock sync.Mutex buildSchema := func(sch lowmodel.ValueReference[*base.SchemaProxy], idx int, bChan chan buildResult) {
buildProps := func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*base.SchemaProxy], c chan bool, p := &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
props map[string]*SchemaProxy, sw int, ValueNode: sch.ValueNode,
) { Value: sch.Value,
plock.Lock() Reference: sch.GetReference(),
props[k.Value] = &SchemaProxy{ }}
schema: &lowmodel.NodeReference[*base.SchemaProxy]{
Value: v.Value,
KeyNode: k.KeyNode,
ValueNode: v.ValueNode,
},
}
plock.Unlock()
switch sw { bChan <- buildResult{idx: idx, s: p}
case 0: }
s.Properties = props
case 1:
s.DependentSchemas = props
case 2:
s.PatternProperties = props
}
c <- true
}
props := make(map[string]*SchemaProxy) // schema async
for k, v := range schema.Properties.Value { buildOutSchemas := func(schemas []lowmodel.ValueReference[*base.SchemaProxy], items *[]*SchemaProxy,
go buildProps(k, v, propsChan, props, 0) doneChan chan bool, e chan error,
} ) {
bChan := make(chan buildResult)
totalSchemas := len(schemas)
for i := range schemas {
go buildSchema(schemas[i], i, bChan)
}
j := 0
for j < totalSchemas {
select {
case r := <-bChan:
j++
(*items)[r.idx] = r.s
}
}
doneChan <- true
}
dependents := make(map[string]*SchemaProxy) // props async
for k, v := range schema.DependentSchemas.Value { var plock sync.Mutex
go buildProps(k, v, propsChan, dependents, 1) buildProps := func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*base.SchemaProxy], c chan bool,
} props map[string]*SchemaProxy, sw int,
patternProps := make(map[string]*SchemaProxy) ) {
for k, v := range schema.PatternProperties.Value { plock.Lock()
go buildProps(k, v, propsChan, patternProps, 2) props[k.Value] = &SchemaProxy{
} schema: &lowmodel.NodeReference[*base.SchemaProxy]{
Value: v.Value,
KeyNode: k.KeyNode,
ValueNode: v.ValueNode,
},
}
plock.Unlock()
var allOf []*SchemaProxy switch sw {
var oneOf []*SchemaProxy case 0:
var anyOf []*SchemaProxy s.Properties = props
var not *SchemaProxy case 1:
var items *DynamicValue[*SchemaProxy, bool] s.DependentSchemas = props
var prefixItems []*SchemaProxy case 2:
s.PatternProperties = props
}
c <- true
}
children := 0 props := make(map[string]*SchemaProxy)
if !schema.AllOf.IsEmpty() { for k, v := range schema.Properties.Value {
children++ go buildProps(k, v, propsChan, props, 0)
allOf = make([]*SchemaProxy, len(schema.AllOf.Value)) }
go buildOutSchemas(schema.AllOf.Value, &allOf, polyCompletedChan, errChan)
}
if !schema.AnyOf.IsEmpty() {
children++
anyOf = make([]*SchemaProxy, len(schema.AnyOf.Value))
go buildOutSchemas(schema.AnyOf.Value, &anyOf, polyCompletedChan, errChan)
}
if !schema.OneOf.IsEmpty() {
children++
oneOf = make([]*SchemaProxy, len(schema.OneOf.Value))
go buildOutSchemas(schema.OneOf.Value, &oneOf, polyCompletedChan, errChan)
}
if !schema.Not.IsEmpty() {
not = NewSchemaProxy(&schema.Not)
}
if !schema.Items.IsEmpty() {
if schema.Items.Value.IsA() {
items = &DynamicValue[*SchemaProxy, bool]{A: &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Items.ValueNode,
Value: schema.Items.Value.A,
KeyNode: schema.Items.KeyNode,
}}}
} else {
items = &DynamicValue[*SchemaProxy, bool]{N: 1, B: schema.Items.Value.B}
}
}
if !schema.PrefixItems.IsEmpty() {
children++
prefixItems = make([]*SchemaProxy, len(schema.PrefixItems.Value))
go buildOutSchemas(schema.PrefixItems.Value, &prefixItems, polyCompletedChan, errChan)
}
completeChildren := 0 dependents := make(map[string]*SchemaProxy)
completedProps := 0 for k, v := range schema.DependentSchemas.Value {
totalProps := len(schema.Properties.Value) + len(schema.DependentSchemas.Value) + len(schema.PatternProperties.Value) go buildProps(k, v, propsChan, dependents, 1)
if totalProps+children > 0 { }
allDone: patternProps := make(map[string]*SchemaProxy)
for true { for k, v := range schema.PatternProperties.Value {
select { go buildProps(k, v, propsChan, patternProps, 2)
case <-polyCompletedChan: }
completeChildren++
if totalProps == completedProps && children == completeChildren { var allOf []*SchemaProxy
break allDone var oneOf []*SchemaProxy
} var anyOf []*SchemaProxy
case <-propsChan: var not *SchemaProxy
completedProps++ var items *DynamicValue[*SchemaProxy, bool]
if totalProps == completedProps && children == completeChildren { var prefixItems []*SchemaProxy
break allDone
} children := 0
} if !schema.AllOf.IsEmpty() {
} children++
} allOf = make([]*SchemaProxy, len(schema.AllOf.Value))
s.OneOf = oneOf go buildOutSchemas(schema.AllOf.Value, &allOf, polyCompletedChan, errChan)
s.AnyOf = anyOf }
s.AllOf = allOf if !schema.AnyOf.IsEmpty() {
s.Items = items children++
s.PrefixItems = prefixItems anyOf = make([]*SchemaProxy, len(schema.AnyOf.Value))
s.Not = not go buildOutSchemas(schema.AnyOf.Value, &anyOf, polyCompletedChan, errChan)
return s }
if !schema.OneOf.IsEmpty() {
children++
oneOf = make([]*SchemaProxy, len(schema.OneOf.Value))
go buildOutSchemas(schema.OneOf.Value, &oneOf, polyCompletedChan, errChan)
}
if !schema.Not.IsEmpty() {
not = NewSchemaProxy(&schema.Not)
}
if !schema.Items.IsEmpty() {
if schema.Items.Value.IsA() {
items = &DynamicValue[*SchemaProxy, bool]{A: &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Items.ValueNode,
Value: schema.Items.Value.A,
KeyNode: schema.Items.KeyNode,
}}}
} else {
items = &DynamicValue[*SchemaProxy, bool]{N: 1, B: schema.Items.Value.B}
}
}
if !schema.PrefixItems.IsEmpty() {
children++
prefixItems = make([]*SchemaProxy, len(schema.PrefixItems.Value))
go buildOutSchemas(schema.PrefixItems.Value, &prefixItems, polyCompletedChan, errChan)
}
completeChildren := 0
completedProps := 0
totalProps := len(schema.Properties.Value) + len(schema.DependentSchemas.Value) + len(schema.PatternProperties.Value)
if totalProps+children > 0 {
allDone:
for true {
select {
case <-polyCompletedChan:
completeChildren++
if totalProps == completedProps && children == completeChildren {
break allDone
}
case <-propsChan:
completedProps++
if totalProps == completedProps && children == completeChildren {
break allDone
}
}
}
}
s.OneOf = oneOf
s.AnyOf = anyOf
s.AllOf = allOf
s.Items = items
s.PrefixItems = prefixItems
s.Not = not
return s
} }
// GoLow will return the low-level instance of Schema that was used to create the high level one. // GoLow will return the low-level instance of Schema that was used to create the high level one.
func (s *Schema) GoLow() *base.Schema { func (s *Schema) GoLow() *base.Schema {
return s.low return s.low
} }
// GoLowUntyped will return the low-level Schema instance that was used to create the high-level one, with no type // GoLowUntyped will return the low-level Schema instance that was used to create the high-level one, with no type
func (s *Schema) GoLowUntyped() any { func (s *Schema) GoLowUntyped() any {
return s.low return s.low
} }
// Render will return a YAML representation of the Schema object as a byte slice. // Render will return a YAML representation of the Schema object as a byte slice.
func (s *Schema) Render() ([]byte, error) { func (s *Schema) Render() ([]byte, error) {
return yaml.Marshal(s) return yaml.Marshal(s)
} }
// RenderInline will return a YAML representation of the Schema object as a byte slice. All of the // RenderInline will return a YAML representation of the Schema object as a byte slice. All of the
@@ -467,18 +486,18 @@ func (s *Schema) Render() ([]byte, error) {
// //
// Make sure you don't have any circular references! // Make sure you don't have any circular references!
func (s *Schema) RenderInline() ([]byte, error) { func (s *Schema) RenderInline() ([]byte, error) {
d, _ := s.MarshalYAMLInline() d, _ := s.MarshalYAMLInline()
return yaml.Marshal(d) return yaml.Marshal(d)
} }
// MarshalYAML will create a ready to render YAML representation of the ExternalDoc object. // MarshalYAML will create a ready to render YAML representation of the ExternalDoc object.
func (s *Schema) MarshalYAML() (interface{}, error) { func (s *Schema) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(s, s.low) nb := high.NewNodeBuilder(s, s.low)
return nb.Render(), nil return nb.Render(), nil
} }
func (s *Schema) MarshalYAMLInline() (interface{}, error) { func (s *Schema) MarshalYAMLInline() (interface{}, error) {
nb := high.NewNodeBuilder(s, s.low) nb := high.NewNodeBuilder(s, s.low)
nb.Resolve = true nb.Resolve = true
return nb.Render(), nil return nb.Render(), nil
} }

View File

@@ -4,17 +4,17 @@
package base package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base" lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strings" "strings"
"testing" "testing"
) )
func TestSchemaProxy_MarshalYAML(t *testing.T) { func TestSchemaProxy_MarshalYAML(t *testing.T) {
const ymlComponents = `components: const ymlComponents = `components:
schemas: schemas:
rice: rice:
type: string type: string
@@ -27,42 +27,41 @@ func TestSchemaProxy_MarshalYAML(t *testing.T) {
rice: rice:
$ref: '#/components/schemas/rice'` $ref: '#/components/schemas/rice'`
idx := func() *index.SpecIndex { idx := func() *index.SpecIndex {
var idxNode yaml.Node var idxNode yaml.Node
err := yaml.Unmarshal([]byte(ymlComponents), &idxNode) err := yaml.Unmarshal([]byte(ymlComponents), &idxNode)
assert.NoError(t, err) assert.NoError(t, err)
return index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig()) return index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
}() }()
const ref = "#/components/schemas/nice" const ref = "#/components/schemas/nice"
const ymlSchema = `$ref: '` + ref + `'` const ymlSchema = `$ref: '` + ref + `'`
var node yaml.Node var node yaml.Node
_ = yaml.Unmarshal([]byte(ymlSchema), &node) _ = yaml.Unmarshal([]byte(ymlSchema), &node)
lowProxy := new(lowbase.SchemaProxy) lowProxy := new(lowbase.SchemaProxy)
err := lowProxy.Build(node.Content[0], idx) err := lowProxy.Build(node.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
lowRef := low.NodeReference[*lowbase.SchemaProxy]{ lowRef := low.NodeReference[*lowbase.SchemaProxy]{
Value: lowProxy, Value: lowProxy,
} }
sp := NewSchemaProxy(&lowRef) sp := NewSchemaProxy(&lowRef)
rend, _ := sp.Render() rend, _ := sp.Render()
assert.Equal(t, "$ref: '#/components/schemas/nice'", strings.TrimSpace(string(rend))) assert.Equal(t, "$ref: '#/components/schemas/nice'", strings.TrimSpace(string(rend)))
} }
func TestCreateSchemaProxy(t *testing.T) { func TestCreateSchemaProxy(t *testing.T) {
sp := CreateSchemaProxy(&Schema{Description: "iAmASchema"}) sp := CreateSchemaProxy(&Schema{Description: "iAmASchema"})
assert.Equal(t, "iAmASchema", sp.rendered.Description) assert.Equal(t, "iAmASchema", sp.rendered.Description)
assert.False(t, sp.IsReference()) assert.False(t, sp.IsReference())
} }
func TestCreateSchemaProxyRef(t *testing.T) { func TestCreateSchemaProxyRef(t *testing.T) {
sp := CreateSchemaProxyRef("#/components/schemas/MySchema") sp := CreateSchemaProxyRef("#/components/schemas/MySchema")
assert.Equal(t, "#/components/schemas/MySchema", sp.GetReference()) assert.Equal(t, "#/components/schemas/MySchema", sp.GetReference())
assert.True(t, sp.IsReference()) assert.True(t, sp.IsReference())
} }

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ import (
// security schemes declared in it which are all required (that is, there is a logical AND between the schemes). // security schemes declared in it which are all required (that is, there is a logical AND between the schemes).
// //
// The name used for each property MUST correspond to a security scheme declared in the Security Definitions // The name used for each property MUST correspond to a security scheme declared in the Security Definitions
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
type SecurityRequirement struct { type SecurityRequirement struct {
Requirements map[string][]string `json:"-" yaml:"-"` Requirements map[string][]string `json:"-" yaml:"-"`
low *base.SecurityRequirement low *base.SecurityRequirement
@@ -108,7 +108,7 @@ func (s *SecurityRequirement) MarshalYAML() (interface{}, error) {
for t := range keys[k].val { for t := range keys[k].val {
reqs[t] = &req{val: keys[k].val[t], line: 9999 + t} reqs[t] = &req{val: keys[k].val[t], line: 9999 + t}
if keys[k].lowVal != nil { if keys[k].lowVal != nil {
for _ = range keys[k].lowVal.Value[t].Value { for range keys[k].lowVal.Value[t].Value {
fh := keys[k].val[t] fh := keys[k].val[t]
df := keys[k].lowVal.Value[t].Value df := keys[k].lowVal.Value[t].Value
if fh == df { if fh == df {

View File

@@ -4,9 +4,9 @@
package base package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// XML represents a high-level representation of an XML object defined by all versions of OpenAPI and backed by // XML represents a high-level representation of an XML object defined by all versions of OpenAPI and backed by
@@ -16,49 +16,49 @@ import (
// //
// When using arrays, XML element names are not inferred (for singular/plural forms) and the name property SHOULD be // When using arrays, XML element names are not inferred (for singular/plural forms) and the name property SHOULD be
// used to add that information. See examples for expected behavior. // used to add that information. See examples for expected behavior.
// v2 - https://swagger.io/specification/v2/#xmlObject //
// v3 - https://swagger.io/specification/#xml-object // v2 - https://swagger.io/specification/v2/#xmlObject
// v3 - https://swagger.io/specification/#xml-object
type XML struct { type XML struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"` Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"` Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"`
Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"` Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"`
Extensions map[string]any Extensions map[string]any
low *low.XML low *low.XML
} }
// NewXML creates a new high-level XML instance from a low-level one. // NewXML creates a new high-level XML instance from a low-level one.
func NewXML(xml *low.XML) *XML { func NewXML(xml *low.XML) *XML {
x := new(XML) x := new(XML)
x.low = xml x.low = xml
x.Name = xml.Name.Value x.Name = xml.Name.Value
x.Namespace = xml.Namespace.Value x.Namespace = xml.Namespace.Value
x.Prefix = xml.Prefix.Value x.Prefix = xml.Prefix.Value
x.Attribute = xml.Attribute.Value x.Attribute = xml.Attribute.Value
x.Wrapped = xml.Wrapped.Value x.Wrapped = xml.Wrapped.Value
x.Extensions = high.ExtractExtensions(xml.Extensions) x.Extensions = high.ExtractExtensions(xml.Extensions)
return x return x
} }
// GoLow returns the low level XML reference used to create the high level one. // GoLow returns the low level XML reference used to create the high level one.
func (x *XML) GoLow() *low.XML { func (x *XML) GoLow() *low.XML {
return x.low return x.low
} }
// GoLowUntyped will return the low-level XML instance that was used to create the high-level one, with no type // GoLowUntyped will return the low-level XML instance that was used to create the high-level one, with no type
func (x *XML) GoLowUntyped() any { func (x *XML) GoLowUntyped() any {
return x.low return x.low
} }
// Render will return a YAML representation of the XML object as a byte slice. // Render will return a YAML representation of the XML object as a byte slice.
func (x *XML) Render() ([]byte, error) { func (x *XML) Render() ([]byte, error) {
return yaml.Marshal(x) return yaml.Marshal(x)
} }
// MarshalYAML will create a ready to render YAML representation of the XML object. // MarshalYAML will create a ready to render YAML representation of the XML object.
func (x *XML) MarshalYAML() (interface{}, error) { func (x *XML) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(x, x.low) nb := high.NewNodeBuilder(x, x.low)
return nb.Render(), nil return nb.Render(), nil
} }

View File

@@ -71,4 +71,4 @@ wrapped: true`
highXMLBytes, _ = highXML.Render() highXMLBytes, _ = highXML.Render()
assert.NotEqual(t, yml, strings.TrimSpace(string(highXMLBytes))) assert.NotEqual(t, yml, strings.TrimSpace(string(highXMLBytes)))
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -58,8 +58,9 @@ func ExtractExtensions(extensions map[low.KeyReference[string]]low.ValueReferenc
// `low` represents the HIGH type of the object that contains the extensions. // `low` represents the HIGH type of the object that contains the extensions.
// //
// to use: // to use:
// schema := schemaProxy.Schema() // any high-level object that has extensions //
// extensions, err := UnpackExtensions[MyComplexType, low.Schema](schema) // schema := schemaProxy.Schema() // any high-level object that has extensions
// extensions, err := UnpackExtensions[MyComplexType, low.Schema](schema)
func UnpackExtensions[T any, R low.HasExtensions[T]](low GoesLow[R]) (map[string]*T, error) { func UnpackExtensions[T any, R low.HasExtensions[T]](low GoesLow[R]) (map[string]*T, error) {
m := make(map[string]*T) m := make(map[string]*T)
ext := low.GoLow().GetExtensions() ext := low.GoLow().GetExtensions()
@@ -75,4 +76,3 @@ func UnpackExtensions[T any, R low.HasExtensions[T]](low GoesLow[R]) (map[string
} }
return m, nil return m, nil
} }

View File

@@ -4,127 +4,127 @@
package high package high
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing" "testing"
) )
func TestExtractExtensions(t *testing.T) { func TestExtractExtensions(t *testing.T) {
n := make(map[low.KeyReference[string]]low.ValueReference[any]) n := make(map[low.KeyReference[string]]low.ValueReference[any])
n[low.KeyReference[string]{ n[low.KeyReference[string]{
Value: "pb33f", Value: "pb33f",
}] = low.ValueReference[any]{ }] = low.ValueReference[any]{
Value: "new cowboy in town", Value: "new cowboy in town",
} }
ext := ExtractExtensions(n) ext := ExtractExtensions(n)
assert.Equal(t, "new cowboy in town", ext["pb33f"]) assert.Equal(t, "new cowboy in town", ext["pb33f"])
} }
type textExtension struct { type textExtension struct {
Cowboy string Cowboy string
Power int Power int
} }
type parent struct { type parent struct {
low *child low *child
} }
func (p *parent) GoLow() *child { func (p *parent) GoLow() *child {
return p.low return p.low
} }
type child struct { type child struct {
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
} }
func (c *child) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (c *child) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
return c.Extensions return c.Extensions
} }
func TestUnpackExtensions(t *testing.T) { func TestUnpackExtensions(t *testing.T) {
var resultA, resultB yaml.Node var resultA, resultB yaml.Node
ymlA := ` ymlA := `
cowboy: buckaroo cowboy: buckaroo
power: 100` power: 100`
ymlB := ` ymlB := `
cowboy: frogman cowboy: frogman
power: 2` power: 2`
err := yaml.Unmarshal([]byte(ymlA), &resultA) err := yaml.Unmarshal([]byte(ymlA), &resultA)
assert.NoError(t, err) assert.NoError(t, err)
err = yaml.Unmarshal([]byte(ymlB), &resultB) err = yaml.Unmarshal([]byte(ymlB), &resultB)
assert.NoError(t, err) assert.NoError(t, err)
n := make(map[low.KeyReference[string]]low.ValueReference[any]) n := make(map[low.KeyReference[string]]low.ValueReference[any])
n[low.KeyReference[string]{ n[low.KeyReference[string]{
Value: "x-rancher-a", Value: "x-rancher-a",
}] = low.ValueReference[any]{ }] = low.ValueReference[any]{
ValueNode: resultA.Content[0], ValueNode: resultA.Content[0],
} }
n[low.KeyReference[string]{ n[low.KeyReference[string]{
Value: "x-rancher-b", Value: "x-rancher-b",
}] = low.ValueReference[any]{ }] = low.ValueReference[any]{
ValueNode: resultB.Content[0], ValueNode: resultB.Content[0],
} }
c := new(child) c := new(child)
c.Extensions = n c.Extensions = n
p := new(parent) p := new(parent)
p.low = c p.low = c
res, err := UnpackExtensions[textExtension, *child](p) res, err := UnpackExtensions[textExtension, *child](p)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, res) assert.NotEmpty(t, res)
assert.Equal(t, "buckaroo", res["x-rancher-a"].Cowboy) assert.Equal(t, "buckaroo", res["x-rancher-a"].Cowboy)
assert.Equal(t, 100, res["x-rancher-a"].Power) assert.Equal(t, 100, res["x-rancher-a"].Power)
assert.Equal(t, "frogman", res["x-rancher-b"].Cowboy) assert.Equal(t, "frogman", res["x-rancher-b"].Cowboy)
assert.Equal(t, 2, res["x-rancher-b"].Power) assert.Equal(t, 2, res["x-rancher-b"].Power)
} }
func TestUnpackExtensions_Fail(t *testing.T) { func TestUnpackExtensions_Fail(t *testing.T) {
var resultA, resultB yaml.Node var resultA, resultB yaml.Node
ymlA := ` ymlA := `
cowboy: buckaroo cowboy: buckaroo
power: 100` power: 100`
// this is incorrect types, unpacking will fail. // this is incorrect types, unpacking will fail.
ymlB := ` ymlB := `
cowboy: 0 cowboy: 0
power: hello` power: hello`
err := yaml.Unmarshal([]byte(ymlA), &resultA) err := yaml.Unmarshal([]byte(ymlA), &resultA)
assert.NoError(t, err) assert.NoError(t, err)
err = yaml.Unmarshal([]byte(ymlB), &resultB) err = yaml.Unmarshal([]byte(ymlB), &resultB)
assert.NoError(t, err) assert.NoError(t, err)
n := make(map[low.KeyReference[string]]low.ValueReference[any]) n := make(map[low.KeyReference[string]]low.ValueReference[any])
n[low.KeyReference[string]{ n[low.KeyReference[string]{
Value: "x-rancher-a", Value: "x-rancher-a",
}] = low.ValueReference[any]{ }] = low.ValueReference[any]{
ValueNode: resultA.Content[0], ValueNode: resultA.Content[0],
} }
n[low.KeyReference[string]{ n[low.KeyReference[string]{
Value: "x-rancher-b", Value: "x-rancher-b",
}] = low.ValueReference[any]{ }] = low.ValueReference[any]{
ValueNode: resultB.Content[0], ValueNode: resultB.Content[0],
} }
c := new(child) c := new(child)
c.Extensions = n c.Extensions = n
p := new(parent) p := new(parent)
p.low = c p.low = c
res, er := UnpackExtensions[textExtension, *child](p) res, er := UnpackExtensions[textExtension, *child](p)
assert.Error(t, er) assert.Error(t, er)
assert.Empty(t, res) assert.Empty(t, res)
} }

View File

@@ -14,7 +14,7 @@ import (
// //
// An object to hold data types that can be consumed and produced by operations. These data types can be primitives, // An object to hold data types that can be consumed and produced by operations. These data types can be primitives,
// arrays or models. // arrays or models.
// - https://swagger.io/specification/v2/#definitionsObject // - https://swagger.io/specification/v2/#definitionsObject
type Definitions struct { type Definitions struct {
Definitions map[string]*highbase.SchemaProxy Definitions map[string]*highbase.SchemaProxy
low *low.Definitions low *low.Definitions

View File

@@ -7,7 +7,7 @@ import low "github.com/pb33f/libopenapi/datamodel/low/v2"
// Example represents a high-level Swagger / OpenAPI 2 Example object, backed by a low level one. // Example represents a high-level Swagger / OpenAPI 2 Example object, backed by a low level one.
// Allows sharing examples for operation responses // Allows sharing examples for operation responses
// - https://swagger.io/specification/v2/#exampleObject // - https://swagger.io/specification/v2/#exampleObject
type Example struct { type Example struct {
Values map[string]any Values map[string]any
low *low.Examples low *low.Examples

View File

@@ -10,7 +10,7 @@ import (
// Header Represents a high-level Swagger / OpenAPI 2 Header object, backed by a low-level one. // Header Represents a high-level Swagger / OpenAPI 2 Header object, backed by a low-level one.
// A Header is essentially identical to a Parameter, except it does not contain 'name' or 'in' properties. // A Header is essentially identical to a Parameter, except it does not contain 'name' or 'in' properties.
// - https://swagger.io/specification/v2/#headerObject // - https://swagger.io/specification/v2/#headerObject
type Header struct { type Header struct {
Type string Type string
Format string Format string

View File

@@ -10,7 +10,7 @@ import (
// Items is a high-level representation of a Swagger / OpenAPI 2 Items object, backed by a low level one. // Items is a high-level representation of a Swagger / OpenAPI 2 Items object, backed by a low level one.
// Items is a limited subset of JSON-Schema's items object. It is used by parameter definitions that are not // Items is a limited subset of JSON-Schema's items object. It is used by parameter definitions that are not
// located in "body" // located in "body"
// - https://swagger.io/specification/v2/#itemsObject // - https://swagger.io/specification/v2/#itemsObject
type Items struct { type Items struct {
Type string Type string
Format string Format string

View File

@@ -11,7 +11,7 @@ import (
// Operation represents a high-level Swagger / OpenAPI 2 Operation object, backed by a low-level one. // Operation represents a high-level Swagger / OpenAPI 2 Operation object, backed by a low-level one.
// It describes a single API operation on a path. // It describes a single API operation on a path.
// - https://swagger.io/specification/v2/#operationObject // - https://swagger.io/specification/v2/#operationObject
type Operation struct { type Operation struct {
Tags []string Tags []string
Summary string Summary string

View File

@@ -16,29 +16,39 @@ import (
// There are five possible parameter types. // There are five possible parameter types.
// //
// Path // Path
// Used together with Path Templating, where the parameter value is actually part of the operation's URL. //
// This does not include the host or base path of the API. For example, in /items/{itemId}, the path parameter is itemId. // Used together with Path Templating, where the parameter value is actually part of the operation's URL.
// This does not include the host or base path of the API. For example, in /items/{itemId}, the path parameter is itemId.
//
// Query // Query
// Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id. //
// Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id.
//
// Header // Header
// Custom headers that are expected as part of the request. //
// Custom headers that are expected as part of the request.
//
// Body // Body
// The payload that's appended to the HTTP request. Since there can only be one payload, there can only be one body parameter. //
// The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only. // The payload that's appended to the HTTP request. Since there can only be one payload, there can only be one body parameter.
// Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation. // The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only.
// Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation.
//
// Form // Form
// Used to describe the payload of an HTTP request when either application/x-www-form-urlencoded, multipart/form-data //
// or both are used as the content type of the request (in Swagger's definition, the consumes property of an operation). // Used to describe the payload of an HTTP request when either application/x-www-form-urlencoded, multipart/form-data
// This is the only parameter type that can be used to send files, thus supporting the file type. Since form parameters // or both are used as the content type of the request (in Swagger's definition, the consumes property of an operation).
// are sent in the payload, they cannot be declared together with a body parameter for the same operation. Form // This is the only parameter type that can be used to send files, thus supporting the file type. Since form parameters
// parameters have a different format based on the content-type used (for further details, // are sent in the payload, they cannot be declared together with a body parameter for the same operation. Form
// consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4): // parameters have a different format based on the content-type used (for further details,
// application/x-www-form-urlencoded - Similar to the format of Query parameters but as a payload. For example, // consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4):
// foo=1&bar=swagger - both foo and bar are form parameters. This is normally used for simple parameters that are // application/x-www-form-urlencoded - Similar to the format of Query parameters but as a payload. For example,
// being transferred. // foo=1&bar=swagger - both foo and bar are form parameters. This is normally used for simple parameters that are
// multipart/form-data - each parameter takes a section in the payload with an internal header. For example, for // being transferred.
// the header Content-Disposition: form-data; name="submit-name" the name of the parameter is // multipart/form-data - each parameter takes a section in the payload with an internal header. For example, for
// submit-name. This type of form parameters is more commonly used for file transfers // the header Content-Disposition: form-data; name="submit-name" the name of the parameter is
// submit-name. This type of form parameters is more commonly used for file transfers
//
// https://swagger.io/specification/v2/#parameterObject // https://swagger.io/specification/v2/#parameterObject
type Parameter struct { type Parameter struct {
Name string Name string

View File

@@ -10,7 +10,7 @@ import low "github.com/pb33f/libopenapi/datamodel/low/v2"
// //
// ParameterDefinitions holds parameters to be reused across operations. Parameter definitions can be // ParameterDefinitions holds parameters to be reused across operations. Parameter definitions can be
// referenced to the ones defined here. It does not define global operation parameters // referenced to the ones defined here. It does not define global operation parameters
// - https://swagger.io/specification/v2/#parametersDefinitionsObject // - https://swagger.io/specification/v2/#parametersDefinitionsObject
type ParameterDefinitions struct { type ParameterDefinitions struct {
Definitions map[string]*Parameter Definitions map[string]*Parameter
low *low.ParameterDefinitions low *low.ParameterDefinitions

View File

@@ -4,8 +4,8 @@
package v2 package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v2" low "github.com/pb33f/libopenapi/datamodel/low/v2"
) )
// PathItem represents a high-level Swagger / OpenAPI 2 PathItem object backed by a low-level one. // PathItem represents a high-level Swagger / OpenAPI 2 PathItem object backed by a low-level one.
@@ -13,128 +13,128 @@ import (
// Describes the operations available on a single path. A Path Item may be empty, due to ACL constraints. // Describes the operations available on a single path. A Path Item may be empty, due to ACL constraints.
// The path itself is still exposed to the tooling, but will not know which operations and parameters // The path itself is still exposed to the tooling, but will not know which operations and parameters
// are available. // are available.
// - https://swagger.io/specification/v2/#pathItemObject // - https://swagger.io/specification/v2/#pathItemObject
type PathItem struct { type PathItem struct {
Ref string Ref string
Get *Operation Get *Operation
Put *Operation Put *Operation
Post *Operation Post *Operation
Delete *Operation Delete *Operation
Options *Operation Options *Operation
Head *Operation Head *Operation
Patch *Operation Patch *Operation
Parameters []*Parameter Parameters []*Parameter
Extensions map[string]any Extensions map[string]any
low *low.PathItem low *low.PathItem
} }
// NewPathItem will create a new high-level PathItem from a low-level one. All paths are built out asynchronously. // 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 *low.PathItem) *PathItem {
p := new(PathItem) p := new(PathItem)
p.low = pathItem p.low = pathItem
p.Extensions = high.ExtractExtensions(pathItem.Extensions) p.Extensions = high.ExtractExtensions(pathItem.Extensions)
if !pathItem.Parameters.IsEmpty() { if !pathItem.Parameters.IsEmpty() {
var params []*Parameter var params []*Parameter
for k := range pathItem.Parameters.Value { for k := range pathItem.Parameters.Value {
params = append(params, NewParameter(pathItem.Parameters.Value[k].Value)) params = append(params, NewParameter(pathItem.Parameters.Value[k].Value))
} }
p.Parameters = params p.Parameters = params
} }
var buildOperation = func(method string, op *low.Operation, resChan chan<- asyncResult[*Operation]) { var buildOperation = func(method string, op *low.Operation, resChan chan<- asyncResult[*Operation]) {
resChan <- asyncResult[*Operation]{ resChan <- asyncResult[*Operation]{
key: method, key: method,
result: NewOperation(op), result: NewOperation(op),
} }
} }
totalOperations := 0 totalOperations := 0
resChan := make(chan asyncResult[*Operation]) resChan := make(chan asyncResult[*Operation])
if !pathItem.Get.IsEmpty() { if !pathItem.Get.IsEmpty() {
totalOperations++ totalOperations++
go buildOperation(low.GetLabel, pathItem.Get.Value, resChan) go buildOperation(low.GetLabel, pathItem.Get.Value, resChan)
} }
if !pathItem.Put.IsEmpty() { if !pathItem.Put.IsEmpty() {
totalOperations++ totalOperations++
go buildOperation(low.PutLabel, pathItem.Put.Value, resChan) go buildOperation(low.PutLabel, pathItem.Put.Value, resChan)
} }
if !pathItem.Post.IsEmpty() { if !pathItem.Post.IsEmpty() {
totalOperations++ totalOperations++
go buildOperation(low.PostLabel, pathItem.Post.Value, resChan) go buildOperation(low.PostLabel, pathItem.Post.Value, resChan)
} }
if !pathItem.Patch.IsEmpty() { if !pathItem.Patch.IsEmpty() {
totalOperations++ totalOperations++
go buildOperation(low.PatchLabel, pathItem.Patch.Value, resChan) go buildOperation(low.PatchLabel, pathItem.Patch.Value, resChan)
} }
if !pathItem.Delete.IsEmpty() { if !pathItem.Delete.IsEmpty() {
totalOperations++ totalOperations++
go buildOperation(low.DeleteLabel, pathItem.Delete.Value, resChan) go buildOperation(low.DeleteLabel, pathItem.Delete.Value, resChan)
} }
if !pathItem.Head.IsEmpty() { if !pathItem.Head.IsEmpty() {
totalOperations++ totalOperations++
go buildOperation(low.HeadLabel, pathItem.Head.Value, resChan) go buildOperation(low.HeadLabel, pathItem.Head.Value, resChan)
} }
if !pathItem.Options.IsEmpty() { if !pathItem.Options.IsEmpty() {
totalOperations++ totalOperations++
go buildOperation(low.OptionsLabel, pathItem.Options.Value, resChan) go buildOperation(low.OptionsLabel, pathItem.Options.Value, resChan)
} }
completedOperations := 0 completedOperations := 0
for completedOperations < totalOperations { for completedOperations < totalOperations {
select { select {
case r := <-resChan: case r := <-resChan:
switch r.key { switch r.key {
case low.GetLabel: case low.GetLabel:
completedOperations++ completedOperations++
p.Get = r.result p.Get = r.result
case low.PutLabel: case low.PutLabel:
completedOperations++ completedOperations++
p.Put = r.result p.Put = r.result
case low.PostLabel: case low.PostLabel:
completedOperations++ completedOperations++
p.Post = r.result p.Post = r.result
case low.PatchLabel: case low.PatchLabel:
completedOperations++ completedOperations++
p.Patch = r.result p.Patch = r.result
case low.DeleteLabel: case low.DeleteLabel:
completedOperations++ completedOperations++
p.Delete = r.result p.Delete = r.result
case low.HeadLabel: case low.HeadLabel:
completedOperations++ completedOperations++
p.Head = r.result p.Head = r.result
case low.OptionsLabel: case low.OptionsLabel:
completedOperations++ completedOperations++
p.Options = r.result p.Options = r.result
} }
} }
} }
return p return p
} }
// GoLow returns the low-level PathItem used to create the high-level one. // GoLow returns the low-level PathItem used to create the high-level one.
func (p *PathItem) GoLow() *low.PathItem { func (p *PathItem) GoLow() *low.PathItem {
return p.low return p.low
} }
func (p *PathItem) GetOperations() map[string]*Operation { func (p *PathItem) GetOperations() map[string]*Operation {
o := make(map[string]*Operation) o := make(map[string]*Operation)
if p.Get != nil { if p.Get != nil {
o[low.GetLabel] = p.Get o[low.GetLabel] = p.Get
} }
if p.Put != nil { if p.Put != nil {
o[low.PutLabel] = p.Put o[low.PutLabel] = p.Put
} }
if p.Post != nil { if p.Post != nil {
o[low.PostLabel] = p.Post o[low.PostLabel] = p.Post
} }
if p.Delete != nil { if p.Delete != nil {
o[low.DeleteLabel] = p.Delete o[low.DeleteLabel] = p.Delete
} }
if p.Options != nil { if p.Options != nil {
o[low.OptionsLabel] = p.Options o[low.OptionsLabel] = p.Options
} }
if p.Head != nil { if p.Head != nil {
o[low.HeadLabel] = p.Head o[low.HeadLabel] = p.Head
} }
if p.Patch != nil { if p.Patch != nil {
o[low.PatchLabel] = p.Patch o[low.PatchLabel] = p.Patch
} }
return o return o
} }

View File

@@ -4,17 +4,17 @@
package v2 package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v2 "github.com/pb33f/libopenapi/datamodel/low/v2" v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing" "testing"
) )
func TestPathItem_GetOperations(t *testing.T) { func TestPathItem_GetOperations(t *testing.T) {
yml := `get: yml := `get:
description: get description: get
put: put:
description: put description: put
@@ -30,15 +30,15 @@ options:
description: options description: options
` `
var idxNode yaml.Node var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode) _ = yaml.Unmarshal([]byte(yml), &idxNode)
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndex(&idxNode)
var n v2.PathItem var n v2.PathItem
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(idxNode.Content[0], idx) _ = n.Build(idxNode.Content[0], idx)
r := NewPathItem(&n) r := NewPathItem(&n)
assert.Len(t, r.GetOperations(), 7) assert.Len(t, r.GetOperations(), 7)
} }

View File

@@ -11,7 +11,7 @@ import (
// Response is a representation of a high-level Swagger / OpenAPI 2 Response object, backed by a low-level one. // Response is a representation of a high-level Swagger / OpenAPI 2 Response object, backed by a low-level one.
// Response describes a single response from an API Operation // Response describes a single response from an API Operation
// - https://swagger.io/specification/v2/#responseObject // - https://swagger.io/specification/v2/#responseObject
type Response struct { type Response struct {
Description string Description string
Schema *base.SchemaProxy Schema *base.SchemaProxy

View File

@@ -10,7 +10,7 @@ import low "github.com/pb33f/libopenapi/datamodel/low/v2"
// //
// ResponsesDefinitions is an object to hold responses to be reused across operations. Response definitions can be // ResponsesDefinitions is an object to hold responses to be reused across operations. Response definitions can be
// referenced to the ones defined here. It does not define global operation responses // referenced to the ones defined here. It does not define global operation responses
// - https://swagger.io/specification/v2/#responsesDefinitionsObject // - https://swagger.io/specification/v2/#responsesDefinitionsObject
type ResponsesDefinitions struct { type ResponsesDefinitions struct {
Definitions map[string]*Response Definitions map[string]*Response
low *low.ResponsesDefinitions low *low.ResponsesDefinitions

View File

@@ -10,7 +10,7 @@ import (
// Scopes is a high-level representation of a Swagger / OpenAPI 2 OAuth2 Scopes object, that is backed by a low-level one. // Scopes is a high-level representation of a Swagger / OpenAPI 2 OAuth2 Scopes object, that is backed by a low-level one.
// //
// Scopes lists the available scopes for an OAuth2 security scheme. // Scopes lists the available scopes for an OAuth2 security scheme.
// - https://swagger.io/specification/v2/#scopesObject // - https://swagger.io/specification/v2/#scopesObject
type Scopes struct { type Scopes struct {
Values map[string]string Values map[string]string
low *low.Scopes low *low.Scopes

View File

@@ -10,7 +10,7 @@ import low "github.com/pb33f/libopenapi/datamodel/low/v2"
// //
// A declaration of the security schemes available to be used in the specification. This does not enforce the security // A declaration of the security schemes available to be used in the specification. This does not enforce the security
// schemes on the operations and only serves to provide the relevant details for each scheme // schemes on the operations and only serves to provide the relevant details for each scheme
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
type SecurityDefinitions struct { type SecurityDefinitions struct {
Definitions map[string]*SecurityScheme Definitions map[string]*SecurityScheme
low *low.SecurityDefinitions low *low.SecurityDefinitions

View File

@@ -14,7 +14,7 @@ import (
// SecurityScheme allows the definition of a security scheme that can be used by the operations. Supported schemes are // SecurityScheme allows the definition of a security scheme that can be used by the operations. Supported schemes are
// basic authentication, an API key (either as a header or as a query parameter) and OAuth2's common flows // basic authentication, an API key (either as a header or as a query parameter) and OAuth2's common flows
// (implicit, password, application and access code) // (implicit, password, application and access code)
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
type SecurityScheme struct { type SecurityScheme struct {
Type string Type string
Description string Description string

View File

@@ -17,7 +17,7 @@ import (
// PathItem Object that describes a set of requests that may be initiated by the API provider and the expected // PathItem Object that describes a set of requests that may be initiated by the API provider and the expected
// responses. The key value used to identify the path item object is an expression, evaluated at runtime, // responses. The key value used to identify the path item object is an expression, evaluated at runtime,
// that identifies a URL to use for the callback operation. // that identifies a URL to use for the callback operation.
// - https://spec.openapis.org/oas/v3.1.0#callback-object // - https://spec.openapis.org/oas/v3.1.0#callback-object
type Callback struct { type Callback struct {
Expression map[string]*PathItem `json:"-" yaml:"-"` Expression map[string]*PathItem `json:"-" yaml:"-"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions map[string]any `json:"-" yaml:"-"`

View File

@@ -4,49 +4,49 @@
package v3 package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strings" "strings"
"testing" "testing"
) )
func TestCallback_MarshalYAML(t *testing.T) { func TestCallback_MarshalYAML(t *testing.T) {
cb := &Callback{ cb := &Callback{
Expression: map[string]*PathItem{ Expression: map[string]*PathItem{
"https://pb33f.io": { "https://pb33f.io": {
Get: &Operation{ Get: &Operation{
OperationId: "oneTwoThree", OperationId: "oneTwoThree",
}, },
}, },
"https://pb33f.io/libopenapi": { "https://pb33f.io/libopenapi": {
Get: &Operation{ Get: &Operation{
OperationId: "openaypeeeye", OperationId: "openaypeeeye",
}, },
}, },
}, },
Extensions: map[string]any{ Extensions: map[string]any{
"x-burgers": "why not?", "x-burgers": "why not?",
}, },
} }
rend, _ := cb.Render() rend, _ := cb.Render()
// there is no way to determine order in brand new maps, so we have to check length. // there is no way to determine order in brand new maps, so we have to check length.
assert.Len(t, rend, 152) assert.Len(t, rend, 152)
// mutate // mutate
cb.Expression["https://pb33f.io"].Get.OperationId = "blim-blam" cb.Expression["https://pb33f.io"].Get.OperationId = "blim-blam"
cb.Extensions = map[string]interface{}{"x-burgers": "yes please!"} cb.Extensions = map[string]interface{}{"x-burgers": "yes please!"}
rend, _ = cb.Render() rend, _ = cb.Render()
// there is no way to determine order in brand new maps, so we have to check length. // there is no way to determine order in brand new maps, so we have to check length.
assert.Len(t, rend, 153) assert.Len(t, rend, 153)
k := `x-break-everything: please k := `x-break-everything: please
'{$request.query.queryUrl}': '{$request.query.queryUrl}':
post: post:
description: Callback payload description: Callback payload
@@ -58,19 +58,19 @@ func TestCallback_MarshalYAML(t *testing.T) {
schema: schema:
type: string` type: string`
var idxNode yaml.Node var idxNode yaml.Node
err := yaml.Unmarshal([]byte(k), &idxNode) err := yaml.Unmarshal([]byte(k), &idxNode)
assert.NoError(t, err) assert.NoError(t, err)
idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig()) idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
var n v3.Callback var n v3.Callback
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx) _ = n.Build(idxNode.Content[0], idx)
r := NewCallback(&n) r := NewCallback(&n)
assert.Equal(t, "please", r.Extensions["x-break-everything"]) assert.Equal(t, "please", r.Extensions["x-break-everything"])
rend, _ = r.Render() rend, _ = r.Render()
assert.Equal(t, k, strings.TrimSpace(string(rend))) assert.Equal(t, k, strings.TrimSpace(string(rend)))
} }

View File

@@ -28,18 +28,18 @@ const (
// //
// Holds a set of reusable objects for different aspects of the OAS. All objects defined within the components object // Holds a set of reusable objects for different aspects of the OAS. All objects defined within the components object
// will have no effect on the API unless they are explicitly referenced from properties outside the components object. // 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 // - https://spec.openapis.org/oas/v3.1.0#components-object
type Components struct { type Components struct {
Schemas map[string]*highbase.SchemaProxy `json:"schemas,omitempty" yaml:"schemas,omitempty"` Schemas map[string]*highbase.SchemaProxy `json:"schemas,omitempty" yaml:"schemas,omitempty"`
Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"` Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"`
Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Examples map[string]*highbase.Example `json:"examples,omitempty" yaml:"examples,omitempty"` Examples map[string]*highbase.Example `json:"examples,omitempty" yaml:"examples,omitempty"`
RequestBodies map[string]*RequestBody `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"` RequestBodies map[string]*RequestBody `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"`
SecuritySchemes map[string]*SecurityScheme `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"` SecuritySchemes map[string]*SecurityScheme `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
Links map[string]*Link `json:"links,omitempty" yaml:"links,omitempty"` Links map[string]*Link `json:"links,omitempty" yaml:"links,omitempty"`
Callbacks map[string]*Callback `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` Callbacks map[string]*Callback `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"` Extensions map[string]any `json:"-" yaml:"-"`
low *low.Components low *low.Components
} }

View File

@@ -4,53 +4,53 @@
package v3 package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strings" "strings"
"testing" "testing"
) )
func TestComponents_MarshalYAML(t *testing.T) { func TestComponents_MarshalYAML(t *testing.T) {
comp := &Components{ comp := &Components{
Responses: map[string]*Response{ Responses: map[string]*Response{
"200": { "200": {
Description: "OK", Description: "OK",
}, },
}, },
Parameters: map[string]*Parameter{ Parameters: map[string]*Parameter{
"id": { "id": {
Name: "id", Name: "id",
In: "path", In: "path",
}, },
}, },
RequestBodies: map[string]*RequestBody{ RequestBodies: map[string]*RequestBody{
"body": { "body": {
Content: map[string]*MediaType{ Content: map[string]*MediaType{
"application/json": { "application/json": {
Example: "why?", Example: "why?",
}, },
}, },
}, },
}, },
} }
dat, _ := comp.Render() dat, _ := comp.Render()
var idxNode yaml.Node var idxNode yaml.Node
_ = yaml.Unmarshal(dat, &idxNode) _ = yaml.Unmarshal(dat, &idxNode)
idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig()) idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
var n v3.Components var n v3.Components
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx) _ = n.Build(idxNode.Content[0], idx)
r := NewComponents(&n) r := NewComponents(&n)
desired := `responses: desired := `responses:
"200": "200":
description: OK description: OK
parameters: parameters:
@@ -63,6 +63,6 @@ requestBodies:
application/json: application/json:
example: why?` example: why?`
dat, _ = r.Render() dat, _ = r.Render()
assert.Equal(t, desired, strings.TrimSpace(string(dat))) assert.Equal(t, desired, strings.TrimSpace(string(dat)))
} }

View File

@@ -11,7 +11,7 @@ import (
) )
// Encoding represents an OpenAPI 3+ Encoding object // Encoding represents an OpenAPI 3+ Encoding object
// - https://spec.openapis.org/oas/v3.1.0#encoding-object // - https://spec.openapis.org/oas/v3.1.0#encoding-object
type Encoding struct { type Encoding struct {
ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"` ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"`
Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"`

View File

@@ -4,64 +4,64 @@
package v3 package v3
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"strings" "strings"
"testing" "testing"
) )
func TestEncoding_MarshalYAML(t *testing.T) { func TestEncoding_MarshalYAML(t *testing.T) {
explode := true explode := true
encoding := &Encoding{ encoding := &Encoding{
ContentType: "application/json", ContentType: "application/json",
Headers: map[string]*Header{"x-pizza-time": {Description: "oh yes please"}}, Headers: map[string]*Header{"x-pizza-time": {Description: "oh yes please"}},
Style: "simple", Style: "simple",
Explode: &explode, Explode: &explode,
} }
rend, _ := encoding.Render() rend, _ := encoding.Render()
desired := `contentType: application/json desired := `contentType: application/json
headers: headers:
x-pizza-time: x-pizza-time:
description: oh yes please description: oh yes please
style: simple style: simple
explode: true` explode: true`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
explode = false explode = false
encoding.Explode = &explode encoding.Explode = &explode
rend, _ = encoding.Render() rend, _ = encoding.Render()
desired = `contentType: application/json desired = `contentType: application/json
headers: headers:
x-pizza-time: x-pizza-time:
description: oh yes please description: oh yes please
style: simple` style: simple`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
encoding.Explode = nil encoding.Explode = nil
rend, _ = encoding.Render() rend, _ = encoding.Render()
desired = `contentType: application/json desired = `contentType: application/json
headers: headers:
x-pizza-time: x-pizza-time:
description: oh yes please description: oh yes please
style: simple` style: simple`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
encoding.Explode = &explode encoding.Explode = &explode
rend, _ = encoding.Render() rend, _ = encoding.Render()
desired = `contentType: application/json desired = `contentType: application/json
headers: headers:
x-pizza-time: x-pizza-time:
description: oh yes please description: oh yes please
style: simple` style: simple`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }

View File

@@ -13,7 +13,7 @@ import (
) )
// Header represents a high-level OpenAPI 3+ Header object that is backed by a low-level one. // Header represents a high-level OpenAPI 3+ Header object that is backed by a low-level one.
// - https://spec.openapis.org/oas/v3.1.0#header-object // - https://spec.openapis.org/oas/v3.1.0#header-object
type Header struct { type Header struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"` Required bool `json:"required,omitempty" yaml:"required,omitempty"`

View File

@@ -4,30 +4,30 @@
package v3 package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"strings" "strings"
"testing" "testing"
) )
func TestHeader_MarshalYAML(t *testing.T) { func TestHeader_MarshalYAML(t *testing.T) {
header := &Header{ header := &Header{
Description: "A header", Description: "A header",
Required: true, Required: true,
Deprecated: true, Deprecated: true,
AllowEmptyValue: true, AllowEmptyValue: true,
Style: "simple", Style: "simple",
Explode: true, Explode: true,
AllowReserved: true, AllowReserved: true,
Example: "example", Example: "example",
Examples: map[string]*base.Example{"example": {Value: "example"}}, Examples: map[string]*base.Example{"example": {Value: "example"}},
Extensions: map[string]interface{}{"x-burgers": "why not?"}, Extensions: map[string]interface{}{"x-burgers": "why not?"},
} }
rend, _ := header.Render() rend, _ := header.Render()
desired := `description: A header desired := `description: A header
required: true required: true
deprecated: true deprecated: true
allowEmptyValue: true allowEmptyValue: true
@@ -40,6 +40,6 @@ examples:
value: example value: example
x-burgers: why not?` x-burgers: why not?`
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }

View File

@@ -4,27 +4,27 @@
package v3 package v3
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"strings" "strings"
"testing" "testing"
) )
func TestLink_MarshalYAML(t *testing.T) { func TestLink_MarshalYAML(t *testing.T) {
link := Link{ link := Link{
OperationRef: "somewhere", OperationRef: "somewhere",
OperationId: "somewhereOutThere", OperationId: "somewhereOutThere",
Parameters: map[string]string{ Parameters: map[string]string{
"over": "theRainbow", "over": "theRainbow",
}, },
RequestBody: "hello?", RequestBody: "hello?",
Description: "are you there?", Description: "are you there?",
Server: &Server{ Server: &Server{
URL: "https://pb33f.io", URL: "https://pb33f.io",
}, },
} }
dat, _ := link.Render() dat, _ := link.Render()
desired := `operationRef: somewhere desired := `operationRef: somewhere
operationId: somewhereOutThere operationId: somewhereOutThere
parameters: parameters:
over: theRainbow over: theRainbow
@@ -33,5 +33,5 @@ description: are you there?
server: server:
url: https://pb33f.io` url: https://pb33f.io`
assert.Equal(t, desired, strings.TrimSpace(string(dat))) assert.Equal(t, desired, strings.TrimSpace(string(dat)))
} }

View File

@@ -10,7 +10,7 @@ import (
) )
// OAuthFlow represents a high-level OpenAPI 3+ OAuthFlow object that is backed by a low-level one. // OAuthFlow represents a high-level OpenAPI 3+ OAuthFlow object that is backed by a low-level one.
// - https://spec.openapis.org/oas/v3.1.0#oauth-flow-object // - https://spec.openapis.org/oas/v3.1.0#oauth-flow-object
type OAuthFlow struct { type OAuthFlow struct {
AuthorizationUrl string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"` AuthorizationUrl string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenUrl string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"` TokenUrl string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`

View File

@@ -4,42 +4,42 @@
package v3 package v3
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"strings" "strings"
"testing" "testing"
) )
func TestOAuthFlow_MarshalYAML(t *testing.T) { func TestOAuthFlow_MarshalYAML(t *testing.T) {
oflow := &OAuthFlow{ oflow := &OAuthFlow{
AuthorizationUrl: "https://pb33f.io", AuthorizationUrl: "https://pb33f.io",
TokenUrl: "https://pb33f.io/token", TokenUrl: "https://pb33f.io/token",
RefreshUrl: "https://pb33f.io/refresh", RefreshUrl: "https://pb33f.io/refresh",
Scopes: map[string]string{"chicken": "nuggets", "beefy": "soup"}, Scopes: map[string]string{"chicken": "nuggets", "beefy": "soup"},
} }
rend, _ := oflow.Render() rend, _ := oflow.Render()
desired := `authorizationUrl: https://pb33f.io desired := `authorizationUrl: https://pb33f.io
tokenUrl: https://pb33f.io/token tokenUrl: https://pb33f.io/token
refreshUrl: https://pb33f.io/refresh refreshUrl: https://pb33f.io/refresh
scopes: scopes:
chicken: nuggets chicken: nuggets
beefy: soup` beefy: soup`
// we can't check for equality, as the scopes map will be randomly ordered when created from scratch. // we can't check for equality, as the scopes map will be randomly ordered when created from scratch.
assert.Len(t, desired, 149) assert.Len(t, desired, 149)
// mutate // mutate
oflow.Scopes = nil oflow.Scopes = nil
oflow.Extensions = map[string]interface{}{"x-burgers": "why not?"} oflow.Extensions = map[string]interface{}{"x-burgers": "why not?"}
desired = `authorizationUrl: https://pb33f.io desired = `authorizationUrl: https://pb33f.io
tokenUrl: https://pb33f.io/token tokenUrl: https://pb33f.io/token
refreshUrl: https://pb33f.io/refresh refreshUrl: https://pb33f.io/refresh
x-burgers: why not?` x-burgers: why not?`
rend, _ = oflow.Render() rend, _ = oflow.Render()
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }

View File

@@ -10,7 +10,7 @@ import (
) )
// OAuthFlows represents a high-level OpenAPI 3+ OAuthFlows object that is backed by a low-level one. // OAuthFlows represents a high-level OpenAPI 3+ OAuthFlows object that is backed by a low-level one.
// - https://spec.openapis.org/oas/v3.1.0#oauth-flows-object // - https://spec.openapis.org/oas/v3.1.0#oauth-flows-object
type OAuthFlows struct { type OAuthFlows struct {
Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"` Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"`
Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"` Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
@@ -63,4 +63,3 @@ func (o *OAuthFlows) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(o, o.low) nb := high.NewNodeBuilder(o, o.low)
return nb.Render(), nil return nb.Render(), nil
} }

View File

@@ -65,7 +65,7 @@ func TestOperation_MarshalYAML(t *testing.T) {
}, },
OperationId: "slice", OperationId: "slice",
Parameters: []*Parameter{ Parameters: []*Parameter{
&Parameter{ {
Name: "mice", Name: "mice",
}, },
}, },
@@ -103,7 +103,7 @@ func TestOperation_MarshalYAMLInline(t *testing.T) {
}, },
OperationId: "slice", OperationId: "slice",
Parameters: []*Parameter{ Parameters: []*Parameter{
&Parameter{ {
Name: "mice", Name: "mice",
}, },
}, },

View File

@@ -18,7 +18,7 @@ import (
// authorization code) as defined in RFC6749 (https://www.rfc-editor.org/rfc/rfc6749), and OpenID Connect Discovery. // authorization code) as defined in RFC6749 (https://www.rfc-editor.org/rfc/rfc6749), and OpenID Connect Discovery.
// Please note that as of 2020, the implicit flow is about to be deprecated by OAuth 2.0 Security Best Current Practice. // Please note that as of 2020, the implicit flow is about to be deprecated by OAuth 2.0 Security Best Current Practice.
// Recommended for most use case is Authorization Code Grant flow with PKCE. // Recommended for most use case is Authorization Code Grant flow with PKCE.
// - https://spec.openapis.org/oas/v3.1.0#security-scheme-object // - https://spec.openapis.org/oas/v3.1.0#security-scheme-object
type SecurityScheme struct { type SecurityScheme struct {
Type string `json:"type,omitempty" yaml:"type,omitempty"` Type string `json:"type,omitempty" yaml:"type,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`

View File

@@ -4,44 +4,44 @@
package v3 package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strings" "strings"
"testing" "testing"
) )
func TestSecurityScheme_MarshalYAML(t *testing.T) { func TestSecurityScheme_MarshalYAML(t *testing.T) {
ss := &SecurityScheme{ ss := &SecurityScheme{
Type: "apiKey", Type: "apiKey",
Description: "this is a description", Description: "this is a description",
Name: "superSecret", Name: "superSecret",
In: "header", In: "header",
Scheme: "https", Scheme: "https",
} }
dat, _ := ss.Render() dat, _ := ss.Render()
var idxNode yaml.Node var idxNode yaml.Node
_ = yaml.Unmarshal(dat, &idxNode) _ = yaml.Unmarshal(dat, &idxNode)
idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig()) idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
var n v3.SecurityScheme var n v3.SecurityScheme
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx) _ = n.Build(idxNode.Content[0], idx)
r := NewSecurityScheme(&n) r := NewSecurityScheme(&n)
dat, _ = r.Render() dat, _ = r.Render()
desired := `type: apiKey desired := `type: apiKey
description: this is a description description: this is a description
name: superSecret name: superSecret
in: header in: header
scheme: https` scheme: https`
assert.Equal(t, desired, strings.TrimSpace(string(dat))) assert.Equal(t, desired, strings.TrimSpace(string(dat)))
} }

View File

@@ -4,32 +4,32 @@
package v3 package v3
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"strings" "strings"
"testing" "testing"
) )
func TestServer_MarshalYAML(t *testing.T) { func TestServer_MarshalYAML(t *testing.T) {
server := &Server{ server := &Server{
URL: "https://pb33f.io", URL: "https://pb33f.io",
Description: "the b33f", Description: "the b33f",
} }
desired := `url: https://pb33f.io desired := `url: https://pb33f.io
description: the b33f` description: the b33f`
rend, _ := server.Render() rend, _ := server.Render()
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
// mutate // mutate
server.Variables = map[string]*ServerVariable{ server.Variables = map[string]*ServerVariable{
"rainbow": { "rainbow": {
Enum: []string{"one", "two", "three"}, Enum: []string{"one", "two", "three"},
}, },
} }
desired = `url: https://pb33f.io desired = `url: https://pb33f.io
description: the b33f description: the b33f
variables: variables:
rainbow: rainbow:
@@ -38,6 +38,6 @@ variables:
- two - two
- three` - three`
rend, _ = server.Render() rend, _ = server.Render()
assert.Equal(t, desired, strings.TrimSpace(string(rend))) assert.Equal(t, desired, strings.TrimSpace(string(rend)))
} }

View File

@@ -4,40 +4,40 @@
package v3 package v3
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"strings" "strings"
"testing" "testing"
) )
func TestServerVariable_MarshalYAML(t *testing.T) { func TestServerVariable_MarshalYAML(t *testing.T) {
svar := &ServerVariable{ svar := &ServerVariable{
Enum: []string{"one", "two", "three"}, Enum: []string{"one", "two", "three"},
Description: "money day", Description: "money day",
} }
desired := `enum: desired := `enum:
- one - one
- two - two
- three - three
description: money day` description: money day`
svarRend, _ := svar.Render() svarRend, _ := svar.Render()
assert.Equal(t, desired, strings.TrimSpace(string(svarRend))) assert.Equal(t, desired, strings.TrimSpace(string(svarRend)))
// mutate // mutate
svar.Default = "is moments away" svar.Default = "is moments away"
desired = `enum: desired = `enum:
- one - one
- two - two
- three - three
default: is moments away default: is moments away
description: money day` description: money day`
svarRend, _ = svar.Render() svarRend, _ = svar.Render()
assert.Equal(t, desired, strings.TrimSpace(string(svarRend))) assert.Equal(t, desired, strings.TrimSpace(string(svarRend)))
} }

View File

@@ -9,4 +9,3 @@
// beats, particularly when polymorphism is used. By re-using the same superset Schema across versions, we can ensure // beats, particularly when polymorphism is used. By re-using the same superset Schema across versions, we can ensure
// that all the latest features are collected, without damaging backwards compatibility. // that all the latest features are collected, without damaging backwards compatibility.
package base package base

View File

@@ -12,8 +12,9 @@ import (
) )
// Contact represents a low-level representation of the Contact definitions found at // Contact represents a low-level representation of the Contact definitions found at
// v2 - https://swagger.io/specification/v2/#contactObject //
// v3 - https://spec.openapis.org/oas/v3.1.0#contact-object // v2 - https://swagger.io/specification/v2/#contactObject
// v3 - https://spec.openapis.org/oas/v3.1.0#contact-object
type Contact struct { type Contact struct {
Name low.NodeReference[string] Name low.NodeReference[string]
URL low.NodeReference[string] URL low.NodeReference[string]
@@ -42,4 +43,3 @@ func (c *Contact) Hash() [32]byte {
} }
return sha256.Sum256([]byte(strings.Join(f, "|"))) return sha256.Sum256([]byte(strings.Join(f, "|")))
} }

View File

@@ -17,7 +17,8 @@ import (
// which is used to inform the consumer of the document of an alternative schema based on the value associated with it. // which is used to inform the consumer of the document of an alternative schema based on the value associated with it.
// //
// When using the discriminator, inline schemas will not be considered. // When using the discriminator, inline schemas will not be considered.
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object //
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object
type Discriminator struct { type Discriminator struct {
PropertyName low.NodeReference[string] PropertyName low.NodeReference[string]
Mapping low.NodeReference[map[low.KeyReference[string]]low.ValueReference[string]] Mapping low.NodeReference[map[low.KeyReference[string]]low.ValueReference[string]]

View File

@@ -16,7 +16,8 @@ import (
) )
// Example represents a low-level Example object as defined by OpenAPI 3+ // Example represents a low-level Example object as defined by OpenAPI 3+
// v3 - https://spec.openapis.org/oas/v3.1.0#example-object //
// v3 - https://spec.openapis.org/oas/v3.1.0#example-object
type Example struct { type Example struct {
Summary low.NodeReference[string] Summary low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]

View File

@@ -16,8 +16,9 @@ import (
// ExternalDoc represents a low-level External Documentation object as defined by OpenAPI 2 and 3 // ExternalDoc represents a low-level External Documentation object as defined by OpenAPI 2 and 3
// //
// Allows referencing an external resource for extended documentation. // Allows referencing an external resource for extended documentation.
// v2 - https://swagger.io/specification/v2/#externalDocumentationObject //
// v3 - https://spec.openapis.org/oas/v3.1.0#external-documentation-object // v2 - https://swagger.io/specification/v2/#externalDocumentationObject
// v3 - https://spec.openapis.org/oas/v3.1.0#external-documentation-object
type ExternalDoc struct { type ExternalDoc struct {
Description low.NodeReference[string] Description low.NodeReference[string]
URL low.NodeReference[string] URL low.NodeReference[string]

View File

@@ -12,8 +12,9 @@ import (
) )
// License is a low-level representation of a License object as defined by OpenAPI 2 and OpenAPI 3 // License is a low-level representation of a License object as defined by OpenAPI 2 and OpenAPI 3
// v2 - https://swagger.io/specification/v2/#licenseObject //
// v3 - https://spec.openapis.org/oas/v3.1.0#license-object // v2 - https://swagger.io/specification/v2/#licenseObject
// v3 - https://spec.openapis.org/oas/v3.1.0#license-object
type License struct { type License struct {
Name low.NodeReference[string] Name low.NodeReference[string]
URL low.NodeReference[string] URL low.NodeReference[string]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -19,8 +19,8 @@ import (
// security schemes declared in it which are all required (that is, there is a logical AND between the schemes). // security schemes declared in it which are all required (that is, there is a logical AND between the schemes).
// //
// The name used for each property MUST correspond to a security scheme declared in the Security Definitions // The name used for each property MUST correspond to a security scheme declared in the Security Definitions
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
// - https://swagger.io/specification/#security-requirement-object // - https://swagger.io/specification/#security-requirement-object
type SecurityRequirement struct { type SecurityRequirement struct {
Requirements low.ValueReference[map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]] Requirements low.ValueReference[map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]]
*low.Reference *low.Reference

View File

@@ -17,8 +17,8 @@ import (
// //
// Adds metadata to a single tag that is used by the Operation Object. It is not mandatory to have a Tag Object per // Adds metadata to a single tag that is used by the Operation Object. It is not mandatory to have a Tag Object per
// tag defined in the Operation Object instances. // tag defined in the Operation Object instances.
// - v2: https://swagger.io/specification/v2/#tagObject // - v2: https://swagger.io/specification/v2/#tagObject
// - v3: https://swagger.io/specification/#tag-object // - v3: https://swagger.io/specification/#tag-object
type Tag struct { type Tag struct {
Name low.NodeReference[string] Name low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]

View File

@@ -84,4 +84,4 @@ x-b33f: princess`
assert.Equal(t, lDoc.Hash(), rDoc.Hash()) assert.Equal(t, lDoc.Hash(), rDoc.Hash())
} }

View File

@@ -16,8 +16,9 @@ import (
// //
// When using arrays, XML element names are not inferred (for singular/plural forms) and the name property SHOULD be // When using arrays, XML element names are not inferred (for singular/plural forms) and the name property SHOULD be
// used to add that information. See examples for expected behavior. // used to add that information. See examples for expected behavior.
// v2 - https://swagger.io/specification/v2/#xmlObject //
// v3 - https://swagger.io/specification/#xml-object // v2 - https://swagger.io/specification/v2/#xmlObject
// v3 - https://swagger.io/specification/#xml-object
type XML struct { type XML struct {
Name low.NodeReference[string] Name low.NodeReference[string]
Namespace low.NodeReference[string] Namespace low.NodeReference[string]

View File

@@ -1024,7 +1024,7 @@ one:
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)
for k, _ := range things { for k := range things {
assert.Equal(t, "one", k.Value) assert.Equal(t, "one", k.Value)
} }
} }

View File

@@ -4,14 +4,14 @@
package low package low
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// BuildModel accepts a yaml.Node pointer and a model, which can be any struct. Using reflection, the model is // BuildModel accepts a yaml.Node pointer and a model, which can be any struct. Using reflection, the model is
@@ -20,198 +20,198 @@ import (
// //
// BuildModel is non-recursive and will only build out a single layer of the node tree. // BuildModel is non-recursive and will only build out a single layer of the node tree.
func BuildModel(node *yaml.Node, model interface{}) error { func BuildModel(node *yaml.Node, model interface{}) error {
if node == nil { if node == nil {
return nil return nil
} }
if reflect.ValueOf(model).Type().Kind() != reflect.Pointer { if reflect.ValueOf(model).Type().Kind() != reflect.Pointer {
return fmt.Errorf("cannot build model on non-pointer: %v", reflect.ValueOf(model).Type().Kind()) return fmt.Errorf("cannot build model on non-pointer: %v", reflect.ValueOf(model).Type().Kind())
} }
v := reflect.ValueOf(model).Elem() v := reflect.ValueOf(model).Elem()
num := v.NumField() num := v.NumField()
for i := 0; i < num; i++ { for i := 0; i < num; i++ {
fName := v.Type().Field(i).Name fName := v.Type().Field(i).Name
if fName == "Extensions" { if fName == "Extensions" {
continue // internal construct continue // internal construct
} }
if fName == "PathItems" { if fName == "PathItems" {
continue // internal construct continue // internal construct
} }
kn, vn := utils.FindKeyNodeTop(strings.ToLower(fName), node.Content) kn, vn := utils.FindKeyNodeTop(strings.ToLower(fName), node.Content)
if vn == nil { if vn == nil {
// no point in going on. // no point in going on.
continue continue
} }
field := v.FieldByName(fName) field := v.FieldByName(fName)
kind := field.Kind() kind := field.Kind()
switch kind { switch kind {
case reflect.Struct, reflect.Slice, reflect.Map, reflect.Pointer: case reflect.Struct, reflect.Slice, reflect.Map, reflect.Pointer:
err := SetField(&field, vn, kn) err := SetField(&field, vn, kn)
if err != nil { if err != nil {
return err return err
} }
default: default:
return fmt.Errorf("unable to parse unsupported type: %v", kind) return fmt.Errorf("unable to parse unsupported type: %v", kind)
} }
} }
return nil return nil
} }
// SetField accepts a field reflection value, a yaml.Node valueNode and a yaml.Node keyNode. Using reflection, the // SetField accepts a field reflection value, a yaml.Node valueNode and a yaml.Node keyNode. Using reflection, the
// function will attempt to set the value of the field based on the key and value nodes. This method is only useful // function will attempt to set the value of the field based on the key and value nodes. This method is only useful
// for low-level models, it has no value to high-level ones. // for low-level models, it has no value to high-level ones.
func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) error { func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) error {
if valueNode == nil { if valueNode == nil {
return nil return nil
} }
switch field.Type() { switch field.Type() {
case reflect.TypeOf(map[string]NodeReference[any]{}): case reflect.TypeOf(map[string]NodeReference[any]{}):
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
if field.CanSet() { if field.CanSet() {
items := make(map[string]NodeReference[any]) items := make(map[string]NodeReference[any])
var currentLabel string var currentLabel string
for i, sliceItem := range valueNode.Content { for i, sliceItem := range valueNode.Content {
if i%2 == 0 { if i%2 == 0 {
currentLabel = sliceItem.Value currentLabel = sliceItem.Value
continue continue
} }
var decoded map[string]interface{} var decoded map[string]interface{}
// I cannot think of a way to make this error out by this point. // I cannot think of a way to make this error out by this point.
_ = sliceItem.Decode(&decoded) _ = sliceItem.Decode(&decoded)
items[currentLabel] = NodeReference[any]{ items[currentLabel] = NodeReference[any]{
Value: decoded, Value: decoded,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
} }
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
case reflect.TypeOf(map[string]NodeReference[string]{}): case reflect.TypeOf(map[string]NodeReference[string]{}):
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
if field.CanSet() { if field.CanSet() {
items := make(map[string]NodeReference[string]) items := make(map[string]NodeReference[string])
var currentLabel string var currentLabel string
for i, sliceItem := range valueNode.Content { for i, sliceItem := range valueNode.Content {
if i%2 == 0 { if i%2 == 0 {
currentLabel = sliceItem.Value currentLabel = sliceItem.Value
continue continue
} }
items[currentLabel] = NodeReference[string]{ items[currentLabel] = NodeReference[string]{
Value: fmt.Sprintf("%v", sliceItem.Value), Value: fmt.Sprintf("%v", sliceItem.Value),
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
} }
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
case reflect.TypeOf(NodeReference[any]{}): case reflect.TypeOf(NodeReference[any]{}):
var decoded interface{} var decoded interface{}
_ = valueNode.Decode(&decoded) _ = valueNode.Decode(&decoded)
if field.CanSet() { if field.CanSet() {
or := NodeReference[any]{Value: decoded, ValueNode: valueNode, KeyNode: keyNode} or := NodeReference[any]{Value: decoded, ValueNode: valueNode, KeyNode: keyNode}
field.Set(reflect.ValueOf(or)) field.Set(reflect.ValueOf(or))
} }
case reflect.TypeOf([]NodeReference[any]{}): case reflect.TypeOf([]NodeReference[any]{}):
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []NodeReference[any] var items []NodeReference[any]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
var decoded map[string]interface{} var decoded map[string]interface{}
err := sliceItem.Decode(&decoded) err := sliceItem.Decode(&decoded)
if err != nil { if err != nil {
return err return err
} }
items = append(items, NodeReference[any]{ items = append(items, NodeReference[any]{
Value: decoded, Value: decoded,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
case reflect.TypeOf(NodeReference[string]{}): case reflect.TypeOf(NodeReference[string]{}):
if field.CanSet() { if field.CanSet() {
nr := NodeReference[string]{ nr := NodeReference[string]{
Value: fmt.Sprintf("%v", valueNode.Value), Value: fmt.Sprintf("%v", valueNode.Value),
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
case reflect.TypeOf(ValueReference[string]{}): case reflect.TypeOf(ValueReference[string]{}):
if field.CanSet() { if field.CanSet() {
nr := ValueReference[string]{ nr := ValueReference[string]{
Value: fmt.Sprintf("%v", valueNode.Value), Value: fmt.Sprintf("%v", valueNode.Value),
ValueNode: valueNode, ValueNode: valueNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
case reflect.TypeOf(NodeReference[bool]{}): case reflect.TypeOf(NodeReference[bool]{}):
if utils.IsNodeBoolValue(valueNode) { if utils.IsNodeBoolValue(valueNode) {
if field.CanSet() { if field.CanSet() {
bv, _ := strconv.ParseBool(valueNode.Value) bv, _ := strconv.ParseBool(valueNode.Value)
nr := NodeReference[bool]{ nr := NodeReference[bool]{
Value: bv, Value: bv,
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
} }
case reflect.TypeOf(NodeReference[int]{}): case reflect.TypeOf(NodeReference[int]{}):
if utils.IsNodeIntValue(valueNode) { if utils.IsNodeIntValue(valueNode) {
if field.CanSet() { if field.CanSet() {
fv, _ := strconv.Atoi(valueNode.Value) fv, _ := strconv.Atoi(valueNode.Value)
nr := NodeReference[int]{ nr := NodeReference[int]{
Value: fv, Value: fv,
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
} }
case reflect.TypeOf(NodeReference[int64]{}): case reflect.TypeOf(NodeReference[int64]{}):
if utils.IsNodeIntValue(valueNode) || utils.IsNodeFloatValue(valueNode) { if utils.IsNodeIntValue(valueNode) || utils.IsNodeFloatValue(valueNode) {
if field.CanSet() { if field.CanSet() {
fv, _ := strconv.ParseInt(valueNode.Value, 10, 64) fv, _ := strconv.ParseInt(valueNode.Value, 10, 64)
nr := NodeReference[int64]{ nr := NodeReference[int64]{
Value: fv, Value: fv,
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
} }
case reflect.TypeOf(NodeReference[float32]{}): case reflect.TypeOf(NodeReference[float32]{}):
if utils.IsNodeNumberValue(valueNode) { if utils.IsNodeNumberValue(valueNode) {
if field.CanSet() { if field.CanSet() {
@@ -225,7 +225,7 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er
} }
} }
case reflect.TypeOf(NodeReference[float64]{}): case reflect.TypeOf(NodeReference[float64]{}):
if utils.IsNodeNumberValue(valueNode) { if utils.IsNodeNumberValue(valueNode) {
if field.CanSet() { if field.CanSet() {
@@ -239,249 +239,249 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er
} }
} }
case reflect.TypeOf([]NodeReference[string]{}): case reflect.TypeOf([]NodeReference[string]{}):
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []NodeReference[string] var items []NodeReference[string]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
items = append(items, NodeReference[string]{ items = append(items, NodeReference[string]{
Value: sliceItem.Value, Value: sliceItem.Value,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
case reflect.TypeOf([]NodeReference[float32]{}): case reflect.TypeOf([]NodeReference[float32]{}):
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []NodeReference[float32] var items []NodeReference[float32]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
fv, _ := strconv.ParseFloat(sliceItem.Value, 32) fv, _ := strconv.ParseFloat(sliceItem.Value, 32)
items = append(items, NodeReference[float32]{ items = append(items, NodeReference[float32]{
Value: float32(fv), Value: float32(fv),
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
case reflect.TypeOf([]NodeReference[float64]{}): case reflect.TypeOf([]NodeReference[float64]{}):
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []NodeReference[float64] var items []NodeReference[float64]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
fv, _ := strconv.ParseFloat(sliceItem.Value, 64) fv, _ := strconv.ParseFloat(sliceItem.Value, 64)
items = append(items, NodeReference[float64]{Value: fv, ValueNode: sliceItem}) items = append(items, NodeReference[float64]{Value: fv, ValueNode: sliceItem})
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
case reflect.TypeOf([]NodeReference[int]{}): case reflect.TypeOf([]NodeReference[int]{}):
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []NodeReference[int] var items []NodeReference[int]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
iv, _ := strconv.Atoi(sliceItem.Value) iv, _ := strconv.Atoi(sliceItem.Value)
items = append(items, NodeReference[int]{ items = append(items, NodeReference[int]{
Value: iv, Value: iv,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
case reflect.TypeOf([]NodeReference[int64]{}): case reflect.TypeOf([]NodeReference[int64]{}):
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []NodeReference[int64] var items []NodeReference[int64]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
iv, _ := strconv.ParseInt(sliceItem.Value, 10, 64) iv, _ := strconv.ParseInt(sliceItem.Value, 10, 64)
items = append(items, NodeReference[int64]{ items = append(items, NodeReference[int64]{
Value: iv, Value: iv,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
case reflect.TypeOf([]NodeReference[bool]{}): case reflect.TypeOf([]NodeReference[bool]{}):
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []NodeReference[bool] var items []NodeReference[bool]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
bv, _ := strconv.ParseBool(sliceItem.Value) bv, _ := strconv.ParseBool(sliceItem.Value)
items = append(items, NodeReference[bool]{ items = append(items, NodeReference[bool]{
Value: bv, Value: bv,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
// helper for unpacking string maps. // helper for unpacking string maps.
case reflect.TypeOf(map[KeyReference[string]]ValueReference[string]{}): case reflect.TypeOf(map[KeyReference[string]]ValueReference[string]{}):
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
if field.CanSet() { if field.CanSet() {
items := make(map[KeyReference[string]]ValueReference[string]) items := make(map[KeyReference[string]]ValueReference[string])
var cf *yaml.Node var cf *yaml.Node
for i, sliceItem := range valueNode.Content { for i, sliceItem := range valueNode.Content {
if i%2 == 0 { if i%2 == 0 {
cf = sliceItem cf = sliceItem
continue continue
} }
items[KeyReference[string]{ items[KeyReference[string]{
Value: cf.Value, Value: cf.Value,
KeyNode: cf, KeyNode: cf,
}] = ValueReference[string]{ }] = ValueReference[string]{
Value: sliceItem.Value, Value: sliceItem.Value,
ValueNode: sliceItem, ValueNode: sliceItem,
} }
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
case reflect.TypeOf(KeyReference[map[KeyReference[string]]ValueReference[string]]{}): case reflect.TypeOf(KeyReference[map[KeyReference[string]]ValueReference[string]]{}):
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
if field.CanSet() { if field.CanSet() {
items := make(map[KeyReference[string]]ValueReference[string]) items := make(map[KeyReference[string]]ValueReference[string])
var cf *yaml.Node var cf *yaml.Node
for i, sliceItem := range valueNode.Content { for i, sliceItem := range valueNode.Content {
if i%2 == 0 { if i%2 == 0 {
cf = sliceItem cf = sliceItem
continue continue
} }
items[KeyReference[string]{ items[KeyReference[string]{
Value: cf.Value, Value: cf.Value,
KeyNode: cf, KeyNode: cf,
}] = ValueReference[string]{ }] = ValueReference[string]{
Value: sliceItem.Value, Value: sliceItem.Value,
ValueNode: sliceItem, ValueNode: sliceItem,
} }
} }
ref := KeyReference[map[KeyReference[string]]ValueReference[string]]{ ref := KeyReference[map[KeyReference[string]]ValueReference[string]]{
Value: items, Value: items,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(ref)) field.Set(reflect.ValueOf(ref))
} }
} }
case reflect.TypeOf(NodeReference[map[KeyReference[string]]ValueReference[string]]{}): case reflect.TypeOf(NodeReference[map[KeyReference[string]]ValueReference[string]]{}):
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
if field.CanSet() { if field.CanSet() {
items := make(map[KeyReference[string]]ValueReference[string]) items := make(map[KeyReference[string]]ValueReference[string])
var cf *yaml.Node var cf *yaml.Node
for i, sliceItem := range valueNode.Content { for i, sliceItem := range valueNode.Content {
if i%2 == 0 { if i%2 == 0 {
cf = sliceItem cf = sliceItem
continue continue
} }
items[KeyReference[string]{ items[KeyReference[string]{
Value: cf.Value, Value: cf.Value,
KeyNode: cf, KeyNode: cf,
}] = ValueReference[string]{ }] = ValueReference[string]{
Value: sliceItem.Value, Value: sliceItem.Value,
ValueNode: sliceItem, ValueNode: sliceItem,
} }
} }
ref := NodeReference[map[KeyReference[string]]ValueReference[string]]{ ref := NodeReference[map[KeyReference[string]]ValueReference[string]]{
Value: items, Value: items,
KeyNode: keyNode, KeyNode: keyNode,
ValueNode: valueNode, ValueNode: valueNode,
} }
field.Set(reflect.ValueOf(ref)) field.Set(reflect.ValueOf(ref))
} }
} }
case reflect.TypeOf(NodeReference[[]ValueReference[string]]{}): case reflect.TypeOf(NodeReference[[]ValueReference[string]]{}):
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []ValueReference[string] var items []ValueReference[string]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
items = append(items, ValueReference[string]{ items = append(items, ValueReference[string]{
Value: sliceItem.Value, Value: sliceItem.Value,
ValueNode: sliceItem, ValueNode: sliceItem,
}) })
} }
n := NodeReference[[]ValueReference[string]]{ n := NodeReference[[]ValueReference[string]]{
Value: items, Value: items,
KeyNode: keyNode, KeyNode: keyNode,
ValueNode: valueNode, ValueNode: valueNode,
} }
field.Set(reflect.ValueOf(n)) field.Set(reflect.ValueOf(n))
} }
} }
case reflect.TypeOf(NodeReference[[]ValueReference[any]]{}): case reflect.TypeOf(NodeReference[[]ValueReference[any]]{}):
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []ValueReference[any] var items []ValueReference[any]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
var val any var val any
if utils.IsNodeIntValue(sliceItem) || utils.IsNodeFloatValue(sliceItem) { if utils.IsNodeIntValue(sliceItem) || utils.IsNodeFloatValue(sliceItem) {
if utils.IsNodeIntValue(sliceItem) { if utils.IsNodeIntValue(sliceItem) {
val, _ = strconv.ParseInt(sliceItem.Value, 10, 64) val, _ = strconv.ParseInt(sliceItem.Value, 10, 64)
} else { } else {
val, _ = strconv.ParseFloat(sliceItem.Value, 64) val, _ = strconv.ParseFloat(sliceItem.Value, 64)
} }
} }
if utils.IsNodeBoolValue(sliceItem) { if utils.IsNodeBoolValue(sliceItem) {
val, _ = strconv.ParseBool(sliceItem.Value) val, _ = strconv.ParseBool(sliceItem.Value)
} }
if utils.IsNodeStringValue(sliceItem) { if utils.IsNodeStringValue(sliceItem) {
val = sliceItem.Value val = sliceItem.Value
} }
items = append(items, ValueReference[any]{ items = append(items, ValueReference[any]{
Value: val, Value: val,
ValueNode: sliceItem, ValueNode: sliceItem,
}) })
} }
n := NodeReference[[]ValueReference[any]]{ n := NodeReference[[]ValueReference[any]]{
Value: items, Value: items,
KeyNode: keyNode, KeyNode: keyNode,
ValueNode: valueNode, ValueNode: valueNode,
} }
field.Set(reflect.ValueOf(n)) field.Set(reflect.ValueOf(n))
} }
} }
default: default:
// we want to ignore everything else, each model handles its own complex types. // we want to ignore everything else, each model handles its own complex types.
break break
} }
return nil return nil
} }
// BuildModelAsync is a convenience function for calling BuildModel from a goroutine, requires a sync.WaitGroup // BuildModelAsync is a convenience function for calling BuildModel from a goroutine, requires a sync.WaitGroup
func BuildModelAsync(n *yaml.Node, model interface{}, lwg *sync.WaitGroup, errors *[]error) { func BuildModelAsync(n *yaml.Node, model interface{}, lwg *sync.WaitGroup, errors *[]error) {
if n != nil { if n != nil {
err := BuildModel(n, model) err := BuildModel(n, model)
if err != nil { if err != nil {
*errors = append(*errors, err) *errors = append(*errors, err)
} }
} }
lwg.Done() lwg.Done()
} }

View File

@@ -1,53 +1,53 @@
package low package low
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sync" "sync"
"testing" "testing"
) )
type hotdog struct { type hotdog struct {
Name NodeReference[string] Name NodeReference[string]
ValueName ValueReference[string] ValueName ValueReference[string]
Fat NodeReference[int] Fat NodeReference[int]
Ketchup NodeReference[float32] Ketchup NodeReference[float32]
Mustard NodeReference[float64] Mustard NodeReference[float64]
Grilled NodeReference[bool] Grilled NodeReference[bool]
MaxTemp NodeReference[int] MaxTemp NodeReference[int]
MaxTempHigh NodeReference[int64] MaxTempHigh NodeReference[int64]
MaxTempAlt []NodeReference[int] MaxTempAlt []NodeReference[int]
Drinks []NodeReference[string] Drinks []NodeReference[string]
Sides []NodeReference[float32] Sides []NodeReference[float32]
BigSides []NodeReference[float64] BigSides []NodeReference[float64]
Temps []NodeReference[int] Temps []NodeReference[int]
HighTemps []NodeReference[int64] HighTemps []NodeReference[int64]
Buns []NodeReference[bool] Buns []NodeReference[bool]
UnknownElements NodeReference[any] UnknownElements NodeReference[any]
LotsOfUnknowns []NodeReference[any] LotsOfUnknowns []NodeReference[any]
Where map[string]NodeReference[any] Where map[string]NodeReference[any]
There map[string]NodeReference[string] There map[string]NodeReference[string]
AllTheThings NodeReference[map[KeyReference[string]]ValueReference[string]] AllTheThings NodeReference[map[KeyReference[string]]ValueReference[string]]
} }
func TestBuildModel_Mismatch(t *testing.T) { func TestBuildModel_Mismatch(t *testing.T) {
yml := `crisps: are tasty` yml := `crisps: are tasty`
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
hd := hotdog{} hd := hotdog{}
cErr := BuildModel(&rootNode, &hd) cErr := BuildModel(&rootNode, &hd)
assert.NoError(t, cErr) assert.NoError(t, cErr)
assert.Empty(t, hd.Name) assert.Empty(t, hd.Name)
} }
func TestBuildModel(t *testing.T) { func TestBuildModel(t *testing.T) {
yml := `name: yummy yml := `name: yummy
valueName: yammy valueName: yammy
beef: true beef: true
fat: 200 fat: 200
@@ -105,347 +105,347 @@ allTheThings:
beer: isGood beer: isGood
cake: isNice` cake: isNice`
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
hd := hotdog{} hd := hotdog{}
cErr := BuildModel(rootNode.Content[0], &hd) cErr := BuildModel(rootNode.Content[0], &hd)
assert.Equal(t, 200, hd.Fat.Value) assert.Equal(t, 200, hd.Fat.Value)
assert.Equal(t, 4, hd.Fat.ValueNode.Line) assert.Equal(t, 4, hd.Fat.ValueNode.Line)
assert.Equal(t, true, hd.Grilled.Value) assert.Equal(t, true, hd.Grilled.Value)
assert.Equal(t, "yummy", hd.Name.Value) assert.Equal(t, "yummy", hd.Name.Value)
assert.Equal(t, "yammy", hd.ValueName.Value) assert.Equal(t, "yammy", hd.ValueName.Value)
assert.Equal(t, float32(200.45), hd.Ketchup.Value) assert.Equal(t, float32(200.45), hd.Ketchup.Value)
assert.Len(t, hd.Drinks, 3) assert.Len(t, hd.Drinks, 3)
assert.Len(t, hd.Sides, 4) assert.Len(t, hd.Sides, 4)
assert.Len(t, hd.BigSides, 4) assert.Len(t, hd.BigSides, 4)
assert.Len(t, hd.Temps, 2) assert.Len(t, hd.Temps, 2)
assert.Len(t, hd.HighTemps, 2) assert.Len(t, hd.HighTemps, 2)
assert.Equal(t, int64(11732849090192923), hd.HighTemps[1].Value) assert.Equal(t, int64(11732849090192923), hd.HighTemps[1].Value)
assert.Len(t, hd.MaxTempAlt, 5) assert.Len(t, hd.MaxTempAlt, 5)
assert.Equal(t, int64(7392837462032342), hd.MaxTempHigh.Value) assert.Equal(t, int64(7392837462032342), hd.MaxTempHigh.Value)
assert.Equal(t, 2, hd.Temps[1].Value) assert.Equal(t, 2, hd.Temps[1].Value)
assert.Equal(t, 27, hd.Temps[1].ValueNode.Line) assert.Equal(t, 27, hd.Temps[1].ValueNode.Line)
assert.Len(t, hd.UnknownElements.Value, 2) assert.Len(t, hd.UnknownElements.Value, 2)
assert.Len(t, hd.LotsOfUnknowns, 3) assert.Len(t, hd.LotsOfUnknowns, 3)
assert.Len(t, hd.Where, 2) assert.Len(t, hd.Where, 2)
assert.Len(t, hd.There, 2) assert.Len(t, hd.There, 2)
assert.Equal(t, "bear", hd.There["care"].Value) assert.Equal(t, "bear", hd.There["care"].Value)
assert.Equal(t, 324938249028.98234892374892374923874823974, hd.Mustard.Value) assert.Equal(t, 324938249028.98234892374892374923874823974, hd.Mustard.Value)
allTheThings := hd.AllTheThings.Value allTheThings := hd.AllTheThings.Value
for i := range allTheThings { for i := range allTheThings {
if i.Value == "beer" { if i.Value == "beer" {
assert.Equal(t, "isGood", allTheThings[i].Value) assert.Equal(t, "isGood", allTheThings[i].Value)
} }
if i.Value == "cake" { if i.Value == "cake" {
assert.Equal(t, "isNice", allTheThings[i].Value) assert.Equal(t, "isNice", allTheThings[i].Value)
} }
} }
assert.NoError(t, cErr) assert.NoError(t, cErr)
} }
func TestBuildModel_UseCopyNotRef(t *testing.T) { func TestBuildModel_UseCopyNotRef(t *testing.T) {
yml := `cake: -99999` yml := `cake: -99999`
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
hd := hotdog{} hd := hotdog{}
cErr := BuildModel(&rootNode, hd) cErr := BuildModel(&rootNode, hd)
assert.Error(t, cErr) assert.Error(t, cErr)
assert.Empty(t, hd.Name) assert.Empty(t, hd.Name)
} }
func TestBuildModel_UseUnsupportedPrimitive(t *testing.T) { func TestBuildModel_UseUnsupportedPrimitive(t *testing.T) {
type notSupported struct { type notSupported struct {
cake string cake string
} }
ns := notSupported{} ns := notSupported{}
yml := `cake: party` yml := `cake: party`
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
cErr := BuildModel(rootNode.Content[0], &ns) cErr := BuildModel(rootNode.Content[0], &ns)
assert.Error(t, cErr) assert.Error(t, cErr)
assert.Empty(t, ns.cake) assert.Empty(t, ns.cake)
} }
func TestBuildModel_UsingInternalConstructs(t *testing.T) { func TestBuildModel_UsingInternalConstructs(t *testing.T) {
type internal struct { type internal struct {
Extensions NodeReference[string] Extensions NodeReference[string]
PathItems NodeReference[string] PathItems NodeReference[string]
Thing NodeReference[string] Thing NodeReference[string]
} }
yml := `extensions: one yml := `extensions: one
pathItems: two pathItems: two
thing: yeah` thing: yeah`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
// try a null build // try a null build
try := BuildModel(nil, ins) try := BuildModel(nil, ins)
assert.NoError(t, try) assert.NoError(t, try)
cErr := BuildModel(rootNode.Content[0], ins) cErr := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, cErr) assert.NoError(t, cErr)
assert.Empty(t, ins.PathItems.Value) assert.Empty(t, ins.PathItems.Value)
assert.Empty(t, ins.Extensions.Value) assert.Empty(t, ins.Extensions.Value)
assert.Equal(t, "yeah", ins.Thing.Value) assert.Equal(t, "yeah", ins.Thing.Value)
} }
func TestSetField_NodeRefAny_Error(t *testing.T) { func TestSetField_NodeRefAny_Error(t *testing.T) {
type internal struct { type internal struct {
Thing []NodeReference[any] Thing []NodeReference[any]
} }
yml := `thing: yml := `thing:
- 999 - 999
- false` - false`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
assert.Error(t, try) assert.Error(t, try)
} }
func TestSetField_MapHelperWrapped(t *testing.T) { func TestSetField_MapHelperWrapped(t *testing.T) {
type internal struct { type internal struct {
Thing KeyReference[map[KeyReference[string]]ValueReference[string]] Thing KeyReference[map[KeyReference[string]]ValueReference[string]]
} }
yml := `thing: yml := `thing:
what: not what: not
chip: chop chip: chop
lip: lop` lip: lop`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try) assert.NoError(t, try)
assert.Len(t, ins.Thing.Value, 3) assert.Len(t, ins.Thing.Value, 3)
} }
func TestSetField_MapHelper(t *testing.T) { func TestSetField_MapHelper(t *testing.T) {
type internal struct { type internal struct {
Thing map[KeyReference[string]]ValueReference[string] Thing map[KeyReference[string]]ValueReference[string]
} }
yml := `thing: yml := `thing:
what: not what: not
chip: chop chip: chop
lip: lop` lip: lop`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try) assert.NoError(t, try)
assert.Len(t, ins.Thing, 3) assert.Len(t, ins.Thing, 3)
} }
func TestSetField_ArrayHelper(t *testing.T) { func TestSetField_ArrayHelper(t *testing.T) {
type internal struct { type internal struct {
Thing NodeReference[[]ValueReference[string]] Thing NodeReference[[]ValueReference[string]]
} }
yml := `thing: yml := `thing:
- nice - nice
- rice - rice
- slice` - slice`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try) assert.NoError(t, try)
assert.Len(t, ins.Thing.Value, 3) assert.Len(t, ins.Thing.Value, 3)
} }
func TestSetField_Enum_Helper(t *testing.T) { func TestSetField_Enum_Helper(t *testing.T) {
type internal struct { type internal struct {
Thing NodeReference[[]ValueReference[any]] Thing NodeReference[[]ValueReference[any]]
} }
yml := `thing: yml := `thing:
- nice - nice
- rice - rice
- slice` - slice`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try) assert.NoError(t, try)
assert.Len(t, ins.Thing.Value, 3) assert.Len(t, ins.Thing.Value, 3)
} }
func TestSetField_Default_Helper(t *testing.T) { func TestSetField_Default_Helper(t *testing.T) {
type cake struct { type cake struct {
thing int thing int
} }
// this should be ignored, no custom objects in here my friend. // this should be ignored, no custom objects in here my friend.
type internal struct { type internal struct {
Thing cake Thing cake
} }
yml := `thing: yml := `thing:
type: cake` type: cake`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try) assert.NoError(t, try)
assert.Equal(t, 0, ins.Thing.thing) assert.Equal(t, 0, ins.Thing.thing)
} }
func TestHandleSlicesOfInts(t *testing.T) { func TestHandleSlicesOfInts(t *testing.T) {
type internal struct { type internal struct {
Thing NodeReference[[]ValueReference[any]] Thing NodeReference[[]ValueReference[any]]
} }
yml := `thing: yml := `thing:
- 5 - 5
- 1.234` - 1.234`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try) assert.NoError(t, try)
assert.Equal(t, int64(5), ins.Thing.Value[0].Value) assert.Equal(t, int64(5), ins.Thing.Value[0].Value)
assert.Equal(t, 1.234, ins.Thing.Value[1].Value) assert.Equal(t, 1.234, ins.Thing.Value[1].Value)
} }
func TestHandleSlicesOfBools(t *testing.T) { func TestHandleSlicesOfBools(t *testing.T) {
type internal struct { type internal struct {
Thing NodeReference[[]ValueReference[any]] Thing NodeReference[[]ValueReference[any]]
} }
yml := `thing: yml := `thing:
- true - true
- false` - false`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
try := BuildModel(rootNode.Content[0], ins) try := BuildModel(rootNode.Content[0], ins)
assert.NoError(t, try) assert.NoError(t, try)
assert.Equal(t, true, ins.Thing.Value[0].Value) assert.Equal(t, true, ins.Thing.Value[0].Value)
assert.Equal(t, false, ins.Thing.Value[1].Value) assert.Equal(t, false, ins.Thing.Value[1].Value)
} }
func TestSetField_Ignore(t *testing.T) { func TestSetField_Ignore(t *testing.T) {
type Complex struct { type Complex struct {
name string name string
} }
type internal struct { type internal struct {
Thing *Complex Thing *Complex
} }
yml := `thing: yml := `thing:
- nice - nice
- rice - rice
- slice` - slice`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
try := BuildModel(&rootNode, ins) try := BuildModel(&rootNode, ins)
assert.NoError(t, try) assert.NoError(t, try)
assert.Nil(t, ins.Thing) assert.Nil(t, ins.Thing)
} }
func TestBuildModelAsync(t *testing.T) { func TestBuildModelAsync(t *testing.T) {
type internal struct { type internal struct {
Thing KeyReference[map[KeyReference[string]]ValueReference[string]] Thing KeyReference[map[KeyReference[string]]ValueReference[string]]
} }
yml := `thing: yml := `thing:
what: not what: not
chip: chop chip: chop
lip: lop` lip: lop`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
var wg sync.WaitGroup var wg sync.WaitGroup
var errors []error var errors []error
wg.Add(1) wg.Add(1)
BuildModelAsync(rootNode.Content[0], ins, &wg, &errors) BuildModelAsync(rootNode.Content[0], ins, &wg, &errors)
wg.Wait() wg.Wait()
assert.Len(t, ins.Thing.Value, 3) assert.Len(t, ins.Thing.Value, 3)
} }
func TestBuildModelAsync_Error(t *testing.T) { func TestBuildModelAsync_Error(t *testing.T) {
type internal struct { type internal struct {
Thing []NodeReference[any] Thing []NodeReference[any]
} }
yml := `thing: yml := `thing:
- 999 - 999
- false` - false`
ins := new(internal) ins := new(internal)
var rootNode yaml.Node var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode) mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
var wg sync.WaitGroup var wg sync.WaitGroup
var errors []error var errors []error
wg.Add(1) wg.Add(1)
BuildModelAsync(rootNode.Content[0], ins, &wg, &errors) BuildModelAsync(rootNode.Content[0], ins, &wg, &errors)
wg.Wait() wg.Wait()
assert.Len(t, errors, 1) assert.Len(t, errors, 1)
assert.Len(t, ins.Thing, 0) assert.Len(t, ins.Thing, 0)
} }

View File

@@ -51,7 +51,7 @@ func TestNodeReference_Mutate(t *testing.T) {
func TestNodeReference_RefNode(t *testing.T) { func TestNodeReference_RefNode(t *testing.T) {
nr := new(NodeReference[string]) nr := new(NodeReference[string])
nr.KeyNode = &yaml.Node{ nr.KeyNode = &yaml.Node{
Content: []*yaml.Node{&yaml.Node{ Content: []*yaml.Node{{
Value: "$ref", Value: "$ref",
}}, }},
} }
@@ -749,8 +749,3 @@ func TestKeyReference_GetKeyNode(t *testing.T) {
assert.Equal(t, 3, nr.GetKeyNode().Line) assert.Equal(t, 3, nr.GetKeyNode().Line)
assert.Equal(t, "pizza", nr.GetKeyNode().Value) assert.Equal(t, "pizza", nr.GetKeyNode().Value)
} }

View File

@@ -17,7 +17,7 @@ import (
// //
// ParameterDefinitions holds parameters to be reused across operations. Parameter definitions can be // ParameterDefinitions holds parameters to be reused across operations. Parameter definitions can be
// referenced to the ones defined here. It does not define global operation parameters // referenced to the ones defined here. It does not define global operation parameters
// - https://swagger.io/specification/v2/#parametersDefinitionsObject // - https://swagger.io/specification/v2/#parametersDefinitionsObject
type ParameterDefinitions struct { type ParameterDefinitions struct {
Definitions map[low.KeyReference[string]]low.ValueReference[*Parameter] Definitions map[low.KeyReference[string]]low.ValueReference[*Parameter]
} }
@@ -26,7 +26,7 @@ type ParameterDefinitions struct {
// //
// ResponsesDefinitions is an object to hold responses to be reused across operations. Response definitions can be // ResponsesDefinitions is an object to hold responses to be reused across operations. Response definitions can be
// referenced to the ones defined here. It does not define global operation responses // referenced to the ones defined here. It does not define global operation responses
// - https://swagger.io/specification/v2/#responsesDefinitionsObject // - https://swagger.io/specification/v2/#responsesDefinitionsObject
type ResponsesDefinitions struct { type ResponsesDefinitions struct {
Definitions map[low.KeyReference[string]]low.ValueReference[*Response] Definitions map[low.KeyReference[string]]low.ValueReference[*Response]
} }
@@ -35,7 +35,7 @@ type ResponsesDefinitions struct {
// //
// A declaration of the security schemes available to be used in the specification. This does not enforce the security // A declaration of the security schemes available to be used in the specification. This does not enforce the security
// schemes on the operations and only serves to provide the relevant details for each scheme // schemes on the operations and only serves to provide the relevant details for each scheme
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
type SecurityDefinitions struct { type SecurityDefinitions struct {
Definitions map[low.KeyReference[string]]low.ValueReference[*SecurityScheme] Definitions map[low.KeyReference[string]]low.ValueReference[*SecurityScheme]
} }
@@ -44,7 +44,7 @@ type SecurityDefinitions struct {
// //
// An object to hold data types that can be consumed and produced by operations. These data types can be primitives, // An object to hold data types that can be consumed and produced by operations. These data types can be primitives,
// arrays or models. // arrays or models.
// - https://swagger.io/specification/v2/#definitionsObject // - https://swagger.io/specification/v2/#definitionsObject
type Definitions struct { type Definitions struct {
Schemas map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy] Schemas map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy]
} }

View File

@@ -15,7 +15,7 @@ import (
// Examples represents a low-level Swagger / OpenAPI 2 Example object. // Examples represents a low-level Swagger / OpenAPI 2 Example object.
// Allows sharing examples for operation responses // Allows sharing examples for operation responses
// - https://swagger.io/specification/v2/#exampleObject // - https://swagger.io/specification/v2/#exampleObject
type Examples struct { type Examples struct {
Values map[low.KeyReference[string]]low.ValueReference[any] Values map[low.KeyReference[string]]low.ValueReference[any]
} }

View File

@@ -17,7 +17,7 @@ import (
// Header Represents a low-level Swagger / OpenAPI 2 Header object. // Header Represents a low-level Swagger / OpenAPI 2 Header object.
// //
// A Header is essentially identical to a Parameter, except it does not contain 'name' or 'in' properties. // A Header is essentially identical to a Parameter, except it does not contain 'name' or 'in' properties.
// - https://swagger.io/specification/v2/#headerObject // - https://swagger.io/specification/v2/#headerObject
type Header struct { type Header struct {
Type low.NodeReference[string] Type low.NodeReference[string]
Format low.NodeReference[string] Format low.NodeReference[string]

View File

@@ -4,90 +4,90 @@
package v2 package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing" "testing"
) )
func TestHeader_Build(t *testing.T) { func TestHeader_Build(t *testing.T) {
yml := `items: yml := `items:
$ref: break` $ref: break`
var idxNode yaml.Node var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode) mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndex(&idxNode)
var n Header var n Header
err := low.BuildModel(idxNode.Content[0], &n) err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err) assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx) err = n.Build(idxNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
} }
func TestHeader_DefaultAsSlice(t *testing.T) { func TestHeader_DefaultAsSlice(t *testing.T) {
yml := `x-ext: thing yml := `x-ext: thing
default: default:
- why - why
- so many - so many
- variations` - variations`
var idxNode yaml.Node var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode) _ = yaml.Unmarshal([]byte(yml), &idxNode)
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndex(&idxNode)
var n Header var n Header
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx) _ = n.Build(idxNode.Content[0], idx)
assert.NotNil(t, n.Default.Value) assert.NotNil(t, n.Default.Value)
assert.Len(t, n.Default.Value, 3) assert.Len(t, n.Default.Value, 3)
assert.Len(t, n.GetExtensions(), 1) assert.Len(t, n.GetExtensions(), 1)
} }
func TestHeader_DefaultAsObject(t *testing.T) { func TestHeader_DefaultAsObject(t *testing.T) {
yml := `default: yml := `default:
lets: lets:
create: create:
a: a:
thing: ok?` thing: ok?`
var idxNode yaml.Node var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode) _ = yaml.Unmarshal([]byte(yml), &idxNode)
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndex(&idxNode)
var n Header var n Header
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx) _ = n.Build(idxNode.Content[0], idx)
assert.NotNil(t, n.Default.Value) assert.NotNil(t, n.Default.Value)
} }
func TestHeader_NoDefault(t *testing.T) { func TestHeader_NoDefault(t *testing.T) {
yml := `minimum: 12` yml := `minimum: 12`
var idxNode yaml.Node var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode) _ = yaml.Unmarshal([]byte(yml), &idxNode)
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndex(&idxNode)
var n Header var n Header
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx) _ = n.Build(idxNode.Content[0], idx)
assert.Equal(t, 12, n.Minimum.Value) assert.Equal(t, 12, n.Minimum.Value)
} }
func TestHeader_Hash_n_Grab(t *testing.T) { func TestHeader_Hash_n_Grab(t *testing.T) {
yml := `description: head yml := `description: head
type: string type: string
format: left format: left
collectionFormat: nice collectionFormat: nice
@@ -110,15 +110,15 @@ minItems: 1
uniqueItems: true uniqueItems: true
multipleOf: 12` multipleOf: 12`
var idxNode yaml.Node var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode) _ = yaml.Unmarshal([]byte(yml), &idxNode)
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndex(&idxNode)
var n Header var n Header
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx) _ = n.Build(idxNode.Content[0], idx)
yml2 := `description: head yml2 := `description: head
items: items:
type: int type: int
format: left format: left
@@ -142,37 +142,37 @@ x-belly: large
pattern: wow pattern: wow
` `
var idxNode2 yaml.Node var idxNode2 yaml.Node
_ = yaml.Unmarshal([]byte(yml2), &idxNode2) _ = yaml.Unmarshal([]byte(yml2), &idxNode2)
idx2 := index.NewSpecIndex(&idxNode2) idx2 := index.NewSpecIndex(&idxNode2)
var n2 Header var n2 Header
_ = low.BuildModel(idxNode2.Content[0], &n2) _ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(idxNode2.Content[0], idx2) _ = n2.Build(idxNode2.Content[0], idx2)
// hash // hash
assert.Equal(t, n.Hash(), n2.Hash()) assert.Equal(t, n.Hash(), n2.Hash())
// and grab // and grab
assert.Equal(t, "string", n.GetType().Value) assert.Equal(t, "string", n.GetType().Value)
assert.Equal(t, "head", n.GetDescription().Value) assert.Equal(t, "head", n.GetDescription().Value)
assert.Equal(t, "left", n.GetFormat().Value) assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "left", n.GetFormat().Value) assert.Equal(t, "left", n.GetFormat().Value)
assert.Equal(t, "nice", n.GetCollectionFormat().Value) assert.Equal(t, "nice", n.GetCollectionFormat().Value)
assert.Equal(t, "shut that door!", n.GetDefault().Value) assert.Equal(t, "shut that door!", n.GetDefault().Value)
assert.Equal(t, 10, n.GetMaximum().Value) assert.Equal(t, 10, n.GetMaximum().Value)
assert.Equal(t, 1, n.GetMinimum().Value) assert.Equal(t, 1, n.GetMinimum().Value)
assert.True(t, n.GetExclusiveMinimum().Value) assert.True(t, n.GetExclusiveMinimum().Value)
assert.True(t, n.GetExclusiveMaximum().Value) assert.True(t, n.GetExclusiveMaximum().Value)
assert.Equal(t, 10, n.GetMaxLength().Value) assert.Equal(t, 10, n.GetMaxLength().Value)
assert.Equal(t, 1, n.GetMinLength().Value) assert.Equal(t, 1, n.GetMinLength().Value)
assert.Equal(t, 10, n.GetMaxItems().Value) assert.Equal(t, 10, n.GetMaxItems().Value)
assert.Equal(t, 1, n.GetMinItems().Value) assert.Equal(t, 1, n.GetMinItems().Value)
assert.True(t, n.GetUniqueItems().Value) assert.True(t, n.GetUniqueItems().Value)
assert.Equal(t, 12, n.GetMultipleOf().Value) assert.Equal(t, 12, n.GetMultipleOf().Value)
assert.Equal(t, "wow", n.GetPattern().Value) assert.Equal(t, "wow", n.GetPattern().Value)
assert.Equal(t, "int", n.GetItems().Value.(*Items).Type.Value) assert.Equal(t, "int", n.GetItems().Value.(*Items).Type.Value)
assert.Len(t, n.GetEnum().Value, 2) assert.Len(t, n.GetEnum().Value, 2)
assert.Equal(t, "large", n.FindExtension("x-belly").Value) assert.Equal(t, "large", n.FindExtension("x-belly").Value)
} }

View File

@@ -18,7 +18,7 @@ import (
// //
// Items is a limited subset of JSON-Schema's items object. It is used by parameter definitions that are not // Items is a limited subset of JSON-Schema's items object. It is used by parameter definitions that are not
// located in "body". Items, is actually identical to a Header, except it does not have description. // located in "body". Items, is actually identical to a Header, except it does not have description.
// - https://swagger.io/specification/v2/#itemsObject // - https://swagger.io/specification/v2/#itemsObject
type Items struct { type Items struct {
Type low.NodeReference[string] Type low.NodeReference[string]
Format low.NodeReference[string] Format low.NodeReference[string]

View File

@@ -17,7 +17,7 @@ import (
// Operation represents a low-level Swagger / OpenAPI 2 Operation object. // Operation represents a low-level Swagger / OpenAPI 2 Operation object.
// //
// It describes a single API operation on a path. // It describes a single API operation on a path.
// - https://swagger.io/specification/v2/#operationObject // - https://swagger.io/specification/v2/#operationObject
type Operation struct { type Operation struct {
Tags low.NodeReference[[]low.ValueReference[string]] Tags low.NodeReference[[]low.ValueReference[string]]
Summary low.NodeReference[string] Summary low.NodeReference[string]

View File

@@ -22,29 +22,39 @@ import (
// There are five possible parameter types. // There are five possible parameter types.
// //
// Path // Path
// Used together with Path Templating, where the parameter value is actually part of the operation's URL. //
// This does not include the host or base path of the API. For example, in /items/{itemId}, the path parameter is itemId. // Used together with Path Templating, where the parameter value is actually part of the operation's URL.
// This does not include the host or base path of the API. For example, in /items/{itemId}, the path parameter is itemId.
//
// Query // Query
// Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id. //
// Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id.
//
// Header // Header
// Custom headers that are expected as part of the request. //
// Custom headers that are expected as part of the request.
//
// Body // Body
// The payload that's appended to the HTTP request. Since there can only be one payload, there can only be one body parameter. //
// The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only. // The payload that's appended to the HTTP request. Since there can only be one payload, there can only be one body parameter.
// Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation. // The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only.
// Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation.
//
// Form // Form
// Used to describe the payload of an HTTP request when either application/x-www-form-urlencoded, multipart/form-data //
// or both are used as the content type of the request (in Swagger's definition, the consumes property of an operation). // Used to describe the payload of an HTTP request when either application/x-www-form-urlencoded, multipart/form-data
// This is the only parameter type that can be used to send files, thus supporting the file type. Since form parameters // or both are used as the content type of the request (in Swagger's definition, the consumes property of an operation).
// are sent in the payload, they cannot be declared together with a body parameter for the same operation. Form // This is the only parameter type that can be used to send files, thus supporting the file type. Since form parameters
// parameters have a different format based on the content-type used (for further details, // are sent in the payload, they cannot be declared together with a body parameter for the same operation. Form
// consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4): // parameters have a different format based on the content-type used (for further details,
// application/x-www-form-urlencoded - Similar to the format of Query parameters but as a payload. For example, // consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4):
// foo=1&bar=swagger - both foo and bar are form parameters. This is normally used for simple parameters that are // application/x-www-form-urlencoded - Similar to the format of Query parameters but as a payload. For example,
// being transferred. // foo=1&bar=swagger - both foo and bar are form parameters. This is normally used for simple parameters that are
// multipart/form-data - each parameter takes a section in the payload with an internal header. For example, for // being transferred.
// the header Content-Disposition: form-data; name="submit-name" the name of the parameter is // multipart/form-data - each parameter takes a section in the payload with an internal header. For example, for
// submit-name. This type of form parameters is more commonly used for file transfers // the header Content-Disposition: form-data; name="submit-name" the name of the parameter is
// submit-name. This type of form parameters is more commonly used for file transfers
//
// https://swagger.io/specification/v2/#parameterObject // https://swagger.io/specification/v2/#parameterObject
type Parameter struct { type Parameter struct {
Name low.NodeReference[string] Name low.NodeReference[string]

View File

@@ -20,7 +20,7 @@ import (
// The path itself is still exposed to the tooling, but will not know which operations and parameters // The path itself is still exposed to the tooling, but will not know which operations and parameters
// are available. // are available.
// //
// - https://swagger.io/specification/v2/#pathItemObject // - https://swagger.io/specification/v2/#pathItemObject
type PathItem struct { type PathItem struct {
Ref low.NodeReference[string] Ref low.NodeReference[string]
Get low.NodeReference[*Operation] Get low.NodeReference[*Operation]

View File

@@ -17,7 +17,7 @@ import (
// Response is a representation of a high-level Swagger / OpenAPI 2 Response object, backed by a low-level one. // Response is a representation of a high-level Swagger / OpenAPI 2 Response object, backed by a low-level one.
// //
// Response describes a single response from an API Operation // Response describes a single response from an API Operation
// - https://swagger.io/specification/v2/#responseObject // - https://swagger.io/specification/v2/#responseObject
type Response struct { type Response struct {
Description low.NodeReference[string] Description low.NodeReference[string]
Schema low.NodeReference[*base.SchemaProxy] Schema low.NodeReference[*base.SchemaProxy]

View File

@@ -17,7 +17,7 @@ import (
// Scopes is a low-level representation of a Swagger / OpenAPI 2 OAuth2 Scopes object. // Scopes is a low-level representation of a Swagger / OpenAPI 2 OAuth2 Scopes object.
// //
// Scopes lists the available scopes for an OAuth2 security scheme. // Scopes lists the available scopes for an OAuth2 security scheme.
// - https://swagger.io/specification/v2/#scopesObject // - https://swagger.io/specification/v2/#scopesObject
type Scopes struct { type Scopes struct {
Values map[low.KeyReference[string]]low.ValueReference[string] Values map[low.KeyReference[string]]low.ValueReference[string]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]

View File

@@ -18,7 +18,7 @@ import (
// SecurityScheme allows the definition of a security scheme that can be used by the operations. Supported schemes are // SecurityScheme allows the definition of a security scheme that can be used by the operations. Supported schemes are
// basic authentication, an API key (either as a header or as a query parameter) and OAuth2's common flows // basic authentication, an API key (either as a header or as a query parameter) and OAuth2's common flows
// (implicit, password, application and access code) // (implicit, password, application and access code)
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
type SecurityScheme struct { type SecurityScheme struct {
Type low.NodeReference[string] Type low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]

View File

@@ -12,12 +12,12 @@
package v2 package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/resolver" "github.com/pb33f/libopenapi/resolver"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// processes a property of a Swagger document asynchronously using bool and error channels for signals. // processes a property of a Swagger document asynchronously using bool and error channels for signals.
@@ -26,272 +26,272 @@ type documentFunction func(root *yaml.Node, doc *Swagger, idx *index.SpecIndex,
// Swagger represents a high-level Swagger / OpenAPI 2 document. An instance of Swagger is the root of the specification. // Swagger represents a high-level Swagger / OpenAPI 2 document. An instance of Swagger is the root of the specification.
type Swagger struct { type Swagger struct {
// Swagger is the version of Swagger / OpenAPI being used, extracted from the 'swagger: 2.x' definition. // Swagger is the version of Swagger / OpenAPI being used, extracted from the 'swagger: 2.x' definition.
Swagger low.ValueReference[string] Swagger low.ValueReference[string]
// Info represents a specification Info definition. // Info represents a specification Info definition.
// Provides metadata about the API. The metadata can be used by the clients if needed. // Provides metadata about the API. The metadata can be used by the clients if needed.
// - https://swagger.io/specification/v2/#infoObject // - https://swagger.io/specification/v2/#infoObject
Info low.NodeReference[*base.Info] Info low.NodeReference[*base.Info]
// Host is The host (name or ip) serving the API. This MUST be the host only and does not include the scheme nor // Host is The host (name or ip) serving the API. This MUST be the host only and does not include the scheme nor
// sub-paths. It MAY include a port. If the host is not included, the host serving the documentation is to be used // sub-paths. It MAY include a port. If the host is not included, the host serving the documentation is to be used
// (including the port). The host does not support path templating. // (including the port). The host does not support path templating.
Host low.NodeReference[string] Host low.NodeReference[string]
// BasePath is The base path on which the API is served, which is relative to the host. If it is not included, // BasePath is The base path on which the API is served, which is relative to the host. If it is not included,
// the API is served directly under the host. The value MUST start with a leading slash (/). // the API is served directly under the host. The value MUST start with a leading slash (/).
// The basePath does not support path templating. // The basePath does not support path templating.
BasePath low.NodeReference[string] BasePath low.NodeReference[string]
// Schemes represents the transfer protocol of the API. Requirements MUST be from the list: "http", "https", "ws", "wss". // Schemes represents the transfer protocol of the API. Requirements MUST be from the list: "http", "https", "ws", "wss".
// If the schemes is not included, the default scheme to be used is the one used to access // If the schemes is not included, the default scheme to be used is the one used to access
// the Swagger definition itself. // the Swagger definition itself.
Schemes low.NodeReference[[]low.ValueReference[string]] Schemes low.NodeReference[[]low.ValueReference[string]]
// Consumes is a list of MIME types the APIs can consume. This is global to all APIs but can be overridden on // Consumes is a list of MIME types the APIs can consume. This is global to all APIs but can be overridden on
// specific API calls. Value MUST be as described under Mime Types. // specific API calls. Value MUST be as described under Mime Types.
Consumes low.NodeReference[[]low.ValueReference[string]] Consumes low.NodeReference[[]low.ValueReference[string]]
// Produces is a list of MIME types the APIs can produce. This is global to all APIs but can be overridden on // Produces is a list of MIME types the APIs can produce. This is global to all APIs but can be overridden on
// specific API calls. Value MUST be as described under Mime Types. // specific API calls. Value MUST be as described under Mime Types.
Produces low.NodeReference[[]low.ValueReference[string]] Produces low.NodeReference[[]low.ValueReference[string]]
// Paths are the paths and operations for the API. Perhaps the most important part of the specification. // Paths are the paths and operations for the API. Perhaps the most important part of the specification.
// - https://swagger.io/specification/v2/#pathsObject // - https://swagger.io/specification/v2/#pathsObject
Paths low.NodeReference[*Paths] Paths low.NodeReference[*Paths]
// Definitions is an object to hold data types produced and consumed by operations. It's composed of Schema instances // Definitions is an object to hold data types produced and consumed by operations. It's composed of Schema instances
// - https://swagger.io/specification/v2/#definitionsObject // - https://swagger.io/specification/v2/#definitionsObject
Definitions low.NodeReference[*Definitions] Definitions low.NodeReference[*Definitions]
// SecurityDefinitions represents security scheme definitions that can be used across the specification. // SecurityDefinitions represents security scheme definitions that can be used across the specification.
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
SecurityDefinitions low.NodeReference[*SecurityDefinitions] SecurityDefinitions low.NodeReference[*SecurityDefinitions]
// Parameters is an object to hold parameters that can be used across operations. // Parameters is an object to hold parameters that can be used across operations.
// This property does not define global parameters for all operations. // This property does not define global parameters for all operations.
// - https://swagger.io/specification/v2/#parametersDefinitionsObject // - https://swagger.io/specification/v2/#parametersDefinitionsObject
Parameters low.NodeReference[*ParameterDefinitions] Parameters low.NodeReference[*ParameterDefinitions]
// Responses is an object to hold responses that can be used across operations. // Responses is an object to hold responses that can be used across operations.
// This property does not define global responses for all operations. // This property does not define global responses for all operations.
// - https://swagger.io/specification/v2/#responsesDefinitionsObject // - https://swagger.io/specification/v2/#responsesDefinitionsObject
Responses low.NodeReference[*ResponsesDefinitions] Responses low.NodeReference[*ResponsesDefinitions]
// Security is a declaration of which security schemes are applied for the API as a whole. The list of values // Security is a declaration of which security schemes are applied for the API as a whole. The list of values
// describes alternative security schemes that can be used (that is, there is a logical OR between the security // describes alternative security schemes that can be used (that is, there is a logical OR between the security
// requirements). Individual operations can override this definition. // requirements). Individual operations can override this definition.
// - https://swagger.io/specification/v2/#securityRequirementObject // - https://swagger.io/specification/v2/#securityRequirementObject
Security low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]] Security low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]
// Tags are A list of tags used by the specification with additional metadata. // Tags are A list of tags used by the specification with additional metadata.
// The order of the tags can be used to reflect on their order by the parsing tools. Not all tags that are used // The order of the tags can be used to reflect on their order by the parsing tools. Not all tags that are used
// by the Operation Object must be declared. The tags that are not declared may be organized randomly or based // by the Operation Object must be declared. The tags that are not declared may be organized randomly or based
// on the tools' logic. Each tag name in the list MUST be unique. // on the tools' logic. Each tag name in the list MUST be unique.
// - https://swagger.io/specification/v2/#tagObject // - https://swagger.io/specification/v2/#tagObject
Tags low.NodeReference[[]low.ValueReference[*base.Tag]] Tags low.NodeReference[[]low.ValueReference[*base.Tag]]
// ExternalDocs is an instance of base.ExternalDoc for.. well, obvious really, innit mate? // ExternalDocs is an instance of base.ExternalDoc for.. well, obvious really, innit mate?
ExternalDocs low.NodeReference[*base.ExternalDoc] ExternalDocs low.NodeReference[*base.ExternalDoc]
// Extensions contains all custom extensions defined for the top-level document. // Extensions contains all custom extensions defined for the top-level document.
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
// Index is a reference to the index.SpecIndex that was created for the document and used // 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 // as a guide when building out the Document. Ideal if further processing is required on the model and
// the original details are required to continue the work. // the original details are required to continue the work.
// //
// This property is not a part of the OpenAPI schema, this is custom to libopenapi. // This property is not a part of the OpenAPI schema, this is custom to libopenapi.
Index *index.SpecIndex Index *index.SpecIndex
// SpecInfo is a reference to the datamodel.SpecInfo instance created when the specification was read. // SpecInfo is a reference to the datamodel.SpecInfo instance created when the specification was read.
// //
// This property is not a part of the OpenAPI schema, this is custom to libopenapi. // This property is not a part of the OpenAPI schema, this is custom to libopenapi.
SpecInfo *datamodel.SpecInfo SpecInfo *datamodel.SpecInfo
} }
// FindExtension locates an extension from the root of the Swagger document. // FindExtension locates an extension from the root of the Swagger document.
func (s *Swagger) FindExtension(ext string) *low.ValueReference[any] { func (s *Swagger) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, s.Extensions) return low.FindItemInMap[any](ext, s.Extensions)
} }
// GetExtensions returns all Swagger/Top level extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all Swagger/Top level extensions and satisfies the low.HasExtensions interface.
func (s *Swagger) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (s *Swagger) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
return s.Extensions return s.Extensions
} }
// CreateDocumentFromConfig will create a new Swagger document from the provided SpecInfo and DocumentConfiguration. // CreateDocumentFromConfig will create a new Swagger document from the provided SpecInfo and DocumentConfiguration.
func CreateDocumentFromConfig(info *datamodel.SpecInfo, func CreateDocumentFromConfig(info *datamodel.SpecInfo,
configuration *datamodel.DocumentConfiguration) (*Swagger, []error) { configuration *datamodel.DocumentConfiguration) (*Swagger, []error) {
return createDocument(info, configuration) return createDocument(info, configuration)
} }
// CreateDocument will create a new Swagger document from the provided SpecInfo. // CreateDocument will create a new Swagger document from the provided SpecInfo.
// //
// Deprecated: Use CreateDocumentFromConfig instead. // Deprecated: Use CreateDocumentFromConfig instead.
func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) { func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) {
return createDocument(info, &datamodel.DocumentConfiguration{ return createDocument(info, &datamodel.DocumentConfiguration{
AllowRemoteReferences: true, AllowRemoteReferences: true,
AllowFileReferences: true, AllowFileReferences: true,
}) })
} }
func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Swagger, []error) { func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Swagger, []error) {
doc := Swagger{Swagger: low.ValueReference[string]{Value: info.Version, ValueNode: info.RootNode}} doc := Swagger{Swagger: low.ValueReference[string]{Value: info.Version, ValueNode: info.RootNode}}
doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0]) doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0])
// build an index // build an index
idx := index.NewSpecIndexWithConfig(info.RootNode, &index.SpecIndexConfig{ idx := index.NewSpecIndexWithConfig(info.RootNode, &index.SpecIndexConfig{
BaseURL: config.BaseURL, BaseURL: config.BaseURL,
AllowRemoteLookup: config.AllowRemoteReferences, AllowRemoteLookup: config.AllowRemoteReferences,
AllowFileLookup: config.AllowFileReferences, AllowFileLookup: config.AllowFileReferences,
}) })
doc.Index = idx doc.Index = idx
doc.SpecInfo = info doc.SpecInfo = info
var errors []error var errors []error
// build out swagger scalar variables. // build out swagger scalar variables.
_ = low.BuildModel(info.RootNode.Content[0], &doc) _ = low.BuildModel(info.RootNode.Content[0], &doc)
// extract externalDocs // extract externalDocs
extDocs, err := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode, idx) extDocs, err := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode, idx)
if err != nil { if err != nil {
errors = append(errors, err) errors = append(errors, err)
} }
doc.ExternalDocs = extDocs doc.ExternalDocs = extDocs
// create resolver and check for circular references. // create resolver and check for circular references.
resolve := resolver.NewResolver(idx) resolve := resolver.NewResolver(idx)
resolvingErrors := resolve.CheckForCircularReferences() resolvingErrors := resolve.CheckForCircularReferences()
if len(resolvingErrors) > 0 { if len(resolvingErrors) > 0 {
for r := range resolvingErrors { for r := range resolvingErrors {
errors = append(errors, resolvingErrors[r]) errors = append(errors, resolvingErrors[r])
} }
} }
extractionFuncs := []documentFunction{ extractionFuncs := []documentFunction{
extractInfo, extractInfo,
extractPaths, extractPaths,
extractDefinitions, extractDefinitions,
extractParamDefinitions, extractParamDefinitions,
extractResponsesDefinitions, extractResponsesDefinitions,
extractSecurityDefinitions, extractSecurityDefinitions,
extractTags, extractTags,
extractSecurity, extractSecurity,
} }
doneChan := make(chan bool) doneChan := make(chan bool)
errChan := make(chan error) errChan := make(chan error)
for i := range extractionFuncs { for i := range extractionFuncs {
go extractionFuncs[i](info.RootNode.Content[0], &doc, idx, doneChan, errChan) go extractionFuncs[i](info.RootNode.Content[0], &doc, idx, doneChan, errChan)
} }
completedExtractions := 0 completedExtractions := 0
for completedExtractions < len(extractionFuncs) { for completedExtractions < len(extractionFuncs) {
select { select {
case <-doneChan: case <-doneChan:
completedExtractions++ completedExtractions++
case e := <-errChan: case e := <-errChan:
completedExtractions++ completedExtractions++
errors = append(errors, e) errors = append(errors, e)
} }
} }
return &doc, errors return &doc, errors
} }
func (s *Swagger) GetExternalDocs() *low.NodeReference[any] { func (s *Swagger) GetExternalDocs() *low.NodeReference[any] {
return &low.NodeReference[any]{ return &low.NodeReference[any]{
KeyNode: s.ExternalDocs.KeyNode, KeyNode: s.ExternalDocs.KeyNode,
ValueNode: s.ExternalDocs.ValueNode, ValueNode: s.ExternalDocs.ValueNode,
Value: s.ExternalDocs.Value, Value: s.ExternalDocs.Value,
} }
} }
func extractInfo(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractInfo(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
info, err := low.ExtractObject[*base.Info](base.InfoLabel, root, idx) info, err := low.ExtractObject[*base.Info](base.InfoLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Info = info doc.Info = info
c <- true c <- true
} }
func extractPaths(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractPaths(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
paths, err := low.ExtractObject[*Paths](PathsLabel, root, idx) paths, err := low.ExtractObject[*Paths](PathsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Paths = paths doc.Paths = paths
c <- true c <- true
} }
func extractDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
def, err := low.ExtractObject[*Definitions](DefinitionsLabel, root, idx) def, err := low.ExtractObject[*Definitions](DefinitionsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Definitions = def doc.Definitions = def
c <- true c <- true
} }
func extractParamDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractParamDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
param, err := low.ExtractObject[*ParameterDefinitions](ParametersLabel, root, idx) param, err := low.ExtractObject[*ParameterDefinitions](ParametersLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Parameters = param doc.Parameters = param
c <- true c <- true
} }
func extractResponsesDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractResponsesDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
resp, err := low.ExtractObject[*ResponsesDefinitions](ResponsesLabel, root, idx) resp, err := low.ExtractObject[*ResponsesDefinitions](ResponsesLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Responses = resp doc.Responses = resp
c <- true c <- true
} }
func extractSecurityDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractSecurityDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
sec, err := low.ExtractObject[*SecurityDefinitions](SecurityDefinitionsLabel, root, idx) sec, err := low.ExtractObject[*SecurityDefinitions](SecurityDefinitionsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.SecurityDefinitions = sec doc.SecurityDefinitions = sec
c <- true c <- true
} }
func extractTags(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractTags(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
tags, ln, vn, err := low.ExtractArray[*base.Tag](base.TagsLabel, root, idx) tags, ln, vn, err := low.ExtractArray[*base.Tag](base.TagsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Tags = low.NodeReference[[]low.ValueReference[*base.Tag]]{ doc.Tags = low.NodeReference[[]low.ValueReference[*base.Tag]]{
Value: tags, Value: tags,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
c <- true c <- true
} }
func extractSecurity(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractSecurity(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, root, idx) sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Security = low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]{ doc.Security = low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]{
Value: sec, Value: sec,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
c <- true c <- true
} }

View File

@@ -4,350 +4,350 @@
package v2 package v2
import ( import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"io/ioutil" "io/ioutil"
"testing" "testing"
) )
var doc *Swagger var doc *Swagger
func initTest() { func initTest() {
if doc != nil { if doc != nil {
return return
} }
data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml") data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err []error
doc, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ doc, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false, AllowFileReferences: false,
AllowRemoteReferences: false, AllowRemoteReferences: false,
}) })
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
if err != nil { if err != nil {
fmt.Print(err) fmt.Print(err)
panic(err) panic(err)
} }
} }
func BenchmarkCreateDocument(b *testing.B) { func BenchmarkCreateDocument(b *testing.B) {
data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml") data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
doc, _ = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ doc, _ = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false, AllowFileReferences: false,
AllowRemoteReferences: false, AllowRemoteReferences: false,
}) })
} }
} }
func TestCreateDocument(t *testing.T) { func TestCreateDocument(t *testing.T) {
initTest() initTest()
doc := doc doc := doc
assert.Equal(t, "2.0", doc.SpecInfo.Version) assert.Equal(t, "2.0", doc.SpecInfo.Version)
assert.Equal(t, "1.0.6", doc.Info.Value.Version.Value) assert.Equal(t, "1.0.6", doc.Info.Value.Version.Value)
assert.Equal(t, "petstore.swagger.io", doc.Host.Value) assert.Equal(t, "petstore.swagger.io", doc.Host.Value)
assert.Equal(t, "/v2", doc.BasePath.Value) assert.Equal(t, "/v2", doc.BasePath.Value)
assert.Len(t, doc.Parameters.Value.Definitions, 1) assert.Len(t, doc.Parameters.Value.Definitions, 1)
assert.Len(t, doc.Tags.Value, 3) assert.Len(t, doc.Tags.Value, 3)
assert.Len(t, doc.Schemes.Value, 2) assert.Len(t, doc.Schemes.Value, 2)
assert.Len(t, doc.Definitions.Value.Schemas, 6) assert.Len(t, doc.Definitions.Value.Schemas, 6)
assert.Len(t, doc.SecurityDefinitions.Value.Definitions, 3) assert.Len(t, doc.SecurityDefinitions.Value.Definitions, 3)
assert.Len(t, doc.Paths.Value.PathItems, 15) assert.Len(t, doc.Paths.Value.PathItems, 15)
assert.Len(t, doc.Responses.Value.Definitions, 2) assert.Len(t, doc.Responses.Value.Definitions, 2)
assert.Equal(t, "http://swagger.io", doc.ExternalDocs.Value.URL.Value) assert.Equal(t, "http://swagger.io", doc.ExternalDocs.Value.URL.Value)
assert.Equal(t, true, doc.FindExtension("x-pet").Value) assert.Equal(t, true, doc.FindExtension("x-pet").Value)
assert.Equal(t, true, doc.FindExtension("X-Pet").Value) assert.Equal(t, true, doc.FindExtension("X-Pet").Value)
assert.NotNil(t, doc.GetExternalDocs()) assert.NotNil(t, doc.GetExternalDocs())
assert.Len(t, doc.GetExtensions(), 1) assert.Len(t, doc.GetExtensions(), 1)
} }
func TestCreateDocument_Info(t *testing.T) { func TestCreateDocument_Info(t *testing.T) {
initTest() initTest()
assert.Equal(t, "Swagger Petstore", doc.Info.Value.Title.Value) assert.Equal(t, "Swagger Petstore", doc.Info.Value.Title.Value)
assert.Equal(t, "apiteam@swagger.io", doc.Info.Value.Contact.Value.Email.Value) assert.Equal(t, "apiteam@swagger.io", doc.Info.Value.Contact.Value.Email.Value)
assert.Equal(t, "Apache 2.0", doc.Info.Value.License.Value.Name.Value) assert.Equal(t, "Apache 2.0", doc.Info.Value.License.Value.Name.Value)
} }
func TestCreateDocument_Parameters(t *testing.T) { func TestCreateDocument_Parameters(t *testing.T) {
initTest() initTest()
simpleParam := doc.Parameters.Value.FindParameter("simpleParam") simpleParam := doc.Parameters.Value.FindParameter("simpleParam")
assert.NotNil(t, simpleParam) assert.NotNil(t, simpleParam)
assert.Equal(t, "simple", simpleParam.Value.Name.Value) assert.Equal(t, "simple", simpleParam.Value.Name.Value)
assert.Equal(t, "nuggets", simpleParam.Value.FindExtension("x-chicken").Value) assert.Equal(t, "nuggets", simpleParam.Value.FindExtension("x-chicken").Value)
} }
func TestCreateDocument_Tags(t *testing.T) { func TestCreateDocument_Tags(t *testing.T) {
initTest() initTest()
assert.Equal(t, "pet", doc.Tags.Value[0].Value.Name.Value) assert.Equal(t, "pet", doc.Tags.Value[0].Value.Name.Value)
assert.Equal(t, "http://swagger.io", doc.Tags.Value[0].Value.ExternalDocs.Value.URL.Value) assert.Equal(t, "http://swagger.io", doc.Tags.Value[0].Value.ExternalDocs.Value.URL.Value)
assert.Equal(t, "store", doc.Tags.Value[1].Value.Name.Value) assert.Equal(t, "store", doc.Tags.Value[1].Value.Name.Value)
assert.Equal(t, "user", doc.Tags.Value[2].Value.Name.Value) assert.Equal(t, "user", doc.Tags.Value[2].Value.Name.Value)
assert.Equal(t, "http://swagger.io", doc.Tags.Value[2].Value.ExternalDocs.Value.URL.Value) assert.Equal(t, "http://swagger.io", doc.Tags.Value[2].Value.ExternalDocs.Value.URL.Value)
} }
func TestCreateDocument_SecurityDefinitions(t *testing.T) { func TestCreateDocument_SecurityDefinitions(t *testing.T) {
initTest() initTest()
apiKey := doc.SecurityDefinitions.Value.FindSecurityDefinition("api_key") apiKey := doc.SecurityDefinitions.Value.FindSecurityDefinition("api_key")
assert.Equal(t, "apiKey", apiKey.Value.Type.Value) assert.Equal(t, "apiKey", apiKey.Value.Type.Value)
petStoreAuth := doc.SecurityDefinitions.Value.FindSecurityDefinition("petstore_auth") petStoreAuth := doc.SecurityDefinitions.Value.FindSecurityDefinition("petstore_auth")
assert.Equal(t, "oauth2", petStoreAuth.Value.Type.Value) assert.Equal(t, "oauth2", petStoreAuth.Value.Type.Value)
assert.Equal(t, "implicit", petStoreAuth.Value.Flow.Value) assert.Equal(t, "implicit", petStoreAuth.Value.Flow.Value)
assert.Len(t, petStoreAuth.Value.Scopes.Value.Values, 2) assert.Len(t, petStoreAuth.Value.Scopes.Value.Values, 2)
assert.Equal(t, "read your pets", petStoreAuth.Value.Scopes.Value.FindScope("read:pets").Value) assert.Equal(t, "read your pets", petStoreAuth.Value.Scopes.Value.FindScope("read:pets").Value)
} }
func TestCreateDocument_Definitions(t *testing.T) { func TestCreateDocument_Definitions(t *testing.T) {
initTest() initTest()
apiResp := doc.Definitions.Value.FindSchema("ApiResponse").Value.Schema() apiResp := doc.Definitions.Value.FindSchema("ApiResponse").Value.Schema()
assert.NotNil(t, apiResp) assert.NotNil(t, apiResp)
assert.Len(t, apiResp.Properties.Value, 3) assert.Len(t, apiResp.Properties.Value, 3)
assert.Equal(t, "integer", apiResp.FindProperty("code").Value.Schema().Type.Value.A) assert.Equal(t, "integer", apiResp.FindProperty("code").Value.Schema().Type.Value.A)
pet := doc.Definitions.Value.FindSchema("Pet").Value.Schema() pet := doc.Definitions.Value.FindSchema("Pet").Value.Schema()
assert.NotNil(t, pet) assert.NotNil(t, pet)
assert.Len(t, pet.Required.Value, 2) assert.Len(t, pet.Required.Value, 2)
// perform a deep inline lookup on a schema to ensure chains work // perform a deep inline lookup on a schema to ensure chains work
assert.Equal(t, "Category", pet.FindProperty("category").Value.Schema().XML.Value.Name.Value) assert.Equal(t, "Category", pet.FindProperty("category").Value.Schema().XML.Value.Name.Value)
// check enums // check enums
assert.Len(t, pet.FindProperty("status").Value.Schema().Enum.Value, 3) assert.Len(t, pet.FindProperty("status").Value.Schema().Enum.Value, 3)
} }
func TestCreateDocument_ResponseDefinitions(t *testing.T) { func TestCreateDocument_ResponseDefinitions(t *testing.T) {
initTest() initTest()
apiResp := doc.Responses.Value.FindResponse("200") apiResp := doc.Responses.Value.FindResponse("200")
assert.NotNil(t, apiResp) assert.NotNil(t, apiResp)
assert.Equal(t, "OK", apiResp.Value.Description.Value) assert.Equal(t, "OK", apiResp.Value.Description.Value)
assert.Equal(t, "morning", apiResp.Value.FindExtension("x-coffee").Value) assert.Equal(t, "morning", apiResp.Value.FindExtension("x-coffee").Value)
header := apiResp.Value.FindHeader("noHeader") header := apiResp.Value.FindHeader("noHeader")
assert.NotNil(t, header) assert.NotNil(t, header)
assert.True(t, header.Value.FindExtension("x-empty").Value.(bool)) assert.True(t, header.Value.FindExtension("x-empty").Value.(bool))
header = apiResp.Value.FindHeader("myHeader") header = apiResp.Value.FindHeader("myHeader")
if k, ok := header.Value.Items.Value.Default.Value.(map[string]interface{}); ok { if k, ok := header.Value.Items.Value.Default.Value.(map[string]interface{}); ok {
assert.Equal(t, "here", k["something"]) assert.Equal(t, "here", k["something"])
} else { } else {
panic("should not fail.") panic("should not fail.")
} }
if k, ok := header.Value.Items.Value.Items.Value.Default.Value.([]interface{}); ok { if k, ok := header.Value.Items.Value.Items.Value.Default.Value.([]interface{}); ok {
assert.Len(t, k, 2) assert.Len(t, k, 2)
assert.Equal(t, "two", k[1]) assert.Equal(t, "two", k[1])
} else { } else {
panic("should not fail.") panic("should not fail.")
} }
header = apiResp.Value.FindHeader("yourHeader") header = apiResp.Value.FindHeader("yourHeader")
assert.Equal(t, "somethingSimple", header.Value.Items.Value.Default.Value) assert.Equal(t, "somethingSimple", header.Value.Items.Value.Default.Value)
assert.NotNil(t, apiResp.Value.Examples.Value.FindExample("application/json").Value) assert.NotNil(t, apiResp.Value.Examples.Value.FindExample("application/json").Value)
} }
func TestCreateDocument_Paths(t *testing.T) { func TestCreateDocument_Paths(t *testing.T) {
initTest() initTest()
uploadImage := doc.Paths.Value.FindPath("/pet/{petId}/uploadImage").Value uploadImage := doc.Paths.Value.FindPath("/pet/{petId}/uploadImage").Value
assert.NotNil(t, uploadImage) assert.NotNil(t, uploadImage)
assert.Nil(t, doc.Paths.Value.FindPath("/nothing-nowhere-nohow")) assert.Nil(t, doc.Paths.Value.FindPath("/nothing-nowhere-nohow"))
assert.Equal(t, "man", uploadImage.FindExtension("x-potato").Value) assert.Equal(t, "man", uploadImage.FindExtension("x-potato").Value)
assert.Equal(t, "fresh", doc.Paths.Value.FindExtension("x-minty").Value) assert.Equal(t, "fresh", doc.Paths.Value.FindExtension("x-minty").Value)
assert.Equal(t, "successful operation", assert.Equal(t, "successful operation",
uploadImage.Post.Value.Responses.Value.FindResponseByCode("200").Value.Description.Value) uploadImage.Post.Value.Responses.Value.FindResponseByCode("200").Value.Description.Value)
} }
func TestCreateDocument_Bad(t *testing.T) { func TestCreateDocument_Bad(t *testing.T) {
yml := `swagger: yml := `swagger:
$ref: bork` $ref: bork`
info, err := datamodel.ExtractSpecInfo([]byte(yml)) info, err := datamodel.ExtractSpecInfo([]byte(yml))
assert.Nil(t, info) assert.Nil(t, info)
assert.Error(t, err) assert.Error(t, err)
} }
func TestCreateDocument_ExternalDocsBad(t *testing.T) { func TestCreateDocument_ExternalDocsBad(t *testing.T) {
yml := `externalDocs: yml := `externalDocs:
$ref: bork` $ref: bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_TagsBad(t *testing.T) { func TestCreateDocument_TagsBad(t *testing.T) {
yml := `tags: yml := `tags:
$ref: bork` $ref: bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_PathsBad(t *testing.T) { func TestCreateDocument_PathsBad(t *testing.T) {
yml := `paths: yml := `paths:
"/hey": "/hey":
post: post:
responses: responses:
"200": "200":
$ref: bork` $ref: bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_SecurityBad(t *testing.T) { func TestCreateDocument_SecurityBad(t *testing.T) {
yml := `security: yml := `security:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_SecurityDefinitionsBad(t *testing.T) { func TestCreateDocument_SecurityDefinitionsBad(t *testing.T) {
yml := `securityDefinitions: yml := `securityDefinitions:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_ResponsesBad(t *testing.T) { func TestCreateDocument_ResponsesBad(t *testing.T) {
yml := `responses: yml := `responses:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_ParametersBad(t *testing.T) { func TestCreateDocument_ParametersBad(t *testing.T) {
yml := `parameters: yml := `parameters:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_DefinitionsBad(t *testing.T) { func TestCreateDocument_DefinitionsBad(t *testing.T) {
yml := `definitions: yml := `definitions:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_InfoBad(t *testing.T) { func TestCreateDocument_InfoBad(t *testing.T) {
yml := `info: yml := `info:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCircularReferenceError(t *testing.T) { func TestCircularReferenceError(t *testing.T) {
data, _ := ioutil.ReadFile("../../../test_specs/swagger-circular-tests.yaml") data, _ := ioutil.ReadFile("../../../test_specs/swagger-circular-tests.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
circDoc, err := CreateDocument(info) circDoc, err := CreateDocument(info)
assert.NotNil(t, circDoc) assert.NotNil(t, circDoc)
assert.Len(t, err, 3) assert.Len(t, err, 3)
} }

View File

@@ -19,7 +19,7 @@ import (
// PathItem Object that describes a set of requests that may be initiated by the API provider and the expected // PathItem Object that describes a set of requests that may be initiated by the API provider and the expected
// responses. The key value used to identify the path item object is an expression, evaluated at runtime, // responses. The key value used to identify the path item object is an expression, evaluated at runtime,
// that identifies a URL to use for the callback operation. // that identifies a URL to use for the callback operation.
// - https://spec.openapis.org/oas/v3.1.0#callback-object // - https://spec.openapis.org/oas/v3.1.0#callback-object
type Callback struct { type Callback struct {
Expression low.ValueReference[map[low.KeyReference[string]]low.ValueReference[*PathItem]] Expression low.ValueReference[map[low.KeyReference[string]]low.ValueReference[*PathItem]]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
@@ -61,9 +61,9 @@ func (cb *Callback) Build(root *yaml.Node, idx *index.SpecIndex) error {
Value: currentCB.Value, Value: currentCB.Value,
KeyNode: currentCB, KeyNode: currentCB,
}] = low.ValueReference[*PathItem]{ }] = low.ValueReference[*PathItem]{
Value: callback, Value: callback,
ValueNode: callbackNode, ValueNode: callbackNode,
Reference: rv, Reference: rv,
} }
} }
if len(callbacks) > 0 { if len(callbacks) > 0 {

View File

@@ -19,7 +19,7 @@ import (
// //
// Holds a set of reusable objects for different aspects of the OAS. All objects defined within the components object // Holds a set of reusable objects for different aspects of the OAS. All objects defined within the components object
// will have no effect on the API unless they are explicitly referenced from properties outside the components object. // 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 // - https://spec.openapis.org/oas/v3.1.0#components-object
type Components struct { type Components struct {
Schemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy]] Schemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy]]
Responses low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Response]] Responses low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Response]]

View File

@@ -13,7 +13,7 @@ import (
) )
// Encoding represents a low-level OpenAPI 3+ Encoding object // Encoding represents a low-level OpenAPI 3+ Encoding object
// - https://spec.openapis.org/oas/v3.1.0#encoding-object // - https://spec.openapis.org/oas/v3.1.0#encoding-object
type Encoding struct { type Encoding struct {
ContentType low.NodeReference[string] ContentType low.NodeReference[string]
Headers low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]] Headers low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]]

View File

@@ -16,7 +16,7 @@ import (
) )
// Header represents a low-level OpenAPI 3+ Header object. // Header represents a low-level OpenAPI 3+ Header object.
// - https://spec.openapis.org/oas/v3.1.0#header-object // - https://spec.openapis.org/oas/v3.1.0#header-object
type Header struct { type Header struct {
Description low.NodeReference[string] Description low.NodeReference[string]
Required low.NodeReference[bool] Required low.NodeReference[bool]

View File

@@ -24,7 +24,7 @@ import (
// //
// For computing links, and providing instructions to execute them, a runtime expression is used for accessing values // For computing links, and providing instructions to execute them, a runtime expression is used for accessing values
// in an operation and using them as parameters while invoking the linked operation. // in an operation and using them as parameters while invoking the linked operation.
// - https://spec.openapis.org/oas/v3.1.0#link-object // - https://spec.openapis.org/oas/v3.1.0#link-object
type Link struct { type Link struct {
OperationRef low.NodeReference[string] OperationRef low.NodeReference[string]
OperationId low.NodeReference[string] OperationId low.NodeReference[string]

View File

@@ -18,7 +18,7 @@ import (
// MediaType represents a low-level OpenAPI MediaType object. // MediaType represents a low-level OpenAPI MediaType object.
// //
// Each Media Type Object provides schema and examples for the media type identified by its key. // Each Media Type Object provides schema and examples for the media type identified by its key.
// - https://spec.openapis.org/oas/v3.1.0#media-type-object // - https://spec.openapis.org/oas/v3.1.0#media-type-object
type MediaType struct { type MediaType struct {
Schema low.NodeReference[*base.SchemaProxy] Schema low.NodeReference[*base.SchemaProxy]
Example low.NodeReference[any] Example low.NodeReference[any]

View File

@@ -14,7 +14,7 @@ import (
) )
// OAuthFlows represents a low-level OpenAPI 3+ OAuthFlows object. // OAuthFlows represents a low-level OpenAPI 3+ OAuthFlows object.
// - https://spec.openapis.org/oas/v3.1.0#oauth-flows-object // - https://spec.openapis.org/oas/v3.1.0#oauth-flows-object
type OAuthFlows struct { type OAuthFlows struct {
Implicit low.NodeReference[*OAuthFlow] Implicit low.NodeReference[*OAuthFlow]
Password low.NodeReference[*OAuthFlow] Password low.NodeReference[*OAuthFlow]
@@ -87,7 +87,7 @@ func (o *OAuthFlows) Hash() [32]byte {
} }
// OAuthFlow represents a low-level OpenAPI 3+ OAuthFlow object. // OAuthFlow represents a low-level OpenAPI 3+ OAuthFlow object.
// - https://spec.openapis.org/oas/v3.1.0#oauth-flow-object // - https://spec.openapis.org/oas/v3.1.0#oauth-flow-object
type OAuthFlow struct { type OAuthFlow struct {
AuthorizationUrl low.NodeReference[string] AuthorizationUrl low.NodeReference[string]
TokenUrl low.NodeReference[string] TokenUrl low.NodeReference[string]

View File

@@ -18,7 +18,7 @@ import (
// //
// An Operation is perhaps the most important object of the entire specification. Everything of value // An Operation is perhaps the most important object of the entire specification. Everything of value
// happens here. The entire being for existence of this library and the specification, is this Operation. // happens here. The entire being for existence of this library and the specification, is this Operation.
// - https://spec.openapis.org/oas/v3.1.0#operation-object // - https://spec.openapis.org/oas/v3.1.0#operation-object
type Operation struct { type Operation struct {
Tags low.NodeReference[[]low.ValueReference[string]] Tags low.NodeReference[[]low.ValueReference[string]]
Summary low.NodeReference[string] Summary low.NodeReference[string]

View File

@@ -18,7 +18,7 @@ import (
// Parameter represents a high-level OpenAPI 3+ Parameter object, that is backed by a low-level one. // Parameter represents a high-level OpenAPI 3+ Parameter object, that is backed by a low-level one.
// //
// A unique parameter is defined by a combination of a name and location. // A unique parameter is defined by a combination of a name and location.
// - https://spec.openapis.org/oas/v3.1.0#parameter-object // - https://spec.openapis.org/oas/v3.1.0#parameter-object
type Parameter struct { type Parameter struct {
Name low.NodeReference[string] Name low.NodeReference[string]
In low.NodeReference[string] In low.NodeReference[string]

View File

@@ -14,7 +14,7 @@ import (
) )
// RequestBody represents a low-level OpenAPI 3+ RequestBody object. // RequestBody represents a low-level OpenAPI 3+ RequestBody object.
// - https://spec.openapis.org/oas/v3.1.0#request-body-object // - https://spec.openapis.org/oas/v3.1.0#request-body-object
type RequestBody struct { type RequestBody struct {
Description low.NodeReference[string] Description low.NodeReference[string]
Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]] Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]]

View File

@@ -22,7 +22,7 @@ import (
// authorization code) as defined in RFC6749 (https://www.rfc-editor.org/rfc/rfc6749), and OpenID Connect Discovery. // authorization code) as defined in RFC6749 (https://www.rfc-editor.org/rfc/rfc6749), and OpenID Connect Discovery.
// Please note that as of 2020, the implicit flow is about to be deprecated by OAuth 2.0 Security Best Current Practice. // Please note that as of 2020, the implicit flow is about to be deprecated by OAuth 2.0 Security Best Current Practice.
// Recommended for most use case is Authorization Code Grant flow with PKCE. // Recommended for most use case is Authorization Code Grant flow with PKCE.
// - https://spec.openapis.org/oas/v3.1.0#security-scheme-object // - https://spec.openapis.org/oas/v3.1.0#security-scheme-object
type SecurityScheme struct { type SecurityScheme struct {
Type low.NodeReference[string] Type low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]

View File

@@ -14,7 +14,7 @@ import (
) )
// Server represents a low-level OpenAPI 3+ Server object. // Server represents a low-level OpenAPI 3+ Server object.
// - https://spec.openapis.org/oas/v3.1.0#server-object // - https://spec.openapis.org/oas/v3.1.0#server-object
type Server struct { type Server struct {
URL low.NodeReference[string] URL low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]

View File

@@ -4,31 +4,31 @@
package datamodel package datamodel
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"testing" "testing"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
const ( const (
// OpenApi3 is used by all OpenAPI 3+ docs // OpenApi3 is used by all OpenAPI 3+ docs
OpenApi3 = "openapi" OpenApi3 = "openapi"
// OpenApi2 is used by all OpenAPI 2 docs, formerly known as swagger. // OpenApi2 is used by all OpenAPI 2 docs, formerly known as swagger.
OpenApi2 = "swagger" OpenApi2 = "swagger"
// AsyncApi is used by akk AsyncAPI docs, all versions. // AsyncApi is used by akk AsyncAPI docs, all versions.
AsyncApi = "asyncapi" AsyncApi = "asyncapi"
) )
func TestSpecInfo_GetJSONParsingChannel(t *testing.T) { func TestSpecInfo_GetJSONParsingChannel(t *testing.T) {
// dumb, but we need to ensure coverage is as high as we can make it. // dumb, but we need to ensure coverage is as high as we can make it.
bchan := make(chan bool) bchan := make(chan bool)
si := &SpecInfo{JsonParsingChannel: bchan} si := &SpecInfo{JsonParsingChannel: bchan}
assert.Equal(t, si.GetJSONParsingChannel(), bchan) assert.Equal(t, si.GetJSONParsingChannel(), bchan)
} }
@@ -115,147 +115,147 @@ info:
version: '0.1.0'` version: '0.1.0'`
func TestExtractSpecInfo_ValidJSON(t *testing.T) { func TestExtractSpecInfo_ValidJSON(t *testing.T) {
r, e := ExtractSpecInfo([]byte(goodJSON)) r, e := ExtractSpecInfo([]byte(goodJSON))
assert.Greater(t, len(*r.SpecJSONBytes), 0) assert.Greater(t, len(*r.SpecJSONBytes), 0)
assert.Error(t, e) assert.Error(t, e)
} }
func TestExtractSpecInfo_InvalidJSON(t *testing.T) { func TestExtractSpecInfo_InvalidJSON(t *testing.T) {
_, e := ExtractSpecInfo([]byte(badJSON)) _, e := ExtractSpecInfo([]byte(badJSON))
assert.Error(t, e) assert.Error(t, e)
} }
func TestExtractSpecInfo_Nothing(t *testing.T) { func TestExtractSpecInfo_Nothing(t *testing.T) {
_, e := ExtractSpecInfo([]byte("")) _, e := ExtractSpecInfo([]byte(""))
assert.Error(t, e) assert.Error(t, e)
} }
func TestExtractSpecInfo_ValidYAML(t *testing.T) { func TestExtractSpecInfo_ValidYAML(t *testing.T) {
r, e := ExtractSpecInfo([]byte(goodYAML)) r, e := ExtractSpecInfo([]byte(goodYAML))
assert.Greater(t, len(*r.SpecJSONBytes), 0) assert.Greater(t, len(*r.SpecJSONBytes), 0)
assert.Error(t, e) assert.Error(t, e)
} }
func TestExtractSpecInfo_InvalidYAML(t *testing.T) { func TestExtractSpecInfo_InvalidYAML(t *testing.T) {
_, e := ExtractSpecInfo([]byte(badYAML)) _, e := ExtractSpecInfo([]byte(badYAML))
assert.Error(t, e) assert.Error(t, e)
} }
func TestExtractSpecInfo_InvalidOpenAPIVersion(t *testing.T) { func TestExtractSpecInfo_InvalidOpenAPIVersion(t *testing.T) {
_, e := ExtractSpecInfo([]byte(OpenApiOne)) _, e := ExtractSpecInfo([]byte(OpenApiOne))
assert.Error(t, e) assert.Error(t, e)
} }
func TestExtractSpecInfo_OpenAPI3(t *testing.T) { func TestExtractSpecInfo_OpenAPI3(t *testing.T) {
r, e := ExtractSpecInfo([]byte(OpenApi3Spec)) r, e := ExtractSpecInfo([]byte(OpenApi3Spec))
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, utils.OpenApi3, r.SpecType) assert.Equal(t, utils.OpenApi3, r.SpecType)
assert.Equal(t, "3.0.1", r.Version) assert.Equal(t, "3.0.1", r.Version)
assert.Greater(t, len(*r.SpecJSONBytes), 0) assert.Greater(t, len(*r.SpecJSONBytes), 0)
assert.Contains(t, r.APISchema, "https://spec.openapis.org/oas/3.0/schema/2021-09-28") assert.Contains(t, r.APISchema, "https://spec.openapis.org/oas/3.0/schema/2021-09-28")
} }
func TestExtractSpecInfo_OpenAPIWat(t *testing.T) { func TestExtractSpecInfo_OpenAPIWat(t *testing.T) {
r, e := ExtractSpecInfo([]byte(OpenApiWat)) r, e := ExtractSpecInfo([]byte(OpenApiWat))
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, OpenApi3, r.SpecType) assert.Equal(t, OpenApi3, r.SpecType)
assert.Equal(t, "3.2", r.Version) assert.Equal(t, "3.2", r.Version)
} }
func TestExtractSpecInfo_OpenAPI31(t *testing.T) { func TestExtractSpecInfo_OpenAPI31(t *testing.T) {
r, e := ExtractSpecInfo([]byte(OpenApi31)) r, e := ExtractSpecInfo([]byte(OpenApi31))
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, OpenApi3, r.SpecType) assert.Equal(t, OpenApi3, r.SpecType)
assert.Equal(t, "3.1", r.Version) assert.Equal(t, "3.1", r.Version)
assert.Contains(t, r.APISchema, "https://spec.openapis.org/oas/3.1/schema/2022-10-07") assert.Contains(t, r.APISchema, "https://spec.openapis.org/oas/3.1/schema/2022-10-07")
} }
func TestExtractSpecInfo_OpenAPIFalse(t *testing.T) { func TestExtractSpecInfo_OpenAPIFalse(t *testing.T) {
spec, e := ExtractSpecInfo([]byte(OpenApiFalse)) spec, e := ExtractSpecInfo([]byte(OpenApiFalse))
assert.NoError(t, e) assert.NoError(t, e)
assert.Equal(t, "false", spec.Version) assert.Equal(t, "false", spec.Version)
} }
func TestExtractSpecInfo_OpenAPI2(t *testing.T) { func TestExtractSpecInfo_OpenAPI2(t *testing.T) {
r, e := ExtractSpecInfo([]byte(OpenApi2Spec)) r, e := ExtractSpecInfo([]byte(OpenApi2Spec))
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, OpenApi2, r.SpecType) assert.Equal(t, OpenApi2, r.SpecType)
assert.Equal(t, "2.0.1", r.Version) assert.Equal(t, "2.0.1", r.Version)
assert.Greater(t, len(*r.SpecJSONBytes), 0) assert.Greater(t, len(*r.SpecJSONBytes), 0)
assert.Contains(t, r.APISchema, "http://swagger.io/v2/schema.json#") assert.Contains(t, r.APISchema, "http://swagger.io/v2/schema.json#")
} }
func TestExtractSpecInfo_OpenAPI2_OddVersion(t *testing.T) { func TestExtractSpecInfo_OpenAPI2_OddVersion(t *testing.T) {
_, e := ExtractSpecInfo([]byte(OpenApi2SpecOdd)) _, e := ExtractSpecInfo([]byte(OpenApi2SpecOdd))
assert.NotNil(t, e) assert.NotNil(t, e)
assert.Equal(t, assert.Equal(t,
"spec is defined as a swagger (openapi 2.0) spec, but is an openapi 3 or unknown version", e.Error()) "spec is defined as a swagger (openapi 2.0) spec, but is an openapi 3 or unknown version", e.Error())
} }
func TestExtractSpecInfo_AsyncAPI(t *testing.T) { func TestExtractSpecInfo_AsyncAPI(t *testing.T) {
r, e := ExtractSpecInfo([]byte(AsyncAPISpec)) r, e := ExtractSpecInfo([]byte(AsyncAPISpec))
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, AsyncApi, r.SpecType) assert.Equal(t, AsyncApi, r.SpecType)
assert.Equal(t, "2.0.0", r.Version) assert.Equal(t, "2.0.0", r.Version)
assert.Greater(t, len(*r.SpecJSONBytes), 0) assert.Greater(t, len(*r.SpecJSONBytes), 0)
} }
func TestExtractSpecInfo_AsyncAPI_OddVersion(t *testing.T) { func TestExtractSpecInfo_AsyncAPI_OddVersion(t *testing.T) {
_, e := ExtractSpecInfo([]byte(AsyncAPISpecOdd)) _, e := ExtractSpecInfo([]byte(AsyncAPISpecOdd))
assert.NotNil(t, e) assert.NotNil(t, e)
assert.Equal(t, assert.Equal(t,
"spec is defined as asyncapi, but has a major version that is invalid", e.Error()) "spec is defined as asyncapi, but has a major version that is invalid", e.Error())
} }
func TestExtractSpecInfo_BadVersion_OpenAPI3(t *testing.T) { func TestExtractSpecInfo_BadVersion_OpenAPI3(t *testing.T) {
yml := `openapi: yml := `openapi:
should: fail` should: fail`
_, err := ExtractSpecInfo([]byte(yml)) _, err := ExtractSpecInfo([]byte(yml))
assert.Error(t, err) assert.Error(t, err)
} }
func TestExtractSpecInfo_BadVersion_Swagger(t *testing.T) { func TestExtractSpecInfo_BadVersion_Swagger(t *testing.T) {
yml := `swagger: yml := `swagger:
should: fail` should: fail`
_, err := ExtractSpecInfo([]byte(yml)) _, err := ExtractSpecInfo([]byte(yml))
assert.Error(t, err) assert.Error(t, err)
} }
func TestExtractSpecInfo_BadVersion_AsyncAPI(t *testing.T) { func TestExtractSpecInfo_BadVersion_AsyncAPI(t *testing.T) {
yml := `asyncapi: yml := `asyncapi:
should: fail` should: fail`
_, err := ExtractSpecInfo([]byte(yml)) _, err := ExtractSpecInfo([]byte(yml))
assert.Error(t, err) assert.Error(t, err)
} }
func ExampleExtractSpecInfo() { func ExampleExtractSpecInfo() {
// load bytes from openapi spec file. // load bytes from openapi spec file.
bytes, _ := ioutil.ReadFile("../test_specs/petstorev3.json") bytes, _ := ioutil.ReadFile("../test_specs/petstorev3.json")
// create a new *SpecInfo instance from loaded bytes // create a new *SpecInfo instance from loaded bytes
specInfo, err := ExtractSpecInfo(bytes) specInfo, err := ExtractSpecInfo(bytes)
if err != nil { if err != nil {
panic(fmt.Sprintf("cannot extract spec info: %e", err)) panic(fmt.Sprintf("cannot extract spec info: %e", err))
} }
// print out the version, format and filetype // print out the version, format and filetype
fmt.Printf("the version of the spec is %s, the format is %s and the file type is %s", fmt.Printf("the version of the spec is %s, the format is %s and the file type is %s",
specInfo.Version, specInfo.SpecFormat, specInfo.SpecFileType) specInfo.Version, specInfo.SpecFormat, specInfo.SpecFileType)
// Output: the version of the spec is 3.0.2, the format is oas3 and the file type is json // Output: the version of the spec is 3.0.2, the format is oas3 and the file type is json
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,22 +4,22 @@
package index package index
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
) )
func TestSpecIndex_Children(t *testing.T) { func TestSpecIndex_Children(t *testing.T) {
idx1 := new(SpecIndex) idx1 := new(SpecIndex)
idx2 := new(SpecIndex) idx2 := new(SpecIndex)
idx3 := new(SpecIndex) idx3 := new(SpecIndex)
idx4 := new(SpecIndex) idx4 := new(SpecIndex)
idx5 := new(SpecIndex) idx5 := new(SpecIndex)
idx1.AddChild(idx2) idx1.AddChild(idx2)
idx1.AddChild(idx3) idx1.AddChild(idx3)
idx3.AddChild(idx4) idx3.AddChild(idx4)
idx4.AddChild(idx5) idx4.AddChild(idx5)
assert.Equal(t, 2, len(idx1.GetChildren())) assert.Equal(t, 2, len(idx1.GetChildren()))
assert.Equal(t, 1, len(idx3.GetChildren())) assert.Equal(t, 1, len(idx3.GetChildren()))
assert.Equal(t, 1, len(idx4.GetChildren())) assert.Equal(t, 1, len(idx4.GetChildren()))
assert.Equal(t, 0, len(idx5.GetChildren())) assert.Equal(t, 0, len(idx5.GetChildren()))
} }

View File

@@ -4,88 +4,88 @@
package index package index
import ( import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"net/http" "net/http"
"strings" "strings"
"time" "time"
) )
func isHttpMethod(val string) bool { func isHttpMethod(val string) bool {
switch strings.ToLower(val) { switch strings.ToLower(val) {
case methodTypes[0]: case methodTypes[0]:
return true return true
case methodTypes[1]: case methodTypes[1]:
return true return true
case methodTypes[2]: case methodTypes[2]:
return true return true
case methodTypes[3]: case methodTypes[3]:
return true return true
case methodTypes[4]: case methodTypes[4]:
return true return true
case methodTypes[5]: case methodTypes[5]:
return true return true
case methodTypes[6]: case methodTypes[6]:
return true return true
} }
return false return false
} }
func DetermineReferenceResolveType(ref string) int { func DetermineReferenceResolveType(ref string) int {
if ref != "" && ref[0] == '#' { if ref != "" && ref[0] == '#' {
return LocalResolve return LocalResolve
} }
if ref != "" && len(ref) >= 5 && (ref[:5] == "https" || ref[:5] == "http:") { if ref != "" && len(ref) >= 5 && (ref[:5] == "https" || ref[:5] == "http:") {
return HttpResolve return HttpResolve
} }
if strings.Contains(ref, ".json") || if strings.Contains(ref, ".json") ||
strings.Contains(ref, ".yaml") || strings.Contains(ref, ".yaml") ||
strings.Contains(ref, ".yml") { strings.Contains(ref, ".yml") {
return FileResolve return FileResolve
} }
return -1 return -1
} }
func boostrapIndexCollections(rootNode *yaml.Node, index *SpecIndex) { func boostrapIndexCollections(rootNode *yaml.Node, index *SpecIndex) {
index.root = rootNode index.root = rootNode
index.allRefs = make(map[string]*Reference) index.allRefs = make(map[string]*Reference)
index.allMappedRefs = make(map[string]*Reference) index.allMappedRefs = make(map[string]*Reference)
index.refsByLine = make(map[string]map[int]bool) index.refsByLine = make(map[string]map[int]bool)
index.linesWithRefs = make(map[int]bool) index.linesWithRefs = make(map[int]bool)
index.pathRefs = make(map[string]map[string]*Reference) index.pathRefs = make(map[string]map[string]*Reference)
index.paramOpRefs = make(map[string]map[string]map[string][]*Reference) index.paramOpRefs = make(map[string]map[string]map[string][]*Reference)
index.operationTagsRefs = make(map[string]map[string][]*Reference) index.operationTagsRefs = make(map[string]map[string][]*Reference)
index.operationDescriptionRefs = make(map[string]map[string]*Reference) index.operationDescriptionRefs = make(map[string]map[string]*Reference)
index.operationSummaryRefs = make(map[string]map[string]*Reference) index.operationSummaryRefs = make(map[string]map[string]*Reference)
index.paramCompRefs = make(map[string]*Reference) index.paramCompRefs = make(map[string]*Reference)
index.paramAllRefs = make(map[string]*Reference) index.paramAllRefs = make(map[string]*Reference)
index.paramInlineDuplicateNames = make(map[string][]*Reference) index.paramInlineDuplicateNames = make(map[string][]*Reference)
index.globalTagRefs = make(map[string]*Reference) index.globalTagRefs = make(map[string]*Reference)
index.securitySchemeRefs = make(map[string]*Reference) index.securitySchemeRefs = make(map[string]*Reference)
index.requestBodiesRefs = make(map[string]*Reference) index.requestBodiesRefs = make(map[string]*Reference)
index.responsesRefs = make(map[string]*Reference) index.responsesRefs = make(map[string]*Reference)
index.headersRefs = make(map[string]*Reference) index.headersRefs = make(map[string]*Reference)
index.examplesRefs = make(map[string]*Reference) index.examplesRefs = make(map[string]*Reference)
index.callbacksRefs = make(map[string]map[string][]*Reference) index.callbacksRefs = make(map[string]map[string][]*Reference)
index.linksRefs = make(map[string]map[string][]*Reference) index.linksRefs = make(map[string]map[string][]*Reference)
index.callbackRefs = make(map[string]*Reference) index.callbackRefs = make(map[string]*Reference)
index.externalSpecIndex = make(map[string]*SpecIndex) index.externalSpecIndex = make(map[string]*SpecIndex)
index.allComponentSchemaDefinitions = make(map[string]*Reference) index.allComponentSchemaDefinitions = make(map[string]*Reference)
index.allParameters = make(map[string]*Reference) index.allParameters = make(map[string]*Reference)
index.allSecuritySchemes = make(map[string]*Reference) index.allSecuritySchemes = make(map[string]*Reference)
index.allRequestBodies = make(map[string]*Reference) index.allRequestBodies = make(map[string]*Reference)
index.allResponses = make(map[string]*Reference) index.allResponses = make(map[string]*Reference)
index.allHeaders = make(map[string]*Reference) index.allHeaders = make(map[string]*Reference)
index.allExamples = make(map[string]*Reference) index.allExamples = make(map[string]*Reference)
index.allLinks = make(map[string]*Reference) index.allLinks = make(map[string]*Reference)
index.allCallbacks = make(map[string]*Reference) index.allCallbacks = make(map[string]*Reference)
index.allExternalDocuments = make(map[string]*Reference) index.allExternalDocuments = make(map[string]*Reference)
index.securityRequirementRefs = make(map[string]map[string][]*Reference) index.securityRequirementRefs = make(map[string]map[string][]*Reference)
index.polymorphicRefs = make(map[string]*Reference) index.polymorphicRefs = make(map[string]*Reference)
index.refsWithSiblings = make(map[string]Reference) index.refsWithSiblings = make(map[string]Reference)
index.seenRemoteSources = make(map[string]*yaml.Node) index.seenRemoteSources = make(map[string]*yaml.Node)
index.seenLocalSources = make(map[string]*yaml.Node) index.seenLocalSources = make(map[string]*yaml.Node)
index.opServersRefs = make(map[string]map[string][]*Reference) index.opServersRefs = make(map[string]map[string][]*Reference)
index.httpClient = &http.Client{Timeout: time.Duration(5) * time.Second} index.httpClient = &http.Client{Timeout: time.Duration(5) * time.Second}
index.componentIndexChan = make(chan bool) index.componentIndexChan = make(chan bool)
index.polyComponentIndexChan = make(chan bool) index.polyComponentIndexChan = make(chan bool)
} }

View File

@@ -4,447 +4,445 @@
package index package index
import ( import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"net/url" "net/url"
"strings" "strings"
"sync" "sync"
) )
func (index *SpecIndex) extractDefinitionsAndSchemas(schemasNode *yaml.Node, pathPrefix string) { func (index *SpecIndex) extractDefinitionsAndSchemas(schemasNode *yaml.Node, pathPrefix string) {
var name string var name string
for i, schema := range schemasNode.Content { for i, schema := range schemasNode.Content {
if i%2 == 0 { if i%2 == 0 {
name = schema.Value name = schema.Value
continue continue
} }
def := fmt.Sprintf("%s%s", pathPrefix, name) def := fmt.Sprintf("%s%s", pathPrefix, name)
ref := &Reference{ ref := &Reference{
Definition: def, Definition: def,
Name: name, Name: name,
Node: schema, Node: schema,
Path: fmt.Sprintf("$.components.schemas.%s", name), Path: fmt.Sprintf("$.components.schemas.%s", name),
ParentNode: schemasNode, ParentNode: schemasNode,
RequiredRefProperties: index.extractDefinitionRequiredRefProperties(schemasNode, map[string][]string{}), RequiredRefProperties: index.extractDefinitionRequiredRefProperties(schemasNode, map[string][]string{}),
} }
index.allComponentSchemaDefinitions[def] = ref index.allComponentSchemaDefinitions[def] = ref
} }
} }
// extractDefinitionRequiredRefProperties goes through the direct properties of a schema and extracts the map of required definitions from within it // extractDefinitionRequiredRefProperties goes through the direct properties of a schema and extracts the map of required definitions from within it
func (index *SpecIndex) extractDefinitionRequiredRefProperties(schemaNode *yaml.Node, reqRefProps map[string][]string) map[string][]string { func (index *SpecIndex) extractDefinitionRequiredRefProperties(schemaNode *yaml.Node, reqRefProps map[string][]string) map[string][]string {
if schemaNode == nil { if schemaNode == nil {
return reqRefProps return reqRefProps
} }
// If the node we're looking at is a direct ref to another model without any properties, mark it as required, but still continue to look for required properties // If the node we're looking at is a direct ref to another model without any properties, mark it as required, but still continue to look for required properties
isRef, _, defPath := utils.IsNodeRefValue(schemaNode) isRef, _, defPath := utils.IsNodeRefValue(schemaNode)
if isRef { if isRef {
if _, ok := reqRefProps[defPath]; !ok { if _, ok := reqRefProps[defPath]; !ok {
reqRefProps[defPath] = []string{} reqRefProps[defPath] = []string{}
} }
} }
// Check for a required parameters list, and return if none exists, as any properties will be optional // Check for a required parameters list, and return if none exists, as any properties will be optional
_, requiredSeqNode := utils.FindKeyNodeTop("required", schemaNode.Content) _, requiredSeqNode := utils.FindKeyNodeTop("required", schemaNode.Content)
if requiredSeqNode == nil { if requiredSeqNode == nil {
return reqRefProps return reqRefProps
} }
_, propertiesMapNode := utils.FindKeyNodeTop("properties", schemaNode.Content) _, propertiesMapNode := utils.FindKeyNodeTop("properties", schemaNode.Content)
if propertiesMapNode == nil { if propertiesMapNode == nil {
// TODO: Log a warning on the resolver, because if you have required properties, but no actual properties, something is wrong // TODO: Log a warning on the resolver, because if you have required properties, but no actual properties, something is wrong
return reqRefProps return reqRefProps
} }
name := "" name := ""
for i, param := range propertiesMapNode.Content { for i, param := range propertiesMapNode.Content {
if i%2 == 0 { if i%2 == 0 {
name = param.Value name = param.Value
continue continue
} }
// Check to see if the current property is directly embedded within the current schema, and handle its properties if so // Check to see if the current property is directly embedded within the current schema, and handle its properties if so
_, paramPropertiesMapNode := utils.FindKeyNodeTop("properties", param.Content) _, paramPropertiesMapNode := utils.FindKeyNodeTop("properties", param.Content)
if paramPropertiesMapNode != nil { if paramPropertiesMapNode != nil {
reqRefProps = index.extractDefinitionRequiredRefProperties(param, reqRefProps) reqRefProps = index.extractDefinitionRequiredRefProperties(param, reqRefProps)
} }
// Check to see if the current property is polymorphic, and dive into that model if so // Check to see if the current property is polymorphic, and dive into that model if so
for _, key := range []string{"allOf", "oneOf", "anyOf"} { for _, key := range []string{"allOf", "oneOf", "anyOf"} {
_, ofNode := utils.FindKeyNodeTop(key, param.Content) _, ofNode := utils.FindKeyNodeTop(key, param.Content)
if ofNode != nil { if ofNode != nil {
for _, ofNodeItem := range ofNode.Content { for _, ofNodeItem := range ofNode.Content {
reqRefProps = index.extractRequiredReferenceProperties(ofNodeItem, name, reqRefProps) reqRefProps = index.extractRequiredReferenceProperties(ofNodeItem, name, reqRefProps)
} }
} }
} }
} }
// Run through each of the required properties and extract _their_ required references // Run through each of the required properties and extract _their_ required references
for _, requiredPropertyNode := range requiredSeqNode.Content { for _, requiredPropertyNode := range requiredSeqNode.Content {
_, requiredPropDefNode := utils.FindKeyNodeTop(requiredPropertyNode.Value, propertiesMapNode.Content) _, requiredPropDefNode := utils.FindKeyNodeTop(requiredPropertyNode.Value, propertiesMapNode.Content)
if requiredPropDefNode == nil { if requiredPropDefNode == nil {
continue continue
} }
reqRefProps = index.extractRequiredReferenceProperties(requiredPropDefNode, requiredPropertyNode.Value, reqRefProps) reqRefProps = index.extractRequiredReferenceProperties(requiredPropDefNode, requiredPropertyNode.Value, reqRefProps)
} }
return reqRefProps return reqRefProps
} }
// extractRequiredReferenceProperties returns a map of definition names to the property or properties which reference it within a node // extractRequiredReferenceProperties returns a map of definition names to the property or properties which reference it within a node
func (index *SpecIndex) extractRequiredReferenceProperties(requiredPropDefNode *yaml.Node, propName string, reqRefProps map[string][]string) map[string][]string { func (index *SpecIndex) extractRequiredReferenceProperties(requiredPropDefNode *yaml.Node, propName string, reqRefProps map[string][]string) map[string][]string {
isRef, _, defPath := utils.IsNodeRefValue(requiredPropDefNode) isRef, _, defPath := utils.IsNodeRefValue(requiredPropDefNode)
if !isRef { if !isRef {
_, defItems := utils.FindKeyNodeTop("items", requiredPropDefNode.Content) _, defItems := utils.FindKeyNodeTop("items", requiredPropDefNode.Content)
if defItems != nil { if defItems != nil {
isRef, _, defPath = utils.IsNodeRefValue(defItems) isRef, _, defPath = utils.IsNodeRefValue(defItems)
} }
} }
if /* still */ !isRef { if /* still */ !isRef {
return reqRefProps return reqRefProps
} }
if _, ok := reqRefProps[defPath]; !ok { if _, ok := reqRefProps[defPath]; !ok {
reqRefProps[defPath] = []string{} reqRefProps[defPath] = []string{}
} }
reqRefProps[defPath] = append(reqRefProps[defPath], propName) reqRefProps[defPath] = append(reqRefProps[defPath], propName)
return reqRefProps return reqRefProps
} }
func (index *SpecIndex) extractComponentParameters(paramsNode *yaml.Node, pathPrefix string) { func (index *SpecIndex) extractComponentParameters(paramsNode *yaml.Node, pathPrefix string) {
var name string var name string
for i, param := range paramsNode.Content { for i, param := range paramsNode.Content {
if i%2 == 0 { if i%2 == 0 {
name = param.Value name = param.Value
continue continue
} }
def := fmt.Sprintf("%s%s", pathPrefix, name) def := fmt.Sprintf("%s%s", pathPrefix, name)
ref := &Reference{ ref := &Reference{
Definition: def, Definition: def,
Name: name, Name: name,
Node: param, Node: param,
} }
index.allParameters[def] = ref index.allParameters[def] = ref
} }
} }
func (index *SpecIndex) extractComponentRequestBodies(requestBodiesNode *yaml.Node, pathPrefix string) { func (index *SpecIndex) extractComponentRequestBodies(requestBodiesNode *yaml.Node, pathPrefix string) {
var name string var name string
for i, reqBod := range requestBodiesNode.Content { for i, reqBod := range requestBodiesNode.Content {
if i%2 == 0 { if i%2 == 0 {
name = reqBod.Value name = reqBod.Value
continue continue
} }
def := fmt.Sprintf("%s%s", pathPrefix, name) def := fmt.Sprintf("%s%s", pathPrefix, name)
ref := &Reference{ ref := &Reference{
Definition: def, Definition: def,
Name: name, Name: name,
Node: reqBod, Node: reqBod,
} }
index.allRequestBodies[def] = ref index.allRequestBodies[def] = ref
} }
} }
func (index *SpecIndex) extractComponentResponses(responsesNode *yaml.Node, pathPrefix string) { func (index *SpecIndex) extractComponentResponses(responsesNode *yaml.Node, pathPrefix string) {
var name string var name string
for i, response := range responsesNode.Content { for i, response := range responsesNode.Content {
if i%2 == 0 { if i%2 == 0 {
name = response.Value name = response.Value
continue continue
} }
def := fmt.Sprintf("%s%s", pathPrefix, name) def := fmt.Sprintf("%s%s", pathPrefix, name)
ref := &Reference{ ref := &Reference{
Definition: def, Definition: def,
Name: name, Name: name,
Node: response, Node: response,
} }
index.allResponses[def] = ref index.allResponses[def] = ref
} }
} }
func (index *SpecIndex) extractComponentHeaders(headersNode *yaml.Node, pathPrefix string) { func (index *SpecIndex) extractComponentHeaders(headersNode *yaml.Node, pathPrefix string) {
var name string var name string
for i, header := range headersNode.Content { for i, header := range headersNode.Content {
if i%2 == 0 { if i%2 == 0 {
name = header.Value name = header.Value
continue continue
} }
def := fmt.Sprintf("%s%s", pathPrefix, name) def := fmt.Sprintf("%s%s", pathPrefix, name)
ref := &Reference{ ref := &Reference{
Definition: def, Definition: def,
Name: name, Name: name,
Node: header, Node: header,
} }
index.allHeaders[def] = ref index.allHeaders[def] = ref
} }
} }
func (index *SpecIndex) extractComponentCallbacks(callbacksNode *yaml.Node, pathPrefix string) { func (index *SpecIndex) extractComponentCallbacks(callbacksNode *yaml.Node, pathPrefix string) {
var name string var name string
for i, callback := range callbacksNode.Content { for i, callback := range callbacksNode.Content {
if i%2 == 0 { if i%2 == 0 {
name = callback.Value name = callback.Value
continue continue
} }
def := fmt.Sprintf("%s%s", pathPrefix, name) def := fmt.Sprintf("%s%s", pathPrefix, name)
ref := &Reference{ ref := &Reference{
Definition: def, Definition: def,
Name: name, Name: name,
Node: callback, Node: callback,
} }
index.allCallbacks[def] = ref index.allCallbacks[def] = ref
} }
} }
func (index *SpecIndex) extractComponentLinks(linksNode *yaml.Node, pathPrefix string) { func (index *SpecIndex) extractComponentLinks(linksNode *yaml.Node, pathPrefix string) {
var name string var name string
for i, link := range linksNode.Content { for i, link := range linksNode.Content {
if i%2 == 0 { if i%2 == 0 {
name = link.Value name = link.Value
continue continue
} }
def := fmt.Sprintf("%s%s", pathPrefix, name) def := fmt.Sprintf("%s%s", pathPrefix, name)
ref := &Reference{ ref := &Reference{
Definition: def, Definition: def,
Name: name, Name: name,
Node: link, Node: link,
} }
index.allLinks[def] = ref index.allLinks[def] = ref
} }
} }
func (index *SpecIndex) extractComponentExamples(examplesNode *yaml.Node, pathPrefix string) { func (index *SpecIndex) extractComponentExamples(examplesNode *yaml.Node, pathPrefix string) {
var name string var name string
for i, example := range examplesNode.Content { for i, example := range examplesNode.Content {
if i%2 == 0 { if i%2 == 0 {
name = example.Value name = example.Value
continue continue
} }
def := fmt.Sprintf("%s%s", pathPrefix, name) def := fmt.Sprintf("%s%s", pathPrefix, name)
ref := &Reference{ ref := &Reference{
Definition: def, Definition: def,
Name: name, Name: name,
Node: example, Node: example,
} }
index.allExamples[def] = ref index.allExamples[def] = ref
} }
} }
func (index *SpecIndex) extractComponentSecuritySchemes(securitySchemesNode *yaml.Node, pathPrefix string) { func (index *SpecIndex) extractComponentSecuritySchemes(securitySchemesNode *yaml.Node, pathPrefix string) {
var name string var name string
for i, secScheme := range securitySchemesNode.Content { for i, secScheme := range securitySchemesNode.Content {
if i%2 == 0 { if i%2 == 0 {
name = secScheme.Value name = secScheme.Value
continue continue
} }
def := fmt.Sprintf("%s%s", pathPrefix, name) def := fmt.Sprintf("%s%s", pathPrefix, name)
ref := &Reference{ ref := &Reference{
Definition: def, Definition: def,
Name: name, Name: name,
Node: secScheme, Node: secScheme,
ParentNode: securitySchemesNode, ParentNode: securitySchemesNode,
Path: fmt.Sprintf("$.components.securitySchemes.%s", name), Path: fmt.Sprintf("$.components.securitySchemes.%s", name),
} }
index.allSecuritySchemes[def] = ref index.allSecuritySchemes[def] = ref
} }
} }
func (index *SpecIndex) countUniqueInlineDuplicates() int { func (index *SpecIndex) countUniqueInlineDuplicates() int {
if index.componentsInlineParamUniqueCount > 0 { if index.componentsInlineParamUniqueCount > 0 {
return index.componentsInlineParamUniqueCount return index.componentsInlineParamUniqueCount
} }
unique := 0 unique := 0
for _, p := range index.paramInlineDuplicateNames { for _, p := range index.paramInlineDuplicateNames {
if len(p) == 1 { if len(p) == 1 {
unique++ unique++
} }
} }
index.componentsInlineParamUniqueCount = unique index.componentsInlineParamUniqueCount = unique
return unique return unique
} }
func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *yaml.Node, method string) { func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *yaml.Node, method string) {
for i, param := range params { for i, param := range params {
// param is ref // param is ref
if len(param.Content) > 0 && param.Content[0].Value == "$ref" { if len(param.Content) > 0 && param.Content[0].Value == "$ref" {
paramRefName := param.Content[1].Value paramRefName := param.Content[1].Value
paramRef := index.allMappedRefs[paramRefName] paramRef := index.allMappedRefs[paramRefName]
if index.paramOpRefs[pathItemNode.Value] == nil { if index.paramOpRefs[pathItemNode.Value] == nil {
index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string][]*Reference) index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string][]*Reference)
index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference) index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference)
} }
// if we know the path, but it's a new method // if we know the path, but it's a new method
if index.paramOpRefs[pathItemNode.Value][method] == nil { if index.paramOpRefs[pathItemNode.Value][method] == nil {
index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference) index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference)
} }
// if this is a duplicate, add an error and ignore it // if this is a duplicate, add an error and ignore it
if index.paramOpRefs[pathItemNode.Value][method][paramRefName] != nil { if index.paramOpRefs[pathItemNode.Value][method][paramRefName] != nil {
path := fmt.Sprintf("$.paths.%s.%s.parameters[%d]", pathItemNode.Value, method, i) path := fmt.Sprintf("$.paths.%s.%s.parameters[%d]", pathItemNode.Value, method, i)
if method == "top" { if method == "top" {
path = fmt.Sprintf("$.paths.%s.parameters[%d]", pathItemNode.Value, i) path = fmt.Sprintf("$.paths.%s.parameters[%d]", pathItemNode.Value, i)
} }
index.operationParamErrors = append(index.operationParamErrors, &IndexingError{ index.operationParamErrors = append(index.operationParamErrors, &IndexingError{
Err: fmt.Errorf("the `%s` operation parameter at path `%s`, "+ Err: fmt.Errorf("the `%s` operation parameter at path `%s`, "+
"index %d has a duplicate ref `%s`", method, pathItemNode.Value, i, paramRefName), "index %d has a duplicate ref `%s`", method, pathItemNode.Value, i, paramRefName),
Node: param, Node: param,
Path: path, Path: path,
}) })
} else { } else {
if paramRef != nil { if paramRef != nil {
index.paramOpRefs[pathItemNode.Value][method][paramRefName] = index.paramOpRefs[pathItemNode.Value][method][paramRefName] =
append(index.paramOpRefs[pathItemNode.Value][method][paramRefName], paramRef) append(index.paramOpRefs[pathItemNode.Value][method][paramRefName], paramRef)
} }
} }
continue continue
} else { } else {
// param is inline. // param is inline.
_, vn := utils.FindKeyNode("name", param.Content) _, vn := utils.FindKeyNode("name", param.Content)
path := fmt.Sprintf("$.paths.%s.%s.parameters[%d]", pathItemNode.Value, method, i) path := fmt.Sprintf("$.paths.%s.%s.parameters[%d]", pathItemNode.Value, method, i)
if method == "top" { if method == "top" {
path = fmt.Sprintf("$.paths.%s.parameters[%d]", pathItemNode.Value, i) path = fmt.Sprintf("$.paths.%s.parameters[%d]", pathItemNode.Value, i)
} }
if vn == nil { if vn == nil {
index.operationParamErrors = append(index.operationParamErrors, &IndexingError{ index.operationParamErrors = append(index.operationParamErrors, &IndexingError{
Err: fmt.Errorf("the '%s' operation parameter at path '%s', index %d has no 'name' value", Err: fmt.Errorf("the '%s' operation parameter at path '%s', index %d has no 'name' value",
method, pathItemNode.Value, i), method, pathItemNode.Value, i),
Node: param, Node: param,
Path: path, Path: path,
}) })
continue continue
} }
ref := &Reference{ ref := &Reference{
Definition: vn.Value, Definition: vn.Value,
Name: vn.Value, Name: vn.Value,
Node: param, Node: param,
Path: path, Path: path,
} }
if index.paramOpRefs[pathItemNode.Value] == nil { if index.paramOpRefs[pathItemNode.Value] == nil {
index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string][]*Reference) index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string][]*Reference)
index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference) index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference)
} }
// if we know the path but this is a new method. // if we know the path but this is a new method.
if index.paramOpRefs[pathItemNode.Value][method] == nil { if index.paramOpRefs[pathItemNode.Value][method] == nil {
index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference) index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*Reference)
} }
// if this is a duplicate name, check if the `in` type is also the same, if so, it's a duplicate. // if this is a duplicate name, check if the `in` type is also the same, if so, it's a duplicate.
if len(index.paramOpRefs[pathItemNode.Value][method][ref.Name]) > 0 { if len(index.paramOpRefs[pathItemNode.Value][method][ref.Name]) > 0 {
currentNode := ref.Node currentNode := ref.Node
checkNodes := index.paramOpRefs[pathItemNode.Value][method][ref.Name] checkNodes := index.paramOpRefs[pathItemNode.Value][method][ref.Name]
_, currentIn := utils.FindKeyNodeTop("in", currentNode.Content) _, currentIn := utils.FindKeyNodeTop("in", currentNode.Content)
for _, checkNode := range checkNodes { for _, checkNode := range checkNodes {
_, checkIn := utils.FindKeyNodeTop("in", checkNode.Node.Content) _, checkIn := utils.FindKeyNodeTop("in", checkNode.Node.Content)
if currentIn != nil && checkIn != nil && currentIn.Value == checkIn.Value { if currentIn != nil && checkIn != nil && currentIn.Value == checkIn.Value {
path := fmt.Sprintf("$.paths.%s.%s.parameters[%d]", pathItemNode.Value, method, i) path := fmt.Sprintf("$.paths.%s.%s.parameters[%d]", pathItemNode.Value, method, i)
if method == "top" { if method == "top" {
path = fmt.Sprintf("$.paths.%s.parameters[%d]", pathItemNode.Value, i) path = fmt.Sprintf("$.paths.%s.parameters[%d]", pathItemNode.Value, i)
} }
index.operationParamErrors = append(index.operationParamErrors, &IndexingError{ index.operationParamErrors = append(index.operationParamErrors, &IndexingError{
Err: fmt.Errorf("the `%s` operation parameter at path `%s`, "+ Err: fmt.Errorf("the `%s` operation parameter at path `%s`, "+
"index %d has a duplicate name `%s` and `in` type", method, pathItemNode.Value, i, vn.Value), "index %d has a duplicate name `%s` and `in` type", method, pathItemNode.Value, i, vn.Value),
Node: param, Node: param,
Path: path, Path: path,
}) })
} else { } else {
index.paramOpRefs[pathItemNode.Value][method][ref.Name] = index.paramOpRefs[pathItemNode.Value][method][ref.Name] =
append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref) append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
} }
} }
} else { } else {
index.paramOpRefs[pathItemNode.Value][method][ref.Name] = index.paramOpRefs[pathItemNode.Value][method][ref.Name] =
append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref) append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
} }
continue continue
} }
} }
} }
func runIndexFunction(funcs []func() int, wg *sync.WaitGroup) { func runIndexFunction(funcs []func() int, wg *sync.WaitGroup) {
for _, cFunc := range funcs { for _, cFunc := range funcs {
go func(wg *sync.WaitGroup, cf func() int) { go func(wg *sync.WaitGroup, cf func() int) {
cf() cf()
wg.Done() wg.Done()
}(wg, cFunc) }(wg, cFunc)
} }
} }
func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bool) string { func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bool) string {
cleanedPath := baseURL.Path // not cleaned yet! cleanedPath := baseURL.Path // not cleaned yet!
// create a slice of path segments from existing path // create a slice of path segments from existing path
pathSegs := strings.Split(cleanedPath, "/") pathSegs := strings.Split(cleanedPath, "/")
dirSegs := strings.Split(dir, "/") dirSegs := strings.Split(dir, "/")
var cleanedSegs []string var cleanedSegs []string
if !includeFile { if !includeFile {
dirSegs = dirSegs[:len(dirSegs)-1] dirSegs = dirSegs[:len(dirSegs)-1]
} }
// relative paths are a pain in the ass, damn you digital ocean, use a single spec, and break them
// down into services, please don't blast apart specs into a billion shards.
if strings.Contains(dir, "../") {
for s := range dirSegs {
if dirSegs[s] == ".." {
// chop off the last segment of the base path.
if len(pathSegs) > 0 {
pathSegs = pathSegs[:len(pathSegs)-1]
}
} else {
cleanedSegs = append(cleanedSegs, dirSegs[s])
}
}
cleanedPath = fmt.Sprintf("%s/%s", strings.Join(pathSegs, "/"), strings.Join(cleanedSegs, "/"))
} else {
if !strings.HasPrefix(dir, "http") {
if len(pathSegs) > 1 || len(dirSegs) > 1 {
cleanedPath = fmt.Sprintf("%s/%s", strings.Join(pathSegs, "/"), strings.Join(dirSegs, "/"))
}
} else {
cleanedPath = strings.Join(dirSegs, "/")
}
}
var p string
if baseURL.Scheme != "" && !strings.HasPrefix(dir, "http") {
p = fmt.Sprintf("%s://%s%s", baseURL.Scheme, baseURL.Host, cleanedPath)
} else {
if !strings.Contains(cleanedPath, "/") {
p = ""
} else {
p = cleanedPath
}
} // relative paths are a pain in the ass, damn you digital ocean, use a single spec, and break them
if strings.HasSuffix(p, "/") { // down into services, please don't blast apart specs into a billion shards.
p = p[:len(p)-1] if strings.Contains(dir, "../") {
} for s := range dirSegs {
return p if dirSegs[s] == ".." {
// chop off the last segment of the base path.
if len(pathSegs) > 0 {
pathSegs = pathSegs[:len(pathSegs)-1]
}
} else {
cleanedSegs = append(cleanedSegs, dirSegs[s])
}
}
cleanedPath = fmt.Sprintf("%s/%s", strings.Join(pathSegs, "/"), strings.Join(cleanedSegs, "/"))
} else {
if !strings.HasPrefix(dir, "http") {
if len(pathSegs) > 1 || len(dirSegs) > 1 {
cleanedPath = fmt.Sprintf("%s/%s", strings.Join(pathSegs, "/"), strings.Join(dirSegs, "/"))
}
} else {
cleanedPath = strings.Join(dirSegs, "/")
}
}
var p string
if baseURL.Scheme != "" && !strings.HasPrefix(dir, "http") {
p = fmt.Sprintf("%s://%s%s", baseURL.Scheme, baseURL.Host, cleanedPath)
} else {
if !strings.Contains(cleanedPath, "/") {
p = ""
} else {
p = cleanedPath
}
}
if strings.HasSuffix(p, "/") {
p = p[:len(p)-1]
}
return p
} }

View File

@@ -4,52 +4,52 @@
package index package index
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"net/url" "net/url"
"testing" "testing"
) )
func TestGenerateCleanSpecConfigBaseURL(t *testing.T) { func TestGenerateCleanSpecConfigBaseURL(t *testing.T) {
u, _ := url.Parse("https://pb33f.io/things/stuff") u, _ := url.Parse("https://pb33f.io/things/stuff")
path := "." path := "."
assert.Equal(t, "https://pb33f.io/things/stuff", assert.Equal(t, "https://pb33f.io/things/stuff",
GenerateCleanSpecConfigBaseURL(u, path, false)) GenerateCleanSpecConfigBaseURL(u, path, false))
} }
func TestGenerateCleanSpecConfigBaseURL_RelativeDeep(t *testing.T) { func TestGenerateCleanSpecConfigBaseURL_RelativeDeep(t *testing.T) {
u, _ := url.Parse("https://pb33f.io/things/stuff/jazz/cakes/winter/oil") u, _ := url.Parse("https://pb33f.io/things/stuff/jazz/cakes/winter/oil")
path := "../../../../foo/bar/baz/crap.yaml#thang" path := "../../../../foo/bar/baz/crap.yaml#thang"
assert.Equal(t, "https://pb33f.io/things/stuff/foo/bar/baz", assert.Equal(t, "https://pb33f.io/things/stuff/foo/bar/baz",
GenerateCleanSpecConfigBaseURL(u, path, false)) GenerateCleanSpecConfigBaseURL(u, path, false))
assert.Equal(t, "https://pb33f.io/things/stuff/foo/bar/baz/crap.yaml#thang", assert.Equal(t, "https://pb33f.io/things/stuff/foo/bar/baz/crap.yaml#thang",
GenerateCleanSpecConfigBaseURL(u, path, true)) GenerateCleanSpecConfigBaseURL(u, path, true))
} }
func TestGenerateCleanSpecConfigBaseURL_NoBaseURL(t *testing.T) { func TestGenerateCleanSpecConfigBaseURL_NoBaseURL(t *testing.T) {
u, _ := url.Parse("/things/stuff/jazz/cakes/winter/oil") u, _ := url.Parse("/things/stuff/jazz/cakes/winter/oil")
path := "../../../../foo/bar/baz/crap.yaml#thang" path := "../../../../foo/bar/baz/crap.yaml#thang"
assert.Equal(t, "/things/stuff/foo/bar/baz", assert.Equal(t, "/things/stuff/foo/bar/baz",
GenerateCleanSpecConfigBaseURL(u, path, false)) GenerateCleanSpecConfigBaseURL(u, path, false))
assert.Equal(t, "/things/stuff/foo/bar/baz/crap.yaml#thang", assert.Equal(t, "/things/stuff/foo/bar/baz/crap.yaml#thang",
GenerateCleanSpecConfigBaseURL(u, path, true)) GenerateCleanSpecConfigBaseURL(u, path, true))
} }
func TestGenerateCleanSpecConfigBaseURL_HttpStrip(t *testing.T) { func TestGenerateCleanSpecConfigBaseURL_HttpStrip(t *testing.T) {
u, _ := url.Parse(".") u, _ := url.Parse(".")
path := "http://thing.com/crap.yaml#thang" path := "http://thing.com/crap.yaml#thang"
assert.Equal(t, "http://thing.com", assert.Equal(t, "http://thing.com",
GenerateCleanSpecConfigBaseURL(u, path, false)) GenerateCleanSpecConfigBaseURL(u, path, false))
assert.Equal(t, "", assert.Equal(t, "",
GenerateCleanSpecConfigBaseURL(u, "crap.yaml#thing", true)) GenerateCleanSpecConfigBaseURL(u, "crap.yaml#thing", true))
} }
func TestSpecIndex_extractDefinitionRequiredRefProperties(t *testing.T) { func TestSpecIndex_extractDefinitionRequiredRefProperties(t *testing.T) {
c := CreateOpenAPIIndexConfig() c := CreateOpenAPIIndexConfig()
idx := NewSpecIndexWithConfig(nil, c) idx := NewSpecIndexWithConfig(nil, c)
assert.Nil(t, idx.extractDefinitionRequiredRefProperties(nil, nil)) assert.Nil(t, idx.extractDefinitionRequiredRefProperties(nil, nil))
} }

View File

@@ -1,170 +1,170 @@
package resolver package resolver
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"testing" "testing"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func TestNewResolver(t *testing.T) { func TestNewResolver(t *testing.T) {
assert.Nil(t, NewResolver(nil)) assert.Nil(t, NewResolver(nil))
} }
func Benchmark_ResolveDocumentStripe(b *testing.B) { func Benchmark_ResolveDocumentStripe(b *testing.B) {
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml") stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(stripe, &rootNode) yaml.Unmarshal(stripe, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
resolver.Resolve() resolver.Resolve()
} }
} }
func TestResolver_ResolveComponents_CircularSpec(t *testing.T) { func TestResolver_ResolveComponents_CircularSpec(t *testing.T) {
circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml") circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(circular, &rootNode) yaml.Unmarshal(circular, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 3) assert.Len(t, circ, 3)
_, err := yaml.Marshal(resolver.resolvedRoot) _, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestResolver_CheckForCircularReferences(t *testing.T) { func TestResolver_CheckForCircularReferences(t *testing.T) {
circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml") circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(circular, &rootNode) yaml.Unmarshal(circular, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences() circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 3) assert.Len(t, circ, 3)
assert.Len(t, resolver.GetResolvingErrors(), 3) assert.Len(t, resolver.GetResolvingErrors(), 3)
assert.Len(t, resolver.GetCircularErrors(), 3) assert.Len(t, resolver.GetCircularErrors(), 3)
_, err := yaml.Marshal(resolver.resolvedRoot) _, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestResolver_CheckForCircularReferences_DigitalOcean(t *testing.T) { func TestResolver_CheckForCircularReferences_DigitalOcean(t *testing.T) {
circular, _ := ioutil.ReadFile("../test_specs/digitalocean.yaml") circular, _ := ioutil.ReadFile("../test_specs/digitalocean.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(circular, &rootNode) yaml.Unmarshal(circular, &rootNode)
baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification") baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification")
index := index.NewSpecIndexWithConfig(&rootNode, &index.SpecIndexConfig{ index := index.NewSpecIndexWithConfig(&rootNode, &index.SpecIndexConfig{
AllowRemoteLookup: true, AllowRemoteLookup: true,
AllowFileLookup: true, AllowFileLookup: true,
BaseURL: baseURL, BaseURL: baseURL,
}) })
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences() circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0) assert.Len(t, circ, 0)
assert.Len(t, resolver.GetResolvingErrors(), 0) assert.Len(t, resolver.GetResolvingErrors(), 0)
assert.Len(t, resolver.GetCircularErrors(), 0) assert.Len(t, resolver.GetCircularErrors(), 0)
_, err := yaml.Marshal(resolver.resolvedRoot) _, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestResolver_CircularReferencesRequiredValid(t *testing.T) { func TestResolver_CircularReferencesRequiredValid(t *testing.T) {
circular, _ := ioutil.ReadFile("../test_specs/swagger-valid-recursive-model.yaml") circular, _ := ioutil.ReadFile("../test_specs/swagger-valid-recursive-model.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(circular, &rootNode) yaml.Unmarshal(circular, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences() circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0) assert.Len(t, circ, 0)
_, err := yaml.Marshal(resolver.resolvedRoot) _, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestResolver_CircularReferencesRequiredInvalid(t *testing.T) { func TestResolver_CircularReferencesRequiredInvalid(t *testing.T) {
circular, _ := ioutil.ReadFile("../test_specs/swagger-invalid-recursive-model.yaml") circular, _ := ioutil.ReadFile("../test_specs/swagger-invalid-recursive-model.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(circular, &rootNode) yaml.Unmarshal(circular, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences() circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 2) assert.Len(t, circ, 2)
_, err := yaml.Marshal(resolver.resolvedRoot) _, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestResolver_DeepJourney(t *testing.T) { func TestResolver_DeepJourney(t *testing.T) {
var journey []*index.Reference var journey []*index.Reference
for f := 0; f < 200; f++ { for f := 0; f < 200; f++ {
journey = append(journey, nil) journey = append(journey, nil)
} }
index := index.NewSpecIndex(nil) index := index.NewSpecIndex(nil)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.Nil(t, resolver.extractRelatives(nil, nil, journey, false)) assert.Nil(t, resolver.extractRelatives(nil, nil, journey, false))
} }
func TestResolver_ResolveComponents_Stripe(t *testing.T) { func TestResolver_ResolveComponents_Stripe(t *testing.T) {
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml") stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(stripe, &rootNode) yaml.Unmarshal(stripe, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 3) assert.Len(t, circ, 3)
assert.Len(t, resolver.GetNonPolymorphicCircularErrors(), 3) assert.Len(t, resolver.GetNonPolymorphicCircularErrors(), 3)
assert.Len(t, resolver.GetPolymorphicCircularErrors(), 0) assert.Len(t, resolver.GetPolymorphicCircularErrors(), 0)
} }
func TestResolver_ResolveComponents_BurgerShop(t *testing.T) { func TestResolver_ResolveComponents_BurgerShop(t *testing.T) {
mixedref, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml") mixedref, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(mixedref, &rootNode) yaml.Unmarshal(mixedref, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 0) assert.Len(t, circ, 0)
} }
func TestResolver_ResolveComponents_PolyNonCircRef(t *testing.T) { func TestResolver_ResolveComponents_PolyNonCircRef(t *testing.T) {
yml := `paths: yml := `paths:
/hey: /hey:
get: get:
responses: responses:
@@ -184,20 +184,20 @@ components:
tea: tea:
description: tea` description: tea`
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal([]byte(yml), &rootNode) yaml.Unmarshal([]byte(yml), &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences() circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0) assert.Len(t, circ, 0)
} }
func TestResolver_ResolveComponents_PolyCircRef(t *testing.T) { func TestResolver_ResolveComponents_PolyCircRef(t *testing.T) {
yml := `openapi: 3.1.0 yml := `openapi: 3.1.0
components: components:
schemas: schemas:
cheese: cheese:
@@ -211,24 +211,24 @@ components:
tea: tea:
description: tea` description: tea`
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal([]byte(yml), &rootNode) yaml.Unmarshal([]byte(yml), &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
_ = resolver.CheckForCircularReferences() _ = resolver.CheckForCircularReferences()
resolver.circularReferences[0].IsInfiniteLoop = true // override resolver.circularReferences[0].IsInfiniteLoop = true // override
assert.Len(t, index.GetCircularReferences(), 1) assert.Len(t, index.GetCircularReferences(), 1)
assert.Len(t, resolver.GetPolymorphicCircularErrors(), 1) assert.Len(t, resolver.GetPolymorphicCircularErrors(), 1)
assert.Equal(t, 2, index.GetCircularReferences()[0].LoopIndex) assert.Equal(t, 2, index.GetCircularReferences()[0].LoopIndex)
} }
func TestResolver_ResolveComponents_Missing(t *testing.T) { func TestResolver_ResolveComponents_Missing(t *testing.T) {
yml := `paths: yml := `paths:
/hey: /hey:
get: get:
responses: responses:
@@ -247,95 +247,95 @@ components:
butter: butter:
$ref: 'go home, I am drunk'` $ref: 'go home, I am drunk'`
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal([]byte(yml), &rootNode) yaml.Unmarshal([]byte(yml), &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
err := resolver.Resolve() err := resolver.Resolve()
assert.Len(t, err, 1) assert.Len(t, err, 1)
assert.Equal(t, "cannot resolve reference `go home, I am drunk`, it's missing: $go home, I am drunk [18:11]", err[0].Error()) assert.Equal(t, "cannot resolve reference `go home, I am drunk`, it's missing: $go home, I am drunk [18:11]", err[0].Error())
} }
func TestResolver_ResolveComponents_MixedRef(t *testing.T) { func TestResolver_ResolveComponents_MixedRef(t *testing.T) {
mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml") mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(mixedref, &rootNode) yaml.Unmarshal(mixedref, &rootNode)
b := index.CreateOpenAPIIndexConfig() b := index.CreateOpenAPIIndexConfig()
idx := index.NewSpecIndexWithConfig(&rootNode, b) idx := index.NewSpecIndexWithConfig(&rootNode, b)
resolver := NewResolver(idx) resolver := NewResolver(idx)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 0) assert.Len(t, circ, 0)
assert.Equal(t, 5, resolver.GetIndexesVisited()) assert.Equal(t, 5, resolver.GetIndexesVisited())
// in v0.8.2 a new check was added when indexing, to prevent re-indexing the same file multiple times. // in v0.8.2 a new check was added when indexing, to prevent re-indexing the same file multiple times.
assert.Equal(t, 191, resolver.GetRelativesSeen()) assert.Equal(t, 191, resolver.GetRelativesSeen())
assert.Equal(t, 35, resolver.GetJourneysTaken()) assert.Equal(t, 35, resolver.GetJourneysTaken())
assert.Equal(t, 62, resolver.GetReferenceVisited()) assert.Equal(t, 62, resolver.GetReferenceVisited())
} }
func TestResolver_ResolveComponents_k8s(t *testing.T) { func TestResolver_ResolveComponents_k8s(t *testing.T) {
k8s, _ := ioutil.ReadFile("../test_specs/k8s.json") k8s, _ := ioutil.ReadFile("../test_specs/k8s.json")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(k8s, &rootNode) yaml.Unmarshal(k8s, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 0) assert.Len(t, circ, 0)
} }
// Example of how to resolve the Stripe OpenAPI specification, and check for circular reference errors // Example of how to resolve the Stripe OpenAPI specification, and check for circular reference errors
func ExampleNewResolver() { func ExampleNewResolver() {
// create a yaml.Node reference as a root node. // create a yaml.Node reference as a root node.
var rootNode yaml.Node var rootNode yaml.Node
// load in the Stripe OpenAPI spec (lots of polymorphic complexity in here) // load in the Stripe OpenAPI spec (lots of polymorphic complexity in here)
stripeBytes, _ := ioutil.ReadFile("../test_specs/stripe.yaml") stripeBytes, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
// unmarshal bytes into our rootNode. // unmarshal bytes into our rootNode.
_ = yaml.Unmarshal(stripeBytes, &rootNode) _ = yaml.Unmarshal(stripeBytes, &rootNode)
// create a new spec index (resolver depends on it) // create a new spec index (resolver depends on it)
indexConfig := index.CreateClosedAPIIndexConfig() indexConfig := index.CreateClosedAPIIndexConfig()
index := index.NewSpecIndexWithConfig(&rootNode, indexConfig) index := index.NewSpecIndexWithConfig(&rootNode, indexConfig)
// create a new resolver using the index. // create a new resolver using the index.
resolver := NewResolver(index) resolver := NewResolver(index)
// resolve the document, if there are circular reference errors, they are returned/ // resolve the document, if there are circular reference errors, they are returned/
// WARNING: this is a destructive action and the rootNode will be PERMANENTLY altered and cannot be unresolved // WARNING: this is a destructive action and the rootNode will be PERMANENTLY altered and cannot be unresolved
circularErrors := resolver.Resolve() circularErrors := resolver.Resolve()
// The Stripe API has a bunch of circular reference problems, mainly from polymorphism. // The Stripe API has a bunch of circular reference problems, mainly from polymorphism.
// So let's print them out. // So let's print them out.
// //
fmt.Printf("There are %d circular reference errors, %d of them are polymorphic errors, %d are not", fmt.Printf("There are %d circular reference errors, %d of them are polymorphic errors, %d are not",
len(circularErrors), len(resolver.GetPolymorphicCircularErrors()), len(resolver.GetNonPolymorphicCircularErrors())) len(circularErrors), len(resolver.GetPolymorphicCircularErrors()), len(resolver.GetNonPolymorphicCircularErrors()))
// Output: There are 3 circular reference errors, 0 of them are polymorphic errors, 3 are not // Output: There are 3 circular reference errors, 0 of them are polymorphic errors, 3 are not
} }
func ExampleResolvingError() { func ExampleResolvingError() {
re := ResolvingError{ re := ResolvingError{
ErrorRef: errors.New("Je suis une erreur"), ErrorRef: errors.New("Je suis une erreur"),
Node: &yaml.Node{ Node: &yaml.Node{
Line: 5, Line: 5,
Column: 21, Column: 21,
}, },
Path: "#/definitions/JeSuisUneErreur", Path: "#/definitions/JeSuisUneErreur",
CircularReference: &index.CircularReferenceResult{}, CircularReference: &index.CircularReferenceResult{},
} }
fmt.Printf("%s", re.Error()) fmt.Printf("%s", re.Error())
// Output: Je suis une erreur: #/definitions/JeSuisUneErreur [5:21] // Output: Je suis une erreur: #/definitions/JeSuisUneErreur [5:21]
} }

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