diff --git a/datamodel/external_doc_test.go b/datamodel/external_doc_test.go deleted file mode 100644 index a63c4e6..0000000 --- a/datamodel/external_doc_test.go +++ /dev/null @@ -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) - -} diff --git a/datamodel/high/3.0/callback.go b/datamodel/high/3.0/callback.go index b0715a0..2cdfd89 100644 --- a/datamodel/high/3.0/callback.go +++ b/datamodel/high/3.0/callback.go @@ -6,11 +6,25 @@ package v3 import low "github.com/pb33f/libopenapi/datamodel/low/3.0" type Callback struct { - Expression map[string]*PathItem - Extensions map[string]any - low *low.Callback + Expression map[string]*PathItem + Extensions map[string]any + 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 + return c.low } diff --git a/datamodel/high/3.0/contact.go b/datamodel/high/3.0/contact.go index ce17450..d3135f3 100644 --- a/datamodel/high/3.0/contact.go +++ b/datamodel/high/3.0/contact.go @@ -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 } diff --git a/datamodel/high/3.0/document.go b/datamodel/high/3.0/document.go index c73c7af..a4a0386 100644 --- a/datamodel/high/3.0/document.go +++ b/datamodel/high/3.0/document.go @@ -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 } diff --git a/datamodel/high/3.0/document_test.go b/datamodel/high/3.0/document_test.go new file mode 100644 index 0000000..768654a --- /dev/null +++ b/datamodel/high/3.0/document_test.go @@ -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) +} diff --git a/datamodel/high/3.0/info.go b/datamodel/high/3.0/info.go index a0a8f7d..878b2d6 100644 --- a/datamodel/high/3.0/info.go +++ b/datamodel/high/3.0/info.go @@ -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 } diff --git a/datamodel/high/3.0/license.go b/datamodel/high/3.0/license.go index f605285..37bce50 100644 --- a/datamodel/high/3.0/license.go +++ b/datamodel/high/3.0/license.go @@ -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 } diff --git a/datamodel/high/3.0/path_item.go b/datamodel/high/3.0/path_item.go index c318d04..b1fd912 100644 --- a/datamodel/high/3.0/path_item.go +++ b/datamodel/high/3.0/path_item.go @@ -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 } diff --git a/datamodel/low/3.0/create_document.go b/datamodel/low/3.0/create_document.go new file mode 100644 index 0000000..9b63a78 --- /dev/null +++ b/datamodel/low/3.0/create_document.go @@ -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 +} diff --git a/openapi/create_document_test.go b/datamodel/low/3.0/create_document_test.go similarity index 97% rename from openapi/create_document_test.go rename to datamodel/low/3.0/create_document_test.go index 9b773bc..8da63ce 100644 --- a/openapi/create_document_test.go +++ b/datamodel/low/3.0/create_document_test.go @@ -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") diff --git a/datamodel/low/3.0/external_doc_test.go b/datamodel/low/3.0/external_doc_test.go index 9d0ef7e..1c8ae91 100644 --- a/datamodel/low/3.0/external_doc_test.go +++ b/datamodel/low/3.0/external_doc_test.go @@ -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) + +} diff --git a/index/spec_index_test.go b/index/spec_index_test.go index 30e6213..d922cea 100644 --- a/index/spec_index_test.go +++ b/index/spec_index_test.go @@ -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()) diff --git a/openapi/create_document.go b/openapi/create_document.go deleted file mode 100644 index 69e2a4b..0000000 --- a/openapi/create_document.go +++ /dev/null @@ -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 -} diff --git a/test_specs/burgershop.openapi.yaml b/test_specs/burgershop.openapi.yaml index c85b8ff..55d1f77 100644 --- a/test_specs/burgershop.openapi.yaml +++ b/test_specs/burgershop.openapi.yaml @@ -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: