mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-07 04:20:14 +00:00
Huge performance increase with building.
Using some designs unearthed from building the higher level model, I have brough that design down to the lower level to speed things up. It only took 8 years, but finally, I think I have mastered mult-threading. No more deadlocks, and no more need for waitgroups for everything.
This commit is contained in:
@@ -24,6 +24,10 @@ const (
|
|||||||
var seenSchemas map[string]*Schema
|
var seenSchemas map[string]*Schema
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
clearSchemas()
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearSchemas() {
|
||||||
seenSchemas = make(map[string]*Schema)
|
seenSchemas = make(map[string]*Schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +112,7 @@ func NewComponents(comp *low.Components) *Components {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range comp.Schemas.Value {
|
for k, v := range comp.Schemas.Value {
|
||||||
go buildSchema(k, v.Value, schemaChan)
|
go buildSchema(k, v, schemaChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
totalComponents := len(comp.Callbacks.Value) + len(comp.Links.Value) + len(comp.Responses.Value) +
|
totalComponents := len(comp.Callbacks.Value) + len(comp.Links.Value) + len(comp.Responses.Value) +
|
||||||
@@ -169,12 +173,13 @@ func buildComponent[N any, O any](comp int, key string, orig O, c chan component
|
|||||||
c <- componentResult[N]{comp: comp, res: f(orig), key: key}
|
c <- componentResult[N]{comp: comp, res: f(orig), key: key}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSchema(key lowmodel.KeyReference[string], orig *low.Schema, c chan componentResult[*Schema]) {
|
func buildSchema(key lowmodel.KeyReference[string], orig lowmodel.ValueReference[*low.Schema], c chan componentResult[*Schema]) {
|
||||||
var sch *Schema
|
var sch *Schema
|
||||||
if ss := getSeenSchema(key.GenerateMapKey()); ss != nil {
|
if ss := getSeenSchema(orig.GenerateMapKey()); ss != nil {
|
||||||
sch = ss
|
sch = ss
|
||||||
} else {
|
} else {
|
||||||
sch = NewSchema(orig)
|
sch = NewSchema(orig.Value)
|
||||||
|
addSeenSchema(orig.GenerateMapKey(), sch)
|
||||||
}
|
}
|
||||||
c <- componentResult[*Schema]{res: sch, key: key.Value}
|
c <- componentResult[*Schema]{res: sch, key: key.Value}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/pb33f/libopenapi/index"
|
"github.com/pb33f/libopenapi/index"
|
||||||
"github.com/pb33f/libopenapi/utils"
|
"github.com/pb33f/libopenapi/utils"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -16,6 +17,31 @@ const (
|
|||||||
SchemasLabel = "schemas"
|
SchemasLabel = "schemas"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var seenSchemas map[string]*Schema
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
clearSchemas()
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearSchemas() {
|
||||||
|
seenSchemas = make(map[string]*Schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
var seenSchemaLock sync.RWMutex
|
||||||
|
|
||||||
|
func addSeenSchema(key string, schema *Schema) {
|
||||||
|
defer seenSchemaLock.Unlock()
|
||||||
|
seenSchemaLock.Lock()
|
||||||
|
if seenSchemas[key] == nil {
|
||||||
|
seenSchemas[key] = schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func getSeenSchema(key string) *Schema {
|
||||||
|
defer seenSchemaLock.Unlock()
|
||||||
|
seenSchemaLock.Lock()
|
||||||
|
return seenSchemas[key]
|
||||||
|
}
|
||||||
|
|
||||||
type Components struct {
|
type Components struct {
|
||||||
Schemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]]
|
Schemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]]
|
||||||
Responses low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Response]]
|
Responses low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Response]]
|
||||||
@@ -94,74 +120,56 @@ func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
|||||||
n := 0
|
n := 0
|
||||||
total := 9
|
total := 9
|
||||||
|
|
||||||
var stateCheck = func() bool {
|
for n < total {
|
||||||
n++
|
|
||||||
if n == total {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
allDone:
|
|
||||||
for {
|
|
||||||
select {
|
select {
|
||||||
case buildError := <-errorChan:
|
case buildError := <-errorChan:
|
||||||
return buildError
|
return buildError
|
||||||
case <-skipChan:
|
case <-skipChan:
|
||||||
if stateCheck() {
|
n++
|
||||||
break allDone
|
|
||||||
}
|
|
||||||
case params := <-paramChan:
|
case params := <-paramChan:
|
||||||
co.Parameters = params
|
co.Parameters = params
|
||||||
if stateCheck() {
|
n++
|
||||||
break allDone
|
|
||||||
}
|
|
||||||
case schemas := <-schemaChan:
|
case schemas := <-schemaChan:
|
||||||
co.Schemas = schemas
|
co.Schemas = schemas
|
||||||
if stateCheck() {
|
cacheSchemas(co.Schemas.Value)
|
||||||
break allDone
|
n++
|
||||||
}
|
|
||||||
case responses := <-responsesChan:
|
case responses := <-responsesChan:
|
||||||
co.Responses = responses
|
co.Responses = responses
|
||||||
if stateCheck() {
|
n++
|
||||||
break allDone
|
|
||||||
}
|
|
||||||
case examples := <-examplesChan:
|
case examples := <-examplesChan:
|
||||||
co.Examples = examples
|
co.Examples = examples
|
||||||
if stateCheck() {
|
n++
|
||||||
break allDone
|
|
||||||
}
|
|
||||||
case reqBody := <-requestBodiesChan:
|
case reqBody := <-requestBodiesChan:
|
||||||
co.RequestBodies = reqBody
|
co.RequestBodies = reqBody
|
||||||
if stateCheck() {
|
n++
|
||||||
break allDone
|
|
||||||
}
|
|
||||||
case headers := <-headersChan:
|
case headers := <-headersChan:
|
||||||
co.Headers = headers
|
co.Headers = headers
|
||||||
if stateCheck() {
|
n++
|
||||||
break allDone
|
|
||||||
}
|
|
||||||
case sScheme := <-securitySchemesChan:
|
case sScheme := <-securitySchemesChan:
|
||||||
co.SecuritySchemes = sScheme
|
co.SecuritySchemes = sScheme
|
||||||
if stateCheck() {
|
n++
|
||||||
break allDone
|
|
||||||
}
|
|
||||||
case links := <-linkChan:
|
case links := <-linkChan:
|
||||||
co.Links = links
|
co.Links = links
|
||||||
if stateCheck() {
|
n++
|
||||||
break allDone
|
|
||||||
}
|
|
||||||
case callbacks := <-callbackChan:
|
case callbacks := <-callbackChan:
|
||||||
co.Callbacks = callbacks
|
co.Callbacks = callbacks
|
||||||
if stateCheck() {
|
n++
|
||||||
break allDone
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cacheSchemas(sch map[low.KeyReference[string]]low.ValueReference[*Schema]) {
|
||||||
|
for _, v := range sch {
|
||||||
|
addSeenSchema(v.GenerateMapKey(), v.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type componentBuildResult[T any] struct {
|
||||||
|
k low.KeyReference[string]
|
||||||
|
v low.ValueReference[T]
|
||||||
|
}
|
||||||
|
|
||||||
func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.Node,
|
func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.Node,
|
||||||
skip chan bool, errorChan chan<- error, resultChan chan<- low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]], idx *index.SpecIndex) {
|
skip chan bool, errorChan chan<- error, resultChan chan<- low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]], idx *index.SpecIndex) {
|
||||||
_, nodeLabel, nodeValue := utils.FindKeyNodeFull(label, root.Content)
|
_, nodeLabel, nodeValue := utils.FindKeyNodeFull(label, root.Content)
|
||||||
@@ -175,25 +183,47 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.
|
|||||||
errorChan <- fmt.Errorf("node is array, cannot be used in components: line %d, column %d", nodeValue.Line, nodeValue.Column)
|
errorChan <- fmt.Errorf("node is array, cannot be used in components: line %d, column %d", nodeValue.Line, nodeValue.Column)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for every component, build in a new thread!
|
||||||
|
bChan := make(chan componentBuildResult[T])
|
||||||
|
var buildComponent = func(label *yaml.Node, value *yaml.Node, c chan componentBuildResult[T], ec chan<- error) {
|
||||||
|
var n T = new(N)
|
||||||
|
_ = low.BuildModel(value, n)
|
||||||
|
err := n.Build(value, idx)
|
||||||
|
if err != nil {
|
||||||
|
ec <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c <- componentBuildResult[T]{
|
||||||
|
k: low.KeyReference[string]{
|
||||||
|
KeyNode: label,
|
||||||
|
Value: label.Value,
|
||||||
|
},
|
||||||
|
v: low.ValueReference[T]{
|
||||||
|
Value: n,
|
||||||
|
ValueNode: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i, v := range nodeValue.Content {
|
for i, v := range nodeValue.Content {
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
currentLabel = v
|
currentLabel = v
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var n T = new(N)
|
go buildComponent(currentLabel, v, bChan, errorChan)
|
||||||
_ = low.BuildModel(v, n)
|
}
|
||||||
err := n.Build(v, idx)
|
|
||||||
if err != nil {
|
totalComponents := len(nodeValue.Content) / 2
|
||||||
errorChan <- err
|
completedComponents := 0
|
||||||
}
|
for completedComponents < totalComponents {
|
||||||
componentValues[low.KeyReference[string]{
|
select {
|
||||||
KeyNode: currentLabel,
|
case r := <-bChan:
|
||||||
Value: currentLabel.Value,
|
componentValues[r.k] = r.v
|
||||||
}] = low.ValueReference[T]{
|
completedComponents++
|
||||||
Value: n,
|
|
||||||
ValueNode: v,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results := low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]]{
|
results := low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]]{
|
||||||
KeyNode: nodeLabel,
|
KeyNode: nodeLabel,
|
||||||
ValueNode: nodeValue,
|
ValueNode: nodeValue,
|
||||||
|
|||||||
@@ -4,18 +4,25 @@ import (
|
|||||||
"github.com/pb33f/libopenapi/datamodel"
|
"github.com/pb33f/libopenapi/datamodel"
|
||||||
"github.com/pb33f/libopenapi/datamodel/low"
|
"github.com/pb33f/libopenapi/datamodel/low"
|
||||||
"github.com/pb33f/libopenapi/index"
|
"github.com/pb33f/libopenapi/index"
|
||||||
|
"github.com/pb33f/libopenapi/resolver"
|
||||||
"github.com/pb33f/libopenapi/utils"
|
"github.com/pb33f/libopenapi/utils"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
|
func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
|
||||||
|
|
||||||
|
// clean state
|
||||||
|
clearSchemas()
|
||||||
doc := Document{Version: low.ValueReference[string]{Value: info.Version, ValueNode: info.RootNode}}
|
doc := Document{Version: low.ValueReference[string]{Value: info.Version, ValueNode: info.RootNode}}
|
||||||
|
|
||||||
// build an index
|
// build an index
|
||||||
idx := index.NewSpecIndex(info.RootNode)
|
idx := index.NewSpecIndex(info.RootNode)
|
||||||
doc.Index = idx
|
doc.Index = idx
|
||||||
|
|
||||||
|
// create resolver and check for circular references.
|
||||||
|
resolve := resolver.NewResolver(idx)
|
||||||
|
_ = resolve.CheckForCircularReferences()
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var errors []error
|
var errors []error
|
||||||
|
|
||||||
@@ -32,24 +39,23 @@ func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
|
|||||||
|
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
extractionFuncs := []func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{
|
extractionFuncs := []func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{
|
||||||
extractInfo,
|
extractInfo,
|
||||||
extractServers,
|
extractServers,
|
||||||
extractTags,
|
extractTags,
|
||||||
extractPaths,
|
|
||||||
extractComponents,
|
extractComponents,
|
||||||
extractSecurity,
|
extractSecurity,
|
||||||
extractExternalDocs,
|
extractExternalDocs,
|
||||||
|
extractPaths,
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Add(len(extractionFuncs))
|
wg.Add(len(extractionFuncs))
|
||||||
for _, f := range extractionFuncs {
|
for _, f := range extractionFuncs {
|
||||||
go runExtraction(info, &doc, idx, f, &errors, &wg)
|
go runExtraction(info, &doc, idx, f, &errors, &wg)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
return &doc, errors
|
return &doc, errors
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import (
|
|||||||
|
|
||||||
var doc *Document
|
var doc *Document
|
||||||
|
|
||||||
func init() {
|
func initTest() {
|
||||||
|
if doc != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml")
|
data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml")
|
||||||
info, _ := datamodel.ExtractSpecInfo(data)
|
info, _ := datamodel.ExtractSpecInfo(data)
|
||||||
var err []error
|
var err []error
|
||||||
@@ -27,8 +30,8 @@ func BenchmarkCreateDocument(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkCreateDocument_Stripe(b *testing.B) {
|
func BenchmarkCreateDocument_Circular(b *testing.B) {
|
||||||
data, _ := ioutil.ReadFile("../../../test_specs/stripe.yaml")
|
data, _ := ioutil.ReadFile("../../../test_specs/circular-tests.yaml")
|
||||||
info, _ := datamodel.ExtractSpecInfo(data)
|
info, _ := datamodel.ExtractSpecInfo(data)
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err := CreateDocument(info)
|
_, err := CreateDocument(info)
|
||||||
@@ -38,6 +41,32 @@ func BenchmarkCreateDocument_Stripe(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkCreateDocument_k8s(b *testing.B) {
|
||||||
|
|
||||||
|
data, _ := ioutil.ReadFile("../../../test_specs/k8s.json")
|
||||||
|
info, _ := datamodel.ExtractSpecInfo(data)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
|
||||||
|
_, err := CreateDocument(info)
|
||||||
|
if err != nil {
|
||||||
|
panic("this should not error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCreateDocument_Stripe(b *testing.B) {
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
data, _ := ioutil.ReadFile("../../../test_specs/stripe.yaml")
|
||||||
|
info, _ := datamodel.ExtractSpecInfo(data)
|
||||||
|
_, err := CreateDocument(info)
|
||||||
|
if err != nil {
|
||||||
|
panic("this should not error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkCreateDocument_Petstore(b *testing.B) {
|
func BenchmarkCreateDocument_Petstore(b *testing.B) {
|
||||||
data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json")
|
data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json")
|
||||||
info, _ := datamodel.ExtractSpecInfo(data)
|
info, _ := datamodel.ExtractSpecInfo(data)
|
||||||
@@ -50,12 +79,14 @@ func BenchmarkCreateDocument_Petstore(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument(t *testing.T) {
|
func TestCreateDocument(t *testing.T) {
|
||||||
|
initTest()
|
||||||
assert.Equal(t, "3.0.1", doc.Version.Value)
|
assert.Equal(t, "3.0.1", doc.Version.Value)
|
||||||
assert.Equal(t, "Burger Shop", doc.Info.Value.Title.Value)
|
assert.Equal(t, "Burger Shop", doc.Info.Value.Title.Value)
|
||||||
assert.NotEmpty(t, doc.Info.Value.Title.Value)
|
assert.NotEmpty(t, doc.Info.Value.Title.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Info(t *testing.T) {
|
func TestCreateDocument_Info(t *testing.T) {
|
||||||
|
initTest()
|
||||||
assert.Equal(t, "https://pb33f.io", doc.Info.Value.TermsOfService.Value)
|
assert.Equal(t, "https://pb33f.io", doc.Info.Value.TermsOfService.Value)
|
||||||
assert.Equal(t, "pb33f", doc.Info.Value.Contact.Value.Name.Value)
|
assert.Equal(t, "pb33f", doc.Info.Value.Contact.Value.Name.Value)
|
||||||
assert.Equal(t, "buckaroo@pb33f.io", doc.Info.Value.Contact.Value.Email.Value)
|
assert.Equal(t, "buckaroo@pb33f.io", doc.Info.Value.Contact.Value.Email.Value)
|
||||||
@@ -65,6 +96,7 @@ func TestCreateDocument_Info(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Servers(t *testing.T) {
|
func TestCreateDocument_Servers(t *testing.T) {
|
||||||
|
initTest()
|
||||||
assert.Len(t, doc.Servers.Value, 2)
|
assert.Len(t, doc.Servers.Value, 2)
|
||||||
server1 := doc.Servers.Value[0].Value
|
server1 := doc.Servers.Value[0].Value
|
||||||
server2 := doc.Servers.Value[1].Value
|
server2 := doc.Servers.Value[1].Value
|
||||||
@@ -89,6 +121,7 @@ func TestCreateDocument_Servers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Tags(t *testing.T) {
|
func TestCreateDocument_Tags(t *testing.T) {
|
||||||
|
initTest()
|
||||||
assert.Len(t, doc.Tags.Value, 2)
|
assert.Len(t, doc.Tags.Value, 2)
|
||||||
|
|
||||||
// tag1
|
// tag1
|
||||||
@@ -134,6 +167,7 @@ func TestCreateDocument_Tags(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Paths(t *testing.T) {
|
func TestCreateDocument_Paths(t *testing.T) {
|
||||||
|
initTest()
|
||||||
assert.Len(t, doc.Paths.Value.PathItems, 5)
|
assert.Len(t, doc.Paths.Value.PathItems, 5)
|
||||||
burgerId := doc.Paths.Value.FindPath("/burgers/{burgerId}")
|
burgerId := doc.Paths.Value.FindPath("/burgers/{burgerId}")
|
||||||
assert.NotNil(t, burgerId)
|
assert.NotNil(t, burgerId)
|
||||||
@@ -262,6 +296,7 @@ func TestCreateDocument_Paths(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Components_Schemas(t *testing.T) {
|
func TestCreateDocument_Components_Schemas(t *testing.T) {
|
||||||
|
initTest()
|
||||||
|
|
||||||
components := doc.Components.Value
|
components := doc.Components.Value
|
||||||
assert.NotNil(t, components)
|
assert.NotNil(t, components)
|
||||||
@@ -286,6 +321,7 @@ func TestCreateDocument_Components_Schemas(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Components_SecuritySchemes(t *testing.T) {
|
func TestCreateDocument_Components_SecuritySchemes(t *testing.T) {
|
||||||
|
initTest()
|
||||||
components := doc.Components.Value
|
components := doc.Components.Value
|
||||||
securitySchemes := components.SecuritySchemes.Value
|
securitySchemes := components.SecuritySchemes.Value
|
||||||
assert.Len(t, securitySchemes, 3)
|
assert.Len(t, securitySchemes, 3)
|
||||||
@@ -314,6 +350,7 @@ func TestCreateDocument_Components_SecuritySchemes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Components_Responses(t *testing.T) {
|
func TestCreateDocument_Components_Responses(t *testing.T) {
|
||||||
|
initTest()
|
||||||
components := doc.Components.Value
|
components := doc.Components.Value
|
||||||
responses := components.Responses.Value
|
responses := components.Responses.Value
|
||||||
assert.Len(t, responses, 1)
|
assert.Len(t, responses, 1)
|
||||||
@@ -326,6 +363,7 @@ func TestCreateDocument_Components_Responses(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Components_Examples(t *testing.T) {
|
func TestCreateDocument_Components_Examples(t *testing.T) {
|
||||||
|
initTest()
|
||||||
components := doc.Components.Value
|
components := doc.Components.Value
|
||||||
examples := components.Examples.Value
|
examples := components.Examples.Value
|
||||||
assert.Len(t, examples, 1)
|
assert.Len(t, examples, 1)
|
||||||
@@ -337,6 +375,7 @@ func TestCreateDocument_Components_Examples(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Components_RequestBodies(t *testing.T) {
|
func TestCreateDocument_Components_RequestBodies(t *testing.T) {
|
||||||
|
initTest()
|
||||||
components := doc.Components.Value
|
components := doc.Components.Value
|
||||||
requestBodies := components.RequestBodies.Value
|
requestBodies := components.RequestBodies.Value
|
||||||
assert.Len(t, requestBodies, 1)
|
assert.Len(t, requestBodies, 1)
|
||||||
@@ -348,6 +387,7 @@ func TestCreateDocument_Components_RequestBodies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Components_Headers(t *testing.T) {
|
func TestCreateDocument_Components_Headers(t *testing.T) {
|
||||||
|
initTest()
|
||||||
components := doc.Components.Value
|
components := doc.Components.Value
|
||||||
headers := components.Headers.Value
|
headers := components.Headers.Value
|
||||||
assert.Len(t, headers, 1)
|
assert.Len(t, headers, 1)
|
||||||
@@ -359,6 +399,7 @@ func TestCreateDocument_Components_Headers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Components_Links(t *testing.T) {
|
func TestCreateDocument_Components_Links(t *testing.T) {
|
||||||
|
initTest()
|
||||||
components := doc.Components.Value
|
components := doc.Components.Value
|
||||||
links := components.Links.Value
|
links := components.Links.Value
|
||||||
assert.Len(t, links, 2)
|
assert.Len(t, links, 2)
|
||||||
@@ -373,6 +414,7 @@ func TestCreateDocument_Components_Links(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Doc_Security(t *testing.T) {
|
func TestCreateDocument_Doc_Security(t *testing.T) {
|
||||||
|
initTest()
|
||||||
security := doc.Security.Value
|
security := doc.Security.Value
|
||||||
assert.NotNil(t, security)
|
assert.NotNil(t, security)
|
||||||
assert.Len(t, security.ValueRequirements, 1)
|
assert.Len(t, security.ValueRequirements, 1)
|
||||||
@@ -382,6 +424,7 @@ func TestCreateDocument_Doc_Security(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Callbacks(t *testing.T) {
|
func TestCreateDocument_Callbacks(t *testing.T) {
|
||||||
|
initTest()
|
||||||
callbacks := doc.Components.Value.Callbacks.Value
|
callbacks := doc.Components.Value.Callbacks.Value
|
||||||
assert.Len(t, callbacks, 1)
|
assert.Len(t, callbacks, 1)
|
||||||
|
|
||||||
@@ -396,6 +439,7 @@ func TestCreateDocument_Callbacks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_Component_Discriminator(t *testing.T) {
|
func TestCreateDocument_Component_Discriminator(t *testing.T) {
|
||||||
|
initTest()
|
||||||
|
|
||||||
components := doc.Components.Value
|
components := doc.Components.Value
|
||||||
dsc := components.FindSchema("Drink").Value.Discriminator.Value
|
dsc := components.FindSchema("Drink").Value.Discriminator.Value
|
||||||
@@ -406,6 +450,7 @@ func TestCreateDocument_Component_Discriminator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_CheckAdditionalProperties_Schema(t *testing.T) {
|
func TestCreateDocument_CheckAdditionalProperties_Schema(t *testing.T) {
|
||||||
|
initTest()
|
||||||
components := doc.Components.Value
|
components := doc.Components.Value
|
||||||
d := components.FindSchema("Dressing")
|
d := components.FindSchema("Dressing")
|
||||||
assert.NotNil(t, d.Value.AdditionalProperties.Value)
|
assert.NotNil(t, d.Value.AdditionalProperties.Value)
|
||||||
@@ -417,6 +462,7 @@ func TestCreateDocument_CheckAdditionalProperties_Schema(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateDocument_CheckAdditionalProperties_Bool(t *testing.T) {
|
func TestCreateDocument_CheckAdditionalProperties_Bool(t *testing.T) {
|
||||||
|
initTest()
|
||||||
components := doc.Components.Value
|
components := doc.Components.Value
|
||||||
d := components.FindSchema("Drink")
|
d := components.FindSchema("Drink")
|
||||||
assert.NotNil(t, d.Value.AdditionalProperties.Value)
|
assert.NotNil(t, d.Value.AdditionalProperties.Value)
|
||||||
|
|||||||
@@ -4,78 +4,78 @@
|
|||||||
package v3
|
package v3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pb33f/libopenapi/datamodel/low"
|
"github.com/pb33f/libopenapi/datamodel/low"
|
||||||
"github.com/pb33f/libopenapi/index"
|
"github.com/pb33f/libopenapi/index"
|
||||||
"github.com/pb33f/libopenapi/utils"
|
"github.com/pb33f/libopenapi/utils"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MediaType struct {
|
type MediaType struct {
|
||||||
Schema low.NodeReference[*Schema]
|
Schema low.NodeReference[*Schema]
|
||||||
Example low.NodeReference[any]
|
Example low.NodeReference[any]
|
||||||
Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]
|
Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]
|
||||||
Encoding low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]
|
Encoding low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]
|
||||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mt *MediaType) FindExtension(ext string) *low.ValueReference[any] {
|
func (mt *MediaType) FindExtension(ext string) *low.ValueReference[any] {
|
||||||
return low.FindItemInMap[any](ext, mt.Extensions)
|
return low.FindItemInMap[any](ext, mt.Extensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mt *MediaType) FindPropertyEncoding(eType string) *low.ValueReference[*Encoding] {
|
func (mt *MediaType) FindPropertyEncoding(eType string) *low.ValueReference[*Encoding] {
|
||||||
return low.FindItemInMap[*Encoding](eType, mt.Encoding.Value)
|
return low.FindItemInMap[*Encoding](eType, mt.Encoding.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mt *MediaType) FindExample(eType string) *low.ValueReference[*Example] {
|
func (mt *MediaType) FindExample(eType string) *low.ValueReference[*Example] {
|
||||||
return low.FindItemInMap[*Example](eType, mt.Examples.Value)
|
return low.FindItemInMap[*Example](eType, mt.Examples.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mt *MediaType) GetAllExamples() map[low.KeyReference[string]]low.ValueReference[*Example] {
|
func (mt *MediaType) GetAllExamples() map[low.KeyReference[string]]low.ValueReference[*Example] {
|
||||||
return mt.Examples.Value
|
return mt.Examples.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mt *MediaType) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
func (mt *MediaType) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||||
mt.Extensions = low.ExtractExtensions(root)
|
mt.Extensions = low.ExtractExtensions(root)
|
||||||
|
|
||||||
// handle example if set.
|
// handle example if set.
|
||||||
_, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content)
|
_, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content)
|
||||||
if expNode != nil {
|
if expNode != nil {
|
||||||
mt.Example = low.NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode}
|
mt.Example = low.NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle schema
|
//handle schema
|
||||||
sch, sErr := ExtractSchema(root, idx)
|
sch, sErr := ExtractSchema(root, idx)
|
||||||
if sErr != nil {
|
if sErr != nil {
|
||||||
return sErr
|
return sErr
|
||||||
}
|
}
|
||||||
if sch != nil {
|
if sch != nil {
|
||||||
mt.Schema = *sch
|
mt.Schema = *sch
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle examples if set.
|
// handle examples if set.
|
||||||
exps, expsL, expsN, eErr := low.ExtractMapFlat[*Example](ExamplesLabel, root, idx)
|
exps, expsL, expsN, eErr := low.ExtractMapFlat[*Example](ExamplesLabel, root, idx)
|
||||||
if eErr != nil {
|
if eErr != nil {
|
||||||
return eErr
|
return eErr
|
||||||
}
|
}
|
||||||
if exps != nil {
|
if exps != nil {
|
||||||
mt.Examples = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]{
|
mt.Examples = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]{
|
||||||
Value: exps,
|
Value: exps,
|
||||||
KeyNode: expsL,
|
KeyNode: expsL,
|
||||||
ValueNode: expsN,
|
ValueNode: expsN,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle encoding
|
// handle encoding
|
||||||
encs, encsL, encsN, encErr := low.ExtractMapFlat[*Encoding](EncodingLabel, root, idx)
|
encs, encsL, encsN, encErr := low.ExtractMapFlat[*Encoding](EncodingLabel, root, idx)
|
||||||
if encErr != nil {
|
if encErr != nil {
|
||||||
return encErr
|
return encErr
|
||||||
}
|
}
|
||||||
if encs != nil {
|
if encs != nil {
|
||||||
mt.Encoding = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]{
|
mt.Encoding = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]{
|
||||||
Value: encs,
|
Value: encs,
|
||||||
KeyNode: encsL,
|
KeyNode: encsL,
|
||||||
ValueNode: encsN,
|
ValueNode: encsN,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,84 +13,110 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PathsLabel = "paths"
|
PathsLabel = "paths"
|
||||||
GetLabel = "get"
|
GetLabel = "get"
|
||||||
PostLabel = "post"
|
PostLabel = "post"
|
||||||
PatchLabel = "patch"
|
PatchLabel = "patch"
|
||||||
PutLabel = "put"
|
PutLabel = "put"
|
||||||
DeleteLabel = "delete"
|
DeleteLabel = "delete"
|
||||||
OptionsLabel = "options"
|
OptionsLabel = "options"
|
||||||
HeadLabel = "head"
|
HeadLabel = "head"
|
||||||
TraceLabel = "trace"
|
TraceLabel = "trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Paths struct {
|
type Paths struct {
|
||||||
PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem]
|
PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem]
|
||||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] {
|
func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] {
|
||||||
for k, p := range p.PathItems {
|
for k, p := range p.PathItems {
|
||||||
if k.Value == path {
|
if k.Value == path {
|
||||||
return &p
|
return &p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Paths) FindExtension(ext string) *low.ValueReference[any] {
|
func (p *Paths) FindExtension(ext string) *low.ValueReference[any] {
|
||||||
return low.FindItemInMap[any](ext, p.Extensions)
|
return low.FindItemInMap[any](ext, p.Extensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||||
p.Extensions = low.ExtractExtensions(root)
|
p.Extensions = low.ExtractExtensions(root)
|
||||||
skip := false
|
skip := false
|
||||||
var currentNode *yaml.Node
|
var currentNode *yaml.Node
|
||||||
|
|
||||||
pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem])
|
pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem])
|
||||||
|
|
||||||
for i, pathNode := range root.Content {
|
// build each new path, in a new thread.
|
||||||
if strings.HasPrefix(strings.ToLower(pathNode.Value), "x-") {
|
type pathBuildResult struct {
|
||||||
skip = true
|
k low.KeyReference[string]
|
||||||
continue
|
v low.ValueReference[*PathItem]
|
||||||
}
|
}
|
||||||
if skip {
|
|
||||||
skip = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if i%2 == 0 {
|
|
||||||
currentNode = pathNode
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, _, _ := utils.IsNodeRefValue(pathNode); ok {
|
bChan := make(chan pathBuildResult)
|
||||||
r := low.LocateRefNode(pathNode, idx)
|
eChan := make(chan error)
|
||||||
if r != nil {
|
var buildPathItem = func(cNode, pNode *yaml.Node, b chan<- pathBuildResult, e chan<- error) {
|
||||||
pathNode = r
|
if ok, _, _ := utils.IsNodeRefValue(pNode); ok {
|
||||||
} else {
|
r := low.LocateRefNode(pNode, idx)
|
||||||
return fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d",
|
if r != nil {
|
||||||
pathNode.Content[1].Value, pathNode.Content[1].Line, pathNode.Content[1].Column)
|
pNode = r
|
||||||
}
|
} else {
|
||||||
}
|
e <- fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d",
|
||||||
|
pNode.Content[1].Value, pNode.Content[1].Line, pNode.Content[1].Column)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var path = PathItem{}
|
path := new(PathItem)
|
||||||
_ = low.BuildModel(pathNode, &path)
|
_ = low.BuildModel(pNode, path)
|
||||||
err := path.Build(pathNode, idx)
|
err := path.Build(pNode, idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
e <- err
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
b <- pathBuildResult{
|
||||||
|
k: low.KeyReference[string]{
|
||||||
|
Value: cNode.Value,
|
||||||
|
KeyNode: cNode,
|
||||||
|
},
|
||||||
|
v: low.ValueReference[*PathItem]{
|
||||||
|
Value: path,
|
||||||
|
ValueNode: pNode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// add bulk here
|
pathCount := 0
|
||||||
pathsMap[low.KeyReference[string]{
|
for i, pathNode := range root.Content {
|
||||||
Value: currentNode.Value,
|
if strings.HasPrefix(strings.ToLower(pathNode.Value), "x-") {
|
||||||
KeyNode: currentNode,
|
skip = true
|
||||||
}] = low.ValueReference[*PathItem]{
|
continue
|
||||||
Value: &path,
|
}
|
||||||
ValueNode: pathNode,
|
if skip {
|
||||||
}
|
skip = false
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
|
if i%2 == 0 {
|
||||||
|
currentNode = pathNode
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pathCount++
|
||||||
|
go buildPathItem(currentNode, pathNode, bChan, eChan)
|
||||||
|
}
|
||||||
|
|
||||||
p.PathItems = pathsMap
|
completedItems := 0
|
||||||
return nil
|
for completedItems < pathCount {
|
||||||
|
select {
|
||||||
|
case err := <-eChan:
|
||||||
|
return err
|
||||||
|
case res := <-bChan:
|
||||||
|
completedItems++
|
||||||
|
pathsMap[res.k] = res.v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.PathItems = pathsMap
|
||||||
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,17 +174,13 @@ func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
n := 0
|
n := 0
|
||||||
allDone:
|
total := len(ops)
|
||||||
for {
|
for n < total {
|
||||||
select {
|
select {
|
||||||
case buildError := <-opErrorChan:
|
case buildError := <-opErrorChan:
|
||||||
return buildError
|
return buildError
|
||||||
case <-opBuildChan:
|
case <-opBuildChan:
|
||||||
n++
|
n++
|
||||||
if n == len(ops) {
|
|
||||||
break allDone
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (r *Response) FindLink(hType string) *low.ValueReference[*Link] {
|
|||||||
func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||||
r.Extensions = low.ExtractExtensions(root)
|
r.Extensions = low.ExtractExtensions(root)
|
||||||
|
|
||||||
// extract headers
|
//extract headers
|
||||||
headers, lN, kN, err := low.ExtractMapFlat[*Header](HeadersLabel, root, idx)
|
headers, lN, kN, err := low.ExtractMapFlat[*Header](HeadersLabel, root, idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -69,11 +69,15 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) error {
|
func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) error {
|
||||||
level++
|
|
||||||
|
if low.IsCircular(root, idx) {
|
||||||
|
return nil // circular references cannot be built.
|
||||||
|
}
|
||||||
|
|
||||||
if level > 30 {
|
if level > 30 {
|
||||||
return fmt.Errorf("schema is too nested to continue: %d levels deep, is too deep", level) // we're done, son! too fricken deep.
|
return fmt.Errorf("schema is too nested to continue: %d levels deep, is too deep", level) // we're done, son! too fricken deep.
|
||||||
}
|
}
|
||||||
|
level++
|
||||||
if h, _, _ := utils.IsNodeRefValue(root); h {
|
if h, _, _ := utils.IsNodeRefValue(root); h {
|
||||||
ref := low.LocateRefNode(root, idx)
|
ref := low.LocateRefNode(root, idx)
|
||||||
if ref != nil {
|
if ref != nil {
|
||||||
@@ -135,11 +139,51 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er
|
|||||||
s.XML = low.NodeReference[*XML]{Value: &xml, KeyNode: xmlLabel, ValueNode: xmlNode}
|
s.XML = low.NodeReference[*XML]{Value: &xml, KeyNode: xmlLabel, ValueNode: xmlNode}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for property, build in a new thread!
|
||||||
|
bChan := make(chan schemaBuildResult)
|
||||||
|
eChan := make(chan error)
|
||||||
|
|
||||||
|
var buildProperty = func(label *yaml.Node, value *yaml.Node, c chan schemaBuildResult, ec chan<- error) {
|
||||||
|
// have we seen this before?
|
||||||
|
seen := getSeenSchema(fmt.Sprintf("%d:%d", value.Line, value.Column))
|
||||||
|
if seen != nil {
|
||||||
|
c <- schemaBuildResult{
|
||||||
|
k: low.KeyReference[string]{
|
||||||
|
KeyNode: label,
|
||||||
|
Value: label.Value,
|
||||||
|
},
|
||||||
|
v: low.ValueReference[*Schema]{
|
||||||
|
Value: seen,
|
||||||
|
ValueNode: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p := new(Schema)
|
||||||
|
_ = low.BuildModel(value, p)
|
||||||
|
err := p.BuildLevel(value, idx, level)
|
||||||
|
if err != nil {
|
||||||
|
ec <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c <- schemaBuildResult{
|
||||||
|
k: low.KeyReference[string]{
|
||||||
|
KeyNode: label,
|
||||||
|
Value: label.Value,
|
||||||
|
},
|
||||||
|
v: low.ValueReference[*Schema]{
|
||||||
|
Value: p,
|
||||||
|
ValueNode: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handle properties
|
// handle properties
|
||||||
_, propLabel, propsNode := utils.FindKeyNodeFull(PropertiesLabel, root.Content)
|
_, propLabel, propsNode := utils.FindKeyNodeFull(PropertiesLabel, root.Content)
|
||||||
if propsNode != nil {
|
if propsNode != nil {
|
||||||
propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*Schema])
|
propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*Schema])
|
||||||
var currentProp *yaml.Node
|
var currentProp *yaml.Node
|
||||||
|
totalProps := 0
|
||||||
for i, prop := range propsNode.Content {
|
for i, prop := range propsNode.Content {
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
currentProp = prop
|
currentProp = prop
|
||||||
@@ -156,19 +200,17 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er
|
|||||||
prop.Content[1].Value, prop.Content[1].Column, prop.Content[1].Line)
|
prop.Content[1].Value, prop.Content[1].Column, prop.Content[1].Line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
totalProps++
|
||||||
var property Schema
|
go buildProperty(currentProp, prop, bChan, eChan)
|
||||||
_ = low.BuildModel(prop, &property)
|
}
|
||||||
err := property.BuildLevel(prop, idx, level)
|
completedProps := 0
|
||||||
if err != nil {
|
for completedProps < totalProps {
|
||||||
|
select {
|
||||||
|
case err := <-eChan:
|
||||||
return err
|
return err
|
||||||
}
|
case res := <-bChan:
|
||||||
propertyMap[low.KeyReference[string]{
|
completedProps++
|
||||||
Value: currentProp.Value,
|
propertyMap[res.k] = res.v
|
||||||
KeyNode: currentProp,
|
|
||||||
}] = low.ValueReference[*Schema]{
|
|
||||||
Value: &property,
|
|
||||||
ValueNode: prop,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.Properties = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]]{
|
s.Properties = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]]{
|
||||||
@@ -233,6 +275,11 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type schemaBuildResult struct {
|
||||||
|
k low.KeyReference[string]
|
||||||
|
v low.ValueReference[*Schema]
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Schema) extractExtensions(root *yaml.Node) {
|
func (s *Schema) extractExtensions(root *yaml.Node) {
|
||||||
s.Extensions = low.ExtractExtensions(root)
|
s.Extensions = low.ExtractExtensions(root)
|
||||||
}
|
}
|
||||||
@@ -247,9 +294,9 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if valueNode != nil {
|
if valueNode != nil {
|
||||||
var build = func(kn *yaml.Node, vn *yaml.Node) *low.NodeReference[*Schema] {
|
|
||||||
var schema Schema
|
|
||||||
|
|
||||||
|
build := func(kn *yaml.Node, vn *yaml.Node) *low.NodeReference[*Schema] {
|
||||||
|
schema := new(Schema)
|
||||||
if h, _, _ := utils.IsNodeRefValue(vn); h {
|
if h, _, _ := utils.IsNodeRefValue(vn); h {
|
||||||
ref := low.LocateRefNode(vn, idx)
|
ref := low.LocateRefNode(vn, idx)
|
||||||
if ref != nil {
|
if ref != nil {
|
||||||
@@ -261,14 +308,28 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = low.BuildModel(vn, &schema)
|
seen := getSeenSchema(fmt.Sprintf("%d:%d", vn.Line, vn.Column))
|
||||||
|
if seen != nil {
|
||||||
|
return &low.NodeReference[*Schema]{
|
||||||
|
Value: seen,
|
||||||
|
KeyNode: kn,
|
||||||
|
ValueNode: vn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = low.BuildModel(vn, schema)
|
||||||
|
|
||||||
|
// add schema before we build, so it doesn't get stuck in an infinite loop.
|
||||||
|
addSeenSchema(fmt.Sprintf("%d:%d", vn.Line, vn.Column), schema)
|
||||||
|
|
||||||
err := schema.BuildLevel(vn, idx, level)
|
err := schema.BuildLevel(vn, idx, level)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
*errors = append(*errors, err)
|
*errors = append(*errors, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &low.NodeReference[*Schema]{
|
return &low.NodeReference[*Schema]{
|
||||||
Value: &schema,
|
Value: schema,
|
||||||
KeyNode: kn,
|
KeyNode: kn,
|
||||||
ValueNode: vn,
|
ValueNode: vn,
|
||||||
}
|
}
|
||||||
@@ -292,8 +353,8 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if utils.IsNodeArray(valueNode) {
|
if utils.IsNodeArray(valueNode) {
|
||||||
|
//fmt.Println("polymorphic looping sucks dude.")
|
||||||
for _, vn := range valueNode.Content {
|
for _, vn := range valueNode.Content {
|
||||||
|
|
||||||
if h, _, _ := utils.IsNodeRefValue(vn); h {
|
if h, _, _ := utils.IsNodeRefValue(vn); h {
|
||||||
ref := low.LocateRefNode(vn, idx)
|
ref := low.LocateRefNode(vn, idx)
|
||||||
if ref != nil {
|
if ref != nil {
|
||||||
@@ -312,7 +373,6 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
//wg.Done()
|
|
||||||
return labelNode, valueNode
|
return labelNode, valueNode
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,12 +405,20 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S
|
|||||||
}
|
}
|
||||||
|
|
||||||
if schNode != nil {
|
if schNode != nil {
|
||||||
|
// check if schema has already been built.
|
||||||
|
seen := getSeenSchema(fmt.Sprintf("%d:%d", schNode.Line, schNode.Column))
|
||||||
|
if seen != nil {
|
||||||
|
return &low.NodeReference[*Schema]{Value: seen, KeyNode: schLabel, ValueNode: schNode}, nil
|
||||||
|
}
|
||||||
|
|
||||||
var schema Schema
|
var schema Schema
|
||||||
_ = low.BuildModel(schNode, &schema)
|
_ = low.BuildModel(schNode, &schema)
|
||||||
err := schema.Build(schNode, idx)
|
err := schema.Build(schNode, idx)
|
||||||
|
addSeenSchema(fmt.Sprintf("%d:%d", schNode.Line, schNode.Column), &schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &low.NodeReference[*Schema]{Value: &schema, KeyNode: schLabel, ValueNode: schNode}, nil
|
return &low.NodeReference[*Schema]{Value: &schema, KeyNode: schLabel, ValueNode: schNode}, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Test_Schema(t *testing.T) {
|
func Test_Schema(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
testSpec := `type: object
|
testSpec := `type: object
|
||||||
description: something object
|
description: something object
|
||||||
discriminator:
|
discriminator:
|
||||||
@@ -221,7 +221,7 @@ additionalProperties: true `
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSchema_BuildLevel_TooDeep(t *testing.T) {
|
func TestSchema_BuildLevel_TooDeep(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
// if you design data models like this, you're doing it fucking wrong. Seriously. why, what is so complex about a model
|
// if you design data models like this, you're doing it fucking wrong. Seriously. why, what is so complex about a model
|
||||||
// that it needs to be 30+ levels deep? I have seen this shit in the wild, it's unreadable, un-parsable garbage.
|
// that it needs to be 30+ levels deep? I have seen this shit in the wild, it's unreadable, un-parsable garbage.
|
||||||
yml := `type: object
|
yml := `type: object
|
||||||
@@ -339,7 +339,7 @@ properties:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSchema_Build_ErrorAdditionalProps(t *testing.T) {
|
func TestSchema_Build_ErrorAdditionalProps(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
yml := `additionalProperties:
|
yml := `additionalProperties:
|
||||||
$ref: #borko`
|
$ref: #borko`
|
||||||
|
|
||||||
@@ -357,19 +357,19 @@ func TestSchema_Build_ErrorAdditionalProps(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSchema_Build_PropsLookup(t *testing.T) {
|
func TestSchema_Build_PropsLookup(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
description: this is something
|
description: this is something
|
||||||
type: string`
|
type: string`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `type: object
|
yml = `type: object
|
||||||
properties:
|
properties:
|
||||||
aValue:
|
aValue:
|
||||||
$ref: '#/components/schemas/Something'`
|
$ref: '#/components/schemas/Something'`
|
||||||
@@ -385,19 +385,19 @@ properties:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSchema_Build_PropsLookup_Fail(t *testing.T) {
|
func TestSchema_Build_PropsLookup_Fail(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
description: this is something
|
description: this is something
|
||||||
type: string`
|
type: string`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `type: object
|
yml = `type: object
|
||||||
properties:
|
properties:
|
||||||
aValue:
|
aValue:
|
||||||
$ref: '#/bork'`
|
$ref: '#/bork'`
|
||||||
@@ -412,8 +412,8 @@ properties:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_Schema_Polymorphism_Array_Ref(t *testing.T) {
|
func Test_Schema_Polymorphism_Array_Ref(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
type: object
|
type: object
|
||||||
@@ -425,11 +425,11 @@ func Test_Schema_Polymorphism_Array_Ref(t *testing.T) {
|
|||||||
example: anything`
|
example: anything`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `type: object
|
yml = `type: object
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/Something'
|
- $ref: '#/components/schemas/Something'
|
||||||
oneOf:
|
oneOf:
|
||||||
@@ -460,8 +460,8 @@ items:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) {
|
func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
type: object
|
type: object
|
||||||
@@ -473,11 +473,11 @@ func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) {
|
|||||||
example: anything`
|
example: anything`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `type: object
|
yml = `type: object
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/Missing'
|
- $ref: '#/components/schemas/Missing'
|
||||||
oneOf:
|
oneOf:
|
||||||
@@ -502,8 +502,8 @@ items:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_Schema_Polymorphism_Map_Ref(t *testing.T) {
|
func Test_Schema_Polymorphism_Map_Ref(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
type: object
|
type: object
|
||||||
@@ -515,11 +515,11 @@ func Test_Schema_Polymorphism_Map_Ref(t *testing.T) {
|
|||||||
example: anything`
|
example: anything`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `type: object
|
yml = `type: object
|
||||||
allOf:
|
allOf:
|
||||||
$ref: '#/components/schemas/Something'
|
$ref: '#/components/schemas/Something'
|
||||||
oneOf:
|
oneOf:
|
||||||
@@ -550,8 +550,8 @@ items:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) {
|
func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
type: object
|
type: object
|
||||||
@@ -563,11 +563,11 @@ func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) {
|
|||||||
example: anything`
|
example: anything`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `type: object
|
yml = `type: object
|
||||||
allOf:
|
allOf:
|
||||||
$ref: '#/components/schemas/Missing'
|
$ref: '#/components/schemas/Missing'
|
||||||
oneOf:
|
oneOf:
|
||||||
@@ -592,18 +592,19 @@ items:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_Schema_Polymorphism_BorkParent(t *testing.T) {
|
func Test_Schema_Polymorphism_BorkParent(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
|
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
$ref: #borko`
|
$ref: #borko`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `type: object
|
yml = `type: object
|
||||||
allOf:
|
allOf:
|
||||||
$ref: '#/components/schemas/Something'`
|
$ref: '#/components/schemas/Something'`
|
||||||
|
|
||||||
@@ -620,18 +621,19 @@ allOf:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_Schema_Polymorphism_BorkChild(t *testing.T) {
|
func Test_Schema_Polymorphism_BorkChild(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
|
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
$ref: #borko`
|
$ref: #borko`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `type: object
|
yml = `type: object
|
||||||
allOf:
|
allOf:
|
||||||
$ref: #borko`
|
$ref: #borko`
|
||||||
|
|
||||||
@@ -648,8 +650,9 @@ allOf:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_Schema_Polymorphism_RefMadness(t *testing.T) {
|
func Test_Schema_Polymorphism_RefMadness(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
|
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
$ref: '#/components/schemas/Else'
|
$ref: '#/components/schemas/Else'
|
||||||
@@ -657,11 +660,11 @@ func Test_Schema_Polymorphism_RefMadness(t *testing.T) {
|
|||||||
description: madness`
|
description: madness`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `type: object
|
yml = `type: object
|
||||||
allOf:
|
allOf:
|
||||||
$ref: '#/components/schemas/Something'`
|
$ref: '#/components/schemas/Something'`
|
||||||
|
|
||||||
@@ -681,8 +684,9 @@ allOf:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_Schema_Polymorphism_RefMadnessBork(t *testing.T) {
|
func Test_Schema_Polymorphism_RefMadnessBork(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
|
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
$ref: '#/components/schemas/Else'
|
$ref: '#/components/schemas/Else'
|
||||||
@@ -690,11 +694,11 @@ func Test_Schema_Polymorphism_RefMadnessBork(t *testing.T) {
|
|||||||
$ref: #borko`
|
$ref: #borko`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `type: object
|
yml = `type: object
|
||||||
allOf:
|
allOf:
|
||||||
$ref: '#/components/schemas/Something'`
|
$ref: '#/components/schemas/Something'`
|
||||||
|
|
||||||
@@ -711,10 +715,11 @@ allOf:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) {
|
func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
|
|
||||||
// this does not work, but it won't error out.
|
// this does not work, but it won't error out.
|
||||||
|
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
$ref: '#/components/schemas/Else'
|
$ref: '#/components/schemas/Else'
|
||||||
@@ -722,11 +727,11 @@ func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) {
|
|||||||
description: hey!`
|
description: hey!`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `$ref: '#/components/schemas/Something'`
|
yml = `$ref: '#/components/schemas/Something'`
|
||||||
|
|
||||||
var sch Schema
|
var sch Schema
|
||||||
var idxNode yaml.Node
|
var idxNode yaml.Node
|
||||||
@@ -741,19 +746,20 @@ func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractSchema(t *testing.T) {
|
func TestExtractSchema(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
|
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
description: this is something
|
description: this is something
|
||||||
type: string`
|
type: string`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `schema:
|
yml = `schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
aValue:
|
aValue:
|
||||||
@@ -765,23 +771,25 @@ func TestExtractSchema(t *testing.T) {
|
|||||||
res, err := ExtractSchema(idxNode.Content[0], idx)
|
res, err := ExtractSchema(idxNode.Content[0], idx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, res.Value)
|
assert.NotNil(t, res.Value)
|
||||||
assert.Equal(t, "this is something", res.Value.FindProperty("aValue").Value.Description.Value)
|
aValue := res.Value.FindProperty("aValue")
|
||||||
|
assert.Equal(t, "this is something", aValue.Value.Description.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractSchema_Ref(t *testing.T) {
|
func TestExtractSchema_Ref(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
|
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
description: this is something
|
description: this is something
|
||||||
type: string`
|
type: string`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `schema:
|
yml = `schema:
|
||||||
$ref: '#/components/schemas/Something'`
|
$ref: '#/components/schemas/Something'`
|
||||||
|
|
||||||
var idxNode yaml.Node
|
var idxNode yaml.Node
|
||||||
@@ -794,19 +802,20 @@ func TestExtractSchema_Ref(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractSchema_Ref_Fail(t *testing.T) {
|
func TestExtractSchema_Ref_Fail(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
|
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
description: this is something
|
description: this is something
|
||||||
type: string`
|
type: string`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `schema:
|
yml = `schema:
|
||||||
$ref: '#/components/schemas/Missing'`
|
$ref: '#/components/schemas/Missing'`
|
||||||
|
|
||||||
var idxNode yaml.Node
|
var idxNode yaml.Node
|
||||||
@@ -817,19 +826,20 @@ func TestExtractSchema_Ref_Fail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractSchema_RefRoot(t *testing.T) {
|
func TestExtractSchema_RefRoot(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
|
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
description: this is something
|
description: this is something
|
||||||
type: string`
|
type: string`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `$ref: '#/components/schemas/Something'`
|
yml = `$ref: '#/components/schemas/Something'`
|
||||||
|
|
||||||
var idxNode yaml.Node
|
var idxNode yaml.Node
|
||||||
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
||||||
@@ -841,19 +851,20 @@ func TestExtractSchema_RefRoot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractSchema_RefRoot_Fail(t *testing.T) {
|
func TestExtractSchema_RefRoot_Fail(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
|
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
description: this is something
|
description: this is something
|
||||||
type: string`
|
type: string`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `$ref: '#/components/schemas/Missing'`
|
yml = `$ref: '#/components/schemas/Missing'`
|
||||||
|
|
||||||
var idxNode yaml.Node
|
var idxNode yaml.Node
|
||||||
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
||||||
@@ -864,18 +875,19 @@ func TestExtractSchema_RefRoot_Fail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) {
|
func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) {
|
||||||
|
clearSchemas()
|
||||||
|
|
||||||
doc := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
$ref: #bork`
|
$ref: #bork`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `$ref: '#/components/schemas/Something'`
|
yml = `$ref: '#/components/schemas/Something'`
|
||||||
|
|
||||||
var idxNode yaml.Node
|
var idxNode yaml.Node
|
||||||
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
||||||
@@ -887,17 +899,19 @@ func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) {
|
|||||||
|
|
||||||
func TestExtractSchema_DoNothing(t *testing.T) {
|
func TestExtractSchema_DoNothing(t *testing.T) {
|
||||||
|
|
||||||
doc := `components:
|
clearSchemas()
|
||||||
|
|
||||||
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
Something:
|
Something:
|
||||||
$ref: #bork`
|
$ref: #bork`
|
||||||
|
|
||||||
var iNode yaml.Node
|
var iNode yaml.Node
|
||||||
mErr := yaml.Unmarshal([]byte(doc), &iNode)
|
mErr := yaml.Unmarshal([]byte(yml), &iNode)
|
||||||
assert.NoError(t, mErr)
|
assert.NoError(t, mErr)
|
||||||
idx := index.NewSpecIndex(&iNode)
|
idx := index.NewSpecIndex(&iNode)
|
||||||
|
|
||||||
yml := `please: do nothing.`
|
yml = `please: do nothing.`
|
||||||
|
|
||||||
var idxNode yaml.Node
|
var idxNode yaml.Node
|
||||||
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
_ = yaml.Unmarshal([]byte(yml), &idxNode)
|
||||||
|
|||||||
@@ -285,6 +285,11 @@ func ExtractMapFlatNoLookup[PT Buildable[N], N any](root *yaml.Node, idx *index.
|
|||||||
return valueMap, nil
|
return valueMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mappingResult[T any] struct {
|
||||||
|
k KeyReference[string]
|
||||||
|
v ValueReference[T]
|
||||||
|
}
|
||||||
|
|
||||||
func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) {
|
func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) {
|
||||||
var labelNode, valueNode *yaml.Node
|
var labelNode, valueNode *yaml.Node
|
||||||
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
|
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
|
||||||
@@ -314,12 +319,36 @@ func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *
|
|||||||
if valueNode != nil {
|
if valueNode != nil {
|
||||||
var currentLabelNode *yaml.Node
|
var currentLabelNode *yaml.Node
|
||||||
valueMap := make(map[KeyReference[string]]ValueReference[PT])
|
valueMap := make(map[KeyReference[string]]ValueReference[PT])
|
||||||
|
|
||||||
|
bChan := make(chan mappingResult[PT])
|
||||||
|
eChan := make(chan error)
|
||||||
|
|
||||||
|
var buildMap = func(label *yaml.Node, value *yaml.Node, c chan mappingResult[PT], ec chan<- error) {
|
||||||
|
var n PT = new(N)
|
||||||
|
_ = BuildModel(value, n)
|
||||||
|
err := n.Build(value, idx)
|
||||||
|
if err != nil {
|
||||||
|
ec <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c <- mappingResult[PT]{
|
||||||
|
k: KeyReference[string]{
|
||||||
|
KeyNode: label,
|
||||||
|
Value: label.Value,
|
||||||
|
},
|
||||||
|
v: ValueReference[PT]{
|
||||||
|
Value: n,
|
||||||
|
ValueNode: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalKeys := 0
|
||||||
for i, en := range valueNode.Content {
|
for i, en := range valueNode.Content {
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
currentLabelNode = en
|
currentLabelNode = en
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// check our valueNode isn't a reference still.
|
// check our valueNode isn't a reference still.
|
||||||
if h, _, _ := utils.IsNodeRefValue(en); h {
|
if h, _, _ := utils.IsNodeRefValue(en); h {
|
||||||
ref := LocateRefNode(en, idx)
|
ref := LocateRefNode(en, idx)
|
||||||
@@ -334,21 +363,18 @@ func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *
|
|||||||
if strings.HasPrefix(strings.ToLower(currentLabelNode.Value), "x-") {
|
if strings.HasPrefix(strings.ToLower(currentLabelNode.Value), "x-") {
|
||||||
continue // yo, don't pay any attention to extensions, not here anyway.
|
continue // yo, don't pay any attention to extensions, not here anyway.
|
||||||
}
|
}
|
||||||
var n PT = new(N)
|
totalKeys++
|
||||||
err := BuildModel(en, n)
|
go buildMap(currentLabelNode, en, bChan, eChan)
|
||||||
if err != nil {
|
}
|
||||||
return nil, labelNode, valueNode, err
|
|
||||||
}
|
completedKeys := 0
|
||||||
berr := n.Build(en, idx)
|
for completedKeys < totalKeys {
|
||||||
if berr != nil {
|
select {
|
||||||
return nil, labelNode, valueNode, berr
|
case err := <-eChan:
|
||||||
}
|
return valueMap, labelNode, valueNode, err
|
||||||
valueMap[KeyReference[string]{
|
case res := <-bChan:
|
||||||
Value: currentLabelNode.Value,
|
completedKeys++
|
||||||
KeyNode: currentLabelNode,
|
valueMap[res.k] = res.v
|
||||||
}] = ValueReference[PT]{
|
|
||||||
Value: n,
|
|
||||||
ValueNode: en,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return valueMap, labelNode, valueNode, nil
|
return valueMap, labelNode, valueNode, nil
|
||||||
|
|||||||
@@ -54,3 +54,16 @@ func (n KeyReference[T]) IsEmpty() bool {
|
|||||||
func (n KeyReference[T]) GenerateMapKey() string {
|
func (n KeyReference[T]) GenerateMapKey() string {
|
||||||
return fmt.Sprintf("%d:%d", n.KeyNode.Line, n.KeyNode.Column)
|
return fmt.Sprintf("%d:%d", n.KeyNode.Line, n.KeyNode.Column)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsCircular(node *yaml.Node, idx *index.SpecIndex) bool {
|
||||||
|
if idx == nil {
|
||||||
|
return false // no index! nothing we can do.
|
||||||
|
}
|
||||||
|
refs := idx.GetCircularReferences()
|
||||||
|
for i := range idx.GetCircularReferences() {
|
||||||
|
if refs[i].LoopPoint.Node == node {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,89 +1,89 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pb33f/libopenapi/index"
|
"github.com/pb33f/libopenapi/index"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewResolver(t *testing.T) {
|
func TestNewResolver(t *testing.T) {
|
||||||
assert.Nil(t, NewResolver(nil))
|
assert.Nil(t, NewResolver(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Benchmark_ResolveDocumentStripe(b *testing.B) {
|
func Benchmark_ResolveDocumentStripe(b *testing.B) {
|
||||||
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
|
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(stripe, &rootNode)
|
yaml.Unmarshal(stripe, &rootNode)
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
resolver.Resolve()
|
resolver.Resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_ResolveComponents_CircularSpec(t *testing.T) {
|
func TestResolver_ResolveComponents_CircularSpec(t *testing.T) {
|
||||||
|
|
||||||
circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml")
|
circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(circular, &rootNode)
|
yaml.Unmarshal(circular, &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.Resolve()
|
circ := resolver.Resolve()
|
||||||
assert.Len(t, circ, 3)
|
assert.Len(t, circ, 4)
|
||||||
|
|
||||||
_, err := yaml.Marshal(resolver.resolvedRoot)
|
_, err := yaml.Marshal(resolver.resolvedRoot)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_ResolveComponents_Stripe(t *testing.T) {
|
func TestResolver_ResolveComponents_Stripe(t *testing.T) {
|
||||||
|
|
||||||
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
|
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(stripe, &rootNode)
|
yaml.Unmarshal(stripe, &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.Resolve()
|
circ := resolver.Resolve()
|
||||||
assert.Len(t, circ, 0)
|
assert.Len(t, circ, 229)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_ResolveComponents_MixedRef(t *testing.T) {
|
func TestResolver_ResolveComponents_MixedRef(t *testing.T) {
|
||||||
|
|
||||||
mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml")
|
mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(mixedref, &rootNode)
|
yaml.Unmarshal(mixedref, &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.Resolve()
|
circ := resolver.Resolve()
|
||||||
assert.Len(t, circ, 2)
|
assert.Len(t, circ, 2)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_ResolveComponents_k8s(t *testing.T) {
|
func TestResolver_ResolveComponents_k8s(t *testing.T) {
|
||||||
|
|
||||||
k8s, _ := ioutil.ReadFile("../test_specs/k8s.json")
|
k8s, _ := ioutil.ReadFile("../test_specs/k8s.json")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(k8s, &rootNode)
|
yaml.Unmarshal(k8s, &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.Resolve()
|
circ := resolver.Resolve()
|
||||||
assert.Len(t, circ, 0)
|
assert.Len(t, circ, 1)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user