Working through the model builder

Shaping out the design as I go, what makes sence, what feels right, what do we need, how do we want to use it and how to we want to search it etc.
This commit is contained in:
Dave Shanley
2022-07-30 15:27:21 -04:00
parent ecc7b20b0a
commit 7535cf568c
13 changed files with 290 additions and 124 deletions

View File

@@ -2,11 +2,9 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
)
type Contact struct {
Node *yaml.Node
Name low.NodeReference[string]
URL low.NodeReference[string]
Email low.NodeReference[string]

View File

@@ -1,48 +1,17 @@
package v3
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
"reflect"
)
type Document struct {
Version string
Info Info
Servers []Server
Paths Paths
Components Components
Security []SecurityRequirement
Tags []Tag
ExternalDocs ExternalDoc
Version low.NodeReference[string]
Info low.NodeReference[*Info]
Servers []low.NodeReference[*Server]
Paths *Paths
Components *Components
Security []*SecurityRequirement
Tags []*Tag
ExternalDocs *ExternalDoc
Extensions map[string]low.ObjectReference
}
func (d Document) Build(node *yaml.Node) {
doc := Document{
Version: "",
Info: Info{},
Servers: nil,
Paths: Paths{},
Components: Components{},
Security: nil,
Tags: nil,
ExternalDocs: ExternalDoc{},
Extensions: nil,
}
var j interface{}
j = doc
t := reflect.TypeOf(j)
v := reflect.ValueOf(j)
k := t.Kind()
fmt.Println("Type ", t)
fmt.Println("Value ", v)
fmt.Println("Kind ", k)
for i := 0; i < v.NumField(); i++ {
fmt.Printf("Field:%d type:%T value:%v\n", i, v.Field(i), v.Field(i))
}
}

View File

@@ -1,16 +1,36 @@
package v3
import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sync"
)
type Info struct {
Node *yaml.Node
Title low.NodeReference[string]
Description low.NodeReference[string]
TermsOfService low.NodeReference[string]
Contact Contact
License License
Contact low.NodeReference[*Contact]
License low.NodeReference[*License]
Version low.NodeReference[string]
}
func (i *Info) Build(root *yaml.Node) error {
var wg sync.WaitGroup
wg.Add(2)
var errs []error
contact := Contact{}
_, kln, cn := utils.FindKeyNodeFull("contact", root.Content)
go datamodel.BuildModelAsync(cn, &contact, &wg, &errs)
license := License{}
_, kln, ln := utils.FindKeyNodeFull("license", root.Content)
go datamodel.BuildModelAsync(ln, &license, &wg, &errs)
wg.Wait()
i.Contact = low.NodeReference[*Contact]{Value: &contact, ValueNode: cn, KeyNode: kln}
i.License = low.NodeReference[*License]{Value: &license, ValueNode: ln, KeyNode: kln}
return nil
}

View File

@@ -2,11 +2,9 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
)
type License struct {
Node *yaml.Node
Name low.NodeReference[string]
URL low.NodeReference[string]
}

View File

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

View File

@@ -2,11 +2,9 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
)
type ServerVariable struct {
Node *yaml.Node
Enum []low.NodeReference[string]
Default low.NodeReference[string]
Description low.NodeReference[string]

View File

@@ -7,15 +7,17 @@ type HasNode interface {
}
type Buildable interface {
Build(node *yaml.Node)
Build(node *yaml.Node) error
}
type NodeReference[T comparable] struct {
type NodeReference[T any] struct {
Value T
Node *yaml.Node
ValueNode *yaml.Node
KeyNode *yaml.Node
}
type ObjectReference struct {
Value map[string]interface{}
Node *yaml.Node
ValueNode *yaml.Node
KeyNode *yaml.Node
}

View File

@@ -1,11 +1,13 @@
package utils
package datamodel
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"reflect"
"strconv"
"sync"
)
func BuildModel(node *yaml.Node, model interface{}) error {
@@ -19,12 +21,12 @@ func BuildModel(node *yaml.Node, model interface{}) error {
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.
cases := []Case{PascalCase, CamelCase, ScreamingSnakeCase,
SnakeCase, KebabCase, RegularCase}
cases := []utils.Case{utils.PascalCase, utils.CamelCase, utils.ScreamingSnakeCase,
utils.SnakeCase, utils.KebabCase, utils.RegularCase}
var vn, kn *yaml.Node
for _, tryCase := range cases {
kn, vn = FindKeyNode(ConvertCase(fName, tryCase), node.Content)
kn, vn = utils.FindKeyNode(utils.ConvertCase(fName, tryCase), node.Content)
if vn != nil {
break
}
@@ -38,7 +40,7 @@ func BuildModel(node *yaml.Node, model interface{}) error {
field := v.FieldByName(fName)
kind := field.Kind()
switch kind {
case reflect.Struct, reflect.Slice, reflect.Map:
case reflect.Struct, reflect.Slice, reflect.Map, reflect.Pointer:
err := SetField(field, vn, kn)
if err != nil {
return err
@@ -57,7 +59,7 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
case reflect.TypeOf(map[string]low.ObjectReference{}):
if valueNode != nil {
if IsNodeMap(valueNode) {
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := make(map[string]low.ObjectReference)
var currentLabel string
@@ -73,7 +75,8 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
}
items[currentLabel] = low.ObjectReference{
Value: decoded,
Node: sliceItem,
ValueNode: sliceItem,
KeyNode: valueNode,
}
}
field.Set(reflect.ValueOf(items))
@@ -84,7 +87,7 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
case reflect.TypeOf(map[string]low.NodeReference[string]{}):
if valueNode != nil {
if IsNodeMap(valueNode) {
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := make(map[string]low.NodeReference[string])
var currentLabel string
@@ -95,7 +98,8 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
}
items[currentLabel] = low.NodeReference[string]{
Value: sliceItem.Value,
Node: sliceItem,
ValueNode: sliceItem,
KeyNode: valueNode,
}
}
field.Set(reflect.ValueOf(items))
@@ -110,9 +114,9 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
if err != nil {
return err
}
if IsNodeMap(valueNode) {
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
or := low.ObjectReference{Value: decoded, Node: valueNode}
or := low.ObjectReference{Value: decoded, ValueNode: valueNode}
field.Set(reflect.ValueOf(or))
}
}
@@ -120,7 +124,7 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf([]low.ObjectReference{}):
if valueNode != nil {
if IsNodeArray(valueNode) {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.ObjectReference
for _, sliceItem := range valueNode.Content {
@@ -131,7 +135,8 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
}
items = append(items, low.ObjectReference{
Value: decoded,
Node: sliceItem,
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
@@ -141,9 +146,13 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf(low.NodeReference[string]{}):
if valueNode != nil {
if IsNodeStringValue(valueNode) {
if utils.IsNodeStringValue(valueNode) {
if field.CanSet() {
nr := low.NodeReference[string]{Value: valueNode.Value, Node: valueNode}
nr := low.NodeReference[string]{
Value: valueNode.Value,
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
@@ -151,10 +160,14 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf(low.NodeReference[bool]{}):
if valueNode != nil {
if IsNodeBoolValue(valueNode) {
if utils.IsNodeBoolValue(valueNode) {
if field.CanSet() {
bv, _ := strconv.ParseBool(valueNode.Value)
nr := low.NodeReference[bool]{Value: bv, Node: valueNode}
nr := low.NodeReference[bool]{
Value: bv,
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
@@ -162,10 +175,14 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf(low.NodeReference[int]{}):
if valueNode != nil {
if IsNodeIntValue(valueNode) {
if utils.IsNodeIntValue(valueNode) {
if field.CanSet() {
fv, _ := strconv.Atoi(valueNode.Value)
nr := low.NodeReference[int]{Value: fv, Node: valueNode}
nr := low.NodeReference[int]{
Value: fv,
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
@@ -173,10 +190,14 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf(low.NodeReference[int64]{}):
if valueNode != nil {
if IsNodeIntValue(valueNode) || IsNodeFloatValue(valueNode) { //
if utils.IsNodeIntValue(valueNode) || utils.IsNodeFloatValue(valueNode) { //
if field.CanSet() {
fv, _ := strconv.ParseInt(valueNode.Value, 10, 64)
nr := low.NodeReference[int64]{Value: fv, Node: valueNode}
nr := low.NodeReference[int64]{
Value: fv,
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
@@ -184,10 +205,14 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf(low.NodeReference[float32]{}):
if valueNode != nil {
if IsNodeFloatValue(valueNode) {
if utils.IsNodeFloatValue(valueNode) {
if field.CanSet() {
fv, _ := strconv.ParseFloat(valueNode.Value, 32)
nr := low.NodeReference[float32]{Value: float32(fv), Node: valueNode}
nr := low.NodeReference[float32]{
Value: float32(fv),
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
@@ -195,10 +220,14 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf(low.NodeReference[float64]{}):
if valueNode != nil {
if IsNodeFloatValue(valueNode) {
if utils.IsNodeFloatValue(valueNode) {
if field.CanSet() {
fv, _ := strconv.ParseFloat(valueNode.Value, 64)
nr := low.NodeReference[float64]{Value: fv, Node: valueNode}
nr := low.NodeReference[float64]{
Value: fv,
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
@@ -206,11 +235,15 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf([]low.NodeReference[string]{}):
if valueNode != nil {
if IsNodeArray(valueNode) {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[string]
for _, sliceItem := range valueNode.Content {
items = append(items, low.NodeReference[string]{Value: sliceItem.Value, Node: sliceItem})
items = append(items, low.NodeReference[string]{
Value: sliceItem.Value,
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
}
@@ -219,12 +252,16 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf([]low.NodeReference[float32]{}):
if valueNode != nil {
if IsNodeArray(valueNode) {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[float32]
for _, sliceItem := range valueNode.Content {
fv, _ := strconv.ParseFloat(sliceItem.Value, 32)
items = append(items, low.NodeReference[float32]{Value: float32(fv), Node: sliceItem})
items = append(items, low.NodeReference[float32]{
Value: float32(fv),
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
}
@@ -233,12 +270,12 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf([]low.NodeReference[float64]{}):
if valueNode != nil {
if IsNodeArray(valueNode) {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[float64]
for _, sliceItem := range valueNode.Content {
fv, _ := strconv.ParseFloat(sliceItem.Value, 64)
items = append(items, low.NodeReference[float64]{Value: fv, Node: sliceItem})
items = append(items, low.NodeReference[float64]{Value: fv, ValueNode: sliceItem})
}
field.Set(reflect.ValueOf(items))
}
@@ -247,12 +284,16 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf([]low.NodeReference[int]{}):
if valueNode != nil {
if IsNodeArray(valueNode) {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[int]
for _, sliceItem := range valueNode.Content {
iv, _ := strconv.Atoi(sliceItem.Value)
items = append(items, low.NodeReference[int]{Value: iv, Node: sliceItem})
items = append(items, low.NodeReference[int]{
Value: iv,
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
}
@@ -261,12 +302,16 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf([]low.NodeReference[int64]{}):
if valueNode != nil {
if IsNodeArray(valueNode) {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[int64]
for _, sliceItem := range valueNode.Content {
iv, _ := strconv.ParseInt(sliceItem.Value, 10, 64)
items = append(items, low.NodeReference[int64]{Value: iv, Node: sliceItem})
items = append(items, low.NodeReference[int64]{
Value: iv,
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
}
@@ -275,12 +320,16 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break
case reflect.TypeOf([]low.NodeReference[bool]{}):
if valueNode != nil {
if IsNodeArray(valueNode) {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[bool]
for _, sliceItem := range valueNode.Content {
bv, _ := strconv.ParseBool(sliceItem.Value)
items = append(items, low.NodeReference[bool]{Value: bv, Node: sliceItem})
items = append(items, low.NodeReference[bool]{
Value: bv,
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
}
@@ -288,8 +337,18 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
}
break
default:
m := field.Type()
return fmt.Errorf("unknown type, cannot parse: %v", m)
// we want to ignore everything else.
break
}
return nil
}
func BuildModelAsync(n *yaml.Node, model interface{}, lwg *sync.WaitGroup, errors *[]error) {
if n != nil {
err := BuildModel(n, model)
if err != nil {
*errors = append(*errors, err)
}
}
lwg.Done()
}

View File

@@ -1,4 +1,4 @@
package utils
package datamodel
import (
"github.com/pb33f/libopenapi/datamodel/low"
@@ -106,7 +106,7 @@ there:
hd := hotdog{}
cErr := BuildModel(&rootNode, &hd)
assert.Equal(t, 200, hd.Fat.Value)
assert.Equal(t, 3, hd.Fat.Node.Line)
assert.Equal(t, 3, hd.Fat.ValueNode.Line)
assert.Equal(t, true, hd.Grilled.Value)
assert.Equal(t, "yummy", hd.Name.Value)
assert.Equal(t, float32(200.45), hd.Ketchup.Value)
@@ -119,7 +119,7 @@ there:
assert.Len(t, hd.MaxTempAlt, 5)
assert.Equal(t, int64(7392837462032342), hd.MaxTempHigh.Value)
assert.Equal(t, 2, hd.Temps[1].Value)
assert.Equal(t, 26, hd.Temps[1].Node.Line)
assert.Equal(t, 26, hd.Temps[1].ValueNode.Line)
assert.Len(t, hd.UnknownElements.Value, 2)
assert.Len(t, hd.LotsOfUnknowns, 3)
assert.Len(t, hd.Where, 2)
@@ -129,24 +129,6 @@ there:
assert.NoError(t, cErr)
}
func TestBuildModel_UnsupportedType(t *testing.T) {
type notSupported struct {
cake low.NodeReference[uintptr]
}
ns := notSupported{}
yml := `cake: -99999`
var rootNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &rootNode)
assert.NoError(t, mErr)
cErr := BuildModel(&rootNode, &ns)
assert.Error(t, cErr)
assert.Nil(t, ns.cake)
}
func TestBuildModel_UseCopyNotRef(t *testing.T) {
yml := `cake: -99999`

View File

@@ -2,7 +2,9 @@ package openapi
import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/3.0"
"github.com/pb33f/libopenapi/utils"
)
func CreateDocument(spec []byte) (*v3.Document, error) {
@@ -13,7 +15,64 @@ func CreateDocument(spec []byte) (*v3.Document, error) {
return nil, err
}
doc := &v3.Document{}
doc.Build(info.RootNode.Content[0])
return doc, nil
doc := v3.Document{Version: low.NodeReference[string]{Value: info.Version, ValueNode: info.RootNode}}
// build an index
//idx := index.NewSpecIndex(info.RootNode)
datamodel.BuildModel(info.RootNode.Content[0], &doc)
// extract info
extractErr := extractInfo(info, &doc)
if extractErr != nil {
return nil, extractErr
}
// extract servers
extractErr = extractServers(info, &doc)
if extractErr != nil {
return nil, extractErr
}
return &doc, nil
}
func extractInfo(info *datamodel.SpecInfo, doc *v3.Document) error {
_, ln, vn := utils.FindKeyNodeFull("info", info.RootNode.Content)
if vn != nil {
ir := v3.Info{}
err := datamodel.BuildModel(vn, &ir)
if err != nil {
return err
}
err = ir.Build(vn)
nr := low.NodeReference[*v3.Info]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Info = nr
}
return nil
}
func extractServers(info *datamodel.SpecInfo, doc *v3.Document) error {
_, ln, vn := utils.FindKeyNodeFull("servers", info.RootNode.Content)
if vn != nil {
if utils.IsNodeArray(vn) {
var servers []low.NodeReference[*v3.Server]
for _, srvN := range vn.Content {
if utils.IsNodeMap(srvN) {
srvr := v3.Server{}
err := datamodel.BuildModel(srvN, &srvr)
if err != nil {
return err
}
srvr.Build(srvN)
servers = append(servers, low.NodeReference[*v3.Server]{
Value: &srvr,
ValueNode: srvN,
KeyNode: ln,
})
}
}
doc.Servers = servers
}
}
return nil
}

View File

@@ -13,7 +13,7 @@ func TestCreateDocument_NoData(t *testing.T) {
}
func TestCreateDocument(t *testing.T) {
data, aErr := ioutil.ReadFile("../test_specs/petstorev3.json")
data, aErr := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
assert.NoError(t, aErr)
doc, err := CreateDocument(data)

View File

@@ -6,8 +6,11 @@ info:
termsOfService: https://quobix.com
contact:
name: quobix
email: test@quobix.com
url: https://quobix.com
license:
name: Quobix
url: https://quobix.com/made-up
version: "1.2"
tags:
- name: "Burgers"
@@ -21,7 +24,22 @@ tags:
description: "Find out more information about our products)"
url: "https://quobix.com/"
servers:
- url: https://quobix.com/api
- url: "{scheme}://api.quobix.com"
description: "this is our main API server, for all fun API things."
variables:
scheme:
enum: [https]
default: https
description: this is a server variable for the scheme
- url: "https://{domain}.{host}.com"
description: "this is our second API server, for all fun API things."
variables:
domain:
default: "api"
description: the default API domain is 'api'
host:
default: "quobix.com"
description: the default host for this API is 'quobix.com'
paths:
/burgers:
post:

View File

@@ -239,6 +239,29 @@ func FindKeyNode(key string, nodes []*yaml.Node) (keyNode *yaml.Node, valueNode
return nil, nil
}
func FindKeyNodeFull(key string, nodes []*yaml.Node) (keyNode *yaml.Node, labelNode *yaml.Node, valueNode *yaml.Node) {
for i, v := range nodes {
if i%2 == 0 && key == v.Value {
return v, nodes[i], nodes[i+1] // next node is what we need.
}
for x, j := range v.Content {
if key == j.Value {
if IsNodeMap(v) {
if x+1 == len(v.Content) {
return v, v.Content[x], v.Content[x]
}
return v, v.Content[x], v.Content[x+1] // next node is what we need.
}
if IsNodeArray(v) {
return v, v.Content[x], v.Content[x]
}
}
}
}
return nil, nil, nil
}
var ObjectLabel = "object"
var IntegerLabel = "integer"
var NumberLabel = "number"