blob: 9b7cd89b4bcca44937d4cc762a3b308d4e220b18 [file] [log] [blame]
package jmespath
import (
"encoding/json"
"errors"
"fmt"
"math"
"reflect"
"sort"
"strconv"
"strings"
"unicode/utf8"
)
type jpFunction func(arguments []interface{}) (interface{}, error)
type jpType string
const (
jpUnknown jpType = "unknown"
jpNumber jpType = "number"
jpString jpType = "string"
jpArray jpType = "array"
jpObject jpType = "object"
jpArrayNumber jpType = "array[number]"
jpArrayString jpType = "array[string]"
jpExpref jpType = "expref"
jpAny jpType = "any"
)
type functionEntry struct {
name string
arguments []argSpec
handler jpFunction
hasExpRef bool
}
type argSpec struct {
types []jpType
variadic bool
}
type byExprString struct {
intr *treeInterpreter
node ASTNode
items []interface{}
hasError bool
}
func (a *byExprString) Len() int {
return len(a.items)
}
func (a *byExprString) Swap(i, j int) {
a.items[i], a.items[j] = a.items[j], a.items[i]
}
func (a *byExprString) Less(i, j int) bool {
first, err := a.intr.Execute(a.node, a.items[i])
if err != nil {
a.hasError = true
// Return a dummy value.
return true
}
ith, ok := first.(string)
if !ok {
a.hasError = true
return true
}
second, err := a.intr.Execute(a.node, a.items[j])
if err != nil {
a.hasError = true
// Return a dummy value.
return true
}
jth, ok := second.(string)
if !ok {
a.hasError = true
return true
}
return ith < jth
}
type byExprFloat struct {
intr *treeInterpreter
node ASTNode
items []interface{}
hasError bool
}
func (a *byExprFloat) Len() int {
return len(a.items)
}
func (a *byExprFloat) Swap(i, j int) {
a.items[i], a.items[j] = a.items[j], a.items[i]
}
func (a *byExprFloat) Less(i, j int) bool {
first, err := a.intr.Execute(a.node, a.items[i])
if err != nil {
a.hasError = true
// Return a dummy value.
return true
}
ith, ok := first.(float64)
if !ok {
a.hasError = true
return true
}
second, err := a.intr.Execute(a.node, a.items[j])
if err != nil {
a.hasError = true
// Return a dummy value.
return true
}
jth, ok := second.(float64)
if !ok {
a.hasError = true
return true
}
return ith < jth
}
type functionCaller struct {
functionTable map[string]functionEntry
}
func newFunctionCaller() *functionCaller {
caller := &functionCaller{}
caller.functionTable = map[string]functionEntry{
"length": {
name: "length",
arguments: []argSpec{
{types: []jpType{jpString, jpArray, jpObject}},
},
handler: jpfLength,
},
"starts_with": {
name: "starts_with",
arguments: []argSpec{
{types: []jpType{jpString}},
{types: []jpType{jpString}},
},
handler: jpfStartsWith,
},
"abs": {
name: "abs",
arguments: []argSpec{
{types: []jpType{jpNumber}},
},
handler: jpfAbs,
},
"avg": {
name: "avg",
arguments: []argSpec{
{types: []jpType{jpArrayNumber}},
},
handler: jpfAvg,
},
"ceil": {
name: "ceil",
arguments: []argSpec{
{types: []jpType{jpNumber}},
},
handler: jpfCeil,
},
"contains": {
name: "contains",
arguments: []argSpec{
{types: []jpType{jpArray, jpString}},
{types: []jpType{jpAny}},
},
handler: jpfContains,
},
"ends_with": {
name: "ends_with",
arguments: []argSpec{
{types: []jpType{jpString}},
{types: []jpType{jpString}},
},
handler: jpfEndsWith,
},
"floor": {
name: "floor",
arguments: []argSpec{
{types: []jpType{jpNumber}},
},
handler: jpfFloor,
},
"map": {
name: "amp",
arguments: []argSpec{
{types: []jpType{jpExpref}},
{types: []jpType{jpArray}},
},
handler: jpfMap,
hasExpRef: true,
},
"max": {
name: "max",
arguments: []argSpec{
{types: []jpType{jpArrayNumber, jpArrayString}},
},
handler: jpfMax,
},
"merge": {
name: "merge",
arguments: []argSpec{
{types: []jpType{jpObject}, variadic: true},
},
handler: jpfMerge,
},
"max_by": {
name: "max_by",
arguments: []argSpec{
{types: []jpType{jpArray}},
{types: []jpType{jpExpref}},
},
handler: jpfMaxBy,
hasExpRef: true,
},
"sum": {
name: "sum",
arguments: []argSpec{
{types: []jpType{jpArrayNumber}},
},
handler: jpfSum,
},
"min": {
name: "min",
arguments: []argSpec{
{types: []jpType{jpArrayNumber, jpArrayString}},
},
handler: jpfMin,
},
"min_by": {
name: "min_by",
arguments: []argSpec{
{types: []jpType{jpArray}},
{types: []jpType{jpExpref}},
},
handler: jpfMinBy,
hasExpRef: true,
},
"type": {
name: "type",
arguments: []argSpec{
{types: []jpType{jpAny}},
},
handler: jpfType,
},
"keys": {
name: "keys",
arguments: []argSpec{
{types: []jpType{jpObject}},
},
handler: jpfKeys,
},
"values": {
name: "values",
arguments: []argSpec{
{types: []jpType{jpObject}},
},
handler: jpfValues,
},
"sort": {
name: "sort",
arguments: []argSpec{
{types: []jpType{jpArrayString, jpArrayNumber}},
},
handler: jpfSort,
},
"sort_by": {
name: "sort_by",
arguments: []argSpec{
{types: []jpType{jpArray}},
{types: []jpType{jpExpref}},
},
handler: jpfSortBy,
hasExpRef: true,
},
"join": {
name: "join",
arguments: []argSpec{
{types: []jpType{jpString}},
{types: []jpType{jpArrayString}},
},
handler: jpfJoin,
},
"reverse": {
name: "reverse",
arguments: []argSpec{
{types: []jpType{jpArray, jpString}},
},
handler: jpfReverse,
},
"to_array": {
name: "to_array",
arguments: []argSpec{
{types: []jpType{jpAny}},
},
handler: jpfToArray,
},
"to_string": {
name: "to_string",
arguments: []argSpec{
{types: []jpType{jpAny}},
},
handler: jpfToString,
},
"to_number": {
name: "to_number",
arguments: []argSpec{
{types: []jpType{jpAny}},
},
handler: jpfToNumber,
},
"not_null": {
name: "not_null",
arguments: []argSpec{
{types: []jpType{jpAny}, variadic: true},
},
handler: jpfNotNull,
},
}
return caller
}
func (e *functionEntry) resolveArgs(arguments []interface{}) ([]interface{}, error) {
if len(e.arguments) == 0 {
return arguments, nil
}
if !e.arguments[len(e.arguments)-1].variadic {
if len(e.arguments) != len(arguments) {
return nil, errors.New("incorrect number of args")
}
for i, spec := range e.arguments {
userArg := arguments[i]
err := spec.typeCheck(userArg)
if err != nil {
return nil, err
}
}
return arguments, nil
}
if len(arguments) < len(e.arguments) {
return nil, errors.New("Invalid arity.")
}
return arguments, nil
}
func (a *argSpec) typeCheck(arg interface{}) error {
for _, t := range a.types {
switch t {
case jpNumber:
if _, ok := arg.(float64); ok {
return nil
}
case jpString:
if _, ok := arg.(string); ok {
return nil
}
case jpArray:
if isSliceType(arg) {
return nil
}
case jpObject:
if _, ok := arg.(map[string]interface{}); ok {
return nil
}
case jpArrayNumber:
if _, ok := toArrayNum(arg); ok {
return nil
}
case jpArrayString:
if _, ok := toArrayStr(arg); ok {
return nil
}
case jpAny:
return nil
case jpExpref:
if _, ok := arg.(expRef); ok {
return nil
}
}
}
return fmt.Errorf("Invalid type for: %v, expected: %#v", arg, a.types)
}
func (f *functionCaller) CallFunction(name string, arguments []interface{}, intr *treeInterpreter) (interface{}, error) {
entry, ok := f.functionTable[name]
if !ok {
return nil, errors.New("unknown function: " + name)
}
resolvedArgs, err := entry.resolveArgs(arguments)
if err != nil {
return nil, err
}
if entry.hasExpRef {
var extra []interface{}
extra = append(extra, intr)
resolvedArgs = append(extra, resolvedArgs...)
}
return entry.handler(resolvedArgs)
}
func jpfAbs(arguments []interface{}) (interface{}, error) {
num := arguments[0].(float64)
return math.Abs(num), nil
}
func jpfLength(arguments []interface{}) (interface{}, error) {
arg := arguments[0]
if c, ok := arg.(string); ok {
return float64(utf8.RuneCountInString(c)), nil
} else if isSliceType(arg) {
v := reflect.ValueOf(arg)
return float64(v.Len()), nil
} else if c, ok := arg.(map[string]interface{}); ok {
return float64(len(c)), nil
}
return nil, errors.New("could not compute length()")
}
func jpfStartsWith(arguments []interface{}) (interface{}, error) {
search := arguments[0].(string)
prefix := arguments[1].(string)
return strings.HasPrefix(search, prefix), nil
}
func jpfAvg(arguments []interface{}) (interface{}, error) {
// We've already type checked the value so we can safely use
// type assertions.
args := arguments[0].([]interface{})
length := float64(len(args))
numerator := 0.0
for _, n := range args {
numerator += n.(float64)
}
return numerator / length, nil
}
func jpfCeil(arguments []interface{}) (interface{}, error) {
val := arguments[0].(float64)
return math.Ceil(val), nil
}
func jpfContains(arguments []interface{}) (interface{}, error) {
search := arguments[0]
el := arguments[1]
if searchStr, ok := search.(string); ok {
if elStr, ok := el.(string); ok {
return strings.Index(searchStr, elStr) != -1, nil
}
return false, nil
}
// Otherwise this is a generic contains for []interface{}
general := search.([]interface{})
for _, item := range general {
if item == el {
return true, nil
}
}
return false, nil
}
func jpfEndsWith(arguments []interface{}) (interface{}, error) {
search := arguments[0].(string)
suffix := arguments[1].(string)
return strings.HasSuffix(search, suffix), nil
}
func jpfFloor(arguments []interface{}) (interface{}, error) {
val := arguments[0].(float64)
return math.Floor(val), nil
}
func jpfMap(arguments []interface{}) (interface{}, error) {
intr := arguments[0].(*treeInterpreter)
exp := arguments[1].(expRef)
node := exp.ref
arr := arguments[2].([]interface{})
mapped := make([]interface{}, 0, len(arr))
for _, value := range arr {
current, err := intr.Execute(node, value)
if err != nil {
return nil, err
}
mapped = append(mapped, current)
}
return mapped, nil
}
func jpfMax(arguments []interface{}) (interface{}, error) {
if items, ok := toArrayNum(arguments[0]); ok {
if len(items) == 0 {
return nil, nil
}
if len(items) == 1 {
return items[0], nil
}
best := items[0]
for _, item := range items[1:] {
if item > best {
best = item
}
}
return best, nil
}
// Otherwise we're dealing with a max() of strings.
items, _ := toArrayStr(arguments[0])
if len(items) == 0 {
return nil, nil
}
if len(items) == 1 {
return items[0], nil
}
best := items[0]
for _, item := range items[1:] {
if item > best {
best = item
}
}
return best, nil
}
func jpfMerge(arguments []interface{}) (interface{}, error) {
final := make(map[string]interface{})
for _, m := range arguments {
mapped := m.(map[string]interface{})
for key, value := range mapped {
final[key] = value
}
}
return final, nil
}
func jpfMaxBy(arguments []interface{}) (interface{}, error) {
intr := arguments[0].(*treeInterpreter)
arr := arguments[1].([]interface{})
exp := arguments[2].(expRef)
node := exp.ref
if len(arr) == 0 {
return nil, nil
} else if len(arr) == 1 {
return arr[0], nil
}
start, err := intr.Execute(node, arr[0])
if err != nil {
return nil, err
}
switch t := start.(type) {
case float64:
bestVal := t
bestItem := arr[0]
for _, item := range arr[1:] {
result, err := intr.Execute(node, item)
if err != nil {
return nil, err
}
current, ok := result.(float64)
if !ok {
return nil, errors.New("invalid type, must be number")
}
if current > bestVal {
bestVal = current
bestItem = item
}
}
return bestItem, nil
case string:
bestVal := t
bestItem := arr[0]
for _, item := range arr[1:] {
result, err := intr.Execute(node, item)
if err != nil {
return nil, err
}
current, ok := result.(string)
if !ok {
return nil, errors.New("invalid type, must be string")
}
if current > bestVal {
bestVal = current
bestItem = item
}
}
return bestItem, nil
default:
return nil, errors.New("invalid type, must be number of string")
}
}
func jpfSum(arguments []interface{}) (interface{}, error) {
items, _ := toArrayNum(arguments[0])
sum := 0.0
for _, item := range items {
sum += item
}
return sum, nil
}
func jpfMin(arguments []interface{}) (interface{}, error) {
if items, ok := toArrayNum(arguments[0]); ok {
if len(items) == 0 {
return nil, nil
}
if len(items) == 1 {
return items[0], nil
}
best := items[0]
for _, item := range items[1:] {
if item < best {
best = item
}
}
return best, nil
}
items, _ := toArrayStr(arguments[0])
if len(items) == 0 {
return nil, nil
}
if len(items) == 1 {
return items[0], nil
}
best := items[0]
for _, item := range items[1:] {
if item < best {
best = item
}
}
return best, nil
}
func jpfMinBy(arguments []interface{}) (interface{}, error) {
intr := arguments[0].(*treeInterpreter)
arr := arguments[1].([]interface{})
exp := arguments[2].(expRef)
node := exp.ref
if len(arr) == 0 {
return nil, nil
} else if len(arr) == 1 {
return arr[0], nil
}
start, err := intr.Execute(node, arr[0])
if err != nil {
return nil, err
}
if t, ok := start.(float64); ok {
bestVal := t
bestItem := arr[0]
for _, item := range arr[1:] {
result, err := intr.Execute(node, item)
if err != nil {
return nil, err
}
current, ok := result.(float64)
if !ok {
return nil, errors.New("invalid type, must be number")
}
if current < bestVal {
bestVal = current
bestItem = item
}
}
return bestItem, nil
} else if t, ok := start.(string); ok {
bestVal := t
bestItem := arr[0]
for _, item := range arr[1:] {
result, err := intr.Execute(node, item)
if err != nil {
return nil, err
}
current, ok := result.(string)
if !ok {
return nil, errors.New("invalid type, must be string")
}
if current < bestVal {
bestVal = current
bestItem = item
}
}
return bestItem, nil
} else {
return nil, errors.New("invalid type, must be number of string")
}
}
func jpfType(arguments []interface{}) (interface{}, error) {
arg := arguments[0]
if _, ok := arg.(float64); ok {
return "number", nil
}
if _, ok := arg.(string); ok {
return "string", nil
}
if _, ok := arg.([]interface{}); ok {
return "array", nil
}
if _, ok := arg.(map[string]interface{}); ok {
return "object", nil
}
if arg == nil {
return "null", nil
}
if arg == true || arg == false {
return "boolean", nil
}
return nil, errors.New("unknown type")
}
func jpfKeys(arguments []interface{}) (interface{}, error) {
arg := arguments[0].(map[string]interface{})
collected := make([]interface{}, 0, len(arg))
for key := range arg {
collected = append(collected, key)
}
return collected, nil
}
func jpfValues(arguments []interface{}) (interface{}, error) {
arg := arguments[0].(map[string]interface{})
collected := make([]interface{}, 0, len(arg))
for _, value := range arg {
collected = append(collected, value)
}
return collected, nil
}
func jpfSort(arguments []interface{}) (interface{}, error) {
if items, ok := toArrayNum(arguments[0]); ok {
d := sort.Float64Slice(items)
sort.Stable(d)
final := make([]interface{}, len(d))
for i, val := range d {
final[i] = val
}
return final, nil
}
// Otherwise we're dealing with sort()'ing strings.
items, _ := toArrayStr(arguments[0])
d := sort.StringSlice(items)
sort.Stable(d)
final := make([]interface{}, len(d))
for i, val := range d {
final[i] = val
}
return final, nil
}
func jpfSortBy(arguments []interface{}) (interface{}, error) {
intr := arguments[0].(*treeInterpreter)
arr := arguments[1].([]interface{})
exp := arguments[2].(expRef)
node := exp.ref
if len(arr) == 0 {
return arr, nil
} else if len(arr) == 1 {
return arr, nil
}
start, err := intr.Execute(node, arr[0])
if err != nil {
return nil, err
}
if _, ok := start.(float64); ok {
sortable := &byExprFloat{intr, node, arr, false}
sort.Stable(sortable)
if sortable.hasError {
return nil, errors.New("error in sort_by comparison")
}
return arr, nil
} else if _, ok := start.(string); ok {
sortable := &byExprString{intr, node, arr, false}
sort.Stable(sortable)
if sortable.hasError {
return nil, errors.New("error in sort_by comparison")
}
return arr, nil
} else {
return nil, errors.New("invalid type, must be number of string")
}
}
func jpfJoin(arguments []interface{}) (interface{}, error) {
sep := arguments[0].(string)
// We can't just do arguments[1].([]string), we have to
// manually convert each item to a string.
arrayStr := []string{}
for _, item := range arguments[1].([]interface{}) {
arrayStr = append(arrayStr, item.(string))
}
return strings.Join(arrayStr, sep), nil
}
func jpfReverse(arguments []interface{}) (interface{}, error) {
if s, ok := arguments[0].(string); ok {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r), nil
}
items := arguments[0].([]interface{})
length := len(items)
reversed := make([]interface{}, length)
for i, item := range items {
reversed[length-(i+1)] = item
}
return reversed, nil
}
func jpfToArray(arguments []interface{}) (interface{}, error) {
if _, ok := arguments[0].([]interface{}); ok {
return arguments[0], nil
}
return arguments[:1:1], nil
}
func jpfToString(arguments []interface{}) (interface{}, error) {
if v, ok := arguments[0].(string); ok {
return v, nil
}
result, err := json.Marshal(arguments[0])
if err != nil {
return nil, err
}
return string(result), nil
}
func jpfToNumber(arguments []interface{}) (interface{}, error) {
arg := arguments[0]
if v, ok := arg.(float64); ok {
return v, nil
}
if v, ok := arg.(string); ok {
conv, err := strconv.ParseFloat(v, 64)
if err != nil {
return nil, nil
}
return conv, nil
}
if _, ok := arg.([]interface{}); ok {
return nil, nil
}
if _, ok := arg.(map[string]interface{}); ok {
return nil, nil
}
if arg == nil {
return nil, nil
}
if arg == true || arg == false {
return nil, nil
}
return nil, errors.New("unknown type")
}
func jpfNotNull(arguments []interface{}) (interface{}, error) {
for _, arg := range arguments {
if arg != nil {
return arg, nil
}
}
return nil, nil
}