mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-01 04:41:22 +00:00
419 lines
10 KiB
Go
419 lines
10 KiB
Go
package jmespath
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
/* This is a tree based interpreter. It walks the AST and directly
|
|
interprets the AST to search through a JSON document.
|
|
*/
|
|
|
|
type treeInterpreter struct {
|
|
fCall *functionCaller
|
|
}
|
|
|
|
func newInterpreter() *treeInterpreter {
|
|
interpreter := treeInterpreter{}
|
|
interpreter.fCall = newFunctionCaller()
|
|
return &interpreter
|
|
}
|
|
|
|
type expRef struct {
|
|
ref ASTNode
|
|
}
|
|
|
|
// Execute takes an ASTNode and input data and interprets the AST directly.
|
|
// It will produce the result of applying the JMESPath expression associated
|
|
// with the ASTNode to the input data "value".
|
|
func (intr *treeInterpreter) Execute(node ASTNode, value interface{}) (interface{}, error) {
|
|
switch node.nodeType {
|
|
case ASTComparator:
|
|
left, err := intr.Execute(node.children[0], value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
right, err := intr.Execute(node.children[1], value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch node.value {
|
|
case tEQ:
|
|
return objsEqual(left, right), nil
|
|
case tNE:
|
|
return !objsEqual(left, right), nil
|
|
}
|
|
leftNum, ok := left.(float64)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
rightNum, ok := right.(float64)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
switch node.value {
|
|
case tGT:
|
|
return leftNum > rightNum, nil
|
|
case tGTE:
|
|
return leftNum >= rightNum, nil
|
|
case tLT:
|
|
return leftNum < rightNum, nil
|
|
case tLTE:
|
|
return leftNum <= rightNum, nil
|
|
}
|
|
case ASTExpRef:
|
|
return expRef{ref: node.children[0]}, nil
|
|
case ASTFunctionExpression:
|
|
resolvedArgs := []interface{}{}
|
|
for _, arg := range node.children {
|
|
current, err := intr.Execute(arg, value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resolvedArgs = append(resolvedArgs, current)
|
|
}
|
|
return intr.fCall.CallFunction(node.value.(string), resolvedArgs, intr)
|
|
case ASTField:
|
|
if m, ok := value.(map[string]interface{}); ok {
|
|
key := node.value.(string)
|
|
return m[key], nil
|
|
}
|
|
return intr.fieldFromStruct(node.value.(string), value)
|
|
case ASTFilterProjection:
|
|
left, err := intr.Execute(node.children[0], value)
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
sliceType, ok := left.([]interface{})
|
|
if !ok {
|
|
if isSliceType(left) {
|
|
return intr.filterProjectionWithReflection(node, left)
|
|
}
|
|
return nil, nil
|
|
}
|
|
compareNode := node.children[2]
|
|
collected := []interface{}{}
|
|
for _, element := range sliceType {
|
|
result, err := intr.Execute(compareNode, element)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !isFalse(result) {
|
|
current, err := intr.Execute(node.children[1], element)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if current != nil {
|
|
collected = append(collected, current)
|
|
}
|
|
}
|
|
}
|
|
return collected, nil
|
|
case ASTFlatten:
|
|
left, err := intr.Execute(node.children[0], value)
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
sliceType, ok := left.([]interface{})
|
|
if !ok {
|
|
// If we can't type convert to []interface{}, there's
|
|
// a chance this could still work via reflection if we're
|
|
// dealing with user provided types.
|
|
if isSliceType(left) {
|
|
return intr.flattenWithReflection(left)
|
|
}
|
|
return nil, nil
|
|
}
|
|
flattened := []interface{}{}
|
|
for _, element := range sliceType {
|
|
if elementSlice, ok := element.([]interface{}); ok {
|
|
flattened = append(flattened, elementSlice...)
|
|
} else if isSliceType(element) {
|
|
reflectFlat := []interface{}{}
|
|
v := reflect.ValueOf(element)
|
|
for i := 0; i < v.Len(); i++ {
|
|
reflectFlat = append(reflectFlat, v.Index(i).Interface())
|
|
}
|
|
flattened = append(flattened, reflectFlat...)
|
|
} else {
|
|
flattened = append(flattened, element)
|
|
}
|
|
}
|
|
return flattened, nil
|
|
case ASTIdentity, ASTCurrentNode:
|
|
return value, nil
|
|
case ASTIndex:
|
|
if sliceType, ok := value.([]interface{}); ok {
|
|
index := node.value.(int)
|
|
if index < 0 {
|
|
index += len(sliceType)
|
|
}
|
|
if index < len(sliceType) && index >= 0 {
|
|
return sliceType[index], nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
// Otherwise try via reflection.
|
|
rv := reflect.ValueOf(value)
|
|
if rv.Kind() == reflect.Slice {
|
|
index := node.value.(int)
|
|
if index < 0 {
|
|
index += rv.Len()
|
|
}
|
|
if index < rv.Len() && index >= 0 {
|
|
v := rv.Index(index)
|
|
return v.Interface(), nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
case ASTKeyValPair:
|
|
return intr.Execute(node.children[0], value)
|
|
case ASTLiteral:
|
|
return node.value, nil
|
|
case ASTMultiSelectHash:
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
collected := make(map[string]interface{})
|
|
for _, child := range node.children {
|
|
current, err := intr.Execute(child, value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
key := child.value.(string)
|
|
collected[key] = current
|
|
}
|
|
return collected, nil
|
|
case ASTMultiSelectList:
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
collected := []interface{}{}
|
|
for _, child := range node.children {
|
|
current, err := intr.Execute(child, value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
collected = append(collected, current)
|
|
}
|
|
return collected, nil
|
|
case ASTOrExpression:
|
|
matched, err := intr.Execute(node.children[0], value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isFalse(matched) {
|
|
matched, err = intr.Execute(node.children[1], value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return matched, nil
|
|
case ASTAndExpression:
|
|
matched, err := intr.Execute(node.children[0], value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isFalse(matched) {
|
|
return matched, nil
|
|
}
|
|
return intr.Execute(node.children[1], value)
|
|
case ASTNotExpression:
|
|
matched, err := intr.Execute(node.children[0], value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isFalse(matched) {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
case ASTPipe:
|
|
result := value
|
|
var err error
|
|
for _, child := range node.children {
|
|
result, err = intr.Execute(child, result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return result, nil
|
|
case ASTProjection:
|
|
left, err := intr.Execute(node.children[0], value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sliceType, ok := left.([]interface{})
|
|
if !ok {
|
|
if isSliceType(left) {
|
|
return intr.projectWithReflection(node, left)
|
|
}
|
|
return nil, nil
|
|
}
|
|
collected := []interface{}{}
|
|
var current interface{}
|
|
for _, element := range sliceType {
|
|
current, err = intr.Execute(node.children[1], element)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if current != nil {
|
|
collected = append(collected, current)
|
|
}
|
|
}
|
|
return collected, nil
|
|
case ASTSubexpression, ASTIndexExpression:
|
|
left, err := intr.Execute(node.children[0], value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return intr.Execute(node.children[1], left)
|
|
case ASTSlice:
|
|
sliceType, ok := value.([]interface{})
|
|
if !ok {
|
|
if isSliceType(value) {
|
|
return intr.sliceWithReflection(node, value)
|
|
}
|
|
return nil, nil
|
|
}
|
|
parts := node.value.([]*int)
|
|
sliceParams := make([]sliceParam, 3)
|
|
for i, part := range parts {
|
|
if part != nil {
|
|
sliceParams[i].Specified = true
|
|
sliceParams[i].N = *part
|
|
}
|
|
}
|
|
return slice(sliceType, sliceParams)
|
|
case ASTValueProjection:
|
|
left, err := intr.Execute(node.children[0], value)
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
mapType, ok := left.(map[string]interface{})
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
values := make([]interface{}, len(mapType))
|
|
for _, value := range mapType {
|
|
values = append(values, value)
|
|
}
|
|
collected := []interface{}{}
|
|
for _, element := range values {
|
|
current, err := intr.Execute(node.children[1], element)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if current != nil {
|
|
collected = append(collected, current)
|
|
}
|
|
}
|
|
return collected, nil
|
|
}
|
|
return nil, errors.New("Unknown AST node: " + node.nodeType.String())
|
|
}
|
|
|
|
func (intr *treeInterpreter) fieldFromStruct(key string, value interface{}) (interface{}, error) {
|
|
rv := reflect.ValueOf(value)
|
|
first, n := utf8.DecodeRuneInString(key)
|
|
fieldName := string(unicode.ToUpper(first)) + key[n:]
|
|
if rv.Kind() == reflect.Struct {
|
|
v := rv.FieldByName(fieldName)
|
|
if !v.IsValid() {
|
|
return nil, nil
|
|
}
|
|
return v.Interface(), nil
|
|
} else if rv.Kind() == reflect.Ptr {
|
|
// Handle multiple levels of indirection?
|
|
if rv.IsNil() {
|
|
return nil, nil
|
|
}
|
|
rv = rv.Elem()
|
|
v := rv.FieldByName(fieldName)
|
|
if !v.IsValid() {
|
|
return nil, nil
|
|
}
|
|
return v.Interface(), nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (intr *treeInterpreter) flattenWithReflection(value interface{}) (interface{}, error) {
|
|
v := reflect.ValueOf(value)
|
|
flattened := []interface{}{}
|
|
for i := 0; i < v.Len(); i++ {
|
|
element := v.Index(i).Interface()
|
|
if reflect.TypeOf(element).Kind() == reflect.Slice {
|
|
// Then insert the contents of the element
|
|
// slice into the flattened slice,
|
|
// i.e flattened = append(flattened, mySlice...)
|
|
elementV := reflect.ValueOf(element)
|
|
for j := 0; j < elementV.Len(); j++ {
|
|
flattened = append(
|
|
flattened, elementV.Index(j).Interface())
|
|
}
|
|
} else {
|
|
flattened = append(flattened, element)
|
|
}
|
|
}
|
|
return flattened, nil
|
|
}
|
|
|
|
func (intr *treeInterpreter) sliceWithReflection(node ASTNode, value interface{}) (interface{}, error) {
|
|
v := reflect.ValueOf(value)
|
|
parts := node.value.([]*int)
|
|
sliceParams := make([]sliceParam, 3)
|
|
for i, part := range parts {
|
|
if part != nil {
|
|
sliceParams[i].Specified = true
|
|
sliceParams[i].N = *part
|
|
}
|
|
}
|
|
final := []interface{}{}
|
|
for i := 0; i < v.Len(); i++ {
|
|
element := v.Index(i).Interface()
|
|
final = append(final, element)
|
|
}
|
|
return slice(final, sliceParams)
|
|
}
|
|
|
|
func (intr *treeInterpreter) filterProjectionWithReflection(node ASTNode, value interface{}) (interface{}, error) {
|
|
compareNode := node.children[2]
|
|
collected := []interface{}{}
|
|
v := reflect.ValueOf(value)
|
|
for i := 0; i < v.Len(); i++ {
|
|
element := v.Index(i).Interface()
|
|
result, err := intr.Execute(compareNode, element)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !isFalse(result) {
|
|
current, err := intr.Execute(node.children[1], element)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if current != nil {
|
|
collected = append(collected, current)
|
|
}
|
|
}
|
|
}
|
|
return collected, nil
|
|
}
|
|
|
|
func (intr *treeInterpreter) projectWithReflection(node ASTNode, value interface{}) (interface{}, error) {
|
|
collected := []interface{}{}
|
|
v := reflect.ValueOf(value)
|
|
for i := 0; i < v.Len(); i++ {
|
|
element := v.Index(i).Interface()
|
|
result, err := intr.Execute(node.children[1], element)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if result != nil {
|
|
collected = append(collected, result)
|
|
}
|
|
}
|
|
return collected, nil
|
|
}
|