Skip to content

Commit

Permalink
Allow fx.Annotate on In/Out structs with ResultTags/ParamTags, respec…
Browse files Browse the repository at this point in the history
…tively (#1053)

It is currently not possible for fx.Annotate to be used on a function
that takes in or returns fx.In and fx.Out structs.

This limitation, however, is not necessary when the annotation is
different from the struct defined annotations, such as using fx.In with
fx.ResultTags Annotation or fx.Out with fx.ParamTags Annotation.

This loosens the constraint so that these combinations of Annotations
become valid.

Fixes #980.
  • Loading branch information
sywhang authored Mar 10, 2023
1 parent 7e9108b commit 60031c6
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 9 deletions.
16 changes: 9 additions & 7 deletions annotated.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,9 @@ func (pt paramTagsAnnotation) parameters(ann *annotated) (
// ParamTags is an Annotation that annotates the parameter(s) of a function.
// When multiple tags are specified, each tag is mapped to the corresponding
// positional parameter.
//
// ParamTags cannot be used in a function that takes an fx.In struct as a
// parameter.
func ParamTags(tags ...string) Annotation {
return paramTagsAnnotation{tags}
}
Expand Down Expand Up @@ -520,6 +523,8 @@ func (rt resultTagsAnnotation) results(ann *annotated) (
// ResultTags is an Annotation that annotates the result(s) of a function.
// When multiple tags are specified, each tag is mapped to the corresponding
// positional result.
//
// ResultTags cannot be used on a function that returns an fx.Out struct.
func ResultTags(tags ...string) Annotation {
return resultTagsAnnotation{tags}
}
Expand Down Expand Up @@ -1603,8 +1608,8 @@ func (ann *annotated) typeCheckOrigFn() error {
if ot.Kind() != reflect.Struct {
continue
}
if dig.IsOut(reflect.New(ft.Out(i)).Elem().Interface()) {
return errors.New("fx.Out structs cannot be annotated")
if len(ann.ResultTags) > 0 && dig.IsOut(reflect.New(ft.Out(i)).Elem().Interface()) {
return errors.New("fx.Out structs cannot be annotated with fx.ResultTags")
}
}

Expand All @@ -1613,8 +1618,8 @@ func (ann *annotated) typeCheckOrigFn() error {
if it.Kind() != reflect.Struct {
continue
}
if dig.IsIn(reflect.New(ft.In(i)).Elem().Interface()) {
return errors.New("fx.In structs cannot be annotated")
if len(ann.ParamTags) > 0 && dig.IsIn(reflect.New(ft.In(i)).Elem().Interface()) {
return errors.New("fx.In structs cannot be annotated with fx.ParamTags")
}
}
return nil
Expand Down Expand Up @@ -1677,9 +1682,6 @@ func (ann *annotated) currentParamTypes() []reflect.Type {
// return result{GW: NewGateway(p.RO, p.RW)}
// })
//
// Annotate cannot be used on functions that takes in or returns
// [In] or [Out] structs.
//
// Using the same annotation multiple times is invalid.
// For example, the following will fail with an error:
//
Expand Down
49 changes: 49 additions & 0 deletions annotated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"errors"
"fmt"
"io"
"strconv"
"strings"
"testing"

Expand Down Expand Up @@ -1472,6 +1473,54 @@ func TestAnnotate(t *testing.T) {
assert.Contains(t, err.Error(), "invalid annotation function func(fx_test.B) string")
assert.Contains(t, err.Error(), "fx.In structs cannot be annotated")
})

t.Run("annotate fx.In with fx.ResultTags", func(t *testing.T) {
t.Parallel()

type A struct {
fx.In

I int
}

app := NewForTest(t,
fx.Provide(
fx.Annotate(func(a A) string { return "ok" + strconv.Itoa(a.I) }, fx.ResultTags(`name:"val"`)),
func() int {
return 1
},
),
fx.Invoke(
fx.Annotate(func(s string) {
assert.Equal(t, "ok1", s)
}, fx.ParamTags(`name:"val"`)),
),
)
err := app.Err()
require.NoError(t, err)
})

t.Run("annotate fx.Out with fx.ParamTags", func(t *testing.T) {
t.Parallel()

type A struct {
fx.Out

S string
}

app := NewForTest(t,
fx.Provide(
fx.Annotate(func() int { return 1 }, fx.ResultTags(`name:"val"`)),
fx.Annotate(func(i int) A { return A{S: strconv.Itoa(i)} }, fx.ParamTags(`name:"val"`)),
),
fx.Invoke(func(s string) {
assert.Equal(t, "1", s)
}),
)
err := app.Err()
require.NoError(t, err)
})
}

func TestAnnotateApplyFail(t *testing.T) {
Expand Down
6 changes: 4 additions & 2 deletions docs/annotate.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ without manually wrapping the function to use

A function that:

- does not accept a [parameter object](parameter-objects.md)
- does not return a [result object](result-objects.md)
- does not accept a [parameter object](parameter-objects.md), when
annotating with `fx.ParamTags`.
- does not return a [result object](result-objects.md) when annotating
with `fx.ResultTags`.

**Steps**

Expand Down

0 comments on commit 60031c6

Please sign in to comment.