Most of the core functions are now in place

The hardest part has come and gone! it's all downhill now.
This commit is contained in:
Dave Shanley
2022-08-06 09:32:48 -04:00
parent 13781cbbde
commit 016595c905
9 changed files with 236 additions and 162 deletions

View File

@@ -17,7 +17,18 @@ type Encoding struct {
AllowReserved low.NodeReference[bool] AllowReserved low.NodeReference[bool]
} }
func (en Encoding) Build(root *yaml.Node) error { func (en *Encoding) FindHeader(hType string) *low.ValueReference[*Header] {
for _, c := range en.Headers {
for n, o := range c {
if n.Value == hType {
return &o
}
}
}
return nil
}
func (en *Encoding) Build(root *yaml.Node) error {
headers, err := ExtractMap[*Header](HeadersLabel, root) headers, err := ExtractMap[*Header](HeadersLabel, root)
if err != nil { if err != nil {

View File

@@ -5,6 +5,7 @@ import (
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strconv" "strconv"
"strings"
"sync" "sync"
) )
@@ -27,6 +28,44 @@ func ExtractSchema(root *yaml.Node) (*low.NodeReference[*Schema], error) {
var mapLock sync.Mutex var mapLock sync.Mutex
func ExtractObject[T any](label string, root *yaml.Node) (low.NodeReference[*T], error) {
_, ln, vn := utils.FindKeyNodeFull(label, root.Content)
var n *T = new(T)
err := BuildModel(root, n)
if err != nil {
return low.NodeReference[*T]{}, err
}
return low.NodeReference[*T]{
Value: n,
KeyNode: ln,
ValueNode: vn,
}, nil
}
func ExtractArray[T low.Buildable[N], N any](label string, root *yaml.Node) ([]low.NodeReference[T], error) {
_, ln, vn := utils.FindKeyNodeFull(label, root.Content)
var items []low.NodeReference[T]
if vn != nil && ln != nil {
for _, node := range vn.Content {
var n T = new(N)
err := BuildModel(node, n)
if err != nil {
return []low.NodeReference[T]{}, err
}
berr := n.Build(node)
if berr != nil {
return nil, berr
}
items = append(items, low.NodeReference[T]{
Value: n,
ValueNode: vn,
KeyNode: ln,
})
}
}
return items, nil
}
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) { 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) _, labelNode, valueNode := utils.FindKeyNodeFull(label, root.Content)
if valueNode != nil { if valueNode != nil {
@@ -37,6 +76,9 @@ func ExtractMap[PT low.Buildable[N], N any](label string, root *yaml.Node) (map[
currentLabelNode = en currentLabelNode = en
continue continue
} }
if strings.HasPrefix(strings.ToLower(currentLabelNode.Value), "x-") {
continue // yo, don't pay any attention to extensions, not here anyway.
}
var n PT = new(N) var n PT = new(N)
err := BuildModel(valueNode, n) err := BuildModel(valueNode, n)
if err != nil { if err != nil {
@@ -54,7 +96,6 @@ func ExtractMap[PT low.Buildable[N], N any](label string, root *yaml.Node) (map[
ValueNode: en, ValueNode: en,
} }
} }
resMap := make(map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[PT]) resMap := make(map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[PT])
resMap[low.KeyReference[string]{ resMap[low.KeyReference[string]{
Value: labelNode.Value, Value: labelNode.Value,

View File

@@ -14,6 +14,17 @@ type MediaType struct {
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
} }
func (mt *MediaType) FindPropertyEncoding(eType string) *low.ValueReference[*Encoding] {
for _, c := range mt.Encoding {
for n, o := range c {
if n.Value == eType {
return &o
}
}
}
return nil
}
func (mt *MediaType) Build(root *yaml.Node) error { func (mt *MediaType) Build(root *yaml.Node) error {
// extract extensions // extract extensions

View File

@@ -2,7 +2,6 @@ package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -34,42 +33,19 @@ func (o *Operation) Build(root *yaml.Node) error {
} }
o.Extensions = extensionMap o.Extensions = extensionMap
// extract external docs // extract externalDocs
_, ln, exDocs := utils.FindKeyNodeFull(ExternalDocsLabel, root.Content) extDocs, dErr := ExtractObject[ExternalDoc](ExternalDocsLabel, root)
if exDocs != nil { if dErr != nil {
var externalDoc ExternalDoc return dErr
err = BuildModel(exDocs, &externalDoc)
if err != nil {
return err
}
o.ExternalDocs = low.NodeReference[*ExternalDoc]{
Value: &externalDoc,
KeyNode: ln,
ValueNode: exDocs,
}
} }
o.ExternalDocs = extDocs
// build parameters // extract parameters
_, paramLabel, paramNode := utils.FindKeyNodeFull(ParametersLabel, root.Content) params, pErr := ExtractArray[*Parameter](ParametersLabel, root)
if paramNode != nil && paramLabel != nil { if pErr != nil {
var params []low.NodeReference[*Parameter] return pErr
}
for _, pN := range paramNode.Content { if params != nil {
var param Parameter
err = BuildModel(pN, &param)
if err != nil {
return err
}
err = param.Build(pN)
if err != nil {
return err
}
params = append(params, low.NodeReference[*Parameter]{
Value: &param,
ValueNode: paramNode,
KeyNode: paramLabel,
})
}
o.Parameters = params o.Parameters = params
} }

View File

@@ -28,7 +28,7 @@ type Parameter struct {
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
} }
func (p *Parameter) GetContent(cType string) *low.ValueReference[*MediaType] { func (p *Parameter) FindContent(cType string) *low.ValueReference[*MediaType] {
for _, c := range p.Content { for _, c := range p.Content {
for n, o := range c { for n, o := range c {
if n.Value == cType { if n.Value == cType {
@@ -61,6 +61,15 @@ func (p *Parameter) Build(root *yaml.Node) error {
} }
p.Schema = *sch p.Schema = *sch
// handle examples if set.
exps, eErr := ExtractMap[*Example](ExamplesLabel, root)
if eErr != nil {
return eErr
}
if exps != nil {
p.Examples = exps
}
// handle content, if set. // handle content, if set.
con, cErr := ExtractMap[*MediaType](ContentLabel, root) con, cErr := ExtractMap[*MediaType](ContentLabel, root)
if cErr != nil { if cErr != nil {

View File

@@ -24,7 +24,7 @@ type Paths struct {
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
} }
func (p *Paths) GetPath(path string) *low.ValueReference[*PathItem] { func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] {
for k, p := range p.PathItems { for k, p := range p.PathItems {
if k.Value == path { if k.Value == path {
return &p return &p
@@ -87,16 +87,16 @@ func (p *Paths) Build(root *yaml.Node) error {
type PathItem struct { type PathItem struct {
Description low.NodeReference[string] Description low.NodeReference[string]
Summary low.NodeReference[string] Summary low.NodeReference[string]
Get *low.NodeReference[*Operation] Get low.NodeReference[*Operation]
Put *low.NodeReference[*Operation] Put low.NodeReference[*Operation]
Post *low.NodeReference[*Operation] Post low.NodeReference[*Operation]
Delete *low.NodeReference[*Operation] Delete low.NodeReference[*Operation]
Options *low.NodeReference[*Operation] Options low.NodeReference[*Operation]
Head *low.NodeReference[*Operation] Head low.NodeReference[*Operation]
Patch *low.NodeReference[*Operation] Patch low.NodeReference[*Operation]
Trace *low.NodeReference[*Operation] Trace low.NodeReference[*Operation]
Servers []*low.NodeReference[*Server] Servers []low.NodeReference[*Server]
Parameters []*low.NodeReference[*Parameter] Parameters []low.NodeReference[*Parameter]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
} }
@@ -143,21 +143,21 @@ func (p *PathItem) Build(root *yaml.Node) error {
switch currentNode.Value { switch currentNode.Value {
case GetLabel: case GetLabel:
p.Get = &opRef p.Get = opRef
case PostLabel: case PostLabel:
p.Post = &opRef p.Post = opRef
case PutLabel: case PutLabel:
p.Put = &opRef p.Put = opRef
case PatchLabel: case PatchLabel:
p.Patch = &opRef p.Patch = opRef
case DeleteLabel: case DeleteLabel:
p.Delete = &opRef p.Delete = opRef
case HeadLabel: case HeadLabel:
p.Head = &opRef p.Head = opRef
case OptionsLabel: case OptionsLabel:
p.Options = &opRef p.Options = opRef
case TraceLabel: case TraceLabel:
p.Trace = &opRef p.Trace = opRef
} }
} }

View File

@@ -6,9 +6,29 @@ import (
) )
type RequestBody struct { type RequestBody struct {
Node *yaml.Node
Description low.NodeReference[string] Description low.NodeReference[string]
Content map[string]MediaType Content map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[*MediaType]
Required low.NodeReference[bool] Required low.NodeReference[bool]
Extensions map[string]low.ObjectReference Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (rb *RequestBody) Build(root *yaml.Node) error {
// extract extensions
extensionMap, err := ExtractExtensions(root)
if err != nil {
return err
}
if extensionMap != nil {
rb.Extensions = extensionMap
}
// handle content, if set.
con, cErr := ExtractMap[*MediaType](ContentLabel, root)
if cErr != nil {
return cErr
}
if con != nil {
rb.Content = con
}
return nil
} }

View File

@@ -2,7 +2,6 @@ package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -26,17 +25,12 @@ func (t *Tag) Build(root *yaml.Node) error {
} }
t.Extensions = extensionMap t.Extensions = extensionMap
_, ln, exDocs := utils.FindKeyNodeFull(ExternalDocsLabel, root.Content) // extract externalDocs
// extract external docs extDocs, dErr := ExtractObject[ExternalDoc](ExternalDocsLabel, root)
var externalDoc ExternalDoc if dErr != nil {
err = BuildModel(exDocs, &externalDoc) return dErr
if err != nil {
return err
}
t.ExternalDocs = low.NodeReference[*ExternalDoc]{
Value: &externalDoc,
KeyNode: ln,
ValueNode: exDocs,
} }
t.ExternalDocs = extDocs
return nil return nil
} }

View File

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