Massive refactor on how the library handles schemas.

Schemas are now rendered on demand. There is no reasonable way to navigate the mayhem that is circular dependencies through multiple inheritance and polymorphism. So now using a msuch simpler design (and MUCH faster), there is a `SchemaProxy` for every schema reference. This holds a reference to the low model and index, that renders the schema on demand. Once rendered, it's done. Any children can also be rendered on demand, and so down the rabbit hole you do (if you want).

All circular dependencies are know by the index, so you can decide when you want to stop, or just keep going for ever, however it's now a choice, not something decided for you.

Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
Dave Shanley
2022-08-27 09:47:37 -04:00
parent 6e0d25776a
commit c34c4f668c
28 changed files with 754 additions and 529 deletions

View File

@@ -47,7 +47,7 @@ func getSeenSchema(key string) *Schema {
}
type Components struct {
Schemas map[string]*Schema
Schemas map[string]*SchemaProxy
Responses map[string]*Response
Parameters map[string]*Parameter
Examples map[string]*Example
@@ -72,8 +72,8 @@ func NewComponents(comp *low.Components) *Components {
requestBodyMap := make(map[string]*RequestBody)
headerMap := make(map[string]*Header)
securitySchemeMap := make(map[string]*SecurityScheme)
schemas := make(map[string]*Schema)
schemaChan := make(chan componentResult[*Schema])
schemas := make(map[string]*SchemaProxy)
schemaChan := make(chan componentResult[*SchemaProxy])
cbChan := make(chan componentResult[*Callback])
linkChan := make(chan componentResult[*Link])
responseChan := make(chan componentResult[*Response])
@@ -173,15 +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}
}
func buildSchema(key lowmodel.KeyReference[string], orig lowmodel.ValueReference[*low.Schema], c chan componentResult[*Schema]) {
var sch *Schema
if ss := getSeenSchema(orig.GenerateMapKey()); ss != nil {
sch = ss
} else {
sch = NewSchema(orig.Value)
addSeenSchema(orig.GenerateMapKey(), sch)
}
c <- componentResult[*Schema]{res: sch, key: key.Value}
func buildSchema(key lowmodel.KeyReference[string], orig lowmodel.ValueReference[*low.SchemaProxy], c chan componentResult[*SchemaProxy]) {
var sch *SchemaProxy
sch = &SchemaProxy{schema: &lowmodel.NodeReference[*low.SchemaProxy]{
Value: orig.Value,
ValueNode: orig.ValueNode,
}}
c <- componentResult[*SchemaProxy]{res: sch, key: key.Value}
}
func (c *Components) GoLow() *low.Components {

View File

@@ -162,34 +162,35 @@ func TestNewDocument_Components_Schemas(t *testing.T) {
goLow := h.Components.GoLow()
a := h.Components.Schemas["Error"]
assert.Equal(t, "No such burger as 'Big-Whopper'", a.Properties["message"].Example)
abcd := a.Schema().Properties["message"].Schema().Example
assert.Equal(t, "No such burger as 'Big-Whopper'", abcd)
assert.Equal(t, 428, goLow.Schemas.KeyNode.Line)
assert.Equal(t, 3, goLow.Schemas.KeyNode.Column)
assert.Equal(t, 431, a.GoLow().Description.KeyNode.Line)
assert.Equal(t, 431, a.Schema().GoLow().Description.KeyNode.Line)
b := h.Components.Schemas["Burger"]
assert.Len(t, b.Required, 2)
assert.Equal(t, "golden slices of happy fun joy", b.Properties["fries"].Description)
assert.Equal(t, int64(2), b.Properties["numPatties"].Example)
assert.Equal(t, 443, goLow.FindSchema("Burger").Value.Properties.KeyNode.Line)
assert.Equal(t, 7, goLow.FindSchema("Burger").Value.Properties.KeyNode.Column)
assert.Equal(t, 445, b.GoLow().FindProperty("name").ValueNode.Line)
assert.Len(t, b.Schema().Required, 2)
assert.Equal(t, "golden slices of happy fun joy", b.Schema().Properties["fries"].Schema().Description)
assert.Equal(t, int64(2), b.Schema().Properties["numPatties"].Schema().Example)
assert.Equal(t, 443, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Line)
assert.Equal(t, 7, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Column)
assert.Equal(t, 445, b.Schema().GoLow().FindProperty("name").ValueNode.Line)
f := h.Components.Schemas["Fries"]
assert.Equal(t, "salt", f.Properties["seasoning"].Items[0].Example)
assert.Len(t, f.Properties["favoriteDrink"].Properties["drinkType"].Enum, 2)
assert.Equal(t, "salt", f.Schema().Properties["seasoning"].Schema().Items[0].Schema().Example)
assert.Len(t, f.Schema().Properties["favoriteDrink"].Schema().Properties["drinkType"].Schema().Enum, 2)
d := h.Components.Schemas["Drink"]
assert.Len(t, d.Required, 2)
assert.True(t, d.AdditionalProperties.(bool))
assert.Equal(t, "drinkType", d.Discriminator.PropertyName)
assert.Equal(t, "some value", d.Discriminator.Mapping["drink"])
assert.Equal(t, 511, d.Discriminator.GoLow().PropertyName.ValueNode.Line)
assert.Equal(t, 23, d.Discriminator.GoLow().PropertyName.ValueNode.Column)
assert.Len(t, d.Schema().Required, 2)
assert.True(t, d.Schema().AdditionalProperties.(bool))
assert.Equal(t, "drinkType", d.Schema().Discriminator.PropertyName)
assert.Equal(t, "some value", d.Schema().Discriminator.Mapping["drink"])
assert.Equal(t, 511, d.Schema().Discriminator.GoLow().PropertyName.ValueNode.Line)
assert.Equal(t, 23, d.Schema().Discriminator.GoLow().PropertyName.ValueNode.Column)
pl := h.Components.Schemas["SomePayload"]
assert.Equal(t, "is html programming? yes.", pl.XML.Name)
assert.Equal(t, 518, pl.XML.GoLow().Name.ValueNode.Line)
assert.Equal(t, "is html programming? yes.", pl.Schema().XML.Name)
assert.Equal(t, 518, pl.Schema().XML.GoLow().Name.ValueNode.Line)
ext := h.Components.Extensions
assert.Equal(t, "loud", ext["x-screaming-baby"])
@@ -225,7 +226,7 @@ func TestNewDocument_Components_Responses(t *testing.T) {
h := NewDocument(doc)
assert.Len(t, h.Components.Responses, 1)
assert.Equal(t, "all the dressings for a burger.", h.Components.Responses["DressingResponse"].Description)
assert.Equal(t, "array", h.Components.Responses["DressingResponse"].Content["application/json"].Schema.Type)
assert.Equal(t, "array", h.Components.Responses["DressingResponse"].Content["application/json"].Schema.Schema().Type)
assert.Equal(t, 347, h.Components.Responses["DressingResponse"].GoLow().Description.KeyNode.Line)
assert.Equal(t, 7, h.Components.Responses["DressingResponse"].GoLow().Description.KeyNode.Column)
}
@@ -266,12 +267,12 @@ func TestNewDocument_Components_Parameters(t *testing.T) {
bh := h.Components.Parameters["BurgerHeader"]
assert.Equal(t, "burgerHeader", bh.Name)
assert.Equal(t, 387, bh.GoLow().Name.KeyNode.Line)
assert.Len(t, bh.Schema.Properties, 2)
assert.Len(t, bh.Schema.Schema().Properties, 2)
assert.Equal(t, "big-mac", bh.Example)
assert.True(t, bh.Required)
assert.Equal(t, "this is a header",
bh.Content["application/json"].Encoding["burgerTheme"].Headers["someHeader"].Description)
assert.Len(t, bh.Content["application/json"].Schema.Properties, 2)
assert.Len(t, bh.Content["application/json"].Schema.Schema().Properties, 2)
assert.Equal(t, 404, bh.Content["application/json"].Encoding["burgerTheme"].GoLow().ContentType.ValueNode.Line)
}

View File

@@ -16,7 +16,7 @@ type Header struct {
Style string
Explode bool
AllowReserved bool
Schema *Schema
Schema *SchemaProxy
Example any
Examples map[string]*Example
Content map[string]*MediaType
@@ -35,13 +35,11 @@ func NewHeader(header *low.Header) *Header {
h.Explode = header.Explode.Value
h.AllowReserved = header.AllowReserved.Value
if !header.Schema.IsEmpty() {
// check if schema has been seen or not.
if v := getSeenSchema(header.Schema.GenerateMapKey()); v != nil {
h.Schema = v
} else {
h.Schema = NewSchema(header.Schema.Value)
addSeenSchema(header.Schema.GenerateMapKey(), h.Schema)
}
h.Schema = &SchemaProxy{schema: &lowmodel.NodeReference[*low.SchemaProxy]{
Value: header.Schema.Value,
KeyNode: header.Schema.KeyNode,
ValueNode: header.Schema.ValueNode,
}}
}
h.Content = ExtractContent(header.Content.Value)
h.Example = header.Example.Value

View File

@@ -10,7 +10,7 @@ import (
)
type MediaType struct {
Schema *Schema
Schema *SchemaProxy
Example any
Examples map[string]*Example
Encoding map[string]*Encoding
@@ -22,13 +22,7 @@ func NewMediaType(mediaType *low.MediaType) *MediaType {
m := new(MediaType)
m.low = mediaType
if !mediaType.Schema.IsEmpty() {
// check if schema has been seen or not.
if v := getSeenSchema(mediaType.Schema.GenerateMapKey()); v != nil {
m.Schema = v
} else {
m.Schema = NewSchema(mediaType.Schema.Value)
addSeenSchema(mediaType.Schema.GenerateMapKey(), m.Schema)
}
m.Schema = &SchemaProxy{schema: &mediaType.Schema}
}
m.Example = mediaType.Example
m.Examples = ExtractExamples(mediaType.Examples.Value)

View File

@@ -18,7 +18,7 @@ type Parameter struct {
Style string
Explode bool
AllowReserved bool
Schema *Schema
Schema *SchemaProxy
Example any
Examples map[string]*Example
Content map[string]*MediaType
@@ -37,14 +37,8 @@ func NewParameter(param *low.Parameter) *Parameter {
p.Style = param.Style.Value
p.Explode = param.Explode.Value
p.AllowReserved = param.AllowReserved.Value
if !param.Schema.IsEmpty() {
if v := getSeenSchema(param.Schema.GenerateMapKey()); v != nil {
p.Schema = v
} else {
p.Schema = NewSchema(param.Schema.Value)
addSeenSchema(param.Schema.GenerateMapKey(), p.Schema)
}
p.Schema = &SchemaProxy{schema: &param.Schema}
}
p.Required = param.Required.Value
p.Example = param.Example.Value

View File

@@ -29,12 +29,12 @@ type Schema struct {
Required []string
Enum []string
Type string
AllOf []*Schema
OneOf []*Schema
AnyOf []*Schema
Not []*Schema
Items []*Schema
Properties map[string]*Schema
AllOf []*SchemaProxy
OneOf []*SchemaProxy
AnyOf []*SchemaProxy
Not []*SchemaProxy
Items []*SchemaProxy
Properties map[string]*SchemaProxy
AdditionalProperties any
Description string
Default any
@@ -105,28 +105,32 @@ func NewSchema(schema *low.Schema) *Schema {
polyCompletedChan := make(chan bool)
propsChan := make(chan bool)
errChan := make(chan error)
// schema async
buildOutSchema := func(schemas []lowmodel.NodeReference[*low.Schema], items *[]*Schema, doneChan chan bool) {
bChan := make(chan *Schema)
buildOutSchema := func(schemas []lowmodel.ValueReference[*low.SchemaProxy], items *[]*SchemaProxy,
doneChan chan bool, e chan error) {
bChan := make(chan *SchemaProxy)
eChan := make(chan error)
// for every item, build schema async
buildSchemaChild := func(sch lowmodel.NodeReference[*low.Schema], bChan chan *Schema) {
if ss := getSeenSchema(sch.GenerateMapKey()); ss != nil {
bChan <- ss
return
}
ns := NewSchema(sch.Value)
addSeenSchema(sch.GenerateMapKey(), ns)
bChan <- ns
buildSchemaChild := func(sch lowmodel.ValueReference[*low.SchemaProxy], bChan chan *SchemaProxy, e chan error) {
p := &SchemaProxy{schema: &lowmodel.NodeReference[*low.SchemaProxy]{
ValueNode: sch.ValueNode,
Value: sch.Value,
}}
bChan <- p
}
totalSchemas := len(schemas)
for v := range schemas {
go buildSchemaChild(schemas[v], bChan)
go buildSchemaChild(schemas[v], bChan, eChan)
}
j := 0
for j < totalSchemas {
select {
case er := <-eChan:
e <- er
return
case t := <-bChan:
j++
*items = append(*items, t)
@@ -137,48 +141,45 @@ func NewSchema(schema *low.Schema) *Schema {
// props async
plock := sync.RWMutex{}
var buildProps = func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*low.Schema], c chan bool,
props map[string]*Schema) {
if ss := getSeenSchema(v.GenerateMapKey()); ss != nil {
defer plock.Unlock()
plock.Lock()
props[k.Value] = ss
} else {
defer plock.Unlock()
plock.Lock()
props[k.Value] = NewSchema(v.Value)
addSeenSchema(k.GenerateMapKey(), props[k.Value])
var buildProps = func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*low.SchemaProxy], c chan bool,
props map[string]*SchemaProxy) {
defer plock.Unlock()
plock.Lock()
props[k.Value] = &SchemaProxy{schema: &lowmodel.NodeReference[*low.SchemaProxy]{
Value: v.Value,
KeyNode: k.KeyNode,
ValueNode: v.ValueNode,
},
}
s.Properties = props
c <- true
}
props := make(map[string]*Schema)
props := make(map[string]*SchemaProxy)
for k, v := range schema.Properties.Value {
go buildProps(k, v, propsChan, props)
}
var allOf []*Schema
var oneOf []*Schema
var anyOf []*Schema
var not []*Schema
var items []*Schema
var allOf []*SchemaProxy
var oneOf []*SchemaProxy
var anyOf []*SchemaProxy
var not []*SchemaProxy
var items []*SchemaProxy
if !schema.AllOf.IsEmpty() {
go buildOutSchema(schema.AllOf.Value, &allOf, polyCompletedChan)
go buildOutSchema(schema.AllOf.Value, &allOf, polyCompletedChan, errChan)
}
if !schema.AnyOf.IsEmpty() {
go buildOutSchema(schema.AnyOf.Value, &anyOf, polyCompletedChan)
go buildOutSchema(schema.AnyOf.Value, &anyOf, polyCompletedChan, errChan)
}
if !schema.OneOf.IsEmpty() {
go buildOutSchema(schema.OneOf.Value, &oneOf, polyCompletedChan)
go buildOutSchema(schema.OneOf.Value, &oneOf, polyCompletedChan, errChan)
}
if !schema.Not.IsEmpty() {
go buildOutSchema(schema.Not.Value, &not, polyCompletedChan)
go buildOutSchema(schema.Not.Value, &not, polyCompletedChan, errChan)
}
if !schema.Items.IsEmpty() {
go buildOutSchema(schema.Items.Value, &items, polyCompletedChan)
go buildOutSchema(schema.Items.Value, &items, polyCompletedChan, errChan)
}
completeChildren := 0

View File

@@ -0,0 +1,27 @@
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/3.0"
)
type SchemaProxy struct {
schema *low.NodeReference[*v3.SchemaProxy]
buildError error
}
func (sp *SchemaProxy) Schema() *Schema {
s := sp.schema.Value.Schema()
if s == nil {
sp.buildError = sp.GetBuildError()
return nil
}
return NewSchema(s)
}
func (sp *SchemaProxy) GetBuildError() error {
return sp.buildError
}

View File

@@ -44,7 +44,7 @@ func getSeenSchema(key string) *Schema {
}
type Components struct {
Schemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]]
Schemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]
Responses low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Response]]
Parameters low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Parameter]]
Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]
@@ -60,8 +60,8 @@ func (co *Components) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, co.Extensions)
}
func (co *Components) FindSchema(schema string) *low.ValueReference[*Schema] {
return low.FindItemInMap[*Schema](schema, co.Schemas.Value)
func (co *Components) FindSchema(schema string) *low.ValueReference[*SchemaProxy] {
return low.FindItemInMap[*SchemaProxy](schema, co.Schemas.Value)
}
func (co *Components) FindResponse(response string) *low.ValueReference[*Response] {
@@ -103,7 +103,7 @@ func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error {
skipChan := make(chan bool)
errorChan := make(chan error)
paramChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Parameter]])
schemaChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]])
schemaChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]])
responsesChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Response]])
examplesChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]])
requestBodiesChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*RequestBody]])
@@ -112,7 +112,7 @@ func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error {
linkChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Link]])
callbackChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Callback]])
go extractComponentValues[*Schema](SchemasLabel, root, skipChan, errorChan, schemaChan, idx)
go extractComponentValues[*SchemaProxy](SchemasLabel, root, skipChan, errorChan, schemaChan, idx)
go extractComponentValues[*Parameter](ParametersLabel, root, skipChan, errorChan, paramChan, idx)
go extractComponentValues[*Response](ResponsesLabel, root, skipChan, errorChan, responsesChan, idx)
go extractComponentValues[*Example](ExamplesLabel, root, skipChan, errorChan, examplesChan, idx)
@@ -136,7 +136,6 @@ func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error {
n++
case schemas := <-schemaChan:
co.Schemas = schemas
cacheSchemas(co.Schemas.Value)
n++
case responses := <-responsesChan:
co.Responses = responses
@@ -191,6 +190,7 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.
// for every component, build in a new thread!
bChan := make(chan componentBuildResult[T])
eChan := make(chan error)
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)
@@ -221,12 +221,14 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.
continue
}
totalComponents++
go buildComponent(currentLabel, v, bChan, errorChan)
go buildComponent(currentLabel, v, bChan, eChan)
}
completedComponents := 0
for completedComponents < totalComponents {
select {
case e := <-eChan:
errorChan <- e
case r := <-bChan:
componentValues[r.k] = r.v
completedComponents++

View File

@@ -76,8 +76,8 @@ func TestComponents_Build_Success(t *testing.T) {
err = n.Build(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "one of many", n.FindSchema("one").Value.Description.Value)
assert.Equal(t, "two of many", n.FindSchema("two").Value.Description.Value)
assert.Equal(t, "one of many", n.FindSchema("one").Value.Schema().Description.Value)
assert.Equal(t, "two of many", n.FindSchema("two").Value.Schema().Description.Value)
assert.Equal(t, "three of many", n.FindResponse("three").Value.Description.Value)
assert.Equal(t, "four of many", n.FindResponse("four").Value.Description.Value)
assert.Equal(t, "five of many", n.FindParameter("five").Value.Description.Value)
@@ -92,8 +92,10 @@ func TestComponents_Build_Success(t *testing.T) {
assert.Equal(t, "fourteen of many", n.FindSecurityScheme("fourteen").Value.Description.Value)
assert.Equal(t, "fifteen of many", n.FindLink("fifteen").Value.Description.Value)
assert.Equal(t, "sixteen of many", n.FindLink("sixteen").Value.Description.Value)
assert.Equal(t, "seventeen of many", n.FindCallback("seventeen").Value.FindExpression("{reference}").Value.Description.Value)
assert.Equal(t, "eighteen of many", n.FindCallback("eighteen").Value.FindExpression("{raference}").Value.Description.Value)
assert.Equal(t, "seventeen of many",
n.FindCallback("seventeen").Value.FindExpression("{reference}").Value.Description.Value)
assert.Equal(t, "eighteen of many",
n.FindCallback("eighteen").Value.FindExpression("{raference}").Value.Description.Value)
}

View File

@@ -36,7 +36,6 @@ func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
if er := runFunc(info, doc, idx); er != nil {
*ers = append(*ers, er)
}
wg.Done()
}
extractionFuncs := []func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{
@@ -46,15 +45,14 @@ func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
extractComponents,
extractSecurity,
extractExternalDocs,
extractPaths,
}
wg.Add(len(extractionFuncs))
for _, f := range extractionFuncs {
go runExtraction(info, &doc, idx, f, &errors, &wg)
}
wg.Wait()
extractPaths(info, &doc, idx)
return &doc, errors
}
@@ -62,11 +60,8 @@ func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex)
_, ln, vn := utils.FindKeyNodeFull(InfoLabel, info.RootNode.Content)
if vn != nil {
ir := Info{}
err := low.BuildModel(vn, &ir)
if err != nil {
return err
}
err = ir.Build(vn, idx)
_ = low.BuildModel(vn, &ir)
_ = ir.Build(vn, idx)
nr := low.NodeReference[*Info]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Info = nr
}
@@ -95,11 +90,11 @@ func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecI
_, ln, vn := utils.FindKeyNodeFull(ComponentsLabel, info.RootNode.Content)
if vn != nil {
ir := Components{}
err := low.BuildModel(vn, &ir)
_ = low.BuildModel(vn, &ir)
err := ir.Build(vn, idx)
if err != nil {
return err
}
err = ir.Build(vn, idx)
nr := low.NodeReference[*Components]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Components = nr
}
@@ -114,11 +109,8 @@ func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecInde
for _, srvN := range vn.Content {
if utils.IsNodeMap(srvN) {
srvr := Server{}
err := low.BuildModel(srvN, &srvr)
if err != nil {
return err
}
srvr.Build(srvN, idx)
_ = low.BuildModel(srvN, &srvr)
_ = srvr.Build(srvN, idx)
servers = append(servers, low.ValueReference[*Server]{
Value: &srvr,
ValueNode: srvN,
@@ -143,11 +135,10 @@ func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex)
for _, tagN := range vn.Content {
if utils.IsNodeMap(tagN) {
tag := Tag{}
err := low.BuildModel(tagN, &tag)
if err != nil {
_ = low.BuildModel(tagN, &tag)
if err := tag.Build(tagN, idx); err != nil {
return err
}
tag.Build(tagN, idx)
tags = append(tags, low.ValueReference[*Tag]{
Value: &tag,
ValueNode: tagN,

View File

@@ -56,10 +56,10 @@ func BenchmarkCreateDocument_k8s(b *testing.B) {
}
func BenchmarkCreateDocument_Stripe(b *testing.B) {
data, _ := ioutil.ReadFile("../../../test_specs/stripe.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
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")
@@ -78,6 +78,20 @@ func BenchmarkCreateDocument_Petstore(b *testing.B) {
}
}
func TestCreateDocumentStripe(t *testing.T) {
data, _ := ioutil.ReadFile("../../../test_specs/stripe.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
d, err := CreateDocument(info)
if err != nil {
panic("broken something")
}
assert.Equal(t, "3.0.0", d.Version.Value)
assert.Equal(t, "Stripe API", d.Info.Value.Title.Value)
assert.NotEmpty(t, d.Info.Value.Title.Value)
}
func TestCreateDocument(t *testing.T) {
initTest()
assert.Equal(t, "3.0.1", doc.Version.Value)
@@ -174,8 +188,8 @@ func TestCreateDocument_Paths(t *testing.T) {
assert.Len(t, burgerId.Value.Get.Value.Parameters.Value, 2)
param := burgerId.Value.Get.Value.Parameters.Value[1]
assert.Equal(t, "burgerHeader", param.Value.Name.Value)
prop := param.Value.Schema.Value.FindProperty("burgerTheme")
assert.Equal(t, "something about a theme?", prop.Value.Description.Value)
prop := param.Value.Schema.Value.Schema().FindProperty("burgerTheme")
assert.Equal(t, "something about a theme?", prop.Value.Schema().Description.Value)
assert.Equal(t, "big-mac", param.Value.Example.Value)
// check content
@@ -189,7 +203,7 @@ func TestCreateDocument_Paths(t *testing.T) {
header := encoding.Value.FindHeader("someHeader")
assert.NotNil(t, header.Value)
assert.Equal(t, "this is a header", header.Value.Description.Value)
assert.Equal(t, "string", header.Value.Schema.Value.Type.Value)
assert.Equal(t, "string", header.Value.Schema.Value.Schema().Type.Value)
// check request body on operation
burgers := doc.Paths.Value.FindPath("/burgers")
@@ -206,7 +220,7 @@ func TestCreateDocument_Paths(t *testing.T) {
content := requestBody.FindContent("application/json").Value
assert.NotNil(t, content)
assert.Len(t, content.Schema.Value.Properties.Value, 4)
assert.Len(t, content.Schema.Value.Schema().Properties.Value, 4)
assert.Len(t, content.GetAllExamples(), 2)
ex := content.FindExample("pbjBurger")
@@ -253,7 +267,7 @@ func TestCreateDocument_Paths(t *testing.T) {
assert.NotNil(t, respContent)
assert.NotNil(t, respContent.Schema.Value)
assert.Len(t, respContent.Schema.Value.Required.Value, 2)
assert.Len(t, respContent.Schema.Value.Schema().Required.Value, 2)
respExample := respContent.FindExample("quarterPounder")
assert.NotNil(t, respExample.Value)
@@ -304,19 +318,20 @@ func TestCreateDocument_Components_Schemas(t *testing.T) {
burger := components.FindSchema("Burger")
assert.NotNil(t, burger.Value)
assert.Equal(t, "The tastiest food on the planet you would love to eat everyday", burger.Value.Description.Value)
assert.Equal(t, "The tastiest food on the planet you would love to eat everyday", burger.Value.Schema().Description.Value)
er := components.FindSchema("Error")
assert.NotNil(t, er.Value)
assert.Equal(t, "Error defining what went wrong when providing a specification. The message should help indicate the issue clearly.", er.Value.Description.Value)
assert.Equal(t, "Error defining what went wrong when providing a specification. The message should help "+
"indicate the issue clearly.", er.Value.Schema().Description.Value)
fries := components.FindSchema("Fries")
assert.NotNil(t, fries.Value)
assert.Len(t, fries.Value.Properties.Value, 3)
p := fries.Value.FindProperty("favoriteDrink")
assert.Len(t, fries.Value.Schema().Properties.Value, 3)
p := fries.Value.Schema().FindProperty("favoriteDrink")
assert.Equal(t, "a frosty cold beverage can be coke or sprite",
p.Value.Description.Value)
p.Value.Schema().Description.Value)
}
@@ -395,7 +410,7 @@ func TestCreateDocument_Components_Headers(t *testing.T) {
useOil := components.FindHeader("UseOil")
assert.NotNil(t, useOil.Value)
assert.Equal(t, "this is a header", useOil.Value.Description.Value)
assert.Equal(t, "string", useOil.Value.Schema.Value.Type.Value)
assert.Equal(t, "string", useOil.Value.Schema.Value.Schema().Type.Value)
}
func TestCreateDocument_Components_Links(t *testing.T) {
@@ -442,7 +457,7 @@ func TestCreateDocument_Component_Discriminator(t *testing.T) {
initTest()
components := doc.Components.Value
dsc := components.FindSchema("Drink").Value.Discriminator.Value
dsc := components.FindSchema("Drink").Value.Schema().Discriminator.Value
assert.NotNil(t, dsc)
assert.Equal(t, "drinkType", dsc.PropertyName.Value)
assert.Equal(t, "some value", dsc.FindMappingValue("drink").Value)
@@ -453,8 +468,8 @@ func TestCreateDocument_CheckAdditionalProperties_Schema(t *testing.T) {
initTest()
components := doc.Components.Value
d := components.FindSchema("Dressing")
assert.NotNil(t, d.Value.AdditionalProperties.Value)
if n, ok := d.Value.AdditionalProperties.Value.(*Schema); ok {
assert.NotNil(t, d.Value.Schema().AdditionalProperties.Value)
if n, ok := d.Value.Schema().AdditionalProperties.Value.(*Schema); ok {
assert.Equal(t, "something in here.", n.Description.Value)
} else {
assert.Fail(t, "should be a schema")
@@ -465,8 +480,8 @@ func TestCreateDocument_CheckAdditionalProperties_Bool(t *testing.T) {
initTest()
components := doc.Components.Value
d := components.FindSchema("Drink")
assert.NotNil(t, d.Value.AdditionalProperties.Value)
assert.True(t, d.Value.AdditionalProperties.Value.(bool))
assert.NotNil(t, d.Value.Schema().AdditionalProperties.Value)
assert.True(t, d.Value.Schema().AdditionalProperties.Value.(bool))
}
func TestCreateDocument_Components_Error(t *testing.T) {
@@ -477,6 +492,51 @@ func TestCreateDocument_Components_Error(t *testing.T) {
bark:
$ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
assert.Len(t, err, 0)
ob := doc.Components.Value.FindSchema("bork").Value
ob.Schema()
assert.Error(t, ob.GetBuildError())
}
func TestCreateDocument_Paths_Errors(t *testing.T) {
yml := `paths:
/p:
$ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
assert.Len(t, err, 1)
}
func TestCreateDocument_Tags_Errors(t *testing.T) {
yml := `tags:
- $ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
assert.Len(t, err, 1)
}
func TestCreateDocument_Security_Error(t *testing.T) {
yml := `security:
$ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)
assert.Len(t, err, 1)
}
func TestCreateDocument_ExternalDoc_Error(t *testing.T) {
yml := `externalDocs:
$ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error
doc, err = CreateDocument(info)

View File

@@ -123,3 +123,10 @@ value:
}
}
func TestExtractExampleValue(t *testing.T) {
assert.True(t, ExtractExampleValue(&yaml.Node{Tag: "!!bool", Value: "true"}).(bool))
assert.Equal(t, int64(10), ExtractExampleValue(&yaml.Node{Tag: "!!int", Value: "10"}).(int64))
assert.Equal(t, 33.2, ExtractExampleValue(&yaml.Node{Tag: "!!float", Value: "33.2"}).(float64))
}

View File

@@ -22,7 +22,7 @@ type Header struct {
Style low.NodeReference[string]
Explode low.NodeReference[bool]
AllowReserved low.NodeReference[bool]
Schema low.NodeReference[*Schema]
Schema low.NodeReference[*SchemaProxy]
Example low.NodeReference[any]
Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]
Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]]

View File

@@ -60,11 +60,11 @@ content:
assert.True(t, n.Required.Value)
assert.False(t, n.Deprecated.Value)
assert.NotNil(t, n.Schema.Value)
assert.Equal(t, "my triple M, my loves", n.Schema.Value.Description.Value)
assert.NotNil(t, n.Schema.Value.Properties.Value)
assert.Equal(t, "she is my heart.", n.Schema.Value.FindProperty("michelle").Value.Description.Value)
assert.Equal(t, "she is my song.", n.Schema.Value.FindProperty("meddy").Value.Description.Value)
assert.Equal(t, "he is my champion.", n.Schema.Value.FindProperty("maddy").Value.Description.Value)
assert.Equal(t, "my triple M, my loves", n.Schema.Value.Schema().Description.Value)
assert.NotNil(t, n.Schema.Value.Schema().Properties.Value)
assert.Equal(t, "she is my heart.", n.Schema.Value.Schema().FindProperty("michelle").Value.Schema().Description.Value)
assert.Equal(t, "she is my song.", n.Schema.Value.Schema().FindProperty("meddy").Value.Schema().Description.Value)
assert.Equal(t, "he is my champion.", n.Schema.Value.Schema().FindProperty("maddy").Value.Schema().Description.Value)
if v, ok := n.Example.Value.(map[string]interface{}); ok {
assert.Equal(t, "my love.", v["michelle"])
@@ -76,7 +76,7 @@ content:
con := n.FindContent("family/love").Value
assert.NotNil(t, con)
assert.Equal(t, "family love.", con.Schema.Value.Description.Value)
assert.Equal(t, "family love.", con.Schema.Value.Schema().Description.Value)
assert.Nil(t, n.FindContent("unknown"))
ext := n.FindExtension("x-family-love").Value

View File

@@ -4,78 +4,78 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
type MediaType struct {
Schema low.NodeReference[*Schema]
Example low.NodeReference[any]
Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]
Encoding low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Schema low.NodeReference[*SchemaProxy]
Example low.NodeReference[any]
Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]
Encoding low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]
Extensions map[low.KeyReference[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] {
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] {
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] {
return mt.Examples.Value
return mt.Examples.Value
}
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.
_, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content)
if expNode != nil {
mt.Example = low.NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode}
}
// handle example if set.
_, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content)
if expNode != nil {
mt.Example = low.NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode}
}
//handle schema
sch, sErr := ExtractSchema(root, idx)
if sErr != nil {
return sErr
}
if sch != nil {
mt.Schema = *sch
}
//handle schema
sch, sErr := ExtractSchema(root, idx)
if sErr != nil {
return sErr
}
if sch != nil {
mt.Schema = *sch
}
// handle examples if set.
exps, expsL, expsN, eErr := low.ExtractMapFlat[*Example](ExamplesLabel, root, idx)
if eErr != nil {
return eErr
}
if exps != nil {
mt.Examples = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]{
Value: exps,
KeyNode: expsL,
ValueNode: expsN,
}
}
// handle examples if set.
exps, expsL, expsN, eErr := low.ExtractMapFlat[*Example](ExamplesLabel, root, idx)
if eErr != nil {
return eErr
}
if exps != nil {
mt.Examples = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]{
Value: exps,
KeyNode: expsL,
ValueNode: expsN,
}
}
// handle encoding
encs, encsL, encsN, encErr := low.ExtractMapFlat[*Encoding](EncodingLabel, root, idx)
if encErr != nil {
return encErr
}
if encs != nil {
mt.Encoding = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]{
Value: encs,
KeyNode: encsL,
ValueNode: encsN,
}
}
return nil
// handle encoding
encs, encsL, encsN, encErr := low.ExtractMapFlat[*Encoding](EncodingLabel, root, idx)
if encErr != nil {
return encErr
}
if encs != nil {
mt.Encoding = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]{
Value: encs,
KeyNode: encsL,
ValueNode: encsN,
}
}
return nil
}

View File

@@ -36,7 +36,7 @@ x-rock: and roll`
err = n.Build(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "and roll", n.FindExtension("x-rock").Value)
assert.Equal(t, "string", n.Schema.Value.Type.Value)
assert.Equal(t, "string", n.Schema.Value.Schema().Type.Value)
assert.Equal(t, "hello", n.Example.Value)
assert.Equal(t, "why?", n.FindExample("what").Value.Value.Value)
assert.Equal(t, "there?", n.FindExample("where").Value.Value.Value)

View File

@@ -25,7 +25,7 @@ type Parameter struct {
Style low.NodeReference[string]
Explode low.NodeReference[bool]
AllowReserved low.NodeReference[bool]
Schema low.NodeReference[*Schema]
Schema low.NodeReference[*SchemaProxy]
Example low.NodeReference[any]
Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]
Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]]

View File

@@ -64,11 +64,11 @@ content:
assert.Equal(t, "happy", n.Name.Value)
assert.Equal(t, "path", n.In.Value)
assert.NotNil(t, n.Schema.Value)
assert.Equal(t, "my triple M, my loves", n.Schema.Value.Description.Value)
assert.NotNil(t, n.Schema.Value.Properties.Value)
assert.Equal(t, "she is my heart.", n.Schema.Value.FindProperty("michelle").Value.Description.Value)
assert.Equal(t, "she is my song.", n.Schema.Value.FindProperty("meddy").Value.Description.Value)
assert.Equal(t, "he is my champion.", n.Schema.Value.FindProperty("maddy").Value.Description.Value)
assert.Equal(t, "my triple M, my loves", n.Schema.Value.Schema().Description.Value)
assert.NotNil(t, n.Schema.Value.Schema().Properties.Value)
assert.Equal(t, "she is my heart.", n.Schema.Value.Schema().FindProperty("michelle").Value.Schema().Description.Value)
assert.Equal(t, "she is my song.", n.Schema.Value.Schema().FindProperty("meddy").Value.Schema().Description.Value)
assert.Equal(t, "he is my champion.", n.Schema.Value.Schema().FindProperty("maddy").Value.Schema().Description.Value)
if v, ok := n.Example.Value.(map[string]interface{}); ok {
assert.Equal(t, "my love.", v["michelle"])
@@ -80,7 +80,7 @@ content:
con := n.FindContent("family/love").Value
assert.NotNil(t, con)
assert.Equal(t, "family love.", con.Schema.Value.Description.Value)
assert.Equal(t, "family love.", con.Schema.Value.Schema().Description.Value)
assert.Nil(t, n.FindContent("unknown"))
ext := n.FindExtension("x-family-love").Value

View File

@@ -30,9 +30,9 @@ type Paths struct {
}
func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] {
for k, p := range p.PathItems {
for k, j := range p.PathItems {
if k.Value == path {
return &p
return &j
}
}
return nil

View File

@@ -39,7 +39,7 @@ func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error {
r.Default = def
}
} else {
return fmt.Errorf("responses build failed: root node is not a map! line %d, col %d", root.Line, root.Column)
return fmt.Errorf("responses build failed: vn node is not a map! line %d, col %d", root.Line, root.Column)
}
return nil
}

View File

@@ -49,7 +49,7 @@ default:
con := ok.Value.FindContent("nice/rice")
assert.NotNil(t, con.Value)
assert.Equal(t, "this is some content.", con.Value.Schema.Value.Description.Value)
assert.Equal(t, "this is some content.", con.Value.Schema.Value.Schema().Description.Value)
head := ok.Value.FindHeader("header1")
assert.NotNil(t, head.Value)

View File

@@ -40,12 +40,12 @@ type Schema struct {
Required low.NodeReference[[]low.ValueReference[string]]
Enum low.NodeReference[[]low.ValueReference[string]]
Type low.NodeReference[string]
AllOf low.NodeReference[[]low.NodeReference[*Schema]]
OneOf low.NodeReference[[]low.NodeReference[*Schema]]
AnyOf low.NodeReference[[]low.NodeReference[*Schema]]
Not low.NodeReference[[]low.NodeReference[*Schema]]
Items low.NodeReference[[]low.NodeReference[*Schema]]
Properties low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]]
AllOf low.NodeReference[[]low.ValueReference[*SchemaProxy]]
OneOf low.NodeReference[[]low.ValueReference[*SchemaProxy]]
AnyOf low.NodeReference[[]low.ValueReference[*SchemaProxy]]
Not low.NodeReference[[]low.ValueReference[*SchemaProxy]]
Items low.NodeReference[[]low.ValueReference[*SchemaProxy]]
Properties low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]
AdditionalProperties low.NodeReference[any]
Description low.NodeReference[string]
Default low.NodeReference[any]
@@ -60,8 +60,8 @@ type Schema struct {
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (s *Schema) FindProperty(name string) *low.ValueReference[*Schema] {
return low.FindItemInMap[*Schema](name, s.Properties.Value)
func (s *Schema) FindProperty(name string) *low.ValueReference[*SchemaProxy] {
return low.FindItemInMap[*SchemaProxy](name, s.Properties.Value)
}
func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
@@ -83,7 +83,7 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er
if ref != nil {
root = ref
} else {
return fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
return fmt.Errorf("build schema failed: reference cannot be found: '%s', line %d, col %d",
root.Content[1].Value, root.Content[1].Line, root.Content[1].Column)
}
}
@@ -140,39 +140,16 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er
}
// for property, build in a new thread!
bChan := make(chan schemaBuildResult)
eChan := make(chan error)
bChan := make(chan schemaProxyBuildResult)
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{
var buildProperty = func(label *yaml.Node, value *yaml.Node, c chan schemaProxyBuildResult) {
c <- schemaProxyBuildResult{
k: low.KeyReference[string]{
KeyNode: label,
Value: label.Value,
},
v: low.ValueReference[*Schema]{
Value: p,
v: low.ValueReference[*SchemaProxy]{
Value: &SchemaProxy{kn: label, vn: value, idx: idx},
ValueNode: value,
},
}
@@ -181,7 +158,7 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er
// handle properties
_, propLabel, propsNode := utils.FindKeyNodeFull(PropertiesLabel, root.Content)
if propsNode != nil {
propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*Schema])
propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*SchemaProxy])
var currentProp *yaml.Node
totalProps := 0
for i, prop := range propsNode.Content {
@@ -201,64 +178,106 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er
}
}
totalProps++
go buildProperty(currentProp, prop, bChan, eChan)
go buildProperty(currentProp, prop, bChan)
}
completedProps := 0
for completedProps < totalProps {
select {
case err := <-eChan:
return err
case res := <-bChan:
completedProps++
propertyMap[res.k] = res.v
}
}
s.Properties = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]]{
s.Properties = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]{
Value: propertyMap,
KeyNode: propLabel,
ValueNode: propsNode,
}
}
// extract all sub-schemas
var errors []error
var allOf, anyOf, oneOf, not, items []low.ValueReference[*SchemaProxy]
var allOf, anyOf, oneOf, not, items []low.NodeReference[*Schema]
_, allOfLabel, allOfValue := utils.FindKeyNodeFull(AllOfLabel, root.Content)
_, anyOfLabel, anyOfValue := utils.FindKeyNodeFull(AnyOfLabel, root.Content)
_, oneOfLabel, oneOfValue := utils.FindKeyNodeFull(OneOfLabel, root.Content)
_, notLabel, notValue := utils.FindKeyNodeFull(NotLabel, root.Content)
_, itemsLabel, itemsValue := utils.FindKeyNodeFull(ItemsLabel, root.Content)
// make this async at some point to speed things up.
allOfLabel, allOfValue := buildSchema(&allOf, AllOfLabel, root, level, &errors, idx)
anyOfLabel, anyOfValue := buildSchema(&anyOf, AnyOfLabel, root, level, &errors, idx)
oneOfLabel, oneOfValue := buildSchema(&oneOf, OneOfLabel, root, level, &errors, idx)
notLabel, notValue := buildSchema(&not, NotLabel, root, level, &errors, idx)
itemsLabel, itemsValue := buildSchema(&items, ItemsLabel, root, level, &errors, idx)
errorChan := make(chan error)
allOfChan := make(chan schemaProxyBuildResult)
anyOfChan := make(chan schemaProxyBuildResult)
oneOfChan := make(chan schemaProxyBuildResult)
itemsChan := make(chan schemaProxyBuildResult)
notChan := make(chan schemaProxyBuildResult)
if len(errors) > 0 {
// todo fix this
return errors[0]
totalBuilds := countSubSchemaItems(allOfValue) +
countSubSchemaItems(anyOfValue) +
countSubSchemaItems(oneOfValue) +
countSubSchemaItems(notValue) +
countSubSchemaItems(itemsValue)
if allOfValue != nil {
go buildSchema(allOfChan, allOfLabel, allOfValue, errorChan, idx)
}
if anyOfValue != nil {
go buildSchema(anyOfChan, anyOfLabel, anyOfValue, errorChan, idx)
}
if oneOfValue != nil {
go buildSchema(oneOfChan, oneOfLabel, oneOfValue, errorChan, idx)
}
if itemsValue != nil {
go buildSchema(itemsChan, itemsLabel, itemsValue, errorChan, idx)
}
if notValue != nil {
go buildSchema(notChan, notLabel, notValue, errorChan, idx)
}
completeCount := 0
for completeCount < totalBuilds {
select {
case e := <-errorChan:
return e
case r := <-allOfChan:
completeCount++
allOf = append(allOf, r.v)
case r := <-anyOfChan:
completeCount++
anyOf = append(anyOf, r.v)
case r := <-oneOfChan:
completeCount++
oneOf = append(oneOf, r.v)
case r := <-itemsChan:
completeCount++
items = append(items, r.v)
case r := <-notChan:
completeCount++
not = append(not, r.v)
}
}
if len(anyOf) > 0 {
s.AnyOf = low.NodeReference[[]low.NodeReference[*Schema]]{
s.AnyOf = low.NodeReference[[]low.ValueReference[*SchemaProxy]]{
Value: anyOf,
KeyNode: anyOfLabel,
ValueNode: anyOfValue,
}
}
if len(oneOf) > 0 {
s.OneOf = low.NodeReference[[]low.NodeReference[*Schema]]{
s.OneOf = low.NodeReference[[]low.ValueReference[*SchemaProxy]]{
Value: oneOf,
KeyNode: oneOfLabel,
ValueNode: oneOfValue,
}
}
if len(allOf) > 0 {
s.AllOf = low.NodeReference[[]low.NodeReference[*Schema]]{
s.AllOf = low.NodeReference[[]low.ValueReference[*SchemaProxy]]{
Value: allOf,
KeyNode: allOfLabel,
ValueNode: allOfValue,
}
}
if len(not) > 0 {
s.Not = low.NodeReference[[]low.NodeReference[*Schema]]{
s.Not = low.NodeReference[[]low.ValueReference[*SchemaProxy]]{
Value: not,
KeyNode: notLabel,
ValueNode: notValue,
@@ -266,74 +285,74 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er
}
if len(items) > 0 {
s.Items = low.NodeReference[[]low.NodeReference[*Schema]]{
s.Items = low.NodeReference[[]low.ValueReference[*SchemaProxy]]{
Value: items,
KeyNode: itemsLabel,
ValueNode: itemsValue,
}
}
return nil
}
func countSubSchemaItems(node *yaml.Node) int {
if utils.IsNodeMap(node) {
return 1
}
if utils.IsNodeArray(node) {
return len(node.Content)
}
return 0
}
type schemaBuildResult struct {
k low.KeyReference[string]
v low.ValueReference[*Schema]
}
type schemaProxyBuildResult struct {
k low.KeyReference[string]
v low.ValueReference[*SchemaProxy]
}
func (s *Schema) extractExtensions(root *yaml.Node) {
s.Extensions = low.ExtractExtensions(root)
}
func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNode *yaml.Node, level int,
errors *[]error, idx *index.SpecIndex) (labelNode *yaml.Node, valueNode *yaml.Node) {
_, labelNode, valueNode = utils.FindKeyNodeFull(attribute, rootNode.Content)
//wg.Add(1)
if valueNode == nil {
return nil, nil
}
func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml.Node, errors chan error, idx *index.SpecIndex) {
if valueNode != nil {
syncChan := make(chan *low.ValueReference[*SchemaProxy])
errorChan := make(chan error)
build := func(kn *yaml.Node, vn *yaml.Node) *low.NodeReference[*Schema] {
schema := new(Schema)
// build out a SchemaProxy for every sub-schema.
build := func(kn *yaml.Node, vn *yaml.Node, c chan *low.ValueReference[*SchemaProxy], e chan error) {
if h, _, _ := utils.IsNodeRefValue(vn); h {
ref := low.LocateRefNode(vn, idx)
if ref != nil {
vn = ref
} else {
*errors = append(*errors, fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
vn.Content[1].Value, vn.Content[1].Line, vn.Content[1].Column))
return nil
err := fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
vn.Content[1].Value, vn.Content[1].Line, vn.Content[1].Column)
e <- err
}
}
seen := getSeenSchema(fmt.Sprintf("%d:%d", vn.Line, vn.Column))
if seen != nil {
return &low.NodeReference[*Schema]{
Value: seen,
KeyNode: kn,
ValueNode: vn,
}
}
// a proxy design works best here. polymorphism, pretty much guarantees that a sub-schema can
// take on circular references through polymorphism. Like the resolver, if we try and follow these
// journey's through hyperspace, we will end up creating endless amounts of threads, spinning off
// chasing down circles, that in turn spin up endless threads.
// In order to combat this, we need a schema proxy that will only resolve the schema when asked, and then
// it will only do it one level at a time.
sp := new(SchemaProxy)
sp.kn = kn
sp.vn = vn
sp.idx = idx
_ = 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)
if err != nil {
*errors = append(*errors, err)
return nil
}
return &low.NodeReference[*Schema]{
Value: schema,
KeyNode: kn,
res := &low.ValueReference[*SchemaProxy]{
Value: sp,
ValueNode: vn,
}
c <- res
}
if utils.IsNodeMap(valueNode) {
@@ -342,42 +361,66 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo
if ref != nil {
valueNode = ref
} else {
*errors = append(*errors, fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
valueNode.Content[1].Value, valueNode.Content[1].Line, valueNode.Content[1].Column))
errors <- fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
valueNode.Content[1].Value, valueNode.Content[1].Line, valueNode.Content[1].Column)
return
}
}
schema := build(labelNode, valueNode)
if schema != nil {
*schemas = append(*schemas, *schema)
// this only runs once, however to keep things consistent, it makes sense to use the same async method
// that arrays will use.
go build(labelNode, valueNode, syncChan, errorChan)
select {
case e := <-errorChan:
errors <- e
break
case r := <-syncChan:
schemas <- schemaProxyBuildResult{
k: low.KeyReference[string]{
KeyNode: labelNode,
Value: labelNode.Value,
},
v: *r,
}
}
}
if utils.IsNodeArray(valueNode) {
//fmt.Println("polymorphic looping sucks dude.")
refBuilds := 0
for _, vn := range valueNode.Content {
if h, _, _ := utils.IsNodeRefValue(vn); h {
ref := low.LocateRefNode(vn, idx)
if ref != nil {
vn = ref
} else {
*errors = append(*errors, fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
vn.Content[1].Value, vn.Content[1].Line, vn.Content[1].Column))
err := fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
vn.Content[1].Value, vn.Content[1].Line, vn.Content[1].Column)
errors <- err
return
}
}
schema := build(vn, vn)
if schema != nil {
*schemas = append(*schemas, *schema)
refBuilds++
go build(vn, vn, syncChan, errorChan)
}
completedBuilds := 0
for completedBuilds < refBuilds {
select {
case res := <-syncChan:
completedBuilds++
schemas <- schemaProxyBuildResult{
k: low.KeyReference[string]{
KeyNode: labelNode,
Value: labelNode.Value,
},
v: *res,
}
}
}
}
}
return labelNode, valueNode
}
func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*Schema], error) {
func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*SchemaProxy], error) {
var schLabel, schNode *yaml.Node
errStr := "schema build failed: reference '%s' cannot be found at line %d, col %d"
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
@@ -407,20 +450,8 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S
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
_ = low.BuildModel(schNode, &schema)
err := schema.Build(schNode, idx)
addSeenSchema(fmt.Sprintf("%d:%d", schNode.Line, schNode.Column), &schema)
if err != nil {
return nil, err
}
return &low.NodeReference[*Schema]{Value: &schema, KeyNode: schLabel, ValueNode: schNode}, nil
schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: idx}
return &low.NodeReference[*SchemaProxy]{Value: schema, KeyNode: schLabel, ValueNode: schNode}, nil
}
return nil, nil
}

View File

@@ -0,0 +1,48 @@
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
)
type SchemaProxy struct {
kn *yaml.Node
vn *yaml.Node
idx *index.SpecIndex
rendered *Schema
buildError error
}
func (sp *SchemaProxy) Build(root *yaml.Node, idx *index.SpecIndex) error {
sp.vn = root
sp.idx = idx
return nil
}
func (sp *SchemaProxy) Schema() *Schema {
if sp.rendered != nil {
return sp.rendered
}
schema := new(Schema)
_ = low.BuildModel(sp.vn, schema)
err := schema.Build(sp.vn, sp.idx)
if err != nil {
low.Log.Error("unable to build schema",
zap.Int("line", sp.vn.Line),
zap.Int("column", sp.vn.Column),
zap.String("error", err.Error()))
sp.buildError = err
return nil
}
sp.rendered = schema
return schema
}
func (sp *SchemaProxy) GetBuildError() error {
return sp.buildError
}

View File

@@ -121,94 +121,98 @@ additionalProperties: true `
assert.Len(t, sch.Properties.Value, 2)
v := sch.FindProperty("somethingB")
assert.Equal(t, "https://pb33f.io", v.Value.ExternalDocs.Value.URL.Value)
assert.Equal(t, "the best docs", v.Value.ExternalDocs.Value.Description.Value)
assert.Equal(t, "https://pb33f.io", v.Value.Schema().ExternalDocs.Value.URL.Value)
assert.Equal(t, "the best docs", v.Value.Schema().ExternalDocs.Value.Description.Value)
j := v.Value.FindProperty("somethingBProp")
assert.NotNil(t, j.Value)
assert.NotNil(t, j.Value.XML.Value)
assert.Equal(t, "an xml thing", j.Value.XML.Value.Name.Value)
assert.Equal(t, "an xml namespace", j.Value.XML.Value.Namespace.Value)
assert.Equal(t, "a prefix", j.Value.XML.Value.Prefix.Value)
assert.Equal(t, true, j.Value.XML.Value.Attribute.Value)
assert.Len(t, j.Value.XML.Value.Extensions, 1)
j := v.Value.Schema().FindProperty("somethingBProp").Value.Schema()
assert.NotNil(t, j)
assert.NotNil(t, j.XML.Value)
assert.Equal(t, "an xml thing", j.XML.Value.Name.Value)
assert.Equal(t, "an xml namespace", j.XML.Value.Namespace.Value)
assert.Equal(t, "a prefix", j.XML.Value.Prefix.Value)
assert.Equal(t, true, j.XML.Value.Attribute.Value)
assert.Len(t, j.XML.Value.Extensions, 1)
assert.NotNil(t, v.Value.AdditionalProperties.Value)
assert.NotNil(t, v.Value.Schema().AdditionalProperties.Value)
var addProps map[string]interface{}
v.Value.AdditionalProperties.ValueNode.Decode(&addProps)
v.Value.Schema().AdditionalProperties.ValueNode.Decode(&addProps)
assert.Equal(t, "yes", addProps["why"])
assert.Equal(t, true, addProps["thatIs"])
// check polymorphic values allOf
assert.Equal(t, "an allof thing", sch.AllOf.Value[0].Value.Description.Value)
assert.Len(t, sch.AllOf.Value[0].Value.Properties.Value, 2)
f := sch.AllOf.Value[0].Value.Schema()
assert.Equal(t, "an allof thing", f.Description.Value)
assert.Len(t, f.Properties.Value, 2)
v = sch.AllOf.Value[0].Value.FindProperty("allOfA")
v = f.FindProperty("allOfA")
assert.NotNil(t, v)
assert.Equal(t, "allOfA description", v.Value.Description.Value)
assert.Equal(t, "allOfAExp", v.Value.Example.Value)
v = sch.AllOf.Value[0].Value.FindProperty("allOfB")
io := v.Value.Schema()
assert.Equal(t, "allOfA description", io.Description.Value)
assert.Equal(t, "allOfAExp", io.Example.Value)
qw := f.FindProperty("allOfB").Value.Schema()
assert.NotNil(t, v)
assert.Equal(t, "allOfB description", v.Value.Description.Value)
assert.Equal(t, "allOfBExp", v.Value.Example.Value)
assert.Equal(t, "allOfB description", qw.Description.Value)
assert.Equal(t, "allOfBExp", qw.Example.Value)
// check polymorphic values anyOf
assert.Equal(t, "an anyOf thing", sch.AnyOf.Value[0].Value.Description.Value)
assert.Len(t, sch.AnyOf.Value[0].Value.Properties.Value, 2)
assert.Equal(t, "an anyOf thing", sch.AnyOf.Value[0].Value.Schema().Description.Value)
assert.Len(t, sch.AnyOf.Value[0].Value.Schema().Properties.Value, 2)
v = sch.AnyOf.Value[0].Value.FindProperty("anyOfA")
v = sch.AnyOf.Value[0].Value.Schema().FindProperty("anyOfA")
assert.NotNil(t, v)
assert.Equal(t, "anyOfA description", v.Value.Description.Value)
assert.Equal(t, "anyOfAExp", v.Value.Example.Value)
assert.Equal(t, "anyOfA description", v.Value.Schema().Description.Value)
assert.Equal(t, "anyOfAExp", v.Value.Schema().Example.Value)
v = sch.AnyOf.Value[0].Value.FindProperty("anyOfB")
v = sch.AnyOf.Value[0].Value.Schema().FindProperty("anyOfB")
assert.NotNil(t, v)
assert.Equal(t, "anyOfB description", v.Value.Description.Value)
assert.Equal(t, "anyOfBExp", v.Value.Example.Value)
assert.Equal(t, "anyOfB description", v.Value.Schema().Description.Value)
assert.Equal(t, "anyOfBExp", v.Value.Schema().Example.Value)
// check polymorphic values oneOf
assert.Equal(t, "a oneof thing", sch.OneOf.Value[0].Value.Description.Value)
assert.Len(t, sch.OneOf.Value[0].Value.Properties.Value, 2)
assert.Equal(t, "a oneof thing", sch.OneOf.Value[0].Value.Schema().Description.Value)
assert.Len(t, sch.OneOf.Value[0].Value.Schema().Properties.Value, 2)
v = sch.OneOf.Value[0].Value.FindProperty("oneOfA")
v = sch.OneOf.Value[0].Value.Schema().FindProperty("oneOfA")
assert.NotNil(t, v)
assert.Equal(t, "oneOfA description", v.Value.Description.Value)
assert.Equal(t, "oneOfAExp", v.Value.Example.Value)
assert.Equal(t, "oneOfA description", v.Value.Schema().Description.Value)
assert.Equal(t, "oneOfAExp", v.Value.Schema().Example.Value)
v = sch.OneOf.Value[0].Value.FindProperty("oneOfB")
v = sch.OneOf.Value[0].Value.Schema().FindProperty("oneOfB")
assert.NotNil(t, v)
assert.Equal(t, "oneOfB description", v.Value.Description.Value)
assert.Equal(t, "oneOfBExp", v.Value.Example.Value)
assert.Equal(t, "oneOfB description", v.Value.Schema().Description.Value)
assert.Equal(t, "oneOfBExp", v.Value.Schema().Example.Value)
// check values NOT
assert.Equal(t, "a not thing", sch.Not.Value[0].Value.Description.Value)
assert.Len(t, sch.Not.Value[0].Value.Properties.Value, 2)
assert.Equal(t, "a not thing", sch.Not.Value[0].Value.Schema().Description.Value)
assert.Len(t, sch.Not.Value[0].Value.Schema().Properties.Value, 2)
v = sch.Not.Value[0].Value.FindProperty("notA")
v = sch.Not.Value[0].Value.Schema().FindProperty("notA")
assert.NotNil(t, v)
assert.Equal(t, "notA description", v.Value.Description.Value)
assert.Equal(t, "notAExp", v.Value.Example.Value)
assert.Equal(t, "notA description", v.Value.Schema().Description.Value)
assert.Equal(t, "notAExp", v.Value.Schema().Example.Value)
v = sch.Not.Value[0].Value.FindProperty("notB")
v = sch.Not.Value[0].Value.Schema().FindProperty("notB")
assert.NotNil(t, v)
assert.Equal(t, "notB description", v.Value.Description.Value)
assert.Equal(t, "notBExp", v.Value.Example.Value)
assert.Equal(t, "notB description", v.Value.Schema().Description.Value)
assert.Equal(t, "notBExp", v.Value.Schema().Example.Value)
// check values Items
assert.Equal(t, "an items thing", sch.Items.Value[0].Value.Description.Value)
assert.Len(t, sch.Items.Value[0].Value.Properties.Value, 2)
assert.Equal(t, "an items thing", sch.Items.Value[0].Value.Schema().Description.Value)
assert.Len(t, sch.Items.Value[0].Value.Schema().Properties.Value, 2)
v = sch.Items.Value[0].Value.FindProperty("itemsA")
v = sch.Items.Value[0].Value.Schema().FindProperty("itemsA")
assert.NotNil(t, v)
assert.Equal(t, "itemsA description", v.Value.Description.Value)
assert.Equal(t, "itemsAExp", v.Value.Example.Value)
assert.Equal(t, "itemsA description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsAExp", v.Value.Schema().Example.Value)
v = sch.Items.Value[0].Value.FindProperty("itemsB")
v = sch.Items.Value[0].Value.Schema().FindProperty("itemsB")
assert.NotNil(t, v)
assert.Equal(t, "itemsB description", v.Value.Description.Value)
assert.Equal(t, "itemsBExp", v.Value.Example.Value)
assert.Equal(t, "itemsB description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsBExp", v.Value.Schema().Example.Value)
// check discriminator
assert.NotNil(t, sch.Discriminator.Value)
@@ -220,123 +224,123 @@ additionalProperties: true `
assert.Equal(t, "party", mv.Value)
}
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
// 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
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object
properties:
aValue:
type: object`
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
idx := index.NewSpecIndex(&idxNode)
var n Schema
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
assert.Error(t, err)
}
//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
// // 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
//properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object
// properties:
// aValue:
// type: object`
//
// var idxNode yaml.Node
// _ = yaml.Unmarshal([]byte(yml), &idxNode)
// idx := index.NewSpecIndex(&idxNode)
//
// var n Schema
// err := low.BuildModel(&idxNode, &n)
// assert.NoError(t, err)
//
// err = n.Build(idxNode.Content[0], idx)
// assert.Error(t, err)
//
//}
func TestSchema_Build_ErrorAdditionalProps(t *testing.T) {
clearSchemas()
@@ -380,7 +384,7 @@ properties:
var n Schema
err := n.Build(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "this is something", n.FindProperty("aValue").Value.Description.Value)
assert.Equal(t, "this is something", n.FindProperty("aValue").Value.Schema().Description.Value)
}
@@ -452,11 +456,11 @@ items:
assert.NoError(t, schErr)
desc := "poly thing"
assert.Equal(t, desc, sch.OneOf.Value[0].Value.Description.Value)
assert.Equal(t, desc, sch.AnyOf.Value[0].Value.Description.Value)
assert.Equal(t, desc, sch.AllOf.Value[0].Value.Description.Value)
assert.Equal(t, desc, sch.Not.Value[0].Value.Description.Value)
assert.Equal(t, desc, sch.Items.Value[0].Value.Description.Value)
assert.Equal(t, desc, sch.OneOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.AnyOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.AllOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.Not.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.Items.Value[0].Value.Schema().Description.Value)
}
func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) {
@@ -542,11 +546,11 @@ items:
assert.NoError(t, schErr)
desc := "poly thing"
assert.Equal(t, desc, sch.OneOf.Value[0].Value.Description.Value)
assert.Equal(t, desc, sch.AnyOf.Value[0].Value.Description.Value)
assert.Equal(t, desc, sch.AllOf.Value[0].Value.Description.Value)
assert.Equal(t, desc, sch.Not.Value[0].Value.Description.Value)
assert.Equal(t, desc, sch.Items.Value[0].Value.Description.Value)
assert.Equal(t, desc, sch.OneOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.AnyOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.AllOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.Not.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.Items.Value[0].Value.Schema().Description.Value)
}
func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) {
@@ -649,6 +653,39 @@ allOf:
}
func Test_Schema_Polymorphism_BorkChild_Array(t *testing.T) {
clearSchemas()
yml := `components:
schemas:
Something:
$ref: #borko`
var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode)
yml = `type: object
allOf:
- type: object
allOf:
- $ref: #bork'`
var sch Schema
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
assert.NoError(t, schErr)
assert.Nil(t, sch.AllOf.Value[0].Value.Schema()) // child can't be resolved, so this will be nil.
assert.Error(t, sch.AllOf.Value[0].Value.GetBuildError())
}
func Test_Schema_Polymorphism_RefMadness(t *testing.T) {
clearSchemas()
@@ -679,7 +716,7 @@ allOf:
assert.NoError(t, schErr)
desc := "madness"
assert.Equal(t, desc, sch.AllOf.Value[0].Value.Description.Value)
assert.Equal(t, desc, sch.AllOf.Value[0].Value.Schema().Description.Value)
}
@@ -709,8 +746,8 @@ allOf:
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
schErr := sch.Build(idxNode.Content[0], idx)
assert.Error(t, schErr)
_ = sch.Build(idxNode.Content[0], idx)
assert.Nil(t, sch.AllOf.Value[0].Value.Schema())
}
@@ -771,8 +808,8 @@ func TestExtractSchema(t *testing.T) {
res, err := ExtractSchema(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.NotNil(t, res.Value)
aValue := res.Value.FindProperty("aValue")
assert.Equal(t, "this is something", aValue.Value.Description.Value)
aValue := res.Value.Schema().FindProperty("aValue")
assert.Equal(t, "this is something", aValue.Value.Schema().Description.Value)
}
func TestExtractSchema_Ref(t *testing.T) {
@@ -798,7 +835,7 @@ func TestExtractSchema_Ref(t *testing.T) {
res, err := ExtractSchema(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.NotNil(t, res.Value)
assert.Equal(t, "this is something", res.Value.Description.Value)
assert.Equal(t, "this is something", res.Value.Schema().Description.Value)
}
func TestExtractSchema_Ref_Fail(t *testing.T) {
@@ -847,7 +884,7 @@ func TestExtractSchema_RefRoot(t *testing.T) {
res, err := ExtractSchema(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.NotNil(t, res.Value)
assert.Equal(t, "this is something", res.Value.Description.Value)
assert.Equal(t, "this is something", res.Value.Schema().Description.Value)
}
func TestExtractSchema_RefRoot_Fail(t *testing.T) {
@@ -892,8 +929,11 @@ func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
_, err := ExtractSchema(idxNode.Content[0], idx)
assert.Error(t, err)
s, _ := ExtractSchema(idxNode.Content[0], idx)
b := s.Value.Schema()
assert.Nil(t, b)
assert.Error(t, s.Value.GetBuildError())
}
@@ -1036,6 +1076,7 @@ func TestExtractSchema_OneOfRef(t *testing.T) {
res, err := ExtractSchema(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "a frosty cold beverage can be coke or sprite", res.Value.OneOf.Value[0].Value.Description.Value)
assert.Equal(t, "a frosty cold beverage can be coke or sprite",
res.Value.Schema().OneOf.Value[0].Value.Schema().Description.Value)
}

View File

@@ -137,7 +137,7 @@ func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *in
vn = ref
} else {
return NodeReference[T]{}, fmt.Errorf("object build failed: reference cannot be found: %s",
root.Content[1].Value)
vn.Content[1].Value)
}
}
}

12
datamodel/low/log.go Normal file
View File

@@ -0,0 +1,12 @@
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package low
import "go.uber.org/zap"
var Log *zap.Logger
func init() {
Log, _ = zap.NewProduction()
}

3
go.mod
View File

@@ -13,4 +13,7 @@ require (
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/iancoleman/strcase v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect
)

15
go.sum
View File

@@ -7,6 +7,10 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
github.com/francoispqt/gojay v0.0.0-20181220093123-f2cc13a668ca h1:F2BD6Vhei4w0rtm4eNpzylNsB07CcCbpYA+xlqMx3mA=
github.com/francoispqt/gojay v0.0.0-20181220093123-f2cc13a668ca/go.mod h1:H8Wgri1Asi1VevY3ySdpIK5+KCpqzToVswNq8g2xZj4=
github.com/francoispqt/onelog v0.0.0-20190306043706-8c2bb31b10a4 h1:N9eG+1y9e3tnNPXKjssLMa8MumIBDWWoJQWM7htGWUc=
github.com/francoispqt/onelog v0.0.0-20190306043706-8c2bb31b10a4/go.mod h1:v1Il1fkBpjiYPpEJcGxqgrPUPcHuTC7eHh9zBV3CLBE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -57,14 +61,24 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=
github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -131,5 +145,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=