status-go/vendor/go.uber.org/fx/annotated.go

1034 lines
26 KiB
Go

// Copyright (c) 2020-2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package fx
import (
"context"
"errors"
"fmt"
"reflect"
"strings"
"go.uber.org/dig"
"go.uber.org/fx/internal/fxreflect"
)
// Annotated annotates a constructor provided to Fx with additional options.
//
// For example,
//
// func NewReadOnlyConnection(...) (*Connection, error)
//
// fx.Provide(fx.Annotated{
// Name: "ro",
// Target: NewReadOnlyConnection,
// })
//
// Is equivalent to,
//
// type result struct {
// fx.Out
//
// Connection *Connection `name:"ro"`
// }
//
// fx.Provide(func(...) (result, error) {
// conn, err := NewReadOnlyConnection(...)
// return result{Connection: conn}, err
// })
//
// Annotated cannot be used with constructors which produce fx.Out objects.
//
// When used with fx.Supply, the target is a value rather than a constructor function.
type Annotated struct {
// If specified, this will be used as the name for all non-error values returned
// by the constructor. For more information on named values, see the documentation
// for the fx.Out type.
//
// A name option may not be provided if a group option is provided.
Name string
// If specified, this will be used as the group name for all non-error values returned
// by the constructor. For more information on value groups, see the package documentation.
//
// A group option may not be provided if a name option is provided.
//
// Similar to group tags, the group name may be followed by a `,flatten`
// option to indicate that each element in the slice returned by the
// constructor should be injected into the value group individually.
Group string
// Target is the constructor or value being annotated with fx.Annotated.
Target interface{}
}
func (a Annotated) String() string {
var fields []string
if len(a.Name) > 0 {
fields = append(fields, fmt.Sprintf("Name: %q", a.Name))
}
if len(a.Group) > 0 {
fields = append(fields, fmt.Sprintf("Group: %q", a.Group))
}
if a.Target != nil {
fields = append(fields, fmt.Sprintf("Target: %v", fxreflect.FuncName(a.Target)))
}
return fmt.Sprintf("fx.Annotated{%v}", strings.Join(fields, ", "))
}
// field used for embedding fx.Out type in generated struct.
var _outAnnotationField = reflect.StructField{
Name: "Out",
Type: reflect.TypeOf(Out{}),
Anonymous: true,
}
// Annotation can be passed to Annotate(f interface{}, anns ...Annotation)
// for annotating the parameter and result types of a function.
type Annotation interface {
apply(*annotated) error
}
var (
_typeOfError reflect.Type = reflect.TypeOf((*error)(nil)).Elem()
_nilError = reflect.Zero(_typeOfError)
)
// annotationError is a wrapper for an error that was encountered while
// applying annotation to a function. It contains the specific error
// that it encountered as well as the target interface that was attempted
// to be annotated.
type annotationError struct {
target interface{}
err error
}
func (e *annotationError) Error() string {
return e.err.Error()
}
type paramTagsAnnotation struct {
tags []string
}
var _ Annotation = paramTagsAnnotation{}
// Given func(T1, T2, T3, ..., TN), this generates a type roughly
// equivalent to,
//
// struct {
// fx.In
//
// Field1 T1 `$tags[0]`
// Field2 T2 `$tags[1]`
// ...
// FieldN TN `$tags[N-1]`
// }
//
// If there has already been a ParamTag that was applied, this
// will return an error.
func (pt paramTagsAnnotation) apply(ann *annotated) error {
if len(ann.ParamTags) > 0 {
return errors.New("cannot apply more than one line of ParamTags")
}
ann.ParamTags = pt.tags
return nil
}
// 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.
func ParamTags(tags ...string) Annotation {
return paramTagsAnnotation{tags}
}
type resultTagsAnnotation struct {
tags []string
}
var _ Annotation = resultTagsAnnotation{}
// Given func(T1, T2, T3, ..., TN), this generates a type roughly
// equivalent to,
//
// struct {
// fx.Out
//
// Field1 T1 `$tags[0]`
// Field2 T2 `$tags[1]`
// ...
// FieldN TN `$tags[N-1]`
// }
//
// If there has already been a ResultTag that was applied, this
// will return an error.
func (rt resultTagsAnnotation) apply(ann *annotated) error {
if len(ann.ResultTags) > 0 {
return errors.New("cannot apply more than one line of ResultTags")
}
ann.ResultTags = rt.tags
return nil
}
// 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.
func ResultTags(tags ...string) Annotation {
return resultTagsAnnotation{tags}
}
type _lifecycleHookAnnotationType int
const (
_unknownHookType _lifecycleHookAnnotationType = iota
_onStartHookType
_onStopHookType
)
type lifecycleHookAnnotation struct {
Type _lifecycleHookAnnotationType
Target interface{}
}
var _ Annotation = (*lifecycleHookAnnotation)(nil)
func (la *lifecycleHookAnnotation) String() string {
name := "UnknownHookAnnotation"
switch la.Type {
case _onStartHookType:
name = _onStartHook
case _onStopHookType:
name = _onStopHook
}
return name
}
func (la *lifecycleHookAnnotation) apply(ann *annotated) error {
if la.Target == nil {
return fmt.Errorf(
"cannot use nil function for %q hook annotation",
la,
)
}
for _, h := range ann.Hooks {
if la.Type == h.Type {
return fmt.Errorf(
"cannot apply more than one %q hook annotation",
la,
)
}
}
ft := reflect.TypeOf(la.Target)
if ft.Kind() != reflect.Func {
return fmt.Errorf(
"must provide function for %q hook, got %v (%T)",
la,
la.Target,
la.Target,
)
}
if ft.NumIn() < 1 || ft.In(0) != _typeOfContext {
return fmt.Errorf(
"first argument of hook must be context.Context, got %v (%T)",
la.Target,
la.Target,
)
}
hasOut := ft.NumOut() == 1
returnsErr := hasOut && ft.Out(0) == _typeOfError
if !hasOut || !returnsErr {
return fmt.Errorf(
"hooks must return only an error type, got %v (%T)",
la.Target,
la.Target,
)
}
if ft.IsVariadic() {
return fmt.Errorf(
"hooks must not accept variatic parameters, got %v (%T)",
la.Target,
la.Target,
)
}
ann.Hooks = append(ann.Hooks, la)
return nil
}
var (
_typeOfLifecycle reflect.Type = reflect.TypeOf((*Lifecycle)(nil)).Elem()
_typeOfContext reflect.Type = reflect.TypeOf((*context.Context)(nil)).Elem()
)
type valueResolver func(reflect.Value, int) reflect.Value
func (la *lifecycleHookAnnotation) resolveMap(results []reflect.Type) (
resultMap map[reflect.Type]valueResolver,
) {
// index the constructor results by type and position to allow
// for us to omit these from the in types that must be injected,
// and to allow us to interleave constructor results
// into our hook arguments.
resultMap = make(map[reflect.Type]valueResolver, len(results))
for _, r := range results {
resultMap[r] = func(v reflect.Value, pos int) (value reflect.Value) {
return v
}
}
return
}
func (la *lifecycleHookAnnotation) resolveLifecycleParamField(
param reflect.Value,
n int,
) (
value reflect.Value,
) {
if param.Kind() == reflect.Struct {
if n <= param.NumField() {
value = param.FieldByName(fmt.Sprintf("Field%d", n))
}
}
return value
}
func (la *lifecycleHookAnnotation) parameters(results ...reflect.Type) (
in reflect.Type,
argmap func(
args []reflect.Value,
) (Lifecycle, []reflect.Value),
) {
resultMap := la.resolveMap(results)
// hook functions require a lifecycle, and it should be injected
params := []reflect.StructField{
{
Name: "In",
Type: _typeOfIn,
Anonymous: true,
},
{
Name: "Lifecycle",
Type: _typeOfLifecycle,
},
}
type argSource struct {
pos int
result bool
resolve valueResolver
}
ft := reflect.TypeOf(la.Target)
resolverIdx := make([]argSource, 1)
for i := 1; i < ft.NumIn(); i++ {
t := ft.In(i)
result, isProvidedByResults := resultMap[t]
if isProvidedByResults {
resolverIdx = append(resolverIdx, argSource{
pos: i,
result: true,
resolve: result,
})
continue
}
field := reflect.StructField{
Name: fmt.Sprintf("Field%d", i),
Type: t,
}
params = append(params, field)
resolverIdx = append(resolverIdx, argSource{
pos: i,
resolve: la.resolveLifecycleParamField,
})
}
in = reflect.StructOf(params)
argmap = func(
args []reflect.Value,
) (lc Lifecycle, remapped []reflect.Value) {
remapped = make([]reflect.Value, ft.NumIn())
if len(args) != 0 {
var (
results reflect.Value
p = args[0]
)
if len(args) > 1 {
results = args[1]
}
lc, _ = p.FieldByName("Lifecycle").Interface().(Lifecycle)
for i := 1; i < ft.NumIn(); i++ {
resolver := resolverIdx[i]
source := p
if resolver.result {
source = results
}
remapped[i] = resolver.resolve(source, i)
}
}
return
}
return
}
func (la *lifecycleHookAnnotation) buildHook(fn func(context.Context) error) (hook Hook) {
switch la.Type {
case _onStartHookType:
hook.OnStart = fn
case _onStopHookType:
hook.OnStop = fn
}
return
}
func (la *lifecycleHookAnnotation) Build(results ...reflect.Type) reflect.Value {
in, paramMap := la.parameters(results...)
params := []reflect.Type{in}
for _, r := range results {
if r != _typeOfError {
params = append(params, r)
}
}
origFn := reflect.ValueOf(la.Target)
newFnType := reflect.FuncOf(params, nil, false)
newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
var lc Lifecycle
lc, args = paramMap(args)
hookFn := func(ctx context.Context) (err error) {
args[0] = reflect.ValueOf(ctx)
results := origFn.Call(args)
if len(results) > 0 && results[0].Type() == _typeOfError {
err, _ = results[0].Interface().(error)
}
return
}
lc.Append(la.buildHook(hookFn))
return []reflect.Value{}
})
return newFn
}
// OnStart is an Annotation that appends an OnStart Hook to the application
// Lifecycle when that function is called. This provides a way to create
// Lifecycle OnStart (see Lifecycle type documentation) hooks without building a
// function that takes a dependency on the Lifecycle type.
//
// fx.Annotate(
// NewServer,
// fx.OnStart(func(ctx context.Context, server Server) error {
// return server.Listen(ctx)
// }),
// )
//
// Which is functionally the same as:
//
// fx.Provide(
// func(lifecycle fx.Lifecycle, p Params) Server {
// server := NewServer(p)
// lifecycle.Append(fx.Hook{
// OnStart: func(ctx context.Context) error {
// return server.Listen(ctx)
// },
// })
// }
// )
//
// Only one OnStart annotation may be applied to a given function at a time,
// however functions may be annotated with other types of lifecylce Hooks, such
// as OnStop.
func OnStart(onStart interface{}) Annotation {
return &lifecycleHookAnnotation{
Type: _onStartHookType,
Target: onStart,
}
}
// OnStop is an Annotation that appends an OnStop Hook to the application
// Lifecycle when that function is called. This provides a way to create
// Lifecycle OnStop (see Lifecycle type documentation) hooks without building a
// function that takes a dependency on the Lifecycle type.
//
// fx.Annotate(
// NewServer,
// fx.OnStop(func(ctx context.Context, server Server) error {
// return server.Shutdown(ctx)
// }),
// )
//
// Which is functionally the same as:
//
// fx.Provide(
// func(lifecycle fx.Lifecycle, p Params) Server {
// server := NewServer(p)
// lifecycle.Append(fx.Hook{
// OnStart: func(ctx context.Context) error {
// return server.Shutdown(ctx)
// },
// })
// }
// )
//
// Only one OnStop annotation may be applied to a given function at a time,
// however functions may be annotated with other types of lifecylce Hooks, such
// as OnStart.
func OnStop(onStop interface{}) Annotation {
return &lifecycleHookAnnotation{
Type: _onStopHookType,
Target: onStop,
}
}
type asAnnotation struct {
targets []interface{}
}
var _ Annotation = asAnnotation{}
// As is an Annotation that annotates the result of a function (i.e. a
// constructor) to be provided as another interface.
//
// For example, the following code specifies that the return type of
// bytes.NewBuffer (bytes.Buffer) should be provided as io.Writer type:
//
// fx.Provide(
// fx.Annotate(bytes.NewBuffer(...), fx.As(new(io.Writer)))
// )
//
// In other words, the code above is equivalent to:
//
// fx.Provide(func() io.Writer {
// return bytes.NewBuffer()
// // provides io.Writer instead of *bytes.Buffer
// })
//
// Note that the bytes.Buffer type is provided as an io.Writer type, so this
// constructor does NOT provide both bytes.Buffer and io.Writer type; it just
// provides io.Writer type.
//
// When multiple values are returned by the annotated function, each type
// gets mapped to corresponding positional result of the annotated function.
//
// For example,
//
// func a() (bytes.Buffer, bytes.Buffer) {
// ...
// }
// fx.Provide(
// fx.Annotate(a, fx.As(new(io.Writer), new(io.Reader)))
// )
//
// Is equivalent to,
//
// fx.Provide(func() (io.Writer, io.Reader) {
// w, r := a()
// return w, r
// }
func As(interfaces ...interface{}) Annotation {
return asAnnotation{interfaces}
}
func (at asAnnotation) apply(ann *annotated) error {
types := make([]reflect.Type, len(at.targets))
for i, typ := range at.targets {
t := reflect.TypeOf(typ)
if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Interface {
return fmt.Errorf("fx.As: argument must be a pointer to an interface: got %v", t)
}
t = t.Elem()
types[i] = t
}
ann.As = append(ann.As, types)
return nil
}
type annotated struct {
Target interface{}
ParamTags []string
ResultTags []string
As [][]reflect.Type
FuncPtr uintptr
Hooks []*lifecycleHookAnnotation
}
func (ann annotated) String() string {
var sb strings.Builder
sb.WriteString("fx.Annotate(")
sb.WriteString(fxreflect.FuncName(ann.Target))
if tags := ann.ParamTags; len(tags) > 0 {
fmt.Fprintf(&sb, ", fx.ParamTags(%q)", tags)
}
if tags := ann.ResultTags; len(tags) > 0 {
fmt.Fprintf(&sb, ", fx.ResultTags(%q)", tags)
}
if as := ann.As; len(as) > 0 {
fmt.Fprintf(&sb, ", fx.As(%v)", as)
}
return sb.String()
}
// Build builds and returns a constructor based on fx.In/fx.Out params and
// results wrapping the original constructor passed to fx.Annotate.
func (ann *annotated) Build() (interface{}, error) {
ft := reflect.TypeOf(ann.Target)
if ft.Kind() != reflect.Func {
return nil, fmt.Errorf("must provide constructor function, got %v (%T)", ann.Target, ann.Target)
}
if err := ann.typeCheckOrigFn(); err != nil {
return nil, fmt.Errorf("invalid annotation function %T: %w", ann.Target, err)
}
resultTypes, remapResults, err := ann.results()
if err != nil {
return nil, err
}
paramTypes, remapParams, hookParams := ann.parameters(resultTypes...)
hookFns := make([]reflect.Value, len(ann.Hooks))
for i, builder := range ann.Hooks {
if hookFn := builder.Build(resultTypes...); !hookFn.IsZero() {
hookFns[i] = hookFn
}
}
newFnType := reflect.FuncOf(paramTypes, resultTypes, false)
origFn := reflect.ValueOf(ann.Target)
ann.FuncPtr = origFn.Pointer()
newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
origArgs := make([]reflect.Value, len(args))
copy(origArgs, args)
args = remapParams(args)
var results []reflect.Value
if ft.IsVariadic() {
results = origFn.CallSlice(args)
} else {
results = origFn.Call(args)
}
results = remapResults(results)
// if the number of results is greater than zero and the final result
// is a non-nil error, do not execute hook installers
hasErrorResult := len(results) > 0 && results[len(results)-1].Type() == _typeOfError
if hasErrorResult {
if err, ok := results[len(results)-1].Interface().(error); ok && err != nil {
return results
}
}
for i, hookFn := range hookFns {
hookArgs := hookParams(i, origArgs, results)
hookFn.Call(hookArgs)
}
return results
})
return newFn.Interface(), nil
}
// checks whether the target function is either
// returning an fx.Out struct or an taking in a
// fx.In struct as a parameter.
func (ann *annotated) typeCheckOrigFn() error {
ft := reflect.TypeOf(ann.Target)
for i := 0; i < ft.NumOut(); i++ {
ot := ft.Out(i)
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")
}
}
for i := 0; i < ft.NumIn(); i++ {
it := ft.In(i)
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")
}
}
return nil
}
// parameters returns the type for the parameters of the annotated function,
// and a function that maps the arguments of the annotated function
// back to the arguments of the target function and a function that maps
// values to any lifecycle hook annotations. It accepts a variactic set
// of reflect.Type which allows for omitting any resulting constructor types
// from required parameters for annotation hooks.
func (ann *annotated) parameters(results ...reflect.Type) (
types []reflect.Type,
remap func([]reflect.Value) []reflect.Value,
hookValueMap func(int, []reflect.Value, []reflect.Value) []reflect.Value,
) {
ft := reflect.TypeOf(ann.Target)
types = make([]reflect.Type, ft.NumIn())
for i := 0; i < ft.NumIn(); i++ {
types[i] = ft.In(i)
}
// No parameter annotations. Return the original types
// and an identity function.
if len(ann.ParamTags) == 0 && !ft.IsVariadic() && len(ann.Hooks) == 0 {
return types, func(args []reflect.Value) []reflect.Value {
return args
}, nil
}
// Turn parameters into an fx.In struct.
inFields := []reflect.StructField{
{
Name: "In",
Type: reflect.TypeOf(In{}),
Anonymous: true,
},
}
for i, t := range types {
field := reflect.StructField{
Name: fmt.Sprintf("Field%d", i),
Type: t,
}
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)
}
// append required types for hooks to types field, but do not
// include them as params in constructor call
for i, t := range ann.Hooks {
params, _ := t.parameters(results...)
field := reflect.StructField{
Name: fmt.Sprintf("Hook%d", i),
Type: params,
}
inFields = append(inFields, field)
}
types = []reflect.Type{reflect.StructOf(inFields)}
remap = func(args []reflect.Value) []reflect.Value {
params := args[0]
args = args[:0]
for i := 0; i < ft.NumIn(); i++ {
args = append(args, params.Field(i+1))
}
return args
}
hookValueMap = func(hook int, args []reflect.Value, results []reflect.Value) (out []reflect.Value) {
params := args[0]
if params.Kind() == reflect.Struct {
var zero reflect.Value
value := params.FieldByName(fmt.Sprintf("Hook%d", hook))
if value != zero {
out = append(out, value)
}
}
for _, r := range results {
if r.Type() != _typeOfError {
out = append(out, r)
}
}
return
}
return
}
// results returns the types of the results of the annotated function,
// and a function that maps the results of the target function,
// into a result compatible with the annotated function.
func (ann *annotated) results() (
types []reflect.Type,
remap func([]reflect.Value) []reflect.Value,
err error,
) {
ft := reflect.TypeOf(ann.Target)
types = make([]reflect.Type, ft.NumOut())
for i := 0; i < ft.NumOut(); i++ {
types[i] = ft.Out(i)
}
// No result annotations. Return the original types
// and an identity function.
if len(ann.ResultTags) == 0 && len(ann.As) == 0 {
return types, func(results []reflect.Value) []reflect.Value {
return results
}, nil
}
numStructs := 1
if len(ann.As) > 0 {
numStructs = len(ann.As)
}
type outStructInfo struct {
Fields []reflect.StructField // fields of the struct
Offsets []int // Offsets[i] is the index of result i in Fields
}
outs := make([]outStructInfo, numStructs)
for i := 0; i < numStructs; i++ {
outs[i].Fields = []reflect.StructField{
{
Name: "Out",
Type: reflect.TypeOf(Out{}),
Anonymous: true,
},
}
outs[i].Offsets = make([]int, len(types))
}
var hasError bool
for i, t := range types {
if t == _typeOfError {
// Guarantee that:
// - only the last result is an error
// - there is at most one error result
if i != len(types)-1 {
return nil, nil, fmt.Errorf(
"only the last result can be an error: "+
"%v (%v) returns error as result %d",
fxreflect.FuncName(ann.Target), ft, i)
}
hasError = true
continue
}
for j := 0; j < numStructs; j++ {
field := reflect.StructField{
Name: fmt.Sprintf("Field%d", i),
Type: t,
}
if len(ann.As) > 0 && i < len(ann.As[j]) {
if !t.Implements(ann.As[j][i]) {
return nil, nil, fmt.Errorf("invalid fx.As: %v does not implement %v", t, ann.As[i])
}
field.Type = ann.As[j][i]
}
if i < len(ann.ResultTags) {
field.Tag = reflect.StructTag(ann.ResultTags[i])
}
outs[j].Offsets[i] = len(outs[j].Fields)
outs[j].Fields = append(outs[j].Fields, field)
}
}
var resTypes []reflect.Type
for _, out := range outs {
resTypes = append(resTypes, reflect.StructOf(out.Fields))
}
outTypes := resTypes
if hasError {
outTypes = append(resTypes, _typeOfError)
}
return outTypes, func(results []reflect.Value) []reflect.Value {
var (
outErr error
outResults []reflect.Value
)
for _, resType := range resTypes {
outResults = append(outResults, reflect.New(resType).Elem())
}
for i, r := range results {
if i == len(results)-1 && hasError {
// If hasError and this is the last item,
// we are guaranteed that this is an error
// object.
if err, _ := r.Interface().(error); err != nil {
outErr = err
}
continue
}
for j := range resTypes {
if fieldIdx := outs[j].Offsets[i]; fieldIdx > 0 {
// fieldIdx 0 is an invalid index
// because it refers to uninitialized
// outs and would point to fx.Out in the
// struct definition. We need to check this
// to prevent panic from setting fx.Out to
// a value.
outResults[j].Field(fieldIdx).Set(r)
}
}
}
if hasError {
if outErr != nil {
outResults = append(outResults, reflect.ValueOf(outErr))
} else {
outResults = append(outResults, _nilError)
}
}
return outResults
}, nil
}
// Annotate lets you annotate a function's parameters and returns
// without you having to declare separate struct definitions for them.
//
// For example,
//
// func NewGateway(ro, rw *db.Conn) *Gateway { ... }
// fx.Provide(
// fx.Annotate(
// NewGateway,
// fx.ParamTags(`name:"ro" optional:"true"`, `name:"rw"`),
// fx.ResultTags(`name:"foo"`),
// ),
// )
//
// Is equivalent to,
//
// type params struct {
// fx.In
//
// RO *db.Conn `name:"ro" optional:"true"`
// RW *db.Conn `name:"rw"`
// }
//
// type result struct {
// fx.Out
//
// GW *Gateway `name:"foo"`
// }
//
// fx.Provide(func(p params) result {
// 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:
//
// fx.Provide(
// fx.Annotate(
// NewGateWay,
// fx.ParamTags(`name:"ro" optional:"true"`),
// fx.ParamTags(`name:"rw"), // ERROR: ParamTags was already used above
// fx.ResultTags(`name:"foo"`)
// )
// )
//
// is considered an invalid usage and will not apply any of the
// Annotations to NewGateway.
//
// If more tags are given than the number of parameters/results, only
// the ones up to the number of parameters/results will be applied.
//
// # Variadic functions
//
// If the provided function is variadic, Annotate treats its parameter as a
// slice. For example,
//
// fx.Annotate(func(w io.Writer, rs ...io.Reader) {
// // ...
// }, ...)
//
// Is equivalent to,
//
// fx.Annotate(func(w io.Writer, rs []io.Reader) {
// // ...
// }, ...)
//
// You can use variadic parameters with Fx's value groups.
// For example,
//
// fx.Annotate(func(mux *http.ServeMux, handlers ...http.Handler) {
// // ...
// }, fx.ParamTags(``, `group:"server"`))
//
// If we provide the above to the application,
// any constructor in the Fx application can inject its HTTP handlers
// by using fx.Annotate, fx.Annotated, or fx.Out.
//
// fx.Annotate(
// func(..) http.Handler { ... },
// fx.ResultTags(`group:"server"`),
// )
//
// fx.Annotated{
// Target: func(..) http.Handler { ... },
// Group: "server",
// }
func Annotate(t interface{}, anns ...Annotation) interface{} {
result := annotated{Target: t}
for _, ann := range anns {
if err := ann.apply(&result); err != nil {
return annotationError{
target: t,
err: err,
}
}
}
return result
}