Working in generics now to reduce code.

generics are a little funky still. sigh.
This commit is contained in:
Dave Shanley
2022-08-05 21:57:04 -04:00
parent 4bb6a9ad49
commit 13781cbbde
19 changed files with 615 additions and 268 deletions

View File

@@ -1,12 +1,12 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
)
type Callback struct {
Node *yaml.Node
Expression map[string]Path
Extensions map[string]low.ObjectReference
Node *yaml.Node
Expression map[string]PathItem
Extensions map[string]low.ObjectReference
}

View File

@@ -1,15 +1,31 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
)
const (
EncodingLabel = "encoding"
)
type Encoding struct {
Node *yaml.Node
ContentType low.NodeReference[string]
Headers map[string]Parameter
Style low.NodeReference[string]
Explode low.NodeReference[bool]
AllowReserved low.NodeReference[bool]
ContentType low.NodeReference[string]
Headers map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[*Header]
Style low.NodeReference[string]
Explode low.NodeReference[bool]
AllowReserved low.NodeReference[bool]
}
func (en Encoding) Build(root *yaml.Node) error {
headers, err := ExtractMap[*Header](HeadersLabel, root)
if err != nil {
return err
}
if headers != nil {
en.Headers = headers
}
return nil
}

View File

@@ -1,15 +1,29 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
)
const (
ExamplesLabel = "examples"
ExampleLabel = "example"
)
type Example struct {
Node *yaml.Node
Summary low.NodeReference[string]
Description low.NodeReference[string]
Value low.ObjectReference
ExternalValue low.NodeReference[string]
Extensions map[string]low.ObjectReference
Summary low.NodeReference[string]
Description low.NodeReference[string]
Value low.NodeReference[any]
ExternalValue low.NodeReference[string]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (ex Example) Build(root *yaml.Node) error {
// extract extensions
extensionMap, err := ExtractExtensions(root)
if err != nil {
return err
}
ex.Extensions = extensionMap
return nil
}

View File

@@ -2,10 +2,21 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
)
type ExternalDoc struct {
Description low.NodeReference[string]
URL low.NodeReference[string]
Extensions map[string]low.ObjectReference
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (ex *ExternalDoc) Build(root *yaml.Node) error {
// extract extensions
extensionMap, err := ExtractExtensions(root)
if err != nil {
return err
}
ex.Extensions = extensionMap
return nil
}

View File

@@ -0,0 +1,123 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"strconv"
"sync"
)
func ExtractSchema(root *yaml.Node) (*low.NodeReference[*Schema], error) {
_, schLabel, schNode := utils.FindKeyNodeFull(SchemaLabel, root.Content)
if schNode != nil {
var schema Schema
err := BuildModel(schNode, &schema)
if err != nil {
return nil, err
}
err = schema.Build(schNode, 0)
if err != nil {
return nil, err
}
return &low.NodeReference[*Schema]{Value: &schema, KeyNode: schLabel, ValueNode: schNode}, nil
}
return nil, nil
}
var mapLock sync.Mutex
func ExtractMap[PT low.Buildable[N], N any](label string, root *yaml.Node) (map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[PT], error) {
_, labelNode, valueNode := utils.FindKeyNodeFull(label, root.Content)
if valueNode != nil {
var currentLabelNode *yaml.Node
valueMap := make(map[low.KeyReference[string]]low.ValueReference[PT])
for i, en := range valueNode.Content {
if i%2 == 0 {
currentLabelNode = en
continue
}
var n PT = new(N)
err := BuildModel(valueNode, n)
if err != nil {
return nil, err
}
berr := n.Build(valueNode)
if berr != nil {
return nil, berr
}
valueMap[low.KeyReference[string]{
Value: currentLabelNode.Value,
KeyNode: currentLabelNode,
}] = low.ValueReference[PT]{
Value: n,
ValueNode: en,
}
}
resMap := make(map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[PT])
resMap[low.KeyReference[string]{
Value: labelNode.Value,
KeyNode: labelNode,
}] = valueMap
return resMap, nil
}
return nil, nil
}
func ExtractExtensions(root *yaml.Node) (map[low.KeyReference[string]]low.ValueReference[any], error) {
extensions := utils.FindExtensionNodes(root.Content)
extensionMap := make(map[low.KeyReference[string]]low.ValueReference[any])
for _, ext := range extensions {
if utils.IsNodeMap(ext.Value) {
var v interface{}
err := ext.Value.Decode(&v)
if err != nil {
return nil, err
}
extensionMap[low.KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.ValueReference[any]{Value: v, ValueNode: ext.Value}
}
if utils.IsNodeStringValue(ext.Value) {
extensionMap[low.KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.ValueReference[any]{Value: ext.Value.Value, ValueNode: ext.Value}
}
if utils.IsNodeFloatValue(ext.Value) {
fv, _ := strconv.ParseFloat(ext.Value.Value, 64)
extensionMap[low.KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.ValueReference[any]{Value: fv, ValueNode: ext.Value}
}
if utils.IsNodeIntValue(ext.Value) {
iv, _ := strconv.ParseInt(ext.Value.Value, 10, 64)
extensionMap[low.KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.ValueReference[any]{Value: iv, ValueNode: ext.Value}
}
if utils.IsNodeBoolValue(ext.Value) {
bv, _ := strconv.ParseBool(ext.Value.Value)
extensionMap[low.KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.ValueReference[any]{Value: bv, ValueNode: ext.Value}
}
if utils.IsNodeArray(ext.Value) {
var v []interface{}
err := ext.Value.Decode(&v)
if err != nil {
return nil, err
}
extensionMap[low.KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.ValueReference[any]{Value: v, ValueNode: ext.Value}
}
}
return extensionMap, nil
}

View File

@@ -0,0 +1,61 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
)
const (
HeadersLabel = "headers"
)
type Header struct {
Description low.NodeReference[string]
Required low.NodeReference[bool]
Deprecated low.NodeReference[bool]
AllowEmptyValue low.NodeReference[bool]
Style low.NodeReference[string]
Explode low.NodeReference[bool]
AllowReserved low.NodeReference[bool]
Schema low.NodeReference[*Schema]
Example low.NodeReference[any]
Examples map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[*Example]
Content map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[*MediaType]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (h *Header) Build(root *yaml.Node) error {
// extract extensions
extensionMap, err := ExtractExtensions(root)
if err != nil {
return err
}
h.Extensions = extensionMap
// handle examples if set.
exps, eErr := ExtractMap[*Example](ExamplesLabel, root)
if eErr != nil {
return eErr
}
if exps != nil {
h.Examples = exps
}
// handle schema
sch, sErr := ExtractSchema(root)
if sErr != nil {
return nil
}
if sch != nil {
h.Schema = *sch
}
// handle content, if set.
con, cErr := ExtractMap[*MediaType](ContentLabel, root)
if cErr != nil {
return cErr
}
h.Content = con
return nil
}

View File

@@ -1,14 +1,59 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
type MediaType struct {
Node *yaml.Node
Schema Schema
Example low.ObjectReference
Examples map[string]Example
Encoding map[string]Encoding
Schema low.NodeReference[*Schema]
Example low.NodeReference[any]
Examples map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[*Example]
Encoding map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[*Encoding]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (mt *MediaType) Build(root *yaml.Node) error {
// extract extensions
extensionMap, err := ExtractExtensions(root)
if err != nil {
return err
}
mt.Extensions = extensionMap
// 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)
if sErr != nil {
return nil
}
if sch != nil {
mt.Schema = *sch
}
// handle examples if set.
exps, eErr := ExtractMap[*Example](ExamplesLabel, root)
if eErr != nil {
return eErr
}
if exps != nil {
mt.Examples = exps
}
// handle encoding
encs, encErr := ExtractMap[*Encoding](EncodingLabel, root)
if encErr != nil {
return err
}
if encs != nil {
mt.Encoding = encs
}
return nil
}

View File

@@ -377,60 +377,3 @@ func BuildModelAsync(n *yaml.Node, model interface{}, lwg *sync.WaitGroup, error
}
lwg.Done()
}
func ExtractExtensions(root *yaml.Node) (map[low.NodeReference[string]]low.NodeReference[any], error) {
extensions := utils.FindExtensionNodes(root.Content)
extensionMap := make(map[low.NodeReference[string]]low.NodeReference[any])
for _, ext := range extensions {
if utils.IsNodeMap(ext.Value) {
var v interface{}
err := ext.Value.Decode(&v)
if err != nil {
return nil, err
}
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: v, KeyNode: ext.Key}
}
if utils.IsNodeStringValue(ext.Value) {
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: ext.Value.Value, ValueNode: ext.Value}
}
if utils.IsNodeFloatValue(ext.Value) {
fv, _ := strconv.ParseFloat(ext.Value.Value, 64)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: fv, ValueNode: ext.Value}
}
if utils.IsNodeIntValue(ext.Value) {
iv, _ := strconv.ParseInt(ext.Value.Value, 10, 64)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: iv, ValueNode: ext.Value}
}
if utils.IsNodeBoolValue(ext.Value) {
bv, _ := strconv.ParseBool(ext.Value.Value)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: bv, ValueNode: ext.Value}
}
if utils.IsNodeArray(ext.Value) {
var v []interface{}
err := ext.Value.Decode(&v)
if err != nil {
return nil, err
}
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: v, ValueNode: ext.Value}
}
}
return extensionMap, nil
}

