diff --git a/annotated.go b/annotated.go index a3f1aa5..06a15ff 100644 --- a/annotated.go +++ b/annotated.go @@ -360,7 +360,7 @@ func (ann *annotated) parameters() ( // No parameter annotations. Return the original types // and an identity function. - if len(ann.ParamTags) == 0 { + if len(ann.ParamTags) == 0 && !ft.IsVariadic() { return types, func(args []reflect.Value) []reflect.Value { return args } @@ -383,6 +383,11 @@ func (ann *annotated) parameters() ( if i < len(ann.ParamTags) { field.Tag = reflect.StructTag(ann.ParamTags[i]) + } else if i == ft.NumIn()-1 && ft.IsVariadic() { + // If a variadic argument is unannotated, mark it optional, + // so that just wrapping a function in fx.Annotate does not + // suddenly introduce a required []arg dependency. + field.Tag = reflect.StructTag(`optional:"true"`) } inFields = append(inFields, field) diff --git a/annotated_test.go b/annotated_test.go index 583c890..19bed2c 100644 --- a/annotated_test.go +++ b/annotated_test.go @@ -522,6 +522,10 @@ func TestAnnotate(t *testing.T) { newSliceA := func(sa ...*a) *sliceA { return &sliceA{sa} } + newSliceAWithB := func(b *b, sa ...*a) *sliceA { + total := append(sa, b.a) + return &sliceA{total} + } t.Run("Provide with optional", func(t *testing.T) { t.Parallel() @@ -599,6 +603,72 @@ func TestAnnotate(t *testing.T) { assert.Len(t, got.sa, 2) }) + t.Run("Provide variadic function with no optional params", func(t *testing.T) { + t.Parallel() + + var got struct { + fx.In + + Result *sliceA `name:"as"` + } + app := fxtest.New(t, + fx.Supply([]*a{{}, {}, {}}), + fx.Provide( + fx.Annotate(newSliceA, + fx.ResultTags(`name:"as"`), + ), + ), + fx.Populate(&got), + ) + defer app.RequireStart().RequireStop() + require.NoError(t, app.Err()) + assert.Len(t, got.Result.sa, 3) + }) + + t.Run("Provide variadic function named with no given params", func(t *testing.T) { + t.Parallel() + + var got *sliceA + app := NewForTest(t, + fx.Provide( + fx.Annotate(newSliceA, fx.ParamTags(`name:"a"`)), + ), + fx.Populate(&got), + ) + err := app.Err() + require.Error(t, err) + assert.Contains(t, err.Error(), `missing dependencies`) + assert.Contains(t, err.Error(), `missing type: []*fx_test.a[name="a"]`) + }) + + t.Run("Invoke variadic function with multiple params", func(t *testing.T) { + t.Parallel() + + app := fxtest.New(t, + fx.Supply( + fx.Annotate(newB(newA()), fx.ResultTags(`name:"b"`)), + ), + fx.Invoke(fx.Annotate(newSliceAWithB, fx.ParamTags(`name:"b"`))), + ) + + defer app.RequireStart().RequireStop() + require.NoError(t, app.Err()) + }) + + t.Run("Invoke non-optional variadic function with a missing dependency", func(t *testing.T) { + t.Parallel() + + app := NewForTest(t, + fx.Invoke( + fx.Annotate(newSliceA, fx.ParamTags(`optional:"false"`)), + ), + ) + err := app.Err() + require.Error(t, err) + assert.Contains(t, err.Error(), `missing dependencies`) + assert.Contains(t, err.Error(), `missing type: []*fx_test.a`) + }) + t.Run("Invoke with variadic function", func(t *testing.T) { t.Parallel()