499 lines
13 KiB
Go
499 lines
13 KiB
Go
/*
|
|
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/kubernetes/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
|
|
}
|
|
|
|
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 parse the given template, return error
|
|
func (j *JSONPath) Parse(text string) (err error) {
|
|
j.parser, err = Parse(j.name, text)
|
|
return
|
|
}
|
|
|
|
// Execute bounds data into template and write 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 write 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 *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
|
|
}
|
|
|
|
// 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 {
|
|
return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[0].Value, sliceLength)
|
|
}
|
|
if params[1].Value > sliceLength {
|
|
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 extract 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 visit the given value recursively and push 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 filter 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{}
|
|
if 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
|
|
}
|
|
if 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
|
|
}
|