diff --git a/ucl/userbuiltin.go b/ucl/userbuiltin.go index 625bea8..904baf1 100644 --- a/ucl/userbuiltin.go +++ b/ucl/userbuiltin.go @@ -111,7 +111,11 @@ func (ca CallArgs) bindArg(v interface{}, arg object) error { } return nil case *string: - *t = arg.String() + if arg != nil { + *t = arg.String() + } else { + *t = "" + } case *int: if iArg, ok := arg.(intObject); ok { *t = int(iArg) diff --git a/ucl/userbuiltin_test.go b/ucl/userbuiltin_test.go index a4bd231..557a833 100644 --- a/ucl/userbuiltin_test.go +++ b/ucl/userbuiltin_test.go @@ -81,6 +81,7 @@ func TestInst_SetBuiltin(t *testing.T) { {"plain eval", `add2 -sep ", " -right "world" -left "Hello"`, "Hello, world"}, {"swap 1", `add2 -right "right" -left "left" -sep ":"`, "left:right"}, {"swap 2", `add2 -left "left" -sep ":" -right "right" -upcase`, "LEFT:RIGHT"}, + {"nil 1", `add2 -right "right" -left () -sep ":"`, ":right"}, } for _, tt := range tests { @@ -186,32 +187,66 @@ func TestInst_SetBuiltin(t *testing.T) { } func TestCallArgs_Bind(t *testing.T) { - ctx := context.Background() + t.Run("happy path", func(t *testing.T) { + ctx := context.Background() - inst := ucl.New() - inst.SetBuiltin("sa", func(ctx context.Context, args ucl.CallArgs) (any, error) { - return doStringA{this: "a val"}, nil - }) - inst.SetBuiltin("sb", func(ctx context.Context, args ucl.CallArgs) (any, error) { - return doStringB{left: "foo", right: "bar"}, nil - }) - inst.SetBuiltin("dostr", func(ctx context.Context, args ucl.CallArgs) (any, error) { - var ds doStringable + inst := ucl.New() + inst.SetBuiltin("sa", func(ctx context.Context, args ucl.CallArgs) (any, error) { + return doStringA{this: "a val"}, nil + }) + inst.SetBuiltin("sb", func(ctx context.Context, args ucl.CallArgs) (any, error) { + return doStringB{left: "foo", right: "bar"}, nil + }) + inst.SetBuiltin("dostr", func(ctx context.Context, args ucl.CallArgs) (any, error) { + var ds doStringable - if err := args.Bind(&ds); err != nil { - return nil, err + if err := args.Bind(&ds); err != nil { + return nil, err + } + + return ds.DoString(), nil + }) + + va, err := inst.Eval(ctx, `dostr (sa)`) + assert.NoError(t, err) + assert.Equal(t, "do string A: a val", va) + + vb, err := inst.Eval(ctx, `dostr (sb)`) + assert.NoError(t, err) + assert.Equal(t, "do string B: foo bar", vb) + }) + + t.Run("bind to string", func(t *testing.T) { + tests := []struct { + descr string + eval string + want string + }{ + {descr: "string value", eval: `sa "hello"`, want: "[hello]"}, + {descr: "int value", eval: `sa 13245`, want: "[13245]"}, + {descr: "nil value", eval: `sa ()`, want: "[]"}, } - return ds.DoString(), nil + for _, tt := range tests { + t.Run(tt.descr, func(t *testing.T) { + ctx := context.Background() + + inst := ucl.New() + inst.SetBuiltin("sa", func(ctx context.Context, args ucl.CallArgs) (any, error) { + var v string + if err := args.Bind(&v); err != nil { + return nil, err + } + + return fmt.Sprintf("[%v]", v), nil + }) + + res, err := inst.Eval(ctx, tt.eval) + assert.NoError(t, err) + assert.Equal(t, tt.want, res) + }) + } }) - - va, err := inst.Eval(ctx, `dostr (sa)`) - assert.NoError(t, err) - assert.Equal(t, "do string A: a val", va) - - vb, err := inst.Eval(ctx, `dostr (sb)`) - assert.NoError(t, err) - assert.Equal(t, "do string B: foo bar", vb) } func TestCallArgs_CanBind(t *testing.T) {