Added tag model build out with tests.

Working through patterns and re-applying them as I go, cleaning things up as I cook.
This commit is contained in:
Dave Shanley
2022-07-31 12:04:15 -04:00
parent 23b0357aa0
commit 2f60694047
10 changed files with 1141 additions and 838 deletions

View File

@@ -1,17 +1,17 @@
package v3 package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
) )
type Document struct { type Document struct {
Version low.NodeReference[string] Version low.NodeReference[string]
Info low.NodeReference[*Info] Info low.NodeReference[*Info]
Servers []low.NodeReference[*Server] Servers []low.NodeReference[*Server]
Paths *Paths Paths *Paths
Components *Components Components *Components
Security []*SecurityRequirement Security []*SecurityRequirement
Tags []*Tag Tags []low.NodeReference[*Tag]
ExternalDocs *ExternalDoc ExternalDocs *ExternalDoc
Extensions map[string]low.ObjectReference Extensions map[string]low.ObjectReference
} }

View File

@@ -1,13 +1,11 @@
package v3 package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
) )
type ExternalDoc struct { type ExternalDoc struct {
Node *yaml.Node Description low.NodeReference[string]
Description low.NodeReference[string] URL low.NodeReference[string]
URL low.NodeReference[string] Extensions map[string]low.ObjectReference
Extensions map[string]low.ObjectReference
} }

View File

@@ -1,49 +1,53 @@
package v3 package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
)
const (
Variables = "variables"
) )
type Server struct { type Server struct {
URL low.NodeReference[string] URL low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]
Variables low.NodeReference[*map[string]low.NodeReference[*ServerVariable]] Variables low.NodeReference[map[string]low.NodeReference[*ServerVariable]]
} }
func (s *Server) Build(root *yaml.Node) error { func (s *Server) Build(root *yaml.Node) error {
kn, vars := utils.FindKeyNode("variables", root.Content) kn, vars := utils.FindKeyNode(Variables, root.Content)
if vars == nil { if vars == nil {
return nil return nil
} }
variablesMap := make(map[string]low.NodeReference[*ServerVariable]) variablesMap := make(map[string]low.NodeReference[*ServerVariable])
if utils.IsNodeMap(vars) { if utils.IsNodeMap(vars) {
var currentNode string var currentNode string
var keyNode *yaml.Node var keyNode *yaml.Node
for i, varNode := range vars.Content { for i, varNode := range vars.Content {
if i%2 == 0 { if i%2 == 0 {
currentNode = varNode.Value currentNode = varNode.Value
keyNode = varNode keyNode = varNode
continue continue
} }
variable := ServerVariable{} variable := ServerVariable{}
err := datamodel.BuildModel(varNode, &variable) err := datamodel.BuildModel(varNode, &variable)
if err != nil { if err != nil {
return err return err
} }
variablesMap[currentNode] = low.NodeReference[*ServerVariable]{ variablesMap[currentNode] = low.NodeReference[*ServerVariable]{
ValueNode: varNode, ValueNode: varNode,
KeyNode: keyNode, KeyNode: keyNode,
Value: &variable, Value: &variable,
} }
} }
s.Variables = low.NodeReference[*map[string]low.NodeReference[*ServerVariable]]{ s.Variables = low.NodeReference[map[string]low.NodeReference[*ServerVariable]]{
KeyNode: kn, KeyNode: kn,
ValueNode: vars, ValueNode: vars,
Value: &variablesMap, Value: variablesMap,
} }
} }
return nil return nil
} }

View File

@@ -1,14 +1,44 @@
package v3 package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel"
"gopkg.in/yaml.v3" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
const (
Tags = "tags"
ExternalDocs = "externalDocs"
) )
type Tag struct { type Tag struct {
Node *yaml.Node Name low.NodeReference[string]
Name low.NodeReference[string] Description low.NodeReference[string]
Description low.NodeReference[string] ExternalDocs low.NodeReference[*ExternalDoc]
ExternalDocs ExternalDoc Extensions map[low.NodeReference[string]]low.NodeReference[any]
Extensions map[string]low.ObjectReference }
func (t *Tag) Build(root *yaml.Node) error {
_, ln, exDocs := utils.FindKeyNodeFull(ExternalDocs, root.Content)
// extract extensions
extensionMap, err := datamodel.ExtractExtensions(root)
if err != nil {
return err
}
t.Extensions = extensionMap
// extract external docs
var externalDoc ExternalDoc
err = datamodel.BuildModel(exDocs, &externalDoc)
if err != nil {
return err
}
t.ExternalDocs = low.NodeReference[*ExternalDoc]{
Value: &externalDoc,
KeyNode: ln,
ValueNode: exDocs,
}
return nil
} }

View File

@@ -3,21 +3,21 @@ package low
import "gopkg.in/yaml.v3" import "gopkg.in/yaml.v3"
type HasNode interface { type HasNode interface {
GetNode() *yaml.Node GetNode() *yaml.Node
} }
type Buildable interface { type Buildable interface {
Build(node *yaml.Node) error Build(node *yaml.Node) error
} }
type NodeReference[T any] struct { type NodeReference[T any] struct {
Value T Value T
ValueNode *yaml.Node ValueNode *yaml.Node
KeyNode *yaml.Node KeyNode *yaml.Node
} }
type ObjectReference struct { type ObjectReference struct {
Value map[string]interface{} Value interface{}
ValueNode *yaml.Node ValueNode *yaml.Node
KeyNode *yaml.Node KeyNode *yaml.Node
} }

View File

@@ -1,354 +1,411 @@
package datamodel package datamodel
import ( import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"reflect" "reflect"
"strconv" "strconv"
"sync" "sync"
) )
func BuildModel(node *yaml.Node, model interface{}) error { func BuildModel(node *yaml.Node, model interface{}) error {
if reflect.ValueOf(model).Type().Kind() != reflect.Pointer { if reflect.ValueOf(model).Type().Kind() != reflect.Pointer {
return fmt.Errorf("cannot build model on non-pointer: %v", reflect.ValueOf(model).Type().Kind()) return fmt.Errorf("cannot build model on non-pointer: %v", reflect.ValueOf(model).Type().Kind())
} }
v := reflect.ValueOf(model).Elem() v := reflect.ValueOf(model).Elem()
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
fName := v.Type().Field(i).Name fName := v.Type().Field(i).Name
// we need to find a matching field in the YAML, the cases may be off, so take no chances. // we need to find a matching field in the YAML, the cases may be off, so take no chances.
cases := []utils.Case{utils.PascalCase, utils.CamelCase, utils.ScreamingSnakeCase, cases := []utils.Case{utils.PascalCase, utils.CamelCase, utils.ScreamingSnakeCase,
utils.SnakeCase, utils.KebabCase, utils.RegularCase} utils.SnakeCase, utils.KebabCase, utils.RegularCase}
var vn, kn *yaml.Node var vn, kn *yaml.Node
for _, tryCase := range cases { for _, tryCase := range cases {
kn, vn = utils.FindKeyNode(utils.ConvertCase(fName, tryCase), node.Content) kn, vn = utils.FindKeyNode(utils.ConvertCase(fName, tryCase), node.Content)
if vn != nil { if vn != nil {
break break
} }
} }
if vn == nil { if vn == nil {
// no point in going on. // no point in going on.
continue continue
} }
field := v.FieldByName(fName) field := v.FieldByName(fName)
kind := field.Kind() kind := field.Kind()
switch kind { switch kind {
case reflect.Struct, reflect.Slice, reflect.Map, reflect.Pointer: case reflect.Struct, reflect.Slice, reflect.Map, reflect.Pointer:
err := SetField(field, vn, kn) err := SetField(field, vn, kn)
if err != nil { if err != nil {
return err return err
} }
default: default:
return fmt.Errorf("unable to parse unsupported type: %v", kind) return fmt.Errorf("unable to parse unsupported type: %v", kind)
} }
} }
return nil return nil
} }
func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) error { func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) error {
switch field.Type() { switch field.Type() {
case reflect.TypeOf(map[string]low.ObjectReference{}): case reflect.TypeOf(map[string]low.ObjectReference{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
if field.CanSet() { if field.CanSet() {
items := make(map[string]low.ObjectReference) items := make(map[string]low.ObjectReference)
var currentLabel string var currentLabel string
for i, sliceItem := range valueNode.Content { for i, sliceItem := range valueNode.Content {
if i%2 == 0 { if i%2 == 0 {
currentLabel = sliceItem.Value currentLabel = sliceItem.Value
continue continue
} }
var decoded map[string]interface{} var decoded map[string]interface{}
err := sliceItem.Decode(&decoded) err := sliceItem.Decode(&decoded)
if err != nil { if err != nil {
return err return err
} }
items[currentLabel] = low.ObjectReference{ items[currentLabel] = low.ObjectReference{
Value: decoded, Value: decoded,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
} }
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
} }
break break
case reflect.TypeOf(map[string]low.NodeReference[string]{}): case reflect.TypeOf(map[string]low.NodeReference[string]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
if field.CanSet() { if field.CanSet() {
items := make(map[string]low.NodeReference[string]) items := make(map[string]low.NodeReference[string])
var currentLabel string var currentLabel string
for i, sliceItem := range valueNode.Content { for i, sliceItem := range valueNode.Content {
if i%2 == 0 { if i%2 == 0 {
currentLabel = sliceItem.Value currentLabel = sliceItem.Value
continue continue
} }
items[currentLabel] = low.NodeReference[string]{ items[currentLabel] = low.NodeReference[string]{
Value: sliceItem.Value, Value: sliceItem.Value,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
} }
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
} }
break break
case reflect.TypeOf(low.ObjectReference{}): case reflect.TypeOf(low.ObjectReference{}):
if valueNode != nil { if valueNode != nil {
var decoded map[string]interface{} var decoded map[string]interface{}
err := valueNode.Decode(&decoded) err := valueNode.Decode(&decoded)
if err != nil { if err != nil {
return err return err
} }
if utils.IsNodeMap(valueNode) { if utils.IsNodeMap(valueNode) {
if field.CanSet() { if field.CanSet() {
or := low.ObjectReference{Value: decoded, ValueNode: valueNode} or := low.ObjectReference{Value: decoded, ValueNode: valueNode}
field.Set(reflect.ValueOf(or)) field.Set(reflect.ValueOf(or))
} }
} }
} }
break break
case reflect.TypeOf([]low.ObjectReference{}): case reflect.TypeOf([]low.ObjectReference{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []low.ObjectReference var items []low.ObjectReference
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
var decoded map[string]interface{} var decoded map[string]interface{}
err := sliceItem.Decode(&decoded) err := sliceItem.Decode(&decoded)
if err != nil { if err != nil {
return err return err
} }
items = append(items, low.ObjectReference{ items = append(items, low.ObjectReference{
Value: decoded, Value: decoded,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
} }
break break
case reflect.TypeOf(low.NodeReference[string]{}): case reflect.TypeOf(low.NodeReference[string]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeStringValue(valueNode) { if utils.IsNodeStringValue(valueNode) {
if field.CanSet() { if field.CanSet() {
nr := low.NodeReference[string]{ nr := low.NodeReference[string]{
Value: valueNode.Value, Value: valueNode.Value,
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
} }
} }
break break
case reflect.TypeOf(low.NodeReference[bool]{}): case reflect.TypeOf(low.NodeReference[bool]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeBoolValue(valueNode) { if utils.IsNodeBoolValue(valueNode) {
if field.CanSet() { if field.CanSet() {
bv, _ := strconv.ParseBool(valueNode.Value) bv, _ := strconv.ParseBool(valueNode.Value)
nr := low.NodeReference[bool]{ nr := low.NodeReference[bool]{
Value: bv, Value: bv,
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
} }
} }
break break
case reflect.TypeOf(low.NodeReference[int]{}): case reflect.TypeOf(low.NodeReference[int]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeIntValue(valueNode) { if utils.IsNodeIntValue(valueNode) {
if field.CanSet() { if field.CanSet() {
fv, _ := strconv.Atoi(valueNode.Value) fv, _ := strconv.Atoi(valueNode.Value)
nr := low.NodeReference[int]{ nr := low.NodeReference[int]{
Value: fv, Value: fv,
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
} }
} }
break break
case reflect.TypeOf(low.NodeReference[int64]{}): case reflect.TypeOf(low.NodeReference[int64]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeIntValue(valueNode) || utils.IsNodeFloatValue(valueNode) { // if utils.IsNodeIntValue(valueNode) || utils.IsNodeFloatValue(valueNode) { //
if field.CanSet() { if field.CanSet() {
fv, _ := strconv.ParseInt(valueNode.Value, 10, 64) fv, _ := strconv.ParseInt(valueNode.Value, 10, 64)
nr := low.NodeReference[int64]{ nr := low.NodeReference[int64]{
Value: fv, Value: fv,
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
} }
} }
break break
case reflect.TypeOf(low.NodeReference[float32]{}): case reflect.TypeOf(low.NodeReference[float32]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeFloatValue(valueNode) { if utils.IsNodeFloatValue(valueNode) {
if field.CanSet() { if field.CanSet() {
fv, _ := strconv.ParseFloat(valueNode.Value, 32) fv, _ := strconv.ParseFloat(valueNode.Value, 32)
nr := low.NodeReference[float32]{ nr := low.NodeReference[float32]{
Value: float32(fv), Value: float32(fv),
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
} }
} }
break break
case reflect.TypeOf(low.NodeReference[float64]{}): case reflect.TypeOf(low.NodeReference[float64]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeFloatValue(valueNode) { if utils.IsNodeFloatValue(valueNode) {
if field.CanSet() { if field.CanSet() {
fv, _ := strconv.ParseFloat(valueNode.Value, 64) fv, _ := strconv.ParseFloat(valueNode.Value, 64)
nr := low.NodeReference[float64]{ nr := low.NodeReference[float64]{
Value: fv, Value: fv,
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
} }
} }
break break
case reflect.TypeOf([]low.NodeReference[string]{}): case reflect.TypeOf([]low.NodeReference[string]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []low.NodeReference[string] var items []low.NodeReference[string]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
items = append(items, low.NodeReference[string]{ items = append(items, low.NodeReference[string]{
Value: sliceItem.Value, Value: sliceItem.Value,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
} }
break break
case reflect.TypeOf([]low.NodeReference[float32]{}): case reflect.TypeOf([]low.NodeReference[float32]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []low.NodeReference[float32] var items []low.NodeReference[float32]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
fv, _ := strconv.ParseFloat(sliceItem.Value, 32) fv, _ := strconv.ParseFloat(sliceItem.Value, 32)
items = append(items, low.NodeReference[float32]{ items = append(items, low.NodeReference[float32]{
Value: float32(fv), Value: float32(fv),
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
} }
break break
case reflect.TypeOf([]low.NodeReference[float64]{}): case reflect.TypeOf([]low.NodeReference[float64]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []low.NodeReference[float64] var items []low.NodeReference[float64]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
fv, _ := strconv.ParseFloat(sliceItem.Value, 64) fv, _ := strconv.ParseFloat(sliceItem.Value, 64)
items = append(items, low.NodeReference[float64]{Value: fv, ValueNode: sliceItem}) items = append(items, low.NodeReference[float64]{Value: fv, ValueNode: sliceItem})
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
} }
break break
case reflect.TypeOf([]low.NodeReference[int]{}): case reflect.TypeOf([]low.NodeReference[int]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []low.NodeReference[int] var items []low.NodeReference[int]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
iv, _ := strconv.Atoi(sliceItem.Value) iv, _ := strconv.Atoi(sliceItem.Value)
items = append(items, low.NodeReference[int]{ items = append(items, low.NodeReference[int]{
Value: iv, Value: iv,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
} }
break break
case reflect.TypeOf([]low.NodeReference[int64]{}): case reflect.TypeOf([]low.NodeReference[int64]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []low.NodeReference[int64] var items []low.NodeReference[int64]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
iv, _ := strconv.ParseInt(sliceItem.Value, 10, 64) iv, _ := strconv.ParseInt(sliceItem.Value, 10, 64)
items = append(items, low.NodeReference[int64]{ items = append(items, low.NodeReference[int64]{
Value: iv, Value: iv,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
} }
break break
case reflect.TypeOf([]low.NodeReference[bool]{}): case reflect.TypeOf([]low.NodeReference[bool]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
if field.CanSet() { if field.CanSet() {
var items []low.NodeReference[bool] var items []low.NodeReference[bool]
for _, sliceItem := range valueNode.Content { for _, sliceItem := range valueNode.Content {
bv, _ := strconv.ParseBool(sliceItem.Value) bv, _ := strconv.ParseBool(sliceItem.Value)
items = append(items, low.NodeReference[bool]{ items = append(items, low.NodeReference[bool]{
Value: bv, Value: bv,
ValueNode: sliceItem, ValueNode: sliceItem,
KeyNode: valueNode, KeyNode: valueNode,
}) })
} }
field.Set(reflect.ValueOf(items)) field.Set(reflect.ValueOf(items))
} }
} }
} }
break break
default: default:
// we want to ignore everything else. // we want to ignore everything else.
break break
} }
return nil return nil
} }
func BuildModelAsync(n *yaml.Node, model interface{}, lwg *sync.WaitGroup, errors *[]error) { func BuildModelAsync(n *yaml.Node, model interface{}, lwg *sync.WaitGroup, errors *[]error) {
if n != nil { if n != nil {
err := BuildModel(n, model) err := BuildModel(n, model)
if err != nil { if err != nil {
*errors = append(*errors, err) *errors = append(*errors, err)
} }
} }
lwg.Done() lwg.Done()
}
func ExtractExtensions(root *yaml.Node) (map[low.NodeReference[string]]low.NodeReference[any], error) {
extensions := utils.FindExtensionNodes(root.Content)
extensionMap := make(map[low.NodeReference[string]]low.NodeReference[any])
for _, ext := range extensions {
if utils.IsNodeMap(ext.Value) {
var v interface{}
err := ext.Value.Decode(&v)
if err != nil {
return nil, err
}
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: v, KeyNode: ext.Key}
}
if utils.IsNodeStringValue(ext.Value) {
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: ext.Value.Value, ValueNode: ext.Value}
}
if utils.IsNodeFloatValue(ext.Value) {
fv, _ := strconv.ParseFloat(ext.Value.Value, 64)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: fv, ValueNode: ext.Value}
}
if utils.IsNodeIntValue(ext.Value) {
iv, _ := strconv.ParseInt(ext.Value.Value, 10, 64)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: iv, ValueNode: ext.Value}
}
if utils.IsNodeBoolValue(ext.Value) {
bv, _ := strconv.ParseBool(ext.Value.Value)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: bv, ValueNode: ext.Value}
}
if utils.IsNodeArray(ext.Value) {
var v []interface{}
err := ext.Value.Decode(&v)
if err != nil {
return nil, err
}
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: v, ValueNode: ext.Value}
}
}
return extensionMap, nil
} }

View File

@@ -1,78 +1,182 @@
package openapi package openapi
import ( import (
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/3.0" v3 "github.com/pb33f/libopenapi/datamodel/low/3.0"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"strconv"
"sync"
) )
func CreateDocument(spec []byte) (*v3.Document, error) { const (
Info = "info"
Servers = "servers"
)
// extract details from spec func CreateDocument(info *datamodel.SpecInfo) (*v3.Document, error) {
info, err := datamodel.ExtractSpecInfo(spec)
if err != nil {
return nil, err
}
doc := v3.Document{Version: low.NodeReference[string]{Value: info.Version, ValueNode: info.RootNode}} doc := v3.Document{Version: low.NodeReference[string]{Value: info.Version, ValueNode: info.RootNode}}
// build an index // build an index
//idx := index.NewSpecIndex(info.RootNode) //idx := index.NewSpecIndex(info.RootNode)
//datamodel.BuildModel(info.RootNode.Content[0], &doc) //datamodel.BuildModel(info.RootNode.Content[0], &doc)
var wg sync.WaitGroup
var errors []error
var runExtraction = func(info *datamodel.SpecInfo, doc *v3.Document,
runFunc func(i *datamodel.SpecInfo, d *v3.Document) error,
ers *[]error,
wg *sync.WaitGroup) {
// extract info if er := runFunc(info, doc); er != nil {
extractErr := extractInfo(info, &doc) *ers = append(*ers, er)
if extractErr != nil { }
return nil, extractErr
}
// extract servers wg.Done()
extractErr = extractServers(info, &doc) }
if extractErr != nil {
return nil, extractErr
}
return &doc, nil wg.Add(3)
go runExtraction(info, &doc, extractInfo, &errors, &wg)
go runExtraction(info, &doc, extractServers, &errors, &wg)
go runExtraction(info, &doc, extractTags, &errors, &wg)
wg.Wait()
// todo fix this.
if len(errors) > 0 {
return &doc, errors[0]
}
return &doc, nil
} }
func extractInfo(info *datamodel.SpecInfo, doc *v3.Document) error { func extractInfo(info *datamodel.SpecInfo, doc *v3.Document) error {
_, ln, vn := utils.FindKeyNodeFull("info", info.RootNode.Content) _, ln, vn := utils.FindKeyNodeFull(Info, info.RootNode.Content)
if vn != nil { if vn != nil {
ir := v3.Info{} ir := v3.Info{}
err := datamodel.BuildModel(vn, &ir) err := datamodel.BuildModel(vn, &ir)
if err != nil { if err != nil {
return err return err
} }
err = ir.Build(vn) err = ir.Build(vn)
nr := low.NodeReference[*v3.Info]{Value: &ir, ValueNode: vn, KeyNode: ln} nr := low.NodeReference[*v3.Info]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Info = nr doc.Info = nr
} }
return nil return nil
} }
func extractServers(info *datamodel.SpecInfo, doc *v3.Document) error { func extractServers(info *datamodel.SpecInfo, doc *v3.Document) error {
_, ln, vn := utils.FindKeyNodeFull("servers", info.RootNode.Content) _, ln, vn := utils.FindKeyNodeFull(Servers, info.RootNode.Content)
if vn != nil { if vn != nil {
if utils.IsNodeArray(vn) { if utils.IsNodeArray(vn) {
var servers []low.NodeReference[*v3.Server] var servers []low.NodeReference[*v3.Server]
for _, srvN := range vn.Content { for _, srvN := range vn.Content {
if utils.IsNodeMap(srvN) { if utils.IsNodeMap(srvN) {
srvr := v3.Server{} srvr := v3.Server{}
err := datamodel.BuildModel(srvN, &srvr) err := datamodel.BuildModel(srvN, &srvr)
if err != nil { if err != nil {
return err return err
} }
srvr.Build(srvN) srvr.Build(srvN)
servers = append(servers, low.NodeReference[*v3.Server]{ servers = append(servers, low.NodeReference[*v3.Server]{
Value: &srvr, Value: &srvr,
ValueNode: srvN, ValueNode: srvN,
KeyNode: ln, KeyNode: ln,
}) })
} }
} }
doc.Servers = servers doc.Servers = servers
} }
} }
return nil return nil
}
func extractTags(info *datamodel.SpecInfo, doc *v3.Document) error {
_, ln, vn := utils.FindKeyNodeFull(v3.Tags, info.RootNode.Content)
if vn != nil {
if utils.IsNodeArray(vn) {
var tags []low.NodeReference[*v3.Tag]
for _, tagN := range vn.Content {
if utils.IsNodeMap(tagN) {
tag := v3.Tag{}
err := datamodel.BuildModel(tagN, &tag)
if err != nil {
return err
}
tag.Build(tagN)
tags = append(tags, low.NodeReference[*v3.Tag]{
Value: &tag,
ValueNode: tagN,
KeyNode: ln,
})
}
}
doc.Tags = tags
}
}
return nil
}
func ExtractExtensions(root *yaml.Node) (map[low.NodeReference[string]]low.NodeReference[any], error) {
extensions := utils.FindExtensionNodes(root.Content)
extensionMap := make(map[low.NodeReference[string]]low.NodeReference[any])
for _, ext := range extensions {
// this is an object, decode into an unknown map.
if utils.IsNodeMap(ext.Value) {
var v interface{}
err := ext.Value.Decode(&v)
if err != nil {
return nil, err
}
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
ValueNode: ext.Value,
}] = low.NodeReference[any]{Value: v, KeyNode: ext.Key}
}
if utils.IsNodeStringValue(ext.Value) {
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
ValueNode: ext.Value,
}] = low.NodeReference[any]{Value: ext.Value.Value, ValueNode: ext.Value}
}
if utils.IsNodeFloatValue(ext.Value) {
fv, _ := strconv.ParseFloat(ext.Value.Value, 64)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
ValueNode: ext.Value,
}] = low.NodeReference[any]{Value: fv, ValueNode: ext.Value}
}
if utils.IsNodeIntValue(ext.Value) {
iv, _ := strconv.ParseInt(ext.Value.Value, 10, 64)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
ValueNode: ext.Value,
}] = low.NodeReference[any]{Value: iv, ValueNode: ext.Value}
}
if utils.IsNodeBoolValue(ext.Value) {
bv, _ := strconv.ParseBool(ext.Value.Value)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
ValueNode: ext.Value,
}] = low.NodeReference[any]{Value: bv, ValueNode: ext.Value}
}
if utils.IsNodeArray(ext.Value) {
var v []interface{}
err := ext.Value.Decode(&v)
if err != nil {
return nil, err
}
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
ValueNode: ext.Value,
}] = low.NodeReference[any]{Value: v, ValueNode: ext.Value}
}
}
return extensionMap, nil
} }

View File

@@ -1,32 +1,109 @@
package openapi package openapi
import ( import (
"github.com/pb33f/libopenapi/datamodel"
v3 "github.com/pb33f/libopenapi/datamodel/low/3.0"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"io/ioutil" "io/ioutil"
"testing" "testing"
) )
func TestCreateDocument_NoData(t *testing.T) { var doc *v3.Document
doc, err := CreateDocument(nil)
assert.Nil(t, doc) func init() {
assert.Error(t, err) data, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
doc, _ = CreateDocument(info)
}
func BenchmarkCreateDocument(b *testing.B) {
data, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
doc, _ = CreateDocument(info)
}
} }
func TestCreateDocument(t *testing.T) { func TestCreateDocument(t *testing.T) {
data, aErr := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
assert.NoError(t, aErr)
doc, err := CreateDocument(data)
assert.NotNil(t, doc)
assert.NoError(t, err)
assert.Equal(t, "3.0.1", doc.Version.Value) assert.Equal(t, "3.0.1", doc.Version.Value)
assert.Equal(t, "Burger Shop", doc.Info.Value.Title.Value) assert.Equal(t, "Burger Shop", doc.Info.Value.Title.Value)
assert.NotEmpty(t, doc.Info.Value.Title.Value) assert.NotEmpty(t, doc.Info.Value.Title.Value)
}
func TestCreateDocument_Info(t *testing.T) {
assert.Equal(t, "https://pb33f.io", doc.Info.Value.TermsOfService.Value) assert.Equal(t, "https://pb33f.io", doc.Info.Value.TermsOfService.Value)
assert.Equal(t, "pb33f", doc.Info.Value.Contact.Value.Name.Value) assert.Equal(t, "pb33f", doc.Info.Value.Contact.Value.Name.Value)
assert.Equal(t, "buckaroo@pb33f.io", doc.Info.Value.Contact.Value.Email.Value) assert.Equal(t, "buckaroo@pb33f.io", doc.Info.Value.Contact.Value.Email.Value)
assert.Equal(t, "https://pb33f.io", doc.Info.Value.Contact.Value.URL.Value) assert.Equal(t, "https://pb33f.io", doc.Info.Value.Contact.Value.URL.Value)
assert.Equal(t, "pb33f", doc.Info.Value.License.Value.Name.Value)
assert.Equal(t, "https://pb33f.io/made-up", doc.Info.Value.License.Value.URL.Value)
}
func TestCreateDocument_Servers(t *testing.T) {
assert.Len(t, doc.Servers, 2)
server1 := doc.Servers[0]
server2 := doc.Servers[1]
// server 1
assert.Equal(t, "{scheme}://api.pb33f.io", server1.Value.URL.Value)
assert.NotEmpty(t, server1.Value.Description.Value)
assert.Len(t, server1.Value.Variables.Value, 1)
assert.Len(t, server1.Value.Variables.Value["scheme"].Value.Enum, 2)
assert.Equal(t, server1.Value.Variables.Value["scheme"].Value.Default.Value, "https")
assert.NotEmpty(t, server1.Value.Variables.Value["scheme"].Value.Description.Value)
// server 2
assert.Equal(t, "https://{domain}.{host}.com", server2.Value.URL.Value)
assert.NotEmpty(t, server2.Value.Description.Value)
assert.Len(t, server2.Value.Variables.Value, 2)
assert.Equal(t, server2.Value.Variables.Value["domain"].Value.Default.Value, "api")
assert.NotEmpty(t, server2.Value.Variables.Value["domain"].Value.Description.Value)
assert.NotEmpty(t, server2.Value.Variables.Value["host"].Value.Description.Value)
assert.Equal(t, server2.Value.Variables.Value["host"].Value.Default.Value, "pb33f.io")
assert.Equal(t, "1.2", doc.Info.Value.Version.Value) assert.Equal(t, "1.2", doc.Info.Value.Version.Value)
}
func TestCreateDocument_Tags(t *testing.T) {
assert.Len(t, doc.Tags, 2)
// tag1
assert.Equal(t, "Burgers", doc.Tags[0].Value.Name.Value)
assert.NotEmpty(t, doc.Tags[0].Value.Description.Value)
assert.NotNil(t, doc.Tags[0].Value.ExternalDocs.Value)
assert.Equal(t, "https://pb33f.io", doc.Tags[0].Value.ExternalDocs.Value.URL.Value)
assert.NotEmpty(t, doc.Tags[0].Value.ExternalDocs.Value.URL.Value)
assert.Len(t, doc.Tags[0].Value.Extensions, 7)
for key, extension := range doc.Tags[0].Value.Extensions {
switch key.Value {
case "x-internal-ting":
assert.Equal(t, "somethingSpecial", extension.Value)
case "x-internal-tong":
assert.Equal(t, int64(1), extension.Value)
case "x-internal-tang":
assert.Equal(t, 1.2, extension.Value)
case "x-internal-tung":
assert.Equal(t, true, extension.Value)
case "x-internal-arr":
assert.Len(t, extension.Value, 2)
assert.Equal(t, "one", extension.Value.([]interface{})[0].(string))
case "x-internal-arrmap":
assert.Len(t, extension.Value, 2)
assert.Equal(t, "now", extension.Value.([]interface{})[0].(map[string]interface{})["what"])
case "x-something-else":
// crazy times in the upside down. this API should be avoided for the higher up use cases.
// this is why we will need a higher level API to this model, this looks cool and all, but dude.
assert.Equal(t, "now?", extension.Value.(map[string]interface{})["ok"].([]interface{})[0].(map[string]interface{})["what"])
}
}
/// tag2
assert.Equal(t, "Dressing", doc.Tags[1].Value.Name.Value)
assert.NotEmpty(t, doc.Tags[1].Value.Description.Value)
assert.NotNil(t, doc.Tags[1].Value.ExternalDocs.Value)
assert.Equal(t, "https://pb33f.io", doc.Tags[1].Value.ExternalDocs.Value.URL.Value)
assert.NotEmpty(t, doc.Tags[1].Value.ExternalDocs.Value.URL.Value)
assert.Len(t, doc.Tags[1].Value.Extensions, 0)
} }

View File

@@ -10,25 +10,38 @@ info:
url: https://pb33f.io url: https://pb33f.io
license: license:
name: pb33f name: pb33f
url: https://quobix.com/made-up url: https://pb33f.io/made-up
version: "1.2" version: "1.2"
tags: tags:
- name: "Burgers" - name: "Burgers"
description: "All kinds of yummy burgers." description: "All kinds of yummy burgers."
externalDocs: externalDocs:
description: "Find out more" description: "Find out more"
url: "https://quobix.com/" url: "https://pb33f.io"
x-internal-ting: somethingSpecial
x-internal-tong: 1
x-internal-tang: 1.2
x-internal-tung: true
x-internal-arr:
- one
- two
x-internal-arrmap:
- what: now
- why: that
x-something-else:
ok:
- what: now?
- name: "Dressing" - name: "Dressing"
description: "Variety of dressings: cheese, veggie, oil and a lot more" description: "Variety of dressings: cheese, veggie, oil and a lot more"
externalDocs: externalDocs:
description: "Find out more information about our products)" description: "Find out more information about our products)"
url: "https://quobix.com/" url: "https://pb33f.io"
servers: servers:
- url: "{scheme}://api.quobix.com" - url: "{scheme}://api.pb33f.io"
description: "this is our main API server, for all fun API things." description: "this is our main API server, for all fun API things."
variables: variables:
scheme: scheme:
enum: [https] enum: [https, wss]
default: https default: https
description: this is a server variable for the scheme description: this is a server variable for the scheme
- url: "https://{domain}.{host}.com" - url: "https://{domain}.{host}.com"
@@ -38,8 +51,8 @@ servers:
default: "api" default: "api"
description: the default API domain is 'api' description: the default API domain is 'api'
host: host:
default: "quobix.com" default: "pb33f.io"
description: the default host for this API is 'quobix.com' description: the default host for this API is 'pb33f.com'
paths: paths:
/burgers: /burgers:
post: post:

View File

@@ -1,265 +1,285 @@
package utils package utils
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/iancoleman/strcase" "github.com/iancoleman/strcase"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
) )
type Case int8 type Case int8
const ( const (
// OpenApi3 is used by all OpenAPI 3+ docs // OpenApi3 is used by all OpenAPI 3+ docs
OpenApi3 = "openapi" OpenApi3 = "openapi"
// OpenApi2 is used by all OpenAPI 2 docs, formerly known as swagger. // OpenApi2 is used by all OpenAPI 2 docs, formerly known as swagger.
OpenApi2 = "swagger" OpenApi2 = "swagger"
// AsyncApi is used by akk AsyncAPI docs, all versions. // AsyncApi is used by akk AsyncAPI docs, all versions.
AsyncApi = "asyncapi" AsyncApi = "asyncapi"
PascalCase Case = iota PascalCase Case = iota
CamelCase CamelCase
ScreamingSnakeCase ScreamingSnakeCase
SnakeCase SnakeCase
KebabCase KebabCase
ScreamingKebabCase ScreamingKebabCase
RegularCase RegularCase
) )
// FindNodes will find a node based on JSONPath, it accepts raw yaml/json as input. // FindNodes will find a node based on JSONPath, it accepts raw yaml/json as input.
func FindNodes(yamlData []byte, jsonPath string) ([]*yaml.Node, error) { func FindNodes(yamlData []byte, jsonPath string) ([]*yaml.Node, error) {
jsonPath = FixContext(jsonPath) jsonPath = FixContext(jsonPath)
var node yaml.Node var node yaml.Node
yaml.Unmarshal(yamlData, &node) yaml.Unmarshal(yamlData, &node)
path, err := yamlpath.NewPath(jsonPath) path, err := yamlpath.NewPath(jsonPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
results, _ := path.Find(&node) results, _ := path.Find(&node)
return results, nil return results, nil
} }
func FindLastChildNode(node *yaml.Node) *yaml.Node { func FindLastChildNode(node *yaml.Node) *yaml.Node {
s := len(node.Content) - 1 s := len(node.Content) - 1
if s < 0 { if s < 0 {
s = 0 s = 0
} }
if len(node.Content) > 0 && len(node.Content[s].Content) > 0 { if len(node.Content) > 0 && len(node.Content[s].Content) > 0 {
return FindLastChildNode(node.Content[s]) return FindLastChildNode(node.Content[s])
} else { } else {
if len(node.Content) > 0 { if len(node.Content) > 0 {
return node.Content[s] return node.Content[s]
} }
return node return node
} }
} }
// BuildPath will construct a JSONPath from a base and an array of strings. // BuildPath will construct a JSONPath from a base and an array of strings.
func BuildPath(basePath string, segs []string) string { func BuildPath(basePath string, segs []string) string {
path := strings.Join(segs, ".") path := strings.Join(segs, ".")
// trim that last period. // trim that last period.
if len(path) > 0 && path[len(path)-1] == '.' { if len(path) > 0 && path[len(path)-1] == '.' {
path = path[:len(path)-1] path = path[:len(path)-1]
} }
return fmt.Sprintf("%s.%s", basePath, path) return fmt.Sprintf("%s.%s", basePath, path)
} }
// FindNodesWithoutDeserializing will find a node based on JSONPath, without deserializing from yaml/json // FindNodesWithoutDeserializing will find a node based on JSONPath, without deserializing from yaml/json
func FindNodesWithoutDeserializing(node *yaml.Node, jsonPath string) ([]*yaml.Node, error) { func FindNodesWithoutDeserializing(node *yaml.Node, jsonPath string) ([]*yaml.Node, error) {
jsonPath = FixContext(jsonPath) jsonPath = FixContext(jsonPath)
path, err := yamlpath.NewPath(jsonPath) path, err := yamlpath.NewPath(jsonPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
results, _ := path.Find(node) results, _ := path.Find(node)
return results, nil return results, nil
} }
// ConvertInterfaceIntoStringMap will convert an unknown input into a string map. // ConvertInterfaceIntoStringMap will convert an unknown input into a string map.
func ConvertInterfaceIntoStringMap(context interface{}) map[string]string { func ConvertInterfaceIntoStringMap(context interface{}) map[string]string {
converted := make(map[string]string) converted := make(map[string]string)
if context != nil { if context != nil {
if v, ok := context.(map[string]interface{}); ok { if v, ok := context.(map[string]interface{}); ok {
for k, n := range v { for k, n := range v {
if s, okB := n.(string); okB { if s, okB := n.(string); okB {
converted[k] = s converted[k] = s
} }
} }
} }
if v, ok := context.(map[string]string); ok { if v, ok := context.(map[string]string); ok {
for k, n := range v { for k, n := range v {
converted[k] = n converted[k] = n
} }
} }
} }
return converted return converted
} }
// ConvertInterfaceToStringArray will convert an unknown input map type into a string array/slice // ConvertInterfaceToStringArray will convert an unknown input map type into a string array/slice
func ConvertInterfaceToStringArray(raw interface{}) []string { func ConvertInterfaceToStringArray(raw interface{}) []string {
if vals, ok := raw.(map[string]interface{}); ok { if vals, ok := raw.(map[string]interface{}); ok {
var s []string var s []string
for _, v := range vals { for _, v := range vals {
if g, y := v.([]interface{}); y { if g, y := v.([]interface{}); y {
for _, q := range g { for _, q := range g {
s = append(s, fmt.Sprint(q)) s = append(s, fmt.Sprint(q))
} }
} }
} }
return s return s
} }
if vals, ok := raw.(map[string][]string); ok { if vals, ok := raw.(map[string][]string); ok {
var s []string var s []string
for _, v := range vals { for _, v := range vals {
s = append(s, v...) s = append(s, v...)
} }
return s return s
} }
return nil return nil
} }
// ConvertInterfaceArrayToStringArray will convert an unknown interface array type, into a string slice // ConvertInterfaceArrayToStringArray will convert an unknown interface array type, into a string slice
func ConvertInterfaceArrayToStringArray(raw interface{}) []string { func ConvertInterfaceArrayToStringArray(raw interface{}) []string {
if vals, ok := raw.([]interface{}); ok { if vals, ok := raw.([]interface{}); ok {
s := make([]string, len(vals)) s := make([]string, len(vals))
for i, v := range vals { for i, v := range vals {
s[i] = fmt.Sprint(v) s[i] = fmt.Sprint(v)
} }
return s return s
} }
if vals, ok := raw.([]string); ok { if vals, ok := raw.([]string); ok {
return vals return vals
} }
return nil return nil
} }
// ExtractValueFromInterfaceMap pulls out an unknown value from a map using a string key // ExtractValueFromInterfaceMap pulls out an unknown value from a map using a string key
func ExtractValueFromInterfaceMap(name string, raw interface{}) interface{} { func ExtractValueFromInterfaceMap(name string, raw interface{}) interface{} {
if propMap, ok := raw.(map[string]interface{}); ok { if propMap, ok := raw.(map[string]interface{}); ok {
if props, okn := propMap[name].([]interface{}); okn { if props, okn := propMap[name].([]interface{}); okn {
return props return props
} else { } else {
return propMap[name] return propMap[name]
} }
} }
if propMap, ok := raw.(map[string][]string); ok { if propMap, ok := raw.(map[string][]string); ok {
return propMap[name] return propMap[name]
} }
return nil return nil
} }
// FindFirstKeyNode will locate the first key and value yaml.Node based on a key. // FindFirstKeyNode will locate the first key and value yaml.Node based on a key.
func FindFirstKeyNode(key string, nodes []*yaml.Node, depth int) (keyNode *yaml.Node, valueNode *yaml.Node) { func FindFirstKeyNode(key string, nodes []*yaml.Node, depth int) (keyNode *yaml.Node, valueNode *yaml.Node) {
if depth > 40 { if depth > 40 {
return nil, nil return nil, nil
} }
for i, v := range nodes { for i, v := range nodes {
if key != "" && key == v.Value { if key != "" && key == v.Value {
if i+1 >= len(nodes) { if i+1 >= len(nodes) {
return v, nodes[i] // next node is what we need. return v, nodes[i] // next node is what we need.
} }
return v, nodes[i+1] // next node is what we need. return v, nodes[i+1] // next node is what we need.
} }
if len(v.Content) > 0 { if len(v.Content) > 0 {
depth++ depth++
x, y := FindFirstKeyNode(key, v.Content, depth) x, y := FindFirstKeyNode(key, v.Content, depth)
if x != nil && y != nil { if x != nil && y != nil {
return x, y return x, y
} }
} }
} }
return nil, nil return nil, nil
} }
// KeyNodeResult is a result from a KeyNodeSearch performed by the FindAllKeyNodesWithPath // KeyNodeResult is a result from a KeyNodeSearch performed by the FindAllKeyNodesWithPath
type KeyNodeResult struct { type KeyNodeResult struct {
KeyNode *yaml.Node KeyNode *yaml.Node
ValueNode *yaml.Node ValueNode *yaml.Node
Parent *yaml.Node Parent *yaml.Node
Path []yaml.Node Path []yaml.Node
} }
// KeyNodeSearch keeps a track of everything we have found on our adventure down the trees. // KeyNodeSearch keeps a track of everything we have found on our adventure down the trees.
type KeyNodeSearch struct { type KeyNodeSearch struct {
Key string Key string
Ignore []string Ignore []string
Results []*KeyNodeResult Results []*KeyNodeResult
AllowExtensions bool AllowExtensions bool
} }
// FindKeyNodeTop is a non-recursive search of top level nodes for a key, will not look at content. // FindKeyNodeTop is a non-recursive search of top level nodes for a key, will not look at content.
// Returns the key and value // Returns the key and value
func FindKeyNodeTop(key string, nodes []*yaml.Node) (keyNode *yaml.Node, valueNode *yaml.Node) { func FindKeyNodeTop(key string, nodes []*yaml.Node) (keyNode *yaml.Node, valueNode *yaml.Node) {
for i, v := range nodes { for i, v := range nodes {
if key == v.Value { if key == v.Value {
return v, nodes[i+1] // next node is what we need. return v, nodes[i+1] // next node is what we need.
} }
} }
return nil, nil return nil, nil
} }
// FindKeyNode is a non-recursive search of a *yaml.Node Content for a child node with a key. // FindKeyNode is a non-recursive search of a *yaml.Node Content for a child node with a key.
// Returns the key and value // Returns the key and value
func FindKeyNode(key string, nodes []*yaml.Node) (keyNode *yaml.Node, valueNode *yaml.Node) { func FindKeyNode(key string, nodes []*yaml.Node) (keyNode *yaml.Node, valueNode *yaml.Node) {
//numNodes := len(nodes) //numNodes := len(nodes)
for i, v := range nodes { for i, v := range nodes {
if i%2 == 0 && key == v.Value { if i%2 == 0 && key == v.Value {
return v, nodes[i+1] // next node is what we need. return v, nodes[i+1] // next node is what we need.
} }
for x, j := range v.Content { for x, j := range v.Content {
if key == j.Value { if key == j.Value {
if IsNodeMap(v) { if IsNodeMap(v) {
if x+1 == len(v.Content) { if x+1 == len(v.Content) {
return v, v.Content[x] return v, v.Content[x]
} }
return v, v.Content[x+1] // next node is what we need. return v, v.Content[x+1] // next node is what we need.
} }
if IsNodeArray(v) { if IsNodeArray(v) {
return v, v.Content[x] return v, v.Content[x]
} }
} }
} }
} }
return nil, nil return nil, nil
} }
func FindKeyNodeFull(key string, nodes []*yaml.Node) (keyNode *yaml.Node, labelNode *yaml.Node, valueNode *yaml.Node) { func FindKeyNodeFull(key string, nodes []*yaml.Node) (keyNode *yaml.Node, labelNode *yaml.Node, valueNode *yaml.Node) {
for i, v := range nodes { for i, v := range nodes {
if i%2 == 0 && key == v.Value { if i%2 == 0 && key == v.Value {
return v, nodes[i], nodes[i+1] // next node is what we need. return v, nodes[i], nodes[i+1] // next node is what we need.
} }
for x, j := range v.Content { for x, j := range v.Content {
if key == j.Value { if key == j.Value {
if IsNodeMap(v) { if IsNodeMap(v) {
if x+1 == len(v.Content) { if x+1 == len(v.Content) {
return v, v.Content[x], v.Content[x] return v, v.Content[x], v.Content[x]
} }
return v, v.Content[x], v.Content[x+1] // next node is what we need. return v, v.Content[x], v.Content[x+1] // next node is what we need.
} }
if IsNodeArray(v) { if IsNodeArray(v) {
return v, v.Content[x], v.Content[x] return v, v.Content[x], v.Content[x]
} }
} }
} }
} }
return nil, nil, nil return nil, nil, nil
}
type ExtensionNode struct {
Key *yaml.Node
Value *yaml.Node
}
func FindExtensionNodes(nodes []*yaml.Node) []*ExtensionNode {
var extensions []*ExtensionNode
for i, v := range nodes {
if i%2 == 0 && strings.HasPrefix(v.Value, "x-") {
if i+1 < len(nodes) {
extensions = append(extensions, &ExtensionNode{
Key: v,
Value: nodes[i+1],
})
}
}
}
return extensions
} }
var ObjectLabel = "object" var ObjectLabel = "object"
@@ -273,257 +293,257 @@ var SchemaSource = "https://json-schema.org/draft/2020-12/schema"
var SchemaId = "https://quobix.com/api/vacuum" var SchemaId = "https://quobix.com/api/vacuum"
func MakeTagReadable(node *yaml.Node) string { func MakeTagReadable(node *yaml.Node) string {
switch node.Tag { switch node.Tag {
case "!!map": case "!!map":
return ObjectLabel return ObjectLabel
case "!!seq": case "!!seq":
return ArrayLabel return ArrayLabel
case "!!str": case "!!str":
return StringLabel return StringLabel
case "!!int": case "!!int":
return IntegerLabel return IntegerLabel
case "!!float": case "!!float":
return NumberLabel return NumberLabel
case "!!bool": case "!!bool":
return BooleanLabel return BooleanLabel
} }
return "unknown" return "unknown"
} }
// IsNodeMap checks if the node is a map type // IsNodeMap checks if the node is a map type
func IsNodeMap(node *yaml.Node) bool { func IsNodeMap(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!map" return node.Tag == "!!map"
} }
// IsNodePolyMorphic will return true if the node contains polymorphic keys. // IsNodePolyMorphic will return true if the node contains polymorphic keys.
func IsNodePolyMorphic(node *yaml.Node) bool { func IsNodePolyMorphic(node *yaml.Node) bool {
for i, v := range node.Content { for i, v := range node.Content {
if i%2 == 0 { if i%2 == 0 {
if v.Value == "anyOf" || v.Value == "oneOf" || v.Value == "allOf" { if v.Value == "anyOf" || v.Value == "oneOf" || v.Value == "allOf" {
return true return true
} }
} }
} }
return false return false
} }
// IsNodeArray checks if a node is an array type // IsNodeArray checks if a node is an array type
func IsNodeArray(node *yaml.Node) bool { func IsNodeArray(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!seq" return node.Tag == "!!seq"
} }
// IsNodeStringValue checks if a node is a string value // IsNodeStringValue checks if a node is a string value
func IsNodeStringValue(node *yaml.Node) bool { func IsNodeStringValue(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!str" return node.Tag == "!!str"
} }
// IsNodeIntValue will check if a node is an int value // IsNodeIntValue will check if a node is an int value
func IsNodeIntValue(node *yaml.Node) bool { func IsNodeIntValue(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!int" return node.Tag == "!!int"
} }
// IsNodeFloatValue will check is a node is a float value. // IsNodeFloatValue will check is a node is a float value.
func IsNodeFloatValue(node *yaml.Node) bool { func IsNodeFloatValue(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!float" return node.Tag == "!!float"
} }
// IsNodeBoolValue will check is a node is a bool // IsNodeBoolValue will check is a node is a bool
func IsNodeBoolValue(node *yaml.Node) bool { func IsNodeBoolValue(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!bool" return node.Tag == "!!bool"
} }
// FixContext will clean up a JSONpath string to be correctly traversable. // FixContext will clean up a JSONpath string to be correctly traversable.
func FixContext(context string) string { func FixContext(context string) string {
tokens := strings.Split(context, ".") tokens := strings.Split(context, ".")
var cleaned = []string{} var cleaned = []string{}
for i, t := range tokens { for i, t := range tokens {
if v, err := strconv.Atoi(t); err == nil { if v, err := strconv.Atoi(t); err == nil {
if v < 200 { // codes start here if v < 200 { // codes start here
if cleaned[i-1] != "" { if cleaned[i-1] != "" {
cleaned[i-1] += fmt.Sprintf("[%v]", t) cleaned[i-1] += fmt.Sprintf("[%v]", t)
} }
} else { } else {
cleaned = append(cleaned, t) cleaned = append(cleaned, t)
} }
continue continue
} }
cleaned = append(cleaned, strings.ReplaceAll(t, "(root)", "$")) cleaned = append(cleaned, strings.ReplaceAll(t, "(root)", "$"))
} }
return strings.Join(cleaned, ".") return strings.Join(cleaned, ".")
} }
// IsJSON will tell you if a string is JSON or not. // IsJSON will tell you if a string is JSON or not.
func IsJSON(testString string) bool { func IsJSON(testString string) bool {
if testString == "" { if testString == "" {
return false return false
} }
runes := []rune(strings.TrimSpace(testString)) runes := []rune(strings.TrimSpace(testString))
if runes[0] == '{' && runes[len(runes)-1] == '}' { if runes[0] == '{' && runes[len(runes)-1] == '}' {
return true return true
} }
return false return false
} }
// IsYAML will tell you if a string is YAML or not. // IsYAML will tell you if a string is YAML or not.
func IsYAML(testString string) bool { func IsYAML(testString string) bool {
if testString == "" { if testString == "" {
return false return false
} }
if IsJSON(testString) { if IsJSON(testString) {
return false return false
} }
var n interface{} var n interface{}
err := yaml.Unmarshal([]byte(testString), &n) err := yaml.Unmarshal([]byte(testString), &n)
if err != nil { if err != nil {
return false return false
} }
_, err = yaml.Marshal(n) _, err = yaml.Marshal(n)
return err == nil return err == nil
} }
// ConvertYAMLtoJSON will do exactly what you think it will. It will deserialize YAML into serialized JSON. // ConvertYAMLtoJSON will do exactly what you think it will. It will deserialize YAML into serialized JSON.
func ConvertYAMLtoJSON(yamlData []byte) ([]byte, error) { func ConvertYAMLtoJSON(yamlData []byte) ([]byte, error) {
var decodedYaml map[string]interface{} var decodedYaml map[string]interface{}
err := yaml.Unmarshal(yamlData, &decodedYaml) err := yaml.Unmarshal(yamlData, &decodedYaml)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// if the data can be decoded, it can be encoded (that's my view anyway). no need for an error check. // if the data can be decoded, it can be encoded (that's my view anyway). no need for an error check.
jsonData, _ := json.Marshal(decodedYaml) jsonData, _ := json.Marshal(decodedYaml)
return jsonData, nil return jsonData, nil
} }
// IsHttpVerb will check if an operation is valid or not. // IsHttpVerb will check if an operation is valid or not.
func IsHttpVerb(verb string) bool { func IsHttpVerb(verb string) bool {
verbs := []string{"get", "post", "put", "patch", "delete", "options", "trace", "head"} verbs := []string{"get", "post", "put", "patch", "delete", "options", "trace", "head"}
for _, v := range verbs { for _, v := range verbs {
if verb == v { if verb == v {
return true return true
} }
} }
return false return false
} }
func ConvertComponentIdIntoFriendlyPathSearch(id string) (string, string) { func ConvertComponentIdIntoFriendlyPathSearch(id string) (string, string) {
segs := strings.Split(id, "/") segs := strings.Split(id, "/")
name := segs[len(segs)-1] name := segs[len(segs)-1]
replaced := strings.ReplaceAll(fmt.Sprintf("%s['%s']", replaced := strings.ReplaceAll(fmt.Sprintf("%s['%s']",
strings.Join(segs[:len(segs)-1], "."), name), "#", "$") strings.Join(segs[:len(segs)-1], "."), name), "#", "$")
if replaced[0] != '$' { if replaced[0] != '$' {
replaced = fmt.Sprintf("$%s", replaced) replaced = fmt.Sprintf("$%s", replaced)
} }
return name, replaced return name, replaced
} }
func ConvertComponentIdIntoPath(id string) (string, string) { func ConvertComponentIdIntoPath(id string) (string, string) {
segs := strings.Split(id, "/") segs := strings.Split(id, "/")
name := segs[len(segs)-1] name := segs[len(segs)-1]
return name, strings.ReplaceAll(fmt.Sprintf("%s.%s", return name, strings.ReplaceAll(fmt.Sprintf("%s.%s",
strings.Join(segs[:len(segs)-1], "."), name), "#", "$") strings.Join(segs[:len(segs)-1], "."), name), "#", "$")
} }
func RenderCodeSnippet(startNode *yaml.Node, specData []string, before, after int) string { func RenderCodeSnippet(startNode *yaml.Node, specData []string, before, after int) string {
buf := new(strings.Builder) buf := new(strings.Builder)
startLine := startNode.Line - before startLine := startNode.Line - before
endLine := startNode.Line + after endLine := startNode.Line + after
if startLine < 0 { if startLine < 0 {
startLine = 0 startLine = 0
} }
if endLine >= len(specData) { if endLine >= len(specData) {
endLine = len(specData) - 1 endLine = len(specData) - 1
} }
delta := endLine - startLine delta := endLine - startLine
for i := 0; i < delta; i++ { for i := 0; i < delta; i++ {
l := startLine + i l := startLine + i
if l < len(specData) { if l < len(specData) {
line := specData[l] line := specData[l]
buf.WriteString(fmt.Sprintf("%s\n", line)) buf.WriteString(fmt.Sprintf("%s\n", line))
} }
} }
return buf.String() return buf.String()
} }
func ConvertCase(input string, convert Case) string { func ConvertCase(input string, convert Case) string {
if input == "" { if input == "" {
return "" return ""
} }
switch convert { switch convert {
case PascalCase: case PascalCase:
return strcase.ToCamel(input) return strcase.ToCamel(input)
case CamelCase: case CamelCase:
return strcase.ToLowerCamel(input) return strcase.ToLowerCamel(input)
case ScreamingKebabCase: case ScreamingKebabCase:
return strcase.ToScreamingKebab(input) return strcase.ToScreamingKebab(input)
case ScreamingSnakeCase: case ScreamingSnakeCase:
return strcase.ToScreamingSnake(input) return strcase.ToScreamingSnake(input)
case SnakeCase: case SnakeCase:
return strcase.ToSnake(input) return strcase.ToSnake(input)
default: default:
return input return input
} }
} }
func DetectCase(input string) Case { func DetectCase(input string) Case {
trim := strings.TrimSpace(input) trim := strings.TrimSpace(input)
if trim == "" { if trim == "" {
return -1 return -1
} }
pascalCase := regexp.MustCompile("^[A-Z][a-z]+(?:[A-Z][a-z]+)*$") pascalCase := regexp.MustCompile("^[A-Z][a-z]+(?:[A-Z][a-z]+)*$")
camelCase := regexp.MustCompile("^[a-z]+(?:[A-Z][a-z]+)*$") camelCase := regexp.MustCompile("^[a-z]+(?:[A-Z][a-z]+)*$")
screamingSnakeCase := regexp.MustCompile("^[A-Z]+(_[A-Z]+)*$") screamingSnakeCase := regexp.MustCompile("^[A-Z]+(_[A-Z]+)*$")
snakeCase := regexp.MustCompile("^[a-z]+(_[a-z]+)*$") snakeCase := regexp.MustCompile("^[a-z]+(_[a-z]+)*$")
kebabCase := regexp.MustCompile("^[a-z]+(-[a-z]+)*$") kebabCase := regexp.MustCompile("^[a-z]+(-[a-z]+)*$")
screamingKebabCase := regexp.MustCompile("^[A-Z]+(-[A-Z]+)*$") screamingKebabCase := regexp.MustCompile("^[A-Z]+(-[A-Z]+)*$")
if pascalCase.MatchString(trim) { if pascalCase.MatchString(trim) {
return PascalCase return PascalCase
} }
if camelCase.MatchString(trim) { if camelCase.MatchString(trim) {
return CamelCase return CamelCase
} }
if screamingSnakeCase.MatchString(trim) { if screamingSnakeCase.MatchString(trim) {
return ScreamingSnakeCase return ScreamingSnakeCase
} }
if snakeCase.MatchString(trim) { if snakeCase.MatchString(trim) {
return SnakeCase return SnakeCase
} }
if kebabCase.MatchString(trim) { if kebabCase.MatchString(trim) {
return KebabCase return KebabCase
} }
if screamingKebabCase.MatchString(trim) { if screamingKebabCase.MatchString(trim) {
return ScreamingKebabCase return ScreamingKebabCase
} }
return RegularCase return RegularCase
} }