Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions annotated.go
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,29 @@ func As(interfaces ...any) Annotation {
return &asAnnotation{targets: interfaces}
}

// AsType is [As](new(T)). Same as errors.AsType versus errors.As.
//
// For example,
//
// fx.Provide(
// fx.Annotate(
// bytes.NewBuffer,
// fx.AsType[io.Writer](),
// ),
// )
//
// Is equivalent to,
//
// fx.Provide(
// fx.Annotate(
// bytes.NewBuffer,
// fx.As(new(io.Writer)),
// ),
// )
func AsType[T any]() Annotation {
return As(new(T))
}

// Self returns a special value that can be passed to [As] to indicate
// that a type should be provided as its original type, in addition to whatever other
// types it gets provided as via other [As] annotations.
Expand Down
101 changes: 101 additions & 0 deletions annotated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,15 @@ func TestAnnotatedAs(t *testing.T) {
assert.Equal(t, s.String(), "a good stringer")
},
},
{
desc: "provide a good stringer via AsType",
provide: fx.Provide(
fx.Annotate(newAsStringer, fx.AsType[fmt.Stringer]()),
),
invoke: func(s fmt.Stringer) {
assert.Equal(t, s.String(), "a good stringer")
},
},
{
desc: "value type implementing interface",
provide: fx.Provide(
Expand Down Expand Up @@ -889,6 +898,22 @@ func TestAnnotatedAsFailures(t *testing.T) {
),
errorContains: "argument must be a pointer to an interface: got string",
},
{
desc: "provide when an illegal type AsType",
provide: fx.Provide(fx.Annotate(newAsStringer, fx.AsType[io.Writer]())),
invoke: func() {},
errorContains: "asStringer does not implement io.Writer",
},
{
desc: "non-interface argument to AsType",
provide: fx.Provide(
fx.Annotate(
newAsStringer,
fx.AsType[string](),
),
),
errorContains: "argument must be a pointer to an interface: got *string",
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -2534,3 +2559,79 @@ func TestHookAnnotationFunctionFlexibility(t *testing.T) {
})
}
}

func TestAnnotatedAsType(t *testing.T) {
t.Parallel()

t.Run("provides interface implementation", func(t *testing.T) {
t.Parallel()

var invoked bool
app := fxtest.New(t,
fx.Provide(
fx.Annotate(
func() *bytes.Buffer {
return bytes.NewBufferString("hello fx")
},
fx.AsType[io.Reader](),
),
),
fx.Invoke(func(r io.Reader) {
invoked = true
data, err := io.ReadAll(r)
require.NoError(t, err)
assert.Equal(t, "hello fx", string(data))
}),
)
app.RequireStart().RequireStop()
assert.True(t, invoked)
})

t.Run("provides multiple interfaces on single constructor", func(t *testing.T) {
t.Parallel()

var invoked bool
app := fxtest.New(t,
fx.Provide(
fx.Annotate(
func() *bytes.Buffer {
return bytes.NewBufferString("test data")
},
fx.AsType[io.Reader](),
fx.AsType[io.Writer](),
),
),
fx.Invoke(func(r io.Reader, w io.Writer) {
invoked = true
assert.NotNil(t, r)
assert.NotNil(t, w)
}),
)
app.RequireStart().RequireStop()
assert.True(t, invoked)
})

t.Run("works alongside fx.Self", func(t *testing.T) {
t.Parallel()

var invoked bool
app := fxtest.New(t,
fx.Provide(
fx.Annotate(
func() *bytes.Buffer {
return bytes.NewBufferString("self data")
},
fx.As(fx.Self()),
fx.AsType[io.Reader](),
),
),
fx.Invoke(func(buf *bytes.Buffer, r io.Reader) {
invoked = true
assert.NotNil(t, buf)
assert.NotNil(t, r)
}),
)
app.RequireStart().RequireStop()
assert.True(t, invoked)
})
}