1034 lines
26 KiB
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
|
|
}
|