mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-10 04:20:24 +00:00
2.0 model at 90% coverage
Error handling still required, but a nice jump.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -90,6 +90,7 @@ func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) {
|
||||
case <-doneChan:
|
||||
completedExtractions++
|
||||
case e := <-errChan:
|
||||
completedExtractions++
|
||||
errors = append(errors, e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
//}
|
||||
|
||||
@@ -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)
|
||||
//}
|
||||
|
||||
@@ -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")
|
||||
|
||||
}
|
||||
|
||||
11
sample.yaml
11
sample.yaml
@@ -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
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user