(feat): Circular / resolving errors returned with document creation. #18

Tristan made a good point, part of the doc building process performs a resolving check and circular reference check, which is ignored by the returned errors. So resolving errors are now unpacked into standard errors and returned. Not sure why gofmt has shifted everything around however.
This commit is contained in:
Dave Shanley
2022-11-29 13:51:05 -05:00
parent 7a159bb467
commit e1dd606c69
7 changed files with 1639 additions and 1556 deletions

View File

@@ -4,393 +4,389 @@
package v3 package v3
import ( import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3" lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"io/ioutil" "io/ioutil"
"testing" "testing"
) )
var lowDoc *lowv3.Document var lowDoc *lowv3.Document
func initTest() { func initTest() {
data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml") data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err []error
lowDoc, err = lowv3.CreateDocument(info) lowDoc, err = lowv3.CreateDocument(info)
if err != nil { if err != nil {
panic("broken something") panic("broken something")
} }
} }
func BenchmarkNewDocument(b *testing.B) { func BenchmarkNewDocument(b *testing.B) {
initTest() initTest()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = NewDocument(lowDoc) _ = NewDocument(lowDoc)
} }
} }
func TestNewDocument_Extensions(t *testing.T) { func TestNewDocument_Extensions(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Equal(t, "darkside", h.Extensions["x-something-something"]) assert.Equal(t, "darkside", h.Extensions["x-something-something"])
} }
func TestNewDocument_ExternalDocs(t *testing.T) { func TestNewDocument_ExternalDocs(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Equal(t, "https://pb33f.io", h.ExternalDocs.URL) assert.Equal(t, "https://pb33f.io", h.ExternalDocs.URL)
} }
func TestNewDocument_Info(t *testing.T) { func TestNewDocument_Info(t *testing.T) {
initTest() initTest()
highDoc := NewDocument(lowDoc) highDoc := NewDocument(lowDoc)
assert.Equal(t, "3.1.0", highDoc.Version) assert.Equal(t, "3.1.0", highDoc.Version)
assert.Equal(t, "Burger Shop", highDoc.Info.Title) assert.Equal(t, "Burger Shop", highDoc.Info.Title)
assert.Equal(t, "https://pb33f.io", highDoc.Info.TermsOfService) assert.Equal(t, "https://pb33f.io", highDoc.Info.TermsOfService)
assert.Equal(t, "pb33f", highDoc.Info.Contact.Name) assert.Equal(t, "pb33f", highDoc.Info.Contact.Name)
assert.Equal(t, "buckaroo@pb33f.io", highDoc.Info.Contact.Email) assert.Equal(t, "buckaroo@pb33f.io", highDoc.Info.Contact.Email)
assert.Equal(t, "https://pb33f.io", highDoc.Info.Contact.URL) assert.Equal(t, "https://pb33f.io", highDoc.Info.Contact.URL)
assert.Equal(t, "pb33f", highDoc.Info.License.Name) assert.Equal(t, "pb33f", highDoc.Info.License.Name)
assert.Equal(t, "https://pb33f.io/made-up", highDoc.Info.License.URL) assert.Equal(t, "https://pb33f.io/made-up", highDoc.Info.License.URL)
assert.Equal(t, "1.2", highDoc.Info.Version) assert.Equal(t, "1.2", highDoc.Info.Version)
assert.Equal(t, "https://pb33f.io/schema", highDoc.JsonSchemaDialect) assert.Equal(t, "https://pb33f.io/schema", highDoc.JsonSchemaDialect)
wentLow := highDoc.GoLow() wentLow := highDoc.GoLow()
assert.Equal(t, 1, wentLow.Version.ValueNode.Line) assert.Equal(t, 1, wentLow.Version.ValueNode.Line)
assert.Equal(t, 3, wentLow.Info.Value.Title.KeyNode.Line) assert.Equal(t, 3, wentLow.Info.Value.Title.KeyNode.Line)
wentLower := highDoc.Info.Contact.GoLow() wentLower := highDoc.Info.Contact.GoLow()
assert.Equal(t, 8, wentLower.Name.ValueNode.Line) assert.Equal(t, 8, wentLower.Name.ValueNode.Line)
assert.Equal(t, 11, wentLower.Name.ValueNode.Column) assert.Equal(t, 11, wentLower.Name.ValueNode.Column)
wentLowAgain := highDoc.Info.GoLow() wentLowAgain := highDoc.Info.GoLow()
assert.Equal(t, 3, wentLowAgain.Title.ValueNode.Line) assert.Equal(t, 3, wentLowAgain.Title.ValueNode.Line)
assert.Equal(t, 10, wentLowAgain.Title.ValueNode.Column) assert.Equal(t, 10, wentLowAgain.Title.ValueNode.Column)
wentOnceMore := highDoc.Info.License.GoLow() wentOnceMore := highDoc.Info.License.GoLow()
assert.Equal(t, 12, wentOnceMore.Name.ValueNode.Line) assert.Equal(t, 12, wentOnceMore.Name.ValueNode.Line)
assert.Equal(t, 11, wentOnceMore.Name.ValueNode.Column) assert.Equal(t, 11, wentOnceMore.Name.ValueNode.Column)
} }
func TestNewDocument_Servers(t *testing.T) { func TestNewDocument_Servers(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Servers, 2) assert.Len(t, h.Servers, 2)
assert.Equal(t, "{scheme}://api.pb33f.io", h.Servers[0].URL) assert.Equal(t, "{scheme}://api.pb33f.io", h.Servers[0].URL)
assert.Equal(t, "this is our main API server, for all fun API things.", h.Servers[0].Description) assert.Equal(t, "this is our main API server, for all fun API things.", h.Servers[0].Description)
assert.Len(t, h.Servers[0].Variables, 1) assert.Len(t, h.Servers[0].Variables, 1)
assert.Equal(t, "https", h.Servers[0].Variables["scheme"].Default) assert.Equal(t, "https", h.Servers[0].Variables["scheme"].Default)
assert.Len(t, h.Servers[0].Variables["scheme"].Enum, 2) assert.Len(t, h.Servers[0].Variables["scheme"].Enum, 2)
assert.Equal(t, "https://{domain}.{host}.com", h.Servers[1].URL) assert.Equal(t, "https://{domain}.{host}.com", h.Servers[1].URL)
assert.Equal(t, "this is our second API server, for all fun API things.", h.Servers[1].Description) assert.Equal(t, "this is our second API server, for all fun API things.", h.Servers[1].Description)
assert.Len(t, h.Servers[1].Variables, 2) assert.Len(t, h.Servers[1].Variables, 2)
assert.Equal(t, "api", h.Servers[1].Variables["domain"].Default) assert.Equal(t, "api", h.Servers[1].Variables["domain"].Default)
assert.Equal(t, "pb33f.io", h.Servers[1].Variables["host"].Default) assert.Equal(t, "pb33f.io", h.Servers[1].Variables["host"].Default)
wentLow := h.GoLow() wentLow := h.GoLow()
assert.Equal(t, 45, wentLow.Servers.Value[0].Value.Description.KeyNode.Line) assert.Equal(t, 45, wentLow.Servers.Value[0].Value.Description.KeyNode.Line)
assert.Equal(t, 5, wentLow.Servers.Value[0].Value.Description.KeyNode.Column) assert.Equal(t, 5, wentLow.Servers.Value[0].Value.Description.KeyNode.Column)
assert.Equal(t, 45, wentLow.Servers.Value[0].Value.Description.ValueNode.Line) assert.Equal(t, 45, wentLow.Servers.Value[0].Value.Description.ValueNode.Line)
assert.Equal(t, 18, wentLow.Servers.Value[0].Value.Description.ValueNode.Column) assert.Equal(t, 18, wentLow.Servers.Value[0].Value.Description.ValueNode.Column)
wentLower := h.Servers[0].GoLow() wentLower := h.Servers[0].GoLow()
assert.Equal(t, 45, wentLower.Description.ValueNode.Line) assert.Equal(t, 45, wentLower.Description.ValueNode.Line)
assert.Equal(t, 18, wentLower.Description.ValueNode.Column) assert.Equal(t, 18, wentLower.Description.ValueNode.Column)
wentLowest := h.Servers[0].Variables["scheme"].GoLow() wentLowest := h.Servers[0].Variables["scheme"].GoLow()
assert.Equal(t, 50, wentLowest.Description.ValueNode.Line) assert.Equal(t, 50, wentLowest.Description.ValueNode.Line)
assert.Equal(t, 22, wentLowest.Description.ValueNode.Column) assert.Equal(t, 22, wentLowest.Description.ValueNode.Column)
} }
func TestNewDocument_Tags(t *testing.T) { func TestNewDocument_Tags(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Tags, 2) assert.Len(t, h.Tags, 2)
assert.Equal(t, "Burgers", h.Tags[0].Name) assert.Equal(t, "Burgers", h.Tags[0].Name)
assert.Equal(t, "All kinds of yummy burgers.", h.Tags[0].Description) assert.Equal(t, "All kinds of yummy burgers.", h.Tags[0].Description)
assert.Equal(t, "Find out more", h.Tags[0].ExternalDocs.Description) assert.Equal(t, "Find out more", h.Tags[0].ExternalDocs.Description)
assert.Equal(t, "https://pb33f.io", h.Tags[0].ExternalDocs.URL) assert.Equal(t, "https://pb33f.io", h.Tags[0].ExternalDocs.URL)
assert.Equal(t, "somethingSpecial", h.Tags[0].Extensions["x-internal-ting"]) assert.Equal(t, "somethingSpecial", h.Tags[0].Extensions["x-internal-ting"])
assert.Equal(t, int64(1), h.Tags[0].Extensions["x-internal-tong"]) assert.Equal(t, int64(1), h.Tags[0].Extensions["x-internal-tong"])
assert.Equal(t, 1.2, h.Tags[0].Extensions["x-internal-tang"]) assert.Equal(t, 1.2, h.Tags[0].Extensions["x-internal-tang"])
assert.True(t, h.Tags[0].Extensions["x-internal-tung"].(bool)) assert.True(t, h.Tags[0].Extensions["x-internal-tung"].(bool))
wentLow := h.Tags[1].GoLow() wentLow := h.Tags[1].GoLow()
assert.Equal(t, 39, wentLow.Description.KeyNode.Line) assert.Equal(t, 39, wentLow.Description.KeyNode.Line)
assert.Equal(t, 5, wentLow.Description.KeyNode.Column) assert.Equal(t, 5, wentLow.Description.KeyNode.Column)
wentLower := h.Tags[0].ExternalDocs.GoLow() wentLower := h.Tags[0].ExternalDocs.GoLow()
assert.Equal(t, 23, wentLower.Description.ValueNode.Line) assert.Equal(t, 23, wentLower.Description.ValueNode.Line)
assert.Equal(t, 20, wentLower.Description.ValueNode.Column) assert.Equal(t, 20, wentLower.Description.ValueNode.Column)
} }
func TestNewDocument_Webhooks(t *testing.T) { func TestNewDocument_Webhooks(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Webhooks, 1) assert.Len(t, h.Webhooks, 1)
assert.Equal(t, "Information about a new burger", h.Webhooks["someHook"].Post.RequestBody.Description) assert.Equal(t, "Information about a new burger", h.Webhooks["someHook"].Post.RequestBody.Description)
} }
func TestNewDocument_Components_Links(t *testing.T) { func TestNewDocument_Components_Links(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Components.Links, 2) assert.Len(t, h.Components.Links, 2)
assert.Equal(t, "locateBurger", h.Components.Links["LocateBurger"].OperationId) assert.Equal(t, "locateBurger", h.Components.Links["LocateBurger"].OperationId)
assert.Equal(t, "$response.body#/id", h.Components.Links["LocateBurger"].Parameters["burgerId"]) assert.Equal(t, "$response.body#/id", h.Components.Links["LocateBurger"].Parameters["burgerId"])
wentLow := h.Components.Links["LocateBurger"].GoLow() wentLow := h.Components.Links["LocateBurger"].GoLow()
assert.Equal(t, 305, wentLow.OperationId.ValueNode.Line) assert.Equal(t, 305, wentLow.OperationId.ValueNode.Line)
assert.Equal(t, 20, wentLow.OperationId.ValueNode.Column) assert.Equal(t, 20, wentLow.OperationId.ValueNode.Column)
} }
func TestNewDocument_Components_Callbacks(t *testing.T) { func TestNewDocument_Components_Callbacks(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Components.Callbacks, 1) assert.Len(t, h.Components.Callbacks, 1)
assert.Equal(t, "Callback payload", assert.Equal(t, "Callback payload",
h.Components.Callbacks["BurgerCallback"].Expression["{$request.query.queryUrl}"].Post.RequestBody.Description) h.Components.Callbacks["BurgerCallback"].Expression["{$request.query.queryUrl}"].Post.RequestBody.Description)
assert.Equal(t, 293, assert.Equal(t, 293,
h.Components.Callbacks["BurgerCallback"].GoLow().FindExpression("{$request.query.queryUrl}").ValueNode.Line) h.Components.Callbacks["BurgerCallback"].GoLow().FindExpression("{$request.query.queryUrl}").ValueNode.Line)
assert.Equal(t, 9, assert.Equal(t, 9,
h.Components.Callbacks["BurgerCallback"].GoLow().FindExpression("{$request.query.queryUrl}").ValueNode.Column) h.Components.Callbacks["BurgerCallback"].GoLow().FindExpression("{$request.query.queryUrl}").ValueNode.Column)
assert.Equal(t, "please", h.Components.Callbacks["BurgerCallback"].Extensions["x-break-everything"]) assert.Equal(t, "please", h.Components.Callbacks["BurgerCallback"].Extensions["x-break-everything"])
for k := range h.Components.GoLow().Callbacks.Value { for k := range h.Components.GoLow().Callbacks.Value {
if k.Value == "BurgerCallback" { if k.Value == "BurgerCallback" {
assert.Equal(t, 290, k.KeyNode.Line) assert.Equal(t, 290, k.KeyNode.Line)
assert.Equal(t, 5, k.KeyNode.Column) assert.Equal(t, 5, k.KeyNode.Column)
} }
} }
} }
func TestNewDocument_Components_Schemas(t *testing.T) { func TestNewDocument_Components_Schemas(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Components.Schemas, 6) assert.Len(t, h.Components.Schemas, 6)
goLow := h.Components.GoLow() goLow := h.Components.GoLow()
a := h.Components.Schemas["Error"] a := h.Components.Schemas["Error"]
abcd := a.Schema().Properties["message"].Schema().Example abcd := a.Schema().Properties["message"].Schema().Example
assert.Equal(t, "No such burger as 'Big-Whopper'", abcd) assert.Equal(t, "No such burger as 'Big-Whopper'", abcd)
assert.Equal(t, 428, goLow.Schemas.KeyNode.Line) assert.Equal(t, 428, goLow.Schemas.KeyNode.Line)
assert.Equal(t, 3, goLow.Schemas.KeyNode.Column) assert.Equal(t, 3, goLow.Schemas.KeyNode.Column)
assert.Equal(t, 431, a.Schema().GoLow().Description.KeyNode.Line) assert.Equal(t, 431, a.Schema().GoLow().Description.KeyNode.Line)
b := h.Components.Schemas["Burger"] b := h.Components.Schemas["Burger"]
assert.Len(t, b.Schema().Required, 2) assert.Len(t, b.Schema().Required, 2)
assert.Equal(t, "golden slices of happy fun joy", b.Schema().Properties["fries"].Schema().Description) assert.Equal(t, "golden slices of happy fun joy", b.Schema().Properties["fries"].Schema().Description)
assert.Equal(t, int64(2), b.Schema().Properties["numPatties"].Schema().Example) assert.Equal(t, int64(2), b.Schema().Properties["numPatties"].Schema().Example)
assert.Equal(t, 443, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Line) assert.Equal(t, 443, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Line)
assert.Equal(t, 7, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Column) assert.Equal(t, 7, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Column)
assert.Equal(t, 445, b.Schema().GoLow().FindProperty("name").ValueNode.Line) assert.Equal(t, 445, b.Schema().GoLow().FindProperty("name").ValueNode.Line)
f := h.Components.Schemas["Fries"] f := h.Components.Schemas["Fries"]
assert.Equal(t, "salt", f.Schema().Properties["seasoning"].Schema().Items[0].Schema().Example) assert.Equal(t, "salt", f.Schema().Properties["seasoning"].Schema().Items[0].Schema().Example)
assert.Len(t, f.Schema().Properties["favoriteDrink"].Schema().Properties["drinkType"].Schema().Enum, 2) assert.Len(t, f.Schema().Properties["favoriteDrink"].Schema().Properties["drinkType"].Schema().Enum, 2)
d := h.Components.Schemas["Drink"] d := h.Components.Schemas["Drink"]
assert.Len(t, d.Schema().Required, 2) assert.Len(t, d.Schema().Required, 2)
assert.True(t, d.Schema().AdditionalProperties.(bool)) assert.True(t, d.Schema().AdditionalProperties.(bool))
assert.Equal(t, "drinkType", d.Schema().Discriminator.PropertyName) assert.Equal(t, "drinkType", d.Schema().Discriminator.PropertyName)
assert.Equal(t, "some value", d.Schema().Discriminator.Mapping["drink"]) assert.Equal(t, "some value", d.Schema().Discriminator.Mapping["drink"])
assert.Equal(t, 511, d.Schema().Discriminator.GoLow().PropertyName.ValueNode.Line) assert.Equal(t, 511, d.Schema().Discriminator.GoLow().PropertyName.ValueNode.Line)
assert.Equal(t, 23, d.Schema().Discriminator.GoLow().PropertyName.ValueNode.Column) assert.Equal(t, 23, d.Schema().Discriminator.GoLow().PropertyName.ValueNode.Column)
pl := h.Components.Schemas["SomePayload"] pl := h.Components.Schemas["SomePayload"]
assert.Equal(t, "is html programming? yes.", pl.Schema().XML.Name) assert.Equal(t, "is html programming? yes.", pl.Schema().XML.Name)
assert.Equal(t, 518, pl.Schema().XML.GoLow().Name.ValueNode.Line) assert.Equal(t, 518, pl.Schema().XML.GoLow().Name.ValueNode.Line)
ext := h.Components.Extensions ext := h.Components.Extensions
assert.Equal(t, "loud", ext["x-screaming-baby"]) assert.Equal(t, "loud", ext["x-screaming-baby"])
} }
func TestNewDocument_Components_Headers(t *testing.T) { func TestNewDocument_Components_Headers(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Components.Headers, 1) assert.Len(t, h.Components.Headers, 1)
assert.Equal(t, "this is a header example for UseOil", h.Components.Headers["UseOil"].Description) assert.Equal(t, "this is a header example for UseOil", h.Components.Headers["UseOil"].Description)
assert.Equal(t, 318, h.Components.Headers["UseOil"].GoLow().Description.ValueNode.Line) assert.Equal(t, 318, h.Components.Headers["UseOil"].GoLow().Description.ValueNode.Line)
assert.Equal(t, 20, h.Components.Headers["UseOil"].GoLow().Description.ValueNode.Column) assert.Equal(t, 20, h.Components.Headers["UseOil"].GoLow().Description.ValueNode.Column)
} }
func TestNewDocument_Components_RequestBodies(t *testing.T) { func TestNewDocument_Components_RequestBodies(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Components.RequestBodies, 1) assert.Len(t, h.Components.RequestBodies, 1)
assert.Equal(t, "Give us the new burger!", h.Components.RequestBodies["BurgerRequest"].Description) assert.Equal(t, "Give us the new burger!", h.Components.RequestBodies["BurgerRequest"].Description)
assert.Equal(t, 323, h.Components.RequestBodies["BurgerRequest"].GoLow().Description.ValueNode.Line) assert.Equal(t, 323, h.Components.RequestBodies["BurgerRequest"].GoLow().Description.ValueNode.Line)
assert.Equal(t, 20, h.Components.RequestBodies["BurgerRequest"].GoLow().Description.ValueNode.Column) assert.Equal(t, 20, h.Components.RequestBodies["BurgerRequest"].GoLow().Description.ValueNode.Column)
assert.Len(t, h.Components.RequestBodies["BurgerRequest"].Content["application/json"].Examples, 2) assert.Len(t, h.Components.RequestBodies["BurgerRequest"].Content["application/json"].Examples, 2)
} }
func TestNewDocument_Components_Examples(t *testing.T) { func TestNewDocument_Components_Examples(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Components.Examples, 1) assert.Len(t, h.Components.Examples, 1)
assert.Equal(t, "A juicy two hander sammich", h.Components.Examples["QuarterPounder"].Summary) assert.Equal(t, "A juicy two hander sammich", h.Components.Examples["QuarterPounder"].Summary)
assert.Equal(t, 341, h.Components.Examples["QuarterPounder"].GoLow().Summary.ValueNode.Line) assert.Equal(t, 341, h.Components.Examples["QuarterPounder"].GoLow().Summary.ValueNode.Line)
assert.Equal(t, 16, h.Components.Examples["QuarterPounder"].GoLow().Summary.ValueNode.Column) assert.Equal(t, 16, h.Components.Examples["QuarterPounder"].GoLow().Summary.ValueNode.Column)
} }
func TestNewDocument_Components_Responses(t *testing.T) { func TestNewDocument_Components_Responses(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Components.Responses, 1) assert.Len(t, h.Components.Responses, 1)
assert.Equal(t, "all the dressings for a burger.", h.Components.Responses["DressingResponse"].Description) assert.Equal(t, "all the dressings for a burger.", h.Components.Responses["DressingResponse"].Description)
assert.Equal(t, "array", h.Components.Responses["DressingResponse"].Content["application/json"].Schema.Schema().Type[0]) assert.Equal(t, "array", h.Components.Responses["DressingResponse"].Content["application/json"].Schema.Schema().Type[0])
assert.Equal(t, 347, h.Components.Responses["DressingResponse"].GoLow().Description.KeyNode.Line) assert.Equal(t, 347, h.Components.Responses["DressingResponse"].GoLow().Description.KeyNode.Line)
assert.Equal(t, 7, h.Components.Responses["DressingResponse"].GoLow().Description.KeyNode.Column) assert.Equal(t, 7, h.Components.Responses["DressingResponse"].GoLow().Description.KeyNode.Column)
} }
func TestNewDocument_Components_SecuritySchemes(t *testing.T) { func TestNewDocument_Components_SecuritySchemes(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Components.SecuritySchemes, 3) assert.Len(t, h.Components.SecuritySchemes, 3)
api := h.Components.SecuritySchemes["APIKeyScheme"] api := h.Components.SecuritySchemes["APIKeyScheme"]
assert.Equal(t, "an apiKey security scheme", api.Description) assert.Equal(t, "an apiKey security scheme", api.Description)
assert.Equal(t, 359, api.GoLow().Description.ValueNode.Line) assert.Equal(t, 359, api.GoLow().Description.ValueNode.Line)
assert.Equal(t, 20, api.GoLow().Description.ValueNode.Column) assert.Equal(t, 20, api.GoLow().Description.ValueNode.Column)
jwt := h.Components.SecuritySchemes["JWTScheme"] jwt := h.Components.SecuritySchemes["JWTScheme"]
assert.Equal(t, "an JWT security scheme", jwt.Description) assert.Equal(t, "an JWT security scheme", jwt.Description)
assert.Equal(t, 364, jwt.GoLow().Description.ValueNode.Line) assert.Equal(t, 364, jwt.GoLow().Description.ValueNode.Line)
assert.Equal(t, 20, jwt.GoLow().Description.ValueNode.Column) assert.Equal(t, 20, jwt.GoLow().Description.ValueNode.Column)
oAuth := h.Components.SecuritySchemes["OAuthScheme"] oAuth := h.Components.SecuritySchemes["OAuthScheme"]
assert.Equal(t, "an oAuth security scheme", oAuth.Description) assert.Equal(t, "an oAuth security scheme", oAuth.Description)
assert.Equal(t, 370, oAuth.GoLow().Description.ValueNode.Line) assert.Equal(t, 370, oAuth.GoLow().Description.ValueNode.Line)
assert.Equal(t, 20, oAuth.GoLow().Description.ValueNode.Column) assert.Equal(t, 20, oAuth.GoLow().Description.ValueNode.Column)
assert.Len(t, oAuth.Flows.Implicit.Scopes, 2) assert.Len(t, oAuth.Flows.Implicit.Scopes, 2)
assert.Equal(t, "read all burgers", oAuth.Flows.Implicit.Scopes["read:burgers"]) assert.Equal(t, "read all burgers", oAuth.Flows.Implicit.Scopes["read:burgers"])
assert.Equal(t, "https://pb33f.io/oauth", oAuth.Flows.AuthorizationCode.AuthorizationUrl) assert.Equal(t, "https://pb33f.io/oauth", oAuth.Flows.AuthorizationCode.AuthorizationUrl)
// check the lowness is low. // check the lowness is low.
assert.Equal(t, 375, oAuth.Flows.GoLow().Implicit.Value.Scopes.KeyNode.Line) assert.Equal(t, 375, oAuth.Flows.GoLow().Implicit.Value.Scopes.KeyNode.Line)
assert.Equal(t, 11, oAuth.Flows.GoLow().Implicit.Value.Scopes.KeyNode.Column) assert.Equal(t, 11, oAuth.Flows.GoLow().Implicit.Value.Scopes.KeyNode.Column)
assert.Equal(t, 375, oAuth.Flows.Implicit.GoLow().Scopes.KeyNode.Line) assert.Equal(t, 375, oAuth.Flows.Implicit.GoLow().Scopes.KeyNode.Line)
assert.Equal(t, 11, oAuth.Flows.Implicit.GoLow().Scopes.KeyNode.Column) assert.Equal(t, 11, oAuth.Flows.Implicit.GoLow().Scopes.KeyNode.Column)
} }
func TestNewDocument_Components_Parameters(t *testing.T) { func TestNewDocument_Components_Parameters(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Components.Parameters, 2) assert.Len(t, h.Components.Parameters, 2)
bh := h.Components.Parameters["BurgerHeader"] bh := h.Components.Parameters["BurgerHeader"]
assert.Equal(t, "burgerHeader", bh.Name) assert.Equal(t, "burgerHeader", bh.Name)
assert.Equal(t, 387, bh.GoLow().Name.KeyNode.Line) assert.Equal(t, 387, bh.GoLow().Name.KeyNode.Line)
assert.Len(t, bh.Schema.Schema().Properties, 2) assert.Len(t, bh.Schema.Schema().Properties, 2)
assert.Equal(t, "big-mac", bh.Example) assert.Equal(t, "big-mac", bh.Example)
assert.True(t, bh.Required) assert.True(t, bh.Required)
assert.Equal(t, "this is a header", assert.Equal(t, "this is a header",
bh.Content["application/json"].Encoding["burgerTheme"].Headers["someHeader"].Description) bh.Content["application/json"].Encoding["burgerTheme"].Headers["someHeader"].Description)
assert.Len(t, bh.Content["application/json"].Schema.Schema().Properties, 2) assert.Len(t, bh.Content["application/json"].Schema.Schema().Properties, 2)
assert.Equal(t, 404, bh.Content["application/json"].Encoding["burgerTheme"].GoLow().ContentType.ValueNode.Line) assert.Equal(t, 404, bh.Content["application/json"].Encoding["burgerTheme"].GoLow().ContentType.ValueNode.Line)
} }
func TestNewDocument_Paths(t *testing.T) { func TestNewDocument_Paths(t *testing.T) {
initTest() initTest()
h := NewDocument(lowDoc) h := NewDocument(lowDoc)
assert.Len(t, h.Paths.PathItems, 5) assert.Len(t, h.Paths.PathItems, 5)
burgersOp := h.Paths.PathItems["/burgers"] burgersOp := h.Paths.PathItems["/burgers"]
assert.Equal(t, "meaty", burgersOp.Extensions["x-burger-meta"]) assert.Equal(t, "meaty", burgersOp.Extensions["x-burger-meta"])
assert.Nil(t, burgersOp.Get) assert.Nil(t, burgersOp.Get)
assert.Nil(t, burgersOp.Put) assert.Nil(t, burgersOp.Put)
assert.Nil(t, burgersOp.Patch) assert.Nil(t, burgersOp.Patch)
assert.Nil(t, burgersOp.Head) assert.Nil(t, burgersOp.Head)
assert.Nil(t, burgersOp.Options) assert.Nil(t, burgersOp.Options)
assert.Nil(t, burgersOp.Trace) assert.Nil(t, burgersOp.Trace)
assert.Equal(t, 64, burgersOp.GoLow().Post.KeyNode.Line) assert.Equal(t, 64, burgersOp.GoLow().Post.KeyNode.Line)
assert.Equal(t, "createBurger", burgersOp.Post.OperationId) assert.Equal(t, "createBurger", burgersOp.Post.OperationId)
assert.Len(t, burgersOp.Post.Tags, 1) assert.Len(t, burgersOp.Post.Tags, 1)
assert.Equal(t, "A new burger for our menu, yummy yum yum.", burgersOp.Post.Description) assert.Equal(t, "A new burger for our menu, yummy yum yum.", burgersOp.Post.Description)
assert.Equal(t, "Give us the new burger!", burgersOp.Post.RequestBody.Description) assert.Equal(t, "Give us the new burger!", burgersOp.Post.RequestBody.Description)
assert.Len(t, burgersOp.Post.Responses.Codes, 3) assert.Len(t, burgersOp.Post.Responses.Codes, 3)
assert.Equal(t, 63, h.Paths.GoLow().FindPath("/burgers").ValueNode.Line) assert.Equal(t, 63, h.Paths.GoLow().FindPath("/burgers").ValueNode.Line)
okResp := burgersOp.Post.Responses.FindResponseByCode(200) okResp := burgersOp.Post.Responses.FindResponseByCode(200)
assert.Len(t, okResp.Headers, 1) assert.Len(t, okResp.Headers, 1)
assert.Equal(t, "A tasty burger for you to eat.", okResp.Description) assert.Equal(t, "A tasty burger for you to eat.", okResp.Description)
assert.Equal(t, 69, burgersOp.Post.GoLow().Description.ValueNode.Line) assert.Equal(t, 69, burgersOp.Post.GoLow().Description.ValueNode.Line)
assert.Len(t, okResp.Content["application/json"].Examples, 2) assert.Len(t, okResp.Content["application/json"].Examples, 2)
assert.Equal(t, "a cripsy fish sammich filled with ocean goodness.", assert.Equal(t, "a cripsy fish sammich filled with ocean goodness.",
okResp.Content["application/json"].Examples["filetOFish"].Summary) okResp.Content["application/json"].Examples["filetOFish"].Summary)
assert.Equal(t, 74, burgersOp.Post.Responses.GoLow().FindResponseByCode("200").ValueNode.Line) assert.Equal(t, 74, burgersOp.Post.Responses.GoLow().FindResponseByCode("200").ValueNode.Line)
assert.Equal(t, 80, okResp.Content["application/json"].GoLow().Schema.KeyNode.Line) assert.Equal(t, 80, okResp.Content["application/json"].GoLow().Schema.KeyNode.Line)
assert.Equal(t, 15, okResp.Content["application/json"].GoLow().Schema.KeyNode.Column) assert.Equal(t, 15, okResp.Content["application/json"].GoLow().Schema.KeyNode.Column)
assert.Equal(t, 77, okResp.GoLow().Description.KeyNode.Line) assert.Equal(t, 77, okResp.GoLow().Description.KeyNode.Line)
assert.Len(t, okResp.Links, 2) assert.Len(t, okResp.Links, 2)
assert.Equal(t, "locateBurger", okResp.Links["LocateBurger"].OperationId) assert.Equal(t, "locateBurger", okResp.Links["LocateBurger"].OperationId)
assert.Equal(t, 305, okResp.Links["LocateBurger"].GoLow().OperationId.ValueNode.Line) assert.Equal(t, 305, okResp.Links["LocateBurger"].GoLow().OperationId.ValueNode.Line)
assert.Len(t, burgersOp.Post.Security[0].Requirements, 1) assert.Len(t, burgersOp.Post.Security[0].Requirements, 1)
assert.Len(t, burgersOp.Post.Security[0].Requirements["OAuthScheme"], 2) assert.Len(t, burgersOp.Post.Security[0].Requirements["OAuthScheme"], 2)
assert.Equal(t, "read:burgers", burgersOp.Post.Security[0].Requirements["OAuthScheme"][0]) assert.Equal(t, "read:burgers", burgersOp.Post.Security[0].Requirements["OAuthScheme"][0])
assert.Equal(t, 118, burgersOp.Post.Security[0].GoLow().Requirements.ValueNode.Line) assert.Equal(t, 118, burgersOp.Post.Security[0].GoLow().Requirements.ValueNode.Line)
assert.Len(t, burgersOp.Post.Servers, 1) assert.Len(t, burgersOp.Post.Servers, 1)
assert.Equal(t, "https://pb33f.io", burgersOp.Post.Servers[0].URL) assert.Equal(t, "https://pb33f.io", burgersOp.Post.Servers[0].URL)
} }
func TestStripeAsDoc(t *testing.T) { func TestStripeAsDoc(t *testing.T) {
data, _ := ioutil.ReadFile("../../../test_specs/stripe.yaml") data, _ := ioutil.ReadFile("../../../test_specs/stripe.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err []error
lowDoc, err = lowv3.CreateDocument(info) lowDoc, err = lowv3.CreateDocument(info)
if err != nil { assert.Len(t, err, 21)
panic("broken something") d := NewDocument(lowDoc)
} fmt.Println(d)
d := NewDocument(lowDoc)
fmt.Println(d)
} }
func TestAsanaAsDoc(t *testing.T) { func TestAsanaAsDoc(t *testing.T) {
data, _ := ioutil.ReadFile("../../../test_specs/asana.yaml") data, _ := ioutil.ReadFile("../../../test_specs/asana.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err []error
lowDoc, err = lowv3.CreateDocument(info) lowDoc, err = lowv3.CreateDocument(info)
if err != nil { if err != nil {
panic("broken something") panic("broken something")
} }
d := NewDocument(lowDoc) d := NewDocument(lowDoc)
fmt.Println(d) fmt.Println(d)
} }
func TestPetstoreAsDoc(t *testing.T) { func TestPetstoreAsDoc(t *testing.T) {
data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json") data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err []error
lowDoc, err = lowv3.CreateDocument(info) lowDoc, err = lowv3.CreateDocument(info)
if err != nil { if err != nil {
panic("broken something") panic("broken something")
} }
d := NewDocument(lowDoc) d := NewDocument(lowDoc)
fmt.Println(d) fmt.Println(d)
} }
func TestCircularReferencesDoc(t *testing.T) { func TestCircularReferencesDoc(t *testing.T) {
data, _ := ioutil.ReadFile("../../../test_specs/circular-tests.yaml") data, _ := ioutil.ReadFile("../../../test_specs/circular-tests.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err []error
lowDoc, err = lowv3.CreateDocument(info) lowDoc, err = lowv3.CreateDocument(info)
if err != nil { assert.Len(t, err, 3)
panic("broken something") d := NewDocument(lowDoc)
} assert.Len(t, d.Components.Schemas, 9)
d := NewDocument(lowDoc) assert.Len(t, d.Index.GetCircularReferences(), 3)
assert.Len(t, d.Components.Schemas, 9)
assert.Len(t, d.Index.GetCircularReferences(), 3)
} }

View File

@@ -12,12 +12,13 @@
package v2 package v2
import ( import (
"github.com/pb33f/libopenapi/datamodel" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/resolver" "github.com/pb33f/libopenapi/index"
"gopkg.in/yaml.v3" "github.com/pb33f/libopenapi/resolver"
"gopkg.in/yaml.v3"
) )
// processes a property of a Swagger document asynchronously using bool and error channels for signals. // processes a property of a Swagger document asynchronously using bool and error channels for signals.
@@ -26,242 +27,250 @@ type documentFunction func(root *yaml.Node, doc *Swagger, idx *index.SpecIndex,
// Swagger represents a high-level Swagger / OpenAPI 2 document. An instance of Swagger is the root of the specification. // Swagger represents a high-level Swagger / OpenAPI 2 document. An instance of Swagger is the root of the specification.
type Swagger struct { type Swagger struct {
// Swagger is the version of Swagger / OpenAPI being used, extracted from the 'swagger: 2.x' definition. // Swagger is the version of Swagger / OpenAPI being used, extracted from the 'swagger: 2.x' definition.
Swagger low.ValueReference[string] Swagger low.ValueReference[string]
// Info represents a specification Info definition. // Info represents a specification Info definition.
// Provides metadata about the API. The metadata can be used by the clients if needed. // Provides metadata about the API. The metadata can be used by the clients if needed.
// - https://swagger.io/specification/v2/#infoObject // - https://swagger.io/specification/v2/#infoObject
Info low.NodeReference[*base.Info] Info low.NodeReference[*base.Info]
// Host is The host (name or ip) serving the API. This MUST be the host only and does not include the scheme nor // Host is The host (name or ip) serving the API. This MUST be the host only and does not include the scheme nor
// sub-paths. It MAY include a port. If the host is not included, the host serving the documentation is to be used // sub-paths. It MAY include a port. If the host is not included, the host serving the documentation is to be used
// (including the port). The host does not support path templating. // (including the port). The host does not support path templating.
Host low.NodeReference[string] Host low.NodeReference[string]
// BasePath is The base path on which the API is served, which is relative to the host. If it is not included, // BasePath is The base path on which the API is served, which is relative to the host. If it is not included,
// the API is served directly under the host. The value MUST start with a leading slash (/). // the API is served directly under the host. The value MUST start with a leading slash (/).
// The basePath does not support path templating. // The basePath does not support path templating.
BasePath low.NodeReference[string] BasePath low.NodeReference[string]
// Schemes represents the transfer protocol of the API. Requirements MUST be from the list: "http", "https", "ws", "wss". // Schemes represents the transfer protocol of the API. Requirements MUST be from the list: "http", "https", "ws", "wss".
// If the schemes is not included, the default scheme to be used is the one used to access // If the schemes is not included, the default scheme to be used is the one used to access
// the Swagger definition itself. // the Swagger definition itself.
Schemes low.NodeReference[[]low.ValueReference[string]] Schemes low.NodeReference[[]low.ValueReference[string]]
// Consumes is a list of MIME types the APIs can consume. This is global to all APIs but can be overridden on // Consumes is a list of MIME types the APIs can consume. This is global to all APIs but can be overridden on
// specific API calls. Value MUST be as described under Mime Types. // specific API calls. Value MUST be as described under Mime Types.
Consumes low.NodeReference[[]low.ValueReference[string]] Consumes low.NodeReference[[]low.ValueReference[string]]
// Produces is a list of MIME types the APIs can produce. This is global to all APIs but can be overridden on // Produces is a list of MIME types the APIs can produce. This is global to all APIs but can be overridden on
// specific API calls. Value MUST be as described under Mime Types. // specific API calls. Value MUST be as described under Mime Types.
Produces low.NodeReference[[]low.ValueReference[string]] Produces low.NodeReference[[]low.ValueReference[string]]
// Paths are the paths and operations for the API. Perhaps the most important part of the specification. // Paths are the paths and operations for the API. Perhaps the most important part of the specification.
// - https://swagger.io/specification/v2/#pathsObject // - https://swagger.io/specification/v2/#pathsObject
Paths low.NodeReference[*Paths] Paths low.NodeReference[*Paths]
// Definitions is an object to hold data types produced and consumed by operations. It's composed of Schema instances // Definitions is an object to hold data types produced and consumed by operations. It's composed of Schema instances
// - https://swagger.io/specification/v2/#definitionsObject // - https://swagger.io/specification/v2/#definitionsObject
Definitions low.NodeReference[*Definitions] Definitions low.NodeReference[*Definitions]
// SecurityDefinitions represents security scheme definitions that can be used across the specification. // SecurityDefinitions represents security scheme definitions that can be used across the specification.
// - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/v2/#securityDefinitionsObject
SecurityDefinitions low.NodeReference[*SecurityDefinitions] SecurityDefinitions low.NodeReference[*SecurityDefinitions]
// Parameters is an object to hold parameters that can be used across operations. // Parameters is an object to hold parameters that can be used across operations.
// This property does not define global parameters for all operations. // This property does not define global parameters for all operations.
// - https://swagger.io/specification/v2/#parametersDefinitionsObject // - https://swagger.io/specification/v2/#parametersDefinitionsObject
Parameters low.NodeReference[*ParameterDefinitions] Parameters low.NodeReference[*ParameterDefinitions]
// Responses is an object to hold responses that can be used across operations. // Responses is an object to hold responses that can be used across operations.
// This property does not define global responses for all operations. // This property does not define global responses for all operations.
// - https://swagger.io/specification/v2/#responsesDefinitionsObject // - https://swagger.io/specification/v2/#responsesDefinitionsObject
Responses low.NodeReference[*ResponsesDefinitions] Responses low.NodeReference[*ResponsesDefinitions]
// Security is a declaration of which security schemes are applied for the API as a whole. The list of values // Security is a declaration of which security schemes are applied for the API as a whole. The list of values
// describes alternative security schemes that can be used (that is, there is a logical OR between the security // describes alternative security schemes that can be used (that is, there is a logical OR between the security
// requirements). Individual operations can override this definition. // requirements). Individual operations can override this definition.
// - https://swagger.io/specification/v2/#securityRequirementObject // - https://swagger.io/specification/v2/#securityRequirementObject
Security low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]] Security low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]
// Tags are A list of tags used by the specification with additional metadata. // Tags are A list of tags used by the specification with additional metadata.
// The order of the tags can be used to reflect on their order by the parsing tools. Not all tags that are used // The order of the tags can be used to reflect on their order by the parsing tools. Not all tags that are used
// by the Operation Object must be declared. The tags that are not declared may be organized randomly or based // by the Operation Object must be declared. The tags that are not declared may be organized randomly or based
// on the tools' logic. Each tag name in the list MUST be unique. // on the tools' logic. Each tag name in the list MUST be unique.
// - https://swagger.io/specification/v2/#tagObject // - https://swagger.io/specification/v2/#tagObject
Tags low.NodeReference[[]low.ValueReference[*base.Tag]] Tags low.NodeReference[[]low.ValueReference[*base.Tag]]
// ExternalDocs is an instance of base.ExternalDoc for.. well, obvious really, innit mate? // ExternalDocs is an instance of base.ExternalDoc for.. well, obvious really, innit mate?
ExternalDocs low.NodeReference[*base.ExternalDoc] ExternalDocs low.NodeReference[*base.ExternalDoc]
// Extensions contains all custom extensions defined for the top-level document. // Extensions contains all custom extensions defined for the top-level document.
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
// Index is a reference to the index.SpecIndex that was created for the document and used // Index is a reference to the index.SpecIndex that was created for the document and used
// as a guide when building out the Document. Ideal if further processing is required on the model and // as a guide when building out the Document. Ideal if further processing is required on the model and
// the original details are required to continue the work. // the original details are required to continue the work.
// //
// This property is not a part of the OpenAPI schema, this is custom to libopenapi. // This property is not a part of the OpenAPI schema, this is custom to libopenapi.
Index *index.SpecIndex Index *index.SpecIndex
// SpecInfo is a reference to the datamodel.SpecInfo instance created when the specification was read. // SpecInfo is a reference to the datamodel.SpecInfo instance created when the specification was read.
// //
// This property is not a part of the OpenAPI schema, this is custom to libopenapi. // This property is not a part of the OpenAPI schema, this is custom to libopenapi.
SpecInfo *datamodel.SpecInfo SpecInfo *datamodel.SpecInfo
} }
// FindExtension locates an extension from the root of the Swagger document. // FindExtension locates an extension from the root of the Swagger document.
func (s *Swagger) FindExtension(ext string) *low.ValueReference[any] { func (s *Swagger) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, s.Extensions) return low.FindItemInMap[any](ext, s.Extensions)
} }
func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) { func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) {
doc := Swagger{Swagger: low.ValueReference[string]{Value: info.Version, ValueNode: info.RootNode}} doc := Swagger{Swagger: low.ValueReference[string]{Value: info.Version, ValueNode: info.RootNode}}
doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0]) doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0])
// build an index // build an index
idx := index.NewSpecIndex(info.RootNode) idx := index.NewSpecIndex(info.RootNode)
doc.Index = idx doc.Index = idx
doc.SpecInfo = info doc.SpecInfo = info
var errors []error var errors []error
// build out swagger scalar variables. // build out swagger scalar variables.
_ = low.BuildModel(info.RootNode.Content[0], &doc) _ = low.BuildModel(info.RootNode.Content[0], &doc)
// extract externalDocs // extract externalDocs
extDocs, err := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode, idx) extDocs, err := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode, idx)
if err != nil { if err != nil {
errors = append(errors, err) errors = append(errors, err)
} }
doc.ExternalDocs = extDocs doc.ExternalDocs = extDocs
// create resolver and check for circular references. // create resolver and check for circular references.
resolve := resolver.NewResolver(idx) resolve := resolver.NewResolver(idx)
_ = resolve.CheckForCircularReferences() resolvingErrors := resolve.CheckForCircularReferences()
extractionFuncs := []documentFunction{ if len(resolvingErrors) > 0 {
extractInfo, for r := range resolvingErrors {
extractPaths, errors = append(errors,
extractDefinitions, fmt.Errorf("%s (%s) [%d:%d]", resolvingErrors[r].Error.Error(),
extractParamDefinitions, resolvingErrors[r].Path, resolvingErrors[r].Node.Line, resolvingErrors[r].Node.Column))
extractResponsesDefinitions, }
extractSecurityDefinitions, }
extractTags,
extractSecurity,
}
doneChan := make(chan bool)
errChan := make(chan error)
for i := range extractionFuncs {
go extractionFuncs[i](info.RootNode.Content[0], &doc, idx, doneChan, errChan)
}
completedExtractions := 0
for completedExtractions < len(extractionFuncs) {
select {
case <-doneChan:
completedExtractions++
case e := <-errChan:
completedExtractions++
errors = append(errors, e)
}
}
return &doc, errors extractionFuncs := []documentFunction{
extractInfo,
extractPaths,
extractDefinitions,
extractParamDefinitions,
extractResponsesDefinitions,
extractSecurityDefinitions,
extractTags,
extractSecurity,
}
doneChan := make(chan bool)
errChan := make(chan error)
for i := range extractionFuncs {
go extractionFuncs[i](info.RootNode.Content[0], &doc, idx, doneChan, errChan)
}
completedExtractions := 0
for completedExtractions < len(extractionFuncs) {
select {
case <-doneChan:
completedExtractions++
case e := <-errChan:
completedExtractions++
errors = append(errors, e)
}
}
return &doc, errors
} }
func (s *Swagger) GetExternalDocs() *low.NodeReference[any] { func (s *Swagger) GetExternalDocs() *low.NodeReference[any] {
return &low.NodeReference[any]{ return &low.NodeReference[any]{
KeyNode: s.ExternalDocs.KeyNode, KeyNode: s.ExternalDocs.KeyNode,
ValueNode: s.ExternalDocs.ValueNode, ValueNode: s.ExternalDocs.ValueNode,
Value: s.ExternalDocs.Value, Value: s.ExternalDocs.Value,
} }
} }
func extractInfo(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractInfo(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
info, err := low.ExtractObject[*base.Info](base.InfoLabel, root, idx) info, err := low.ExtractObject[*base.Info](base.InfoLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Info = info doc.Info = info
c <- true c <- true
} }
func extractPaths(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractPaths(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
paths, err := low.ExtractObject[*Paths](PathsLabel, root, idx) paths, err := low.ExtractObject[*Paths](PathsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Paths = paths doc.Paths = paths
c <- true c <- true
} }
func extractDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
def, err := low.ExtractObject[*Definitions](DefinitionsLabel, root, idx) def, err := low.ExtractObject[*Definitions](DefinitionsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Definitions = def doc.Definitions = def
c <- true c <- true
} }
func extractParamDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractParamDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
param, err := low.ExtractObject[*ParameterDefinitions](ParametersLabel, root, idx) param, err := low.ExtractObject[*ParameterDefinitions](ParametersLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Parameters = param doc.Parameters = param
c <- true c <- true
} }
func extractResponsesDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractResponsesDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
resp, err := low.ExtractObject[*ResponsesDefinitions](ResponsesLabel, root, idx) resp, err := low.ExtractObject[*ResponsesDefinitions](ResponsesLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Responses = resp doc.Responses = resp
c <- true c <- true
} }
func extractSecurityDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractSecurityDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
sec, err := low.ExtractObject[*SecurityDefinitions](SecurityDefinitionsLabel, root, idx) sec, err := low.ExtractObject[*SecurityDefinitions](SecurityDefinitionsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.SecurityDefinitions = sec doc.SecurityDefinitions = sec
c <- true c <- true
} }
func extractTags(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractTags(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
tags, ln, vn, err := low.ExtractArray[*base.Tag](base.TagsLabel, root, idx) tags, ln, vn, err := low.ExtractArray[*base.Tag](base.TagsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Tags = low.NodeReference[[]low.ValueReference[*base.Tag]]{ doc.Tags = low.NodeReference[[]low.ValueReference[*base.Tag]]{
Value: tags, Value: tags,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
c <- true c <- true
} }
func extractSecurity(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractSecurity(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, root, idx) sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
} }
doc.Security = low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]{ doc.Security = low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]{
Value: sec, Value: sec,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
c <- true c <- true
} }

