diff --git a/ucl/userbuiltin.go b/ucl/userbuiltin.go index 46c493e..625bea8 100644 --- a/ucl/userbuiltin.go +++ b/ucl/userbuiltin.go @@ -216,7 +216,15 @@ type Invokable struct { ec *evalCtx } +func (i Invokable) IsNil() bool { + return i.inv == nil +} + func (i Invokable) Invoke(ctx context.Context, args ...any) (any, error) { + if i.inv == nil { + return nil, nil + } + var err error invArgs := invocationArgs{ eval: i.eval, diff --git a/ucl/userbuiltin_test.go b/ucl/userbuiltin_test.go index 3e6ada6..a4bd231 100644 --- a/ucl/userbuiltin_test.go +++ b/ucl/userbuiltin_test.go @@ -290,6 +290,37 @@ func TestCallArgs_CanBind(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "[[HELLO]]", res) }) + + t.Run("can carry invokable outside of context", func(t *testing.T) { + inst := ucl.New() + var inv ucl.Invokable + + inst.SetBuiltin("wrap", func(ctx context.Context, args ucl.CallArgs) (any, error) { + if err := args.Bind(&inv); err != nil { + return nil, err + } + + return nil, nil + }) + + ctx := context.Background() + + assert.True(t, inv.IsNil()) + + before, err := inv.Invoke(ctx, "hello") + assert.NoError(t, err) + assert.Nil(t, before) + + res, err := inst.Eval(ctx, `wrap { |x| toUpper $x }`) + assert.NoError(t, err) + assert.Nil(t, res) + + assert.False(t, inv.IsNil()) + + after, err := inv.Invoke(ctx, "hello") + assert.NoError(t, err) + assert.Equal(t, "HELLO", after) + }) } func TestCallArgs_MissingCommandHandler(t *testing.T) {