Skip to content

Commit aa53692

Browse files
committed
refactor: stdlib cleanup and simplification
1 parent 7a9bc28 commit aa53692

File tree

3 files changed

+92
-27
lines changed

3 files changed

+92
-27
lines changed

pkg/stdlib/errors.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package stdlib
2+
3+
import "fmt"
4+
5+
type ArgumentError struct {
6+
name string
7+
wrapped error
8+
}
9+
10+
func NewArgumentError(name string, wrapped error) ArgumentError {
11+
return ArgumentError{name: name, wrapped: wrapped}
12+
}
13+
14+
func (e ArgumentError) Error() string {
15+
return fmt.Sprintf("%s(): argument error: %s", e.name, e.wrapped)
16+
}
17+
18+
func (e ArgumentError) Unwrap() error {
19+
return e.wrapped
20+
}
21+
22+
type InvalidNumberOfArgumentsError struct {
23+
Expected int
24+
Actual int
25+
Message string
26+
}
27+
28+
func NewInvalidNumberOfArgumentsError(name, message string, expected, actual int) error {
29+
return NewArgumentError(name, InvalidNumberOfArgumentsError{Message: message, Actual: actual, Expected: expected})
30+
}
31+
32+
func (e InvalidNumberOfArgumentsError) Error() string {
33+
if e.Message == "" {
34+
return fmt.Sprintf("accepts exactly %d argument, %d provided", e.Expected, e.Actual)
35+
}
36+
37+
return fmt.Sprintf(e.Message, e.Expected, e.Actual)
38+
}
39+
40+
type InvalidArgumentTypeError struct {
41+
Message string
42+
}
43+
44+
func (e InvalidArgumentTypeError) Error() string {
45+
return e.Message
46+
}
47+
48+
func NewInvalidArgumentTypeError(name, message string) error {
49+
return NewArgumentError(name, InvalidArgumentTypeError{Message: message})
50+
}

pkg/stdlib/functions.go

+39-26
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ package stdlib
22

33
import (
44
"cmp"
5-
"errors"
65
"fmt"
76
"path/filepath"
8-
"reflect"
97
"slices"
108
"strings"
9+
"time"
1110

1211
"github.com/expr-lang/expr"
1312
"github.com/xhit/go-str2duration/v2"
@@ -17,12 +16,12 @@ var FilepathDir = expr.Function(
1716
"filepath_dir",
1817
func(params ...any) (any, error) {
1918
if len(params) != 1 {
20-
return nil, fmt.Errorf("filepath_dir: accepts exactly 1 argument, %d provided", len(params))
19+
return nil, NewInvalidNumberOfArgumentsError("filepath_dir", "", 1, len(params))
2120
}
2221

2322
val, ok := params[0].(string)
2423
if !ok {
25-
return nil, errors.New("input to filepath_dir must be of type 'string'")
24+
return nil, NewInvalidArgumentTypeError("filepath_dir", "input must be string")
2625
}
2726

2827
return filepath.Dir(val), nil
@@ -36,61 +35,74 @@ func UniqSlice[T cmp.Ordered](in []T) []T {
3635
return slices.Compact(in)
3736
}
3837

38+
// Uniq takes a list of strings or interface{}, sorts them
39+
// and remove duplicated values
3940
var Uniq = expr.Function(
4041
"uniq",
41-
func(params ...any) (any, error) {
42-
arg := params[0]
43-
val := reflect.ValueOf(arg)
44-
45-
switch val.Kind() { //nolint:exhaustive
46-
case reflect.Slice:
47-
switch val.Type().Elem().Kind() { //nolint:exhaustive
48-
case reflect.Interface:
49-
var x []string
50-
for _, v := range arg.([]any) { //nolint:forcetypeassert
51-
x = append(x, fmt.Sprintf("%s", v))
52-
}
42+
func(args ...any) (any, error) {
43+
if len(args) != 1 {
44+
return nil, NewInvalidNumberOfArgumentsError("uniq", "", 1, len(args))
45+
}
5346

54-
return UniqSlice(x), nil
47+
arg := args[0]
5548

56-
case reflect.String:
57-
return UniqSlice(arg.([]string)), nil //nolint:forcetypeassert
49+
switch val := arg.(type) {
50+
case []any:
51+
var result []string
5852

53+
for _, v := range val {
54+
result = append(result, fmt.Sprintf("%s", v))
5955
}
60-
}
6156

62-
return nil, errors.New("invalid type")
57+
return UniqSlice(result), nil
58+
59+
case []string:
60+
return UniqSlice(val), nil
61+
62+
default:
63+
return nil, NewInvalidArgumentTypeError("uniq", fmt.Sprintf("invalid input, must be an array of [string] or [interface], got %T", arg))
64+
}
6365
},
66+
new(func([]string) []string),
67+
new(func([]any) []string),
6468
)
6569

70+
// Override built-in duration() function to provide support for additional periods
71+
// - 'd' (day)
72+
// - 'w' (week)
73+
// - 'm' (month)
6674
var Duration = expr.Function(
6775
"duration",
6876
func(args ...any) (any, error) {
77+
if len(args) != 1 {
78+
return nil, NewInvalidNumberOfArgumentsError("duration", "", 1, len(args))
79+
}
80+
6981
val, ok := args[0].(string)
7082
if !ok {
71-
return nil, errors.New("input to duration() must be of type 'string'")
83+
return nil, NewInvalidArgumentTypeError("duration", fmt.Sprintf("invalid input, must be a string, got %T", args[0]))
7284
}
7385

7486
return str2duration.ParseDuration(val)
7587
},
76-
str2duration.ParseDuration,
88+
time.ParseDuration,
7789
)
7890

7991
var LimitPathDepthTo = expr.Function(
8092
"limit_path_depth_to",
8193
func(args ...any) (any, error) {
8294
if len(args) != 2 {
83-
return nil, errors.New("limit_path_depth_to() expect exactly two arguments")
95+
return nil, NewInvalidNumberOfArgumentsError("limit_path_depth_to", "", 2, len(args))
8496
}
8597

8698
input, ok := args[0].(string)
8799
if !ok {
88-
return nil, errors.New("first input to limit_path_depth_to() must be of type 'string'")
100+
return nil, NewInvalidArgumentTypeError("limit_path_depth_to", fmt.Sprintf("invalid input, first argument must be a 'string', got %T", args[0]))
89101
}
90102

91103
length, ok := args[1].(int)
92104
if !ok {
93-
return nil, errors.New("second input to limit_path_depth_to() must be of type 'int'")
105+
return nil, NewInvalidArgumentTypeError("limit_path_depth_to", fmt.Sprintf("invalid input, first argument must be an 'int', got %T", args[0]))
94106
}
95107

96108
chunks := strings.Split(input, "/")
@@ -100,4 +112,5 @@ var LimitPathDepthTo = expr.Function(
100112

101113
return strings.Join(chunks[0:length-1], "/"), nil // nosemgrep
102114
},
115+
new(func(string, int) string),
103116
)

pkg/stdlib/stdlib.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package stdlib
22

3-
import "github.com/expr-lang/expr"
3+
import (
4+
"github.com/expr-lang/expr"
5+
)
46

57
var Functions = []expr.Option{
68
// Replace built-in duration function with one that supports "d" (days) and "w" (weeks)

0 commit comments

Comments
 (0)