Building out spec model

this one will take a few tests, there is significant recursion and threading going on.
This commit is contained in:
Dave Shanley
2022-08-04 10:01:59 -04:00
parent a4012594de
commit e6518af576
5 changed files with 209 additions and 49 deletions

View File

@@ -72,7 +72,7 @@ func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error {
KeyNode: paramLabel,
})
}
o.Parameters = params
}
return nil

View File

@@ -4,9 +4,14 @@ import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
const (
SchemaLabel = "schema"
)
type Parameter struct {
Name low.NodeReference[string]
In low.NodeReference[string]
@@ -32,7 +37,27 @@ func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error {
}
p.Extensions = extensionMap
// deal with schema
// handle schema
_, schLabel, schNode := utils.FindKeyNodeFull(SchemaLabel, root.Content)
if schNode != nil {
// deal with schema flat props
var schema Schema
err = datamodel.BuildModel(schNode, &schema)
if err != nil {
return err
}
// now comes the part where things may get hairy, schemas are recursive.
// which means we could be here forever if our resolver has some unknown bug in it.
// in order to prevent this from happening, we will add a counter that tracks the depth
// and will hard stop once we reach 50 levels. That's too deep for any data structure IMHO.
err = schema.Build(schNode, idx, 0)
if err != nil {
return err
}
p.Schema = low.NodeReference[*Schema]{Value: &schema, KeyNode: schLabel, ValueNode: schNode}
}
return nil
}

View File