View File

@@ -4,333 +4,343 @@
package v2 package v2
import ( import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"io/ioutil" "io/ioutil"
"testing" "testing"
) )
var doc *Swagger var doc *Swagger
func initTest() { func initTest() {
if doc != nil { if doc != nil {
return return
} }
data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml") data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
if err != nil { if err != nil {
fmt.Print(err) fmt.Print(err)
panic(err) panic(err)
} }
} }
func BenchmarkCreateDocument(b *testing.B) { func BenchmarkCreateDocument(b *testing.B) {
data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.yaml") data, _ := ioutil.ReadFile("../../../test_specs/petstorev2-complete.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) {
initTest() initTest()
doc := doc doc := doc
assert.Equal(t, "2.0", doc.SpecInfo.Version) assert.Equal(t, "2.0", doc.SpecInfo.Version)
assert.Equal(t, "1.0.6", doc.Info.Value.Version.Value) assert.Equal(t, "1.0.6", doc.Info.Value.Version.Value)
assert.Equal(t, "petstore.swagger.io", doc.Host.Value) assert.Equal(t, "petstore.swagger.io", doc.Host.Value)
assert.Equal(t, "/v2", doc.BasePath.Value) assert.Equal(t, "/v2", doc.BasePath.Value)
assert.Len(t, doc.Parameters.Value.Definitions, 1) assert.Len(t, doc.Parameters.Value.Definitions, 1)
assert.Len(t, doc.Tags.Value, 3) assert.Len(t, doc.Tags.Value, 3)
assert.Len(t, doc.Schemes.Value, 2) assert.Len(t, doc.Schemes.Value, 2)
assert.Len(t, doc.Definitions.Value.Schemas, 6) assert.Len(t, doc.Definitions.Value.Schemas, 6)
assert.Len(t, doc.SecurityDefinitions.Value.Definitions, 3) assert.Len(t, doc.SecurityDefinitions.Value.Definitions, 3)
assert.Len(t, doc.Paths.Value.PathItems, 15) assert.Len(t, doc.Paths.Value.PathItems, 15)
assert.Len(t, doc.Responses.Value.Definitions, 2) assert.Len(t, doc.Responses.Value.Definitions, 2)
assert.Equal(t, "http://swagger.io", doc.ExternalDocs.Value.URL.Value) assert.Equal(t, "http://swagger.io", doc.ExternalDocs.Value.URL.Value)
assert.Equal(t, true, doc.FindExtension("x-pet").Value) assert.Equal(t, true, doc.FindExtension("x-pet").Value)
assert.Equal(t, true, doc.FindExtension("X-Pet").Value) assert.Equal(t, true, doc.FindExtension("X-Pet").Value)
assert.NotNil(t, doc.GetExternalDocs()) assert.NotNil(t, doc.GetExternalDocs())
} }
func TestCreateDocument_Info(t *testing.T) { func TestCreateDocument_Info(t *testing.T) {
initTest() initTest()
assert.Equal(t, "Swagger Petstore", doc.Info.Value.Title.Value) assert.Equal(t, "Swagger Petstore", doc.Info.Value.Title.Value)
assert.Equal(t, "apiteam@swagger.io", doc.Info.Value.Contact.Value.Email.Value) assert.Equal(t, "apiteam@swagger.io", doc.Info.Value.Contact.Value.Email.Value)
assert.Equal(t, "Apache 2.0", doc.Info.Value.License.Value.Name.Value) assert.Equal(t, "Apache 2.0", doc.Info.Value.License.Value.Name.Value)
} }
func TestCreateDocument_Parameters(t *testing.T) { func TestCreateDocument_Parameters(t *testing.T) {
initTest() initTest()
simpleParam := doc.Parameters.Value.FindParameter("simpleParam") simpleParam := doc.Parameters.Value.FindParameter("simpleParam")
assert.NotNil(t, simpleParam) assert.NotNil(t, simpleParam)
assert.Equal(t, "simple", simpleParam.Value.Name.Value) assert.Equal(t, "simple", simpleParam.Value.Name.Value)
assert.Equal(t, "nuggets", simpleParam.Value.FindExtension("x-chicken").Value) assert.Equal(t, "nuggets", simpleParam.Value.FindExtension("x-chicken").Value)
} }
func TestCreateDocument_Tags(t *testing.T) { func TestCreateDocument_Tags(t *testing.T) {
initTest() initTest()
assert.Equal(t, "pet", doc.Tags.Value[0].Value.Name.Value) assert.Equal(t, "pet", doc.Tags.Value[0].Value.Name.Value)
assert.Equal(t, "http://swagger.io", doc.Tags.Value[0].Value.ExternalDocs.Value.URL.Value) assert.Equal(t, "http://swagger.io", doc.Tags.Value[0].Value.ExternalDocs.Value.URL.Value)
assert.Equal(t, "store", doc.Tags.Value[1].Value.Name.Value) assert.Equal(t, "store", doc.Tags.Value[1].Value.Name.Value)
assert.Equal(t, "user", doc.Tags.Value[2].Value.Name.Value) assert.Equal(t, "user", doc.Tags.Value[2].Value.Name.Value)
assert.Equal(t, "http://swagger.io", doc.Tags.Value[2].Value.ExternalDocs.Value.URL.Value) assert.Equal(t, "http://swagger.io", doc.Tags.Value[2].Value.ExternalDocs.Value.URL.Value)
} }
func TestCreateDocument_SecurityDefinitions(t *testing.T) { func TestCreateDocument_SecurityDefinitions(t *testing.T) {
initTest() initTest()
apiKey := doc.SecurityDefinitions.Value.FindSecurityDefinition("api_key") apiKey := doc.SecurityDefinitions.Value.FindSecurityDefinition("api_key")
assert.Equal(t, "apiKey", apiKey.Value.Type.Value) assert.Equal(t, "apiKey", apiKey.Value.Type.Value)
petStoreAuth := doc.SecurityDefinitions.Value.FindSecurityDefinition("petstore_auth") petStoreAuth := doc.SecurityDefinitions.Value.FindSecurityDefinition("petstore_auth")
assert.Equal(t, "oauth2", petStoreAuth.Value.Type.Value) assert.Equal(t, "oauth2", petStoreAuth.Value.Type.Value)
assert.Equal(t, "implicit", petStoreAuth.Value.Flow.Value) assert.Equal(t, "implicit", petStoreAuth.Value.Flow.Value)
assert.Len(t, petStoreAuth.Value.Scopes.Value.Values, 2) assert.Len(t, petStoreAuth.Value.Scopes.Value.Values, 2)
assert.Equal(t, "read your pets", petStoreAuth.Value.Scopes.Value.FindScope("read:pets").Value) assert.Equal(t, "read your pets", petStoreAuth.Value.Scopes.Value.FindScope("read:pets").Value)
} }
func TestCreateDocument_Definitions(t *testing.T) { func TestCreateDocument_Definitions(t *testing.T) {
initTest() initTest()
apiResp := doc.Definitions.Value.FindSchema("ApiResponse").Value.Schema() apiResp := doc.Definitions.Value.FindSchema("ApiResponse").Value.Schema()
assert.NotNil(t, apiResp) assert.NotNil(t, apiResp)
assert.Len(t, apiResp.Properties.Value, 3) assert.Len(t, apiResp.Properties.Value, 3)
assert.Equal(t, "integer", apiResp.FindProperty("code").Value.Schema().Type.Value.A) assert.Equal(t, "integer", apiResp.FindProperty("code").Value.Schema().Type.Value.A)
pet := doc.Definitions.Value.FindSchema("Pet").Value.Schema() pet := doc.Definitions.Value.FindSchema("Pet").Value.Schema()
assert.NotNil(t, pet) assert.NotNil(t, pet)
assert.Len(t, pet.Required.Value, 2) assert.Len(t, pet.Required.Value, 2)
// perform a deep inline lookup on a schema to ensure chains work // perform a deep inline lookup on a schema to ensure chains work
assert.Equal(t, "Category", pet.FindProperty("category").Value.Schema().XML.Value.Name.Value) assert.Equal(t, "Category", pet.FindProperty("category").Value.Schema().XML.Value.Name.Value)
// check enums // check enums
assert.Len(t, pet.FindProperty("status").Value.Schema().Enum.Value, 3) assert.Len(t, pet.FindProperty("status").Value.Schema().Enum.Value, 3)
} }
func TestCreateDocument_ResponseDefinitions(t *testing.T) { func TestCreateDocument_ResponseDefinitions(t *testing.T) {
initTest() initTest()
apiResp := doc.Responses.Value.FindResponse("200") apiResp := doc.Responses.Value.FindResponse("200")
assert.NotNil(t, apiResp) assert.NotNil(t, apiResp)
assert.Equal(t, "OK", apiResp.Value.Description.Value) assert.Equal(t, "OK", apiResp.Value.Description.Value)
assert.Equal(t, "morning", apiResp.Value.FindExtension("x-coffee").Value) assert.Equal(t, "morning", apiResp.Value.FindExtension("x-coffee").Value)
header := apiResp.Value.FindHeader("noHeader") header := apiResp.Value.FindHeader("noHeader")
assert.NotNil(t, header) assert.NotNil(t, header)
assert.True(t, header.Value.FindExtension("x-empty").Value.(bool)) assert.True(t, header.Value.FindExtension("x-empty").Value.(bool))
header = apiResp.Value.FindHeader("myHeader") header = apiResp.Value.FindHeader("myHeader")
if k, ok := header.Value.Items.Value.Default.Value.(map[string]interface{}); ok { if k, ok := header.Value.Items.Value.Default.Value.(map[string]interface{}); ok {
assert.Equal(t, "here", k["something"]) assert.Equal(t, "here", k["something"])
} else { } else {
panic("should not fail.") panic("should not fail.")
} }
if k, ok := header.Value.Items.Value.Items.Value.Default.Value.([]interface{}); ok { if k, ok := header.Value.Items.Value.Items.Value.Default.Value.([]interface{}); ok {
assert.Len(t, k, 2) assert.Len(t, k, 2)
assert.Equal(t, "two", k[1]) assert.Equal(t, "two", k[1])
} else { } else {
panic("should not fail.") panic("should not fail.")
} }
header = apiResp.Value.FindHeader("yourHeader") header = apiResp.Value.FindHeader("yourHeader")
assert.Equal(t, "somethingSimple", header.Value.Items.Value.Default.Value) assert.Equal(t, "somethingSimple", header.Value.Items.Value.Default.Value)
assert.NotNil(t, apiResp.Value.Examples.Value.FindExample("application/json").Value) assert.NotNil(t, apiResp.Value.Examples.Value.FindExample("application/json").Value)
} }
func TestCreateDocument_Paths(t *testing.T) { func TestCreateDocument_Paths(t *testing.T) {
initTest() initTest()
uploadImage := doc.Paths.Value.FindPath("/pet/{petId}/uploadImage").Value uploadImage := doc.Paths.Value.FindPath("/pet/{petId}/uploadImage").Value
assert.NotNil(t, uploadImage) assert.NotNil(t, uploadImage)
assert.Nil(t, doc.Paths.Value.FindPath("/nothing-nowhere-nohow")) assert.Nil(t, doc.Paths.Value.FindPath("/nothing-nowhere-nohow"))
assert.Equal(t, "man", uploadImage.FindExtension("x-potato").Value) assert.Equal(t, "man", uploadImage.FindExtension("x-potato").Value)
assert.Equal(t, "fresh", doc.Paths.Value.FindExtension("x-minty").Value) assert.Equal(t, "fresh", doc.Paths.Value.FindExtension("x-minty").Value)
assert.Equal(t, "successful operation", assert.Equal(t, "successful operation",
uploadImage.Post.Value.Responses.Value.FindResponseByCode("200").Value.Description.Value) uploadImage.Post.Value.Responses.Value.FindResponseByCode("200").Value.Description.Value)
} }
func TestCreateDocument_Bad(t *testing.T) { func TestCreateDocument_Bad(t *testing.T) {
yml := `swagger: yml := `swagger:
$ref: bork` $ref: bork`
info, err := datamodel.ExtractSpecInfo([]byte(yml)) info, err := datamodel.ExtractSpecInfo([]byte(yml))
assert.Nil(t, info) assert.Nil(t, info)
assert.Error(t, err) assert.Error(t, err)
} }
func TestCreateDocument_ExternalDocsBad(t *testing.T) { func TestCreateDocument_ExternalDocsBad(t *testing.T) {
yml := `externalDocs: yml := `externalDocs:
$ref: bork` $ref: bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_TagsBad(t *testing.T) { func TestCreateDocument_TagsBad(t *testing.T) {
yml := `tags: yml := `tags:
$ref: bork` $ref: bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_PathsBad(t *testing.T) { func TestCreateDocument_PathsBad(t *testing.T) {
yml := `paths: yml := `paths:
"/hey": "/hey":
post: post:
responses: responses:
"200": "200":
$ref: bork` $ref: bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_SecurityBad(t *testing.T) { func TestCreateDocument_SecurityBad(t *testing.T) {
yml := `security: yml := `security:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_SecurityDefinitionsBad(t *testing.T) { func TestCreateDocument_SecurityDefinitionsBad(t *testing.T) {
yml := `securityDefinitions: yml := `securityDefinitions:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_ResponsesBad(t *testing.T) { func TestCreateDocument_ResponsesBad(t *testing.T) {
yml := `responses: yml := `responses:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_ParametersBad(t *testing.T) { func TestCreateDocument_ParametersBad(t *testing.T) {
yml := `parameters: yml := `parameters:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_DefinitionsBad(t *testing.T) { func TestCreateDocument_DefinitionsBad(t *testing.T) {
yml := `definitions: yml := `definitions:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestCreateDocument_InfoBad(t *testing.T) { func TestCreateDocument_InfoBad(t *testing.T) {
yml := `info: yml := `info:
$ref: ` $ref: `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err []error
doc, err = CreateDocument(info) doc, err = CreateDocument(info)
wait := true wait := true
for wait { for wait {
select { select {
case <-info.JsonParsingChannel: case <-info.JsonParsingChannel:
wait = false wait = false
} }
} }
assert.Len(t, err, 1) assert.Len(t, err, 1)
}
func TestCircularReferenceError(t *testing.T) {
data, _ := ioutil.ReadFile("../../../test_specs/swagger-circular-tests.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
circDoc, err := CreateDocument(info)
assert.NotNil(t, circDoc)
assert.Len(t, err, 3)
} }

View File

@@ -1,203 +1,213 @@
package v3 package v3
import ( import (
"errors" "errors"
"github.com/pb33f/libopenapi/datamodel" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/resolver" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/resolver"
"sync" "github.com/pb33f/libopenapi/utils"
"sync"
) )
func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) { func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
_, labelNode, versionNode := utils.FindKeyNodeFull(OpenAPILabel, info.RootNode.Content) _, labelNode, versionNode := utils.FindKeyNodeFull(OpenAPILabel, info.RootNode.Content)
var version low.NodeReference[string] var version low.NodeReference[string]
if versionNode == nil { if versionNode == nil {
return nil, []error{errors.New("no openapi version/tag found, cannot create document")} return nil, []error{errors.New("no openapi version/tag found, cannot create document")}
} }
version = low.NodeReference[string]{Value: versionNode.Value, KeyNode: labelNode, ValueNode: versionNode} version = low.NodeReference[string]{Value: versionNode.Value, KeyNode: labelNode, ValueNode: versionNode}
doc := Document{Version: version} doc := Document{Version: version}
// build an index // build an index
idx := index.NewSpecIndex(info.RootNode) idx := index.NewSpecIndex(info.RootNode)
doc.Index = idx doc.Index = idx
// create resolver and check for circular references. var errors []error
resolve := resolver.NewResolver(idx)
_ = resolve.CheckForCircularReferences()
var wg sync.WaitGroup // create resolver and check for circular references.
var errors []error resolve := resolver.NewResolver(idx)
resolvingErrors := resolve.CheckForCircularReferences()
doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0]) if len(resolvingErrors) > 0 {
for r := range resolvingErrors {
errors = append(errors,
fmt.Errorf("%s: %s [%d:%d]", resolvingErrors[r].Error.Error(),
resolvingErrors[r].Path, resolvingErrors[r].Node.Line, resolvingErrors[r].Node.Column))
}
}
// if set, extract jsonSchemaDialect (3.1) var wg sync.WaitGroup
_, dialectLabel, dialectNode := utils.FindKeyNodeFull(JSONSchemaDialectLabel, info.RootNode.Content)
if dialectNode != nil {
doc.JsonSchemaDialect = low.NodeReference[string]{
Value: dialectNode.Value, KeyNode: dialectLabel, ValueNode: dialectNode}
}
var runExtraction = func(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex, doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0])
runFunc func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error,
ers *[]error,
wg *sync.WaitGroup) {
if er := runFunc(info, doc, idx); er != nil { // if set, extract jsonSchemaDialect (3.1)
*ers = append(*ers, er) _, dialectLabel, dialectNode := utils.FindKeyNodeFull(JSONSchemaDialectLabel, info.RootNode.Content)
} if dialectNode != nil {
wg.Done() doc.JsonSchemaDialect = low.NodeReference[string]{
} Value: dialectNode.Value, KeyNode: dialectLabel, ValueNode: dialectNode}
extractionFuncs := []func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{ }
extractInfo,
extractServers,
extractTags,
extractComponents,
extractSecurity,
extractExternalDocs,
extractPaths,
extractWebhooks,
}
wg.Add(len(extractionFuncs)) var runExtraction = func(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex,
for _, f := range extractionFuncs { runFunc func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error,
go runExtraction(info, &doc, idx, f, &errors, &wg) ers *[]error,
} wg *sync.WaitGroup) {
wg.Wait()
return &doc, errors 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,
extractComponents,
extractSecurity,
extractExternalDocs,
extractPaths,
extractWebhooks,
}
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 { func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFullTop(base.InfoLabel, info.RootNode.Content[0].Content) _, ln, vn := utils.FindKeyNodeFullTop(base.InfoLabel, info.RootNode.Content[0].Content)
if vn != nil { if vn != nil {
ir := base.Info{} ir := base.Info{}
_ = low.BuildModel(vn, &ir) _ = low.BuildModel(vn, &ir)
_ = ir.Build(vn, idx) _ = ir.Build(vn, idx)
nr := low.NodeReference[*base.Info]{Value: &ir, ValueNode: vn, KeyNode: ln} nr := low.NodeReference[*base.Info]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Info = nr doc.Info = nr
} }
return nil return nil
} }
func extractSecurity(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractSecurity(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, info.RootNode.Content[0], idx) sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, info.RootNode.Content[0], idx)
if err != nil { if err != nil {
return err return err
} }
doc.Security = low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]{ doc.Security = low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]{
Value: sec, Value: sec,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
return nil return nil
} }
func extractExternalDocs(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractExternalDocs(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode.Content[0], idx) extDocs, dErr := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode.Content[0], idx)
if dErr != nil { if dErr != nil {
return dErr return dErr
} }
doc.ExternalDocs = extDocs doc.ExternalDocs = extDocs
return nil return nil
} }
func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFullTop(ComponentsLabel, info.RootNode.Content[0].Content) _, ln, vn := utils.FindKeyNodeFullTop(ComponentsLabel, info.RootNode.Content[0].Content)
if vn != nil { if vn != nil {
ir := Components{} ir := Components{}
_ = low.BuildModel(vn, &ir) _ = low.BuildModel(vn, &ir)
err := ir.Build(vn, idx) err := ir.Build(vn, idx)
if err != nil { if err != nil {
return err return err
} }
nr := low.NodeReference[*Components]{Value: &ir, ValueNode: vn, KeyNode: ln} nr := low.NodeReference[*Components]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Components = nr doc.Components = nr
} }
return nil return nil
} }
func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(ServersLabel, info.RootNode.Content[0].Content) _, ln, vn := utils.FindKeyNodeFull(ServersLabel, info.RootNode.Content[0].Content)
if vn != nil { if vn != nil {
if utils.IsNodeArray(vn) { if utils.IsNodeArray(vn) {
var servers []low.ValueReference[*Server] var servers []low.ValueReference[*Server]
for _, srvN := range vn.Content { for _, srvN := range vn.Content {
if utils.IsNodeMap(srvN) { if utils.IsNodeMap(srvN) {
srvr := Server{} srvr := Server{}
_ = low.BuildModel(srvN, &srvr) _ = low.BuildModel(srvN, &srvr)
_ = srvr.Build(srvN, idx) _ = srvr.Build(srvN, idx)
servers = append(servers, low.ValueReference[*Server]{ servers = append(servers, low.ValueReference[*Server]{
Value: &srvr, Value: &srvr,
ValueNode: srvN, ValueNode: srvN,
}) })
} }
} }
doc.Servers = low.NodeReference[[]low.ValueReference[*Server]]{ doc.Servers = low.NodeReference[[]low.ValueReference[*Server]]{
Value: servers, Value: servers,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
} }
} }
return nil return nil
} }
func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(base.TagsLabel, info.RootNode.Content[0].Content) _, ln, vn := utils.FindKeyNodeFull(base.TagsLabel, info.RootNode.Content[0].Content)
if vn != nil { if vn != nil {
if utils.IsNodeArray(vn) { if utils.IsNodeArray(vn) {
var tags []low.ValueReference[*base.Tag] var tags []low.ValueReference[*base.Tag]
for _, tagN := range vn.Content { for _, tagN := range vn.Content {
if utils.IsNodeMap(tagN) { if utils.IsNodeMap(tagN) {
tag := base.Tag{} tag := base.Tag{}
_ = low.BuildModel(tagN, &tag) _ = low.BuildModel(tagN, &tag)
if err := tag.Build(tagN, idx); err != nil { if err := tag.Build(tagN, idx); err != nil {
return err return err
} }
tags = append(tags, low.ValueReference[*base.Tag]{ tags = append(tags, low.ValueReference[*base.Tag]{
Value: &tag, Value: &tag,
ValueNode: tagN, ValueNode: tagN,
}) })
} }
} }
doc.Tags = low.NodeReference[[]low.ValueReference[*base.Tag]]{ doc.Tags = low.NodeReference[[]low.ValueReference[*base.Tag]]{
Value: tags, Value: tags,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
} }
} }
return nil return nil
} }
func extractPaths(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractPaths(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(PathsLabel, info.RootNode.Content[0].Content) _, ln, vn := utils.FindKeyNodeFull(PathsLabel, info.RootNode.Content[0].Content)
if vn != nil { if vn != nil {
ir := Paths{} ir := Paths{}
err := ir.Build(vn, idx) err := ir.Build(vn, idx)
if err != nil { if err != nil {
return err return err
} }
nr := low.NodeReference[*Paths]{Value: &ir, ValueNode: vn, KeyNode: ln} nr := low.NodeReference[*Paths]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Paths = nr doc.Paths = nr
} }
return nil return nil
} }
func extractWebhooks(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractWebhooks(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
hooks, hooksL, hooksN, eErr := low.ExtractMap[*PathItem](WebhooksLabel, info.RootNode, idx) hooks, hooksL, hooksN, eErr := low.ExtractMap[*PathItem](WebhooksLabel, info.RootNode, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }
if hooks != nil { if hooks != nil {
doc.Webhooks = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*PathItem]]{ doc.Webhooks = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*PathItem]]{
Value: hooks, Value: hooks,
KeyNode: hooksL, KeyNode: hooksL,
ValueNode: hooksN, ValueNode: hooksN,
} }
} }
return nil return nil
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,303 +4,303 @@
package libopenapi package libopenapi
import ( import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"io/ioutil" "io/ioutil"
"testing" "testing"
) )
func TestLoadDocument_Simple_V2(t *testing.T) { func TestLoadDocument_Simple_V2(t *testing.T) {
yml := `swagger: 2.0.1` yml := `swagger: 2.0.1`
doc, err := NewDocument([]byte(yml)) doc, err := NewDocument([]byte(yml))
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "2.0.1", doc.GetVersion()) assert.Equal(t, "2.0.1", doc.GetVersion())
v2Doc, docErr := doc.BuildV2Model() v2Doc, docErr := doc.BuildV2Model()
assert.Len(t, docErr, 0) assert.Len(t, docErr, 0)
assert.NotNil(t, v2Doc) assert.NotNil(t, v2Doc)
assert.NotNil(t, doc.GetSpecInfo()) assert.NotNil(t, doc.GetSpecInfo())
fmt.Print() fmt.Print()
} }
func TestLoadDocument_Simple_V2_Error(t *testing.T) { func TestLoadDocument_Simple_V2_Error(t *testing.T) {
yml := `swagger: 2.0` yml := `swagger: 2.0`
doc, err := NewDocument([]byte(yml)) doc, err := NewDocument([]byte(yml))
assert.NoError(t, err) assert.NoError(t, err)
v2Doc, docErr := doc.BuildV3Model() v2Doc, docErr := doc.BuildV3Model()
assert.Len(t, docErr, 1) assert.Len(t, docErr, 1)
assert.Nil(t, v2Doc) assert.Nil(t, v2Doc)
} }
func TestLoadDocument_Simple_V2_Error_BadSpec(t *testing.T) { func TestLoadDocument_Simple_V2_Error_BadSpec(t *testing.T) {
yml := `swagger: 2.0 yml := `swagger: 2.0
definitions: definitions:
thing: thing:
$ref: bork` $ref: bork`
doc, err := NewDocument([]byte(yml)) doc, err := NewDocument([]byte(yml))
assert.NoError(t, err) assert.NoError(t, err)
v2Doc, docErr := doc.BuildV2Model() v2Doc, docErr := doc.BuildV2Model()
assert.Len(t, docErr, 1) assert.Len(t, docErr, 2)
assert.Nil(t, v2Doc) assert.Nil(t, v2Doc)
} }
func TestLoadDocument_Simple_V3_Error(t *testing.T) { func TestLoadDocument_Simple_V3_Error(t *testing.T) {
yml := `openapi: 3.0.1` yml := `openapi: 3.0.1`
doc, err := NewDocument([]byte(yml)) doc, err := NewDocument([]byte(yml))
assert.NoError(t, err) assert.NoError(t, err)
v2Doc, docErr := doc.BuildV2Model() v2Doc, docErr := doc.BuildV2Model()
assert.Len(t, docErr, 1) assert.Len(t, docErr, 1)
assert.Nil(t, v2Doc) assert.Nil(t, v2Doc)
} }
func TestLoadDocument_Error_V2NoSpec(t *testing.T) { func TestLoadDocument_Error_V2NoSpec(t *testing.T) {
doc := new(document) // not how this should be instantiated. doc := new(document) // not how this should be instantiated.
_, err := doc.BuildV2Model() _, err := doc.BuildV2Model()
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestLoadDocument_Error_V3NoSpec(t *testing.T) { func TestLoadDocument_Error_V3NoSpec(t *testing.T) {
doc := new(document) // not how this should be instantiated. doc := new(document) // not how this should be instantiated.
_, err := doc.BuildV3Model() _, err := doc.BuildV3Model()
assert.Len(t, err, 1) assert.Len(t, err, 1)
} }
func TestLoadDocument_Empty(t *testing.T) { func TestLoadDocument_Empty(t *testing.T) {
yml := `` yml := ``
_, err := NewDocument([]byte(yml)) _, err := NewDocument([]byte(yml))
assert.Error(t, err) assert.Error(t, err)
} }
func TestLoadDocument_Simple_V3(t *testing.T) { func TestLoadDocument_Simple_V3(t *testing.T) {
yml := `openapi: 3.0.1` yml := `openapi: 3.0.1`
doc, err := NewDocument([]byte(yml)) doc, err := NewDocument([]byte(yml))
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "3.0.1", doc.GetVersion()) assert.Equal(t, "3.0.1", doc.GetVersion())
v3Doc, docErr := doc.BuildV3Model() v3Doc, docErr := doc.BuildV3Model()
assert.Len(t, docErr, 0) assert.Len(t, docErr, 0)
assert.NotNil(t, v3Doc) assert.NotNil(t, v3Doc)
} }
func TestLoadDocument_Simple_V3_Error_BadSpec(t *testing.T) { func TestLoadDocument_Simple_V3_Error_BadSpec(t *testing.T) {
yml := `openapi: 3.0 yml := `openapi: 3.0
paths: paths:
"/some": "/some":
$ref: bork` $ref: bork`
doc, err := NewDocument([]byte(yml)) doc, err := NewDocument([]byte(yml))
assert.NoError(t, err) assert.NoError(t, err)
v3Doc, docErr := doc.BuildV3Model() v3Doc, docErr := doc.BuildV3Model()
assert.Len(t, docErr, 1) assert.Len(t, docErr, 1)
assert.Nil(t, v3Doc) assert.Nil(t, v3Doc)
} }
func TestDocument_Serialize_Error(t *testing.T) { func TestDocument_Serialize_Error(t *testing.T) {
doc := new(document) // not how this should be instantiated. doc := new(document) // not how this should be instantiated.
_, err := doc.Serialize() _, err := doc.Serialize()
assert.Error(t, err) assert.Error(t, err)
} }
func TestDocument_Serialize(t *testing.T) { func TestDocument_Serialize(t *testing.T) {
yml := `openapi: 3.0 yml := `openapi: 3.0
info: info:
title: The magic API title: The magic API
` `
doc, _ := NewDocument([]byte(yml)) doc, _ := NewDocument([]byte(yml))
serial, err := doc.Serialize() serial, err := doc.Serialize()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, yml, string(serial)) assert.Equal(t, yml, string(serial))
} }
func TestDocument_Serialize_Modified(t *testing.T) { func TestDocument_Serialize_Modified(t *testing.T) {
yml := `openapi: 3.0 yml := `openapi: 3.0
info: info:
title: The magic API title: The magic API
` `
ymlModified := `openapi: 3.0 ymlModified := `openapi: 3.0
info: info:
title: The magic API - but now, altered! title: The magic API - but now, altered!
` `
doc, _ := NewDocument([]byte(yml)) doc, _ := NewDocument([]byte(yml))
v3Doc, _ := doc.BuildV3Model() v3Doc, _ := doc.BuildV3Model()
v3Doc.Model.Info.GoLow().Title.Mutate("The magic API - but now, altered!") v3Doc.Model.Info.GoLow().Title.Mutate("The magic API - but now, altered!")
serial, err := doc.Serialize() serial, err := doc.Serialize()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, ymlModified, string(serial)) assert.Equal(t, ymlModified, string(serial))
} }
func TestDocument_Serialize_JSON_Modified(t *testing.T) { func TestDocument_Serialize_JSON_Modified(t *testing.T) {
json := `{ 'openapi': '3.0', json := `{ 'openapi': '3.0',
'info': { 'info': {
'title': 'The magic API' 'title': 'The magic API'
} }
} }
` `
jsonModified := `{"info":{"title":"The magic API - but now, altered!"},"openapi":"3.0"}` jsonModified := `{"info":{"title":"The magic API - but now, altered!"},"openapi":"3.0"}`
doc, _ := NewDocument([]byte(json)) doc, _ := NewDocument([]byte(json))
v3Doc, _ := doc.BuildV3Model() v3Doc, _ := doc.BuildV3Model()
// eventually this will be encapsulated up high. // eventually this will be encapsulated up high.
// mutation does not replace low model, eventually pointers will be used. // mutation does not replace low model, eventually pointers will be used.
newTitle := v3Doc.Model.Info.GoLow().Title.Mutate("The magic API - but now, altered!") newTitle := v3Doc.Model.Info.GoLow().Title.Mutate("The magic API - but now, altered!")
v3Doc.Model.Info.GoLow().Title = newTitle v3Doc.Model.Info.GoLow().Title = newTitle
assert.Equal(t, "The magic API - but now, altered!", v3Doc.Model.Info.GoLow().Title.Value) assert.Equal(t, "The magic API - but now, altered!", v3Doc.Model.Info.GoLow().Title.Value)
serial, err := doc.Serialize() serial, err := doc.Serialize()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, jsonModified, string(serial)) assert.Equal(t, jsonModified, string(serial))
} }
func ExampleNewDocument_fromOpenAPI3Document() { func ExampleNewDocument_fromOpenAPI3Document() {
// How to read in an OpenAPI 3 Specification, into a Document. // How to read in an OpenAPI 3 Specification, into a Document.
// load an OpenAPI 3 specification from bytes // load an OpenAPI 3 specification from bytes
petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json") petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json")
// create a new document from specification bytes // create a new document from specification bytes
document, err := NewDocument(petstore) document, err := NewDocument(petstore)
// if anything went wrong, an error is thrown // if anything went wrong, an error is thrown
if err != nil { if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err)) panic(fmt.Sprintf("cannot create new document: %e", err))
} }
// because we know this is a v3 spec, we can build a ready to go model from it. // because we know this is a v3 spec, we can build a ready to go model from it.
v3Model, errors := document.BuildV3Model() v3Model, errors := document.BuildV3Model()
// if anything went wrong when building the v3 model, a slice of errors will be returned // if anything went wrong when building the v3 model, a slice of errors will be returned
if len(errors) > 0 { if len(errors) > 0 {
for i := range errors { for i := range errors {
fmt.Printf("error: %e\n", errors[i]) fmt.Printf("error: %e\n", errors[i])
} }
panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
} }
// get a count of the number of paths and schemas. // get a count of the number of paths and schemas.
paths := len(v3Model.Model.Paths.PathItems) paths := len(v3Model.Model.Paths.PathItems)
schemas := len(v3Model.Model.Components.Schemas) schemas := len(v3Model.Model.Components.Schemas)
// print the number of paths and schemas in the document // print the number of paths and schemas in the document
fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas)
// Output: There are 13 paths and 8 schemas in the document // Output: There are 13 paths and 8 schemas in the document
} }
func ExampleNewDocument_fromSwaggerDocument() { func ExampleNewDocument_fromSwaggerDocument() {
// How to read in a Swagger / OpenAPI 2 Specification, into a Document. // How to read in a Swagger / OpenAPI 2 Specification, into a Document.
// load a Swagger specification from bytes // load a Swagger specification from bytes
petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json") petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json")
// create a new document from specification bytes // create a new document from specification bytes
document, err := NewDocument(petstore) document, err := NewDocument(petstore)
// if anything went wrong, an error is thrown // if anything went wrong, an error is thrown
if err != nil { if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err)) panic(fmt.Sprintf("cannot create new document: %e", err))
} }
// because we know this is a v2 spec, we can build a ready to go model from it. // because we know this is a v2 spec, we can build a ready to go model from it.
v2Model, errors := document.BuildV2Model() v2Model, errors := document.BuildV2Model()
// if anything went wrong when building the v3 model, a slice of errors will be returned // if anything went wrong when building the v3 model, a slice of errors will be returned
if len(errors) > 0 { if len(errors) > 0 {
for i := range errors { for i := range errors {
fmt.Printf("error: %e\n", errors[i]) fmt.Printf("error: %e\n", errors[i])
} }
panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
} }
// get a count of the number of paths and schemas. // get a count of the number of paths and schemas.
paths := len(v2Model.Model.Paths.PathItems) paths := len(v2Model.Model.Paths.PathItems)
schemas := len(v2Model.Model.Definitions.Definitions) schemas := len(v2Model.Model.Definitions.Definitions)
// print the number of paths and schemas in the document // print the number of paths and schemas in the document
fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas)
// Output: There are 14 paths and 6 schemas in the document // Output: There are 14 paths and 6 schemas in the document
} }
func ExampleNewDocument_fromUnknownVersion() { func ExampleNewDocument_fromUnknownVersion() {
// load an unknown version of an OpenAPI spec // load an unknown version of an OpenAPI spec
petstore, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml") petstore, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml")
// create a new document from specification bytes // create a new document from specification bytes
document, err := NewDocument(petstore) document, err := NewDocument(petstore)
// if anything went wrong, an error is thrown // if anything went wrong, an error is thrown
if err != nil { if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err)) panic(fmt.Sprintf("cannot create new document: %e", err))
} }
var paths, schemas int var paths, schemas int
var errors []error var errors []error
// We don't know which type of document this is, so we can use the spec info to inform us // We don't know which type of document this is, so we can use the spec info to inform us
if document.GetSpecInfo().SpecType == utils.OpenApi3 { if document.GetSpecInfo().SpecType == utils.OpenApi3 {
v3Model, errs := document.BuildV3Model() v3Model, errs := document.BuildV3Model()
if len(errs) > 0 { if len(errs) > 0 {
errors = errs errors = errs
} }
if len(errors) <= 0 { if len(errors) <= 0 {
paths = len(v3Model.Model.Paths.PathItems) paths = len(v3Model.Model.Paths.PathItems)
schemas = len(v3Model.Model.Components.Schemas) schemas = len(v3Model.Model.Components.Schemas)
} }
} }
if document.GetSpecInfo().SpecType == utils.OpenApi2 { if document.GetSpecInfo().SpecType == utils.OpenApi2 {
v2Model, errs := document.BuildV2Model() v2Model, errs := document.BuildV2Model()
if len(errs) > 0 { if len(errs) > 0 {
errors = errs errors = errs
} }
if len(errors) <= 0 { if len(errors) <= 0 {
paths = len(v2Model.Model.Paths.PathItems) paths = len(v2Model.Model.Paths.PathItems)
schemas = len(v2Model.Model.Definitions.Definitions) schemas = len(v2Model.Model.Definitions.Definitions)
} }
} }
// if anything went wrong when building the model, report errors. // if anything went wrong when building the model, report errors.
if len(errors) > 0 { if len(errors) > 0 {
for i := range errors { for i := range errors {
fmt.Printf("error: %e\n", errors[i]) fmt.Printf("error: %e\n", errors[i])
} }
panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
} }
// print the number of paths and schemas in the document // print the number of paths and schemas in the document
fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas)
// Output: There are 5 paths and 6 schemas in the document // Output: There are 5 paths and 6 schemas in the document
} }
func ExampleNewDocument_mutateValuesAndSerialize() { func ExampleNewDocument_mutateValuesAndSerialize() {
// How to mutate values in an OpenAPI Specification, without re-ordering original content. // How to mutate values in an OpenAPI Specification, without re-ordering original content.
// create very small, and useless spec that does nothing useful, except showcase this feature. // create very small, and useless spec that does nothing useful, except showcase this feature.
spec := ` spec := `
openapi: 3.1.0 openapi: 3.1.0
info: info:
title: This is a title title: This is a title
@@ -310,155 +310,155 @@ info:
license: license:
url: http://some-place-on-the-internet.com/license url: http://some-place-on-the-internet.com/license
` `
// create a new document from specification bytes // create a new document from specification bytes
document, err := NewDocument([]byte(spec)) document, err := NewDocument([]byte(spec))
// if anything went wrong, an error is thrown // if anything went wrong, an error is thrown
if err != nil { if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err)) panic(fmt.Sprintf("cannot create new document: %e", err))
} }
// because we know this is a v3 spec, we can build a ready to go model from it. // because we know this is a v3 spec, we can build a ready to go model from it.
v3Model, errors := document.BuildV3Model() v3Model, errors := document.BuildV3Model()
// if anything went wrong when building the v3 model, a slice of errors will be returned // if anything went wrong when building the v3 model, a slice of errors will be returned
if len(errors) > 0 { if len(errors) > 0 {
for i := range errors { for i := range errors {
fmt.Printf("error: %e\n", errors[i]) fmt.Printf("error: %e\n", errors[i])
} }
panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
} }
// mutate the title, to do this we currently need to drop down to the low-level API. // mutate the title, to do this we currently need to drop down to the low-level API.
v3Model.Model.GoLow().Info.Value.Title.Mutate("A new title for a useless spec") v3Model.Model.GoLow().Info.Value.Title.Mutate("A new title for a useless spec")
// mutate the email address in the contact object. // mutate the email address in the contact object.
v3Model.Model.GoLow().Info.Value.Contact.Value.Email.Mutate("buckaroo@pb33f.io") v3Model.Model.GoLow().Info.Value.Contact.Value.Email.Mutate("buckaroo@pb33f.io")
// mutate the name in the contact object. // mutate the name in the contact object.
v3Model.Model.GoLow().Info.Value.Contact.Value.Name.Mutate("Buckaroo") v3Model.Model.GoLow().Info.Value.Contact.Value.Name.Mutate("Buckaroo")
// mutate the URL for the license object. // mutate the URL for the license object.
v3Model.Model.GoLow().Info.Value.License.Value.URL.Mutate("https://pb33f.io/license") v3Model.Model.GoLow().Info.Value.License.Value.URL.Mutate("https://pb33f.io/license")
// serialize the document back into the original YAML or JSON // serialize the document back into the original YAML or JSON
mutatedSpec, serialError := document.Serialize() mutatedSpec, serialError := document.Serialize()
// if something went wrong serializing // if something went wrong serializing
if serialError != nil { if serialError != nil {
panic(fmt.Sprintf("cannot serialize document: %e", serialError)) panic(fmt.Sprintf("cannot serialize document: %e", serialError))
} }
// print our modified spec! // print our modified spec!
fmt.Println(string(mutatedSpec)) fmt.Println(string(mutatedSpec))
// Output: openapi: 3.1.0 // Output: openapi: 3.1.0
//info: //info:
// title: A new title for a useless spec // title: A new title for a useless spec
// contact: // contact:
// name: Buckaroo // name: Buckaroo
// email: buckaroo@pb33f.io // email: buckaroo@pb33f.io
// license: // license:
// url: https://pb33f.io/license // url: https://pb33f.io/license
} }
func ExampleCompareDocuments_openAPI() { func ExampleCompareDocuments_openAPI() {
// How to compare two different OpenAPI specifications. // How to compare two different OpenAPI specifications.
// load an original OpenAPI 3 specification from bytes // load an original OpenAPI 3 specification from bytes
burgerShopOriginal, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml") burgerShopOriginal, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml")
// load an **updated** OpenAPI 3 specification from bytes // load an **updated** OpenAPI 3 specification from bytes
burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml") burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml")
// create a new document from original specification bytes // create a new document from original specification bytes
originalDoc, err := NewDocument(burgerShopOriginal) originalDoc, err := NewDocument(burgerShopOriginal)
// if anything went wrong, an error is thrown // if anything went wrong, an error is thrown
if err != nil { if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err)) panic(fmt.Sprintf("cannot create new document: %e", err))
} }
// create a new document from updated specification bytes // create a new document from updated specification bytes
updatedDoc, err := NewDocument(burgerShopUpdated) updatedDoc, err := NewDocument(burgerShopUpdated)
// if anything went wrong, an error is thrown // if anything went wrong, an error is thrown
if err != nil { if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err)) panic(fmt.Sprintf("cannot create new document: %e", err))
} }
// Compare documents for all changes made // Compare documents for all changes made
documentChanges, errs := CompareDocuments(originalDoc, updatedDoc) documentChanges, errs := CompareDocuments(originalDoc, updatedDoc)
// If anything went wrong when building models for documents. // If anything went wrong when building models for documents.
if len(errs) > 0 { if len(errs) > 0 {
for i := range errs { for i := range errs {
fmt.Printf("error: %e\n", errs[i]) fmt.Printf("error: %e\n", errs[i])
} }
panic(fmt.Sprintf("cannot compare documents: %d errors reported", len(errs))) panic(fmt.Sprintf("cannot compare documents: %d errors reported", len(errs)))
} }
// Extract SchemaChanges from components changes. // Extract SchemaChanges from components changes.
schemaChanges := documentChanges.ComponentsChanges.SchemaChanges schemaChanges := documentChanges.ComponentsChanges.SchemaChanges
// Print out some interesting stats about the OpenAPI document changes. // Print out some interesting stats about the OpenAPI document changes.
fmt.Printf("There are %d changes, of which %d are breaking. %v schemas have changes.", fmt.Printf("There are %d changes, of which %d are breaking. %v schemas have changes.",
documentChanges.TotalChanges(), documentChanges.TotalBreakingChanges(), len(schemaChanges)) documentChanges.TotalChanges(), documentChanges.TotalBreakingChanges(), len(schemaChanges))
//Output: There are 67 changes, of which 17 are breaking. 5 schemas have changes. //Output: There are 67 changes, of which 17 are breaking. 5 schemas have changes.
} }
func ExampleCompareDocuments_swagger() { func ExampleCompareDocuments_swagger() {
// How to compare two different Swagger specifications. // How to compare two different Swagger specifications.
// load an original OpenAPI 3 specification from bytes // load an original OpenAPI 3 specification from bytes
petstoreOriginal, _ := ioutil.ReadFile("test_specs/petstorev2-complete.yaml") petstoreOriginal, _ := ioutil.ReadFile("test_specs/petstorev2-complete.yaml")
// load an **updated** OpenAPI 3 specification from bytes // load an **updated** OpenAPI 3 specification from bytes
petstoreUpdated, _ := ioutil.ReadFile("test_specs/petstorev2-complete-modified.yaml") petstoreUpdated, _ := ioutil.ReadFile("test_specs/petstorev2-complete-modified.yaml")
// create a new document from original specification bytes // create a new document from original specification bytes
originalDoc, err := NewDocument(petstoreOriginal) originalDoc, err := NewDocument(petstoreOriginal)
// if anything went wrong, an error is thrown // if anything went wrong, an error is thrown
if err != nil { if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err)) panic(fmt.Sprintf("cannot create new document: %e", err))
} }
// create a new document from updated specification bytes // create a new document from updated specification bytes
updatedDoc, err := NewDocument(petstoreUpdated) updatedDoc, err := NewDocument(petstoreUpdated)
// if anything went wrong, an error is thrown // if anything went wrong, an error is thrown
if err != nil { if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err)) panic(fmt.Sprintf("cannot create new document: %e", err))
} }
// Compare documents for all changes made // Compare documents for all changes made
documentChanges, errs := CompareDocuments(originalDoc, updatedDoc) documentChanges, errs := CompareDocuments(originalDoc, updatedDoc)
// If anything went wrong when building models for documents. // If anything went wrong when building models for documents.
if len(errs) > 0 { if len(errs) > 0 {
for i := range errs { for i := range errs {
fmt.Printf("error: %e\n", errs[i]) fmt.Printf("error: %e\n", errs[i])
} }
panic(fmt.Sprintf("cannot compare documents: %d errors reported", len(errs))) panic(fmt.Sprintf("cannot compare documents: %d errors reported", len(errs)))
} }
// Extract SchemaChanges from components changes. // Extract SchemaChanges from components changes.
schemaChanges := documentChanges.ComponentsChanges.SchemaChanges schemaChanges := documentChanges.ComponentsChanges.SchemaChanges
// Print out some interesting stats about the Swagger document changes. // Print out some interesting stats about the Swagger document changes.
fmt.Printf("There are %d changes, of which %d are breaking. %v schemas have changes.", fmt.Printf("There are %d changes, of which %d are breaking. %v schemas have changes.",
documentChanges.TotalChanges(), documentChanges.TotalBreakingChanges(), len(schemaChanges)) documentChanges.TotalChanges(), documentChanges.TotalBreakingChanges(), len(schemaChanges))
//Output: There are 52 changes, of which 27 are breaking. 5 schemas have changes. //Output: There are 52 changes, of which 27 are breaking. 5 schemas have changes.
} }
func TestDocument_Paths_As_Array(t *testing.T) { func TestDocument_Paths_As_Array(t *testing.T) {
// paths can now be wrapped in an array. // paths can now be wrapped in an array.
spec := `{ spec := `{
"openapi": "3.1.0", "openapi": "3.1.0",
"paths": [ "paths": [
"/": { "/": {
@@ -467,13 +467,13 @@ func TestDocument_Paths_As_Array(t *testing.T) {
] ]
} }
` `
// create a new document from specification bytes // create a new document from specification bytes
doc, err := NewDocument([]byte(spec)) doc, err := NewDocument([]byte(spec))
// if anything went wrong, an error is thrown // if anything went wrong, an error is thrown
if err != nil { if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err)) panic(fmt.Sprintf("cannot create new document: %e", err))
} }
v3Model, _ := doc.BuildV3Model() v3Model, _ := doc.BuildV3Model()
assert.NotNil(t, v3Model) assert.NotNil(t, v3Model)
} }

View File

@@ -0,0 +1,51 @@
swagger: "2.0"
paths:
/burgers:
post:
responses:
200:
schema:
$ref: '#/definitions/Nine'
definitions:
One:
description: "test one"
properties:
things:
"$ref": "#/definitions/Two"
Two:
description: "test two"
properties:
testThing:
"$ref": "#/definitions/One"
Three:
description: "test three"
properties:
tester:
"$ref": "#/definitions/Four"
bester:
"$ref": "#/definitions/Seven"
yester:
"$ref": "#/definitions/Seven"
Four:
description: "test four"
properties:
lemons:
"$ref": "#/definitions/Nine"
Five:
properties:
rice:
"$ref": "#/definitions/Six"
Six:
properties:
mints:
"$ref": "#/definitions/Nine"
Seven:
properties:
wow:
"$ref": "#/definitions/Three"
Nine:
description: done.
Ten:
properties:
yeah:
"$ref": "#/definitions/Ten"