High level model build is currently fast as lightning.

little refactoring to keep things all clean and in place.
This commit is contained in:
Dave Shanley
2022-08-18 10:39:47 -04:00
parent 10dbc203f6
commit 0fd825aef8
14 changed files with 313 additions and 226 deletions

View File

@@ -1,35 +0,0 @@
package datamodel
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/3.0"
"github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func TestExternalDoc_Build(t *testing.T) {
yml := `url: https://pb33f.io
description: the ranch
x-b33f: princess`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
var n v3.ExternalDoc
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "https://pb33f.io", n.URL.Value)
assert.Equal(t, "the ranch", n.Description.Value)
ext := n.FindExtension("x-b33f")
assert.NotNil(t, ext)
assert.Equal(t, "princess", ext.Value)
}

View File

@@ -11,6 +11,20 @@ type Callback struct {
low *low.Callback
}
func NewCallback(lowCallback *low.Callback) *Callback {
n := new(Callback)
n.low = lowCallback
n.Expression = make(map[string]*PathItem)
for i := range lowCallback.Expression.Value {
n.Expression[i.Value] = NewPathItem(lowCallback.Expression.Value[i].Value)
}
n.Extensions = make(map[string]any)
for k, v := range lowCallback.Extensions {
n.Extensions[k.Value] = v
}
return n
}
func (c *Callback) GoLow() *low.Callback {
return c.low
}

View File

@@ -12,6 +12,15 @@ type Contact struct {
low *low.Contact
}
func NewContact(contact *low.Contact) *Contact {
c := new(Contact)
c.low = contact
c.URL = contact.URL.Value
c.Name = contact.Name.Value
c.Email = contact.Email.Value
return c
}
func (c *Contact) GoLow() *low.Contact {
return c.low
}

View File

@@ -18,6 +18,14 @@ type Document struct {
low *low.Document
}
func NewDocument(document *low.Document) *Document {
d := new(Document)
d.low = document
d.Info = NewInfo(document.Info.Value)
d.Version = document.Version.Value
return d
}
func (d *Document) GoLow() *low.Document {
return d.low
}

View File

@@ -0,0 +1,43 @@
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package v3
import (
"github.com/pb33f/libopenapi/datamodel"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/3.0"
"github.com/stretchr/testify/assert"
"io/ioutil"
"testing"
)
var doc *lowv3.Document
func init() {
data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
doc, err = lowv3.CreateDocument(info)
if err != nil {
panic("broken something")
}
}
func BenchmarkNewDocument(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = NewDocument(doc)
}
}
func TestNewDocument_Info(t *testing.T) {
highDoc := NewDocument(doc)
assert.Equal(t, "3.0.1", highDoc.Version)
assert.Equal(t, "Burger Shop", highDoc.Info.Title)
assert.Equal(t, "https://pb33f.io", highDoc.Info.TermsOfService)
assert.Equal(t, "pb33f", highDoc.Info.Contact.Name)
assert.Equal(t, "buckaroo@pb33f.io", highDoc.Info.Contact.Email)
assert.Equal(t, "https://pb33f.io", highDoc.Info.Contact.URL)
assert.Equal(t, "pb33f", highDoc.Info.License.Name)
assert.Equal(t, "https://pb33f.io/made-up", highDoc.Info.License.URL)
assert.Equal(t, "1.2", highDoc.Info.Version)
}

View File

@@ -15,6 +15,18 @@ type Info struct {
low *low.Info
}
func NewInfo(info *low.Info) *Info {
i := new(Info)
i.low = info
i.Title = info.Title.Value
i.Description = info.Description.Value
i.TermsOfService = info.TermsOfService.Value
i.Contact = NewContact(info.Contact.Value)
i.License = NewLicense(info.License.Value)
i.Version = info.Version.Value
return i
}
func (i *Info) GoLow() *low.Info {
return i.low
}

View File

@@ -11,6 +11,14 @@ type License struct {
low *low.License
}
func NewLicense(license *low.License) *License {
l := new(License)
l.low = license
l.URL = license.URL.Value
l.Name = license.Name.Value
return l
}
func (l *License) GoLow() *low.License {
return l.low
}

View File

@@ -22,6 +22,13 @@ type PathItem struct {
low *low.PathItem
}
func NewPathItem(lowPathItem *low.PathItem) *PathItem {
pi := new(PathItem)
pi.Description = lowPathItem.Description.Value
pi.Summary = lowPathItem.Summary.Value
return pi
}
func (p *PathItem) GoLow() *low.PathItem {
return p.low
}

View File

@@ -0,0 +1,171 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"sync"
)
func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
doc := Document{Version: low.NodeReference[string]{Value: info.Version, ValueNode: info.RootNode}}
// build an index
idx := index.NewSpecIndex(info.RootNode)
doc.Index = idx
var wg sync.WaitGroup
var errors []error
var runExtraction = func(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex,
runFunc func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error,
ers *[]error,
wg *sync.WaitGroup) {
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{
extractInfo,
extractServers,
extractTags,
extractPaths,
extractComponents,
extractSecurity,
extractExternalDocs,
}
wg.Add(len(extractionFuncs))
for _, f := range extractionFuncs {
go runExtraction(info, &doc, idx, f, &errors, &wg)
}
wg.Wait()
return &doc, errors
}
func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, 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)
nr := low.NodeReference[*Info]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Info = nr
}
return nil
}
func extractSecurity(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
sec, sErr := low.ExtractObject[*SecurityRequirement](SecurityLabel, info.RootNode, idx)
if sErr != nil {
return sErr
}
doc.Security = sec
return nil
}
func extractExternalDocs(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
extDocs, dErr := low.ExtractObject[*ExternalDoc](ExternalDocsLabel, info.RootNode, idx)
if dErr != nil {
return dErr
}
doc.ExternalDocs = extDocs
return nil
}
func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(ComponentsLabel, info.RootNode.Content)
if vn != nil {
ir := Components{}
err := low.BuildModel(vn, &ir)
if err != nil {
return err
}
err = ir.Build(vn, idx)
nr := low.NodeReference[*Components]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Components = nr
}
return nil
}
func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(ServersLabel, info.RootNode.Content)
if vn != nil {
if utils.IsNodeArray(vn) {
var servers []low.ValueReference[*Server]
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)
servers = append(servers, low.ValueReference[*Server]{
Value: &srvr,
ValueNode: srvN,
})
}
}
doc.Servers = low.NodeReference[[]low.ValueReference[*Server]]{
Value: servers,
KeyNode: ln,
ValueNode: vn,
}
}
}
return nil
}
func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(TagsLabel, info.RootNode.Content)
if vn != nil {
if utils.IsNodeArray(vn) {
var tags []low.ValueReference[*Tag]
for _, tagN := range vn.Content {
if utils.IsNodeMap(tagN) {
tag := Tag{}
err := low.BuildModel(tagN, &tag)
if err != nil {
return err
}
tag.Build(tagN, idx)
tags = append(tags, low.ValueReference[*Tag]{
Value: &tag,
ValueNode: tagN,
})
}
}
doc.Tags = low.NodeReference[[]low.ValueReference[*Tag]]{
Value: tags,
KeyNode: ln,
ValueNode: vn,
}
}
}
return nil
}
func extractPaths(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(PathsLabel, info.RootNode.Content)
if vn != nil {
ir := Paths{}
err := ir.Build(vn, idx)
if err != nil {
return err
}
nr := low.NodeReference[*Paths]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Paths = nr
}
return nil
}

View File

