Merge branch 'main' into Baliedge/PIP-2552-consistent-ordering

This commit is contained in:
Tristan Cartledge
2023-11-27 12:06:39 +00:00
219 changed files with 20934 additions and 4697 deletions

BIN
.github/sponsors/scalar-dark.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

BIN
.github/sponsors/scalar-light.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

View File

@@ -16,7 +16,7 @@ jobs:
- name: Set up Go 1.x
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.21
id: go
- name: Checkout code

View File

@@ -29,6 +29,17 @@ like our _very kind_ sponsors:
[Speakeasy](https://speakeasyapi.dev/?utm_source=libopenapi+repo&utm_medium=github+sponsorship)
<a href="https://scalar.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset=".github/sponsors/scalar-dark.png">
<img alt="scalar'" src=".github/sponsors/scalar-light.png">
</picture>
</a>
[scalar](https://scalar.com)
---
`libopenapi` is pretty new, so our list of notable projects that depend on `libopenapi` is small (let me know if you'd like to add your project)
@@ -39,7 +50,7 @@ like our _very kind_ sponsors:
- [github.com/danielgtaylor/restish](https://github.com/danielgtaylor/restish) - "Restish is a CLI for interacting with REST-ish HTTP APIs"
- [github.com/speakeasy-api/speakeasy](https://github.com/speakeasy-api/speakeasy) - "Speakeasy CLI makes validating OpenAPI docs and generating idiomatic SDKs easy!"
- [github.com/apicat/apicat](https://github.com/apicat/apicat) - "AI-powered API development tool"
- [github.com/mattermost/mattermost](https://github.com/mattermost/mattermost) = "Software development lifecycle platform"
- [github.com/mattermost/mattermost](https://github.com/mattermost/mattermost) - "Software development lifecycle platform"
- Your project here?
---
@@ -67,6 +78,7 @@ See all the documentation at https://pb33f.io/libopenapi/
- [Using Vendor Extensions](https://pb33f.io/libopenapi/extensions/)
- [The Index](https://pb33f.io/libopenapi/index/)
- [The Resolver](https://pb33f.io/libopenapi/resolver/)
- [The Rolodex](https://pb33f.io/libopenapi/rolodex/)
- [Circular References](https://pb33f.io/libopenapi/circular-references/)
- [What Changed / Diff Engine](https://pb33f.io/libopenapi/what-changed/)
- [FAQ](https://pb33f.io/libopenapi/faq/)

View File

@@ -4,8 +4,11 @@
package datamodel
import (
"net/http"
"github.com/pb33f/libopenapi/utils"
"io/fs"
"log/slog"
"net/url"
"os"
)
// DocumentConfiguration is used to configure the document creation process. It was added in v0.6.0 to allow
@@ -20,17 +23,54 @@ type DocumentConfiguration struct {
// RemoteURLHandler is a function that will be used to retrieve remote documents. If not set, the default
// remote document getter will be used.
//
// The remote handler is only used if the BaseURL is set. If the BaseURL is not set, then the remote handler
// will not be used, as there will be nothing to use it against.
//
// Resolves [#132]: https://github.com/pb33f/libopenapi/issues/132
RemoteURLHandler func(url string) (*http.Response, error)
RemoteURLHandler utils.RemoteURLHandler
// If resolving locally, the BasePath will be the root from which relative references will be resolved from.
// It's usually the location of the root specification.
//
// Be warned, setting this value will instruct the rolodex to index EVERY yaml and JSON file it finds from the
// base path. The rolodex will recurse into every directory and pick up everything form this location down.
//
// To avoid sucking in all the files, set the FileFilter to a list of specific files to be included.
BasePath string // set the Base Path for resolving relative references if the spec is exploded.
// FileFilter is a list of specific files to be included by the rolodex when looking up references. If this value
// is set, then only these specific files will be included. If this value is not set, then all files will be included.
FileFilter []string
// RemoteFS is a filesystem that will be used to retrieve remote documents. If not set, then the rolodex will
// use its own internal remote filesystem implementation. The RemoteURLHandler will be used to retrieve remote
// documents if it has been set. The default is to use the internal remote filesystem loader.
RemoteFS fs.FS
// LocalFS is a filesystem that will be used to retrieve local documents. If not set, then the rolodex will
// use its own internal local filesystem implementation. The default is to use the internal local filesystem loader.
LocalFS fs.FS
// AllowFileReferences will allow the index to locate relative file references. This is disabled by default.
//
// This behavior is now driven by the inclusion of a BasePath. If a BasePath is set, then the
// rolodex will look for relative file references. If no BasePath is set, then the rolodex will not look for
// relative file references.
//
// This value when set, will force the creation of a local file system even when the BasePath has not been set.
// it will suck in and index everything from the current working directory, down... so be warned
// FileFilter should be used to limit the scope of the rolodex.
AllowFileReferences bool
// AllowRemoteReferences will allow the index to lookup remote references. This is disabled by default.
//
// This behavior is now driven by the inclusion of a BaseURL. If a BaseURL is set, then the
// rolodex will look for remote references. If no BaseURL is set, then the rolodex will not look for
// remote references. This value has no effect as of version 0.13.0 and will be removed in a future release.
//
// This value when set, will force the creation of a remote file system even when the BaseURL has not been set.
// it will suck in every http link it finds, and recurse through all references located in each document.
AllowRemoteReferences bool
// AvoidIndexBuild will avoid building the index. This is disabled by default, only use if you are sure you don't need it.
@@ -52,18 +92,21 @@ type DocumentConfiguration struct {
// So if libopenapi is returning circular references for this use case, then this option should be enabled.
// this is disabled by default, which means array circular references will be checked.
IgnoreArrayCircularReferences bool
// SkipCircularReferenceCheck will skip over checking for circular references. This is disabled by default, which
// means circular references will be checked. This is useful for developers building out models that should be
// indexed later on.
SkipCircularReferenceCheck bool
// Logger is a structured logger that will be used for logging errors and warnings. If not set, a default logger
// will be used, set to the Error level.
Logger *slog.Logger
}
func NewOpenDocumentConfiguration() *DocumentConfiguration {
func NewDocumentConfiguration() *DocumentConfiguration {
return &DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
}
}
func NewClosedDocumentConfiguration() *DocumentConfiguration {
return &DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
Logger: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelError,
})),
}
}

View File

@@ -9,13 +9,6 @@ import (
)
func TestNewClosedDocumentConfiguration(t *testing.T) {
cfg := NewClosedDocumentConfiguration()
assert.False(t, cfg.AllowRemoteReferences)
assert.False(t, cfg.AllowFileReferences)
}
func TestNewOpenDocumentConfiguration(t *testing.T) {
cfg := NewOpenDocumentConfiguration()
assert.True(t, cfg.AllowRemoteReferences)
assert.True(t, cfg.AllowFileReferences)
cfg := NewDocumentConfiguration()
assert.NotNil(t, cfg)
}

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"fmt"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
@@ -70,7 +71,7 @@ email: buckaroo@pb33f.io
// build low
var lowContact lowbase.Contact
_ = lowmodel.BuildModel(cNode.Content[0], &lowContact)
_ = lowContact.Build(nil, cNode.Content[0], nil)
_ = lowContact.Build(context.Background(), nil, cNode.Content[0], nil)
// build high
highContact := NewContact(&lowContact)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
@@ -116,7 +117,7 @@ func TestDynamicValue_MarshalYAMLInline(t *testing.T) {
_ = yaml.Unmarshal([]byte(ymlSchema), &node)
lowProxy := new(lowbase.SchemaProxy)
err := lowProxy.Build(nil, node.Content[0], idx)
err := lowProxy.Build(context.Background(), nil, node.Content[0], idx)
assert.NoError(t, err)
lowRef := low.NodeReference[*lowbase.SchemaProxy]{
@@ -160,7 +161,7 @@ func TestDynamicValue_MarshalYAMLInline_Error(t *testing.T) {
_ = yaml.Unmarshal([]byte(ymlSchema), &node)
lowProxy := new(lowbase.SchemaProxy)
err := lowProxy.Build(nil, node.Content[0], idx)
err := lowProxy.Build(context.Background(), nil, node.Content[0], idx)
assert.NoError(t, err)
lowRef := low.NodeReference[*lowbase.SchemaProxy]{

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"fmt"
"strings"
"testing"
@@ -31,7 +32,7 @@ x-hack: code`
var lowExample lowbase.Example
_ = lowmodel.BuildModel(cNode.Content[0], &lowExample)
_ = lowExample.Build(&cNode, cNode.Content[0], nil)
_ = lowExample.Build(context.Background(), &cNode, cNode.Content[0], nil)
// build high
highExample := NewExample(&lowExample)
@@ -61,7 +62,7 @@ func TestExtractExamples(t *testing.T) {
var lowExample lowbase.Example
_ = lowmodel.BuildModel(cNode.Content[0], &lowExample)
_ = lowExample.Build(nil, cNode.Content[0], nil)
_ = lowExample.Build(context.Background(), nil, cNode.Content[0], nil)
examplesMap := orderedmap.New[lowmodel.KeyReference[string], lowmodel.ValueReference[*lowbase.Example]]()
examplesMap.Set(
@@ -90,7 +91,7 @@ x-hack: code`
_ = lowmodel.BuildModel(node.Content[0], &lowExample)
// build out low-level example
_ = lowExample.Build(nil, node.Content[0], nil)
_ = lowExample.Build(context.Background(), nil, node.Content[0], nil)
// create a new high-level example
highExample := NewExample(&lowExample)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"fmt"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
@@ -26,7 +27,7 @@ x-hack: code`
var lowExt lowbase.ExternalDoc
_ = lowmodel.BuildModel(cNode.Content[0], &lowExt)
_ = lowExt.Build(nil, cNode.Content[0], nil)
_ = lowExt.Build(context.Background(), nil, cNode.Content[0], nil)
highExt := NewExternalDoc(&lowExt)
@@ -61,7 +62,7 @@ x-hack: code`
_ = lowmodel.BuildModel(node.Content[0], &lowExt)
// build out low-level properties (like extensions)
_ = lowExt.Build(nil, node.Content[0], nil)
_ = lowExt.Build(context.Background(), nil, node.Content[0], nil)
// create new high-level ExternalDoc
highExt := NewExternalDoc(&lowExt)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"fmt"
"testing"
@@ -32,7 +33,7 @@ x-cli-name: chicken cli`
var lowInfo lowbase.Info
_ = lowmodel.BuildModel(cNode.Content[0], &lowInfo)
_ = lowInfo.Build(nil, cNode.Content[0], nil)
_ = lowInfo.Build(context.Background(), nil, cNode.Content[0], nil)
highInfo := NewInfo(&lowInfo)
@@ -74,7 +75,7 @@ version: 1.2.3`
// build out the low-level model
var lowInfo lowbase.Info
_ = lowmodel.BuildModel(&node, &lowInfo)
_ = lowInfo.Build(nil, node.Content[0], nil)
_ = lowInfo.Build(context.Background(), nil, node.Content[0], nil)
// build the high level model
highInfo := NewInfo(&lowInfo)
@@ -97,7 +98,7 @@ url: https://opensource.org/licenses/MIT`
// build out the low-level model
var lowLicense lowbase.License
_ = lowmodel.BuildModel(node.Content[0], &lowLicense)
_ = lowLicense.Build(nil, node.Content[0], nil)
_ = lowLicense.Build(context.Background(), nil, node.Content[0], nil)
// build the high level model
highLicense := NewLicense(&lowLicense)
@@ -140,7 +141,7 @@ func TestInfo_Render(t *testing.T) {
// build low
var lowInfo lowbase.Info
_ = lowmodel.BuildModel(cNode.Content[0], &lowInfo)
_ = lowInfo.Build(nil, cNode.Content[0], nil)
_ = lowInfo.Build(context.Background(), nil, cNode.Content[0], nil)
// build high
highInfo := NewInfo(&lowInfo)
@@ -181,7 +182,7 @@ x-cake:
// build low
var lowInfo lowbase.Info
_ = lowmodel.BuildModel(cNode.Content[0], &lowInfo)
_ = lowInfo.Build(nil, cNode.Content[0], nil)
_ = lowInfo.Build(context.Background(), nil, cNode.Content[0], nil)
// build high
highInfo := NewInfo(&lowInfo)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/stretchr/testify/assert"
@@ -44,7 +45,7 @@ url: https://pb33f.io/not-real
// build low
var lowLicense lowbase.License
_ = lowmodel.BuildModel(cNode.Content[0], &lowLicense)
_ = lowLicense.Build(nil, cNode.Content[0], nil)
_ = lowLicense.Build(context.Background(), nil, cNode.Content[0], nil)
// build high
highLicense := NewLicense(&lowLicense)
@@ -92,7 +93,7 @@ func TestLicense_Render_IdentifierAndURL_Error(t *testing.T) {
// build low
var lowLicense lowbase.License
_ = lowmodel.BuildModel(cNode.Content[0], &lowLicense)
err := lowLicense.Build(nil, cNode.Content[0], nil)
err := lowLicense.Build(context.Background(), nil, cNode.Content[0], nil)
assert.Error(t, err)
}

View File

@@ -64,44 +64,44 @@ type Schema struct {
// in 3.1 UnevaluatedProperties can be a Schema or a boolean
// https://github.com/pb33f/libopenapi/issues/118
UnevaluatedProperties *DynamicValue[*SchemaProxy, *bool] `json:"unevaluatedProperties,omitempty" yaml:"unevaluatedProperties,omitempty"`
UnevaluatedProperties *DynamicValue[*SchemaProxy, bool] `json:"unevaluatedProperties,omitempty" yaml:"unevaluatedProperties,omitempty"`
// in 3.1 Items can be a Schema or a boolean
Items *DynamicValue[*SchemaProxy, bool] `json:"items,omitempty" yaml:"items,omitempty"`
// 3.1 only, part of the JSON Schema spec provides a way to identify a subschema
// 3.1 only, part of the JSON Schema spec provides a way to identify a sub-schema
Anchor string `json:"$anchor,omitempty" yaml:"$anchor,omitempty"`
// Compatible with all versions
Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"`
Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"`
Properties orderedmap.Map[string, *SchemaProxy] `json:"properties,omitempty" yaml:"properties,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
Maximum *float64 `json:"maximum,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"`
Const any `json:"const,omitempty" yaml:"const,renderZero,omitempty"`
Nullable *bool `json:"nullable,omitempty" yaml:"nullable,omitempty"`
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30
WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30
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:"-"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
Maximum *float64 `json:"maximum,renderZero,omitempty" yaml:"maximum,renderZero,omitempty"`
Minimum *float64 `json:"minimum,renderZero,omitempty," yaml:"minimum,renderZero,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 *DynamicValue[*SchemaProxy, bool] `json:"additionalProperties,renderZero,omitempty" yaml:"additionalProperties,renderZero,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Default any `json:"default,omitempty" yaml:"default,renderZero,omitempty"`
Const any `json:"const,omitempty" yaml:"const,renderZero,omitempty"`
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.
@@ -212,29 +212,22 @@ func NewSchema(schema *base.Schema) *Schema {
Value: schema.UnevaluatedItems.Value,
})
}
// check if unevaluated properties is a schema
if !schema.UnevaluatedProperties.IsEmpty() && schema.UnevaluatedProperties.Value.IsA() {
s.UnevaluatedProperties = &DynamicValue[*SchemaProxy, *bool]{
A: NewSchemaProxy(
&lowmodel.NodeReference[*base.SchemaProxy]{
var unevaluatedProperties *DynamicValue[*SchemaProxy, bool]
if !schema.UnevaluatedProperties.IsEmpty() {
if schema.UnevaluatedProperties.Value.IsA() {
unevaluatedProperties = &DynamicValue[*SchemaProxy, bool]{
A: NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.UnevaluatedProperties.ValueNode,
Value: schema.UnevaluatedProperties.Value.A,
},
),
N: 0,
KeyNode: schema.UnevaluatedProperties.KeyNode,
}),
}
} else {
unevaluatedProperties = &DynamicValue[*SchemaProxy, bool]{N: 1, B: schema.UnevaluatedProperties.Value.B}
}
}
// check if unevaluated properties is a bool
if !schema.UnevaluatedProperties.IsEmpty() && schema.UnevaluatedProperties.Value.IsB() {
s.UnevaluatedProperties = &DynamicValue[*SchemaProxy, *bool]{
B: schema.UnevaluatedProperties.Value.B,
N: 1,
}
}
if !schema.UnevaluatedProperties.IsEmpty() {
}
s.UnevaluatedProperties = unevaluatedProperties
s.Pattern = schema.Pattern.Value
s.Format = schema.Format.Value
@@ -249,19 +242,23 @@ func NewSchema(schema *base.Schema) *Schema {
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
var additionalProperties *DynamicValue[*SchemaProxy, bool]
if !schema.AdditionalProperties.IsEmpty() {
if schema.AdditionalProperties.Value.IsA() {
additionalProperties = &DynamicValue[*SchemaProxy, bool]{
A: NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.AdditionalProperties.ValueNode,
Value: schema.AdditionalProperties.Value.A,
KeyNode: schema.AdditionalProperties.KeyNode,
}),
}
} else {
additionalProperties = &DynamicValue[*SchemaProxy, bool]{N: 1, B: schema.AdditionalProperties.Value.B}
}
}
s.AdditionalProperties = additionalProperties
s.Description = schema.Description.Value
s.Default = schema.Default.Value
s.Const = schema.Const.Value
@@ -306,7 +303,6 @@ func NewSchema(schema *base.Schema) *Schema {
s.Anchor = schema.Anchor.Value
}
// TODO: check this behavior.
for i := range schema.Enum.Value {
enum = append(enum, schema.Enum.Value[i].Value)
}
@@ -423,7 +419,8 @@ func NewSchema(schema *base.Schema) *Schema {
Value: schema.Items.Value.A,
KeyNode: schema.Items.KeyNode,
},
)}
),
}
} else {
items = &DynamicValue[*SchemaProxy, bool]{N: 1, B: schema.Items.Value.B}
}
@@ -437,7 +434,7 @@ func NewSchema(schema *base.Schema) *Schema {
completeChildren := 0
if children > 0 {
allDone:
for true {
for {
select {
case <-polyCompletedChan:
completeChildren++
@@ -471,8 +468,8 @@ func (s *Schema) Render() ([]byte, error) {
return yaml.Marshal(s)
}
// RenderInline will return a YAML representation of the Schema object as a byte slice. All of the
// $ref values will be inlined, as in resolved in place.
// RenderInline will return a YAML representation of the Schema object as a byte slice.
// All the $ref values will be inlined, as in resolved in place.
//
// Make sure you don't have any circular references!
func (s *Schema) RenderInline() ([]byte, error) {
@@ -483,11 +480,26 @@ func (s *Schema) RenderInline() ([]byte, error) {
// MarshalYAML will create a ready to render YAML representation of the ExternalDoc object.
func (s *Schema) MarshalYAML() (interface{}, error) {
nb := high.NewNodeBuilder(s, s.low)
// determine index version
idx := s.GoLow().Index
if idx != nil {
if idx.GetConfig().SpecInfo != nil {
nb.Version = idx.GetConfig().SpecInfo.VersionNumeric
}
}
return nb.Render(), nil
}
func (s *Schema) MarshalYAMLInline() (interface{}, error) {
nb := high.NewNodeBuilder(s, s.low)
nb.Resolve = true
// determine index version
idx := s.GoLow().Index
if idx != nil {
if idx.GetConfig().SpecInfo != nil {
nb.Version = idx.GetConfig().SpecInfo.VersionNumeric
}
}
return nb.Render(), nil
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sync"
@@ -114,6 +115,15 @@ func (sp *SchemaProxy) GetReference() string {
return sp.schema.Value.GetSchemaReference()
}
// GetReferenceOrigin returns a pointer to the index.NodeOrigin of the $ref if this SchemaProxy is a reference to another Schema.
// returns nil if the origin cannot be found (which, means there is a bug, and we need to fix it).
func (sp *SchemaProxy) GetReferenceOrigin() *index.NodeOrigin {
if sp.schema != nil {
return sp.schema.Value.GetSchemaReferenceLocation()
}
return nil
}
// BuildSchema operates the same way as Schema, except it will return any error along with the *Schema
func (sp *SchemaProxy) BuildSchema() (*Schema, error) {
if sp.rendered != nil {

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
@@ -40,7 +41,7 @@ func TestSchemaProxy_MarshalYAML(t *testing.T) {
_ = yaml.Unmarshal([]byte(ymlSchema), &node)
lowProxy := new(lowbase.SchemaProxy)
err := lowProxy.Build(nil, node.Content[0], idx)
err := lowProxy.Build(context.Background(), nil, node.Content[0], idx)
assert.NoError(t, err)
lowRef := low.NodeReference[*lowbase.SchemaProxy]{
@@ -49,6 +50,9 @@ func TestSchemaProxy_MarshalYAML(t *testing.T) {
sp := NewSchemaProxy(&lowRef)
origin := sp.GetReferenceOrigin()
assert.Nil(t, origin)
rend, _ := sp.Render()
assert.Equal(t, "$ref: '#/components/schemas/nice'", strings.TrimSpace(string(rend)))
@@ -65,3 +69,8 @@ func TestCreateSchemaProxyRef(t *testing.T) {
assert.Equal(t, "#/components/schemas/MySchema", sp.GetReference())
assert.True(t, sp.IsReference())
}
func TestSchemaProxy_NoSchema_GetOrigin(t *testing.T) {
sp := &SchemaProxy{}
assert.Nil(t, sp.GetReferenceOrigin())
}

View File

@@ -4,10 +4,13 @@
package base
import (
"context"
"fmt"
"strings"
"testing"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
@@ -49,7 +52,7 @@ func TestNewSchemaProxy(t *testing.T) {
_ = yaml.Unmarshal([]byte(yml), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], idx)
err := sp.Build(context.Background(), nil, compNode.Content[0], idx)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -90,7 +93,7 @@ func TestNewSchemaProxyRender(t *testing.T) {
_ = yaml.Unmarshal([]byte(yml), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], idx)
err := sp.Build(context.Background(), nil, compNode.Content[0], idx)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -111,7 +114,6 @@ func TestNewSchemaProxyRender(t *testing.T) {
rice:
$ref: '#/components/schemas/rice'`
assert.Equal(t, desired, strings.TrimSpace(string(rend)))
}
func TestNewSchemaProxy_WithObject(t *testing.T) {
@@ -217,10 +219,7 @@ properties:
type: number
description: a number
example: "2"
additionalProperties:
- chicken
- nugget
- soup
additionalProperties: false
somethingB:
type: object
exclusiveMinimum: true
@@ -241,8 +240,7 @@ properties:
attribute: true
x-pizza: love
additionalProperties:
why: yes
thatIs: true
type: string
additionalProperties: true
required:
- them
@@ -274,7 +272,7 @@ $anchor: anchor`
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], nil)
err := sp.Build(context.Background(), nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -315,12 +313,12 @@ $anchor: anchor`
assert.Equal(t, "anchor", compiled.Anchor)
wentLow := compiled.GoLow()
assert.Equal(t, 129, wentLow.AdditionalProperties.ValueNode.Line)
assert.Equal(t, 125, wentLow.AdditionalProperties.ValueNode.Line)
assert.NotNil(t, compiled.GoLowUntyped())
// now render it out!
schemaBytes, _ := compiled.Render()
assert.Len(t, schemaBytes, 3494)
assert.Len(t, schemaBytes, 3417)
}
func TestSchemaObjectWithAllOfSequenceOrder(t *testing.T) {
@@ -348,7 +346,7 @@ func TestSchemaObjectWithAllOfSequenceOrder(t *testing.T) {
}
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], nil)
err := sp.Build(context.Background(), nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -485,7 +483,7 @@ required: [cake, fish]`
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], nil)
err := sp.Build(context.Background(), nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -504,7 +502,7 @@ required: [cake, fish]`
assert.Equal(t, float64(334), compiled.Properties.GetOrZero("somethingB").Schema().ExclusiveMaximum.B)
assert.Len(t, compiled.Properties.GetOrZero("somethingB").Schema().Properties.GetOrZero("somethingBProp").Schema().Type, 2)
assert.Equal(t, "nice", compiled.AdditionalProperties.(*SchemaProxy).Schema().Description)
assert.Equal(t, "nice", compiled.AdditionalProperties.A.Schema().Description)
wentLow := compiled.GoLow()
assert.Equal(t, 97, wentLow.AdditionalProperties.ValueNode.Line)
@@ -541,7 +539,7 @@ func TestSchemaProxy_GoLow(t *testing.T) {
_ = yaml.Unmarshal([]byte(ymlSchema), &node)
lowProxy := new(lowbase.SchemaProxy)
err := lowProxy.Build(nil, node.Content[0], idx)
err := lowProxy.Build(context.Background(), nil, node.Content[0], idx)
assert.NoError(t, err)
lowRef := low.NodeReference[*lowbase.SchemaProxy]{
@@ -556,7 +554,6 @@ func TestSchemaProxy_GoLow(t *testing.T) {
spNil := NewSchemaProxy(nil)
assert.Nil(t, spNil.GoLow())
assert.Nil(t, spNil.GoLowUntyped())
}
func getHighSchema(t *testing.T, yml string) *Schema {
@@ -567,7 +564,7 @@ func getHighSchema(t *testing.T, yml string) *Schema {
// build out the low-level model
var lowSchema lowbase.Schema
assert.NoError(t, low.BuildModel(node.Content[0], &lowSchema))
assert.NoError(t, lowSchema.Build(node.Content[0], nil))
assert.NoError(t, lowSchema.Build(context.Background(), node.Content[0], nil))
// build the high level model
return NewSchema(&lowSchema)
@@ -728,7 +725,7 @@ properties:
// build out the low-level model
var lowSchema lowbase.Schema
_ = low.BuildModel(node.Content[0], &lowSchema)
_ = lowSchema.Build(node.Content[0], nil)
_ = lowSchema.Build(context.Background(), node.Content[0], nil)
// build the high level model
highSchema := NewSchema(&lowSchema)
@@ -757,7 +754,7 @@ properties:
// build out the low-level model
var lowSchema lowbase.SchemaProxy
_ = low.BuildModel(node.Content[0], &lowSchema)
_ = lowSchema.Build(nil, node.Content[0], nil)
_ = lowSchema.Build(context.Background(), nil, node.Content[0], nil)
// build the high level schema proxy
highSchema := NewSchemaProxy(&low.NodeReference[*lowbase.SchemaProxy]{
@@ -817,7 +814,7 @@ allOf:
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], nil)
err := sp.Build(context.Background(), nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -836,7 +833,6 @@ allOf:
// now render it out, it should be identical.
schemaBytes, _ := compiled.Render()
assert.Equal(t, testSpec, string(schemaBytes))
}
func TestNewSchemaProxy_RenderSchemaWithMultipleObjectTypes(t *testing.T) {
@@ -881,7 +877,7 @@ items:
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], nil)
err := sp.Build(context.Background(), nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -934,8 +930,7 @@ func TestNewSchemaProxy_RenderSchemaEnsurePropertyOrdering(t *testing.T) {
attribute: true
x-pizza: love
additionalProperties:
why: yes
thatIs: true
type: string
additionalProperties: true
xml:
name: XML Thing`
@@ -944,7 +939,7 @@ xml:
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], nil)
err := sp.Build(context.Background(), nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -973,7 +968,7 @@ func TestNewSchemaProxy_RenderSchemaCheckDiscriminatorMappingOrder(t *testing.T)
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], nil)
err := sp.Build(context.Background(), nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -989,60 +984,6 @@ func TestNewSchemaProxy_RenderSchemaCheckDiscriminatorMappingOrder(t *testing.T)
assert.Equal(t, testSpec, strings.TrimSpace(string(schemaBytes)))
}
func TestNewSchemaProxy_RenderSchemaCheckAdditionalPropertiesSlice(t *testing.T) {
testSpec := `additionalProperties:
- one
- two
- miss a few
- ninety nine
- hundred`
var compNode yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
Value: sp,
ValueNode: compNode.Content[0],
}
schemaProxy := NewSchemaProxy(&lowproxy)
compiled := schemaProxy.Schema()
// now render it out, it should be identical.
schemaBytes, _ := compiled.Render()
assert.Len(t, schemaBytes, 91)
}
func TestNewSchemaProxy_RenderSchemaCheckAdditionalPropertiesSliceMap(t *testing.T) {
testSpec := `additionalProperties:
- nice: cake
- yummy: beer
- hot: coffee`
var compNode yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
Value: sp,
ValueNode: compNode.Content[0],
}
schemaProxy := NewSchemaProxy(&lowproxy)
compiled := schemaProxy.Schema()
// now render it out, it should be identical.
schemaBytes, _ := compiled.Render()
assert.Len(t, schemaBytes, 75)
}
func TestNewSchemaProxy_CheckDefaultBooleanFalse(t *testing.T) {
testSpec := `default: false`
@@ -1050,7 +991,7 @@ func TestNewSchemaProxy_CheckDefaultBooleanFalse(t *testing.T) {
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], nil)
err := sp.Build(context.Background(), nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -1073,7 +1014,7 @@ func TestNewSchemaProxy_RenderAdditionalPropertiesFalse(t *testing.T) {
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], nil)
err := sp.Build(context.Background(), nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -1117,7 +1058,7 @@ components:
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], idx)
err := sp.Build(context.Background(), nil, compNode.Content[0], idx)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -1169,7 +1110,7 @@ components:
sp := new(lowbase.SchemaProxy)
err := sp.Build(nil, compNode.Content[0], idx)
err := sp.Build(context.Background(), nil, compNode.Content[0], idx)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
@@ -1192,8 +1133,7 @@ unevaluatedProperties: true
`
highSchema := getHighSchema(t, yml)
value := true
assert.EqualValues(t, &value, highSchema.UnevaluatedProperties.B)
assert.True(t, highSchema.UnevaluatedProperties.B)
}
func TestUnevaluatedPropertiesBoolean_False(t *testing.T) {
@@ -1203,6 +1143,147 @@ unevaluatedProperties: false
`
highSchema := getHighSchema(t, yml)
value := false
assert.EqualValues(t, &value, highSchema.UnevaluatedProperties.B)
assert.False(t, highSchema.UnevaluatedProperties.B)
}
func TestUnevaluatedPropertiesBoolean_Unset(t *testing.T) {
yml := `
type: number
`
highSchema := getHighSchema(t, yml)
assert.Nil(t, highSchema.UnevaluatedProperties)
}
func TestAdditionalProperties(t *testing.T) {
testSpec := `type: object
properties:
additionalPropertiesSimpleSchema:
type: object
additionalProperties:
type: string
additionalPropertiesBool:
type: object
additionalProperties: true
additionalPropertiesAnyOf:
type: object
additionalProperties:
anyOf:
- type: string
- type: array
items:
type: string
`
var compNode yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(context.Background(), nil, compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
Value: sp,
ValueNode: compNode.Content[0],
}
schemaProxy := NewSchemaProxy(&lowproxy)
compiled := schemaProxy.Schema()
assert.Equal(t, []string{"string"}, compiled.Properties.GetOrZero("additionalPropertiesSimpleSchema").Schema().AdditionalProperties.A.Schema().Type)
assert.Equal(t, true, compiled.Properties.GetOrZero("additionalPropertiesBool").Schema().AdditionalProperties.B)
assert.Equal(t, []string{"string"}, compiled.Properties.GetOrZero("additionalPropertiesAnyOf").Schema().AdditionalProperties.A.Schema().AnyOf[0].Schema().Type)
}
func TestSchema_RenderProxyWithConfig_3(t *testing.T) {
testSpec := `exclusiveMinimum: true`
var compNode yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(context.Background(), nil, compNode.Content[0], nil)
assert.NoError(t, err)
config := index.CreateOpenAPIIndexConfig()
config.SpecInfo = &datamodel.SpecInfo{
VersionNumeric: 3.0,
}
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
Value: sp,
ValueNode: compNode.Content[0],
}
schemaProxy := NewSchemaProxy(&lowproxy)
compiled := schemaProxy.Schema()
// now render it out, it should be identical.
schemaBytes, _ := compiled.Render()
assert.Equal(t, testSpec, strings.TrimSpace(string(schemaBytes)))
}
func TestSchema_RenderProxyWithConfig_Corrected_31(t *testing.T) {
testSpec := `exclusiveMinimum: true`
testSpecCorrect := `exclusiveMinimum: 0`
var compNode yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
config := index.CreateOpenAPIIndexConfig()
config.SpecInfo = &datamodel.SpecInfo{
VersionNumeric: 3.1,
}
idx := index.NewSpecIndexWithConfig(compNode.Content[0], config)
err := sp.Build(context.Background(), nil, compNode.Content[0], idx)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
Value: sp,
ValueNode: compNode.Content[0],
}
schemaProxy := NewSchemaProxy(&lowproxy)
compiled := schemaProxy.Schema()
// now render it out, it should be identical.
schemaBytes, _ := compiled.Render()
assert.Equal(t, testSpecCorrect, strings.TrimSpace(string(schemaBytes)))
schemaBytes, _ = compiled.RenderInline()
assert.Equal(t, testSpecCorrect, strings.TrimSpace(string(schemaBytes)))
}
func TestSchema_RenderProxyWithConfig_Corrected_3(t *testing.T) {
testSpec := `exclusiveMinimum: 0`
testSpecCorrect := `exclusiveMinimum: false`
var compNode yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
config := index.CreateOpenAPIIndexConfig()
config.SpecInfo = &datamodel.SpecInfo{
VersionNumeric: 3.0,
}
idx := index.NewSpecIndexWithConfig(compNode.Content[0], config)
err := sp.Build(context.Background(), nil, compNode.Content[0], idx)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
Value: sp,
ValueNode: compNode.Content[0],
}
schemaProxy := NewSchemaProxy(&lowproxy)
compiled := schemaProxy.Schema()
// now render it out, it should be identical.
schemaBytes, _ := compiled.Render()
assert.Equal(t, testSpecCorrect, strings.TrimSpace(string(schemaBytes)))
schemaBytes, _ = compiled.RenderInline()
assert.Equal(t, testSpecCorrect, strings.TrimSpace(string(schemaBytes)))
}

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"strings"
"testing"
@@ -30,7 +31,7 @@ cake:
var lowExt lowbase.SecurityRequirement
_ = lowmodel.BuildModel(cNode.Content[0], &lowExt)
_ = lowExt.Build(nil, cNode.Content[0], nil)
_ = lowExt.Build(context.Background(), nil, cNode.Content[0], nil)
highExt := NewSecurityRequirement(&lowExt)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"fmt"
"strings"
"testing"
@@ -28,7 +29,7 @@ x-hack: code`
var lowTag lowbase.Tag
_ = lowmodel.BuildModel(cNode.Content[0], &lowTag)
_ = lowTag.Build(nil, cNode.Content[0], nil)
_ = lowTag.Build(context.Background(), nil, cNode.Content[0], nil)
highTag := NewTag(&lowTag)
@@ -75,7 +76,7 @@ x-hack: code`
// build out the low-level model
var lowTag lowbase.Tag
_ = lowmodel.BuildModel(node.Content[0], &lowTag)
_ = lowTag.Build(nil, node.Content[0], nil)
_ = lowTag.Build(context.Background(), nil, node.Content[0], nil)
// build the high level tag
highTag := NewTag(&lowTag)

View File

@@ -30,6 +30,7 @@ type NodeEntry struct {
// NodeBuilder is a structure used by libopenapi high-level objects, to render themselves back to YAML.
// this allows high-level objects to be 'mutable' because all changes will be rendered out.
type NodeBuilder struct {
Version float32
Nodes []*NodeEntry
High any
Low any
@@ -587,8 +588,12 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *NodeEntry) *yaml.Nod
}
if b, bok := value.(*float64); bok {
encodeSkip = true
if *b > 0 {
valueNode = utils.CreateFloatNode(strconv.FormatFloat(*b, 'f', -1, 64))
if *b > 0 || (entry.RenderZero && entry.Line > 0) {
if *b > 0 {
valueNode = utils.CreateFloatNode(strconv.FormatFloat(*b, 'f', -1, 64))
} else {
valueNode = utils.CreateIntNode(strconv.FormatFloat(*b, 'f', -1, 64))
}
valueNode.Line = line
}
}
@@ -642,7 +647,7 @@ func (n *NodeBuilder) extractLowMapKeysWrapped(iu reflect.Value, x string, order
}
func (n *NodeBuilder) extractLowMapKeys(fg reflect.Value, x string, found bool, orderedCollection []*NodeEntry, m reflect.Value, k reflect.Value) (bool, []*NodeEntry) {
if !fg.IsZero() {
if fg.IsValid() && !fg.IsZero() {
for j, ky := range fg.MapKeys() {
hu := ky.Interface()
if we, wok := hu.(low.HasKeyNode); wok {

View File

@@ -90,6 +90,7 @@ type test1 struct {
Thugg *bool `yaml:"thugg,renderZero"`
Thurr *int64 `yaml:"thurr,omitempty"`
Thral *float64 `yaml:"thral,omitempty"`
Throo *float64 `yaml:"throo,renderZero,omitempty"`
Tharg []string `yaml:"tharg,omitempty"`
Type []string `yaml:"type,omitempty"`
Throg []*key `yaml:"throg,omitempty"`
@@ -421,8 +422,9 @@ func TestNewNodeBuilder_MapKeyHasValue(t *testing.T) {
}
type test1low struct {
Thrug key `yaml:"thrug"`
Thugg *bool `yaml:"thugg"`
Thrug key `yaml:"thrug"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
t2 := test1low{
@@ -454,8 +456,9 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValue(t *testing.T) {
}
type test1low struct {
Thomp key `yaml:"thomp"`
Thugg *bool `yaml:"thugg"`
Thomp key `yaml:"thomp"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
t2 := test1low{
@@ -495,6 +498,7 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatch(t *testing.T) {
type test1low struct {
Thomp low.NodeReference[map[key]string] `yaml:"thomp"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
g := low.NodeReference[map[key]string]{
@@ -529,6 +533,7 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatchKeyNode(t *testing.T) {
type test1low struct {
Thomp low.NodeReference[map[key]string] `yaml:"thomp"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
g := low.NodeReference[map[key]string]{
@@ -563,6 +568,7 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatch_NoWrap(t *testing.T) {
type test1low struct {
Thomp map[key]string `yaml:"thomp"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
t2 := test1low{
@@ -922,6 +928,40 @@ func TestNewNodeBuilder_TestRenderZero(t *testing.T) {
assert.Equal(t, desired, strings.TrimSpace(string(data)))
}
func TestNewNodeBuilder_TestRenderZero_Float(t *testing.T) {
f := 0.0
t1 := test1{
Throo: &f,
}
nb := NewNodeBuilder(&t1, &t1)
node := nb.Render()
data, _ := yaml.Marshal(node)
desired := `throo: 0`
assert.Equal(t, desired, strings.TrimSpace(string(data)))
}
func TestNewNodeBuilder_TestRenderZero_Float_NotZero(t *testing.T) {
f := 0.12
t1 := test1{
Throo: &f,
}
nb := NewNodeBuilder(&t1, &t1)
node := nb.Render()
data, _ := yaml.Marshal(node)
desired := `throo: 0.12`
assert.Equal(t, desired, strings.TrimSpace(string(data)))
}
func TestNewNodeBuilder_TestRenderServerVariableSimulation(t *testing.T) {
t1 := test1{
@@ -961,7 +1001,8 @@ func TestNewNodeBuilder_ShouldHaveNotDoneTestsLikeThisOhWell(t *testing.T) {
type t1low struct {
Thril low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*key]]
Thugg *bool `yaml:"thugg"`
Thugg *bool `yaml:"thugg"`
Throo *float32 `yaml:"throo"`
}
t1 := test1{

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/index"
@@ -36,7 +37,7 @@ options:
var n v2.PathItem
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewPathItem(&n)

View File

@@ -4,6 +4,7 @@
package v2
import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/low"
v2low "github.com/pb33f/libopenapi/datamodel/low/v2"
@@ -34,7 +35,7 @@ func NewPaths(paths *v2low.Paths) *Paths {
pathItems.Set(result.key, result.result)
return nil
}
_ = orderedmap.TranslateMapParallel[low.KeyReference[string], low.ValueReference[*v2low.PathItem], asyncResult[*PathItem]](
_ = datamodel.TranslateMapParallel[low.KeyReference[string], low.ValueReference[*v2low.PathItem], asyncResult[*PathItem]](
paths.PathItems, translateFunc, resultFunc,
)
p.PathItems = pathItems

View File

@@ -4,22 +4,23 @@
package v2
import (
"os"
"github.com/pb33f/libopenapi/datamodel"
v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
"io/ioutil"
"testing"
)
var doc *v2.Swagger
func initTest() {
data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml")
data, _ := os.ReadFile("../../../test_specs/petstorev2-complete.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
doc, err = v2.CreateDocument(info)
var err error
doc, err = v2.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
if err != nil {
panic("broken something")
}

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"strings"
"testing"
@@ -67,7 +68,7 @@ func TestCallback_MarshalYAML(t *testing.T) {
var n v3.Callback
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewCallback(&n)

View File

@@ -6,6 +6,7 @@ package v3
import (
"sync"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high"
highbase "github.com/pb33f/libopenapi/datamodel/high/base"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
@@ -121,7 +122,7 @@ func buildComponent[IN any, OUT any](inMap orderedmap.Map[lowmodel.KeyReference[
outMap.Set(value.key, value.res)
return nil
}
_ = orderedmap.TranslateMapParallel(inMap, translateFunc, resultFunc)
_ = datamodel.TranslateMapParallel(inMap, translateFunc, resultFunc)
}
// buildSchema builds a schema from low level structs.
@@ -139,7 +140,7 @@ func buildSchema(inMap orderedmap.Map[lowmodel.KeyReference[string], lowmodel.Va
outMap.Set(value.key, value.res)
return nil
}
_ = orderedmap.TranslateMapParallel(inMap, translateFunc, resultFunc)
_ = datamodel.TranslateMapParallel(inMap, translateFunc, resultFunc)
}
// GoLow returns the low-level Components instance used to create the high-level one.

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"strings"
"testing"
@@ -48,7 +49,7 @@ func TestComponents_MarshalYAML(t *testing.T) {
var n v3.Components
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx)
_ = n.Build(context.Background(), idxNode.Content[0], idx)
r := NewComponents(&n)

View File

@@ -91,7 +91,11 @@ type Document struct {
//
// This property is not a part of the OpenAPI schema, this is custom to libopenapi.
Index *index.SpecIndex `json:"-" yaml:"-"`
low *low.Document
// Rolodex is the low-level rolodex used when creating this document.
// This in an internal structure and not part of the OpenAPI schema.
Rolodex *index.Rolodex `json:"-" yaml:"-"`
low *low.Document
}
// NewDocument will create a new high-level Document from a low-level one.

View File

@@ -5,17 +5,25 @@ package v3
import (
"fmt"
"log"
"log/slog"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/pb33f/libopenapi/datamodel"
v2 "github.com/pb33f/libopenapi/datamodel/high/v2"
lowv2 "github.com/pb33f/libopenapi/datamodel/low/v2"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
var lowDoc *lowv3.Document
@@ -23,7 +31,7 @@ var lowDoc *lowv3.Document
func initTest() {
data, _ := os.ReadFile("../../../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
@@ -221,7 +229,7 @@ func TestNewDocument_Components_Schemas(t *testing.T) {
d := h.Components.Schemas.GetOrZero("Drink")
assert.Len(t, d.Schema().Required, 2)
assert.True(t, d.Schema().AdditionalProperties.(bool))
assert.True(t, d.Schema().AdditionalProperties.B)
assert.Equal(t, "drinkType", d.Schema().Discriminator.PropertyName)
assert.Equal(t, "some value", d.Schema().Discriminator.Mapping["drink"])
assert.Equal(t, 516, d.Schema().Discriminator.GoLow().PropertyName.ValueNode.Line)
@@ -378,15 +386,14 @@ func testBurgerShop(t *testing.T, h *Document, checkLines bool) {
assert.Equal(t, 310, okResp.Links.GetOrZero("LocateBurger").GoLow().OperationId.ValueNode.Line)
assert.Equal(t, 118, burgersOp.Post.Security[0].GoLow().Requirements.ValueNode.Line)
}
}
func TestStripeAsDoc(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/stripe.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
assert.Len(t, err, 3)
var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
assert.Len(t, utils.UnwrapErrors(err), 3)
d := NewDocument(lowDoc)
assert.NotNil(t, d)
}
@@ -394,18 +401,18 @@ func TestStripeAsDoc(t *testing.T) {
func TestK8sAsDoc(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/k8s.json")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
lowSwag, err := lowv2.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
var err error
lowSwag, err := lowv2.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
d := v2.NewSwaggerDocument(lowSwag)
assert.Len(t, err, 0)
assert.Len(t, utils.UnwrapErrors(err), 0)
assert.NotNil(t, d)
}
func TestAsanaAsDoc(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/asana.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
if err != nil {
panic("broken something")
}
@@ -414,10 +421,53 @@ func TestAsanaAsDoc(t *testing.T) {
assert.Equal(t, 118, orderedmap.Len(d.Paths.PathItems))
}
func TestDigitalOceanAsDocViaCheckout(t *testing.T) {
// this is a full checkout of the digitalocean API repo.
tmp, _ := os.MkdirTemp("", "openapi")
cmd := exec.Command("git", "clone", "https://github.com/digitalocean/openapi", tmp)
defer os.RemoveAll(filepath.Join(tmp, "openapi"))
err := cmd.Run()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
spec, _ := filepath.Abs(filepath.Join(tmp, "specification", "DigitalOcean-public.v2.yaml"))
doLocal, _ := os.ReadFile(spec)
var rootNode yaml.Node
_ = yaml.Unmarshal(doLocal, &rootNode)
basePath := filepath.Join(tmp, "specification")
data, _ := os.ReadFile("../../../test_specs/digitalocean.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
config := datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
BasePath: basePath,
Logger: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
})),
}
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &config)
if err != nil {
er := utils.UnwrapErrors(err)
for e := range er {
fmt.Println(er[e])
}
}
d := NewDocument(lowDoc)
assert.NotNil(t, d)
assert.Equal(t, 183, d.Paths.PathItems.Len())
}
func TestDigitalOceanAsDocFromSHA(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/digitalocean.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
var err error
baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/82e1d558e15a59edc1d47d2c5544e7138f5b3cbf/specification")
config := datamodel.DocumentConfiguration{
@@ -426,24 +476,68 @@ func TestDigitalOceanAsDocFromSHA(t *testing.T) {
BaseURL: baseURL,
}
if os.Getenv("GH_PAT") != "" {
client := &http.Client{
Timeout: time.Second * 60,
}
config.RemoteURLHandler = func(url string) (*http.Response, error) {
request, _ := http.NewRequest(http.MethodGet, url, nil)
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("GH_PAT")))
return client.Do(request)
}
}
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &config)
assert.Len(t, utils.UnwrapErrors(err), 3) // there are 3 404's in this release of the API.
d := NewDocument(lowDoc)
assert.NotNil(t, d)
assert.Equal(t, 183, d.Paths.PathItems.Len())
}
func TestDigitalOceanAsDocFromMain(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/digitalocean.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err error
baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification")
config := datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
BaseURL: baseURL,
}
config.Logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelError,
}))
if os.Getenv("GH_PAT") != "" {
client := &http.Client{
Timeout: time.Second * 60,
}
config.RemoteURLHandler = func(url string) (*http.Response, error) {
request, _ := http.NewRequest(http.MethodGet, url, nil)
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("GITHUB_TOKEN")))
return client.Do(request)
}
}
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &config)
if err != nil {
for e := range err {
fmt.Println(err[e])
er := utils.UnwrapErrors(err)
for e := range er {
fmt.Printf("Reported Error: %s\n", er[e])
}
panic("broken something")
}
d := NewDocument(lowDoc)
assert.NotNil(t, d)
assert.Equal(t, 183, orderedmap.Len(d.Paths.PathItems))
}
func TestPetstoreAsDoc(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
if err != nil {
panic("broken something")
}
@@ -455,16 +549,15 @@ func TestPetstoreAsDoc(t *testing.T) {
func TestCircularReferencesDoc(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/circular-tests.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
assert.Len(t, err, 3)
d := NewDocument(lowDoc)
lDoc, err := lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
assert.Len(t, utils.UnwrapErrors(err), 3)
d := NewDocument(lDoc)
assert.Len(t, d.Components.Schemas, 9)
assert.Len(t, d.Index.GetCircularReferences(), 3)
}
func TestDocument_MarshalYAML(t *testing.T) {
// create a new document
initTest()
h := NewDocument(lowDoc)
@@ -473,20 +566,18 @@ func TestDocument_MarshalYAML(t *testing.T) {
r, _ := h.Render()
info, _ := datamodel.ExtractSpecInfo(r)
lDoc, e := lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
lDoc, e := lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
assert.Nil(t, e)
highDoc := NewDocument(lDoc)
testBurgerShop(t, highDoc, false)
}
func TestDocument_MarshalIndention(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/single-definition.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
lowDoc, _ = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
lowDoc, _ = lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
highDoc := NewDocument(lowDoc)
rendered := highDoc.RenderWithIndention(2)
@@ -496,15 +587,13 @@ func TestDocument_MarshalIndention(t *testing.T) {
rendered = highDoc.RenderWithIndention(4)
assert.NotEqual(t, string(data), strings.TrimSpace(string(rendered)))
}
func TestDocument_MarshalIndention_Error(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/single-definition.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
lowDoc, _ = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
lowDoc, _ = lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
highDoc := NewDocument(lowDoc)
rendered := highDoc.RenderWithIndention(2)
@@ -514,15 +603,13 @@ func TestDocument_MarshalIndention_Error(t *testing.T) {
rendered = highDoc.RenderWithIndention(4)
assert.NotEqual(t, string(data), strings.TrimSpace(string(rendered)))
}
func TestDocument_MarshalJSON(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data)
lowDoc, _ = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
lowDoc, _ = lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
highDoc := NewDocument(lowDoc)
@@ -530,7 +617,7 @@ func TestDocument_MarshalJSON(t *testing.T) {
// now read back in the JSON
info, _ = datamodel.ExtractSpecInfo(rendered)
lowDoc, _ = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
lowDoc, _ = lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
newDoc := NewDocument(lowDoc)
assert.Equal(t, orderedmap.Len(newDoc.Paths.PathItems), orderedmap.Len(highDoc.Paths.PathItems))
@@ -538,7 +625,6 @@ func TestDocument_MarshalJSON(t *testing.T) {
}
func TestDocument_MarshalYAMLInline(t *testing.T) {
// create a new document
initTest()
h := NewDocument(lowDoc)
@@ -547,16 +633,14 @@ func TestDocument_MarshalYAMLInline(t *testing.T) {
r, _ := h.RenderInline()
info, _ := datamodel.ExtractSpecInfo(r)
lDoc, e := lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
lDoc, e := lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
assert.Nil(t, e)
highDoc := NewDocument(lDoc)
testBurgerShop(t, highDoc, false)
}
func TestDocument_MarshalYAML_TestRefs(t *testing.T) {
// create a new document
yml := `openapi: 3.1.0
paths:
@@ -617,7 +701,7 @@ components:
numPatties: 1`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
@@ -634,7 +718,6 @@ components:
}
func TestDocument_MarshalYAML_TestParamRefs(t *testing.T) {
// create a new document
yml := `openapi: 3.1.0
paths:
@@ -671,7 +754,7 @@ components:
required: true`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
@@ -687,7 +770,6 @@ components:
}
func TestDocument_MarshalYAML_TestModifySchemas(t *testing.T) {
// create a new document
yml := `openapi: 3.1.0
components:
@@ -700,7 +782,7 @@ components:
`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,

View File

@@ -4,7 +4,8 @@
package v3
import (
"io/ioutil"
"context"
"os"
"strings"
"testing"
@@ -18,9 +19,9 @@ import (
func TestMediaType_MarshalYAMLInline(t *testing.T) {
// load the petstore spec
data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json")
data, _ := os.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
var err error
lowDoc, err = v3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
if err != nil {
panic("broken something")
@@ -108,9 +109,9 @@ example: testing a nice mutation`
func TestMediaType_MarshalYAML(t *testing.T) {
// load the petstore spec
data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json")
data, _ := os.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
var err error
lowDoc, err = v3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
if err != nil {
panic("broken something")
@@ -161,7 +162,7 @@ func TestMediaType_Examples(t *testing.T) {
var n v3.MediaType
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewMediaType(&n)

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"strings"
"testing"
@@ -44,7 +45,7 @@ clientCredentials:
var n v3.OAuthFlows
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewOAuthFlows(&n)

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"strings"
"testing"
@@ -43,7 +44,7 @@ callbacks:
var n v3.Operation
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewOperation(&n)
@@ -140,7 +141,7 @@ security: []`
var n v3.Operation
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewOperation(&n)
@@ -158,7 +159,7 @@ func TestOperation_NoSecurity(t *testing.T) {
var n v3.Operation
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewOperation(&n)

View File

@@ -5,7 +5,8 @@ package v3
import (
"fmt"
"io/ioutil"
"github.com/pb33f/libopenapi/utils"
"os"
"github.com/pb33f/libopenapi/datamodel"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
@@ -15,22 +16,19 @@ import (
// An example of how to create a new high-level OpenAPI 3+ document from an OpenAPI specification.
func Example_createHighLevelOpenAPIDocument() {
// Load in an OpenAPI 3+ specification as a byte slice.
data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json")
data, _ := os.ReadFile("../../../test_specs/petstorev3.json")
// Create a new *datamodel.SpecInfo from bytes.
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
var err error
// Create a new low-level Document, capture any errors thrown during creation.
lowDoc, err = lowv3.CreateDocument(info)
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
// Get upset if any errors were thrown.
if len(err) > 0 {
for i := range err {
fmt.Printf("error: %e", err[i])
}
panic("something went wrong")
for i := range utils.UnwrapErrors(err) {
fmt.Printf("error: %v", i)
}
// Create a high-level Document from the low-level one.

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"strings"
"testing"
@@ -28,7 +29,7 @@ func TestPathItem(t *testing.T) {
var n v3.PathItem
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewPathItem(&n)
@@ -62,7 +63,7 @@ trace:
var n v3.PathItem
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewPathItem(&n)

View File

@@ -6,6 +6,7 @@ package v3
import (
"sort"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/low"
v3low "github.com/pb33f/libopenapi/datamodel/low/v3"
@@ -45,7 +46,7 @@ func NewPaths(paths *v3low.Paths) *Paths {
items.Set(value.key, value.value)
return nil
}
_ = orderedmap.TranslateMapParallel[low.KeyReference[string], low.ValueReference[*v3low.PathItem], pathItemResult](
_ = datamodel.TranslateMapParallel[low.KeyReference[string], low.ValueReference[*v3low.PathItem], pathItemResult](
paths.PathItems, translateFunc, resultFunc,
)
p.PathItems = items

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"strings"
"testing"
@@ -37,7 +38,7 @@ func TestPaths_MarshalYAML(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
high := NewPaths(&n)
@@ -89,7 +90,7 @@ func TestPaths_MarshalYAMLInline(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
high := NewPaths(&n)

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"strings"
"testing"
@@ -39,7 +40,7 @@ links:
var n v3.Response
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponse(&n)
@@ -70,7 +71,7 @@ links:
var n v3.Response
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponse(&n)
@@ -98,7 +99,7 @@ links:
var n v3.Response
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponse(&n)

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"sort"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high"
lowbase "github.com/pb33f/libopenapi/datamodel/low"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
@@ -47,14 +48,9 @@ func NewResponses(responses *low.Responses) *Responses {
}
codes := orderedmap.New[string, *Response]()
type respRes struct {
code string
resp *Response
}
translateFunc := func(pair orderedmap.Pair[lowbase.KeyReference[string], lowbase.ValueReference[*low.Response]]) (asyncResult[*Response], error) {
return asyncResult[*Response]{
key: pair.Key().Value,
key: pair.Key().Value,
result: NewResponse(pair.Value().Value),
}, nil
}
@@ -62,7 +58,7 @@ func NewResponses(responses *low.Responses) *Responses {
codes.Set(value.key, value.result)
return nil
}
_ = orderedmap.TranslateMapParallel[lowbase.KeyReference[string], lowbase.ValueReference[*low.Response], asyncResult[*Response]](responses.Codes, translateFunc, resultFunc)
_ = datamodel.TranslateMapParallel[lowbase.KeyReference[string], lowbase.ValueReference[*low.Response]](responses.Codes, translateFunc, resultFunc)
r.Codes = codes
return r
}
@@ -126,8 +122,10 @@ func (r *Responses) MarshalYAML() (interface{}, error) {
label = extNode.Content[u].Value
continue
}
mapped = append(mapped, &responseItem{nil, label,
extNode.Content[u].Line, extNode.Content[u]})
mapped = append(mapped, &responseItem{
nil, label,
extNode.Content[u].Line, extNode.Content[u],
})
}
}
@@ -183,8 +181,10 @@ func (r *Responses) MarshalYAMLInline() (interface{}, error) {
label = extNode.Content[u].Value
continue
}
mapped = append(mapped, &responseItem{nil, label,
extNode.Content[u].Line, extNode.Content[u]})
mapped = append(mapped, &responseItem{
nil, label,
extNode.Content[u].Line, extNode.Content[u],
})
}
}

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"strings"
"testing"
@@ -30,7 +31,7 @@ func TestNewResponses(t *testing.T) {
var n v3.Responses
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponses(&n)
@@ -60,7 +61,7 @@ func TestResponses_MarshalYAML(t *testing.T) {
var n v3.Responses
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponses(&n)
@@ -90,7 +91,7 @@ func TestResponses_MarshalYAMLInline(t *testing.T) {
var n v3.Responses
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponses(&n)

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index"
@@ -31,7 +32,7 @@ func TestSecurityScheme_MarshalYAML(t *testing.T) {
var n v3.SecurityScheme
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewSecurityScheme(&n)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"crypto/sha256"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
@@ -23,7 +24,7 @@ type Contact struct {
}
// Build is not implemented for Contact (there is nothing to build).
func (c *Contact) Build(_, _ *yaml.Node, _ *index.SpecIndex) error {
func (c *Contact) Build(_ context.Context, _, _ *yaml.Node, _ *index.SpecIndex) error {
c.Reference = new(low.Reference)
// not implemented.
return nil

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"crypto/sha256"
"fmt"
"sort"
@@ -61,7 +62,7 @@ func (ex *Example) Hash() [32]byte {
}
// Build extracts extensions and example value
func (ex *Example) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (ex *Example) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
ex.Reference = new(low.Reference)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -26,7 +27,7 @@ x-cake: hot`
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value)
@@ -52,7 +53,7 @@ x-cake: hot`
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value)
@@ -79,7 +80,7 @@ value:
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value)
@@ -110,7 +111,7 @@ value:
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value)
@@ -142,7 +143,7 @@ func TestExample_Build_Success_MergeNode(t *testing.T) {
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value)
@@ -237,8 +238,8 @@ x-burger: nice`
var rDoc Example
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(nil, lNode.Content[0], nil)
_ = rDoc.Build(nil, rNode.Content[0], nil)
_ = lDoc.Build(context.Background(), nil, lNode.Content[0], nil)
_ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil)
assert.Equal(t, lDoc.Hash(), rDoc.Hash())
assert.Len(t, lDoc.GetExtensions(), 1)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -33,7 +34,7 @@ func (ex *ExternalDoc) FindExtension(ext string) *low.ValueReference[any] {
}
// Build will extract extensions from the ExternalDoc instance.
func (ex *ExternalDoc) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (ex *ExternalDoc) Build(_ context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
ex.Reference = new(low.Reference)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -23,7 +24,7 @@ func TestExternalDoc_FindExtension(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "cake", n.FindExtension("x-fish").Value)
@@ -44,7 +45,7 @@ x-b33f: princess`
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "https://pb33f.io", n.URL.Value)
assert.Equal(t, "the ranch", n.Description.Value)
@@ -73,8 +74,8 @@ description: the ranch`
var rDoc ExternalDoc
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(nil, lNode.Content[0], nil)
_ = rDoc.Build(nil, rNode.Content[0], nil)
_ = lDoc.Build(context.Background(), nil, lNode.Content[0], nil)
_ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil)
assert.Equal(t, lDoc.Hash(), rDoc.Hash())
assert.Len(t, lDoc.GetExtensions(), 1)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/utils"
@@ -45,18 +46,18 @@ func (i *Info) GetExtensions() map[low.KeyReference[string]]low.ValueReference[a
}
// Build will extract out the Contact and Info objects from the supplied root node.
func (i *Info) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (i *Info) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
i.Reference = new(low.Reference)
i.Extensions = low.ExtractExtensions(root)
// extract contact
contact, _ := low.ExtractObject[*Contact](ContactLabel, root, idx)
contact, _ := low.ExtractObject[*Contact](ctx, ContactLabel, root, idx)
i.Contact = contact
// extract license
lic, _ := low.ExtractObject[*License](LicenseLabel, root, idx)
lic, _ := low.ExtractObject[*License](ctx, LicenseLabel, root, idx)
i.License = lic
return nil
}

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -34,7 +35,7 @@ x-cli-name: pizza cli`
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "pizza", n.Title.Value)
@@ -61,13 +62,13 @@ x-cli-name: pizza cli`
func TestContact_Build(t *testing.T) {
n := &Contact{}
k := n.Build(nil, nil, nil)
k := n.Build(context.Background(), nil, nil, nil)
assert.Nil(t, k)
}
func TestLicense_Build(t *testing.T) {
n := &License{}
k := n.Build(nil, nil, nil)
k := n.Build(context.Background(), nil, nil, nil)
assert.Nil(t, k)
}
@@ -107,8 +108,8 @@ x-b33f: princess`
var rDoc Info
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(nil, lNode.Content[0], nil)
_ = rDoc.Build(nil, rNode.Content[0], nil)
_ = lDoc.Build(context.Background(), nil, lNode.Content[0], nil)
_ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil)
assert.Equal(t, lDoc.Hash(), rDoc.Hash())
}

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -25,7 +26,7 @@ type License struct {
}
// Build out a license, complain if both a URL and identifier are present as they are mutually exclusive
func (l *License) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (l *License) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
l.Reference = new(low.Reference)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
@@ -70,7 +71,7 @@ description: the ranch`
var lDoc License
err := low.BuildModel(lNode.Content[0], &lDoc)
err = lDoc.Build(nil, lNode.Content[0], nil)
err = lDoc.Build(context.Background(), nil, lNode.Content[0], nil)
assert.Error(t, err)
assert.Equal(t, "license cannot have both a URL and an identifier, they are mutually exclusive", err.Error())

View File

@@ -1,9 +1,9 @@
package base
import (
"context"
"crypto/sha256"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
@@ -101,7 +101,7 @@ type Schema struct {
PatternProperties low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]
PropertyNames low.NodeReference[*SchemaProxy]
UnevaluatedItems low.NodeReference[*SchemaProxy]
UnevaluatedProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, *bool]]
UnevaluatedProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]
Anchor low.NodeReference[string]
// Compatible with all versions
@@ -122,7 +122,7 @@ type Schema struct {
Enum low.NodeReference[[]low.ValueReference[any]]
Not low.NodeReference[*SchemaProxy]
Properties low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]
AdditionalProperties low.NodeReference[any]
AdditionalProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]
Description low.NodeReference[string]
ContentEncoding low.NodeReference[string]
ContentMediaType low.NodeReference[string]
@@ -139,6 +139,9 @@ type Schema struct {
// Parent Proxy refers back to the low level SchemaProxy that is proxying this schema.
ParentProxy *SchemaProxy
// Index is a reference to the SpecIndex that was used to build this schema.
Index *index.SpecIndex
*low.Reference
}
@@ -190,53 +193,7 @@ func (s *Schema) Hash() [32]byte {
d = append(d, fmt.Sprint(s.MinProperties.Value))
}
if !s.AdditionalProperties.IsEmpty() {
// check type of properties, if we have a low level map, we need to hash the values in a repeatable
// order.
to := reflect.TypeOf(s.AdditionalProperties.Value)
vo := reflect.ValueOf(s.AdditionalProperties.Value)
var values []string
switch to.Kind() {
case reflect.Slice:
for i := 0; i < vo.Len(); i++ {
vn := vo.Index(i).Interface()
if jh, ok := vn.(low.HasValueUnTyped); ok {
vn = jh.GetValueUntyped()
fg := reflect.TypeOf(vn)
gf := reflect.ValueOf(vn)
if fg.Kind() == reflect.Map {
for _, ky := range gf.MapKeys() {
hu := ky.Interface()
values = append(values, fmt.Sprintf("%s:%s", hu, low.GenerateHashString(gf.MapIndex(ky).Interface())))
}
continue
}
values = append(values, fmt.Sprintf("%d:%s", i, low.GenerateHashString(vn)))
}
}
sort.Strings(values)
d = append(d, strings.Join(values, "||"))
case reflect.Map:
for _, k := range vo.MapKeys() {
var x string
var l int
var v any
// extract key
if o, ok := k.Interface().(low.HasKeyNode); ok {
x = o.GetKeyNode().Value
l = o.GetKeyNode().Line
v = vo.MapIndex(k).Interface().(low.HasValueNodeUntyped).GetValueNode().Value
}
values = append(values, fmt.Sprintf("%d:%s:%s", l, x, low.GenerateHashString(v)))
}
sort.Strings(values)
d = append(d, strings.Join(values, "||"))
default:
d = append(d, low.GenerateHashString(s.AdditionalProperties.Value))
}
d = append(d, low.GenerateHashString(s.AdditionalProperties.Value))
}
if !s.Description.IsEmpty() {
d = append(d, fmt.Sprint(s.Description.Value))
@@ -535,12 +492,13 @@ func (s *Schema) GetExtensions() map[low.KeyReference[string]]low.ValueReference
// - UnevaluatedItems
// - UnevaluatedProperties
// - Anchor
func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
s.Reference = new(low.Reference)
s.Index = idx
if h, _, _ := utils.IsNodeRefValue(root); h {
ref, err := low.LocateRefNode(root, idx)
ref, _, err := low.LocateRefNode(root, idx)
if ref != nil {
root = ref
if err != nil {
@@ -591,20 +549,43 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
// determine exclusive minimum type, bool (3.0) or int (3.1)
_, exMinLabel, exMinValue := utils.FindKeyNodeFullTop(ExclusiveMinimumLabel, root.Content)
if exMinValue != nil {
if utils.IsNodeBoolValue(exMinValue) {
val, _ := strconv.ParseBool(exMinValue.Value)
s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMinLabel,
ValueNode: exMinValue,
Value: &SchemaDynamicValue[bool, float64]{N: 0, A: val},
// if there is an index, determine if this a 3.0 or 3.1 schema
if idx != nil {
if idx.GetConfig().SpecInfo.VersionNumeric == 3.1 {
val, _ := strconv.ParseFloat(exMinValue.Value, 64)
s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMinLabel,
ValueNode: exMinValue,
Value: &SchemaDynamicValue[bool, float64]{N: 1, B: val},
}
}
}
if utils.IsNodeIntValue(exMinValue) {
val, _ := strconv.ParseFloat(exMinValue.Value, 64)
s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMinLabel,
ValueNode: exMinValue,
Value: &SchemaDynamicValue[bool, float64]{N: 1, B: val},
if idx.GetConfig().SpecInfo.VersionNumeric <= 3.0 {
val, _ := strconv.ParseBool(exMinValue.Value)
s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMinLabel,
ValueNode: exMinValue,
Value: &SchemaDynamicValue[bool, float64]{N: 0, A: val},
}
}
} else {
// there is no index, so we have to determine the type based on the value
if utils.IsNodeBoolValue(exMinValue) {
val, _ := strconv.ParseBool(exMinValue.Value)
s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMinLabel,
ValueNode: exMinValue,
Value: &SchemaDynamicValue[bool, float64]{N: 0, A: val},
}
}
if utils.IsNodeIntValue(exMinValue) {
val, _ := strconv.ParseFloat(exMinValue.Value, 64)
s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMinLabel,
ValueNode: exMinValue,
Value: &SchemaDynamicValue[bool, float64]{N: 1, B: val},
}
}
}
}
@@ -612,20 +593,43 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
// determine exclusive maximum type, bool (3.0) or int (3.1)
_, exMaxLabel, exMaxValue := utils.FindKeyNodeFullTop(ExclusiveMaximumLabel, root.Content)
if exMaxValue != nil {
if utils.IsNodeBoolValue(exMaxValue) {
val, _ := strconv.ParseBool(exMaxValue.Value)
s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMaxLabel,
ValueNode: exMaxValue,
Value: &SchemaDynamicValue[bool, float64]{N: 0, A: val},
// if there is an index, determine if this a 3.0 or 3.1 schema
if idx != nil {
if idx.GetConfig().SpecInfo.VersionNumeric == 3.1 {
val, _ := strconv.ParseFloat(exMaxValue.Value, 64)
s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMaxLabel,
ValueNode: exMaxValue,
Value: &SchemaDynamicValue[bool, float64]{N: 1, B: val},
}
}
}
if utils.IsNodeIntValue(exMaxValue) {
val, _ := strconv.ParseFloat(exMaxValue.Value, 64)
s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMaxLabel,
ValueNode: exMaxValue,
Value: &SchemaDynamicValue[bool, float64]{N: 1, B: val},
if idx.GetConfig().SpecInfo.VersionNumeric <= 3.0 {
val, _ := strconv.ParseBool(exMaxValue.Value)
s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMaxLabel,
ValueNode: exMaxValue,
Value: &SchemaDynamicValue[bool, float64]{N: 0, A: val},
}
}
} else {
// there is no index, so we have to determine the type based on the value
if utils.IsNodeBoolValue(exMaxValue) {
val, _ := strconv.ParseBool(exMaxValue.Value)
s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMaxLabel,
ValueNode: exMaxValue,
Value: &SchemaDynamicValue[bool, float64]{N: 0, A: val},
}
}
if utils.IsNodeIntValue(exMaxValue) {
val, _ := strconv.ParseFloat(exMaxValue.Value, 64)
s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMaxLabel,
ValueNode: exMaxValue,
Value: &SchemaDynamicValue[bool, float64]{N: 1, B: val},
}
}
}
}
@@ -668,77 +672,24 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
}
}
_, addPLabel, addPNode := utils.FindKeyNodeFullTop(AdditionalPropertiesLabel, root.Content)
if addPNode != nil {
if utils.IsNodeMap(addPNode) || utils.IsNodeArray(addPNode) {
// check if this is a reference, or an inline schema.
isRef, _, _ := utils.IsNodeRefValue(addPNode)
var sp *SchemaProxy
// now check if this object has a 'type' if so, it's a schema, if not... it's a random
// object, and we should treat it as a raw map.
if _, v := utils.FindKeyNodeTop(TypeLabel, addPNode.Content); v != nil {
sp = &SchemaProxy{
kn: addPLabel,
vn: addPNode,
idx: idx,
}
}
if isRef {
_, vn := utils.FindKeyNodeTop("$ref", addPNode.Content)
sp = &SchemaProxy{
kn: addPLabel,
vn: addPNode,
idx: idx,
isReference: true,
referenceLookup: vn.Value,
}
}
// if this is a reference, or a schema, we're done.
if sp != nil {
s.AdditionalProperties = low.NodeReference[any]{Value: sp, KeyNode: addPLabel, ValueNode: addPNode}
} else {
// if this is a map, collect all the keys and values.
if utils.IsNodeMap(addPNode) {
addProps := make(map[low.KeyReference[string]]low.ValueReference[any])
var label string
for g := range addPNode.Content {
if g%2 == 0 {
label = addPNode.Content[g].Value
continue
} else {
addProps[low.KeyReference[string]{Value: label, KeyNode: addPNode.Content[g-1]}] = low.ValueReference[any]{Value: addPNode.Content[g].Value, ValueNode: addPNode.Content[g]}
}
}
s.AdditionalProperties = low.NodeReference[any]{Value: addProps, KeyNode: addPLabel, ValueNode: addPNode}
}
// if the node is an array, extract everything into a trackable structure
if utils.IsNodeArray(addPNode) {
var addProps []low.ValueReference[any]
// if this is an array or maps, encode the map items correctly.
for i := range addPNode.Content {
if utils.IsNodeMap(addPNode.Content[i]) {
var prop map[string]any
_ = addPNode.Content[i].Decode(&prop)
addProps = append(addProps,
low.ValueReference[any]{Value: prop, ValueNode: addPNode.Content[i]})
} else {
addProps = append(addProps,
low.ValueReference[any]{Value: addPNode.Content[i].Value, ValueNode: addPNode.Content[i]})
}
}
s.AdditionalProperties = low.NodeReference[any]{Value: addProps, KeyNode: addPLabel, ValueNode: addPNode}
}
}
// check additionalProperties type for schema or bool
addPropsIsBool := false
addPropsBoolValue := true
_, addPLabel, addPValue := utils.FindKeyNodeFullTop(AdditionalPropertiesLabel, root.Content)
if addPValue != nil {
if utils.IsNodeBoolValue(addPValue) {
addPropsIsBool = true
addPropsBoolValue, _ = strconv.ParseBool(addPValue.Value)
}
if utils.IsNodeBoolValue(addPNode) {
b, _ := strconv.ParseBool(addPNode.Value)
s.AdditionalProperties = low.NodeReference[any]{Value: b, KeyNode: addPLabel, ValueNode: addPNode}
}
if addPropsIsBool {
s.AdditionalProperties = low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]{
Value: &SchemaDynamicValue[*SchemaProxy, bool]{
B: addPropsBoolValue,
N: 1,
},
KeyNode: addPLabel,
ValueNode: addPValue,
}
}
@@ -755,7 +706,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
if extDocNode != nil {
var exDoc ExternalDoc
_ = low.BuildModel(extDocNode, &exDoc)
_ = exDoc.Build(extDocLabel, extDocNode, idx) // throws no errors, can't check for one.
_ = exDoc.Build(ctx, extDocLabel, extDocNode, idx) // throws no errors, can't check for one.
s.ExternalDocs = low.NodeReference[*ExternalDoc]{Value: &exDoc, KeyNode: extDocLabel, ValueNode: extDocNode}
}
@@ -770,7 +721,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
}
// handle properties
props, err := buildPropertyMap(root, idx, PropertiesLabel)
props, err := buildPropertyMap(ctx, root, idx, PropertiesLabel)
if err != nil {
return err
}
@@ -779,7 +730,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
}
// handle dependent schemas
props, err = buildPropertyMap(root, idx, DependentSchemasLabel)
props, err = buildPropertyMap(ctx, root, idx, DependentSchemasLabel)
if err != nil {
return err
}
@@ -788,7 +739,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
}
// handle pattern properties
props, err = buildPropertyMap(root, idx, PatternPropertiesLabel)
props, err = buildPropertyMap(ctx, root, idx, PatternPropertiesLabel)
if err != nil {
return err
}
@@ -828,9 +779,9 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
}
}
if unevalIsBool {
s.UnevaluatedProperties = low.NodeReference[*SchemaDynamicValue[*SchemaProxy, *bool]]{
Value: &SchemaDynamicValue[*SchemaProxy, *bool]{
B: &unevalBoolValue,
s.UnevaluatedProperties = low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]{
Value: &SchemaDynamicValue[*SchemaProxy, bool]{
B: unevalBoolValue,
N: 1,
},
KeyNode: unevalLabel,
@@ -839,7 +790,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
}
var allOf, anyOf, oneOf, prefixItems []low.ValueReference[*SchemaProxy]
var items, not, contains, sif, selse, sthen, propertyNames, unevalItems, unevalProperties low.ValueReference[*SchemaProxy]
var items, not, contains, sif, selse, sthen, propertyNames, unevalItems, unevalProperties, addProperties low.ValueReference[*SchemaProxy]
_, allOfLabel, allOfValue := utils.FindKeyNodeFullTop(AllOfLabel, root.Content)
_, anyOfLabel, anyOfValue := utils.FindKeyNodeFullTop(AnyOfLabel, root.Content)
@@ -853,6 +804,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
_, propNamesLabel, propNamesValue := utils.FindKeyNodeFullTop(PropertyNamesLabel, root.Content)
_, unevalItemsLabel, unevalItemsValue := utils.FindKeyNodeFullTop(UnevaluatedItemsLabel, root.Content)
_, unevalPropsLabel, unevalPropsValue := utils.FindKeyNodeFullTop(UnevaluatedPropertiesLabel, root.Content)
_, addPropsLabel, addPropsValue := utils.FindKeyNodeFullTop(AdditionalPropertiesLabel, root.Content)
errorChan := make(chan error)
allOfChan := make(chan schemaProxyBuildResult)
@@ -868,6 +820,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
propNamesChan := make(chan schemaProxyBuildResult)
unevalItemsChan := make(chan schemaProxyBuildResult)
unevalPropsChan := make(chan schemaProxyBuildResult)
addPropsChan := make(chan schemaProxyBuildResult)
totalBuilds := countSubSchemaItems(allOfValue) +
countSubSchemaItems(anyOfValue) +
@@ -875,52 +828,56 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
countSubSchemaItems(prefixItemsValue)
if allOfValue != nil {
go buildSchema(allOfChan, allOfLabel, allOfValue, errorChan, idx)
go buildSchema(ctx, allOfChan, allOfLabel, allOfValue, errorChan, idx)
}
if anyOfValue != nil {
go buildSchema(anyOfChan, anyOfLabel, anyOfValue, errorChan, idx)
go buildSchema(ctx, anyOfChan, anyOfLabel, anyOfValue, errorChan, idx)
}
if oneOfValue != nil {
go buildSchema(oneOfChan, oneOfLabel, oneOfValue, errorChan, idx)
go buildSchema(ctx, oneOfChan, oneOfLabel, oneOfValue, errorChan, idx)
}
if prefixItemsValue != nil {
go buildSchema(prefixItemsChan, prefixItemsLabel, prefixItemsValue, errorChan, idx)
go buildSchema(ctx, prefixItemsChan, prefixItemsLabel, prefixItemsValue, errorChan, idx)
}
if notValue != nil {
totalBuilds++
go buildSchema(notChan, notLabel, notValue, errorChan, idx)
go buildSchema(ctx, notChan, notLabel, notValue, errorChan, idx)
}
if containsValue != nil {
totalBuilds++
go buildSchema(containsChan, containsLabel, containsValue, errorChan, idx)
go buildSchema(ctx, containsChan, containsLabel, containsValue, errorChan, idx)
}
if !itemsIsBool && itemsValue != nil {
totalBuilds++
go buildSchema(itemsChan, itemsLabel, itemsValue, errorChan, idx)
go buildSchema(ctx, itemsChan, itemsLabel, itemsValue, errorChan, idx)
}
if sifValue != nil {
totalBuilds++
go buildSchema(ifChan, sifLabel, sifValue, errorChan, idx)
go buildSchema(ctx, ifChan, sifLabel, sifValue, errorChan, idx)
}
if selseValue != nil {
totalBuilds++
go buildSchema(elseChan, selseLabel, selseValue, errorChan, idx)
go buildSchema(ctx, elseChan, selseLabel, selseValue, errorChan, idx)
}
if sthenValue != nil {
totalBuilds++
go buildSchema(thenChan, sthenLabel, sthenValue, errorChan, idx)
go buildSchema(ctx, thenChan, sthenLabel, sthenValue, errorChan, idx)
}
if propNamesValue != nil {
totalBuilds++
go buildSchema(propNamesChan, propNamesLabel, propNamesValue, errorChan, idx)
go buildSchema(ctx, propNamesChan, propNamesLabel, propNamesValue, errorChan, idx)
}
if unevalItemsValue != nil {
totalBuilds++
go buildSchema(unevalItemsChan, unevalItemsLabel, unevalItemsValue, errorChan, idx)
go buildSchema(ctx, unevalItemsChan, unevalItemsLabel, unevalItemsValue, errorChan, idx)
}
if !unevalIsBool && unevalPropsValue != nil {
totalBuilds++
go buildSchema(unevalPropsChan, unevalPropsLabel, unevalPropsValue, errorChan, idx)
go buildSchema(ctx, unevalPropsChan, unevalPropsLabel, unevalPropsValue, errorChan, idx)
}
if !addPropsIsBool && addPropsValue != nil {
totalBuilds++
go buildSchema(ctx, addPropsChan, addPropsLabel, addPropsValue, errorChan, idx)
}
completeCount := 0
@@ -967,6 +924,9 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
case r := <-unevalPropsChan:
completeCount++
unevalProperties = r.v
case r := <-addPropsChan:
completeCount++
addProperties = r.v
}
}
@@ -1057,22 +1017,31 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
}
}
if !unevalIsBool && !unevalProperties.IsEmpty() {
s.UnevaluatedProperties = low.NodeReference[*SchemaDynamicValue[*SchemaProxy, *bool]]{
Value: &SchemaDynamicValue[*SchemaProxy, *bool]{
s.UnevaluatedProperties = low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]{
Value: &SchemaDynamicValue[*SchemaProxy, bool]{
A: unevalProperties.Value,
},
KeyNode: unevalPropsLabel,
ValueNode: unevalPropsValue,
}
}
if !addPropsIsBool && !addProperties.IsEmpty() {
s.AdditionalProperties = low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]{
Value: &SchemaDynamicValue[*SchemaProxy, bool]{
A: addProperties.Value,
},
KeyNode: addPropsLabel,
ValueNode: addPropsValue,
}
}
return nil
}
func buildPropertyMap(root *yaml.Node, idx *index.SpecIndex, label string) (*low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]], error) {
func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex, label string) (*low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]], error) {
// for property, build in a new thread!
bChan := make(chan schemaProxyBuildResult)
buildProperty := func(label *yaml.Node, value *yaml.Node, c chan schemaProxyBuildResult, isRef bool,
buildProperty := func(ctx context.Context, label *yaml.Node, value *yaml.Node, c chan schemaProxyBuildResult, isRef bool,
refString string,
) {
c <- schemaProxyBuildResult{
@@ -1081,7 +1050,7 @@ func buildPropertyMap(root *yaml.Node, idx *index.SpecIndex, label string) (*low
Value: label.Value,
},
v: low.ValueReference[*SchemaProxy]{
Value: &SchemaProxy{kn: label, vn: value, idx: idx, isReference: isRef, referenceLookup: refString},
Value: &SchemaProxy{ctx: ctx, kn: label, vn: value, idx: idx, isReference: isRef, referenceLookup: refString},
ValueNode: value,
},
}
@@ -1098,22 +1067,24 @@ func buildPropertyMap(root *yaml.Node, idx *index.SpecIndex, label string) (*low
continue
}
foundCtx := ctx
// check our prop isn't reference
isRef := false
refString := ""
if h, _, l := utils.IsNodeRefValue(prop); h {
ref, _ := low.LocateRefNode(prop, idx)
ref, _, _, fctx := low.LocateRefNodeWithContext(ctx, prop, idx)
if ref != nil {
isRef = true
prop = ref
refString = l
foundCtx = fctx
} else {
return nil, fmt.Errorf("schema properties build failed: cannot find reference %s, line %d, col %d",
prop.Content[1].Value, prop.Content[1].Line, prop.Content[1].Column)
}
}
totalProps++
go buildProperty(currentProp, prop, bChan, isRef, refString)
go buildProperty(foundCtx, currentProp, prop, bChan, isRef, refString)
}
completedProps := 0
for completedProps < totalProps {
@@ -1155,7 +1126,7 @@ func (s *Schema) extractExtensions(root *yaml.Node) {
}
// build out a child schema for parent schema.
func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml.Node, errors chan error, idx *index.SpecIndex) {
func buildSchema(ctx context.Context, schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml.Node, errors chan error, idx *index.SpecIndex) {
if valueNode != nil {
type buildResult struct {
res *low.ValueReference[*SchemaProxy]
@@ -1165,7 +1136,7 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
syncChan := make(chan buildResult)
// build out a SchemaProxy for every sub-schema.
build := func(kn *yaml.Node, vn *yaml.Node, schemaIdx int, c chan buildResult,
build := func(pctx context.Context, kn *yaml.Node, vn *yaml.Node, schemaIdx int, c chan buildResult,
isRef bool, refLocation string,
) {
// a proxy design works best here. polymorphism, pretty much guarantees that a sub-schema can
@@ -1178,6 +1149,7 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
sp.kn = kn
sp.vn = vn
sp.idx = idx
sp.ctx = pctx
if isRef {
sp.referenceLookup = refLocation
sp.isReference = true
@@ -1194,13 +1166,15 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
isRef := false
refLocation := ""
foundCtx := ctx
if utils.IsNodeMap(valueNode) {
h := false
if h, _, refLocation = utils.IsNodeRefValue(valueNode); h {
isRef = true
ref, _ := low.LocateRefNode(valueNode, idx)
ref, _, _, fctx := low.LocateRefNodeWithContext(ctx, valueNode, idx)
if ref != nil {
valueNode = ref
foundCtx = fctx
} else {
errors <- fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
valueNode.Content[1].Value, valueNode.Content[1].Line, valueNode.Content[1].Column)
@@ -1209,7 +1183,7 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
// this only runs once, however to keep things consistent, it makes sense to use the same async method
// that arrays will use.
go build(labelNode, valueNode, -1, syncChan, isRef, refLocation)
go build(foundCtx, labelNode, valueNode, -1, syncChan, isRef, refLocation)
select {
case r := <-syncChan:
schemas <- schemaProxyBuildResult{
@@ -1220,8 +1194,7 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
v: *r.res,
}
}
}
if utils.IsNodeArray(valueNode) {
} else if utils.IsNodeArray(valueNode) {
refBuilds := 0
results := make([]*low.ValueReference[*SchemaProxy], len(valueNode.Content))
@@ -1230,9 +1203,10 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
h := false
if h, _, refLocation = utils.IsNodeRefValue(vn); h {
isRef = true
ref, _ := low.LocateRefNode(vn, idx)
ref, _, _, fctx := low.LocateRefNodeWithContext(ctx, vn, idx)
if ref != nil {
vn = ref
foundCtx = fctx
} else {
err := fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
vn.Content[1].Value, vn.Content[1].Line, vn.Content[1].Column)
@@ -1241,7 +1215,7 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
}
}
refBuilds++
go build(vn, vn, i, syncChan, isRef, refLocation)
go build(foundCtx, vn, vn, i, syncChan, isRef, refLocation)
}
completedBuilds := 0
@@ -1262,6 +1236,8 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
v: *r,
}
}
} else {
errors <- fmt.Errorf("build schema failed: unexpected node type: %s, line %d, col %d", valueNode.Tag, valueNode.Line, valueNode.Column)
}
}
}
@@ -1269,22 +1245,29 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
// ExtractSchema will return a pointer to a NodeReference that contains a *SchemaProxy if successful. The function
// will specifically look for a key node named 'schema' and extract the value mapped to that key. If the operation
// fails then no NodeReference is returned and an error is returned instead.
func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*SchemaProxy], error) {
func ExtractSchema(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*SchemaProxy], error) {
var schLabel, schNode *yaml.Node
errStr := "schema build failed: reference '%s' cannot be found at line %d, col %d"
isRef := false
refLocation := ""
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
isRef = true
ref, _ := low.LocateRefNode(root, idx)
ref, fIdx, _, nCtx := low.LocateRefNodeWithContext(ctx, root, idx)
if ref != nil {
schNode = ref
schLabel = rl
ctx = nCtx
idx = fIdx
} else {
v := root.Content[1].Value
if root.Content[1].Value == "" {
v = "[empty]"
}
return nil, fmt.Errorf(errStr,
root.Content[1].Value, root.Content[1].Line, root.Content[1].Column)
v, root.Content[1].Line, root.Content[1].Column)
}
} else {
_, schLabel, schNode = utils.FindKeyNodeFull(SchemaLabel, root.Content)
@@ -1292,12 +1275,21 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S
h := false
if h, _, refLocation = utils.IsNodeRefValue(schNode); h {
isRef = true
ref, _ := low.LocateRefNode(schNode, idx)
ref, foundIdx, _, nCtx := low.LocateRefNodeWithContext(ctx, schNode, idx)
if ref != nil {
schNode = ref
if foundIdx != nil {
// TODO: check on this
//idx = foundIdx
}
ctx = nCtx
} else {
v := schNode.Content[1].Value
if schNode.Content[1].Value == "" {
v = "[empty]"
}
return nil, fmt.Errorf(errStr,
schNode.Content[1].Value, schNode.Content[1].Line, schNode.Content[1].Column)
v, schNode.Content[1].Line, schNode.Content[1].Column)
}
}
}
@@ -1305,7 +1297,7 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S
if schNode != nil {
// check if schema has already been built.
schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: idx, isReference: isRef, referenceLookup: refLocation}
schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: idx, ctx: ctx, isReference: isRef, referenceLookup: refLocation}
return &low.NodeReference[*SchemaProxy]{
Value: schema, KeyNode: schLabel, ValueNode: schNode, ReferenceNode: isRef,
Reference: refLocation,

View File

@@ -4,8 +4,8 @@
package base
import (
"context"
"crypto/sha256"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
@@ -51,14 +51,16 @@ type SchemaProxy struct {
buildError error
isReference bool // Is the schema underneath originally a $ref?
referenceLookup string // If the schema is a $ref, what's its name?
ctx context.Context
}
// Build will prepare the SchemaProxy for rendering, it does not build the Schema, only sets up internal state.
// Key maybe nil if absent.
func (sp *SchemaProxy) Build(key, value *yaml.Node, idx *index.SpecIndex) error {
func (sp *SchemaProxy) Build(ctx context.Context, key, value *yaml.Node, idx *index.SpecIndex) error {
sp.kn = key
sp.vn = value
sp.idx = idx
sp.ctx = ctx
if rf, _, r := utils.IsNodeRefValue(value); rf {
sp.isReference = true
sp.referenceLookup = r
@@ -83,7 +85,7 @@ func (sp *SchemaProxy) Schema() *Schema {
}
schema := new(Schema)
utils.CheckForMergeNodes(sp.vn)
err := schema.Build(sp.vn, sp.idx)
err := schema.Build(sp.ctx, sp.vn, sp.idx)
if err != nil {
sp.buildError = err
return nil
@@ -129,6 +131,20 @@ func (sp *SchemaProxy) GetSchemaReference() string {
return sp.referenceLookup
}
func (sp *SchemaProxy) GetSchemaReferenceLocation() *index.NodeOrigin {
if sp.idx != nil {
origin := sp.idx.FindNodeOrigin(sp.vn)
if origin != nil {
return origin
}
if sp.idx.GetRolodex() != nil {
origin = sp.idx.GetRolodex().FindNodeOrigin(sp.vn)
return origin
}
}
return nil
}
// GetKeyNode will return the yaml.Node pointer that is a key for value node.
func (sp *SchemaProxy) GetKeyNode() *yaml.Node {
return sp.kn

View File

@@ -4,7 +4,9 @@
package base
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
@@ -19,7 +21,7 @@ description: something`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
err := sch.Build(&idxNode, idxNode.Content[0], nil)
err := sch.Build(context.Background(), &idxNode, idxNode.Content[0], nil)
assert.NoError(t, err)
assert.Equal(t, "db2a35dd6fb3d9481d0682571b9d687616bb2a34c1887f7863f0b2e769ca7b23",
@@ -51,7 +53,7 @@ func TestSchemaProxy_Build_CheckRef(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
err := sch.Build(nil, idxNode.Content[0], nil)
err := sch.Build(context.Background(), nil, idxNode.Content[0], nil)
assert.NoError(t, err)
assert.True(t, sch.IsSchemaReference())
assert.Equal(t, "wat", sch.GetSchemaReference())
@@ -67,7 +69,7 @@ func TestSchemaProxy_Build_HashInline(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
err := sch.Build(nil, idxNode.Content[0], nil)
err := sch.Build(context.Background(), nil, idxNode.Content[0], nil)
assert.NoError(t, err)
assert.False(t, sch.IsSchemaReference())
assert.NotNil(t, sch.Schema())
@@ -89,9 +91,73 @@ x-common-definitions:
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
err := sch.Build(nil, idxNode.Content[0], nil)
err := sch.Build(context.Background(), nil, idxNode.Content[0], nil)
assert.NoError(t, err)
assert.Len(t, sch.Schema().Enum.Value, 3)
assert.Equal(t, "The type of life cycle", sch.Schema().Description.Value)
}
func TestSchemaProxy_GetSchemaReferenceLocation(t *testing.T) {
yml := `type: object
properties:
name:
type: string
description: thing`
var idxNodeA yaml.Node
e := yaml.Unmarshal([]byte(yml), &idxNodeA)
assert.NoError(t, e)
yml = `
type: object
properties:
name:
type: string
description: thang`
var schA SchemaProxy
var schB SchemaProxy
var schC SchemaProxy
var idxNodeB yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNodeB)
c := index.CreateOpenAPIIndexConfig()
rolo := index.NewRolodex(c)
rolo.SetRootNode(&idxNodeA)
_ = rolo.IndexTheRolodex()
err := schA.Build(context.Background(), nil, idxNodeA.Content[0], rolo.GetRootIndex())
assert.NoError(t, err)
err = schB.Build(context.Background(), nil, idxNodeB.Content[0].Content[3].Content[1], rolo.GetRootIndex())
assert.NoError(t, err)
rolo.GetRootIndex().SetAbsolutePath("/rooty/rootster")
origin := schA.GetSchemaReferenceLocation()
assert.NotNil(t, origin)
assert.Equal(t, "/rooty/rootster", origin.AbsoluteLocation)
// mess things up so it cannot be found
schA.vn = schB.vn
origin = schA.GetSchemaReferenceLocation()
assert.Nil(t, origin)
// create a new index
idx := index.NewSpecIndexWithConfig(&idxNodeB, c)
idx.SetAbsolutePath("/boaty/mcboatface")
// add the index to the rolodex
rolo.AddIndex(idx)
// can now find the origin
origin = schA.GetSchemaReferenceLocation()
assert.NotNil(t, origin)
assert.Equal(t, "/boaty/mcboatface", origin.AbsoluteLocation)
// do it again, but with no index
err = schC.Build(context.Background(), nil, idxNodeA.Content[0], nil)
origin = schC.GetSchemaReferenceLocation()
assert.Nil(t, origin)
}

View File

@@ -1,15 +1,15 @@
package base
import (
"testing"
"context"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/resolver"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func test_get_schema_blob() string {
@@ -167,10 +167,10 @@ func Test_Schema(t *testing.T) {
mbErr := low.BuildModel(rootNode.Content[0], &sch)
assert.NoError(t, mbErr)
schErr := sch.Build(rootNode.Content[0], nil)
schErr := sch.Build(context.Background(), rootNode.Content[0], nil)
assert.NoError(t, schErr)
assert.Equal(t, "something object", sch.Description.Value)
assert.True(t, sch.AdditionalProperties.Value.(bool))
assert.True(t, sch.AdditionalProperties.Value.B)
assert.Equal(t, 2, orderedmap.Len(sch.Properties.Value))
v := sch.FindProperty("somethingB")
@@ -343,7 +343,7 @@ func TestSchemaAllOfSequenceOrder(t *testing.T) {
mbErr := low.BuildModel(rootNode.Content[0], &sch)
assert.NoError(t, mbErr)
schErr := sch.Build(rootNode.Content[0], nil)
schErr := sch.Build(context.Background(), rootNode.Content[0], nil)
assert.NoError(t, schErr)
assert.Equal(t, "allOf sequence check", sch.Description.Value)
@@ -363,13 +363,13 @@ func TestSchema_Hash(t *testing.T) {
_ = yaml.Unmarshal([]byte(testSpec), &sc1n)
sch1 := Schema{}
_ = low.BuildModel(&sc1n, &sch1)
_ = sch1.Build(sc1n.Content[0], nil)
_ = sch1.Build(context.Background(), sc1n.Content[0], nil)
var sc2n yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &sc2n)
sch2 := Schema{}
_ = low.BuildModel(&sc2n, &sch2)
_ = sch2.Build(sc2n.Content[0], nil)
_ = sch2.Build(context.Background(), sc2n.Content[0], nil)
assert.Equal(t, sch1.Hash(), sch2.Hash())
}
@@ -381,13 +381,13 @@ func BenchmarkSchema_Hash(b *testing.B) {
_ = yaml.Unmarshal([]byte(testSpec), &sc1n)
sch1 := Schema{}
_ = low.BuildModel(&sc1n, &sch1)
_ = sch1.Build(sc1n.Content[0], nil)
_ = sch1.Build(context.Background(), sc1n.Content[0], nil)
var sc2n yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &sc2n)
sch2 := Schema{}
_ = low.BuildModel(&sc2n, &sch2)
_ = sch2.Build(sc2n.Content[0], nil)
_ = sch2.Build(context.Background(), sc2n.Content[0], nil)
for i := 0; i < b.N; i++ {
assert.Equal(b, sch1.Hash(), sch2.Hash())
@@ -417,7 +417,7 @@ const: tasty`
mbErr := low.BuildModel(rootNode.Content[0], &sch)
assert.NoError(t, mbErr)
schErr := sch.Build(rootNode.Content[0], nil)
schErr := sch.Build(context.Background(), rootNode.Content[0], nil)
assert.NoError(t, schErr)
assert.Equal(t, "something object", sch.Description.Value)
assert.Len(t, sch.Type.Value.B, 2)
@@ -458,7 +458,7 @@ properties:
_ = yaml.Unmarshal([]byte(yml), &idxNode)
var n Schema
err := n.Build(idxNode.Content[0], idx)
err := n.Build(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "this is something", n.FindProperty("aValue").Value.Schema().Description.Value)
}
@@ -484,7 +484,7 @@ properties:
_ = yaml.Unmarshal([]byte(yml), &idxNode)
var n Schema
err := n.Build(idxNode.Content[0], idx)
err := n.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -509,7 +509,7 @@ dependentSchemas:
_ = yaml.Unmarshal([]byte(yml), &idxNode)
var n Schema
err := n.Build(idxNode.Content[0], idx)
err := n.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -534,7 +534,7 @@ patternProperties:
_ = yaml.Unmarshal([]byte(yml), &idxNode)
var n Schema
err := n.Build(idxNode.Content[0], idx)
err := n.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -574,7 +574,7 @@ items:
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
schErr := sch.Build(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, schErr)
desc := "poly thing"
@@ -621,7 +621,7 @@ items:
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
schErr := sch.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, schErr)
}
@@ -661,7 +661,7 @@ items:
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
schErr := sch.Build(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, schErr)
desc := "poly thing"
@@ -708,7 +708,7 @@ items:
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
schErr := sch.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, schErr)
}
@@ -734,7 +734,7 @@ allOf:
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
schErr := sch.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, schErr)
}
@@ -760,7 +760,7 @@ allOf:
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
schErr := sch.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, schErr)
}
@@ -788,7 +788,7 @@ allOf:
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
schErr := sch.Build(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, schErr)
assert.Nil(t, sch.AllOf.Value[0].Value.Schema()) // child can't be resolved, so this will be nil.
assert.Error(t, sch.AllOf.Value[0].Value.GetBuildError())
@@ -818,7 +818,7 @@ allOf:
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
schErr := sch.Build(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, schErr)
desc := "madness"
@@ -849,7 +849,7 @@ allOf:
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
err = sch.Build(idxNode.Content[0], idx)
err = sch.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -877,7 +877,7 @@ func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) {
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
schErr := sch.Build(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, schErr)
}
@@ -902,14 +902,14 @@ func Test_Schema_RefMadnessIllegal_Circular(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 1)
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
schErr := sch.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, schErr)
}
@@ -934,14 +934,14 @@ func Test_Schema_RefMadnessIllegal_Nonexist(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 1)
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
schErr := sch.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, schErr)
}
@@ -966,7 +966,7 @@ func TestExtractSchema(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, err := ExtractSchema(idxNode.Content[0], idx)
res, err := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, err)
assert.NotNil(t, res.Value)
aValue := res.Value.Schema().FindProperty("aValue")
@@ -982,7 +982,7 @@ schema:
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, err := ExtractSchema(idxNode.Content[0], nil)
res, err := ExtractSchema(context.Background(), idxNode.Content[0], nil)
assert.NoError(t, err)
assert.NotNil(t, res.Value)
sch := res.Value.Schema()
@@ -998,7 +998,7 @@ schema:
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, err := ExtractSchema(idxNode.Content[0], nil)
res, err := ExtractSchema(context.Background(), idxNode.Content[0], nil)
assert.NoError(t, err)
assert.NotNil(t, res.Value)
sch := res.Value.Schema()
@@ -1023,7 +1023,7 @@ func TestExtractSchema_Ref(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, err := ExtractSchema(idxNode.Content[0], idx)
res, err := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, err)
assert.NotNil(t, res.Value)
assert.Equal(t, "this is something", res.Value.Schema().Description.Value)
@@ -1047,7 +1047,7 @@ func TestExtractSchema_Ref_Fail(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
_, err := ExtractSchema(idxNode.Content[0], idx)
_, err := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -1075,14 +1075,14 @@ func TestExtractSchema_CheckChildPropCircular(t *testing.T) {
yml = `$ref: '#/components/schemas/Something'`
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 1)
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, err := ExtractSchema(idxNode.Content[0], idx)
res, err := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, err)
assert.NotNil(t, res.Value)
@@ -1107,7 +1107,7 @@ func TestExtractSchema_RefRoot(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, err := ExtractSchema(idxNode.Content[0], idx)
res, err := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, err)
assert.NotNil(t, res.Value)
assert.Equal(t, "this is something", res.Value.Schema().Description.Value)
@@ -1130,7 +1130,7 @@ func TestExtractSchema_RefRoot_Fail(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
_, err := ExtractSchema(idxNode.Content[0], idx)
_, err := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -1150,7 +1150,7 @@ func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
_, err := ExtractSchema(idxNode.Content[0], idx)
_, err := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -1171,32 +1171,9 @@ func TestExtractSchema_AdditionalPropertiesAsSchema(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, err := ExtractSchema(idxNode.Content[0], idx)
res, err := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.NotNil(t, res.Value.Schema().AdditionalProperties.Value.(*SchemaProxy).Schema())
assert.Nil(t, err)
}
func TestExtractSchema_AdditionalPropertiesAsSchemaSlice(t *testing.T) {
yml := `components:
schemas:
Something:
additionalProperties:
- nice: rice`
var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode)
yml = `$ref: '#/components/schemas/Something'`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, err := ExtractSchema(idxNode.Content[0], idx)
assert.NotNil(t, res.Value.Schema().AdditionalProperties.Value.([]low.ValueReference[interface{}]))
assert.NotNil(t, res.Value.Schema().AdditionalProperties.Value.A.Schema())
assert.Nil(t, err)
}
@@ -1216,7 +1193,7 @@ func TestExtractSchema_DoNothing(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, err := ExtractSchema(idxNode.Content[0], idx)
res, err := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Nil(t, res)
assert.Nil(t, err)
}
@@ -1244,8 +1221,8 @@ func TestExtractSchema_AdditionalProperties_Ref(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, err := ExtractSchema(idxNode.Content[0], idx)
assert.NotNil(t, res.Value.Schema().AdditionalProperties.Value.(*SchemaProxy).Schema())
res, err := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.NotNil(t, res.Value.Schema().AdditionalProperties.Value.A.Schema())
assert.Nil(t, err)
}
@@ -1358,7 +1335,7 @@ func TestExtractSchema_OneOfRef(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, err := ExtractSchema(idxNode.Content[0], idx)
res, err := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "a frosty cold beverage can be coke or sprite",
res.Value.Schema().OneOf.Value[0].Value.Schema().Description.Value)
@@ -1379,7 +1356,7 @@ func TestSchema_Hash_Equal(t *testing.T) {
uniqueItems: 1
maxProperties: 10
minProperties: 1
additionalProperties: anything
additionalProperties: true
description: milky
contentEncoding: rubber shoes
contentMediaType: paper tiger
@@ -1421,7 +1398,7 @@ func TestSchema_Hash_Equal(t *testing.T) {
uniqueItems: 1
maxProperties: 10
minProperties: 1
additionalProperties: anything
additionalProperties: true
description: milky
contentEncoding: rubber shoes
contentMediaType: paper tiger
@@ -1451,8 +1428,8 @@ func TestSchema_Hash_Equal(t *testing.T) {
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
lDoc, _ := ExtractSchema(lNode.Content[0], nil)
rDoc, _ := ExtractSchema(rNode.Content[0], nil)
lDoc, _ := ExtractSchema(context.Background(), lNode.Content[0], nil)
rDoc, _ := ExtractSchema(context.Background(), rNode.Content[0], nil)
assert.NotNil(t, lDoc)
assert.NotNil(t, rDoc)
@@ -1476,8 +1453,8 @@ func TestSchema_Hash_AdditionalPropsSlice(t *testing.T) {
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
lDoc, _ := ExtractSchema(lNode.Content[0], nil)
rDoc, _ := ExtractSchema(rNode.Content[0], nil)
lDoc, _ := ExtractSchema(context.Background(), lNode.Content[0], nil)
rDoc, _ := ExtractSchema(context.Background(), rNode.Content[0], nil)
assert.NotNil(t, lDoc)
assert.NotNil(t, rDoc)
@@ -1501,8 +1478,8 @@ func TestSchema_Hash_AdditionalPropsSliceNoMap(t *testing.T) {
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
lDoc, _ := ExtractSchema(lNode.Content[0], nil)
rDoc, _ := ExtractSchema(rNode.Content[0], nil)
lDoc, _ := ExtractSchema(context.Background(), lNode.Content[0], nil)
rDoc, _ := ExtractSchema(context.Background(), rNode.Content[0], nil)
assert.NotNil(t, lDoc)
assert.NotNil(t, rDoc)
@@ -1538,8 +1515,8 @@ func TestSchema_Hash_NotEqual(t *testing.T) {
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
lDoc, _ := ExtractSchema(lNode.Content[0], nil)
rDoc, _ := ExtractSchema(rNode.Content[0], nil)
lDoc, _ := ExtractSchema(context.Background(), lNode.Content[0], nil)
rDoc, _ := ExtractSchema(context.Background(), rNode.Content[0], nil)
assert.False(t, low.AreEqual(lDoc.Value.Schema(), rDoc.Value.Schema()))
}
@@ -1575,8 +1552,8 @@ func TestSchema_Hash_EqualJumbled(t *testing.T) {
_ = yaml.Unmarshal([]byte(left), &lNode)
_ = yaml.Unmarshal([]byte(right), &rNode)
lDoc, _ := ExtractSchema(lNode.Content[0], nil)
rDoc, _ := ExtractSchema(rNode.Content[0], nil)
lDoc, _ := ExtractSchema(context.Background(), lNode.Content[0], nil)
rDoc, _ := ExtractSchema(context.Background(), rNode.Content[0], nil)
assert.True(t, low.AreEqual(lDoc.Value.Schema(), rDoc.Value.Schema()))
}
@@ -1609,10 +1586,10 @@ func TestSchema_UnevaluatedPropertiesAsBool_DefinedAsTrue(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, _ := ExtractSchema(idxNode.Content[0], idx)
res, _ := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.True(t, res.Value.Schema().UnevaluatedProperties.Value.IsB())
assert.True(t, *res.Value.Schema().UnevaluatedProperties.Value.B)
assert.True(t, res.Value.Schema().UnevaluatedProperties.Value.B)
assert.Equal(t, "571bd1853c22393131e2dcadce86894da714ec14968895c8b7ed18154b2be8cd",
low.GenerateHashString(res.Value.Schema().UnevaluatedProperties.Value))
@@ -1634,10 +1611,10 @@ func TestSchema_UnevaluatedPropertiesAsBool_DefinedAsFalse(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, _ := ExtractSchema(idxNode.Content[0], idx)
res, _ := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.True(t, res.Value.Schema().UnevaluatedProperties.Value.IsB())
assert.False(t, *res.Value.Schema().UnevaluatedProperties.Value.B)
assert.False(t, res.Value.Schema().UnevaluatedProperties.Value.B)
}
func TestSchema_UnevaluatedPropertiesAsBool_Undefined(t *testing.T) {
@@ -1656,7 +1633,186 @@ func TestSchema_UnevaluatedPropertiesAsBool_Undefined(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, _ := ExtractSchema(idxNode.Content[0], idx)
res, _ := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Nil(t, res.Value.Schema().UnevaluatedProperties.Value)
}
func TestSchema_ExclusiveMinimum_3_with_Config(t *testing.T) {
yml := `openapi: 3.0.3
components:
schemas:
Something:
type: integer
minimum: 3
exclusiveMinimum: true`
var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr)
config := index.CreateOpenAPIIndexConfig()
config.SpecInfo = &datamodel.SpecInfo{
VersionNumeric: 3.0,
}
idx := index.NewSpecIndexWithConfig(&iNode, config)
yml = `$ref: '#/components/schemas/Something'`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, _ := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.True(t, res.Value.Schema().ExclusiveMinimum.Value.A)
}
func TestSchema_ExclusiveMinimum_31_with_Config(t *testing.T) {
yml := `openapi: 3.1
components:
schemas:
Something:
type: integer
minimum: 3
exclusiveMinimum: 3`
var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr)
config := index.CreateOpenAPIIndexConfig()
config.SpecInfo = &datamodel.SpecInfo{
VersionNumeric: 3.1,
}
idx := index.NewSpecIndexWithConfig(&iNode, config)
yml = `$ref: '#/components/schemas/Something'`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, _ := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Equal(t, 3.0, res.Value.Schema().ExclusiveMinimum.Value.B)
}
func TestSchema_ExclusiveMaximum_3_with_Config(t *testing.T) {
yml := `openapi: 3.0.3
components:
schemas:
Something:
type: integer
maximum: 3
exclusiveMaximum: true`
var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr)
config := index.CreateOpenAPIIndexConfig()
config.SpecInfo = &datamodel.SpecInfo{
VersionNumeric: 3.0,
}
idx := index.NewSpecIndexWithConfig(&iNode, config)
yml = `$ref: '#/components/schemas/Something'`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, _ := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.True(t, res.Value.Schema().ExclusiveMaximum.Value.A)
}
func TestSchema_ExclusiveMaximum_31_with_Config(t *testing.T) {
yml := `openapi: 3.1
components:
schemas:
Something:
type: integer
maximum: 3
exclusiveMaximum: 3`
var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr)
config := index.CreateOpenAPIIndexConfig()
config.SpecInfo = &datamodel.SpecInfo{
VersionNumeric: 3.1,
}
idx := index.NewSpecIndexWithConfig(&iNode, config)
yml = `$ref: '#/components/schemas/Something'`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, _ := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Equal(t, 3.0, res.Value.Schema().ExclusiveMaximum.Value.B)
}
func TestSchema_EmptyySchemaRef(t *testing.T) {
yml := `openapi: 3.0.3
components:
schemas:
Something:
$ref: ''`
var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr)
config := index.CreateOpenAPIIndexConfig()
config.SpecInfo = &datamodel.SpecInfo{
VersionNumeric: 3.0,
}
idx := index.NewSpecIndexWithConfig(&iNode, config)
yml = `schema:
$ref: ''`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, e := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Nil(t, res)
assert.Equal(t, "schema build failed: reference '[empty]' cannot be found at line 2, col 9", e.Error())
}
func TestSchema_EmptyRef(t *testing.T) {
yml := `openapi: 3.0.3
components:
schemas:
Something:
$ref: ''`
var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr)
config := index.CreateOpenAPIIndexConfig()
config.SpecInfo = &datamodel.SpecInfo{
VersionNumeric: 3.0,
}
idx := index.NewSpecIndexWithConfig(&iNode, config)
yml = `$ref: ''`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
res, e := ExtractSchema(context.Background(), idxNode.Content[0], idx)
assert.Nil(t, res)
assert.Equal(t, "schema build failed: reference '[empty]' cannot be found at line 1, col 7", e.Error())
}

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"crypto/sha256"
"fmt"
"sort"
@@ -30,7 +31,7 @@ type SecurityRequirement struct {
}
// Build will extract security requirements from the node (the structure is odd, to be honest)
func (s *SecurityRequirement) Build(_, root *yaml.Node, _ *index.SpecIndex) error {
func (s *SecurityRequirement) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
s.Reference = new(low.Reference)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"testing"
"github.com/pb33f/libopenapi/orderedmap"
@@ -35,8 +36,8 @@ one:
var idxNode2 yaml.Node
_ = yaml.Unmarshal([]byte(yml2), &idxNode2)
_ = sr.Build(nil, idxNode.Content[0], nil)
_ = sr2.Build(nil, idxNode2.Content[0], nil)
_ = sr.Build(context.Background(), nil, idxNode.Content[0], nil)
_ = sr2.Build(context.Background(), nil, idxNode2.Content[0], nil)
assert.Equal(t, 2, orderedmap.Len(sr.Requirements.Value))
assert.Len(t, sr.GetKeys(), 2)

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -34,14 +35,14 @@ func (t *Tag) FindExtension(ext string) *low.ValueReference[any] {
}
// Build will extract extensions and external docs for the Tag.
func (t *Tag) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (t *Tag) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
t.Reference = new(low.Reference)
t.Extensions = low.ExtractExtensions(root)
// extract externalDocs
extDocs, err := low.ExtractObject[*ExternalDoc](ExternalDocsLabel, root, idx)
extDocs, err := low.ExtractObject[*ExternalDoc](ctx, ExternalDocsLabel, root, idx)
t.ExternalDocs = extDocs
return err
}
@@ -73,25 +74,3 @@ func (t *Tag) Hash() [32]byte {
f = append(f, keys...)
return sha256.Sum256([]byte(strings.Join(f, "|")))
}
// TODO: future mutation API experiment code is here. this snippet is to re-marshal the object.
//func (t *Tag) MarshalYAML() (interface{}, error) {
// m := make(map[string]interface{})
// for i := range t.Extensions {
// m[i.Value] = t.Extensions[i].Value
// }
// if t.Name.Value != "" {
// m[NameLabel] = t.Name.Value
// }
// if t.Description.Value != "" {
// m[DescriptionLabel] = t.Description.Value
// }
// if t.ExternalDocs.Value != nil {
// m[ExternalDocsLabel] = t.ExternalDocs.Value
// }
// return m, nil
//}
//
//func NewTag() *Tag {
// return new(Tag)
//}

View File

@@ -4,6 +4,7 @@
package base
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -27,7 +28,7 @@ x-coffee: tasty`
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "a tag", n.Name.Value)
assert.Equal(t, "a description", n.Description.Value)
@@ -52,7 +53,7 @@ externalDocs:
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -79,8 +80,8 @@ x-b33f: princess`
var rDoc Tag
_ = low.BuildModel(lNode.Content[0], &lDoc)
_ = low.BuildModel(rNode.Content[0], &rDoc)
_ = lDoc.Build(nil, lNode.Content[0], nil)
_ = rDoc.Build(nil, rNode.Content[0], nil)
_ = lDoc.Build(context.Background(), nil, lNode.Content[0], nil)
_ = rDoc.Build(context.Background(), nil, rNode.Content[0], nil)
assert.Equal(t, lDoc.Hash(), rDoc.Hash())

View File

@@ -4,6 +4,7 @@
package low
import (
"context"
"crypto/sha256"
"fmt"
"reflect"
@@ -15,6 +16,8 @@ import (
"github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3"
"net/url"
"path/filepath"
)
// FindItemInMap accepts a string key and a collection of KeyReference[string] and ValueReference[T]. Every
@@ -64,26 +67,18 @@ func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.Re
}
}
// LocateRefNode will perform a complete lookup for a $ref node. This function searches the entire index for
// the reference being supplied. If there is a match found, the reference *yaml.Node is returned.
func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) {
func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, *index.SpecIndex, error, context.Context) {
if rf, _, rv := utils.IsNodeRefValue(root); rf {
if rv == "" {
return nil, nil, fmt.Errorf("reference at line %d, column %d is empty, it cannot be resolved",
root.Line, root.Column), ctx
}
// run through everything and return as soon as we find a match.
// this operates as fast as possible as ever
collections := generateIndexCollection(idx)
// if there are any external indexes being used by remote
// documents, then we need to search through them also.
externalIndexes := idx.GetAllExternalIndexes()
if len(externalIndexes) > 0 {
var extCollection []func() map[string]*index.Reference
for _, extIndex := range externalIndexes {
extCollection = generateIndexCollection(extIndex)
collections = append(collections, extCollection...)
}
}
var found map[string]*index.Reference
for _, collection := range collections {
found = collection()
@@ -94,23 +89,107 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) {
if jh, _, _ := utils.IsNodeRefValue(found[rv].Node); jh {
// if this node is circular, stop drop and roll.
if !IsCircular(found[rv].Node, idx) {
return LocateRefNode(found[rv].Node, idx)
return LocateRefNodeWithContext(ctx, found[rv].Node, idx)
} else {
return found[rv].Node, fmt.Errorf("circular reference '%s' found during lookup at line "+
return found[rv].Node, idx, fmt.Errorf("circular reference '%s' found during lookup at line "+
"%d, column %d, It cannot be resolved",
GetCircularReferenceResult(found[rv].Node, idx).GenerateJourneyPath(),
found[rv].Node.Line,
found[rv].Node.Column)
found[rv].Node.Column), ctx
}
}
return utils.NodeAlias(found[rv].Node), nil
return utils.NodeAlias(found[rv].Node), idx, nil, ctx
}
}
// perform a search for the reference in the index
foundRefs := idx.SearchIndexForReference(rv)
if len(foundRefs) > 0 {
return utils.NodeAlias(foundRefs[0].Node), nil
// extract the correct root
specPath := idx.GetSpecAbsolutePath()
if ctx.Value(index.CurrentPathKey) != nil {
specPath = ctx.Value(index.CurrentPathKey).(string)
}
explodedRefValue := strings.Split(rv, "#")
if len(explodedRefValue) == 2 {
if !strings.HasPrefix(explodedRefValue[0], "http") {
if !filepath.IsAbs(explodedRefValue[0]) {
if strings.HasPrefix(specPath, "http") {
u, _ := url.Parse(specPath)
p := ""
if u.Path != "" && explodedRefValue[0] != "" {
p = filepath.Dir(u.Path)
}
if p != "" && explodedRefValue[0] != "" {
u.Path = filepath.Join(p, explodedRefValue[0])
}
u.Fragment = ""
rv = fmt.Sprintf("%s#%s", u.String(), explodedRefValue[1])
} else {
if specPath != "" {
var abs string
if explodedRefValue[0] == "" {
abs = specPath
} else {
abs, _ = filepath.Abs(filepath.Join(filepath.Dir(specPath), explodedRefValue[0]))
}
rv = fmt.Sprintf("%s#%s", abs, explodedRefValue[1])
} else {
// check for a config baseURL and use that if it exists.
if idx.GetConfig().BaseURL != nil {
u := *idx.GetConfig().BaseURL
p := ""
if u.Path != "" {
p = filepath.Dir(u.Path)
}
u.Path = filepath.Join(p, explodedRefValue[0])
rv = fmt.Sprintf("%s#%s", u.String(), explodedRefValue[1])
}
}
}
}
}
} else {
if !strings.HasPrefix(explodedRefValue[0], "http") {
if !filepath.IsAbs(explodedRefValue[0]) {
if strings.HasPrefix(specPath, "http") {
u, _ := url.Parse(specPath)
p := filepath.Dir(u.Path)
abs, _ := filepath.Abs(filepath.Join(p, rv))
u.Path = abs
rv = u.String()
} else {
if specPath != "" {
abs, _ := filepath.Abs(filepath.Join(filepath.Dir(specPath), rv))
rv = abs
} else {
// check for a config baseURL and use that if it exists.
if idx.GetConfig().BaseURL != nil {
u := *idx.GetConfig().BaseURL
abs, _ := filepath.Abs(filepath.Join(u.Path, rv))
u.Path = abs
rv = u.String()
}
}
}
}
}
}
foundRef, fIdx, newCtx := idx.SearchIndexForReferenceWithContext(ctx, rv)
if foundRef != nil {
return utils.NodeAlias(foundRef.Node), fIdx, nil, newCtx
}
// let's try something else to find our references.
@@ -123,30 +202,40 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) {
nodes, fErr := path.Find(idx.GetRootNode())
if fErr == nil {
if len(nodes) > 0 {
return utils.NodeAlias(nodes[0]), nil
return utils.NodeAlias(nodes[0]), idx, nil, ctx
}
}
}
}
return nil, fmt.Errorf("reference '%s' at line %d, column %d was not found",
rv, root.Line, root.Column)
return nil, idx, fmt.Errorf("reference '%s' at line %d, column %d was not found",
rv, root.Line, root.Column), ctx
}
return nil, nil
return nil, idx, nil, ctx
}
// LocateRefNode will perform a complete lookup for a $ref node. This function searches the entire index for
// the reference being supplied. If there is a match found, the reference *yaml.Node is returned.
func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, *index.SpecIndex, error) {
r, i, e, _ := LocateRefNodeWithContext(context.Background(), root, idx)
return r, i, e
}
// ExtractObjectRaw will extract a typed Buildable[N] object from a root yaml.Node. The 'raw' aspect is
// that there is no NodeReference wrapper around the result returned, just the raw object.
func ExtractObjectRaw[T Buildable[N], N any](key, root *yaml.Node, idx *index.SpecIndex) (T, error, bool, string) {
func ExtractObjectRaw[T Buildable[N], N any](ctx context.Context, key, root *yaml.Node, idx *index.SpecIndex) (T, error, bool, string) {
var circError error
var isReference bool
var referenceValue string
root = utils.NodeAlias(root)
if h, _, rv := utils.IsNodeRefValue(root); h {
ref, err := LocateRefNode(root, idx)
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, root, idx)
if ref != nil {
root = ref
isReference = true
referenceValue = rv
idx = fIdx
ctx = nCtx
if err != nil {
circError = err
}
@@ -161,7 +250,7 @@ func ExtractObjectRaw[T Buildable[N], N any](key, root *yaml.Node, idx *index.Sp
if err != nil {
return n, err, isReference, referenceValue
}
err = n.Build(key, root, idx)
err = n.Build(ctx, key, root, idx)
if err != nil {
return n, err, isReference, referenceValue
}
@@ -180,19 +269,21 @@ func ExtractObjectRaw[T Buildable[N], N any](key, root *yaml.Node, idx *index.Sp
// ExtractObject will extract a typed Buildable[N] object from a root yaml.Node. The result is wrapped in a
// NodeReference[T] that contains the key node found and value node found when looking up the reference.
func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (NodeReference[T], error) {
func ExtractObject[T Buildable[N], N any](ctx context.Context, label string, root *yaml.Node, idx *index.SpecIndex) (NodeReference[T], error) {
var ln, vn *yaml.Node
var circError error
var isReference bool
var referenceValue string
root = utils.NodeAlias(root)
if rf, rl, refVal := utils.IsNodeRefValue(root); rf {
ref, err := LocateRefNode(root, idx)
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, root, idx)
if ref != nil {
vn = ref
ln = rl
isReference = true
referenceValue = refVal
idx = fIdx
ctx = nCtx
if err != nil {
circError = err
}
@@ -205,9 +296,13 @@ func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *in
_, ln, vn = utils.FindKeyNodeFull(label, root.Content)
if vn != nil {
if h, _, rVal := utils.IsNodeRefValue(vn); h {
ref, lerr := LocateRefNode(vn, idx)
ref, fIdx, lerr, nCtx := LocateRefNodeWithContext(ctx, vn, idx)
if ref != nil {
vn = ref
if fIdx != nil {
idx = fIdx
}
ctx = nCtx
isReference = true
referenceValue = rVal
if lerr != nil {
@@ -229,7 +324,7 @@ func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *in
if ln == nil {
return NodeReference[T]{}, nil
}
err = n.Build(ln, vn, idx)
err = n.Build(ctx, ln, vn, idx)
if err != nil {
return NodeReference[T]{}, err
}
@@ -265,17 +360,21 @@ func SetReference(obj any, ref string) {
// ExtractArray will extract a slice of []ValueReference[T] from a root yaml.Node that is defined as a sequence.
// Used when the value being extracted is an array.
func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) ([]ValueReference[T],
func ExtractArray[T Buildable[N], N any](ctx context.Context, label string, root *yaml.Node, idx *index.SpecIndex) ([]ValueReference[T],
*yaml.Node, *yaml.Node, error,
) {
var ln, vn *yaml.Node
var circError error
root = utils.NodeAlias(root)
isRef := false
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
ref, err := LocateRefNode(root, idx)
ref, fIdx, err, nCtx := LocateRefEnd(ctx, root, idx, 0)
if ref != nil {
isRef = true
vn = ref
ln = rl
idx = fIdx
ctx = nCtx
if err != nil {
circError = err
}
@@ -287,17 +386,20 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind
_, ln, vn = utils.FindKeyNodeFullTop(label, root.Content)
if vn != nil {
if h, _, _ := utils.IsNodeRefValue(vn); h {
ref, err := LocateRefNode(vn, idx)
ref, fIdx, err, nCtx := LocateRefEnd(ctx, vn, idx, 0)
if ref != nil {
isRef = true
vn = ref
//referenceValue = rVal
idx = fIdx
ctx = nCtx
if err != nil {
circError = err
}
} else {
if err != nil {
return []ValueReference[T]{}, nil, nil, fmt.Errorf("array build failed: reference cannot be found: %s",
err.Error())
return []ValueReference[T]{}, nil, nil,
fmt.Errorf("array build failed: reference cannot be found: %s",
err.Error())
}
}
}
@@ -307,18 +409,33 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind
var items []ValueReference[T]
if vn != nil && ln != nil {
if !utils.IsNodeArray(vn) {
return []ValueReference[T]{}, nil, nil, fmt.Errorf("array build failed, input is not an array, line %d, column %d", vn.Line, vn.Column)
if !isRef {
return []ValueReference[T]{}, nil, nil,
fmt.Errorf("array build failed, input is not an array, line %d, column %d", vn.Line, vn.Column)
}
// if this was pulled from a ref, but it's not a sequence, check the label and see if anything comes out,
// and then check that is a sequence, if not, fail it.
_, _, fvn := utils.FindKeyNodeFullTop(label, vn.Content)
if fvn != nil {
if !utils.IsNodeArray(vn) {
return []ValueReference[T]{}, nil, nil,
fmt.Errorf("array build failed, input is not an array, line %d, column %d", vn.Line, vn.Column)
}
}
}
for _, node := range vn.Content {
localReferenceValue := ""
//localIsReference := false
foundCtx := ctx
foundIndex := idx
if rf, _, rv := utils.IsNodeRefValue(node); rf {
refg, err := LocateRefNode(node, idx)
refg, fIdx, err, nCtx := LocateRefEnd(ctx, node, idx, 0)
if refg != nil {
node = refg
//localIsReference = true
localReferenceValue = rv
foundIndex = fIdx
foundCtx = nCtx
if err != nil {
circError = err
}
@@ -334,7 +451,7 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind
if err != nil {
return []ValueReference[T]{}, ln, vn, err
}
berr := n.Build(ln, node, idx)
berr := n.Build(foundCtx, ln, node, foundIndex)
if berr != nil {
return nil, ln, vn, berr
}
@@ -381,6 +498,7 @@ func ExtractExample(expNode, expLabel *yaml.Node) NodeReference[any] {
//
// This is useful when the node to be extracted, is already known and does not require a search.
func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
ctx context.Context,
root *yaml.Node,
idx *index.SpecIndex,
includeExtensions bool,
@@ -417,15 +535,22 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
}
node = utils.NodeAlias(node)
foundIndex := idx
foundContext := ctx
var isReference bool
var referenceValue string
// if value is a reference, we have to look it up in the index!
if h, _, rv := utils.IsNodeRefValue(node); h {
ref, err := LocateRefNode(node, idx)
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, node, idx)
if ref != nil {
node = ref
isReference = true
referenceValue = rv
if fIdx != nil {
foundIndex = fIdx
}
foundContext = nCtx
if err != nil {
circError = err
}
@@ -435,13 +560,12 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
}
}
}
var n PT = new(N)
err := BuildModel(node, n)
if err != nil {
return nil, err
}
berr := n.Build(currentKey, node, idx)
berr := n.Build(foundContext, currentKey, node, foundIndex)
if berr != nil {
return nil, berr
}
@@ -457,7 +581,6 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
ValueReference[PT]{
Value: n,
ValueNode: node,
//IsReference: isReference,
Reference: referenceValue,
},
)
@@ -477,10 +600,11 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
//
// This is useful when the node to be extracted, is already known and does not require a search.
func ExtractMapNoLookup[PT Buildable[N], N any](
ctx context.Context,
root *yaml.Node,
idx *index.SpecIndex,
) (orderedmap.Map[KeyReference[string], ValueReference[PT]], error) {
return ExtractMapNoLookupExtensions[PT, N](root, idx, false)
return ExtractMapNoLookupExtensions[PT, N](ctx, root, idx, false)
}
type mappingResult[T any] struct {
@@ -495,24 +619,25 @@ type mappingResult[T any] struct {
// The second return value is the yaml.Node found for the 'label' and the third return value is the yaml.Node
// found for the value extracted from the label node.
func ExtractMapExtensions[PT Buildable[N], N any](
ctx context.Context,
label string,
root *yaml.Node,
idx *index.SpecIndex,
extensions bool,
) (orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) {
//var isReference bool
var referenceValue string
var labelNode, valueNode *yaml.Node
var circError error
root = utils.NodeAlias(root)
if rf, rl, rv := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref, err := LocateRefNode(root, idx)
ref, fIdx, err, fCtx := LocateRefNodeWithContext(ctx, root, idx)
if ref != nil {
valueNode = ref
labelNode = rl
//isReference = true
referenceValue = rv
ctx = fCtx
idx = fIdx
if err != nil {
circError = err
}
@@ -522,13 +647,15 @@ func ExtractMapExtensions[PT Buildable[N], N any](
}
} else {
_, labelNode, valueNode = utils.FindKeyNodeFull(label, root.Content)
valueNode = utils.NodeAlias(valueNode)
if valueNode != nil {
if h, _, rvt := utils.IsNodeRefValue(valueNode); h {
ref, err := LocateRefNode(valueNode, idx)
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, valueNode, idx)
if ref != nil {
valueNode = ref
//isReference = true
referenceValue = rvt
idx = fIdx
ctx = nCtx
if err != nil {
circError = err
}
@@ -549,19 +676,17 @@ func ExtractMapExtensions[PT Buildable[N], N any](
bChan := make(chan mappingResult[PT])
eChan := make(chan error)
buildMap := func(label *yaml.Node, value *yaml.Node, c chan mappingResult[PT], ec chan<- error, ref string) {
buildMap := func(nctx context.Context, label *yaml.Node, value *yaml.Node, c chan mappingResult[PT], ec chan<- error, ref string, fIdx *index.SpecIndex) {
var n PT = new(N)
value = utils.NodeAlias(value)
_ = BuildModel(value, n)
err := n.Build(label, value, idx)
err := n.Build(nctx, label, value, fIdx)
if err != nil {
ec <- err
return
}
//isRef := false
if ref != "" {
//isRef = true
SetReference(n, ref)
}
@@ -573,7 +698,6 @@ func ExtractMapExtensions[PT Buildable[N], N any](
v: ValueReference[PT]{
Value: n,
ValueNode: value,
//IsReference: isRef,
Reference: ref,
},
}
@@ -587,12 +711,20 @@ func ExtractMapExtensions[PT Buildable[N], N any](
currentLabelNode = en
continue
}
foundIndex := idx
foundContext := ctx
// check our valueNode isn't a reference still.
if h, _, refVal := utils.IsNodeRefValue(en); h {
ref, err := LocateRefNode(en, idx)
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, en, idx)
if ref != nil {
en = ref
referenceValue = refVal
if fIdx != nil {
foundIndex = fIdx
}
foundContext = nCtx
if err != nil {
circError = err
}
@@ -610,7 +742,7 @@ func ExtractMapExtensions[PT Buildable[N], N any](
}
}
totalKeys++
go buildMap(currentLabelNode, en, bChan, eChan, referenceValue)
go buildMap(foundContext, currentLabelNode, en, bChan, eChan, referenceValue, foundIndex)
}
completedKeys := 0
@@ -637,11 +769,12 @@ func ExtractMapExtensions[PT Buildable[N], N any](
// The second return value is the yaml.Node found for the 'label' and the third return value is the yaml.Node
// found for the value extracted from the label node.
func ExtractMap[PT Buildable[N], N any](
ctx context.Context,
label string,
root *yaml.Node,
idx *index.SpecIndex,
) (orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) {
return ExtractMapExtensions[PT, N](label, root, idx, false)
return ExtractMapExtensions[PT, N](ctx, label, root, idx, false)
}
// ExtractExtensions will extract any 'x-' prefixed key nodes from a root node into a map. Requirements have been pre-cast:
@@ -714,6 +847,14 @@ func AreEqual(l, r Hashable) bool {
if l == nil || r == nil {
return false
}
vol := reflect.ValueOf(l)
vor := reflect.ValueOf(r)
if vol.Kind() != reflect.Struct && vor.Kind() != reflect.Struct {
if vol.IsNil() || vor.IsNil() {
return false
}
}
return l.Hash() == r.Hash()
}
@@ -734,3 +875,23 @@ func GenerateHashString(v any) string {
}
return fmt.Sprintf(HASH, sha256.Sum256([]byte(fmt.Sprint(v))))
}
// LocateRefEnd will perform a complete lookup for a $ref node. This function searches the entire index for
// the reference being supplied. If there is a match found, the reference *yaml.Node is returned.
// the function operates recursively and will keep iterating through references until it finds a non-reference
// node.
func LocateRefEnd(ctx context.Context, root *yaml.Node, idx *index.SpecIndex, depth int) (*yaml.Node, *index.SpecIndex, error, context.Context) {
depth++
if depth > 100 {
return nil, nil, fmt.Errorf("reference resolution depth exceeded, possible circular reference"), ctx
}
ref, fIdx, err, nCtx := LocateRefNodeWithContext(ctx, root, idx)
if err != nil {
return ref, fIdx, err, nCtx
}
if rf, _, _ := utils.IsNodeRefValue(ref); rf {
return LocateRefEnd(nCtx, ref, fIdx, depth)
} else {
return ref, fIdx, err, nCtx
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -96,8 +96,6 @@ type OpenAPIParameter interface {
//TODO: this needs to be fixed, move returns to pointers.
type SharedOperations interface {
//HasDescription
//HasExternalDocs
GetOperationId() NodeReference[string]
GetExternalDocs() NodeReference[any]
GetDescription() NodeReference[string]

View File

@@ -1,6 +1,7 @@
package low
import (
"context"
"fmt"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
@@ -38,7 +39,7 @@ type IsReferenced interface {
//
// Used by generic functions when automatically building out structs based on yaml.Node inputs.
type Buildable[T any] interface {
Build(key, value *yaml.Node, idx *index.SpecIndex) error
Build(ctx context.Context, key, value *yaml.Node, idx *index.SpecIndex) error
*T
}
@@ -112,6 +113,8 @@ type NodeReference[T any] struct {
// If HasReference is true, then Reference contains the original $ref value.
Reference string
Context context.Context
}
// KeyReference is a low-level container for key nodes holding a Value of type T. A KeyNode is a pointer to the

View File

@@ -11,7 +11,6 @@ import (
"testing"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/resolver"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
@@ -124,14 +123,14 @@ func TestIsCircular_LookupFromJourney(t *testing.T) {
yml = `$ref: '#/components/schemas/Something'`
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 1)
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
ref, err := LocateRefNode(idxNode.Content[0], idx)
ref, _, err := LocateRefNode(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.True(t, IsCircular(ref, idx))
}
@@ -157,14 +156,14 @@ func TestIsCircular_LookupFromJourney_Optional(t *testing.T) {
yml = `$ref: '#/components/schemas/Something'`
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 0)
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
ref, err := LocateRefNode(idxNode.Content[0], idx)
ref, _, err := LocateRefNode(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.True(t, IsCircular(ref, idx))
}
@@ -193,14 +192,14 @@ func TestIsCircular_LookupFromLoopPoint(t *testing.T) {
yml = `$ref: '#/components/schemas/Nothing'`
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 1)
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
ref, err := LocateRefNode(idxNode.Content[0], idx)
ref, _, err := LocateRefNode(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.True(t, IsCircular(ref, idx))
}
@@ -225,14 +224,14 @@ func TestIsCircular_LookupFromLoopPoint_Optional(t *testing.T) {
yml = `$ref: '#/components/schemas/Nothing'`
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 0)
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
ref, err := LocateRefNode(idxNode.Content[0], idx)
ref, _, err := LocateRefNode(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.True(t, IsCircular(ref, idx))
}
@@ -262,7 +261,7 @@ func TestIsCircular_FromRefLookup(t *testing.T) {
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode)
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 1)
@@ -298,7 +297,7 @@ func TestIsCircular_FromRefLookup_Optional(t *testing.T) {
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode)
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 0)
@@ -346,14 +345,14 @@ func TestGetCircularReferenceResult_FromJourney(t *testing.T) {
yml = `$ref: '#/components/schemas/Something'`
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 1)
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
ref, err := LocateRefNode(idxNode.Content[0], idx)
ref, _, err := LocateRefNode(idxNode.Content[0], idx)
assert.NoError(t, err)
circ := GetCircularReferenceResult(ref, idx)
assert.NotNil(t, circ)
@@ -380,14 +379,14 @@ func TestGetCircularReferenceResult_FromJourney_Optional(t *testing.T) {
yml = `$ref: '#/components/schemas/Something'`
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 0)
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
ref, err := LocateRefNode(idxNode.Content[0], idx)
ref, _, err := LocateRefNode(idxNode.Content[0], idx)
assert.NoError(t, err)
circ := GetCircularReferenceResult(ref, idx)
assert.NotNil(t, circ)
@@ -418,14 +417,14 @@ func TestGetCircularReferenceResult_FromLoopPoint(t *testing.T) {
yml = `$ref: '#/components/schemas/Nothing'`
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 1)
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
ref, err := LocateRefNode(idxNode.Content[0], idx)
ref, _, err := LocateRefNode(idxNode.Content[0], idx)
assert.NoError(t, err)
circ := GetCircularReferenceResult(ref, idx)
assert.NotNil(t, circ)
@@ -452,14 +451,14 @@ func TestGetCircularReferenceResult_FromLoopPoint_Optional(t *testing.T) {
yml = `$ref: '#/components/schemas/Nothing'`
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 0)
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
ref, err := LocateRefNode(idxNode.Content[0], idx)
ref, _, err := LocateRefNode(idxNode.Content[0], idx)
assert.NoError(t, err)
circ := GetCircularReferenceResult(ref, idx)
assert.NotNil(t, circ)
@@ -490,7 +489,7 @@ func TestGetCircularReferenceResult_FromMappedRef(t *testing.T) {
yml = `$ref: '#/components/schemas/Nothing'`
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 1)
@@ -522,7 +521,7 @@ func TestGetCircularReferenceResult_FromMappedRef_Optional(t *testing.T) {
yml = `$ref: '#/components/schemas/Nothing'`
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 0)
@@ -545,7 +544,7 @@ func TestGetCircularReferenceResult_NothingFound(t *testing.T) {
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode)
resolve := resolver.NewResolver(idx)
resolve := index.NewResolver(idx)
errs := resolve.CheckForCircularReferences()
assert.Len(t, errs, 0)

View File

@@ -1,4 +0,0 @@
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package low

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"sort"
"strings"
@@ -73,7 +74,7 @@ func (s *SecurityDefinitions) FindSecurityDefinition(securityDef string) *low.Va
}
// Build will extract all definitions into SchemaProxy instances.
func (d *Definitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (d *Definitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
// TODO: Refactor with orderedmap.TranslatePipeline.
@@ -84,7 +85,7 @@ func (d *Definitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*base.SchemaProxy], e chan error) {
obj, err, _, rv := low.ExtractObjectRaw[*base.SchemaProxy](label, value, idx)
obj, err, _, rv := low.ExtractObjectRaw[*base.SchemaProxy](ctx, label, value, idx)
if err != nil {
e <- err
}
@@ -137,7 +138,7 @@ func (d *Definitions) Hash() [32]byte {
}
// Build will extract all ParameterDefinitions into Parameter instances.
func (pd *ParameterDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (pd *ParameterDefinitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
errorChan := make(chan error)
resultChan := make(chan definitionResult[*Parameter])
var defLabel *yaml.Node
@@ -145,7 +146,7 @@ func (pd *ParameterDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex)
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*Parameter], e chan error) {
obj, err, _, rv := low.ExtractObjectRaw[*Parameter](label, value, idx)
obj, err, _, rv := low.ExtractObjectRaw[*Parameter](ctx, label, value, idx)
if err != nil {
e <- err
}
@@ -187,7 +188,7 @@ type definitionResult[T any] struct {
}
// Build will extract all ResponsesDefinitions into Response instances.
func (r *ResponsesDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (r *ResponsesDefinitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
errorChan := make(chan error)
resultChan := make(chan definitionResult[*Response])
var defLabel *yaml.Node
@@ -195,7 +196,7 @@ func (r *ResponsesDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) e
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*Response], e chan error) {
obj, err, _, rv := low.ExtractObjectRaw[*Response](label, value, idx)
obj, err, _, rv := low.ExtractObjectRaw[*Response](ctx, label, value, idx)
if err != nil {
e <- err
}
@@ -231,7 +232,7 @@ func (r *ResponsesDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) e
}
// Build will extract all SecurityDefinitions into SecurityScheme instances.
func (s *SecurityDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (s *SecurityDefinitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
errorChan := make(chan error)
resultChan := make(chan definitionResult[*SecurityScheme])
var defLabel *yaml.Node
@@ -240,7 +241,7 @@ func (s *SecurityDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) er
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*SecurityScheme], e chan error) {
obj, err, _, rv := low.ExtractObjectRaw[*SecurityScheme](label, value, idx)
obj, err, _, rv := low.ExtractObjectRaw[*SecurityScheme](ctx, label, value, idx)
if err != nil {
e <- err
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -25,7 +26,7 @@ func TestDefinitions_Schemas_Build_Error(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -44,7 +45,7 @@ func TestDefinitions_Parameters_Build_Error(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -63,7 +64,7 @@ func TestDefinitions_Hash(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Equal(t, "26d23786e6873e1a337f8e9be85f7de1490e4ff6cd303c3b15e593a25a6a149d",
low.GenerateHashString(&n))
@@ -83,7 +84,7 @@ func TestDefinitions_Responses_Build_Error(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -102,7 +103,7 @@ func TestDefinitions_Security_Build_Error(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"fmt"
"sort"
@@ -29,7 +30,7 @@ func (e *Examples) FindExample(name string) *low.ValueReference[any] {
}
// Build will extract all examples and will attempt to unmarshal content into a map or slice based on type.
func (e *Examples) Build(_, root *yaml.Node, _ *index.SpecIndex) error {
func (e *Examples) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
var keyNode, currNode *yaml.Node

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -27,7 +28,7 @@ nothing: int`
var n Examples
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `anything:
cake: burger
@@ -43,7 +44,7 @@ yes:
var n2 Examples
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -51,11 +52,11 @@ func (h *Header) GetExtensions() map[low.KeyReference[string]]low.ValueReference
}
// Build will build out items, extensions and default value from the supplied node.
func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (h *Header) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
h.Extensions = low.ExtractExtensions(root)
items, err := low.ExtractObject[*Items](ItemsLabel, root, idx)
items, err := low.ExtractObject[*Items](ctx, ItemsLabel, root, idx)
if err != nil {
return err
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -25,7 +26,7 @@ func TestHeader_Build(t *testing.T) {
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -44,7 +45,7 @@ default:
var n Header
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NotNil(t, n.Default.Value)
assert.Len(t, n.Default.Value, 3)
@@ -65,7 +66,7 @@ func TestHeader_DefaultAsObject(t *testing.T) {
var n Header
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NotNil(t, n.Default.Value)
}
@@ -80,7 +81,7 @@ func TestHeader_NoDefault(t *testing.T) {
var n Header
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Equal(t, 12, n.Minimum.Value)
}
@@ -116,7 +117,7 @@ multipleOf: 12`
var n Header
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `description: head
items:
@@ -148,7 +149,7 @@ pattern: wow
var n2 Header
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -102,11 +103,11 @@ func (i *Items) Hash() [32]byte {
}
// Build will build out items and default value.
func (i *Items) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (i *Items) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
i.Extensions = low.ExtractExtensions(root)
items, iErr := low.ExtractObject[*Items](ItemsLabel, root, idx)
items, iErr := low.ExtractObject[*Items](ctx, ItemsLabel, root, idx)
if iErr != nil {
return iErr
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -25,7 +26,7 @@ func TestItems_Build(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -42,7 +43,7 @@ default:
var n Items
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value, 2)
assert.Len(t, n.GetExtensions(), 1)
@@ -60,7 +61,7 @@ func TestItems_DefaultAsMap(t *testing.T) {
var n Items
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value, 2)
@@ -96,7 +97,7 @@ multipleOf: 12`
var n Items
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `items:
type: int
@@ -127,7 +128,7 @@ pattern: wow
var n2 Items
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -36,20 +37,20 @@ type Operation struct {
}
// Build will extract external docs, extensions, parameters, responses and security requirements.
func (o *Operation) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (o *Operation) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
o.Extensions = low.ExtractExtensions(root)
// extract externalDocs
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, root, idx)
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](ctx, base.ExternalDocsLabel, root, idx)
if dErr != nil {
return dErr
}
o.ExternalDocs = extDocs
// extract parameters
params, ln, vn, pErr := low.ExtractArray[*Parameter](ParametersLabel, root, idx)
params, ln, vn, pErr := low.ExtractArray[*Parameter](ctx, ParametersLabel, root, idx)
if pErr != nil {
return pErr
}
@@ -62,14 +63,14 @@ func (o *Operation) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
}
// extract responses
respBody, respErr := low.ExtractObject[*Responses](ResponsesLabel, root, idx)
respBody, respErr := low.ExtractObject[*Responses](ctx, ResponsesLabel, root, idx)
if respErr != nil {
return respErr
}
o.Responses = respBody
// extract security
sec, sln, svn, sErr := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, root, idx)
sec, sln, svn, sErr := low.ExtractArray[*base.SecurityRequirement](ctx, SecurityLabel, root, idx)
if sErr != nil {
return sErr
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -28,7 +29,7 @@ func TestOperation_Build_ExternalDocs(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -47,7 +48,7 @@ func TestOperation_Build_Params(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -66,7 +67,7 @@ func TestOperation_Build_Responses(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -85,7 +86,7 @@ func TestOperation_Build_Security(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -128,7 +129,7 @@ x-smoke: not for a while`
var n Operation
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `summary: a nice day
tags:
@@ -166,7 +167,7 @@ security:
var n2 Operation
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -5,8 +5,10 @@ package v2
import (
"fmt"
"github.com/pb33f/libopenapi/utils"
"os"
"github.com/pb33f/libopenapi/datamodel"
"io/ioutil"
)
// How to create a low-level Swagger / OpenAPI 2 Document from a specification
@@ -15,18 +17,19 @@ func Example_createLowLevelSwaggerDocument() {
// How to create a low-level OpenAPI 2 Document
// load petstore into bytes
petstoreBytes, _ := ioutil.ReadFile("../../../test_specs/petstorev2.json")
petstoreBytes, _ := os.ReadFile("../../../test_specs/petstorev2.json")
// read in specification
info, _ := datamodel.ExtractSpecInfo(petstoreBytes)
// build low-level document model
document, errors := CreateDocument(info)
document, err := CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
// if something went wrong, a slice of errors is returned
if len(errors) > 0 {
for i := range errors {
fmt.Printf("error: %s\n", errors[i].Error())
errs := utils.UnwrapErrors(err)
if len(errs) > 0 {
for i := range errs {
fmt.Printf("error: %s\n", errs[i].Error())
}
panic("cannot build document")
}
@@ -43,18 +46,19 @@ func ExampleCreateDocument() {
// How to create a low-level OpenAPI 2 Document
// load petstore into bytes
petstoreBytes, _ := ioutil.ReadFile("../../../test_specs/petstorev2.json")
petstoreBytes, _ := os.ReadFile("../../../test_specs/petstorev2.json")
// read in specification
info, _ := datamodel.ExtractSpecInfo(petstoreBytes)
// build low-level document model
document, errors := CreateDocument(info)
document, err := CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
// if something went wrong, a slice of errors is returned
if len(errors) > 0 {
for i := range errors {
fmt.Printf("error: %s\n", errors[i].Error())
errs := utils.UnwrapErrors(err)
if len(errs) > 0 {
for i := range errs {
fmt.Printf("error: %s\n", errs[i].Error())
}
panic("cannot build document")
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -94,18 +95,18 @@ func (p *Parameter) GetExtensions() map[low.KeyReference[string]]low.ValueRefere
}
// Build will extract out extensions, schema, items and default value
func (p *Parameter) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (p *Parameter) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
p.Extensions = low.ExtractExtensions(root)
sch, sErr := base.ExtractSchema(root, idx)
sch, sErr := base.ExtractSchema(ctx, root, idx)
if sErr != nil {
return sErr
}
if sch != nil {
p.Schema = *sch
}
items, iErr := low.ExtractObject[*Items](ItemsLabel, root, idx)
items, iErr := low.ExtractObject[*Items](ctx, ItemsLabel, root, idx)
if iErr != nil {
return iErr
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
@@ -25,7 +26,7 @@ func TestParameter_Build(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -44,7 +45,7 @@ func TestParameter_Build_Items(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -63,7 +64,7 @@ func TestParameter_DefaultSlice(t *testing.T) {
var n Parameter
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value.([]any), 3)
}
@@ -80,7 +81,7 @@ func TestParameter_DefaultMap(t *testing.T) {
var n Parameter
_ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Len(t, n.Default.Value.(map[string]any), 2)
}
@@ -95,7 +96,7 @@ func TestParameter_NoDefaultNoError(t *testing.T) {
var n Parameter
_ = low.BuildModel(&idxNode, &n)
err := n.Build(nil, idxNode.Content[0], idx)
err := n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
}
@@ -136,7 +137,7 @@ required: true`
var n Parameter
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `items:
type: int
@@ -174,7 +175,7 @@ allowEmptyValue: true
var n2 Parameter
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"fmt"
"sort"
@@ -48,7 +49,7 @@ func (p *PathItem) GetExtensions() map[low.KeyReference[string]]low.ValueReferen
// Build will extract extensions, parameters and operations for all methods. Every method is handled
// asynchronously, in order to keep things moving quickly for complex operations.
func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (p *PathItem) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
p.Extensions = low.ExtractExtensions(root)
@@ -61,7 +62,7 @@ func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
var ops []low.NodeReference[*Operation]
// extract parameters
params, ln, vn, pErr := low.ExtractArray[*Parameter](ParametersLabel, root, idx)
params, ln, vn, pErr := low.ExtractArray[*Parameter](ctx, ParametersLabel, root, idx)
if pErr != nil {
return pErr
}
@@ -158,7 +159,7 @@ func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
opErrorChan := make(chan error)
var buildOpFunc = func(op low.NodeReference[*Operation], ch chan<- bool, errCh chan<- error) {
er := op.Value.Build(op.KeyNode, op.ValueNode, idx)
er := op.Value.Build(ctx, op.KeyNode, op.ValueNode, idx)
if er != nil {
errCh <- er
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -25,7 +26,7 @@ func TestPathItem_Build_Params(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -44,7 +45,7 @@ func TestPathItem_Build_MethodFail(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -76,7 +77,7 @@ x-winter: is coming`
var n PathItem
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `post:
description: post me there
@@ -103,7 +104,7 @@ parameters:
var n2 PathItem
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"fmt"
"sort"
@@ -58,7 +59,7 @@ func (p *Paths) FindExtension(ext string) *low.ValueReference[any] {
}
// Build will extract extensions and paths from node.
func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (p *Paths) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
p.Extensions = low.ExtractExtensions(root)
@@ -130,7 +131,7 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
cNode := value.currentNode
path := new(PathItem)
_ = low.BuildModel(pNode, path)
err := path.Build(cNode, pNode, idx)
err := path.Build(ctx, cNode, pNode, idx)
if err != nil {
return pathBuildResult{}, err
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"fmt"
"testing"
@@ -27,7 +28,7 @@ func TestPaths_Build(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -47,7 +48,7 @@ func TestPaths_FindPathAndKey(t *testing.T) {
var n Paths
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
_, k := n.FindPathAndKey("/no/pizza")
assert.Equal(t, "because i'm fat", k.Value.Post.Value.Description.Value)
@@ -74,7 +75,7 @@ x-milk: creamy`
var n Paths
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `x-milk: creamy
/spl/unk:
@@ -94,7 +95,7 @@ x-milk: creamy`
var n2 Paths
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())
@@ -123,6 +124,6 @@ func TestPaths_Build_Fail_Many(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"fmt"
"sort"
@@ -45,11 +46,11 @@ func (r *Response) FindHeader(hType string) *low.ValueReference[*Header] {
}
// Build will extract schema, extensions, examples and headers from node
func (r *Response) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (r *Response) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
r.Extensions = low.ExtractExtensions(root)
s, err := base.ExtractSchema(root, idx)
s, err := base.ExtractSchema(ctx, root, idx)
if err != nil {
return err
}
@@ -58,14 +59,14 @@ func (r *Response) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
}
// extract examples
examples, expErr := low.ExtractObject[*Examples](ExamplesLabel, root, idx)
examples, expErr := low.ExtractObject[*Examples](ctx, ExamplesLabel, root, idx)
if expErr != nil {
return expErr
}
r.Examples = examples
//extract headers
headers, lN, kN, err := low.ExtractMap[*Header](HeadersLabel, root, idx)
headers, lN, kN, err := low.ExtractMap[*Header](ctx, HeadersLabel, root, idx)
if err != nil {
return err
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -25,7 +26,7 @@ func TestResponse_Build_Schema(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -44,7 +45,7 @@ func TestResponse_Build_Examples(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -63,7 +64,7 @@ func TestResponse_Build_Headers(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -87,7 +88,7 @@ x-herbs: missing`
var n Response
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `description: your thing, sir.
examples:
@@ -106,7 +107,7 @@ headers:
var n2 Response
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"fmt"
"sort"
@@ -29,13 +30,13 @@ func (r *Responses) GetExtensions() map[low.KeyReference[string]]low.ValueRefere
}
// Build will extract default value and extensions from node.
func (r *Responses) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (r *Responses) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
r.Extensions = low.ExtractExtensions(root)
if utils.IsNodeMap(root) {
codes, err := low.ExtractMapNoLookup[*Response](root, idx)
codes, err := low.ExtractMapNoLookup[*Response](ctx, root, idx)
if err != nil {
return err
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -24,7 +25,7 @@ func TestResponses_Build_Response(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -43,7 +44,7 @@ func TestResponses_Build_Response_Default(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -61,7 +62,7 @@ func TestResponses_Build_WrongType(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -88,7 +89,7 @@ x-tea: warm
var n Responses
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `401:
description: and you are?
@@ -110,7 +111,7 @@ x-tea: warm`
var n2 Responses
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"fmt"
"sort"
@@ -36,7 +37,7 @@ func (s *Scopes) FindScope(scope string) *low.ValueReference[string] {
}
// Build will extract scope values and extensions from node.
func (s *Scopes) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (s *Scopes) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
s.Extensions = low.ExtractExtensions(root)

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -23,7 +24,7 @@ x-men: needs a reboot or a refresh`
var n Scopes
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `x-men: needs a reboot or a refresh
pizza: beans
@@ -35,7 +36,7 @@ burgers: chips`
var n2 Scopes
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"crypto/sha256"
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -38,12 +39,12 @@ func (ss *SecurityScheme) GetExtensions() map[low.KeyReference[string]]low.Value
}
// Build will extract extensions and scopes from the node.
func (ss *SecurityScheme) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (ss *SecurityScheme) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
ss.Extensions = low.ExtractExtensions(root)
scopes, sErr := low.ExtractObject[*Scopes](ScopesLabel, root, idx)
scopes, sErr := low.ExtractObject[*Scopes](ctx, ScopesLabel, root, idx)
if sErr != nil {
return sErr
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -27,7 +28,7 @@ func TestSecurityScheme_Build_Borked(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -47,7 +48,7 @@ func TestSecurityScheme_Build_Scopes(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, 2, orderedmap.Len(n.Scopes.Value.Values))
@@ -72,7 +73,7 @@ x-beer: not for a while`
var n SecurityScheme
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `in: my heart
scopes:
@@ -92,7 +93,7 @@ authorizationUrl: https://pb33f.io
var n2 SecurityScheme
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -12,16 +12,18 @@
package v2
import (
"context"
"errors"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/resolver"
"gopkg.in/yaml.v3"
"path/filepath"
)
// processes a property of a Swagger document asynchronously using bool and error channels for signals.
type documentFunction func(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error)
type documentFunction func(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error)
// Swagger represents a high-level Swagger / OpenAPI 2 document. An instance of Swagger is the root of the specification.
type Swagger struct {
@@ -109,6 +111,10 @@ type Swagger struct {
//
// This property is not a part of the OpenAPI schema, this is custom to libopenapi.
SpecInfo *datamodel.SpecInfo
// Rolodex is a reference to the index.Rolodex instance created when the specification was read.
// The rolodex is used to look up references from file systems (local or remote)
Rolodex *index.Rolodex
}
// FindExtension locates an extension from the root of the Swagger document.
@@ -123,57 +129,100 @@ func (s *Swagger) GetExtensions() map[low.KeyReference[string]]low.ValueReferenc
// CreateDocumentFromConfig will create a new Swagger document from the provided SpecInfo and DocumentConfiguration.
func CreateDocumentFromConfig(info *datamodel.SpecInfo,
configuration *datamodel.DocumentConfiguration) (*Swagger, []error) {
configuration *datamodel.DocumentConfiguration) (*Swagger, error) {
return createDocument(info, configuration)
}
// CreateDocument will create a new Swagger document from the provided SpecInfo.
//
// Deprecated: Use CreateDocumentFromConfig instead.
func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) {
return createDocument(info, &datamodel.DocumentConfiguration{
AllowRemoteReferences: 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.Extensions = low.ExtractExtensions(info.RootNode.Content[0])
// build an index
idx := index.NewSpecIndexWithConfig(info.RootNode, &index.SpecIndexConfig{
BaseURL: config.BaseURL,
RemoteURLHandler: config.RemoteURLHandler,
AllowRemoteLookup: config.AllowRemoteReferences,
AllowFileLookup: config.AllowFileReferences,
})
doc.Index = idx
doc.SpecInfo = info
// create an index config and shadow the document configuration.
idxConfig := index.CreateClosedAPIIndexConfig()
idxConfig.SpecInfo = info
idxConfig.IgnoreArrayCircularReferences = config.IgnoreArrayCircularReferences
idxConfig.IgnorePolymorphicCircularReferences = config.IgnorePolymorphicCircularReferences
idxConfig.AvoidCircularReferenceCheck = true
idxConfig.BaseURL = config.BaseURL
idxConfig.BasePath = config.BasePath
idxConfig.Logger = config.Logger
rolodex := index.NewRolodex(idxConfig)
rolodex.SetRootNode(info.RootNode)
doc.Rolodex = rolodex
var errors []error
// If basePath is provided, add a local filesystem to the rolodex.
if idxConfig.BasePath != "" {
var cwd string
cwd, _ = filepath.Abs(config.BasePath)
// if a supplied local filesystem is provided, add it to the rolodex.
if config.LocalFS != nil {
rolodex.AddLocalFS(cwd, config.LocalFS)
} else {
// create a local filesystem
localFSConf := index.LocalFSConfig{
BaseDirectory: cwd,
IndexConfig: idxConfig,
FileFilters: config.FileFilter,
}
fileFS, _ := index.NewLocalFSWithConfig(&localFSConf)
idxConfig.AllowFileLookup = true
// add the filesystem to the rolodex
rolodex.AddLocalFS(cwd, fileFS)
}
}
// if base url is provided, add a remote filesystem to the rolodex.
if idxConfig.BaseURL != nil {
// create a remote filesystem
remoteFS, _ := index.NewRemoteFSWithConfig(idxConfig)
if config.RemoteURLHandler != nil {
remoteFS.RemoteHandlerFunc = config.RemoteURLHandler
}
idxConfig.AllowRemoteLookup = true
// add to the rolodex
rolodex.AddRemoteFS(config.BaseURL.String(), remoteFS)
}
doc.Rolodex = rolodex
var errs []error
// index all the things!
_ = rolodex.IndexTheRolodex()
// check for circular references
if !config.SkipCircularReferenceCheck {
rolodex.CheckForCircularReferences()
}
// extract errors
roloErrs := rolodex.GetCaughtErrors()
if roloErrs != nil {
errs = append(errs, roloErrs...)
}
// set the index on the document.
doc.Index = rolodex.GetRootIndex()
doc.SpecInfo = info
// build out swagger scalar variables.
_ = low.BuildModel(info.RootNode.Content[0], &doc)
ctx := context.Background()
// extract externalDocs
extDocs, err := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode, idx)
extDocs, err := low.ExtractObject[*base.ExternalDoc](ctx, base.ExternalDocsLabel, info.RootNode, rolodex.GetRootIndex())
if err != nil {
errors = append(errors, err)
errs = append(errs, err)
}
doc.ExternalDocs = extDocs
// create resolver and check for circular references.
resolve := resolver.NewResolver(idx)
resolvingErrors := resolve.CheckForCircularReferences()
if len(resolvingErrors) > 0 {
for r := range resolvingErrors {
errors = append(errors, resolvingErrors[r])
}
}
extractionFuncs := []documentFunction{
extractInfo,
extractPaths,
@@ -187,7 +236,7 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
doneChan := make(chan bool)
errChan := make(chan error)
for i := range extractionFuncs {
go extractionFuncs[i](info.RootNode.Content[0], &doc, idx, doneChan, errChan)
go extractionFuncs[i](ctx, info.RootNode.Content[0], &doc, rolodex.GetRootIndex(), doneChan, errChan)
}
completedExtractions := 0
for completedExtractions < len(extractionFuncs) {
@@ -196,11 +245,11 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
completedExtractions++
case e := <-errChan:
completedExtractions++
errors = append(errors, e)
errs = append(errs, e)
}
}
return &doc, errors
return &doc, errors.Join(errs...)
}
func (s *Swagger) GetExternalDocs() *low.NodeReference[any] {
@@ -211,8 +260,8 @@ func (s *Swagger) GetExternalDocs() *low.NodeReference[any] {
}
}
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)
func extractInfo(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
info, err := low.ExtractObject[*base.Info](ctx, base.InfoLabel, root, idx)
if err != nil {
e <- err
return
@@ -221,8 +270,8 @@ func extractInfo(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- b
c <- true
}
func extractPaths(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
paths, err := low.ExtractObject[*Paths](PathsLabel, root, idx)
func extractPaths(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
paths, err := low.ExtractObject[*Paths](ctx, PathsLabel, root, idx)
if err != nil {
e <- err
return
@@ -230,8 +279,8 @@ func extractPaths(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<-
doc.Paths = paths
c <- true
}
func extractDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
def, err := low.ExtractObject[*Definitions](DefinitionsLabel, root, idx)
func extractDefinitions(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
def, err := low.ExtractObject[*Definitions](ctx, DefinitionsLabel, root, idx)
if err != nil {
e <- err
return
@@ -239,8 +288,8 @@ func extractDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c c
doc.Definitions = def
c <- true
}
func extractParamDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
param, err := low.ExtractObject[*ParameterDefinitions](ParametersLabel, root, idx)
func extractParamDefinitions(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
param, err := low.ExtractObject[*ParameterDefinitions](ctx, ParametersLabel, root, idx)
if err != nil {
e <- err
return
@@ -249,8 +298,8 @@ func extractParamDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex
c <- true
}
func extractResponsesDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
resp, err := low.ExtractObject[*ResponsesDefinitions](ResponsesLabel, root, idx)
func extractResponsesDefinitions(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
resp, err := low.ExtractObject[*ResponsesDefinitions](ctx, ResponsesLabel, root, idx)
if err != nil {
e <- err
return
@@ -259,8 +308,8 @@ func extractResponsesDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecI
c <- true
}
func extractSecurityDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
sec, err := low.ExtractObject[*SecurityDefinitions](SecurityDefinitionsLabel, root, idx)
func extractSecurityDefinitions(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
sec, err := low.ExtractObject[*SecurityDefinitions](ctx, SecurityDefinitionsLabel, root, idx)
if err != nil {
e <- err
return
@@ -269,8 +318,8 @@ func extractSecurityDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIn
c <- true
}
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)
func extractTags(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
tags, ln, vn, err := low.ExtractArray[*base.Tag](ctx, base.TagsLabel, root, idx)
if err != nil {
e <- err
return
@@ -283,8 +332,8 @@ func extractTags(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- b
c <- true
}
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)
func extractSecurity(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](ctx, SecurityLabel, root, idx)
if err != nil {
e <- err
return

View File

@@ -5,7 +5,11 @@ package v2
import (
"fmt"
"io/ioutil"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"net/http"
"net/url"
"os"
"testing"
"github.com/pb33f/libopenapi/datamodel"
@@ -19,13 +23,10 @@ func initTest() {
if doc != nil {
return
}
data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml")
data, _ := os.ReadFile("../../../test_specs/petstorev2-complete.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
doc, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
var err error
doc, err = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
wait := true
for wait {
select {
@@ -40,13 +41,10 @@ func initTest() {
}
func BenchmarkCreateDocument(b *testing.B) {
data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml")
data, _ := os.ReadFile("../../../test_specs/petstorev2-complete.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
doc, _ = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
doc, _ = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
}
}
@@ -184,8 +182,8 @@ func TestCreateDocument_ExternalDocsBad(t *testing.T) {
$ref: bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
var err error
doc, err = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
wait := true
for wait {
select {
@@ -193,7 +191,7 @@ func TestCreateDocument_ExternalDocsBad(t *testing.T) {
wait = false
}
}
assert.Len(t, err, 1)
assert.Len(t, utils.UnwrapErrors(err), 2)
}
func TestCreateDocument_TagsBad(t *testing.T) {
@@ -202,8 +200,8 @@ func TestCreateDocument_TagsBad(t *testing.T) {
$ref: bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
var err error
doc, err = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
wait := true
for wait {
select {
@@ -211,7 +209,7 @@ func TestCreateDocument_TagsBad(t *testing.T) {
wait = false
}
}
assert.Len(t, err, 1)
assert.Len(t, utils.UnwrapErrors(err), 2)
}
func TestCreateDocument_PathsBad(t *testing.T) {
@@ -224,8 +222,8 @@ func TestCreateDocument_PathsBad(t *testing.T) {
$ref: bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
var err error
doc, err = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
wait := true
for wait {
select {
@@ -233,7 +231,7 @@ func TestCreateDocument_PathsBad(t *testing.T) {
wait = false
}
}
assert.Len(t, err, 1)
assert.Len(t, utils.UnwrapErrors(err), 2)
}
func TestCreateDocument_SecurityBad(t *testing.T) {
@@ -242,8 +240,8 @@ func TestCreateDocument_SecurityBad(t *testing.T) {
$ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
var err error
doc, err = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
wait := true
for wait {
select {
@@ -251,7 +249,7 @@ func TestCreateDocument_SecurityBad(t *testing.T) {
wait = false
}
}
assert.Len(t, err, 1)
assert.Len(t, utils.UnwrapErrors(err), 1)
}
func TestCreateDocument_SecurityDefinitionsBad(t *testing.T) {
@@ -260,8 +258,8 @@ func TestCreateDocument_SecurityDefinitionsBad(t *testing.T) {
$ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
var err error
doc, err = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
wait := true
for wait {
select {
@@ -269,7 +267,7 @@ func TestCreateDocument_SecurityDefinitionsBad(t *testing.T) {
wait = false
}
}
assert.Len(t, err, 1)
assert.Len(t, utils.UnwrapErrors(err), 1)
}
func TestCreateDocument_ResponsesBad(t *testing.T) {
@@ -278,8 +276,8 @@ func TestCreateDocument_ResponsesBad(t *testing.T) {
$ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
var err error
doc, err = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
wait := true
for wait {
select {
@@ -287,7 +285,7 @@ func TestCreateDocument_ResponsesBad(t *testing.T) {
wait = false
}
}
assert.Len(t, err, 1)
assert.Len(t, utils.UnwrapErrors(err), 1)
}
func TestCreateDocument_ParametersBad(t *testing.T) {
@@ -296,8 +294,8 @@ func TestCreateDocument_ParametersBad(t *testing.T) {
$ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
var err error
doc, err = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
wait := true
for wait {
select {
@@ -305,7 +303,7 @@ func TestCreateDocument_ParametersBad(t *testing.T) {
wait = false
}
}
assert.Len(t, err, 1)
assert.Len(t, utils.UnwrapErrors(err), 1)
}
func TestCreateDocument_DefinitionsBad(t *testing.T) {
@@ -314,8 +312,8 @@ func TestCreateDocument_DefinitionsBad(t *testing.T) {
$ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
var err error
doc, err = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
wait := true
for wait {
select {
@@ -323,7 +321,7 @@ func TestCreateDocument_DefinitionsBad(t *testing.T) {
wait = false
}
}
assert.Len(t, err, 1)
assert.Len(t, utils.UnwrapErrors(err), 1)
}
func TestCreateDocument_InfoBad(t *testing.T) {
@@ -332,8 +330,8 @@ func TestCreateDocument_InfoBad(t *testing.T) {
$ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
var err error
doc, err = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
wait := true
for wait {
select {
@@ -341,15 +339,151 @@ func TestCreateDocument_InfoBad(t *testing.T) {
wait = false
}
}
assert.Len(t, err, 1)
assert.Len(t, utils.UnwrapErrors(err), 1)
}
func TestCircularReferenceError(t *testing.T) {
data, _ := ioutil.ReadFile("../../../test_specs/swagger-circular-tests.yaml")
data, _ := os.ReadFile("../../../test_specs/swagger-circular-tests.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
circDoc, err := CreateDocument(info)
circDoc, err := CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
assert.NotNil(t, circDoc)
assert.Len(t, err, 3)
assert.Len(t, utils.UnwrapErrors(err), 3)
}
func TestRolodexLocalFileSystem(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
cf.BasePath = "../../../test_specs"
cf.FileFilter = []string{"first.yaml", "second.yaml", "third.yaml"}
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.NoError(t, err)
}
func TestRolodexLocalFileSystem_ProvideNonRolodexFS(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
baseDir := "../../../test_specs"
cf := datamodel.NewDocumentConfiguration()
cf.BasePath = baseDir
cf.FileFilter = []string{"first.yaml", "second.yaml", "third.yaml"}
cf.LocalFS = os.DirFS(baseDir)
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.Error(t, err)
}
func TestRolodexLocalFileSystem_ProvideRolodexFS(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
baseDir := "../../../test_specs"
cf := datamodel.NewDocumentConfiguration()
cf.BasePath = baseDir
cf.FileFilter = []string{"first.yaml", "second.yaml", "third.yaml"}
localFS, lErr := index.NewLocalFSWithConfig(&index.LocalFSConfig{
BaseDirectory: baseDir,
DirFS: os.DirFS(baseDir),
FileFilters: cf.FileFilter,
})
cf.LocalFS = localFS
assert.NoError(t, lErr)
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.NoError(t, err)
}
func TestRolodexLocalFileSystem_BadPath(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
cf.BasePath = "/NOWHERE"
cf.FileFilter = []string{"first.yaml", "second.yaml", "third.yaml"}
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.Error(t, err)
}
func TestRolodexRemoteFileSystem(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
baseUrl := "https://raw.githubusercontent.com/pb33f/libopenapi/main/test_specs"
u, _ := url.Parse(baseUrl)
cf.BaseURL = u
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.NoError(t, err)
}
func TestRolodexRemoteFileSystem_BadBase(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
baseUrl := "https://no-no-this-will-not-work-it-just-will-not-get-the-job-done-mate.com"
u, _ := url.Parse(baseUrl)
cf.BaseURL = u
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.Error(t, err)
}
func TestRolodexRemoteFileSystem_CustomRemote_NoBaseURL(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
cf.RemoteFS, _ = index.NewRemoteFSWithConfig(&index.SpecIndexConfig{})
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.Error(t, err)
}
func TestRolodexRemoteFileSystem_CustomHttpHandler(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
cf.RemoteURLHandler = http.Get
baseUrl := "https://no-no-this-will-not-work-it-just-will-not-get-the-job-done-mate.com"
u, _ := url.Parse(baseUrl)
cf.BaseURL = u
pizza := func(url string) (resp *http.Response, err error) {
return nil, nil
}
cf.RemoteURLHandler = pizza
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.Error(t, err)
}
func TestRolodexRemoteFileSystem_FailRemoteFS(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
cf.RemoteURLHandler = http.Get
baseUrl := "https://no-no-this-will-not-work-it-just-will-not-get-the-job-done-mate.com"
u, _ := url.Parse(baseUrl)
cf.BaseURL = u
pizza := func(url string) (resp *http.Response, err error) {
return nil, nil
}
cf.RemoteURLHandler = pizza
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.Error(t, err)
}

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"crypto/sha256"
"fmt"
"sort"
@@ -41,7 +42,7 @@ func (cb *Callback) FindExpression(exp string) *low.ValueReference[*PathItem] {
}
// Build will extract extensions, expressions and PathItem objects for Callback
func (cb *Callback) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (cb *Callback) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
cb.Reference = new(low.Reference)
@@ -59,7 +60,7 @@ func (cb *Callback) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
if strings.HasPrefix(currentCB.Value, "x-") {
continue // ignore extension.
}
callback, eErr, _, rv := low.ExtractObjectRaw[*PathItem](currentCB, callbackNode, idx)
callback, eErr, _, rv := low.ExtractObjectRaw[*PathItem](ctx, currentCB, callbackNode, idx)
if eErr != nil {
return eErr
}

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"testing"
"github.com/pb33f/libopenapi/datamodel/low"
@@ -35,7 +36,7 @@ func TestCallback_Build_Success(t *testing.T) {
err := low.BuildModel(rootNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, rootNode.Content[0], nil)
err = n.Build(context.Background(), nil, rootNode.Content[0], nil)
assert.NoError(t, err)
assert.Equal(t, 1, orderedmap.Len(n.Expression.Value))
@@ -67,7 +68,7 @@ func TestCallback_Build_Error(t *testing.T) {
err := low.BuildModel(rootNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, rootNode.Content[0], idx)
err = n.Build(context.Background(), nil, rootNode.Content[0], idx)
assert.Error(t, err)
}
@@ -102,7 +103,7 @@ func TestCallback_Build_Using_InlineRef(t *testing.T) {
err := low.BuildModel(rootNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, rootNode.Content[0], idx)
err = n.Build(context.Background(), nil, rootNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, 1, orderedmap.Len(n.Expression.Value))
@@ -130,7 +131,7 @@ x-weed: loved`
var n Callback
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `burgers:
description: tasty!
@@ -147,7 +148,7 @@ beer:
var n2 Callback
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"crypto/sha256"
"fmt"
"sort"
@@ -142,7 +143,7 @@ func (co *Components) FindCallback(callback string) *low.ValueReference[*Callbac
// Build converts root YAML node containing components to low level model.
// Process each component in parallel.
func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error {
func (co *Components) Build(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
co.Reference = new(low.Reference)
@@ -162,55 +163,55 @@ func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error {
}
go func() {
schemas, err := extractComponentValues[*base.SchemaProxy](SchemasLabel, root, idx)
schemas, err := extractComponentValues[*base.SchemaProxy](ctx, SchemasLabel, root, idx)
captureError(err)
co.Schemas = schemas
wg.Done()
}()
go func() {
parameters, err := extractComponentValues[*Parameter](ParametersLabel, root, idx)
parameters, err := extractComponentValues[*Parameter](ctx, ParametersLabel, root, idx)
captureError(err)
co.Parameters = parameters
wg.Done()
}()
go func() {
responses, err := extractComponentValues[*Response](ResponsesLabel, root, idx)
responses, err := extractComponentValues[*Response](ctx, ResponsesLabel, root, idx)
captureError(err)
co.Responses = responses
wg.Done()
}()
go func() {
examples, err := extractComponentValues[*base.Example](base.ExamplesLabel, root, idx)
examples, err := extractComponentValues[*base.Example](ctx, base.ExamplesLabel, root, idx)
captureError(err)
co.Examples = examples
wg.Done()
}()
go func() {
requestBodies, err := extractComponentValues[*RequestBody](RequestBodiesLabel, root, idx)
requestBodies, err := extractComponentValues[*RequestBody](ctx, RequestBodiesLabel, root, idx)
captureError(err)
co.RequestBodies = requestBodies
wg.Done()
}()
go func() {
headers, err := extractComponentValues[*Header](HeadersLabel, root, idx)
headers, err := extractComponentValues[*Header](ctx, HeadersLabel, root, idx)
captureError(err)
co.Headers = headers
wg.Done()
}()
go func() {
securitySchemes, err := extractComponentValues[*SecurityScheme](SecuritySchemesLabel, root, idx)
securitySchemes, err := extractComponentValues[*SecurityScheme](ctx, SecuritySchemesLabel, root, idx)
captureError(err)
co.SecuritySchemes = securitySchemes
wg.Done()
}()
go func() {
links, err := extractComponentValues[*Link](LinksLabel, root, idx)
links, err := extractComponentValues[*Link](ctx, LinksLabel, root, idx)
captureError(err)
co.Links = links
wg.Done()
}()
go func() {
callbacks, err := extractComponentValues[*Callback](CallbacksLabel, root, idx)
callbacks, err := extractComponentValues[*Callback](ctx, CallbacksLabel, root, idx)
captureError(err)
co.Callbacks = callbacks
wg.Done()
@@ -223,7 +224,7 @@ func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error {
// extractComponentValues converts all the YAML nodes of a component type to
// low level model.
// Process each node in parallel.
func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]], error) {
func extractComponentValues[T low.Buildable[N], N any](ctx context.Context, label string, root *yaml.Node, idx *index.SpecIndex) (low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]], error) {
var emptyResult low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]]
_, nodeLabel, nodeValue := utils.FindKeyNodeFullTop(label, root.Content)
if nodeValue == nil {
@@ -289,7 +290,7 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.
// TODO: check circular crazy on this. It may explode
var err error
if h, _, _ := utils.IsNodeRefValue(node); h && label != SchemasLabel {
node, err = low.LocateRefNode(node, idx)
node, _, err = low.LocateRefNode(node, idx)
}
if err != nil {
return componentBuildResult[T]{}, err
@@ -297,7 +298,7 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.
// build.
_ = low.BuildModel(node, n)
err = n.Build(currentLabel, node, idx)
err = n.Build(ctx, currentLabel, node, idx)
if err != nil {
return componentBuildResult[T]{}, err
}

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"fmt"
"testing"
@@ -66,7 +67,6 @@ var testComponentsYaml = `
description: eighteen of many`
func TestComponents_Build_Success(t *testing.T) {
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(testComponentsYaml), &idxNode)
assert.NoError(t, mErr)
@@ -76,7 +76,7 @@ func TestComponents_Build_Success(t *testing.T) {
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
err = n.Build(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "one of many", n.FindSchema("one").Value.Schema().Description.Value)
@@ -102,11 +102,9 @@ func TestComponents_Build_Success(t *testing.T) {
assert.Equal(t, "7add1a6c63a354b1a8ffe22552c213fe26d1229beb0b0cbe7c7ca06e63f9a364",
low.GenerateHashString(&n))
}
func TestComponents_Build_Success_Skip(t *testing.T) {
yml := `components:`
var idxNode yaml.Node
@@ -118,13 +116,11 @@ func TestComponents_Build_Success_Skip(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
err = n.Build(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, err)
}
func TestComponents_Build_Fail(t *testing.T) {
yml := `
parameters:
schema:
@@ -139,13 +135,11 @@ func TestComponents_Build_Fail(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
err = n.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestComponents_Build_ParameterFail(t *testing.T) {
yml := `
parameters:
pizza:
@@ -161,9 +155,8 @@ func TestComponents_Build_ParameterFail(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
err = n.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, err)
}
// Test parse failure among many parameters.
@@ -191,12 +184,11 @@ func TestComponents_Build_ParameterFail_Many(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
err = n.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestComponents_Build_Fail_TypeFail(t *testing.T) {
yml := `
parameters:
- schema:
@@ -211,12 +203,11 @@ func TestComponents_Build_Fail_TypeFail(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
err = n.Build(context.Background(), idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestComponents_Build_ExtensionTest(t *testing.T) {
yml := `x-curry: seagull
headers:
x-curry-gull: vinadloo`
@@ -230,14 +221,12 @@ headers:
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
err = n.Build(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "seagull", n.FindExtension("x-curry").Value)
}
func TestComponents_Build_HashEmpty(t *testing.T) {
yml := `x-curry: seagull`
var idxNode yaml.Node
@@ -249,11 +238,10 @@ func TestComponents_Build_HashEmpty(t *testing.T) {
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
err = n.Build(context.Background(), idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "seagull", n.FindExtension("x-curry").Value)
assert.Len(t, n.GetExtensions(), 1)
assert.Equal(t, "9cf2c6ab3f9ff7e5231fcb391c8af5c47406711d2ca366533f21a8bb2f67edfe",
low.GenerateHashString(&n))
}

View File

@@ -1,8 +1,9 @@
package v3
import (
"context"
"errors"
"os"
"path/filepath"
"sync"
"github.com/pb33f/libopenapi/datamodel"
@@ -10,7 +11,6 @@ import (
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/resolver"
"github.com/pb33f/libopenapi/utils"
)
@@ -18,70 +18,97 @@ import (
//
// Deprecated: Use CreateDocumentFromConfig instead. This function will be removed in a later version, it
// defaults to allowing file and remote references, and does not support relative file references.
func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
config := datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
}
return createDocument(info, &config)
func CreateDocument(info *datamodel.SpecInfo) (*Document, error) {
return createDocument(info, datamodel.NewDocumentConfiguration())
}
// CreateDocumentFromConfig Create a new document from the provided SpecInfo and DocumentConfiguration pointer.
func CreateDocumentFromConfig(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, []error) {
func CreateDocumentFromConfig(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, error) {
return createDocument(info, config)
}
func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, []error) {
func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, error) {
_, labelNode, versionNode := utils.FindKeyNodeFull(OpenAPILabel, info.RootNode.Content)
var version low.NodeReference[string]
if versionNode == nil {
return nil, []error{errors.New("no openapi version/tag found, cannot create document")}
return nil, errors.New("no openapi version/tag found, cannot create document")
}
version = low.NodeReference[string]{Value: versionNode.Value, KeyNode: labelNode, ValueNode: versionNode}
doc := Document{Version: version}
// get current working directory as a basePath
cwd, _ := os.Getwd()
// create an index config and shadow the document configuration.
idxConfig := index.CreateClosedAPIIndexConfig()
idxConfig.SpecInfo = info
idxConfig.IgnoreArrayCircularReferences = config.IgnoreArrayCircularReferences
idxConfig.IgnorePolymorphicCircularReferences = config.IgnorePolymorphicCircularReferences
idxConfig.AvoidCircularReferenceCheck = true
idxConfig.BaseURL = config.BaseURL
idxConfig.BasePath = config.BasePath
idxConfig.Logger = config.Logger
rolodex := index.NewRolodex(idxConfig)
rolodex.SetRootNode(info.RootNode)
doc.Rolodex = rolodex
// If basePath is provided override it
if config.BasePath != "" {
cwd = config.BasePath
}
// build an index
idx := index.NewSpecIndexWithConfig(info.RootNode, &index.SpecIndexConfig{
BaseURL: config.BaseURL,
RemoteURLHandler: config.RemoteURLHandler,
BasePath: cwd,
AllowFileLookup: config.AllowFileReferences,
AllowRemoteLookup: config.AllowRemoteReferences,
AvoidBuildIndex: config.AvoidIndexBuild,
})
doc.Index = idx
// If basePath is provided, add a local filesystem to the rolodex.
if idxConfig.BasePath != "" || config.AllowFileReferences {
var cwd string
cwd, _ = filepath.Abs(config.BasePath)
// if a supplied local filesystem is provided, add it to the rolodex.
if config.LocalFS != nil {
rolodex.AddLocalFS(cwd, config.LocalFS)
} else {
var errs []error
// create a local filesystem
localFSConf := index.LocalFSConfig{
BaseDirectory: cwd,
IndexConfig: idxConfig,
FileFilters: config.FileFilter,
}
errs = idx.GetReferenceIndexErrors()
fileFS, _ := index.NewLocalFSWithConfig(&localFSConf)
idxConfig.AllowFileLookup = true
// create resolver and check for circular references.
resolve := resolver.NewResolver(idx)
// if configured, ignore circular references in arrays and polymorphic schemas
if config.IgnoreArrayCircularReferences {
resolve.IgnoreArrayCircularReferences()
}
if config.IgnorePolymorphicCircularReferences {
resolve.IgnorePolymorphicCircularReferences()
}
// check for circular references.
resolvingErrors := resolve.CheckForCircularReferences()
if len(resolvingErrors) > 0 {
for r := range resolvingErrors {
errs = append(errs, resolvingErrors[r])
// add the filesystem to the rolodex
rolodex.AddLocalFS(cwd, fileFS)
}
}
// if base url is provided, add a remote filesystem to the rolodex.
if idxConfig.BaseURL != nil || config.AllowRemoteReferences {
// create a remote filesystem
remoteFS, _ := index.NewRemoteFSWithConfig(idxConfig)
if config.RemoteURLHandler != nil {
remoteFS.RemoteHandlerFunc = config.RemoteURLHandler
}
idxConfig.AllowRemoteLookup = true
// add to the rolodex
u := "default"
if config.BaseURL != nil {
u = config.BaseURL.String()
}
rolodex.AddRemoteFS(u, remoteFS)
}
// index the rolodex
var errs []error
// index all the things.
_ = rolodex.IndexTheRolodex()
// check for circular references
if !config.SkipCircularReferenceCheck {
rolodex.CheckForCircularReferences()
}
// extract errors
roloErrs := rolodex.GetCaughtErrors()
if roloErrs != nil {
errs = append(errs, roloErrs...)
}
// set root index.
doc.Index = rolodex.GetRootIndex()
var wg sync.WaitGroup
doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0])
@@ -94,17 +121,17 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
}
}
runExtraction := func(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex,
runFunc func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error,
runExtraction := func(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex,
runFunc func(ctx context.Context, i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error,
ers *[]error,
wg *sync.WaitGroup,
) {
if er := runFunc(info, doc, idx); er != nil {
if er := runFunc(ctx, info, doc, idx); er != nil {
*ers = append(*ers, er)
}
wg.Done()
}
extractionFuncs := []func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{
extractionFuncs := []func(ctx context.Context, i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{
extractInfo,
extractServers,
extractTags,
@@ -115,28 +142,30 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
extractWebhooks,
}
ctx := context.Background()
wg.Add(len(extractionFuncs))
for _, f := range extractionFuncs {
go runExtraction(info, &doc, idx, f, &errs, &wg)
go runExtraction(ctx, info, &doc, rolodex.GetRootIndex(), f, &errs, &wg)
}
wg.Wait()
return &doc, errs
return &doc, errors.Join(errs...)
}
func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
func extractInfo(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFullTop(base.InfoLabel, info.RootNode.Content[0].Content)
if vn != nil {
ir := base.Info{}
_ = low.BuildModel(vn, &ir)
_ = ir.Build(ln, vn, idx)
_ = ir.Build(ctx, ln, vn, idx)
nr := low.NodeReference[*base.Info]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Info = nr
}
return nil
}
func extractSecurity(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, info.RootNode.Content[0], idx)
func extractSecurity(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](ctx, SecurityLabel, info.RootNode.Content[0], idx)
if err != nil {
return err
}
@@ -150,8 +179,8 @@ func extractSecurity(info *datamodel.SpecInfo, doc *Document, idx *index.SpecInd
return nil
}
func extractExternalDocs(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode.Content[0], idx)
func extractExternalDocs(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](ctx, base.ExternalDocsLabel, info.RootNode.Content[0], idx)
if dErr != nil {
return dErr
}
@@ -159,12 +188,12 @@ func extractExternalDocs(info *datamodel.SpecInfo, doc *Document, idx *index.Spe
return nil
}
func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
func extractComponents(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFullTop(ComponentsLabel, info.RootNode.Content[0].Content)
if vn != nil {
ir := Components{}
_ = low.BuildModel(vn, &ir)
err := ir.Build(vn, idx)
err := ir.Build(ctx, vn, idx)
if err != nil {
return err
}
@@ -174,7 +203,7 @@ func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecI
return nil
}
func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
func extractServers(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(ServersLabel, info.RootNode.Content[0].Content)
if vn != nil {
if utils.IsNodeArray(vn) {
@@ -183,7 +212,7 @@ func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecInde
if utils.IsNodeMap(srvN) {
srvr := Server{}
_ = low.BuildModel(srvN, &srvr)
_ = srvr.Build(ln, srvN, idx)
_ = srvr.Build(ctx, ln, srvN, idx)
servers = append(servers, low.ValueReference[*Server]{
Value: &srvr,
ValueNode: srvN,
@@ -200,7 +229,7 @@ func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecInde
return nil
}
func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
func extractTags(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(base.TagsLabel, info.RootNode.Content[0].Content)
if vn != nil {
if utils.IsNodeArray(vn) {
@@ -209,7 +238,7 @@ func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex)
if utils.IsNodeMap(tagN) {
tag := base.Tag{}
_ = low.BuildModel(tagN, &tag)
if err := tag.Build(ln, tagN, idx); err != nil {
if err := tag.Build(ctx, ln, tagN, idx); err != nil {
return err
}
tags = append(tags, low.ValueReference[*base.Tag]{
@@ -228,11 +257,11 @@ func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex)
return nil
}
func extractPaths(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
func extractPaths(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(PathsLabel, info.RootNode.Content[0].Content)
if vn != nil {
ir := Paths{}
err := ir.Build(ln, vn, idx)
err := ir.Build(ctx, ln, vn, idx)
if err != nil {
return err
}
@@ -242,8 +271,8 @@ func extractPaths(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex)
return nil
}
func extractWebhooks(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
hooks, hooksL, hooksN, eErr := low.ExtractMap[*PathItem](WebhooksLabel, info.RootNode, idx)
func extractWebhooks(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
hooks, hooksL, hooksN, eErr := low.ExtractMap[*PathItem](ctx, WebhooksLabel, info.RootNode, idx)
if eErr != nil {
return eErr
}

View File

@@ -2,11 +2,15 @@ package v3
import (
"fmt"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"log/slog"
"net/http"
"net/url"
"os"
"testing"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
)
@@ -19,9 +23,9 @@ func initTest() {
}
data, _ := os.ReadFile("../../../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
var err error
// deprecated function test.
doc, err = CreateDocument(info)
doc, err = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
if err != nil {
panic("broken something")
}
@@ -31,10 +35,7 @@ func BenchmarkCreateDocument(b *testing.B) {
data, _ := os.ReadFile("../../../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
doc, _ = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
doc, _ = CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
}
}
@@ -42,47 +43,145 @@ func BenchmarkCreateDocument_Circular(b *testing.B) {
data, _ := os.ReadFile("../../../test_specs/circular-tests.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
_, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
if err != nil {
panic("this should not error")
}
}
}
func BenchmarkCreateDocument_k8s(b *testing.B) {
data, _ := os.ReadFile("../../../test_specs/k8s.json")
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
_, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
if err != nil {
panic("this should not error")
_, err := CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
if err == nil {
panic("this should error, it has circular references")
}
}
}
func TestCircularReferenceError(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/circular-tests.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
circDoc, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
circDoc, err := CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration())
assert.NotNil(t, circDoc)
assert.Len(t, err, 3)
assert.Error(t, err)
assert.Len(t, utils.UnwrapErrors(err), 3)
}
func TestRolodexLocalFileSystem(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
cf.BasePath = "../../../test_specs"
cf.FileFilter = []string{"first.yaml", "second.yaml", "third.yaml"}
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.NoError(t, err)
}
func TestRolodexLocalFileSystem_ProvideNonRolodexFS(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
baseDir := "../../../test_specs"
cf := datamodel.NewDocumentConfiguration()
cf.BasePath = baseDir
cf.FileFilter = []string{"first.yaml", "second.yaml", "third.yaml"}
cf.LocalFS = os.DirFS(baseDir)
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.Error(t, err)
}
func TestRolodexLocalFileSystem_ProvideRolodexFS(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
baseDir := "../../../test_specs"
cf := datamodel.NewDocumentConfiguration()
cf.BasePath = baseDir
cf.FileFilter = []string{"first.yaml", "second.yaml", "third.yaml"}
localFS, lErr := index.NewLocalFSWithConfig(&index.LocalFSConfig{
BaseDirectory: baseDir,
DirFS: os.DirFS(baseDir),
FileFilters: cf.FileFilter,
})
cf.LocalFS = localFS
assert.NoError(t, lErr)
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.NoError(t, err)
}
func TestRolodexLocalFileSystem_BadPath(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
cf.BasePath = "/NOWHERE"
cf.FileFilter = []string{"first.yaml", "second.yaml", "third.yaml"}
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.Error(t, err)
}
func TestRolodexRemoteFileSystem(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
cf.Logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
baseUrl := "https://raw.githubusercontent.com/pb33f/libopenapi/main/test_specs"
u, _ := url.Parse(baseUrl)
cf.BaseURL = u
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.NoError(t, err)
}
func TestRolodexRemoteFileSystem_BadBase(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
baseUrl := "https://no-no-this-will-not-work-it-just-will-not-get-the-job-done-mate.com"
u, _ := url.Parse(baseUrl)
cf.BaseURL = u
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.Error(t, err)
}
func TestRolodexRemoteFileSystem_CustomRemote_NoBaseURL(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
cf.RemoteFS, _ = index.NewRemoteFSWithConfig(&index.SpecIndexConfig{})
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.Error(t, err)
}
func TestRolodexRemoteFileSystem_CustomHttpHandler(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/first.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
cf := datamodel.NewDocumentConfiguration()
cf.RemoteURLHandler = http.Get
baseUrl := "https://no-no-this-will-not-work-it-just-will-not-get-the-job-done-mate.com"
u, _ := url.Parse(baseUrl)
cf.BaseURL = u
pizza := func(url string) (resp *http.Response, err error) {
return nil, nil
}
cf.RemoteURLHandler = pizza
lDoc, err := CreateDocumentFromConfig(info, cf)
assert.NotNil(t, lDoc)
assert.Error(t, err)
}
func TestCircularReference_IgnoreArray(t *testing.T) {
spec := `openapi: 3.1.0
components:
schemas:
@@ -102,16 +201,13 @@ components:
info, _ := datamodel.ExtractSpecInfo([]byte(spec))
circDoc, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
IgnoreArrayCircularReferences: true,
})
assert.NotNil(t, circDoc)
assert.Len(t, err, 0)
assert.Len(t, utils.UnwrapErrors(err), 0)
}
func TestCircularReference_IgnorePoly(t *testing.T) {
spec := `openapi: 3.1.0
components:
schemas:
@@ -131,12 +227,10 @@ components:
info, _ := datamodel.ExtractSpecInfo([]byte(spec))
circDoc, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
IgnorePolymorphicCircularReferences: true,
})
assert.NotNil(t, circDoc)
assert.Len(t, err, 0)
assert.Len(t, utils.UnwrapErrors(err), 0)
}
func BenchmarkCreateDocument_Stripe(b *testing.B) {
@@ -144,10 +238,7 @@ func BenchmarkCreateDocument_Stripe(b *testing.B) {
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
_, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
_, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
if err != nil {
panic("this should not error")
}
@@ -158,10 +249,7 @@ func BenchmarkCreateDocument_Petstore(b *testing.B) {
data, _ := os.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
_, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
_, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
if err != nil {
panic("this should not error")
}
@@ -169,15 +257,10 @@ func BenchmarkCreateDocument_Petstore(b *testing.B) {
}
func TestCreateDocumentStripe(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/stripe.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
d, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
BasePath: "/here",
})
assert.Len(t, err, 3)
d, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
assert.Len(t, utils.UnwrapErrors(err), 3)
assert.Equal(t, "3.0.0", d.Version.Value)
assert.Equal(t, "Stripe API", d.Info.Value.Title.Value)
@@ -234,16 +317,14 @@ func TestCreateDocument_WebHooks(t *testing.T) {
}
func TestCreateDocument_WebHooks_Error(t *testing.T) {
yml := `webhooks:
yml := `openapi: 3.0
webhooks:
$ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
assert.Len(t, err, 1)
var err error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
assert.Len(t, utils.UnwrapErrors(err), 1)
}
func TestCreateDocument_Servers(t *testing.T) {
@@ -304,7 +385,6 @@ func TestCreateDocument_Tags(t *testing.T) {
// this is why we will need a higher level API to this model, this looks cool and all, but dude.
assert.Equal(t, "now?", extension.Value.(map[string]interface{})["ok"].([]interface{})[0].(map[string]interface{})["what"])
}
}
/// tag2
@@ -314,7 +394,6 @@ func TestCreateDocument_Tags(t *testing.T) {
assert.Equal(t, "https://pb33f.io", doc.Tags.Value[1].Value.ExternalDocs.Value.URL.Value)
assert.NotEmpty(t, doc.Tags.Value[1].Value.ExternalDocs.Value.URL.Value)
assert.Len(t, doc.Tags.Value[1].Value.Extensions, 0)
}
func TestCreateDocument_Paths(t *testing.T) {
@@ -439,7 +518,6 @@ func TestCreateDocument_Paths(t *testing.T) {
assert.NotNil(t, servers)
assert.Len(t, servers, 1)
assert.Equal(t, "https://pb33f.io", servers[0].Value.URL.Value)
}
func TestCreateDocument_Components_Schemas(t *testing.T) {
@@ -465,7 +543,6 @@ func TestCreateDocument_Components_Schemas(t *testing.T) {
p := fries.Value.Schema().FindProperty("favoriteDrink")
assert.Equal(t, "a frosty cold beverage can be coke or sprite",
p.Value.Schema().Description.Value)
}
func TestCreateDocument_Components_SecuritySchemes(t *testing.T) {
@@ -494,7 +571,6 @@ func TestCreateDocument_Components_SecuritySchemes(t *testing.T) {
readScope = oAuth.Flows.Value.AuthorizationCode.Value.FindScope("write:burgers")
assert.NotNil(t, readScope)
assert.Equal(t, "modify burgers and stuff", readScope.Value)
}
func TestCreateDocument_Components_Responses(t *testing.T) {
@@ -507,7 +583,6 @@ func TestCreateDocument_Components_Responses(t *testing.T) {
assert.NotNil(t, dressingResponse.Value)
assert.Equal(t, "all the dressings for a burger.", dressingResponse.Value.Description.Value)
assert.Len(t, dressingResponse.Value.Content.Value, 1)
}
func TestCreateDocument_Components_Examples(t *testing.T) {
@@ -594,7 +669,6 @@ func TestCreateDocument_Component_Discriminator(t *testing.T) {
assert.Nil(t, dsc.FindMappingValue("don't exist"))
assert.NotNil(t, doc.GetExternalDocs())
assert.Nil(t, doc.FindSecurityRequirement("scooby doo"))
}
func TestCreateDocument_CheckAdditionalProperties_Schema(t *testing.T) {
@@ -602,11 +676,8 @@ func TestCreateDocument_CheckAdditionalProperties_Schema(t *testing.T) {
components := doc.Components.Value
d := components.FindSchema("Dressing")
assert.NotNil(t, d.Value.Schema().AdditionalProperties.Value)
if n, ok := d.Value.Schema().AdditionalProperties.Value.(*base.SchemaProxy); ok {
assert.Equal(t, "something in here.", n.Schema().Description.Value)
} else {
assert.Fail(t, "should be a schema")
}
assert.True(t, d.Value.Schema().AdditionalProperties.Value.IsA(), "should be a schema")
}
func TestCreateDocument_CheckAdditionalProperties_Bool(t *testing.T) {
@@ -614,7 +685,7 @@ func TestCreateDocument_CheckAdditionalProperties_Bool(t *testing.T) {
components := doc.Components.Value
d := components.FindSchema("Drink")
assert.NotNil(t, d.Value.Schema().AdditionalProperties.Value)
assert.True(t, d.Value.Schema().AdditionalProperties.Value.(bool))
assert.True(t, d.Value.Schema().AdditionalProperties.Value.B)
}
func TestCreateDocument_Components_Error(t *testing.T) {
@@ -627,12 +698,9 @@ components:
$ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
assert.Len(t, err, 0)
var err error
doc, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
assert.NoError(t, err)
ob := doc.Components.Value.FindSchema("bork").Value
ob.Schema()
@@ -646,12 +714,10 @@ webhooks:
$ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
assert.Len(t, err, 1)
var err error
doc, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
assert.Equal(t, "flat map build failed: reference cannot be found: reference at line 4, column 5 is empty, it cannot be resolved",
err.Error())
}
func TestCreateDocument_Components_Error_Extract(t *testing.T) {
@@ -662,13 +728,9 @@ components:
$ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
assert.Len(t, err, 1)
var err error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
assert.Equal(t, "reference at line 5, column 7 is empty, it cannot be resolved", err.Error())
}
func TestCreateDocument_Paths_Errors(t *testing.T) {
@@ -678,12 +740,10 @@ paths:
$ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
assert.Len(t, err, 1)
var err error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
assert.Equal(t,
"path item build failed: cannot find reference: at line 4, col 10", err.Error())
}
func TestCreateDocument_Tags_Errors(t *testing.T) {
@@ -692,12 +752,10 @@ tags:
- $ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
assert.Len(t, err, 1)
var err error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
assert.Equal(t,
"object extraction failed: reference at line 3, column 5 is empty, it cannot be resolved", err.Error())
}
func TestCreateDocument_Security_Error(t *testing.T) {
@@ -706,12 +764,11 @@ security:
$ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
assert.Len(t, err, 1)
var err error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
assert.Equal(t,
"array build failed: reference cannot be found: reference at line 3, column 3 is empty, it cannot be resolved",
err.Error())
}
func TestCreateDocument_ExternalDoc_Error(t *testing.T) {
@@ -720,12 +777,9 @@ externalDocs:
$ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
assert.Len(t, err, 1)
var err error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
assert.Equal(t, "object extraction failed: reference at line 3, column 3 is empty, it cannot be resolved", err.Error())
}
func TestCreateDocument_YamlAnchor(t *testing.T) {
@@ -736,16 +790,10 @@ func TestCreateDocument_YamlAnchor(t *testing.T) {
info, _ := datamodel.ExtractSpecInfo(anchorDocument)
// build low-level document model
document, errors := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
document, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
// if something went wrong, a slice of errors is returned
if len(errors) > 0 {
for i := range errors {
fmt.Printf("error: %s\n", errors[i].Error())
}
if err != nil {
fmt.Printf("error: %s\n", err.Error())
panic("cannot build document")
}
@@ -777,8 +825,19 @@ func TestCreateDocument_YamlAnchor(t *testing.T) {
assert.NotNil(t, jsonGet)
// Should this work? It doesn't
//postJsonType := examplePath.GetValue().Post.GetValue().RequestBody.GetValue().FindContent("application/json")
//assert.NotNil(t, postJsonType)
// update from quobix 10/14/2023: It does now!
postJsonType := examplePath.GetValue().Post.GetValue().RequestBody.GetValue().FindContent("application/json")
assert.NotNil(t, postJsonType)
}
func TestCreateDocument_NotOpenAPI_EnforcedDocCheck(t *testing.T) {
yml := `notadoc: no`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err error
_, err = CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
assert.Equal(t,
"no openapi version/tag found, cannot create document", err.Error())
}
func ExampleCreateDocument() {
@@ -791,16 +850,10 @@ func ExampleCreateDocument() {
info, _ := datamodel.ExtractSpecInfo(petstoreBytes)
// build low-level document model
document, errors := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false,
AllowRemoteReferences: false,
})
document, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
// if something went wrong, a slice of errors is returned
if len(errors) > 0 {
for i := range errors {
fmt.Printf("error: %s\n", errors[i].Error())
}
if err != nil {
fmt.Printf("error: %s\n", err.Error())
panic("cannot build document")
}

View File

@@ -83,6 +83,9 @@ type Document struct {
//
// This property is not a part of the OpenAPI schema, this is custom to libopenapi.
Index *index.SpecIndex
// Rolodex is a reference to the rolodex used when creating this document.
Rolodex *index.Rolodex
}
// FindSecurityRequirement will attempt to locate a security requirement string from a supplied name.

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"crypto/sha256"
"fmt"
"strings"
@@ -61,11 +62,11 @@ func (en *Encoding) Hash() [32]byte {
}
// Build will extract all Header objects from supplied node.
func (en *Encoding) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (en *Encoding) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
en.Reference = new(low.Reference)
headers, hL, hN, err := low.ExtractMap[*Header](HeadersLabel, root, idx)
headers, hL, hN, err := low.ExtractMap[*Header](ctx, HeadersLabel, root, idx)
if err != nil {
return err
}

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
@@ -31,7 +32,7 @@ explode: true`
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "hot/cakes", n.ContentType.Value)
assert.Equal(t, true, n.AllowReserved.Value)
@@ -59,7 +60,7 @@ headers:
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx)
err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -79,7 +80,7 @@ allowReserved: true`
var n Encoding
_ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx)
_ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
yml2 := `explode: true
contentType: application/waffle
@@ -96,7 +97,7 @@ style: post modern
var n2 Encoding
_ = low.BuildModel(idxNode2.Content[0], &n2)
_ = n2.Build(nil, idxNode2.Content[0], idx2)
_ = n2.Build(context.Background(), nil, idxNode2.Content[0], idx2)
// hash
assert.Equal(t, n.Hash(), n2.Hash())

View File

@@ -5,8 +5,9 @@ package v3
import (
"fmt"
"os"
"github.com/pb33f/libopenapi/datamodel"
"io/ioutil"
)
// How to create a low-level OpenAPI 3+ Document from an OpenAPI specification
@@ -14,19 +15,17 @@ func Example_createLowLevelOpenAPIDocument() {
// How to create a low-level OpenAPI 3 Document
// load petstore into bytes
petstoreBytes, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json")
petstoreBytes, _ := os.ReadFile("../../../test_specs/petstorev3.json")
// read in specification
info, _ := datamodel.ExtractSpecInfo(petstoreBytes)
// build low-level document model
document, errors := CreateDocument(info)
document, errs := CreateDocument(info)
// if something went wrong, a slice of errors is returned
if len(errors) > 0 {
for i := range errors {
fmt.Printf("error: %s\n", errors[i].Error())
}
if errs != nil {
fmt.Printf("error: %s\n", errs.Error())
panic("cannot build document")
}

View File

@@ -4,6 +4,7 @@
package v3
import (
"context"
"crypto/sha256"
"fmt"
"sort"
@@ -97,7 +98,7 @@ func (h *Header) Hash() [32]byte {
}
// Build will extract extensions, examples, schema and content/media types from node.
func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
func (h *Header) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
h.Reference = new(low.Reference)
@@ -110,7 +111,7 @@ func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
}
// handle examples if set.
exps, expsL, expsN, eErr := low.ExtractMap[*base.Example](base.ExamplesLabel, root, idx)
exps, expsL, expsN, eErr := low.ExtractMap[*base.Example](ctx, base.ExamplesLabel, root, idx)
if eErr != nil {
return eErr
}
@@ -123,7 +124,7 @@ func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
}
// handle schema
sch, sErr := base.ExtractSchema(root, idx)
sch, sErr := base.ExtractSchema(ctx, root, idx)
if sErr != nil {
return sErr
}
@@ -132,7 +133,7 @@ func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
}
// handle content, if set.
con, cL, cN, cErr := low.ExtractMap[*MediaType](ContentLabel, root, idx)
con, cL, cN, cErr := low.ExtractMap[*MediaType](ctx, ContentLabel, root, idx)
if cErr != nil {
return cErr
}

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