@@ -1,12 +1,25 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sync"
)
const (
PropertiesLabel = "properties"
ItemsLabel = "items"
AllOfLabel = "allOf"
AnyOfLabel = "anyOf"
OneOfLabel = "oneOf"
NotLabel = "not"
DiscriminatorLabel = "discriminator"
)
type Schema struct {
Node *yaml.Node
Title low.NodeReference[string]
MultipleOf low.NodeReference[int]
Maximum low.NodeReference[int]
@@ -24,22 +37,128 @@ type Schema struct {
Required []low.NodeReference[string]
Enum []low.NodeReference[string]
Type low.NodeReference[string]
AllOf *Schema
OneOf *Schema
AnyOf *Schema
Not *Schema
Items *Schema
Properties map[string]*Schema
AdditionalProperties low.ObjectReference
AllOf *low.NodeReference[*Schema]
OneOf *low.NodeReference[*Schema]
AnyOf *low.NodeReference[*Schema]
Not *low.NodeReference[*Schema]
Items *low.NodeReference[*Schema]
Properties map[low.NodeReference[string]]*low.NodeReference[*Schema]
AdditionalProperties low.NodeReference[any]
Description low.NodeReference[string]
Default low.ObjectReference
Default low.NodeReference[any]
Nullable low.NodeReference[bool]
Discriminator Discriminator
Discriminator *low.NodeReference[*Discriminator]
ReadOnly low.NodeReference[bool]
WriteOnly low.NodeReference[bool]
XML XML
ExternalDocs ExternalDoc
Example low.ObjectReference
XML *low.NodeReference[*XML]
ExternalDocs *low.NodeReference[*ExternalDoc]
Example low.NodeReference[any]
Deprecated low.NodeReference[bool]
Extensions map[string]low.ObjectReference
Extensions map[low.NodeReference[string]]low.NodeReference[any]
}
func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex, level int) error {
level++
if level > 50 {
return nil // we done, son! too fricken deep.
}
extensionMap, err := datamodel.ExtractExtensions(root)
if err != nil {
return err
}
s.Extensions = extensionMap
// handle properties
_, propLabel, propsNode := utils.FindKeyNodeFull(PropertiesLabel, root.Content)
if propsNode != nil {
propertyMap := make(map[low.NodeReference[string]]*low.NodeReference[*Schema])
var currentProp *yaml.Node
for i, prop := range propsNode.Content {
if i%2 == 0 {
currentProp = prop
continue
}
var property Schema
err = datamodel.BuildModel(prop, &property)
if err != nil {
return err
}
err = property.Build(prop, idx, level)
if err != nil {
return err
}
propertyMap[low.NodeReference[string]{
Value: currentProp.Value,
KeyNode: propLabel,
ValueNode: propsNode,
}] = &low.NodeReference[*Schema]{
Value: &property,
KeyNode: currentProp,
ValueNode: prop,
}
}
s.Properties = propertyMap
// extract all sub-schemas
var errors []error
var wg sync.WaitGroup
var allOf, anyOf, oneOf, not, items low.NodeReference[*Schema]
allOf = low.NodeReference[*Schema]{Value: &Schema{}}
anyOf = low.NodeReference[*Schema]{Value: &Schema{}}
oneOf = low.NodeReference[*Schema]{Value: &Schema{}}
not = low.NodeReference[*Schema]{Value: &Schema{}}
items = low.NodeReference[*Schema]{Value: &Schema{}}
go buildSchema(&allOf, AllOfLabel, idx, root, level, &errors, &wg)
go buildSchema(&anyOf, AnyOfLabel, idx, root, level, &errors, &wg)
go buildSchema(&oneOf, OneOfLabel, idx, root, level, &errors, &wg)
go buildSchema(&not, NotLabel, idx, root, level, &errors, &wg)
go buildSchema(&items, ItemsLabel, idx, root, level, &errors, &wg)
wg.Wait()
if len(errors) > 0 {
// todo fix this
return errors[0]
}
if anyOf.KeyNode != nil {
s.AnyOf = &anyOf
}
if oneOf.KeyNode != nil {
s.OneOf = &oneOf
}
if allOf.KeyNode != nil {
s.AllOf = &allOf
}
if not.KeyNode != nil {
s.Not = &not
}
if items.KeyNode != nil {
s.Items = &items
}
}
return nil
}
func buildSchema(schema *low.NodeReference[*Schema], attribute string, idx *index.SpecIndex, rootNode *yaml.Node, level int, errors *[]error, wg *sync.WaitGroup) {
_, labelNode, valueNode := utils.FindKeyNodeFull(attribute, rootNode.Content)
if valueNode != nil {
wg.Add(1)
err := datamodel.BuildModel(valueNode, &schema.Value)
if err != nil {
*errors = append(*errors, err)
return
}
err = schema.Value.Build(rootNode, idx, level)
if err != nil {
*errors = append(*errors, err)
return
}
schema.KeyNode = labelNode
schema.ValueNode = valueNode
wg.Done()
}
}

View File

@@ -1,7 +1,6 @@
package openapi
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/3.0"
@@ -52,7 +51,6 @@ func CreateDocument(info *datamodel.SpecInfo) (*v3.Document, error) {
if len(errors) > 0 {
return &doc, errors[0]
}
fmt.Sprint(idx)
return &doc, nil
}

View File

@@ -141,13 +141,8 @@ paths:
summary: Search a burger by ID - returns the burger with that identifier
description: Look up a tasty burger take it and enjoy it
parameters:
- in: path
name: burgerId
schema:
type: string
example: big-mac
description: the name of the burger. use this to order your food
required: true
- $ref: '#/components/parameters/BurgerId'
- $ref: '#/components/parameters/BurgerHeader'
responses:
"200":
description: A tasty burger for you to eat. Wide variety of products to choose from
@@ -311,6 +306,29 @@ paths:
example:
message: "failed looking up all dressings, something went wrong."
components:
parameters:
BurgerHeader:
in: header
name: burgerHeader
schema:
properties:
burgerTheme:
type: string
description: something about a theme?
burgerTime:
type: number
description: number of burgers ordered this year.
example: big-mac
description: the name of the burger. use this to order your food
required: true
BurgerId:
in: path
name: burgerId
schema:
type: string
example: big-mac
description: the name of the burger. use this to order your food
required: true
schemas:
Error:
type: object