@@ -1,17 +1,16 @@
package openapi
package v3
import (
"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
var doc *Document
func init() {
data, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err []error
doc, err = CreateDocument(info)
@@ -21,7 +20,7 @@ func init() {
}
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)
for i := 0; i < b.N; i++ {
doc, _ = CreateDocument(info)
@@ -29,7 +28,7 @@ func BenchmarkCreateDocument(b *testing.B) {
}
func BenchmarkCreateDocument_Stripe(b *testing.B) {
data, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
data, _ := ioutil.ReadFile("../../../test_specs/stripe.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
_, err := CreateDocument(info)
@@ -40,7 +39,7 @@ func BenchmarkCreateDocument_Stripe(b *testing.B) {
}
func BenchmarkCreateDocument_Petstore(b *testing.B) {
data, _ := ioutil.ReadFile("../test_specs/petstorev3.json")
data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
_, err := CreateDocument(info)
@@ -136,7 +135,7 @@ func TestCreateDocument_Tags(t *testing.T) {
func TestCreateDocument_Paths(t *testing.T) {
doc := doc
assert.Len(t, doc.Paths.Value.PathItems, 6)
assert.Len(t, doc.Paths.Value.PathItems, 5)
burgerId := doc.Paths.Value.FindPath("/burgers/{burgerId}")
assert.NotNil(t, burgerId)
assert.Len(t, burgerId.Value.Get.Value.Parameters.Value, 2)
@@ -411,7 +410,7 @@ func TestCreateDocument_CheckAdditionalProperties_Schema(t *testing.T) {
components := doc.Components.Value
d := components.FindSchema("Dressing")
assert.NotNil(t, d.Value.AdditionalProperties.Value)
if n, ok := d.Value.AdditionalProperties.Value.(*v3.Schema); ok {
if n, ok := d.Value.AdditionalProperties.Value.(*Schema); ok {
assert.Equal(t, "something in here.", n.Description.Value)
} else {
assert.Fail(t, "should be a schema")

View File

@@ -28,3 +28,28 @@ func TestExternalDoc_FindExtension(t *testing.T) {
assert.Equal(t, "cake", n.FindExtension("x-fish").Value)
}
func TestExternalDoc_Build(t *testing.T) {
yml := `url: https://pb33f.io
description: the ranch
x-b33f: princess`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
var n ExternalDoc
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "https://pb33f.io", n.URL.Value)
assert.Equal(t, "the ranch", n.Description.Value)
ext := n.FindExtension("x-b33f")
assert.NotNil(t, ext)
assert.Equal(t, "princess", ext.Value)
}

View File

@@ -162,7 +162,7 @@ func TestSpecIndex_PetstoreV3(t *testing.T) {
}
var mappedRefs = 16
var mappedRefs = 15
func TestSpecIndex_BurgerShop(t *testing.T) {
@@ -177,12 +177,12 @@ func TestSpecIndex_BurgerShop(t *testing.T) {
assert.Equal(t, mappedRefs, len(index.GetMappedReferences()))
assert.Equal(t, mappedRefs, len(index.GetMappedReferencesSequenced()))
assert.Equal(t, 7, index.pathCount)
assert.Equal(t, 7, index.GetPathCount())
assert.Equal(t, 6, index.pathCount)
assert.Equal(t, 6, index.GetPathCount())
assert.Equal(t, 6, len(index.GetAllSchemas()))
assert.Equal(t, 30, len(index.GetAllSequencedReferences()))
assert.Equal(t, 29, len(index.GetAllSequencedReferences()))
assert.NotNil(t, index.GetSchemasNode())
assert.NotNil(t, index.GetParametersNode())

View File

@@ -1,172 +0,0 @@
package openapi
import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/3.0"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"sync"
)
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)
doc.Index = idx
var wg sync.WaitGroup
var errors []error
var runExtraction = func(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex,
runFunc func(i *datamodel.SpecInfo, d *v3.Document, idx *index.SpecIndex) error,
ers *[]error,
wg *sync.WaitGroup) {
if er := runFunc(info, doc, idx); er != nil {
*ers = append(*ers, er)
}
wg.Done()
}
extractionFuncs := []func(i *datamodel.SpecInfo, d *v3.Document, idx *index.SpecIndex) error{
extractInfo,
extractServers,
extractTags,
extractPaths,
extractComponents,
extractSecurity,
extractExternalDocs,
}
wg.Add(len(extractionFuncs))
for _, f := range extractionFuncs {
go runExtraction(info, &doc, idx, f, &errors, &wg)
}
wg.Wait()
return &doc, errors
}
func extractInfo(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(v3.InfoLabel, info.RootNode.Content)
if vn != nil {
ir := v3.Info{}
err := low.BuildModel(vn, &ir)
if err != nil {
return err
}
err = ir.Build(vn, idx)
nr := low.NodeReference[*v3.Info]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Info = nr
}
return nil
}
func extractSecurity(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error {
sec, sErr := low.ExtractObject[*v3.SecurityRequirement](v3.SecurityLabel, info.RootNode, idx)
if sErr != nil {
return sErr
}
doc.Security = sec
return nil
}
func extractExternalDocs(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error {
extDocs, dErr := low.ExtractObject[*v3.ExternalDoc](v3.ExternalDocsLabel, info.RootNode, idx)
if dErr != nil {
return dErr
}
doc.ExternalDocs = extDocs
return nil
}
func extractComponents(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(v3.ComponentsLabel, info.RootNode.Content)
if vn != nil {
ir := v3.Components{}
err := low.BuildModel(vn, &ir)
if err != nil {
return err
}
err = ir.Build(vn, idx)
nr := low.NodeReference[*v3.Components]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Components = nr
}
return nil
}
func extractServers(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(v3.ServersLabel, info.RootNode.Content)
if vn != nil {
if utils.IsNodeArray(vn) {
var servers []low.ValueReference[*v3.Server]
for _, srvN := range vn.Content {
if utils.IsNodeMap(srvN) {
srvr := v3.Server{}
err := low.BuildModel(srvN, &srvr)
if err != nil {
return err
}
srvr.Build(srvN, idx)
servers = append(servers, low.ValueReference[*v3.Server]{
Value: &srvr,
ValueNode: srvN,
})
}
}
doc.Servers = low.NodeReference[[]low.ValueReference[*v3.Server]]{
Value: servers,
KeyNode: ln,
ValueNode: vn,
}
}
}
return nil
}
func extractTags(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(v3.TagsLabel, info.RootNode.Content)
if vn != nil {
if utils.IsNodeArray(vn) {
var tags []low.ValueReference[*v3.Tag]
for _, tagN := range vn.Content {
if utils.IsNodeMap(tagN) {
tag := v3.Tag{}
err := low.BuildModel(tagN, &tag)
if err != nil {
return err
}
tag.Build(tagN, idx)
tags = append(tags, low.ValueReference[*v3.Tag]{
Value: &tag,
ValueNode: tagN,
})
}
}
doc.Tags = low.NodeReference[[]low.ValueReference[*v3.Tag]]{
Value: tags,
KeyNode: ln,
ValueNode: vn,
}
}
}
return nil
}
func extractPaths(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(v3.PathsLabel, info.RootNode.Content)
if vn != nil {
ir := v3.Paths{}
err := ir.Build(vn, idx)
if err != nil {
return err
}
nr := low.NodeReference[*v3.Paths]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Paths = nr
}
return nil
}

View File

@@ -59,8 +59,6 @@ servers:
description: the default host for this API is 'pb33f.io'
paths:
x-milky-milk: milky
/refingtons:
$ref: '../test_specs/petstorev3.json#/paths~1pet~1findByStatus'
/burgers:
x-burger-meta: meaty
post: