mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 02:33:34 +00:00
added vendors
This commit is contained in:
41
vendor/k8s.io/client-go/util/jsonpath/BUILD
generated
vendored
Normal file
41
vendor/k8s.io/client-go/util/jsonpath/BUILD
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"jsonpath_test.go",
|
||||
"parser_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"jsonpath.go",
|
||||
"node.go",
|
||||
"parser.go",
|
||||
],
|
||||
importpath = "k8s.io/client-go/util/jsonpath",
|
||||
deps = ["//vendor/k8s.io/client-go/third_party/forked/golang/template:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
20
vendor/k8s.io/client-go/util/jsonpath/doc.go
generated
vendored
Normal file
20
vendor/k8s.io/client-go/util/jsonpath/doc.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// package jsonpath is a template engine using jsonpath syntax,
|
||||
// which can be seen at http://goessner.net/articles/JsonPath/.
|
||||
// In addition, it has {range} {end} function to iterate list and slice.
|
||||
package jsonpath // import "k8s.io/client-go/util/jsonpath"
|
517
vendor/k8s.io/client-go/util/jsonpath/jsonpath.go
generated
vendored
Normal file
517
vendor/k8s.io/client-go/util/jsonpath/jsonpath.go
generated
vendored
Normal file
@ -0,0 +1,517 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package jsonpath
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"k8s.io/client-go/third_party/forked/golang/template"
|
||||
)
|
||||
|
||||
type JSONPath struct {
|
||||
name string
|
||||
parser *Parser
|
||||
stack [][]reflect.Value // push and pop values in different scopes
|
||||
cur []reflect.Value // current scope values
|
||||
beginRange int
|
||||
inRange int
|
||||
endRange int
|
||||
|
||||
allowMissingKeys bool
|
||||
}
|
||||
|
||||
// New creates a new JSONPath with the given name.
|
||||
func New(name string) *JSONPath {
|
||||
return &JSONPath{
|
||||
name: name,
|
||||
beginRange: 0,
|
||||
inRange: 0,
|
||||
endRange: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// AllowMissingKeys allows a caller to specify whether they want an error if a field or map key
|
||||
// cannot be located, or simply an empty result. The receiver is returned for chaining.
|
||||
func (j *JSONPath) AllowMissingKeys(allow bool) *JSONPath {
|
||||
j.allowMissingKeys = allow
|
||||
return j
|
||||
}
|
||||
|
||||
// Parse parses the given template and returns an error.
|
||||
func (j *JSONPath) Parse(text string) error {
|
||||
var err error
|
||||
j.parser, err = Parse(j.name, text)
|
||||
return err
|
||||
}
|
||||
|
||||
// Execute bounds data into template and writes the result.
|
||||
func (j *JSONPath) Execute(wr io.Writer, data interface{}) error {
|
||||
fullResults, err := j.FindResults(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for ix := range fullResults {
|
||||
if err := j.PrintResults(wr, fullResults[ix]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) {
|
||||
if j.parser == nil {
|
||||
return nil, fmt.Errorf("%s is an incomplete jsonpath template", j.name)
|
||||
}
|
||||
|
||||
j.cur = []reflect.Value{reflect.ValueOf(data)}
|
||||
nodes := j.parser.Root.Nodes
|
||||
fullResult := [][]reflect.Value{}
|
||||
for i := 0; i < len(nodes); i++ {
|
||||
node := nodes[i]
|
||||
results, err := j.walk(j.cur, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// encounter an end node, break the current block
|
||||
if j.endRange > 0 && j.endRange <= j.inRange {
|
||||
j.endRange -= 1
|
||||
break
|
||||
}
|
||||
// encounter a range node, start a range loop
|
||||
if j.beginRange > 0 {
|
||||
j.beginRange -= 1
|
||||
j.inRange += 1
|
||||
for k, value := range results {
|
||||
j.parser.Root.Nodes = nodes[i+1:]
|
||||
if k == len(results)-1 {
|
||||
j.inRange -= 1
|
||||
}
|
||||
nextResults, err := j.FindResults(value.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fullResult = append(fullResult, nextResults...)
|
||||
}
|
||||
break
|
||||
}
|
||||
fullResult = append(fullResult, results)
|
||||
}
|
||||
return fullResult, nil
|
||||
}
|
||||
|
||||
// PrintResults writes the results into writer
|
||||
func (j *JSONPath) PrintResults(wr io.Writer, results []reflect.Value) error {
|
||||
for i, r := range results {
|
||||
text, err := j.evalToText(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i != len(results)-1 {
|
||||
text = append(text, ' ')
|
||||
}
|
||||
if _, err = wr.Write(text); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// walk visits tree rooted at the given node in DFS order
|
||||
func (j *JSONPath) walk(value []reflect.Value, node Node) ([]reflect.Value, error) {
|
||||
switch node := node.(type) {
|
||||
case *ListNode:
|
||||
return j.evalList(value, node)
|
||||
case *TextNode:
|
||||
return []reflect.Value{reflect.ValueOf(node.Text)}, nil
|
||||
case *FieldNode:
|
||||
return j.evalField(value, node)
|
||||
case *ArrayNode:
|
||||
return j.evalArray(value, node)
|
||||
case *FilterNode:
|
||||
return j.evalFilter(value, node)
|
||||
case *IntNode:
|
||||
return j.evalInt(value, node)
|
||||
case *BoolNode:
|
||||
return j.evalBool(value, node)
|
||||
case *FloatNode:
|
||||
return j.evalFloat(value, node)
|
||||
case *WildcardNode:
|
||||
return j.evalWildcard(value, node)
|
||||
case *RecursiveNode:
|
||||
return j.evalRecursive(value, node)
|
||||
case *UnionNode:
|
||||
return j.evalUnion(value, node)
|
||||
case *IdentifierNode:
|
||||
return j.evalIdentifier(value, node)
|
||||
default:
|
||||
return value, fmt.Errorf("unexpected Node %v", node)
|
||||
}
|
||||
}
|
||||
|
||||
// evalInt evaluates IntNode
|
||||
func (j *JSONPath) evalInt(input []reflect.Value, node *IntNode) ([]reflect.Value, error) {
|
||||
result := make([]reflect.Value, len(input))
|
||||
for i := range input {
|
||||
result[i] = reflect.ValueOf(node.Value)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// evalFloat evaluates FloatNode
|
||||
func (j *JSONPath) evalFloat(input []reflect.Value, node *FloatNode) ([]reflect.Value, error) {
|
||||
result := make([]reflect.Value, len(input))
|
||||
for i := range input {
|
||||
result[i] = reflect.ValueOf(node.Value)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// evalBool evaluates BoolNode
|
||||
func (j *JSONPath) evalBool(input []reflect.Value, node *BoolNode) ([]reflect.Value, error) {
|
||||
result := make([]reflect.Value, len(input))
|
||||
for i := range input {
|
||||
result[i] = reflect.ValueOf(node.Value)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// evalList evaluates ListNode
|
||||
func (j *JSONPath) evalList(value []reflect.Value, node *ListNode) ([]reflect.Value, error) {
|
||||
var err error
|
||||
curValue := value
|
||||
for _, node := range node.Nodes {
|
||||
curValue, err = j.walk(curValue, node)
|
||||
if err != nil {
|
||||
return curValue, err
|
||||
}
|
||||
}
|
||||
return curValue, nil
|
||||
}
|
||||
|
||||
// evalIdentifier evaluates IdentifierNode
|
||||
func (j *JSONPath) evalIdentifier(input []reflect.Value, node *IdentifierNode) ([]reflect.Value, error) {
|
||||
results := []reflect.Value{}
|
||||
switch node.Name {
|
||||
case "range":
|
||||
j.stack = append(j.stack, j.cur)
|
||||
j.beginRange += 1
|
||||
results = input
|
||||
case "end":
|
||||
if j.endRange < j.inRange { // inside a loop, break the current block
|
||||
j.endRange += 1
|
||||
break
|
||||
}
|
||||
// the loop is about to end, pop value and continue the following execution
|
||||
if len(j.stack) > 0 {
|
||||
j.cur, j.stack = j.stack[len(j.stack)-1], j.stack[:len(j.stack)-1]
|
||||
} else {
|
||||
return results, fmt.Errorf("not in range, nothing to end")
|
||||
}
|
||||
default:
|
||||
return input, fmt.Errorf("unrecognized identifier %v", node.Name)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// evalArray evaluates ArrayNode
|
||||
func (j *JSONPath) evalArray(input []reflect.Value, node *ArrayNode) ([]reflect.Value, error) {
|
||||
result := []reflect.Value{}
|
||||
for _, value := range input {
|
||||
|
||||
value, isNil := template.Indirect(value)
|
||||
if isNil {
|
||||
continue
|
||||
}
|
||||
if value.Kind() != reflect.Array && value.Kind() != reflect.Slice {
|
||||
return input, fmt.Errorf("%v is not array or slice", value.Type())
|
||||
}
|
||||
params := node.Params
|
||||
if !params[0].Known {
|
||||
params[0].Value = 0
|
||||
}
|
||||
if params[0].Value < 0 {
|
||||
params[0].Value += value.Len()
|
||||
}
|
||||
if !params[1].Known {
|
||||
params[1].Value = value.Len()
|
||||
}
|
||||
|
||||
if params[1].Value < 0 {
|
||||
params[1].Value += value.Len()
|
||||
}
|
||||
|
||||
sliceLength := value.Len()
|
||||
if params[1].Value != params[0].Value { // if you're requesting zero elements, allow it through.
|
||||
if params[0].Value >= sliceLength || params[0].Value < 0 {
|
||||
return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[0].Value, sliceLength)
|
||||
}
|
||||
if params[1].Value > sliceLength || params[1].Value < 0 {
|
||||
return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[1].Value-1, sliceLength)
|
||||
}
|
||||
}
|
||||
|
||||
if !params[2].Known {
|
||||
value = value.Slice(params[0].Value, params[1].Value)
|
||||
} else {
|
||||
value = value.Slice3(params[0].Value, params[1].Value, params[2].Value)
|
||||
}
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
result = append(result, value.Index(i))
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// evalUnion evaluates UnionNode
|
||||
func (j *JSONPath) evalUnion(input []reflect.Value, node *UnionNode) ([]reflect.Value, error) {
|
||||
result := []reflect.Value{}
|
||||
for _, listNode := range node.Nodes {
|
||||
temp, err := j.evalList(input, listNode)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
result = append(result, temp...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (j *JSONPath) findFieldInValue(value *reflect.Value, node *FieldNode) (reflect.Value, error) {
|
||||
t := value.Type()
|
||||
var inlineValue *reflect.Value
|
||||
for ix := 0; ix < t.NumField(); ix++ {
|
||||
f := t.Field(ix)
|
||||
jsonTag := f.Tag.Get("json")
|
||||
parts := strings.Split(jsonTag, ",")
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == node.Value {
|
||||
return value.Field(ix), nil
|
||||
}
|
||||
if len(parts[0]) == 0 {
|
||||
val := value.Field(ix)
|
||||
inlineValue = &val
|
||||
}
|
||||
}
|
||||
if inlineValue != nil {
|
||||
if inlineValue.Kind() == reflect.Struct {
|
||||
// handle 'inline'
|
||||
match, err := j.findFieldInValue(inlineValue, node)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
if match.IsValid() {
|
||||
return match, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return value.FieldByName(node.Value), nil
|
||||
}
|
||||
|
||||
// evalField evaluates field of struct or key of map.
|
||||
func (j *JSONPath) evalField(input []reflect.Value, node *FieldNode) ([]reflect.Value, error) {
|
||||
results := []reflect.Value{}
|
||||
// If there's no input, there's no output
|
||||
if len(input) == 0 {
|
||||
return results, nil
|
||||
}
|
||||
for _, value := range input {
|
||||
var result reflect.Value
|
||||
value, isNil := template.Indirect(value)
|
||||
if isNil {
|
||||
continue
|
||||
}
|
||||
|
||||
if value.Kind() == reflect.Struct {
|
||||
var err error
|
||||
if result, err = j.findFieldInValue(&value, node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if value.Kind() == reflect.Map {
|
||||
mapKeyType := value.Type().Key()
|
||||
nodeValue := reflect.ValueOf(node.Value)
|
||||
// node value type must be convertible to map key type
|
||||
if !nodeValue.Type().ConvertibleTo(mapKeyType) {
|
||||
return results, fmt.Errorf("%s is not convertible to %s", nodeValue, mapKeyType)
|
||||
}
|
||||
result = value.MapIndex(nodeValue.Convert(mapKeyType))
|
||||
}
|
||||
if result.IsValid() {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
if len(results) == 0 {
|
||||
if j.allowMissingKeys {
|
||||
return results, nil
|
||||
}
|
||||
return results, fmt.Errorf("%s is not found", node.Value)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// evalWildcard extracts all contents of the given value
|
||||
func (j *JSONPath) evalWildcard(input []reflect.Value, node *WildcardNode) ([]reflect.Value, error) {
|
||||
results := []reflect.Value{}
|
||||
for _, value := range input {
|
||||
value, isNil := template.Indirect(value)
|
||||
if isNil {
|
||||
continue
|
||||
}
|
||||
|
||||
kind := value.Kind()
|
||||
if kind == reflect.Struct {
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
results = append(results, value.Field(i))
|
||||
}
|
||||
} else if kind == reflect.Map {
|
||||
for _, key := range value.MapKeys() {
|
||||
results = append(results, value.MapIndex(key))
|
||||
}
|
||||
} else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String {
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
results = append(results, value.Index(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// evalRecursive visits the given value recursively and pushes all of them to result
|
||||
func (j *JSONPath) evalRecursive(input []reflect.Value, node *RecursiveNode) ([]reflect.Value, error) {
|
||||
result := []reflect.Value{}
|
||||
for _, value := range input {
|
||||
results := []reflect.Value{}
|
||||
value, isNil := template.Indirect(value)
|
||||
if isNil {
|
||||
continue
|
||||
}
|
||||
|
||||
kind := value.Kind()
|
||||
if kind == reflect.Struct {
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
results = append(results, value.Field(i))
|
||||
}
|
||||
} else if kind == reflect.Map {
|
||||
for _, key := range value.MapKeys() {
|
||||
results = append(results, value.MapIndex(key))
|
||||
}
|
||||
} else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String {
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
results = append(results, value.Index(i))
|
||||
}
|
||||
}
|
||||
if len(results) != 0 {
|
||||
result = append(result, value)
|
||||
output, err := j.evalRecursive(results, node)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result = append(result, output...)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// evalFilter filters array according to FilterNode
|
||||
func (j *JSONPath) evalFilter(input []reflect.Value, node *FilterNode) ([]reflect.Value, error) {
|
||||
results := []reflect.Value{}
|
||||
for _, value := range input {
|
||||
value, _ = template.Indirect(value)
|
||||
|
||||
if value.Kind() != reflect.Array && value.Kind() != reflect.Slice {
|
||||
return input, fmt.Errorf("%v is not array or slice and cannot be filtered", value)
|
||||
}
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
temp := []reflect.Value{value.Index(i)}
|
||||
lefts, err := j.evalList(temp, node.Left)
|
||||
|
||||
//case exists
|
||||
if node.Operator == "exists" {
|
||||
if len(lefts) > 0 {
|
||||
results = append(results, value.Index(i))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
|
||||
var left, right interface{}
|
||||
switch {
|
||||
case len(lefts) == 0:
|
||||
continue
|
||||
case len(lefts) > 1:
|
||||
return input, fmt.Errorf("can only compare one element at a time")
|
||||
}
|
||||
left = lefts[0].Interface()
|
||||
|
||||
rights, err := j.evalList(temp, node.Right)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
switch {
|
||||
case len(rights) == 0:
|
||||
continue
|
||||
case len(rights) > 1:
|
||||
return input, fmt.Errorf("can only compare one element at a time")
|
||||
}
|
||||
right = rights[0].Interface()
|
||||
|
||||
pass := false
|
||||
switch node.Operator {
|
||||
case "<":
|
||||
pass, err = template.Less(left, right)
|
||||
case ">":
|
||||
pass, err = template.Greater(left, right)
|
||||
case "==":
|
||||
pass, err = template.Equal(left, right)
|
||||
case "!=":
|
||||
pass, err = template.NotEqual(left, right)
|
||||
case "<=":
|
||||
pass, err = template.LessEqual(left, right)
|
||||
case ">=":
|
||||
pass, err = template.GreaterEqual(left, right)
|
||||
default:
|
||||
return results, fmt.Errorf("unrecognized filter operator %s", node.Operator)
|
||||
}
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
if pass {
|
||||
results = append(results, value.Index(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// evalToText translates reflect value to corresponding text
|
||||
func (j *JSONPath) evalToText(v reflect.Value) ([]byte, error) {
|
||||
iface, ok := template.PrintableValue(v)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can't print type %s", v.Type())
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
fmt.Fprint(&buffer, iface)
|
||||
return buffer.Bytes(), nil
|
||||
}
|
371
vendor/k8s.io/client-go/util/jsonpath/jsonpath_test.go
generated
vendored
Normal file
371
vendor/k8s.io/client-go/util/jsonpath/jsonpath_test.go
generated
vendored
Normal file
@ -0,0 +1,371 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package jsonpath
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type jsonpathTest struct {
|
||||
name string
|
||||
template string
|
||||
input interface{}
|
||||
expect string
|
||||
expectError bool
|
||||
}
|
||||
|
||||
func testJSONPath(tests []jsonpathTest, allowMissingKeys bool, t *testing.T) {
|
||||
for _, test := range tests {
|
||||
j := New(test.name)
|
||||
j.AllowMissingKeys(allowMissingKeys)
|
||||
err := j.Parse(test.template)
|
||||
if err != nil {
|
||||
t.Errorf("in %s, parse %s error %v", test.name, test.template, err)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
err = j.Execute(buf, test.input)
|
||||
if test.expectError {
|
||||
if test.expectError && err == nil {
|
||||
t.Errorf("in %s, expected execute error", test.name)
|
||||
}
|
||||
continue
|
||||
} else if err != nil {
|
||||
t.Errorf("in %s, execute error %v", test.name, err)
|
||||
}
|
||||
out := buf.String()
|
||||
if out != test.expect {
|
||||
t.Errorf(`in %s, expect to get "%s", got "%s"`, test.name, test.expect, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testJSONPathSortOutput test cases related to map, the results may print in random order
|
||||
func testJSONPathSortOutput(tests []jsonpathTest, t *testing.T) {
|
||||
for _, test := range tests {
|
||||
j := New(test.name)
|
||||
err := j.Parse(test.template)
|
||||
if err != nil {
|
||||
t.Errorf("in %s, parse %s error %v", test.name, test.template, err)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
err = j.Execute(buf, test.input)
|
||||
if err != nil {
|
||||
t.Errorf("in %s, execute error %v", test.name, err)
|
||||
}
|
||||
out := buf.String()
|
||||
//since map is visited in random order, we need to sort the results.
|
||||
sortedOut := strings.Fields(out)
|
||||
sort.Strings(sortedOut)
|
||||
sortedExpect := strings.Fields(test.expect)
|
||||
sort.Strings(sortedExpect)
|
||||
if !reflect.DeepEqual(sortedOut, sortedExpect) {
|
||||
t.Errorf(`in %s, expect to get "%s", got "%s"`, test.name, test.expect, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testFailJSONPath(tests []jsonpathTest, t *testing.T) {
|
||||
for _, test := range tests {
|
||||
j := New(test.name)
|
||||
err := j.Parse(test.template)
|
||||
if err != nil {
|
||||
t.Errorf("in %s, parse %s error %v", test.name, test.template, err)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
err = j.Execute(buf, test.input)
|
||||
var out string
|
||||
if err == nil {
|
||||
out = "nil"
|
||||
} else {
|
||||
out = err.Error()
|
||||
}
|
||||
if out != test.expect {
|
||||
t.Errorf("in %s, expect to get error %q, got %q", test.name, test.expect, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type book struct {
|
||||
Category string
|
||||
Author string
|
||||
Title string
|
||||
Price float32
|
||||
}
|
||||
|
||||
func (b book) String() string {
|
||||
return fmt.Sprintf("{Category: %s, Author: %s, Title: %s, Price: %v}", b.Category, b.Author, b.Title, b.Price)
|
||||
}
|
||||
|
||||
type bicycle struct {
|
||||
Color string
|
||||
Price float32
|
||||
IsNew bool
|
||||
}
|
||||
|
||||
type empName string
|
||||
type job string
|
||||
type store struct {
|
||||
Book []book
|
||||
Bicycle []bicycle
|
||||
Name string
|
||||
Labels map[string]int
|
||||
Employees map[empName]job
|
||||
}
|
||||
|
||||
func TestStructInput(t *testing.T) {
|
||||
|
||||
storeData := store{
|
||||
Name: "jsonpath",
|
||||
Book: []book{
|
||||
{"reference", "Nigel Rees", "Sayings of the Centurey", 8.95},
|
||||
{"fiction", "Evelyn Waugh", "Sword of Honour", 12.99},
|
||||
{"fiction", "Herman Melville", "Moby Dick", 8.99},
|
||||
},
|
||||
Bicycle: []bicycle{
|
||||
{"red", 19.95, true},
|
||||
{"green", 20.01, false},
|
||||
},
|
||||
Labels: map[string]int{
|
||||
"engieer": 10,
|
||||
"web/html": 15,
|
||||
"k8s-app": 20,
|
||||
},
|
||||
Employees: map[empName]job{
|
||||
"jason": "manager",
|
||||
"dan": "clerk",
|
||||
},
|
||||
}
|
||||
|
||||
storeTests := []jsonpathTest{
|
||||
{"plain", "hello jsonpath", nil, "hello jsonpath", false},
|
||||
{"recursive", "{..}", []int{1, 2, 3}, "[1 2 3]", false},
|
||||
{"filter", "{[?(@<5)]}", []int{2, 6, 3, 7}, "2 3", false},
|
||||
{"quote", `{"{"}`, nil, "{", false},
|
||||
{"union", "{[1,3,4]}", []int{0, 1, 2, 3, 4}, "1 3 4", false},
|
||||
{"array", "{[0:2]}", []string{"Monday", "Tudesday"}, "Monday Tudesday", false},
|
||||
{"variable", "hello {.Name}", storeData, "hello jsonpath", false},
|
||||
{"dict/", "{$.Labels.web/html}", storeData, "15", false},
|
||||
{"dict/", "{$.Employees.jason}", storeData, "manager", false},
|
||||
{"dict/", "{$.Employees.dan}", storeData, "clerk", false},
|
||||
{"dict-", "{.Labels.k8s-app}", storeData, "20", false},
|
||||
{"nest", "{.Bicycle[*].Color}", storeData, "red green", false},
|
||||
{"allarray", "{.Book[*].Author}", storeData, "Nigel Rees Evelyn Waugh Herman Melville", false},
|
||||
{"allfileds", "{.Bicycle.*}", storeData, "{red 19.95 true} {green 20.01 false}", false},
|
||||
{"recurfileds", "{..Price}", storeData, "8.95 12.99 8.99 19.95 20.01", false},
|
||||
{"lastarray", "{.Book[-1:]}", storeData,
|
||||
"{Category: fiction, Author: Herman Melville, Title: Moby Dick, Price: 8.99}", false},
|
||||
{"recurarray", "{..Book[2]}", storeData,
|
||||
"{Category: fiction, Author: Herman Melville, Title: Moby Dick, Price: 8.99}", false},
|
||||
{"bool", "{.Bicycle[?(@.IsNew==true)]}", storeData, "{red 19.95 true}", false},
|
||||
}
|
||||
testJSONPath(storeTests, false, t)
|
||||
|
||||
missingKeyTests := []jsonpathTest{
|
||||
{"nonexistent field", "{.hello}", storeData, "", false},
|
||||
}
|
||||
testJSONPath(missingKeyTests, true, t)
|
||||
|
||||
failStoreTests := []jsonpathTest{
|
||||
{"invalid identifier", "{hello}", storeData, "unrecognized identifier hello", false},
|
||||
{"nonexistent field", "{.hello}", storeData, "hello is not found", false},
|
||||
{"invalid array", "{.Labels[0]}", storeData, "map[string]int is not array or slice", false},
|
||||
{"invalid filter operator", "{.Book[?(@.Price<>10)]}", storeData, "unrecognized filter operator <>", false},
|
||||
{"redundant end", "{range .Labels.*}{@}{end}{end}", storeData, "not in range, nothing to end", false},
|
||||
}
|
||||
testFailJSONPath(failStoreTests, t)
|
||||
}
|
||||
|
||||
func TestJSONInput(t *testing.T) {
|
||||
var pointsJSON = []byte(`[
|
||||
{"id": "i1", "x":4, "y":-5},
|
||||
{"id": "i2", "x":-2, "y":-5, "z":1},
|
||||
{"id": "i3", "x": 8, "y": 3 },
|
||||
{"id": "i4", "x": -6, "y": -1 },
|
||||
{"id": "i5", "x": 0, "y": 2, "z": 1 },
|
||||
{"id": "i6", "x": 1, "y": 4 }
|
||||
]`)
|
||||
var pointsData interface{}
|
||||
err := json.Unmarshal(pointsJSON, &pointsData)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
pointsTests := []jsonpathTest{
|
||||
{"exists filter", "{[?(@.z)].id}", pointsData, "i2 i5", false},
|
||||
{"bracket key", "{[0]['id']}", pointsData, "i1", false},
|
||||
}
|
||||
testJSONPath(pointsTests, false, t)
|
||||
}
|
||||
|
||||
// TestKubernetes tests some use cases from kubernetes
|
||||
func TestKubernetes(t *testing.T) {
|
||||
var input = []byte(`{
|
||||
"kind": "List",
|
||||
"items":[
|
||||
{
|
||||
"kind":"None",
|
||||
"metadata":{
|
||||
"name":"127.0.0.1",
|
||||
"labels":{
|
||||
"kubernetes.io/hostname":"127.0.0.1"
|
||||
}
|
||||
},
|
||||
"status":{
|
||||
"capacity":{"cpu":"4"},
|
||||
"ready": true,
|
||||
"addresses":[{"type": "LegacyHostIP", "address":"127.0.0.1"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind":"None",
|
||||
"metadata":{
|
||||
"name":"127.0.0.2",
|
||||
"labels":{
|
||||
"kubernetes.io/hostname":"127.0.0.2"
|
||||
}
|
||||
},
|
||||
"status":{
|
||||
"capacity":{"cpu":"8"},
|
||||
"ready": false,
|
||||
"addresses":[
|
||||
{"type": "LegacyHostIP", "address":"127.0.0.2"},
|
||||
{"type": "another", "address":"127.0.0.3"}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"users":[
|
||||
{
|
||||
"name": "myself",
|
||||
"user": {}
|
||||
},
|
||||
{
|
||||
"name": "e2e",
|
||||
"user": {"username": "admin", "password": "secret"}
|
||||
}
|
||||
]
|
||||
}`)
|
||||
var nodesData interface{}
|
||||
err := json.Unmarshal(input, &nodesData)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
nodesTests := []jsonpathTest{
|
||||
{"range item", `{range .items[*]}{.metadata.name}, {end}{.kind}`, nodesData, "127.0.0.1, 127.0.0.2, List", false},
|
||||
{"range item with quote", `{range .items[*]}{.metadata.name}{"\t"}{end}`, nodesData, "127.0.0.1\t127.0.0.2\t", false},
|
||||
{"range addresss", `{.items[*].status.addresses[*].address}`, nodesData,
|
||||
"127.0.0.1 127.0.0.2 127.0.0.3", false},
|
||||
{"double range", `{range .items[*]}{range .status.addresses[*]}{.address}, {end}{end}`, nodesData,
|
||||
"127.0.0.1, 127.0.0.2, 127.0.0.3, ", false},
|
||||
{"item name", `{.items[*].metadata.name}`, nodesData, "127.0.0.1 127.0.0.2", false},
|
||||
{"union nodes capacity", `{.items[*]['metadata.name', 'status.capacity']}`, nodesData,
|
||||
"127.0.0.1 127.0.0.2 map[cpu:4] map[cpu:8]", false},
|
||||
{"range nodes capacity", `{range .items[*]}[{.metadata.name}, {.status.capacity}] {end}`, nodesData,
|
||||
"[127.0.0.1, map[cpu:4]] [127.0.0.2, map[cpu:8]] ", false},
|
||||
{"user password", `{.users[?(@.name=="e2e")].user.password}`, &nodesData, "secret", false},
|
||||
{"hostname", `{.items[0].metadata.labels.kubernetes\.io/hostname}`, &nodesData, "127.0.0.1", false},
|
||||
{"hostname filter", `{.items[?(@.metadata.labels.kubernetes\.io/hostname=="127.0.0.1")].kind}`, &nodesData, "None", false},
|
||||
{"bool item", `{.items[?(@..ready==true)].metadata.name}`, &nodesData, "127.0.0.1", false},
|
||||
}
|
||||
testJSONPath(nodesTests, false, t)
|
||||
|
||||
randomPrintOrderTests := []jsonpathTest{
|
||||
{"recursive name", "{..name}", nodesData, `127.0.0.1 127.0.0.2 myself e2e`, false},
|
||||
}
|
||||
testJSONPathSortOutput(randomPrintOrderTests, t)
|
||||
}
|
||||
|
||||
func TestFilterPartialMatchesSometimesMissingAnnotations(t *testing.T) {
|
||||
// for https://issues.k8s.io/45546
|
||||
var input = []byte(`{
|
||||
"kind": "List",
|
||||
"items": [
|
||||
{
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "pod1",
|
||||
"annotations": {
|
||||
"color": "blue"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "pod2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "pod3",
|
||||
"annotations": {
|
||||
"color": "green"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "pod4",
|
||||
"annotations": {
|
||||
"color": "blue"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}`)
|
||||
var data interface{}
|
||||
err := json.Unmarshal(input, &data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testJSONPath(
|
||||
[]jsonpathTest{
|
||||
{
|
||||
"filter, should only match a subset, some items don't have annotations, tolerate missing items",
|
||||
`{.items[?(@.metadata.annotations.color=="blue")].metadata.name}`,
|
||||
data,
|
||||
"pod1 pod4",
|
||||
false, // expect no error
|
||||
},
|
||||
},
|
||||
true, // allow missing keys
|
||||
t,
|
||||
)
|
||||
|
||||
testJSONPath(
|
||||
[]jsonpathTest{
|
||||
{
|
||||
"filter, should only match a subset, some items don't have annotations, error on missing items",
|
||||
`{.items[?(@.metadata.annotations.color=="blue")].metadata.name}`,
|
||||
data,
|
||||
"",
|
||||
true, // expect an error
|
||||
},
|
||||
},
|
||||
false, // don't allow missing keys
|
||||
t,
|
||||
)
|
||||
}
|
255
vendor/k8s.io/client-go/util/jsonpath/node.go
generated
vendored
Normal file
255
vendor/k8s.io/client-go/util/jsonpath/node.go
generated
vendored
Normal file
@ -0,0 +1,255 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package jsonpath
|
||||
|
||||
import "fmt"
|
||||
|
||||
// NodeType identifies the type of a parse tree node.
|
||||
type NodeType int
|
||||
|
||||
// Type returns itself and provides an easy default implementation
|
||||
func (t NodeType) Type() NodeType {
|
||||
return t
|
||||
}
|
||||
|
||||
func (t NodeType) String() string {
|
||||
return NodeTypeName[t]
|
||||
}
|
||||
|
||||
const (
|
||||
NodeText NodeType = iota
|
||||
NodeArray
|
||||
NodeList
|
||||
NodeField
|
||||
NodeIdentifier
|
||||
NodeFilter
|
||||
NodeInt
|
||||
NodeFloat
|
||||
NodeWildcard
|
||||
NodeRecursive
|
||||
NodeUnion
|
||||
NodeBool
|
||||
)
|
||||
|
||||
var NodeTypeName = map[NodeType]string{
|
||||
NodeText: "NodeText",
|
||||
NodeArray: "NodeArray",
|
||||
NodeList: "NodeList",
|
||||
NodeField: "NodeField",
|
||||
NodeIdentifier: "NodeIdentifier",
|
||||
NodeFilter: "NodeFilter",
|
||||
NodeInt: "NodeInt",
|
||||
NodeFloat: "NodeFloat",
|
||||
NodeWildcard: "NodeWildcard",
|
||||
NodeRecursive: "NodeRecursive",
|
||||
NodeUnion: "NodeUnion",
|
||||
NodeBool: "NodeBool",
|
||||
}
|
||||
|
||||
type Node interface {
|
||||
Type() NodeType
|
||||
String() string
|
||||
}
|
||||
|
||||
// ListNode holds a sequence of nodes.
|
||||
type ListNode struct {
|
||||
NodeType
|
||||
Nodes []Node // The element nodes in lexical order.
|
||||
}
|
||||
|
||||
func newList() *ListNode {
|
||||
return &ListNode{NodeType: NodeList}
|
||||
}
|
||||
|
||||
func (l *ListNode) append(n Node) {
|
||||
l.Nodes = append(l.Nodes, n)
|
||||
}
|
||||
|
||||
func (l *ListNode) String() string {
|
||||
return fmt.Sprintf("%s", l.Type())
|
||||
}
|
||||
|
||||
// TextNode holds plain text.
|
||||
type TextNode struct {
|
||||
NodeType
|
||||
Text string // The text; may span newlines.
|
||||
}
|
||||
|
||||
func newText(text string) *TextNode {
|
||||
return &TextNode{NodeType: NodeText, Text: text}
|
||||
}
|
||||
|
||||
func (t *TextNode) String() string {
|
||||
return fmt.Sprintf("%s: %s", t.Type(), t.Text)
|
||||
}
|
||||
|
||||
// FieldNode holds field of struct
|
||||
type FieldNode struct {
|
||||
NodeType
|
||||
Value string
|
||||
}
|
||||
|
||||
func newField(value string) *FieldNode {
|
||||
return &FieldNode{NodeType: NodeField, Value: value}
|
||||
}
|
||||
|
||||
func (f *FieldNode) String() string {
|
||||
return fmt.Sprintf("%s: %s", f.Type(), f.Value)
|
||||
}
|
||||
|
||||
// IdentifierNode holds an identifier
|
||||
type IdentifierNode struct {
|
||||
NodeType
|
||||
Name string
|
||||
}
|
||||
|
||||
func newIdentifier(value string) *IdentifierNode {
|
||||
return &IdentifierNode{
|
||||
NodeType: NodeIdentifier,
|
||||
Name: value,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *IdentifierNode) String() string {
|
||||
return fmt.Sprintf("%s: %s", f.Type(), f.Name)
|
||||
}
|
||||
|
||||
// ParamsEntry holds param information for ArrayNode
|
||||
type ParamsEntry struct {
|
||||
Value int
|
||||
Known bool // whether the value is known when parse it
|
||||
}
|
||||
|
||||
// ArrayNode holds start, end, step information for array index selection
|
||||
type ArrayNode struct {
|
||||
NodeType
|
||||
Params [3]ParamsEntry // start, end, step
|
||||
}
|
||||
|
||||
func newArray(params [3]ParamsEntry) *ArrayNode {
|
||||
return &ArrayNode{
|
||||
NodeType: NodeArray,
|
||||
Params: params,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ArrayNode) String() string {
|
||||
return fmt.Sprintf("%s: %v", a.Type(), a.Params)
|
||||
}
|
||||
|
||||
// FilterNode holds operand and operator information for filter
|
||||
type FilterNode struct {
|
||||
NodeType
|
||||
Left *ListNode
|
||||
Right *ListNode
|
||||
Operator string
|
||||
}
|
||||
|
||||
func newFilter(left, right *ListNode, operator string) *FilterNode {
|
||||
return &FilterNode{
|
||||
NodeType: NodeFilter,
|
||||
Left: left,
|
||||
Right: right,
|
||||
Operator: operator,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FilterNode) String() string {
|
||||
return fmt.Sprintf("%s: %s %s %s", f.Type(), f.Left, f.Operator, f.Right)
|
||||
}
|
||||
|
||||
// IntNode holds integer value
|
||||
type IntNode struct {
|
||||
NodeType
|
||||
Value int
|
||||
}
|
||||
|
||||
func newInt(num int) *IntNode {
|
||||
return &IntNode{NodeType: NodeInt, Value: num}
|
||||
}
|
||||
|
||||
func (i *IntNode) String() string {
|
||||
return fmt.Sprintf("%s: %d", i.Type(), i.Value)
|
||||
}
|
||||
|
||||
// FloatNode holds float value
|
||||
type FloatNode struct {
|
||||
NodeType
|
||||
Value float64
|
||||
}
|
||||
|
||||
func newFloat(num float64) *FloatNode {
|
||||
return &FloatNode{NodeType: NodeFloat, Value: num}
|
||||
}
|
||||
|
||||
func (i *FloatNode) String() string {
|
||||
return fmt.Sprintf("%s: %f", i.Type(), i.Value)
|
||||
}
|
||||
|
||||
// WildcardNode means a wildcard
|
||||
type WildcardNode struct {
|
||||
NodeType
|
||||
}
|
||||
|
||||
func newWildcard() *WildcardNode {
|
||||
return &WildcardNode{NodeType: NodeWildcard}
|
||||
}
|
||||
|
||||
func (i *WildcardNode) String() string {
|
||||
return fmt.Sprintf("%s", i.Type())
|
||||
}
|
||||
|
||||
// RecursiveNode means a recursive descent operator
|
||||
type RecursiveNode struct {
|
||||
NodeType
|
||||
}
|
||||
|
||||
func newRecursive() *RecursiveNode {
|
||||
return &RecursiveNode{NodeType: NodeRecursive}
|
||||
}
|
||||
|
||||
func (r *RecursiveNode) String() string {
|
||||
return fmt.Sprintf("%s", r.Type())
|
||||
}
|
||||
|
||||
// UnionNode is union of ListNode
|
||||
type UnionNode struct {
|
||||
NodeType
|
||||
Nodes []*ListNode
|
||||
}
|
||||
|
||||
func newUnion(nodes []*ListNode) *UnionNode {
|
||||
return &UnionNode{NodeType: NodeUnion, Nodes: nodes}
|
||||
}
|
||||
|
||||
func (u *UnionNode) String() string {
|
||||
return fmt.Sprintf("%s", u.Type())
|
||||
}
|
||||
|
||||
// BoolNode holds bool value
|
||||
type BoolNode struct {
|
||||
NodeType
|
||||
Value bool
|
||||
}
|
||||
|
||||
func newBool(value bool) *BoolNode {
|
||||
return &BoolNode{NodeType: NodeBool, Value: value}
|
||||
}
|
||||
|
||||
func (b *BoolNode) String() string {
|
||||
return fmt.Sprintf("%s: %t", b.Type(), b.Value)
|
||||
}
|
525
vendor/k8s.io/client-go/util/jsonpath/parser.go
generated
vendored
Normal file
525
vendor/k8s.io/client-go/util/jsonpath/parser.go
generated
vendored
Normal file
@ -0,0 +1,525 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package jsonpath
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const eof = -1
|
||||
|
||||
const (
|
||||
leftDelim = "{"
|
||||
rightDelim = "}"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
Name string
|
||||
Root *ListNode
|
||||
input string
|
||||
cur *ListNode
|
||||
pos int
|
||||
start int
|
||||
width int
|
||||
}
|
||||
|
||||
var (
|
||||
ErrSyntax = errors.New("invalid syntax")
|
||||
dictKeyRex = regexp.MustCompile(`^'([^']*)'$`)
|
||||
sliceOperatorRex = regexp.MustCompile(`^(-?[\d]*)(:-?[\d]*)?(:[\d]*)?$`)
|
||||
)
|
||||
|
||||
// Parse parsed the given text and return a node Parser.
|
||||
// If an error is encountered, parsing stops and an empty
|
||||
// Parser is returned with the error
|
||||
func Parse(name, text string) (*Parser, error) {
|
||||
p := NewParser(name)
|
||||
err := p.Parse(text)
|
||||
if err != nil {
|
||||
p = nil
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
func NewParser(name string) *Parser {
|
||||
return &Parser{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// parseAction parsed the expression inside delimiter
|
||||
func parseAction(name, text string) (*Parser, error) {
|
||||
p, err := Parse(name, fmt.Sprintf("%s%s%s", leftDelim, text, rightDelim))
|
||||
// when error happens, p will be nil, so we need to return here
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
p.Root = p.Root.Nodes[0].(*ListNode)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(text string) error {
|
||||
p.input = text
|
||||
p.Root = newList()
|
||||
p.pos = 0
|
||||
return p.parseText(p.Root)
|
||||
}
|
||||
|
||||
// consumeText return the parsed text since last cosumeText
|
||||
func (p *Parser) consumeText() string {
|
||||
value := p.input[p.start:p.pos]
|
||||
p.start = p.pos
|
||||
return value
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (p *Parser) next() rune {
|
||||
if int(p.pos) >= len(p.input) {
|
||||
p.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(p.input[p.pos:])
|
||||
p.width = w
|
||||
p.pos += p.width
|
||||
return r
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (p *Parser) peek() rune {
|
||||
r := p.next()
|
||||
p.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
func (p *Parser) backup() {
|
||||
p.pos -= p.width
|
||||
}
|
||||
|
||||
func (p *Parser) parseText(cur *ListNode) error {
|
||||
for {
|
||||
if strings.HasPrefix(p.input[p.pos:], leftDelim) {
|
||||
if p.pos > p.start {
|
||||
cur.append(newText(p.consumeText()))
|
||||
}
|
||||
return p.parseLeftDelim(cur)
|
||||
}
|
||||
if p.next() == eof {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Correctly reached EOF.
|
||||
if p.pos > p.start {
|
||||
cur.append(newText(p.consumeText()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseLeftDelim scans the left delimiter, which is known to be present.
|
||||
func (p *Parser) parseLeftDelim(cur *ListNode) error {
|
||||
p.pos += len(leftDelim)
|
||||
p.consumeText()
|
||||
newNode := newList()
|
||||
cur.append(newNode)
|
||||
cur = newNode
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
|
||||
func (p *Parser) parseInsideAction(cur *ListNode) error {
|
||||
prefixMap := map[string]func(*ListNode) error{
|
||||
rightDelim: p.parseRightDelim,
|
||||
"[?(": p.parseFilter,
|
||||
"..": p.parseRecursive,
|
||||
}
|
||||
for prefix, parseFunc := range prefixMap {
|
||||
if strings.HasPrefix(p.input[p.pos:], prefix) {
|
||||
return parseFunc(cur)
|
||||
}
|
||||
}
|
||||
|
||||
switch r := p.next(); {
|
||||
case r == eof || isEndOfLine(r):
|
||||
return fmt.Errorf("unclosed action")
|
||||
case r == ' ':
|
||||
p.consumeText()
|
||||
case r == '@' || r == '$': //the current object, just pass it
|
||||
p.consumeText()
|
||||
case r == '[':
|
||||
return p.parseArray(cur)
|
||||
case r == '"' || r == '\'':
|
||||
return p.parseQuote(cur, r)
|
||||
case r == '.':
|
||||
return p.parseField(cur)
|
||||
case r == '+' || r == '-' || unicode.IsDigit(r):
|
||||
p.backup()
|
||||
return p.parseNumber(cur)
|
||||
case isAlphaNumeric(r):
|
||||
p.backup()
|
||||
return p.parseIdentifier(cur)
|
||||
default:
|
||||
return fmt.Errorf("unrecognized character in action: %#U", r)
|
||||
}
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
|
||||
// parseRightDelim scans the right delimiter, which is known to be present.
|
||||
func (p *Parser) parseRightDelim(cur *ListNode) error {
|
||||
p.pos += len(rightDelim)
|
||||
p.consumeText()
|
||||
cur = p.Root
|
||||
return p.parseText(cur)
|
||||
}
|
||||
|
||||
// parseIdentifier scans build-in keywords, like "range" "end"
|
||||
func (p *Parser) parseIdentifier(cur *ListNode) error {
|
||||
var r rune
|
||||
for {
|
||||
r = p.next()
|
||||
if isTerminator(r) {
|
||||
p.backup()
|
||||
break
|
||||
}
|
||||
}
|
||||
value := p.consumeText()
|
||||
|
||||
if isBool(value) {
|
||||
v, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can not parse bool '%s': %s", value, err.Error())
|
||||
}
|
||||
|
||||
cur.append(newBool(v))
|
||||
} else {
|
||||
cur.append(newIdentifier(value))
|
||||
}
|
||||
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
|
||||
// parseRecursive scans the recursive desent operator ..
|
||||
func (p *Parser) parseRecursive(cur *ListNode) error {
|
||||
p.pos += len("..")
|
||||
p.consumeText()
|
||||
cur.append(newRecursive())
|
||||
if r := p.peek(); isAlphaNumeric(r) {
|
||||
return p.parseField(cur)
|
||||
}
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
|
||||
// parseNumber scans number
|
||||
func (p *Parser) parseNumber(cur *ListNode) error {
|
||||
r := p.peek()
|
||||
if r == '+' || r == '-' {
|
||||
r = p.next()
|
||||
}
|
||||
for {
|
||||
r = p.next()
|
||||
if r != '.' && !unicode.IsDigit(r) {
|
||||
p.backup()
|
||||
break
|
||||
}
|
||||
}
|
||||
value := p.consumeText()
|
||||
i, err := strconv.Atoi(value)
|
||||
if err == nil {
|
||||
cur.append(newInt(i))
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
d, err := strconv.ParseFloat(value, 64)
|
||||
if err == nil {
|
||||
cur.append(newFloat(d))
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
return fmt.Errorf("cannot parse number %s", value)
|
||||
}
|
||||
|
||||
// parseArray scans array index selection
|
||||
func (p *Parser) parseArray(cur *ListNode) error {
|
||||
Loop:
|
||||
for {
|
||||
switch p.next() {
|
||||
case eof, '\n':
|
||||
return fmt.Errorf("unterminated array")
|
||||
case ']':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
text := p.consumeText()
|
||||
text = string(text[1 : len(text)-1])
|
||||
if text == "*" {
|
||||
text = ":"
|
||||
}
|
||||
|
||||
//union operator
|
||||
strs := strings.Split(text, ",")
|
||||
if len(strs) > 1 {
|
||||
union := []*ListNode{}
|
||||
for _, str := range strs {
|
||||
parser, err := parseAction("union", fmt.Sprintf("[%s]", strings.Trim(str, " ")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
union = append(union, parser.Root)
|
||||
}
|
||||
cur.append(newUnion(union))
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
|
||||
// dict key
|
||||
value := dictKeyRex.FindStringSubmatch(text)
|
||||
if value != nil {
|
||||
parser, err := parseAction("arraydict", fmt.Sprintf(".%s", value[1]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, node := range parser.Root.Nodes {
|
||||
cur.append(node)
|
||||
}
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
|
||||
//slice operator
|
||||
value = sliceOperatorRex.FindStringSubmatch(text)
|
||||
if value == nil {
|
||||
return fmt.Errorf("invalid array index %s", text)
|
||||
}
|
||||
value = value[1:]
|
||||
params := [3]ParamsEntry{}
|
||||
for i := 0; i < 3; i++ {
|
||||
if value[i] != "" {
|
||||
if i > 0 {
|
||||
value[i] = value[i][1:]
|
||||
}
|
||||
if i > 0 && value[i] == "" {
|
||||
params[i].Known = false
|
||||
} else {
|
||||
var err error
|
||||
params[i].Known = true
|
||||
params[i].Value, err = strconv.Atoi(value[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("array index %s is not a number", value[i])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if i == 1 {
|
||||
params[i].Known = true
|
||||
params[i].Value = params[0].Value + 1
|
||||
} else {
|
||||
params[i].Known = false
|
||||
params[i].Value = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
cur.append(newArray(params))
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
|
||||
// parseFilter scans filter inside array selection
|
||||
func (p *Parser) parseFilter(cur *ListNode) error {
|
||||
p.pos += len("[?(")
|
||||
p.consumeText()
|
||||
begin := false
|
||||
end := false
|
||||
var pair rune
|
||||
|
||||
Loop:
|
||||
for {
|
||||
r := p.next()
|
||||
switch r {
|
||||
case eof, '\n':
|
||||
return fmt.Errorf("unterminated filter")
|
||||
case '"', '\'':
|
||||
if begin == false {
|
||||
//save the paired rune
|
||||
begin = true
|
||||
pair = r
|
||||
continue
|
||||
}
|
||||
//only add when met paired rune
|
||||
if p.input[p.pos-2] != '\\' && r == pair {
|
||||
end = true
|
||||
}
|
||||
case ')':
|
||||
//in rightParser below quotes only appear zero or once
|
||||
//and must be paired at the beginning and end
|
||||
if begin == end {
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
}
|
||||
if p.next() != ']' {
|
||||
return fmt.Errorf("unclosed array expect ]")
|
||||
}
|
||||
reg := regexp.MustCompile(`^([^!<>=]+)([!<>=]+)(.+?)$`)
|
||||
text := p.consumeText()
|
||||
text = string(text[:len(text)-2])
|
||||
value := reg.FindStringSubmatch(text)
|
||||
if value == nil {
|
||||
parser, err := parseAction("text", text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cur.append(newFilter(parser.Root, newList(), "exists"))
|
||||
} else {
|
||||
leftParser, err := parseAction("left", value[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rightParser, err := parseAction("right", value[3])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cur.append(newFilter(leftParser.Root, rightParser.Root, value[2]))
|
||||
}
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
|
||||
// parseQuote unquotes string inside double or single quote
|
||||
func (p *Parser) parseQuote(cur *ListNode, end rune) error {
|
||||
Loop:
|
||||
for {
|
||||
switch p.next() {
|
||||
case eof, '\n':
|
||||
return fmt.Errorf("unterminated quoted string")
|
||||
case end:
|
||||
//if it's not escape break the Loop
|
||||
if p.input[p.pos-2] != '\\' {
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
}
|
||||
value := p.consumeText()
|
||||
s, err := UnquoteExtend(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unquote string %s error %v", value, err)
|
||||
}
|
||||
cur.append(newText(s))
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
|
||||
// parseField scans a field until a terminator
|
||||
func (p *Parser) parseField(cur *ListNode) error {
|
||||
p.consumeText()
|
||||
for p.advance() {
|
||||
}
|
||||
value := p.consumeText()
|
||||
if value == "*" {
|
||||
cur.append(newWildcard())
|
||||
} else {
|
||||
cur.append(newField(strings.Replace(value, "\\", "", -1)))
|
||||
}
|
||||
return p.parseInsideAction(cur)
|
||||
}
|
||||
|
||||
// advance scans until next non-escaped terminator
|
||||
func (p *Parser) advance() bool {
|
||||
r := p.next()
|
||||
if r == '\\' {
|
||||
p.next()
|
||||
} else if isTerminator(r) {
|
||||
p.backup()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isTerminator reports whether the input is at valid termination character to appear after an identifier.
|
||||
func isTerminator(r rune) bool {
|
||||
if isSpace(r) || isEndOfLine(r) {
|
||||
return true
|
||||
}
|
||||
switch r {
|
||||
case eof, '.', ',', '[', ']', '$', '@', '{', '}':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isSpace reports whether r is a space character.
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
// isEndOfLine reports whether r is an end-of-line character.
|
||||
func isEndOfLine(r rune) bool {
|
||||
return r == '\r' || r == '\n'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
}
|
||||
|
||||
// isBool reports whether s is a boolean value.
|
||||
func isBool(s string) bool {
|
||||
return s == "true" || s == "false"
|
||||
}
|
||||
|
||||
//UnquoteExtend is almost same as strconv.Unquote(), but it support parse single quotes as a string
|
||||
func UnquoteExtend(s string) (string, error) {
|
||||
n := len(s)
|
||||
if n < 2 {
|
||||
return "", ErrSyntax
|
||||
}
|
||||
quote := s[0]
|
||||
if quote != s[n-1] {
|
||||
return "", ErrSyntax
|
||||
}
|
||||
s = s[1 : n-1]
|
||||
|
||||
if quote != '"' && quote != '\'' {
|
||||
return "", ErrSyntax
|
||||
}
|
||||
|
||||
// Is it trivial? Avoid allocation.
|
||||
if !contains(s, '\\') && !contains(s, quote) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
var runeTmp [utf8.UTFMax]byte
|
||||
buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
|
||||
for len(s) > 0 {
|
||||
c, multibyte, ss, err := strconv.UnquoteChar(s, quote)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s = ss
|
||||
if c < utf8.RuneSelf || !multibyte {
|
||||
buf = append(buf, byte(c))
|
||||
} else {
|
||||
n := utf8.EncodeRune(runeTmp[:], c)
|
||||
buf = append(buf, runeTmp[:n]...)
|
||||
}
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func contains(s string, c byte) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
152
vendor/k8s.io/client-go/util/jsonpath/parser_test.go
generated
vendored
Normal file
152
vendor/k8s.io/client-go/util/jsonpath/parser_test.go
generated
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package jsonpath
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type parserTest struct {
|
||||
name string
|
||||
text string
|
||||
nodes []Node
|
||||
shouldError bool
|
||||
}
|
||||
|
||||
var parserTests = []parserTest{
|
||||
{"plain", `hello jsonpath`, []Node{newText("hello jsonpath")}, false},
|
||||
{"variable", `hello {.jsonpath}`,
|
||||
[]Node{newText("hello "), newList(), newField("jsonpath")}, false},
|
||||
{"arrayfiled", `hello {['jsonpath']}`,
|
||||
[]Node{newText("hello "), newList(), newField("jsonpath")}, false},
|
||||
{"quote", `{"{"}`, []Node{newList(), newText("{")}, false},
|
||||
{"array", `{[1:3]}`, []Node{newList(),
|
||||
newArray([3]ParamsEntry{{1, true}, {3, true}, {0, false}})}, false},
|
||||
{"allarray", `{.book[*].author}`,
|
||||
[]Node{newList(), newField("book"),
|
||||
newArray([3]ParamsEntry{{0, false}, {0, false}, {0, false}}), newField("author")}, false},
|
||||
{"wildcard", `{.bicycle.*}`,
|
||||
[]Node{newList(), newField("bicycle"), newWildcard()}, false},
|
||||
{"filter", `{[?(@.price<3)]}`,
|
||||
[]Node{newList(), newFilter(newList(), newList(), "<"),
|
||||
newList(), newField("price"), newList(), newInt(3)}, false},
|
||||
{"recursive", `{..}`, []Node{newList(), newRecursive()}, false},
|
||||
{"recurField", `{..price}`,
|
||||
[]Node{newList(), newRecursive(), newField("price")}, false},
|
||||
{"arraydict", `{['book.price']}`, []Node{newList(),
|
||||
newField("book"), newField("price"),
|
||||
}, false},
|
||||
{"union", `{['bicycle.price', 3, 'book.price']}`, []Node{newList(), newUnion([]*ListNode{}),
|
||||
newList(), newField("bicycle"), newField("price"),
|
||||
newList(), newArray([3]ParamsEntry{{3, true}, {4, true}, {0, false}}),
|
||||
newList(), newField("book"), newField("price"),
|
||||
}, false},
|
||||
{"range", `{range .items}{.name},{end}`, []Node{
|
||||
newList(), newIdentifier("range"), newField("items"),
|
||||
newList(), newField("name"), newText(","),
|
||||
newList(), newIdentifier("end"),
|
||||
}, false},
|
||||
{"malformat input", `{\\\}`, []Node{}, true},
|
||||
{"paired parentheses in quotes", `{[?(@.status.nodeInfo.osImage == "()")]}`,
|
||||
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("()")}, false},
|
||||
{"paired parentheses in double quotes and with double quotes escape", `{[?(@.status.nodeInfo.osImage == "(\"\")")]}`,
|
||||
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("(\"\")")}, false},
|
||||
{"unregular parentheses in double quotes", `{[?(@.test == "())(")]}`,
|
||||
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("test"), newList(), newText("())(")}, false},
|
||||
{"plain text in single quotes", `{[?(@.status.nodeInfo.osImage == 'Linux')]}`,
|
||||
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("Linux")}, false},
|
||||
{"test filter suffix", `{[?(@.status.nodeInfo.osImage == "{[()]}")]}`,
|
||||
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("{[()]}")}, false},
|
||||
{"double inside single", `{[?(@.status.nodeInfo.osImage == "''")]}`,
|
||||
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("''")}, false},
|
||||
{"single inside double", `{[?(@.status.nodeInfo.osImage == '""')]}`,
|
||||
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("\"\"")}, false},
|
||||
{"single containing escaped single", `{[?(@.status.nodeInfo.osImage == '\\\'')]}`,
|
||||
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("\\'")}, false},
|
||||
}
|
||||
|
||||
func collectNode(nodes []Node, cur Node) []Node {
|
||||
nodes = append(nodes, cur)
|
||||
switch cur.Type() {
|
||||
case NodeList:
|
||||
for _, node := range cur.(*ListNode).Nodes {
|
||||
nodes = collectNode(nodes, node)
|
||||
}
|
||||
case NodeFilter:
|
||||
nodes = collectNode(nodes, cur.(*FilterNode).Left)
|
||||
nodes = collectNode(nodes, cur.(*FilterNode).Right)
|
||||
case NodeUnion:
|
||||
for _, node := range cur.(*UnionNode).Nodes {
|
||||
nodes = collectNode(nodes, node)
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
for _, test := range parserTests {
|
||||
parser, err := Parse(test.name, test.text)
|
||||
if test.shouldError {
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error when parsing %s", test.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("parse %s error %v", test.name, err)
|
||||
}
|
||||
result := collectNode([]Node{}, parser.Root)[1:]
|
||||
if len(result) != len(test.nodes) {
|
||||
t.Errorf("in %s, expect to get %d nodes, got %d nodes", test.name, len(test.nodes), len(result))
|
||||
t.Error(result)
|
||||
}
|
||||
for i, expect := range test.nodes {
|
||||
if result[i].String() != expect.String() {
|
||||
t.Errorf("in %s, %dth node, expect %v, got %v", test.name, i, expect, result[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type failParserTest struct {
|
||||
name string
|
||||
text string
|
||||
err string
|
||||
}
|
||||
|
||||
func TestFailParser(t *testing.T) {
|
||||
failParserTests := []failParserTest{
|
||||
{"unclosed action", "{.hello", "unclosed action"},
|
||||
{"unrecognized character", "{*}", "unrecognized character in action: U+002A '*'"},
|
||||
{"invalid number", "{+12.3.0}", "cannot parse number +12.3.0"},
|
||||
{"unterminated array", "{[1}", "unterminated array"},
|
||||
{"invalid index", "{[::-1]}", "invalid array index ::-1"},
|
||||
{"unterminated filter", "{[?(.price]}", "unterminated filter"},
|
||||
}
|
||||
for _, test := range failParserTests {
|
||||
_, err := Parse(test.name, test.text)
|
||||
var out string
|
||||
if err == nil {
|
||||
out = "nil"
|
||||
} else {
|
||||
out = err.Error()
|
||||
}
|
||||
if out != test.err {
|
||||
t.Errorf("in %s, expect to get error %v, got %v", test.name, test.err, out)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user