diff --git a/builtin/builtin.go b/builtin/builtin.go index 87e73614..25aa9791 100644 --- a/builtin/builtin.go +++ b/builtin/builtin.go @@ -337,7 +337,11 @@ var Builtins = []*Function{ case []any: var s []string for _, arg := range args[0].([]any) { - s = append(s, arg.(string)) + str, ok := arg.(string) + if !ok { + return nil, fmt.Errorf("invalid argument for join (type %s)", reflect.TypeOf(arg)) + } + s = append(s, str) } return strings.Join(s, glue), nil } @@ -996,6 +1000,15 @@ var Builtins = []*Function{ for i, v := range in { array[i] = v } + default: + v := reflect.ValueOf(args[0]) + if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { + return nil, 0, fmt.Errorf("cannot sort %s", v.Kind()) + } + array = make([]any, v.Len()) + for i := 0; i < v.Len(); i++ { + array[i] = v.Index(i).Interface() + } } var desc bool diff --git a/builtin/builtin_test.go b/builtin/builtin_test.go index 0d0dec35..f3e138a8 100644 --- a/builtin/builtin_test.go +++ b/builtin/builtin_test.go @@ -281,6 +281,7 @@ func TestBuiltin_errors(t *testing.T) { {`now(nil)`, "invalid number of arguments (expected 0, got 1)"}, {`date(nil)`, "interface {} is nil, not string (1:1)"}, {`timezone(nil)`, "cannot use nil as argument (type string) to call timezone (1:10)"}, + {`join([1, 2])`, "invalid argument for join (type int)"}, {`flatten([1, 2], [3, 4])`, "invalid number of arguments (expected 1, got 2)"}, {`flatten(1)`, "cannot flatten int"}, {`fromJSON("5e2482")`, "cannot unmarshal number"}, @@ -653,6 +654,30 @@ func TestBuiltin_sort_i64(t *testing.T) { assert.Equal(t, []any{int64(1), int64(1), int64(1)}, out) } +func TestBuiltin_sort_non_standard_slice(t *testing.T) { + tests := []struct { + input string + env any + want any + }{ + {`sort(x)`, map[string]any{"x": []int64{3, 1, 2}}, []any{int64(1), int64(2), int64(3)}}, + {`sort(x)`, map[string]any{"x": []uint{3, 1, 2}}, []any{uint(1), uint(2), uint(3)}}, + {`sort(x, "desc")`, map[string]any{"x": []int32{1, 3, 2}}, []any{int32(3), int32(2), int32(1)}}, + } + + for _, test := range tests { + t.Run(test.input, func(t *testing.T) { + out, err := expr.Eval(test.input, test.env) + require.NoError(t, err) + assert.Equal(t, test.want, out) + }) + } + + _, err := expr.Eval(`sort(x)`, map[string]any{"x": 42}) + require.Error(t, err) + assert.Contains(t, err.Error(), "cannot sort int") +} + func TestBuiltin_bitOpsFunc(t *testing.T) { tests := []struct { input string