View File

@@ -2,7 +2,6 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
@@ -15,19 +14,19 @@ type Operation struct {
Tags []low.NodeReference[string]
Summary low.NodeReference[string]
Description low.NodeReference[string]
ExternalDocs *low.NodeReference[*ExternalDoc]
ExternalDocs low.NodeReference[*ExternalDoc]
OperationId low.NodeReference[string]
Parameters []low.NodeReference[*Parameter]
RequestBody *low.NodeReference[*RequestBody]
Responses *low.NodeReference[*Responses]
Callbacks map[low.NodeReference[string]]low.NodeReference[*Callback]
Deprecated *low.NodeReference[bool]
RequestBody low.NodeReference[*RequestBody]
Responses low.NodeReference[*Responses]
Callbacks map[low.KeyReference[string]]low.ValueReference[*Callback]
Deprecated low.NodeReference[bool]
Security []low.NodeReference[*SecurityRequirement]
Servers []low.NodeReference[*Server]
Extensions map[low.NodeReference[string]]low.NodeReference[any]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error {
func (o *Operation) Build(root *yaml.Node) error {
extensionMap, err := ExtractExtensions(root)
if err != nil {
@@ -43,7 +42,7 @@ func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error {
if err != nil {
return err
}
o.ExternalDocs = &low.NodeReference[*ExternalDoc]{
o.ExternalDocs = low.NodeReference[*ExternalDoc]{
Value: &externalDoc,
KeyNode: ln,
ValueNode: exDocs,
@@ -61,7 +60,7 @@ func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error {
if err != nil {
return err
}
err = param.Build(pN, idx)
err = param.Build(pN)
if err != nil {
return err
}

View File

@@ -2,13 +2,13 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
const (
SchemaLabel = "schema"
SchemaLabel = "schema"
ContentLabel = "content"
)
type Parameter struct {
@@ -23,11 +23,23 @@ type Parameter struct {
AllowReserved low.NodeReference[bool]
Schema low.NodeReference[*Schema]
Example low.NodeReference[any]
Examples map[low.NodeReference[string]]low.NodeReference[*Example]
Extensions map[low.NodeReference[string]]low.NodeReference[any]
Examples map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[*Example]
Content map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[*MediaType]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error {
func (p *Parameter) GetContent(cType string) *low.ValueReference[*MediaType] {
for _, c := range p.Content {
for n, o := range c {
if n.Value == cType {
return &o
}
}
}
return nil
}
func (p *Parameter) Build(root *yaml.Node) error {
// extract extensions
extensionMap, err := ExtractExtensions(root)
@@ -36,27 +48,24 @@ func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error {
}
p.Extensions = extensionMap
// handle schema
_, schLabel, schNode := utils.FindKeyNodeFull(SchemaLabel, root.Content)
if schNode != nil {
// deal with schema flat props
var schema Schema
err = 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}
// handle example if set.
_, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content)
if expNode != nil {
p.Example = low.NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode}
}
// handle schema
sch, sErr := ExtractSchema(root)
if sErr != nil {
return sErr
}
p.Schema = *sch
// handle content, if set.
con, cErr := ExtractMap[*MediaType](ContentLabel, root)
if cErr != nil {
return cErr
}
p.Content = con
return nil
}

View File

@@ -2,7 +2,6 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"gopkg.in/yaml.v3"
"strings"
"sync"
@@ -21,19 +20,20 @@ const (
)
type Paths struct {
Paths map[low.NodeReference[string]]low.NodeReference[*Path]
Extensions map[low.NodeReference[string]]low.NodeReference[any]
PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (p *Paths) GetPathMap() map[string]*Path {
pMap := make(map[string]*Path)
for i, pv := range p.Paths {
pMap[i.Value] = pv.Value
func (p *Paths) GetPath(path string) *low.ValueReference[*PathItem] {
for k, p := range p.PathItems {
if k.Value == path {
return &p
}
}
return pMap
return nil
}
func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error {
func (p *Paths) Build(root *yaml.Node) error {
// extract extensions
extensionMap, err := ExtractExtensions(root)
@@ -44,7 +44,7 @@ func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error {
skip := false
var currentNode *yaml.Node
pathsMap := make(map[low.NodeReference[string]]low.NodeReference[*Path])
pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem])
for i, pathNode := range root.Content {
if strings.HasPrefix(strings.ToLower(pathNode.Value), "x-") {
@@ -59,32 +59,32 @@ func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error {
currentNode = pathNode
continue
}
var path = Path{}
var path = PathItem{}
err = BuildModel(pathNode, &path)
if err != nil {
}
err = path.Build(pathNode, idx)
err = path.Build(pathNode)
if err != nil {
return err
}
// add bulk here
pathsMap[low.NodeReference[string]{
pathsMap[low.KeyReference[string]{
Value: currentNode.Value,
KeyNode: currentNode,
}] = low.NodeReference[*Path]{
}] = low.ValueReference[*PathItem]{
Value: &path,
ValueNode: pathNode,
}
}
p.Paths = pathsMap
p.PathItems = pathsMap
return nil
}
type Path struct {
type PathItem struct {
Description low.NodeReference[string]
Summary low.NodeReference[string]
Get *low.NodeReference[*Operation]
@@ -97,10 +97,10 @@ type Path struct {
Trace *low.NodeReference[*Operation]
Servers []*low.NodeReference[*Server]
Parameters []*low.NodeReference[*Parameter]
Extensions map[low.NodeReference[string]]low.NodeReference[any]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (p *Path) Build(root *yaml.Node, idx *index.SpecIndex) error {
func (p *PathItem) Build(root *yaml.Node) error {
extensionMap, err := ExtractExtensions(root)
if err != nil {
return err
@@ -171,7 +171,7 @@ func (p *Path) Build(root *yaml.Node, idx *index.SpecIndex) error {
var buildOpFunc = func(op low.NodeReference[*Operation], ch chan<- bool, errCh chan<- error) {
// build out the operation.
er := op.Value.Build(op.ValueNode, idx)
er := op.Value.Build(op.ValueNode)
if err != nil {
errCh <- er
}

View File

@@ -2,7 +2,6 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"strconv"
@@ -12,7 +11,7 @@ import (
const (
PropertiesLabel = "properties"
AdditionalPropertiesLabel = "additionalProperties"
ExampleLabel = "example"
XMLLabel = "xml"
ItemsLabel = "items"
AllOfLabel = "allOf"
AnyOfLabel = "anyOf"
@@ -44,7 +43,7 @@ type Schema struct {
AnyOf []low.NodeReference[*Schema]
Not []low.NodeReference[*Schema]
Items []low.NodeReference[*Schema]
Properties map[low.NodeReference[string]]*low.NodeReference[*Schema]
Properties map[low.KeyReference[string]]*low.ValueReference[*Schema]
AdditionalProperties low.NodeReference[any]
Description low.NodeReference[string]
Default low.NodeReference[any]
@@ -52,14 +51,14 @@ type Schema struct {
Discriminator low.NodeReference[*Discriminator]
ReadOnly low.NodeReference[bool]
WriteOnly low.NodeReference[bool]
XML *low.NodeReference[*XML]
ExternalDocs *low.NodeReference[*ExternalDoc]
XML low.NodeReference[*XML]
ExternalDocs low.NodeReference[*ExternalDoc]
Example low.NodeReference[any]
Deprecated low.NodeReference[bool]
Extensions map[low.NodeReference[string]]low.NodeReference[any]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (s *Schema) FindProperty(name string) *low.NodeReference[*Schema] {
func (s *Schema) FindProperty(name string) *low.ValueReference[*Schema] {
for k, v := range s.Properties {
if k.Value == name {
return v
@@ -68,17 +67,16 @@ func (s *Schema) FindProperty(name string) *low.NodeReference[*Schema] {
return nil
}
func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex, level int) error {
func (s *Schema) Build(root *yaml.Node, level int) error {
level++
if level > 50 {
return nil // we're done, son! too fricken deep.
}
extensionMap, err := ExtractExtensions(root)
err := s.extractExtensions(root)
if err != nil {
return err
}
s.Extensions = extensionMap
// handle example if set.
_, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content)
@@ -90,7 +88,10 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex, level int) error {
if addPNode != nil {
if utils.IsNodeMap(addPNode) {
var props map[string]interface{}
addPNode.Decode(&props)
err = addPNode.Decode(&props)
if err != nil {
return err
}
s.AdditionalProperties = low.NodeReference[any]{Value: props, KeyNode: addPLabel, ValueNode: addPNode}
}
@@ -111,11 +112,43 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex, level int) error {
s.Discriminator = low.NodeReference[*Discriminator]{Value: &discriminator, KeyNode: discLabel, ValueNode: discNode}
}
// handle externalDocs if set.
_, extDocLabel, extDocNode := utils.FindKeyNodeFull(ExternalDocsLabel, root.Content)
if extDocNode != nil {
var exDoc ExternalDoc
err = BuildModel(extDocNode, &exDoc)
if err != nil {
return err
}
err = exDoc.Build(extDocNode)
if err != nil {
return err
}
s.ExternalDocs = low.NodeReference[*ExternalDoc]{Value: &exDoc, KeyNode: extDocLabel, ValueNode: extDocNode}
}
// handle xml if set.
_, xmlLabel, xmlNode := utils.FindKeyNodeFull(XMLLabel, root.Content)
if xmlNode != nil {
var xml XML
err = BuildModel(xmlNode, &xml)
if err != nil {
return err
}
// extract extensions if set.
err = xml.Build(xmlNode)
if err != nil {
return err
}
s.XML = low.NodeReference[*XML]{Value: &xml, KeyNode: xmlLabel, ValueNode: xmlNode}
}
// handle properties
_, propLabel, propsNode := utils.FindKeyNodeFull(PropertiesLabel, root.Content)
if propsNode != nil {
propertyMap := make(map[low.NodeReference[string]]*low.NodeReference[*Schema])
propertyMap := make(map[low.KeyReference[string]]*low.ValueReference[*Schema])
var currentProp *yaml.Node
var wg sync.WaitGroup
for i, prop := range propsNode.Content {
if i%2 == 0 {
currentProp = prop
@@ -127,17 +160,15 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex, level int) error {
if err != nil {
return err
}
err = property.Build(prop, idx, level)
err = property.Build(prop, level)
if err != nil {
return err
}
propertyMap[low.NodeReference[string]{
Value: currentProp.Value,
KeyNode: propLabel,
ValueNode: propsNode,
}] = &low.NodeReference[*Schema]{
propertyMap[low.KeyReference[string]{
Value: currentProp.Value,
KeyNode: propLabel,
}] = &low.ValueReference[*Schema]{
Value: &property,
KeyNode: currentProp,
ValueNode: prop,
}
}
@@ -145,16 +176,15 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex, level int) error {
// extract all sub-schemas
var errors []error
var wg sync.WaitGroup
var allOf, anyOf, oneOf, not, items []low.NodeReference[*Schema]
// make this async at some point to speed things up.
buildSchema(&allOf, AllOfLabel, idx, root, level, &errors, &wg)
buildSchema(&anyOf, AnyOfLabel, idx, root, level, &errors, &wg)
buildSchema(&oneOf, OneOfLabel, idx, root, level, &errors, &wg)
buildSchema(&not, NotLabel, idx, root, level, &errors, &wg)
buildSchema(&items, ItemsLabel, idx, root, level, &errors, &wg)
buildSchema(&allOf, AllOfLabel, root, level, &errors, &wg)
buildSchema(&anyOf, AnyOfLabel, root, level, &errors, &wg)
buildSchema(&oneOf, OneOfLabel, root, level, &errors, &wg)
buildSchema(&not, NotLabel, root, level, &errors, &wg)
buildSchema(&items, ItemsLabel, root, level, &errors, &wg)
//wg.Wait()
if len(errors) > 0 {
@@ -182,7 +212,16 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex, level int) error {
return nil
}
func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, idx *index.SpecIndex, rootNode *yaml.Node, level int, errors *[]error, wg *sync.WaitGroup) {
func (s *Schema) extractExtensions(root *yaml.Node) error {
extensionMap, err := ExtractExtensions(root)
if err != nil {
return err
}
s.Extensions = extensionMap
return err
}
func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNode *yaml.Node, level int, errors *[]error, wg *sync.WaitGroup) {
_, labelNode, valueNode := utils.FindKeyNodeFull(attribute, rootNode.Content)
//wg.Add(1)
if valueNode != nil {
@@ -194,7 +233,7 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, idx *i
*errors = append(*errors, err)
return nil
}
err = schema.Build(vn, idx, level)
err = schema.Build(vn, level)
if err != nil {
*errors = append(*errors, err)
return nil

View File

@@ -83,15 +83,25 @@ properties:
somethingB:
type: object
description: an object
externalDocs:
description: the best docs
url: https://pb33f.io
properties:
somethingBProp:
type: string
description: something b subprop
example: picnics are nice.
xml:
name: an xml thing
namespace: an xml namespace
prefix: a prefix
attribute: true
wrapped: false
x-pizza: love
additionalProperties:
why: yes
thatIs: true
additionalProperties: true`
additionalProperties: true `
var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(testSpec), &rootNode)
@@ -101,13 +111,26 @@ additionalProperties: true`
mbErr := BuildModel(&rootNode, &sch)
assert.NoError(t, mbErr)
schErr := sch.Build(rootNode.Content[0], nil, 0)
schErr := sch.Build(rootNode.Content[0], 0)
assert.NoError(t, schErr)
assert.Equal(t, "something object", sch.Description.Value)
assert.True(t, sch.AdditionalProperties.Value.(bool))
assert.Len(t, sch.Properties, 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)
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)
assert.NotNil(t, v.Value.AdditionalProperties.Value)
var addProps map[string]interface{}

View File

@@ -15,7 +15,7 @@ type Tag struct {
Name low.NodeReference[string]
Description low.NodeReference[string]
ExternalDocs low.NodeReference[*ExternalDoc]
Extensions map[low.NodeReference[string]]low.NodeReference[any]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (t *Tag) Build(root *yaml.Node) error {

View File

@@ -1,16 +1,24 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
)
type XML struct {
Node *yaml.Node
Name low.NodeReference[string]
Namespace low.NodeReference[string]
Prefix low.NodeReference[string]
Attribute low.NodeReference[string]
Wrapped low.NodeReference[bool]
Extensions map[string]low.ObjectReference
Name low.NodeReference[string]
Namespace low.NodeReference[string]
Prefix low.NodeReference[string]
Attribute low.NodeReference[bool]
Wrapped low.NodeReference[bool]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (x *XML) Build(root *yaml.Node) error {
extensionMap, err := ExtractExtensions(root)
if err != nil {
return err
}
x.Extensions = extensionMap
return nil
}

View File

@@ -6,8 +6,9 @@ type HasNode interface {
GetNode() *yaml.Node
}
type Buildable interface {
type Buildable[T any] interface {
Build(node *yaml.Node) error
*T
}
type NodeReference[T any] struct {
@@ -16,6 +17,16 @@ type NodeReference[T any] struct {
KeyNode *yaml.Node
}
type KeyReference[T any] struct {
Value T
KeyNode *yaml.Node
}
type ValueReference[T any] struct {
Value T
ValueNode *yaml.Node
}
type ObjectReference struct {
Value interface{}
ValueNode *yaml.Node
@@ -25,3 +36,11 @@ type ObjectReference struct {
func (n NodeReference[T]) IsEmpty() bool {
return n.KeyNode == nil && n.ValueNode == nil
}
func (n NodeReference[T]) IsMapKeyNode() bool {
return n.KeyNode != nil && n.ValueNode == nil
}
func (n NodeReference[T]) IsMapValueNode() bool {
return n.KeyNode == nil && n.ValueNode != nil
}

View File

@@ -15,8 +15,7 @@ func CreateDocument(info *datamodel.SpecInfo) (*v3.Document, error) {
doc := v3.Document{Version: low.NodeReference[string]{Value: info.Version, ValueNode: info.RootNode}}
// build an index
idx := index.NewSpecIndex(info.RootNode)
rsolvr := resolver.NewResolver(idx)
rsolvr := resolver.NewResolver(index.NewSpecIndex(info.RootNode))
// todo handle errors
rsolvr.Resolve()
@@ -24,18 +23,18 @@ func CreateDocument(info *datamodel.SpecInfo) (*v3.Document, error) {
var wg sync.WaitGroup
var errors []error
var runExtraction = func(info *datamodel.SpecInfo, doc *v3.Document,
runFunc func(i *datamodel.SpecInfo, d *v3.Document, idx *index.SpecIndex) error,
runFunc func(i *datamodel.SpecInfo, d *v3.Document) error,
ers *[]error,
wg *sync.WaitGroup) {
if er := runFunc(info, doc, idx); er != nil {
if er := runFunc(info, doc); er != nil {
*ers = append(*ers, er)
}
wg.Done()
}
extractionFuncs := []func(i *datamodel.SpecInfo, d *v3.Document, idx *index.SpecIndex) error{
extractionFuncs := []func(i *datamodel.SpecInfo, d *v3.Document) error{
extractInfo,
extractServers,
extractTags,
@@ -54,7 +53,7 @@ func CreateDocument(info *datamodel.SpecInfo) (*v3.Document, error) {
return &doc, nil
}
func extractInfo(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error {
func extractInfo(info *datamodel.SpecInfo, doc *v3.Document) error {
_, ln, vn := utils.FindKeyNodeFull(v3.InfoLabel, info.RootNode.Content)
if vn != nil {
ir := v3.Info{}
@@ -69,7 +68,7 @@ func extractInfo(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecInde
return nil
}
func extractServers(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error {
func extractServers(info *datamodel.SpecInfo, doc *v3.Document) error {
_, ln, vn := utils.FindKeyNodeFull(v3.ServersLabel, info.RootNode.Content)
if vn != nil {
if utils.IsNodeArray(vn) {
@@ -95,7 +94,7 @@ func extractServers(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecI
return nil
}
func extractTags(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error {
func extractTags(info *datamodel.SpecInfo, doc *v3.Document) error {
_, ln, vn := utils.FindKeyNodeFull(v3.TagsLabel, info.RootNode.Content)
if vn != nil {
if utils.IsNodeArray(vn) {
@@ -121,11 +120,11 @@ func extractTags(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecInde
return nil
}
func extractPaths(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error {
func extractPaths(info *datamodel.SpecInfo, doc *v3.Document) error {
_, ln, vn := utils.FindKeyNodeFull(v3.PathsLabel, info.RootNode.Content)
if vn != nil {
ir := v3.Paths{}
err := ir.Build(vn, idx)
err := ir.Build(vn)
if err != nil {
return err
}

View File

@@ -1,109 +1,126 @@
package openapi
import (
"github.com/pb33f/libopenapi/datamodel"
v3 "github.com/pb33f/libopenapi/datamodel/low/3.0"
"github.com/stretchr/testify/assert"
"io/ioutil"
"testing"
"github.com/pb33f/libopenapi/datamodel"
v3 "github.com/pb33f/libopenapi/datamodel/low/3.0"
"github.com/stretchr/testify/assert"
"io/ioutil"
"testing"
)
var doc *v3.Document
func init() {
data, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
doc, _ = CreateDocument(info)
data, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
doc, _ = CreateDocument(info)
}
func BenchmarkCreateDocument(b *testing.B) {
data, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
doc, _ = CreateDocument(info)
}
data, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
doc, _ = CreateDocument(info)
}
}
func TestCreateDocument(t *testing.T) {
assert.Equal(t, "3.0.1", doc.Version.Value)
assert.Equal(t, "Burger Shop", doc.Info.Value.Title.Value)
assert.NotEmpty(t, doc.Info.Value.Title.Value)
assert.Equal(t, "3.0.1", doc.Version.Value)
assert.Equal(t, "Burger Shop", doc.Info.Value.Title.Value)
assert.NotEmpty(t, doc.Info.Value.Title.Value)
}
func TestCreateDocument_Info(t *testing.T) {
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, "buckaroo@pb33f.io", doc.Info.Value.Contact.Value.Email.Value)
assert.Equal(t, "https://pb33f.io", doc.Info.Value.Contact.Value.URL.Value)
assert.Equal(t, "pb33f", doc.Info.Value.License.Value.Name.Value)
assert.Equal(t, "https://pb33f.io/made-up", doc.Info.Value.License.Value.URL.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, "buckaroo@pb33f.io", doc.Info.Value.Contact.Value.Email.Value)
assert.Equal(t, "https://pb33f.io", doc.Info.Value.Contact.Value.URL.Value)
assert.Equal(t, "pb33f", doc.Info.Value.License.Value.Name.Value)
assert.Equal(t, "https://pb33f.io/made-up", doc.Info.Value.License.Value.URL.Value)
}
func TestCreateDocument_Servers(t *testing.T) {
assert.Len(t, doc.Servers, 2)
server1 := doc.Servers[0]
server2 := doc.Servers[1]
assert.Len(t, doc.Servers, 2)
server1 := doc.Servers[0]
server2 := doc.Servers[1]
// server 1
assert.Equal(t, "{scheme}://api.pb33f.io", server1.Value.URL.Value)
assert.NotEmpty(t, server1.Value.Description.Value)
assert.Len(t, server1.Value.Variables.Value, 1)
assert.Len(t, server1.Value.Variables.Value["scheme"].Value.Enum, 2)
assert.Equal(t, server1.Value.Variables.Value["scheme"].Value.Default.Value, "https")
assert.NotEmpty(t, server1.Value.Variables.Value["scheme"].Value.Description.Value)
// server 1
assert.Equal(t, "{scheme}://api.pb33f.io", server1.Value.URL.Value)
assert.NotEmpty(t, server1.Value.Description.Value)
assert.Len(t, server1.Value.Variables.Value, 1)
assert.Len(t, server1.Value.Variables.Value["scheme"].Value.Enum, 2)
assert.Equal(t, server1.Value.Variables.Value["scheme"].Value.Default.Value, "https")
assert.NotEmpty(t, server1.Value.Variables.Value["scheme"].Value.Description.Value)
// server 2
assert.Equal(t, "https://{domain}.{host}.com", server2.Value.URL.Value)
assert.NotEmpty(t, server2.Value.Description.Value)
assert.Len(t, server2.Value.Variables.Value, 2)
assert.Equal(t, server2.Value.Variables.Value["domain"].Value.Default.Value, "api")
assert.NotEmpty(t, server2.Value.Variables.Value["domain"].Value.Description.Value)
assert.NotEmpty(t, server2.Value.Variables.Value["host"].Value.Description.Value)
assert.Equal(t, server2.Value.Variables.Value["host"].Value.Default.Value, "pb33f.io")
assert.Equal(t, "1.2", doc.Info.Value.Version.Value)
// server 2
assert.Equal(t, "https://{domain}.{host}.com", server2.Value.URL.Value)
assert.NotEmpty(t, server2.Value.Description.Value)
assert.Len(t, server2.Value.Variables.Value, 2)
assert.Equal(t, server2.Value.Variables.Value["domain"].Value.Default.Value, "api")
assert.NotEmpty(t, server2.Value.Variables.Value["domain"].Value.Description.Value)
assert.NotEmpty(t, server2.Value.Variables.Value["host"].Value.Description.Value)
assert.Equal(t, server2.Value.Variables.Value["host"].Value.Default.Value, "pb33f.io")
assert.Equal(t, "1.2", doc.Info.Value.Version.Value)
}
func TestCreateDocument_Tags(t *testing.T) {
assert.Len(t, doc.Tags, 2)
assert.Len(t, doc.Tags, 2)
// tag1
assert.Equal(t, "Burgers", doc.Tags[0].Value.Name.Value)
assert.NotEmpty(t, doc.Tags[0].Value.Description.Value)
assert.NotNil(t, doc.Tags[0].Value.ExternalDocs.Value)
assert.Equal(t, "https://pb33f.io", doc.Tags[0].Value.ExternalDocs.Value.URL.Value)
assert.NotEmpty(t, doc.Tags[0].Value.ExternalDocs.Value.URL.Value)
assert.Len(t, doc.Tags[0].Value.Extensions, 7)
// tag1
assert.Equal(t, "Burgers", doc.Tags[0].Value.Name.Value)
assert.NotEmpty(t, doc.Tags[0].Value.Description.Value)
assert.NotNil(t, doc.Tags[0].Value.ExternalDocs.Value)
assert.Equal(t, "https://pb33f.io", doc.Tags[0].Value.ExternalDocs.Value.URL.Value)
assert.NotEmpty(t, doc.Tags[0].Value.ExternalDocs.Value.URL.Value)
assert.Len(t, doc.Tags[0].Value.Extensions, 7)
for key, extension := range doc.Tags[0].Value.Extensions {
switch key.Value {
case "x-internal-ting":
assert.Equal(t, "somethingSpecial", extension.Value)
case "x-internal-tong":
assert.Equal(t, int64(1), extension.Value)
case "x-internal-tang":
assert.Equal(t, 1.2, extension.Value)
case "x-internal-tung":
assert.Equal(t, true, extension.Value)
case "x-internal-arr":
assert.Len(t, extension.Value, 2)
assert.Equal(t, "one", extension.Value.([]interface{})[0].(string))
case "x-internal-arrmap":
assert.Len(t, extension.Value, 2)
assert.Equal(t, "now", extension.Value.([]interface{})[0].(map[string]interface{})["what"])
case "x-something-else":
// crazy times in the upside down. this API should be avoided for the higher up use cases.
// this is why we will need a higher level API to this model, this looks cool and all, but dude.
assert.Equal(t, "now?", extension.Value.(map[string]interface{})["ok"].([]interface{})[0].(map[string]interface{})["what"])
}
for key, extension := range doc.Tags[0].Value.Extensions {
switch key.Value {
case "x-internal-ting":
assert.Equal(t, "somethingSpecial", extension.Value)
case "x-internal-tong":
assert.Equal(t, int64(1), extension.Value)
case "x-internal-tang":
assert.Equal(t, 1.2, extension.Value)
case "x-internal-tung":
assert.Equal(t, true, extension.Value)
case "x-internal-arr":
assert.Len(t, extension.Value, 2)
assert.Equal(t, "one", extension.Value.([]interface{})[0].(string))
case "x-internal-arrmap":
assert.Len(t, extension.Value, 2)
assert.Equal(t, "now", extension.Value.([]interface{})[0].(map[string]interface{})["what"])
case "x-something-else":
// crazy times in the upside down. this API should be avoided for the higher up use cases.
// this is why we will need a higher level API to this model, this looks cool and all, but dude.
assert.Equal(t, "now?", extension.Value.(map[string]interface{})["ok"].([]interface{})[0].(map[string]interface{})["what"])
}
}
}
/// tag2
assert.Equal(t, "Dressing", doc.Tags[1].Value.Name.Value)
assert.NotEmpty(t, doc.Tags[1].Value.Description.Value)
assert.NotNil(t, doc.Tags[1].Value.ExternalDocs.Value)
assert.Equal(t, "https://pb33f.io", doc.Tags[1].Value.ExternalDocs.Value.URL.Value)
assert.NotEmpty(t, doc.Tags[1].Value.ExternalDocs.Value.URL.Value)
assert.Len(t, doc.Tags[1].Value.Extensions, 0)
/// tag2
assert.Equal(t, "Dressing", doc.Tags[1].Value.Name.Value)
assert.NotEmpty(t, doc.Tags[1].Value.Description.Value)
assert.NotNil(t, doc.Tags[1].Value.ExternalDocs.Value)
assert.Equal(t, "https://pb33f.io", doc.Tags[1].Value.ExternalDocs.Value.URL.Value)
assert.NotEmpty(t, doc.Tags[1].Value.ExternalDocs.Value.URL.Value)
assert.Len(t, doc.Tags[1].Value.Extensions, 0)
}
func TestCreateDocument_Paths(t *testing.T) {
assert.Len(t, doc.Paths.Value.PathItems, 6)
burgerId := doc.Paths.Value.GetPath("/burgers/{burgerId}")
assert.NotNil(t, burgerId)
assert.Len(t, burgerId.Value.Get.Value.Parameters, 2)
param := burgerId.Value.Get.Value.Parameters[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)
assert.Equal(t, "big-mac", param.Value.Example.Value)
// check content
pContent := param.Value.GetContent("application/json")
assert.Equal(t, "somethingNice", pContent.Value.Example.Value)
}

View File

@@ -321,6 +321,27 @@ components:
example: big-mac
description: the name of the burger. use this to order your food
required: true
content:
application/json:
example: somethingNice
encoding:
burgerTheme:
contentType: text/plain
headers:
someHeader:
description: this is a header
schema:
type: string
schema:
type: object
required: [burgerTheme, burgerTime]
properties:
burgerTheme:
type: string
description: something about a theme?
burgerTime:
type: number
description: number of burgers ordered this year.
BurgerId:
in: path
name: burgerId