mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-06 04:20:11 +00:00
Added some new getters to the index
Also added map conversion utilities based on reported vacuum error https://github.com/daveshanley/vacuum/issues/417 also prevented the bundler from inlining root references. Signed-off-by: quobix <dave@quobix.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/pb33f/libopenapi/datamodel"
|
"github.com/pb33f/libopenapi/datamodel"
|
||||||
"github.com/pb33f/libopenapi/datamodel/high/v3"
|
"github.com/pb33f/libopenapi/datamodel/high/v3"
|
||||||
"github.com/pb33f/libopenapi/index"
|
"github.com/pb33f/libopenapi/index"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BundleBytes will take a byte slice of an OpenAPI specification and return a bundled version of it.
|
// BundleBytes will take a byte slice of an OpenAPI specification and return a bundled version of it.
|
||||||
@@ -29,7 +30,7 @@ func BundleBytes(bytes []byte, configuration *datamodel.DocumentConfiguration) (
|
|||||||
v3Doc, errs := doc.BuildV3Model()
|
v3Doc, errs := doc.BuildV3Model()
|
||||||
err = errors.Join(errs...)
|
err = errors.Join(errs...)
|
||||||
|
|
||||||
bundledBytes, e := BundleDocument(&v3Doc.Model)
|
bundledBytes, e := bundle(&v3Doc.Model, configuration.BundleInlineRefs)
|
||||||
return bundledBytes, errors.Join(err, e)
|
return bundledBytes, errors.Join(err, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,14 +44,32 @@ func BundleBytes(bytes []byte, configuration *datamodel.DocumentConfiguration) (
|
|||||||
//
|
//
|
||||||
// Circular references will not be resolved and will be skipped.
|
// Circular references will not be resolved and will be skipped.
|
||||||
func BundleDocument(model *v3.Document) ([]byte, error) {
|
func BundleDocument(model *v3.Document) ([]byte, error) {
|
||||||
|
return bundle(model, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bundle(model *v3.Document, inline bool) ([]byte, error) {
|
||||||
rolodex := model.Rolodex
|
rolodex := model.Rolodex
|
||||||
compress := func(idx *index.SpecIndex) {
|
compact := func(idx *index.SpecIndex, root bool) {
|
||||||
mappedReferences := idx.GetMappedReferences()
|
mappedReferences := idx.GetMappedReferences()
|
||||||
sequencedReferences := idx.GetRawReferencesSequenced()
|
sequencedReferences := idx.GetRawReferencesSequenced()
|
||||||
for _, sequenced := range sequencedReferences {
|
for _, sequenced := range sequencedReferences {
|
||||||
mappedReference := mappedReferences[sequenced.FullDefinition]
|
mappedReference := mappedReferences[sequenced.FullDefinition]
|
||||||
|
|
||||||
|
//if we're in the root document, don't bundle anything.
|
||||||
|
refExp := strings.Split(sequenced.FullDefinition, "#/")
|
||||||
|
if len(refExp) == 2 {
|
||||||
|
if refExp[0] == "" {
|
||||||
|
if root && !inline {
|
||||||
|
idx.GetLogger().Debug("[bundler] skipping local root reference",
|
||||||
|
"ref", sequenced.Definition)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if mappedReference != nil && !mappedReference.Circular {
|
if mappedReference != nil && !mappedReference.Circular {
|
||||||
sequenced.Node.Content = mappedReference.Node.Content
|
sequenced.Node.Content = mappedReference.Node.Content
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if mappedReference != nil && mappedReference.Circular {
|
if mappedReference != nil && mappedReference.Circular {
|
||||||
if idx.GetLogger() != nil {
|
if idx.GetLogger() != nil {
|
||||||
@@ -62,9 +81,9 @@ func BundleDocument(model *v3.Document) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
indexes := rolodex.GetIndexes()
|
indexes := rolodex.GetIndexes()
|
||||||
compress(rolodex.GetRootIndex())
|
|
||||||
for _, idx := range indexes {
|
for _, idx := range indexes {
|
||||||
compress(idx)
|
compact(idx, false)
|
||||||
}
|
}
|
||||||
|
compact(rolodex.GetRootIndex(), true)
|
||||||
return model.Render()
|
return model.Render()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package bundler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"github.com/pb33f/libopenapi"
|
"github.com/pb33f/libopenapi"
|
||||||
"github.com/pb33f/libopenapi/datamodel"
|
"github.com/pb33f/libopenapi/datamodel"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -81,19 +80,15 @@ func TestBundleDocument_Circular(t *testing.T) {
|
|||||||
|
|
||||||
bytes, e := BundleDocument(&v3Doc.Model)
|
bytes, e := BundleDocument(&v3Doc.Model)
|
||||||
assert.NoError(t, e)
|
assert.NoError(t, e)
|
||||||
assert.Len(t, bytes, 3069)
|
assert.Len(t, *doc.GetSpecInfo().SpecBytes, 1563)
|
||||||
|
assert.Len(t, bytes, 2016)
|
||||||
|
|
||||||
logEntries := strings.Split(byteBuf.String(), "\n")
|
logEntries := strings.Split(byteBuf.String(), "\n")
|
||||||
|
if len(logEntries) == 1 && logEntries[0] == "" {
|
||||||
assert.Len(t, logEntries, 5)
|
logEntries = []string{}
|
||||||
for _, entry := range logEntries {
|
|
||||||
items := make(map[string]any)
|
|
||||||
if entry != "" {
|
|
||||||
_ = json.Unmarshal([]byte(entry), &items)
|
|
||||||
assert.Equal(t, "[bundler] skipping circular reference", items["msg"])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
assert.NoError(t, e)
|
|
||||||
|
assert.Len(t, logEntries, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBundleBytes(t *testing.T) {
|
func TestBundleBytes(t *testing.T) {
|
||||||
@@ -112,18 +107,102 @@ func TestBundleBytes(t *testing.T) {
|
|||||||
|
|
||||||
bytes, e := BundleBytes(digi, config)
|
bytes, e := BundleBytes(digi, config)
|
||||||
assert.Error(t, e)
|
assert.Error(t, e)
|
||||||
assert.Len(t, bytes, 3069)
|
assert.Len(t, bytes, 2016)
|
||||||
|
|
||||||
logEntries := strings.Split(byteBuf.String(), "\n")
|
logEntries := strings.Split(byteBuf.String(), "\n")
|
||||||
|
if len(logEntries) == 1 && logEntries[0] == "" {
|
||||||
assert.Len(t, logEntries, 5)
|
logEntries = []string{}
|
||||||
for _, entry := range logEntries {
|
|
||||||
items := make(map[string]any)
|
|
||||||
if entry != "" {
|
|
||||||
_ = json.Unmarshal([]byte(entry), &items)
|
|
||||||
assert.Equal(t, "[bundler] skipping circular reference", items["msg"])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.Len(t, logEntries, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBundleBytes_CircularArray(t *testing.T) {
|
||||||
|
|
||||||
|
digi := []byte(`openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: FailureCases
|
||||||
|
version: 0.1.0
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:35123
|
||||||
|
description: The default server.
|
||||||
|
paths:
|
||||||
|
/test:
|
||||||
|
get:
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Obj:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
children:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Obj'
|
||||||
|
required:
|
||||||
|
- children`)
|
||||||
|
|
||||||
|
var logs []byte
|
||||||
|
byteBuf := bytes.NewBuffer(logs)
|
||||||
|
|
||||||
|
config := &datamodel.DocumentConfiguration{
|
||||||
|
ExtractRefsSequentially: true,
|
||||||
|
IgnoreArrayCircularReferences: true,
|
||||||
|
Logger: slog.New(slog.NewJSONHandler(byteBuf, &slog.HandlerOptions{
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, e := BundleBytes(digi, config)
|
||||||
|
assert.NoError(t, e)
|
||||||
|
assert.Len(t, bytes, 537)
|
||||||
|
|
||||||
|
logEntries := strings.Split(byteBuf.String(), "\n")
|
||||||
|
assert.Len(t, logEntries, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBundleBytes_CircularFile(t *testing.T) {
|
||||||
|
|
||||||
|
digi := []byte(`openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: FailureCases
|
||||||
|
version: 0.1.0
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:35123
|
||||||
|
description: The default server.
|
||||||
|
paths:
|
||||||
|
/test:
|
||||||
|
get:
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Obj:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
children:
|
||||||
|
$ref: '../test_specs/circular-tests.yaml#/components/schemas/One'`)
|
||||||
|
|
||||||
|
var logs []byte
|
||||||
|
byteBuf := bytes.NewBuffer(logs)
|
||||||
|
|
||||||
|
config := &datamodel.DocumentConfiguration{
|
||||||
|
BasePath: ".",
|
||||||
|
ExtractRefsSequentially: true,
|
||||||
|
Logger: slog.New(slog.NewJSONHandler(byteBuf, &slog.HandlerOptions{
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, e := BundleBytes(digi, config)
|
||||||
|
assert.Error(t, e)
|
||||||
|
assert.Len(t, bytes, 458)
|
||||||
|
|
||||||
|
logEntries := strings.Split(byteBuf.String(), "\n")
|
||||||
|
assert.Len(t, logEntries, 13)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBundleBytes_Bad(t *testing.T) {
|
func TestBundleBytes_Bad(t *testing.T) {
|
||||||
|
|||||||
@@ -107,6 +107,10 @@ type DocumentConfiguration struct {
|
|||||||
// This is a more thorough way of building the index, but it's slower. It's required building a document
|
// This is a more thorough way of building the index, but it's slower. It's required building a document
|
||||||
// to be bundled.
|
// to be bundled.
|
||||||
ExtractRefsSequentially bool
|
ExtractRefsSequentially bool
|
||||||
|
|
||||||
|
// BundleInlineRefs is used by the bundler module. If set to true, all references will be inlined, including
|
||||||
|
// local references (to the root document) as well as all external references. This is false by default.
|
||||||
|
BundleInlineRefs bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDocumentConfiguration() *DocumentConfiguration {
|
func NewDocumentConfiguration() *DocumentConfiguration {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package datamodel_test
|
package datamodel_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pb33f/libopenapi/datamodel"
|
"github.com/pb33f/libopenapi/datamodel"
|
||||||
"github.com/pb33f/libopenapi/orderedmap"
|
"github.com/pb33f/libopenapi/orderedmap"
|
||||||
@@ -486,41 +488,59 @@ func TestTranslatePipeline(t *testing.T) {
|
|||||||
// context cancel. Then the second item is aborted by this error
|
// context cancel. Then the second item is aborted by this error
|
||||||
// handler.
|
// handler.
|
||||||
t.Run("Error while waiting on worker", func(t *testing.T) {
|
t.Run("Error while waiting on worker", func(t *testing.T) {
|
||||||
const concurrency = 2
|
|
||||||
in := make(chan int)
|
|
||||||
out := make(chan string)
|
|
||||||
done := make(chan struct{})
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1) // input goroutine
|
|
||||||
|
|
||||||
// Send input.
|
// this test gets stuck sometimes, so it needs a hard limit.
|
||||||
go func() {
|
|
||||||
// Fill up worker pool with items.
|
ctx, c := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
for i := 0; i < concurrency; i++ {
|
defer c()
|
||||||
select {
|
doneChan := make(chan bool)
|
||||||
case in <- i:
|
|
||||||
case <-done:
|
go func(completedChan chan bool) {
|
||||||
|
|
||||||
|
const concurrency = 2
|
||||||
|
in := make(chan int)
|
||||||
|
out := make(chan string)
|
||||||
|
done := make(chan struct{})
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1) // input goroutine
|
||||||
|
|
||||||
|
// Send input.
|
||||||
|
go func() {
|
||||||
|
// Fill up worker pool with items.
|
||||||
|
for i := 0; i < concurrency; i++ {
|
||||||
|
select {
|
||||||
|
case in <- i:
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
wg.Done()
|
||||||
wg.Done()
|
}()
|
||||||
}()
|
|
||||||
|
|
||||||
// No need to capture output channel.
|
// No need to capture output channel.
|
||||||
|
|
||||||
var itemCount atomic.Int64
|
var itemCount atomic.Int64
|
||||||
err := datamodel.TranslatePipeline[int, string](in, out,
|
err := datamodel.TranslatePipeline[int, string](in, out,
|
||||||
func(value int) (string, error) {
|
func(value int) (string, error) {
|
||||||
counter := itemCount.Add(1)
|
counter := itemCount.Add(1)
|
||||||
// Cause error on first call.
|
// Cause error on first call.
|
||||||
if counter == 1 {
|
if counter == 1 {
|
||||||
return "", errors.New("Foobar")
|
return "", errors.New("Foobar")
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
close(done)
|
close(done)
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
doneChan <- true
|
||||||
|
}(doneChan)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Log("error waiting on worker test timed out")
|
||||||
|
case <-doneChan:
|
||||||
|
// test passed
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -401,7 +401,9 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
|
|||||||
|
|
||||||
if i%2 == 0 && n.Value != "$ref" && n.Value != "" {
|
if i%2 == 0 && n.Value != "$ref" && n.Value != "" {
|
||||||
|
|
||||||
nodePath := fmt.Sprintf("$.%s", strings.Join(seenPath, "."))
|
loc := append(seenPath, n.Value)
|
||||||
|
definitionPath := fmt.Sprintf("#/%s", strings.Join(loc, "/"))
|
||||||
|
_, jsonPath := utils.ConvertComponentIdIntoFriendlyPathSearch(definitionPath)
|
||||||
|
|
||||||
// capture descriptions and summaries
|
// capture descriptions and summaries
|
||||||
if n.Value == "description" {
|
if n.Value == "description" {
|
||||||
@@ -413,7 +415,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
|
|||||||
|
|
||||||
ref := &DescriptionReference{
|
ref := &DescriptionReference{
|
||||||
Content: node.Content[i+1].Value,
|
Content: node.Content[i+1].Value,
|
||||||
Path: nodePath,
|
Path: jsonPath,
|
||||||
Node: node.Content[i+1],
|
Node: node.Content[i+1],
|
||||||
IsSummary: false,
|
IsSummary: false,
|
||||||
}
|
}
|
||||||
@@ -434,7 +436,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
|
|||||||
}
|
}
|
||||||
ref := &DescriptionReference{
|
ref := &DescriptionReference{
|
||||||
Content: b.Value,
|
Content: b.Value,
|
||||||
Path: nodePath,
|
Path: jsonPath,
|
||||||
Node: b,
|
Node: b,
|
||||||
IsSummary: true,
|
IsSummary: true,
|
||||||
}
|
}
|
||||||
@@ -477,7 +479,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
|
|||||||
|
|
||||||
refs = append(refs, &Reference{
|
refs = append(refs, &Reference{
|
||||||
Definition: b.Content[k].Content[g].Content[r].Value,
|
Definition: b.Content[k].Content[g].Content[r].Value,
|
||||||
Path: fmt.Sprintf("%s.security[%d].%s[%d]", nodePath, k, secKey, r),
|
Path: fmt.Sprintf("%s.security[%d].%s[%d]", jsonPath, k, secKey, r),
|
||||||
Node: b.Content[k].Content[g].Content[r],
|
Node: b.Content[k].Content[g].Content[r],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -506,7 +508,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
|
|||||||
|
|
||||||
if enumKeyValueNode != nil {
|
if enumKeyValueNode != nil {
|
||||||
ref := &EnumReference{
|
ref := &EnumReference{
|
||||||
Path: nodePath,
|
Path: jsonPath,
|
||||||
Node: node.Content[i+1],
|
Node: node.Content[i+1],
|
||||||
Type: enumKeyValueNode,
|
Type: enumKeyValueNode,
|
||||||
SchemaNode: node,
|
SchemaNode: node,
|
||||||
@@ -536,7 +538,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
|
|||||||
|
|
||||||
if isObject {
|
if isObject {
|
||||||
index.allObjectsWithProperties = append(index.allObjectsWithProperties, &ObjectReference{
|
index.allObjectsWithProperties = append(index.allObjectsWithProperties, &ObjectReference{
|
||||||
Path: nodePath,
|
Path: jsonPath,
|
||||||
Node: node,
|
Node: node,
|
||||||
ParentNode: parent,
|
ParentNode: parent,
|
||||||
})
|
})
|
||||||
@@ -544,8 +546,8 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//seenPath = append(seenPath, strings.ReplaceAll(n.Value, "/", "~1"))
|
seenPath = append(seenPath, strings.ReplaceAll(n.Value, "/", "~1"))
|
||||||
seenPath = append(seenPath, n.Value)
|
//seenPath = append(seenPath, n.Value)
|
||||||
prev = n.Value
|
prev = n.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,6 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants used to determine if resolving is local, file based or remote file based.
|
|
||||||
const (
|
|
||||||
LocalResolve = iota
|
|
||||||
HttpResolve
|
|
||||||
FileResolve
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reference is a wrapper around *yaml.Node results to make things more manageable when performing
|
// Reference is a wrapper around *yaml.Node results to make things more manageable when performing
|
||||||
// algorithms on data models. the *yaml.Node def is just a bit too low level for tracking state.
|
// algorithms on data models. the *yaml.Node def is just a bit too low level for tracking state.
|
||||||
type Reference struct {
|
type Reference struct {
|
||||||
@@ -272,6 +265,8 @@ type SpecIndex struct {
|
|||||||
componentLock sync.RWMutex
|
componentLock sync.RWMutex
|
||||||
errorLock sync.RWMutex
|
errorLock sync.RWMutex
|
||||||
circularReferences []*CircularReferenceResult // only available when the resolver has been used.
|
circularReferences []*CircularReferenceResult // only available when the resolver has been used.
|
||||||
|
polyCircularReferences []*CircularReferenceResult // only available when the resolver has been used.
|
||||||
|
arrayCircularReferences []*CircularReferenceResult // only available when the resolver has been used.
|
||||||
allowCircularReferences bool // decide if you want to error out, or allow circular references, default is false.
|
allowCircularReferences bool // decide if you want to error out, or allow circular references, default is false.
|
||||||
config *SpecIndexConfig // configuration for the index
|
config *SpecIndexConfig // configuration for the index
|
||||||
componentIndexChan chan bool
|
componentIndexChan chan bool
|
||||||
@@ -300,6 +295,10 @@ func (index *SpecIndex) SetCache(sync *syncmap.Map) {
|
|||||||
index.cache = sync
|
index.cache = sync
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (index *SpecIndex) GetNodeMap() map[int]map[int]*yaml.Node {
|
||||||
|
return index.nodeMap
|
||||||
|
}
|
||||||
|
|
||||||
func (index *SpecIndex) GetCache() *syncmap.Map {
|
func (index *SpecIndex) GetCache() *syncmap.Map {
|
||||||
return index.cache
|
return index.cache
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,6 +229,8 @@ func (resolver *Resolver) CheckForCircularReferences() []*ResolvingError {
|
|||||||
}
|
}
|
||||||
// update our index with any circular refs we found.
|
// update our index with any circular refs we found.
|
||||||
resolver.specIndex.SetCircularReferences(resolver.circularReferences)
|
resolver.specIndex.SetCircularReferences(resolver.circularReferences)
|
||||||
|
resolver.specIndex.SetIgnoredArrayCircularReferences(resolver.ignoredArrayReferences)
|
||||||
|
resolver.specIndex.SetIgnoredPolymorphicCircularReferences(resolver.ignoredPolyReferences)
|
||||||
resolver.circChecked = true
|
resolver.circChecked = true
|
||||||
return resolver.resolvingErrors
|
return resolver.resolvingErrors
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,28 @@ func (index *SpecIndex) GetCircularReferences() []*CircularReferenceResult {
|
|||||||
return index.circularReferences
|
return index.circularReferences
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetIgnoredPolymorphicCircularReferences passes on any ignored poly circular refs captured using
|
||||||
|
// `IgnorePolymorphicCircularReferences`
|
||||||
|
func (index *SpecIndex) SetIgnoredPolymorphicCircularReferences(refs []*CircularReferenceResult) {
|
||||||
|
index.polyCircularReferences = refs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index *SpecIndex) SetIgnoredArrayCircularReferences(refs []*CircularReferenceResult) {
|
||||||
|
index.arrayCircularReferences = refs
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIgnoredPolymorphicCircularReferences will return any polymorphic circular references that were 'ignored' by
|
||||||
|
// using the `IgnorePolymorphicCircularReferences` configuration option.
|
||||||
|
func (index *SpecIndex) GetIgnoredPolymorphicCircularReferences() []*CircularReferenceResult {
|
||||||
|
return index.polyCircularReferences
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIgnoredArrayCircularReferences will return any array based circular references that were 'ignored' by
|
||||||
|
// using the `IgnoreArrayCircularReferences` configuration option.
|
||||||
|
func (index *SpecIndex) GetIgnoredArrayCircularReferences() []*CircularReferenceResult {
|
||||||
|
return index.arrayCircularReferences
|
||||||
|
}
|
||||||
|
|
||||||
// GetPathsNode returns document root node.
|
// GetPathsNode returns document root node.
|
||||||
func (index *SpecIndex) GetPathsNode() *yaml.Node {
|
func (index *SpecIndex) GetPathsNode() *yaml.Node {
|
||||||
return index.pathsNode
|
return index.pathsNode
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ func TestSpecIndex_GetCache(t *testing.T) {
|
|||||||
loaded, ok = extCache.Load("test2")
|
loaded, ok = extCache.Load("test2")
|
||||||
assert.Nil(t, loaded)
|
assert.Nil(t, loaded)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
|
||||||
|
assert.Len(t, index.GetIgnoredPolymorphicCircularReferences(), 0)
|
||||||
|
assert.Len(t, index.GetIgnoredArrayCircularReferences(), 0)
|
||||||
|
assert.Equal(t, len(index.GetRawReferencesSequenced()), 42)
|
||||||
|
assert.Equal(t, len(index.GetNodeMap()), 824)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpecIndex_ExtractRefsStripe(t *testing.T) {
|
func TestSpecIndex_ExtractRefsStripe(t *testing.T) {
|
||||||
@@ -1535,3 +1540,77 @@ paths:
|
|||||||
assert.Equal(t, "$.paths['/test2'].put", paths["/test2"]["put"].Path)
|
assert.Equal(t, "$.paths['/test2'].put", paths["/test2"]["put"].Path)
|
||||||
assert.Equal(t, 22, paths["/test2"]["put"].ParentNode.Line)
|
assert.Equal(t, 22, paths["/test2"]["put"].ParentNode.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSpecIndex_TestInlineSchemaPaths(t *testing.T) {
|
||||||
|
|
||||||
|
yml := `openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: Test
|
||||||
|
version: 0.0.1
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:35123
|
||||||
|
paths:
|
||||||
|
/test:
|
||||||
|
get:
|
||||||
|
operationId: TestSomething
|
||||||
|
parameters:
|
||||||
|
- name: test
|
||||||
|
in: query
|
||||||
|
description: test param for duplicate inline schema
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
- message
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'5XX':
|
||||||
|
description: test response for slightly different inline schema
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
- messages
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
messages:
|
||||||
|
type: string
|
||||||
|
default:
|
||||||
|
description: test response for duplicate inline schema
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
- message
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
message:
|
||||||
|
type: string`
|
||||||
|
|
||||||
|
var rootNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(yml), &rootNode)
|
||||||
|
|
||||||
|
idx := NewSpecIndexWithConfig(&rootNode, CreateOpenAPIIndexConfig())
|
||||||
|
|
||||||
|
schemas := idx.GetAllInlineSchemas()
|
||||||
|
assert.Equal(t, "$.paths['/test'].get.parameters.schema", schemas[0].Path)
|
||||||
|
assert.Equal(t, "$.paths['/test'].get.parameters.schema.properties.code", schemas[1].Path)
|
||||||
|
assert.Equal(t, "$.paths['/test'].get.parameters.schema.properties.message", schemas[2].Path)
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -139,6 +139,18 @@ func ConvertInterfaceIntoStringMap(context interface{}) map[string]string {
|
|||||||
if s, okB := n.(string); okB {
|
if s, okB := n.(string); okB {
|
||||||
converted[k] = s
|
converted[k] = s
|
||||||
}
|
}
|
||||||
|
if s, okB := n.(float64); okB {
|
||||||
|
converted[k] = fmt.Sprint(s)
|
||||||
|
}
|
||||||
|
if s, okB := n.(bool); okB {
|
||||||
|
converted[k] = fmt.Sprint(s)
|
||||||
|
}
|
||||||
|
if s, okB := n.(int); okB {
|
||||||
|
converted[k] = fmt.Sprint(s)
|
||||||
|
}
|
||||||
|
if s, okB := n.(int64); okB {
|
||||||
|
converted[k] = fmt.Sprint(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v, ok := context.(map[string]string); ok {
|
if v, ok := context.(map[string]string); ok {
|
||||||
|
|||||||
@@ -132,6 +132,42 @@ func TestConvertInterfaceIntoStringMap(t *testing.T) {
|
|||||||
assert.Equal(t, "baby girl", parsed["melody"])
|
assert.Equal(t, "baby girl", parsed["melody"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConvertInterfaceIntoStringMap_Float64(t *testing.T) {
|
||||||
|
var d interface{}
|
||||||
|
n := make(map[string]interface{})
|
||||||
|
n["melody"] = 5.9
|
||||||
|
d = n
|
||||||
|
parsed := ConvertInterfaceIntoStringMap(d)
|
||||||
|
assert.Equal(t, "5.9", parsed["melody"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertInterfaceIntoStringMap_Bool(t *testing.T) {
|
||||||
|
var d interface{}
|
||||||
|
n := make(map[string]interface{})
|
||||||
|
n["melody"] = true
|
||||||
|
d = n
|
||||||
|
parsed := ConvertInterfaceIntoStringMap(d)
|
||||||
|
assert.Equal(t, "true", parsed["melody"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertInterfaceIntoStringMap_int64(t *testing.T) {
|
||||||
|
var d interface{}
|
||||||
|
n := make(map[string]interface{})
|
||||||
|
n["melody"] = int64(12345)
|
||||||
|
d = n
|
||||||
|
parsed := ConvertInterfaceIntoStringMap(d)
|
||||||
|
assert.Equal(t, "12345", parsed["melody"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertInterfaceIntoStringMap_int(t *testing.T) {
|
||||||
|
var d interface{}
|
||||||
|
n := make(map[string]interface{})
|
||||||
|
n["melody"] = 12345
|
||||||
|
d = n
|
||||||
|
parsed := ConvertInterfaceIntoStringMap(d)
|
||||||
|
assert.Equal(t, "12345", parsed["melody"])
|
||||||
|
}
|
||||||
|
|
||||||
func TestConvertInterfaceIntoStringMap_NoType(t *testing.T) {
|
func TestConvertInterfaceIntoStringMap_NoType(t *testing.T) {
|
||||||
var d interface{}
|
var d interface{}
|
||||||
n := make(map[string]interface{})
|
n := make(map[string]interface{})
|
||||||
|
|||||||
Reference in New Issue
Block a user