2.0 model at 90% coverage

Error handling still required, but a nice jump.
This commit is contained in:
Dave Shanley
2022-09-05 12:22:04 -04:00
parent 1814ac58eb
commit eb7e60c412
13 changed files with 438 additions and 310 deletions

View File

@@ -36,6 +36,13 @@ func (t *Tag) GoLow() *low.Tag {
return t.low
}
func (t *Tag) SetName(value string) {
t.GoLow().Name.ValueNode.Value = value
}
func (t *Tag) SetDescription(value string) {
t.GoLow().Description.ValueNode.Value = value
}
//func (t *Tag) MarshalYAML() (interface{}, error) {
// m := make(map[string]interface{})
// for i := range t.Extensions {

View File

@@ -6,10 +6,13 @@ package v2
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
const (
ExamplesLabel = "examples"
)
type Examples struct {
Values map[low.KeyReference[string]]low.ValueReference[any]
}
@@ -17,13 +20,13 @@ type Examples struct {
func (e *Examples) Build(root *yaml.Node, _ *index.SpecIndex) error {
var keyNode, currNode *yaml.Node
var err error
e.Values = make(map[low.KeyReference[string]]low.ValueReference[any])
for i := range root.Content {
if i%2 == 0 {
keyNode = root.Content[i]
continue
}
currNode = root.Content[i]
if utils.IsNodeMap(currNode) {
var n map[string]interface{}
err = currNode.Decode(&n)
if err != nil {
@@ -58,7 +61,7 @@ func (e *Examples) Build(root *yaml.Node, _ *index.SpecIndex) error {
Value: n,
ValueNode: currNode,
}
}
}
return nil
}

View File

@@ -68,7 +68,7 @@ func (i *Items) Build(root *yaml.Node, idx *index.SpecIndex) error {
}
return nil
}
i.Default.Value = low.NodeReference[any]{
i.Default = low.NodeReference[any]{
Value: n,
KeyNode: ln,
ValueNode: vn,

View File

@@ -4,10 +4,8 @@
package v2
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"strings"
"sync"
@@ -58,10 +56,6 @@ func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
skip = true
continue
}
if strings.HasPrefix(strings.ToLower(pathNode.Value), "parameters") {
skip = true
continue
}
// because (for some reason) the spec for swagger docs allows for a '$ref' property for path items.
// this is kinda nuts, because '$ref' is a reserved keyword for JSON references, which is ALSO used
// in swagger. Why this choice was made, I do not know.
@@ -108,21 +102,6 @@ func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
wg.Add(1)
if ok, _, _ := utils.IsNodeRefValue(pathNode); ok {
r, err := low.LocateRefNode(pathNode, idx)
if r != nil {
pathNode = r
if err != nil {
if !idx.AllowCircularReferenceResolving() {
return fmt.Errorf("build schema failed: %s", err.Error())
}
}
} else {
return fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d",
pathNode.Content[1].Value, pathNode.Content[1].Line, pathNode.Content[1].Column)
}
}
go low.BuildModelAsync(pathNode, &op, &wg, &errors)
opRef := low.NodeReference[*Operation]{

View File

@@ -4,10 +4,8 @@
package v2
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"strings"
)
@@ -21,7 +19,6 @@ const (
DeleteLabel = "delete"
OptionsLabel = "options"
HeadLabel = "head"
TraceLabel = "trace"
)
type Paths struct {
@@ -58,23 +55,6 @@ func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error {
bChan := make(chan pathBuildResult)
eChan := make(chan error)
var buildPathItem = func(cNode, pNode *yaml.Node, b chan<- pathBuildResult, e chan<- error) {
if ok, _, _ := utils.IsNodeRefValue(pNode); ok {
r, err := low.LocateRefNode(pNode, idx)
if r != nil {
pNode = r
if err != nil {
if !idx.AllowCircularReferenceResolving() {
e <- fmt.Errorf("path item build failed: %s", err.Error())
return
}
}
} else {
e <- fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d",
pNode.Content[1].Value, pNode.Content[1].Line, pNode.Content[1].Column)
return
}
}
path := new(PathItem)
_ = low.BuildModel(pNode, path)
err := path.Build(pNode, idx)

View File

@@ -40,6 +40,13 @@ func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error {
r.Schema = *s
}
// extract examples
examples, expErr := low.ExtractObject[*Examples](ExamplesLabel, root, idx)
if expErr != nil {
return expErr
}
r.Examples = examples
//extract headers
headers, lN, kN, err := low.ExtractMapFlat[*Header](HeadersLabel, root, idx)
if err != nil {

View File

@@ -90,6 +90,7 @@ func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) {
case <-doneChan:
completedExtractions++
case e := <-errChan:
completedExtractions++
errors = append(errors, e)
}
}

View File

@@ -4,6 +4,7 @@
package v2
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel"
"github.com/stretchr/testify/assert"
"io/ioutil"
@@ -28,7 +29,8 @@ func initTest() {
}
}
if err != nil {
panic("broken something")
fmt.Print(err)
panic(err)
}
}
@@ -51,7 +53,7 @@ func TestCreateDocument(t *testing.T) {
assert.Len(t, doc.Schemes.Value, 2)
assert.Len(t, doc.Definitions.Value.Schemas, 6)
assert.Len(t, doc.SecurityDefinitions.Value.Definitions, 2)
assert.Len(t, doc.Paths.Value.PathItems, 14)
assert.Len(t, doc.Paths.Value.PathItems, 15)
assert.Len(t, doc.Responses.Value.Definitions, 2)
assert.Equal(t, "http://swagger.io", doc.ExternalDocs.Value.URL.Value)
assert.Equal(t, true, doc.FindExtension("x-pet").Value)
@@ -70,6 +72,8 @@ func TestCreateDocument_Parameters(t *testing.T) {
simpleParam := doc.Parameters.Value.FindParameter("simpleParam")
assert.NotNil(t, simpleParam)
assert.Equal(t, "simple", simpleParam.Value.Name.Value)
assert.Equal(t, "nuggets", simpleParam.Value.FindExtension("x-chicken").Value)
}
func TestCreateDocument_Tags(t *testing.T) {
@@ -91,3 +95,62 @@ func TestCreateDocument_SecurityDefinitions(t *testing.T) {
assert.Len(t, petStoreAuth.Value.Scopes.Value.Values, 2)
assert.Equal(t, "read your pets", petStoreAuth.Value.Scopes.Value.FindScope("read:pets").Value)
}
func TestCreateDocument_Definitions(t *testing.T) {
initTest()
apiResp := doc.Definitions.Value.FindSchema("ApiResponse").Value.Schema()
assert.NotNil(t, apiResp)
assert.Len(t, apiResp.Properties.Value, 3)
assert.Equal(t, "integer", apiResp.FindProperty("code").Value.Schema().Type.Value)
pet := doc.Definitions.Value.FindSchema("Pet").Value.Schema()
assert.NotNil(t, pet)
assert.Len(t, pet.Required.Value, 2)
// 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)
// check enums
assert.Len(t, pet.FindProperty("status").Value.Schema().Enum.Value, 3)
}
func TestCreateDocument_ResponseDefinitions(t *testing.T) {
initTest()
apiResp := doc.Responses.Value.FindResponse("200")
assert.NotNil(t, apiResp)
assert.Equal(t, "OK", apiResp.Value.Description.Value)
assert.Equal(t, "morning", apiResp.Value.FindExtension("x-coffee").Value)
header := apiResp.Value.FindHeader("noHeader")
assert.NotNil(t, header)
assert.True(t, header.Value.FindExtension("x-empty").Value.(bool))
header = apiResp.Value.FindHeader("myHeader")
if k, ok := header.Value.Items.Value.Default.Value.(map[string]interface{}); ok {
assert.Equal(t, "here", k["something"])
} else {
panic("should not fail.")
}
if k, ok := header.Value.Items.Value.Items.Value.Default.Value.([]interface{}); ok {
assert.Len(t, k, 2)
assert.Equal(t, "two", k[1])
} else {
panic("should not fail.")
}
header = apiResp.Value.FindHeader("yourHeader")
assert.Equal(t, "somethingSimple", header.Value.Items.Value.Default.Value)
}
func TestCreateDocument_Paths(t *testing.T) {
initTest()
uploadImage := doc.Paths.Value.FindPath("/pet/{petId}/uploadImage").Value
assert.NotNil(t, uploadImage)
assert.Nil(t, doc.Paths.Value.FindPath("/nothing-nowhere-nohow"))
assert.Equal(t, "man", uploadImage.FindExtension("x-potato").Value)
assert.Equal(t, "fresh", doc.Paths.Value.FindExtension("x-minty").Value)
assert.Equal(t, "successful operation",
uploadImage.Post.Value.Responses.Value.FindResponseByCode("200").Value.Description.Value)
}

View File

@@ -21,3 +21,18 @@ type Document struct {
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Index *index.SpecIndex
}
//
//func (d *Document) AddTag() *base.Tag {
// t := base.NewTag()
// //d.Tags.KeyNode
// t.Name.Value = "nice new tag"
//
// dat, _ := yaml.Marshal(t)
// var inject yaml.Node
// _ = yaml.Unmarshal(dat, &inject)
//
// d.Tags.ValueNode.Content = append(d.Tags.ValueNode.Content, inject.Content[0])
//
// return t
//}

View File

@@ -12,6 +12,8 @@ import (
const (
TagsLabel = "tags"
ExternalDocsLabel = "externalDocs"
NameLabel = "name"
DescriptionLabel = "description"
)
type Tag struct {
@@ -33,3 +35,24 @@ func (t *Tag) Build(root *yaml.Node, idx *index.SpecIndex) error {
t.ExternalDocs = extDocs
return err
}
//func (t *Tag) MarshalYAML() (interface{}, error) {
// m := make(map[string]interface{})
// for i := range t.Extensions {
// m[i.Value] = t.Extensions[i].Value
// }
// if t.Name.Value != "" {
// m[NameLabel] = t.Name.Value
// }
// if t.Description.Value != "" {
// m[DescriptionLabel] = t.Description.Value
// }
// if t.ExternalDocs.Value != nil {
// m[ExternalDocsLabel] = t.ExternalDocs.Value
// }
// return m, nil
//}
//
//func NewTag() *Tag {
// return new(Tag)
//}

View File

@@ -36,16 +36,16 @@ tags:
return
}
highDoc := high.NewDocument(lowDoc)
fmt.Println(highDoc.Info.Title)
highDoc.Info.GoLow().Title.ValueNode.Value = "let's hack this shizzle"
highDoc.Info.GoLow().Title.ValueNode.Value = "let's hack this"
highDoc.Tags[0].SetName("We are a new name now")
highDoc.Tags[0].SetDescription("and a new description")
//newTag := lowDoc.AddTag()
//fmt.Println(newTag)
modified, _ := yaml.Marshal(info.RootNode)
fmt.Println(string(modified))
d, _ := yaml.Marshal(highDoc.Tags[0])
fmt.Println(d)
os.Remove("sample.yaml")
}

View File

@@ -1,11 +0,0 @@
openapi: 3.0.1
info:
title: this is a title
description: this is a description
tags:
- name: Tag A
description: cake
x-hack: true
- name: Tag B
description: coffee
x-code: hack

View File

@@ -20,6 +20,7 @@ parameters:
in: query
name: simple
type: string
x-chicken: nuggets
tags:
- name: pet
description: Everything about your Pets
@@ -37,7 +38,9 @@ schemes:
- https
- http
paths:
x-minty: fresh
"/pet/{petId}/uploadImage":
x-potato: man
post:
tags:
- pet
@@ -67,15 +70,6 @@ paths:
type: file
responses:
'200':
headers:
myHeader:
type: array
description: myHeader
items:
type: string
collectionFormat: csv
default: [ one, two, three ]
enum: [99,100,101]
description: successful operation
schema:
"$ref": "#/definitions/ApiResponse"
@@ -574,6 +568,28 @@ paths:
default:
description: successful operation
"/user":
borked:
summary: not allowed
options:
summary: hola!
responses:
default:
description: hello there
delete:
summary: sup!
responses:
default:
description: hello there
head:
summary: yo!
responses:
default:
description: hello there
patch:
summary: hi!
responses:
default:
description: hello there
post:
tags:
- user
@@ -595,6 +611,8 @@ paths:
responses:
default:
description: successful operation
"/ref":
$ref: "#/externalPaths/test"
securityDefinitions:
api_key:
type: apiKey
@@ -609,7 +627,46 @@ securityDefinitions:
write:pets: modify pets in your account
responses:
"200":
x-coffee: morning
description: OK
headers:
noHeader:
type: string
x-empty: true
items:
type: string
yourHeader:
type: string
default: yourHeader
items:
default: somethingSimple
ourHeader:
type: string
default:
funny: bunny
myHeader:
type: array
description: myHeader
items:
type: string
default:
something: here
items:
default:
- one
- two
collectionFormat: csv
default: [ one, two, three ]
enum: [ 99,100,101 ]
examples:
"application/json":
one: two
three: four
"text/xml":
- one
- two
- three
"text/plain": something else.
schema:
$ref: '#/definitions/ApiResponse'
"500":
@@ -739,3 +796,7 @@ definitions:
externalDocs:
description: Find out more about Swagger
url: http://swagger.io
externalPaths:
# this is illegal
test:
- summary: this is illegal.