// 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 fximport ()// 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 [Supply], Target is a value instead of a constructor.//// This type represents a less powerful version of the [Annotate] construct;// prefer [Annotate] where possible.typeAnnotatedstruct {// 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 ( Annotated) () string {var []stringiflen(.Name) > 0 { = append(, fmt.Sprintf("Name: %q", .Name)) }iflen(.Group) > 0 { = append(, fmt.Sprintf("Group: %q", .Group)) }if .Target != nil { = append(, fmt.Sprintf("Target: %v", fxreflect.FuncName(.Target))) }returnfmt.Sprintf("fx.Annotated{%v}", strings.Join(, ", "))}var (// field used for embedding fx.In type in generated struct. _inAnnotationField = reflect.StructField{Name: "In",Type: reflect.TypeOf(In{}),Anonymous: true, }// field used for embedding fx.Out type in generated struct. _outAnnotationField = reflect.StructField{Name: "Out",Type: reflect.TypeOf(Out{}),Anonymous: true, })// Annotation specifies how to wrap a target for [Annotate].// It can be used to set up additional options for a constructor,// or with [Supply], for a value.typeAnnotationinterface { apply(*annotated) error build(*annotated) (interface{}, error)}var ( _typeOfError = 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 ( *annotationError) () string {return .err.Error()}// Unwrap the wrapped error.func ( *annotationError) () error {return .err}type paramTagsAnnotation struct { tags []string}var _ Annotation = paramTagsAnnotation{}var ( errTagSyntaxSpace = errors.New(`multiple tags are not separated by space`) errTagKeySyntax = errors.New("tag key is invalid, Use group, name or optional as tag keys") errTagValueSyntaxQuote = errors.New(`tag value should start with double quote. i.e. key:"value" `) errTagValueSyntaxEndingQuote = errors.New(`tag value should end in double quote. i.e. key:"value" `))// Collections of key value pairs within a tag should be separated by a space.// Eg: `group:"some" optional:"true"`.func verifyTagsSpaceSeparated( int, string) error {if > 0 && != "" && [0] != ' ' {returnerrTagSyntaxSpace }returnnil}// verify tag values are delimited with double quotes.func verifyValueQuote( string) (string, error) {// starting quote should be a double quoteif [0] != '"' {return"", errTagValueSyntaxQuote }// validate tag value is within quotes := 1for < len() && [] != '"' {if [] == '\\' { ++ } ++ }if >= len() {return"", errTagValueSyntaxEndingQuote }return [+1:], nil}// Check whether the tag follows valid struct.// format and returns an error if it's invalid. (i.e. not following// tag:"value" space-separated list )// Currently dig accepts only 'name', 'group', 'optional' as valid tag keys.func verifyAnnotateTag( string) error { := 0 := map[string]struct{}{"group": {}, "optional": {}, "name": {}}for ; != ""; ++ {if := verifyTagsSpaceSeparated(, ); != nil {return } := 0ifstrings.TrimSpace() == "" {returnnil }// parsing the key i.e. till reaching colon :for < len() && [] != ':' { ++ } := strings.TrimSpace([:])if , := []; ! {returnerrTagKeySyntax } , := verifyValueQuote([+1:])if != nil {return } = }returnnil}// 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.//// If the tag is invalid and has mismatched quotation for example,// (`tag_name:"tag_value') , this will return an error.func ( paramTagsAnnotation) ( *annotated) error {iflen(.ParamTags) > 0 {returnerrors.New("cannot apply more than one line of ParamTags") }for , := range .tags {if := verifyAnnotateTag(); != nil {return } } .ParamTags = .tagsreturnnil}// build builds and returns a constructor after applying a ParamTags annotationfunc ( paramTagsAnnotation) ( *annotated) (interface{}, error) { , := .parameters() , := .currentResultTypes() := reflect.ValueOf(.Target) := reflect.FuncOf(, , false) := reflect.MakeFunc(, func( []reflect.Value) []reflect.Value { = ()return .Call() })return .Interface(), 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.func ( paramTagsAnnotation) ( *annotated) ( []reflect.Type,func([]reflect.Value) []reflect.Value,) { := reflect.TypeOf(.Target) = make([]reflect.Type, .NumIn())for := 0; < .NumIn(); ++ { [] = .In() }// No parameter annotations. Return the original types // and an identity function.iflen(.tags) == 0 {return , func( []reflect.Value) []reflect.Value {return } }// Turn parameters into an fx.In struct. := []reflect.StructField{_inAnnotationField}// there was a variadic argument, so it was pre-transformediflen() > 0 && isIn([0]) { := [0]for := 1; < .NumField(); ++ { := .Field() := reflect.StructField{Name: .Name,Type: .Type,Tag: .Tag, }if -1 < len(.tags) { .Tag = reflect.StructTag(.tags[-1]) } = append(, ) } = []reflect.Type{reflect.StructOf()}return , func( []reflect.Value) []reflect.Value { := [0] [0] = reflect.New().Elem()for := 1; < .NumField(); ++ { [0].Field().Set(.Field()) }return } }for , := range { := reflect.StructField{Name: fmt.Sprintf("Field%d", ),Type: , }if < len(.tags) { .Tag = reflect.StructTag(.tags[]) } = append(, ) } = []reflect.Type{reflect.StructOf()}return , func( []reflect.Value) []reflect.Value { := [0] = [:0]for := 0; < .NumIn(); ++ { = append(, .Field(+1)) }return }}// 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.// For example, the following will refer to a named database connection,// and the default, unnamed logger://// fx.Annotate(func(log *log.Logger, conn *sql.DB) *Handler {// // ...// }, fx.ParamTags("", `name:"ro"`))//// ParamTags cannot be used in a function that takes an fx.In struct as a// parameter.func ( ...string) Annotation {returnparamTagsAnnotation{}}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.//// If the tag is invalid and has mismatched quotation for example,// (`tag_name:"tag_value') , this will return an error.func ( resultTagsAnnotation) ( *annotated) error {iflen(.ResultTags) > 0 {returnerrors.New("cannot apply more than one line of ResultTags") }for , := range .tags {if := verifyAnnotateTag(); != nil {return } } .ResultTags = .tagsreturnnil}// build builds and returns a constructor after applying a ResultTags annotationfunc ( resultTagsAnnotation) ( *annotated) (interface{}, error) { := .currentParamTypes() , := .results() := reflect.ValueOf(.Target) := reflect.FuncOf(, , false) := reflect.MakeFunc(, func( []reflect.Value) []reflect.Value { := .Call()return () })return .Interface(), nil}// 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 ( resultTagsAnnotation) ( *annotated) ( []reflect.Type,func([]reflect.Value) []reflect.Value,) { , := .currentResultTypes()if { = [:len()-1] }// No result annotations. Return the original types // and an identity function.iflen(.tags) == 0 {return , func( []reflect.Value) []reflect.Value {return } }// if there's no Out struct among the return types, there was no As annotation applied // just replace original result types with an Out struct and apply tagsvar (outStructInfo []reflect.Type ) .Fields = []reflect.StructField{_outAnnotationField} .Offsets = []int{}for , := range {if !isOut() {// this must be from the original function. // apply the tags := reflect.StructField{Name: fmt.Sprintf("Field%d", ),Type: , }if < len(.tags) { .Tag = reflect.StructTag(.tags[]) } .Offsets = append(.Offsets, len(.Fields)) .Fields = append(.Fields, )continue }// this must be from an As annotation // apply the tags to the existing type := make([]reflect.StructField, .NumField()) [0] = _outAnnotationFieldfor , := range .tags {if +1 < .NumField() { := .Field( + 1) [+1] = reflect.StructField{Name: .Name,Type: .Type,Tag: reflect.StructTag(), } } } = append(, reflect.StructOf()) } := reflect.StructOf(.Fields) := []reflect.Type{}// append existing outs back to outTypes = append(, ...)if { = append(, _typeOfError) }return , func( []reflect.Value) []reflect.Value {var (error []reflect.Value ) = append(, reflect.New().Elem()) := 0for , := range {if == len()-1 && {// If hasError and this is the last item, // we are guaranteed that this is an error // object.if , := .Interface().(error); != nil { = }continue }if < len(.Offsets) {if := .Offsets[]; > 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. [0].Field().Set() }continue }ifisOut(.Type()) { ++if < len() { := reflect.New([]).Elem()for := 1; < [].NumField(); ++ { .Field().Set(.Field()) } = append(, ) } } }if {if != nil { = append(, reflect.ValueOf()) } else { = append(, _nilError) } }return }}// 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.//// For example, the following will produce a named database connection.//// fx.Annotate(func() (*sql.DB, error) {// // ...// }, fx.ResultTags(`name:"ro"`))//// ResultTags cannot be used on a function that returns an fx.Out struct.func ( ...string) Annotation {returnresultTagsAnnotation{}}type outStructInfo struct { Fields []reflect.StructField// fields of the struct Offsets []int// Offsets[i] is the index of result i in Fields}type _lifecycleHookAnnotationType intconst ( _unknownHookType _lifecycleHookAnnotationType = iota _onStartHookType _onStopHookType)type lifecycleHookAnnotation struct { Type _lifecycleHookAnnotationType Target interface{}}var _ Annotation = (*lifecycleHookAnnotation)(nil)func ( *lifecycleHookAnnotation) () string { := "UnknownHookAnnotation"switch .Type {case_onStartHookType: = _onStartHookcase_onStopHookType: = _onStopHook }return}func ( *lifecycleHookAnnotation) ( *annotated) error {if .Target == nil {returnfmt.Errorf("cannot use nil function for %q hook annotation", , ) }for , := range .Hooks {if .Type == .Type {returnfmt.Errorf("cannot apply more than one %q hook annotation", , ) } } := reflect.TypeOf(.Target)if .Kind() != reflect.Func {returnfmt.Errorf("must provide function for %q hook, got %v (%T)", , .Target, .Target, ) }if := .NumOut(); > 0 {if > 1 || .Out(0) != _typeOfError {returnfmt.Errorf("optional hook return may only be an error, got %v (%T)", .Target, .Target, ) } }if .IsVariadic() {returnfmt.Errorf("hooks must not accept variadic parameters, got %v (%T)", .Target, .Target, ) } .Hooks = append(.Hooks, )returnnil}// build builds and returns a constructor after applying a lifecycle hook annotation.func ( *lifecycleHookAnnotation) ( *annotated) (interface{}, error) { , := .currentResultTypes()if ! { = append(, _typeOfError) } , , , := .buildHookInstaller()if != nil {returnnil, } := reflect.ValueOf(.Target) := reflect.FuncOf(, , false) := reflect.MakeFunc(, func( []reflect.Value) []reflect.Value {// copy the original arguments before remapping the parameters // so that we can apply them to the hookInstaller. := make([]reflect.Value, len())copy(, ) = () := .Call()if { := [len()-1] = [:len()-1]if , := .Interface().(error); != nil {// if constructor returned error, do not call hook installerreturnappend(, ) } } := .Call(append(, ...)) = append(, [0])return })return .Interface(), nil}var ( _typeOfLifecycle = reflect.TypeOf((*Lifecycle)(nil)).Elem() _typeOfContext = reflect.TypeOf((*context.Context)(nil)).Elem())// validateHookDeps validates the dependencies of a hook function and returns true if the dependencies are valid.func ( *lifecycleHookAnnotation) ( []reflect.Type, []reflect.Type, []reflect.Type) ( error) {typestruct {reflect.Typestringstring } := func( ) error {var []stringiflen(.) > 0 { = append(, fmt.Sprintf("name:\"%s\"", .)) }iflen(.) > 0 { = append(, fmt.Sprintf("group:\"%s\"", .)) }varstringiflen() > 0 { = fmt.Sprintf("%s `%s`", ..String(), strings.Join(, " ")) } else { = ..String() }returnfmt.Errorf("the %s hook function takes in a parameter of \"%s\", but the annotated function does not have parameters or results of that type", .String(), ) } = nil := make(map[]struct{})for , := range {if !isIn() { [{: }] = struct{}{}continue }for := 1; < .NumField(); ++ { := .Field() [{ : .Type, : .Tag.Get("name"), : .Tag.Get("group"), }] = struct{}{} } }for , := range {if !isOut() { [{: }] = struct{}{}continue }for := 1; < .NumField(); ++ { := .Field() [{ : .Type, : .Tag.Get("name"), : .Tag.Get("group"), }] = struct{}{} } }for , := range {if !isIn() { := {: }if , := []; ! { = ()return }continue }for := 1; < .NumField(); ++ { := .Field() := { : .Type, : .Tag.Get("name"), : .Tag.Get("group"), }if , := []; ! { = ()return } } }return}// buildHookInstaller returns a function that appends a hook to Lifecycle when called,// along with the new parameter types and a function that maps arguments to the annotated constructorfunc ( *lifecycleHookAnnotation) ( *annotated) (reflect.Value, []reflect.Type,func([]reflect.Value) []reflect.Value, // function to remap parameters to function being annotatederror,) { = .currentParamTypes() , = injectLifecycle() , := .currentResultTypes()if { = [:len()-1] }// look for the context.Context type from the original hook function // and then exclude it from the paramTypes of invokeFn because context.Context // will be injected by the lifecycle := -1 := -1 := reflect.ValueOf(.Target) := reflect.TypeOf(.Target) := []reflect.Type{_typeOfLifecycle, }for := 0; < .NumIn(); ++ { := .In()if == _typeOfContext && < 0 { = continue }if !isIn() { = append(, .In())continue } := []reflect.StructField{_inAnnotationField}for := 1; < .NumField(); ++ { := .Field()if .Type == _typeOfContext && < 0 { = = continue } = append(, ) } = append(, reflect.StructOf()) }if = .validateHookDeps(, , ); != nil {return } := reflect.FuncOf(, []reflect.Type{}, false) := reflect.MakeFunc(, func( []reflect.Value) ( []reflect.Value) { := [0].Interface().(Lifecycle) = [1:] := make([]reflect.Value, .NumIn()) := func( context.Context) ( error) {// If the hook function has multiple parameters, and the first // parameter is a context, inject the provided context.if < 0 { := 0for := 0; < len(); ++ {if == { [] = reflect.ValueOf() = 1continue }if - >= 0 && - < len() { [] = [-] } } } else {for := 0; < .NumIn(); ++ {if != { [] = []continue } := .In() := reflect.New().Elem()for := 1; < .NumField(); ++ {if < { .Field().Set([].Field()) } elseif == { .Field().Set(reflect.ValueOf()) } else { .Field().Set([].Field( - 1)) } } [] = } } := .Call()iflen() > 0 && [0].Type() == _typeOfError { , _ = [0].Interface().(error) }return } .Append(.buildHook())return }) := reflect.FuncOf(append(, ...), []reflect.Type{_typeOfError}, false) = reflect.MakeFunc(, func( []reflect.Value) ( []reflect.Value) {// build a private scope for hook functionvar *dig.Scopeswitch .Type {case_onStartHookType: = .container.Scope("onStartHookScope")case_onStopHookType: = .container.Scope("onStopHookScope") }// provide the private scope with the current dependencies and results of the annotated function = []reflect.Value{_nilError} := makeHookScopeCtor(, , )if := .Provide(); != nil { [0] = reflect.ValueOf(fmt.Errorf("error providing possible parameters for hook installer: %w", ))return }// invoking invokeFn appends the hook function to lifecycleif := .Invoke(.Interface()); != nil { [0] = reflect.ValueOf(fmt.Errorf("error invoking hook installer: %w", ))return }return })return , , , nil}var ( _nameTag = "name" _groupTag = "group")// makeHookScopeCtor makes a constructor that provides all possible parameters// that the lifecycle hook being appended can depend on. It also deduplicates// duplicate param and result types, which is possible when using fx.Decorate,// and uses values from results for providing the deduplicated types.func makeHookScopeCtor( []reflect.Type, []reflect.Type, []reflect.Value) interface{} {typestruct {reflect.Typestringstring } := map[]struct{}{} := make([]reflect.Type, len())for , := range { [] = ifisOut() {for := 1; < .NumField(); ++ { := .Field() [{ : .Type, : .Tag.Get(_nameTag), : .Tag.Get(_groupTag), }] = struct{}{} }continue } [{: }] = struct{}{} } := []reflect.StructField{_outAnnotationField} := make([][]int, len())for , := range { [] = []int{}ifisIn() {for := 1; < .NumField(); ++ { := .Field() := { : .Type, : .Tag.Get(_nameTag), : .Tag.Get(_groupTag), }if , := []; { [] = append([], )continue } := reflect.StructField{Name: fmt.Sprintf("Field%d", -1),Type: .Type,Tag: .Tag, } = append(, ) }continue } := {: }if , := []; { [] = append([], )continue } := reflect.StructField{Name: fmt.Sprintf("Field%d", ),Type: , } = append(, ) } = append(, reflect.StructOf()) := reflect.FuncOf([]reflect.Type{}, , false) := reflect.MakeFunc(, func( []reflect.Value) []reflect.Value { := len() := make([]reflect.Value, )for := 0; < -1; ++ { [] = [] } := reflect.New([-1]).Elem() := 1for := - 1; < len(); ++ { := - ( - 1)ifisIn([]) { := 0for := 1; < [].NumField(); ++ {iflen([]) > 0 && [][] == {// skip ++continue } .Field().Set([].Field()) ++ } } else {iflen([]) > 0 && [][0] == {continue } .Field().Set([]) ++ } } [-1] = return })return .Interface()}func injectLifecycle( []reflect.Type) ([]reflect.Type, func([]reflect.Value) []reflect.Value) {// since lifecycle already exists in param types, no need to inject againiflifecycleExists() {return , func( []reflect.Value) []reflect.Value {return } }// If params are tagged or there's an untagged variadic argument, // add a Lifecycle field to the param structiflen() > 0 && isIn([0]) { := [0] := []reflect.StructField{ .Field(0), {Name: "Lifecycle",Type: _typeOfLifecycle, }, }for := 1; < .NumField(); ++ { = append(, .Field()) } := reflect.StructOf()return []reflect.Type{}, func( []reflect.Value) []reflect.Value { := [0] [0] = reflect.New().Elem()for := 1; < .NumField(); ++ { [0].Field().Set(.Field( + 1)) }return } }returnappend([]reflect.Type{_typeOfLifecycle}, ...), func( []reflect.Value) []reflect.Value {return [1:] }}func lifecycleExists( []reflect.Type) bool {for , := range {if == _typeOfLifecycle {returntrue }ifisIn() {for := 1; < .NumField(); ++ {if .Field().Type == _typeOfLifecycle {returntrue } } } }returnfalse}func ( *lifecycleHookAnnotation) ( func(context.Context) error) ( Hook) {switch .Type {case_onStartHookType: .OnStart = case_onStopHookType: .OnStop = }return}// 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.Provide(// 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)// },// })// return server// }// )//// It is also possible to use OnStart annotation with other parameter and result// annotations, provided that the parameter of the function passed to OnStart// matches annotated parameters and results.//// For example, the following is possible://// fx.Provide(// fx.Annotate(// func (a A) B {...},// fx.ParamTags(`name:"A"`),// fx.ResultTags(`name:"B"`),// fx.OnStart(func (p OnStartParams) {...}),// ),// )//// As long as OnStartParams looks like the following and has no other dependencies// besides Context or Lifecycle://// type OnStartParams struct {// fx.In// FieldA A `name:"A"`// FieldB B `name:"B"`// }//// Only one OnStart annotation may be applied to a given function at a time,// however functions may be annotated with other types of lifecycle Hooks, such// as OnStop. The hook function passed into OnStart cannot take any arguments// outside of the annotated constructor's existing dependencies or results, except// a context.Context.func ( interface{}) Annotation {return &lifecycleHookAnnotation{Type: _onStartHookType,Target: , }}// 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.Provide(// 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{// OnStop: func(ctx context.Context) error {// return server.Shutdown(ctx)// },// })// return server// }// )//// It is also possible to use OnStop annotation with other parameter and result// annotations, provided that the parameter of the function passed to OnStop// matches annotated parameters and results.//// For example, the following is possible://// fx.Provide(// fx.Annotate(// func (a A) B {...},// fx.ParamTags(`name:"A"`),// fx.ResultTags(`name:"B"`),// fx.OnStop(func (p OnStopParams) {...}),// ),// )//// As long as OnStopParams looks like the following and has no other dependencies// besides Context or Lifecycle://// type OnStopParams struct {// fx.In// FieldA A `name:"A"`// FieldB B `name:"B"`// }//// Only one OnStop annotation may be applied to a given function at a time,// however functions may be annotated with other types of lifecycle Hooks, such// as OnStart. The hook function passed into OnStop cannot take any arguments// outside of the annotated constructor's existing dependencies or results, except// a context.Context.func ( interface{}) Annotation {return &lifecycleHookAnnotation{Type: _onStopHookType,Target: , }}type asAnnotation struct { targets []interface{} types []asType}type asType struct { self bool typ reflect.Type// May be nil if self is true.}func ( asType) () string {if .self {return"self" }return .typ.String()}func isOut( reflect.Type) bool {return (.Kind() == reflect.Struct &&dig.IsOut(reflect.New().Elem().Interface()))}func isIn( reflect.Type) bool {return (.Kind() == reflect.Struct &&dig.IsIn(reflect.New().Elem().Interface()))}var _ Annotation = (*asAnnotation)(nil)// 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// }//// As entirely replaces the default return types of a function. In order// to maintain the original return types when using As, see [Self].//// As annotation cannot be used in a function that returns an [Out] struct as a return type.func ( ...interface{}) Annotation {return &asAnnotation{targets: }}// 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.//// For example,//// fx.Provide(// fx.Annotate(// bytes.NewBuffer,// fx.As(new(io.Writer)),// fx.As(fx.Self()),// )// )//// Is equivalent to,//// fx.Provide(// bytes.NewBuffer,// func(b *bytes.Buffer) io.Writer {// return b// },// )//// in that it provides the same *bytes.Buffer instance// as both a *bytes.Buffer and an io.Writer.func () any {return &self{}}type self struct{}func ( *asAnnotation) ( *annotated) error { .types = make([]asType, len(.targets))for , := range .targets {if , := .(*self); { .types[] = asType{self: true}continue } := reflect.TypeOf()if .Kind() != reflect.Ptr || .Elem().Kind() != reflect.Interface {returnfmt.Errorf("fx.As: argument must be a pointer to an interface: got %v", ) } = .Elem() .types[] = asType{typ: } } .As = append(.As, .types)returnnil}// build implements Annotationfunc ( *asAnnotation) ( *annotated) (interface{}, error) { := .currentParamTypes() , , := .results()if != nil {returnnil, } := reflect.ValueOf(.Target) := reflect.FuncOf(, , false) := reflect.MakeFunc(, func( []reflect.Value) []reflect.Value { := .Call()return () })return .Interface(), nil}func ( *asAnnotation) ( *annotated) ( []reflect.Type,func([]reflect.Value) []reflect.Value,error,) { , := .currentResultTypes() := []reflect.StructField{_outAnnotationField}if { = [:len()-1] } , := extractResultFields()for , := range { := .Type := reflect.StructField{Name: fmt.Sprintf("Field%d", ),Type: ,Tag: .Tag, }if >= len(.types) || .types[].self { = append(, )continue }if !.Implements(.types[].typ) {returnnil, nil, fmt.Errorf("invalid fx.As: %v does not implement %v", , .types[]) } .Type = .types[].typ = append(, ) } := reflect.StructOf()var []reflect.Type = append(, )if { = append(, _typeOfError) }return , func( []reflect.Value) []reflect.Value {var (error []reflect.Value )for , := range {if == len()-1 && {// If hasError and this is the last item, // we are guaranteed that this is an error // object.if , := .Interface().(error); != nil { = }continue } = append(, ) } := reflect.New().Elem()for := 1; < .NumField(); ++ { .Field().Set((, )) } = append(, )if {if != nil { = append(, reflect.ValueOf()) } else { = append(, _nilError) } }return }, nil}func extractResultFields( []reflect.Type) ([]reflect.StructField, func(int, []reflect.Value) reflect.Value) {var []reflect.StructFieldiflen() > 0 && isOut([0]) {for := 1; < [0].NumField(); ++ { = append(, [0].Field()) }return , func( int, []reflect.Value) reflect.Value {return [0].Field() } }for , := range {ifisOut() {continue } := reflect.StructField{Name: fmt.Sprintf("Field%d", ),Type: , } = append(, ) }return , func( int, []reflect.Value) reflect.Value {return [-1] }}type fromAnnotation struct { targets []interface{} types []reflect.Type}var _ Annotation = (*fromAnnotation)(nil)// From is an [Annotation] that annotates the parameter(s) for a function (i.e. a// constructor) to be accepted from other provided types. It is analogous to the// [As] for parameter types to the constructor.//// For example,//// type Runner interface { Run() }// func NewFooRunner() *FooRunner // implements Runner// func NewRunnerWrap(r Runner) *RunnerWrap//// fx.Provide(// fx.Annotate(// NewRunnerWrap,// fx.From(new(*FooRunner)),// ),// )//// Is equivalent to,//// fx.Provide(func(r *FooRunner) *RunnerWrap {// // need *FooRunner instead of Runner// return NewRunnerWrap(r)// })//// When the annotated function takes in multiple parameters, each type gets// mapped to corresponding positional parameter of the annotated function//// For example,//// func NewBarRunner() *BarRunner // implements Runner// func NewRunnerWraps(r1 Runner, r2 Runner) *RunnerWraps//// fx.Provide(// fx.Annotate(// NewRunnerWraps,// fx.From(new(*FooRunner), new(*BarRunner)),// ),// )//// Is equivalent to,//// fx.Provide(func(r1 *FooRunner, r2 *BarRunner) *RunnerWraps {// return NewRunnerWraps(r1, r2)// })//// From annotation cannot be used in a function that takes an [In] struct as a// parameter.func ( ...interface{}) Annotation {return &fromAnnotation{targets: }}func ( *fromAnnotation) ( *annotated) error {iflen(.From) > 0 {returnerrors.New("cannot apply more than one line of From") } := reflect.TypeOf(.Target) .types = make([]reflect.Type, len(.targets))for , := range .targets {if .IsVariadic() && == .NumIn()-1 {returnerrors.New("fx.From: cannot annotate a variadic argument") } := reflect.TypeOf()if == nil || .Kind() != reflect.Ptr {returnfmt.Errorf("fx.From: argument must be a pointer to a type that implements some interface: got %v", ) } .types[] = .Elem() } .From = .typesreturnnil}// build builds and returns a constructor after applying a From annotationfunc ( *fromAnnotation) ( *annotated) (interface{}, error) { , , := .parameters()if != nil {returnnil, } , := .currentResultTypes() := reflect.ValueOf(.Target) := reflect.FuncOf(, , false) := reflect.MakeFunc(, func( []reflect.Value) []reflect.Value { = ()return .Call() })return .Interface(), 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.func ( *fromAnnotation) ( *annotated) ( []reflect.Type,func([]reflect.Value) []reflect.Value,error,) { := reflect.TypeOf(.Target) = make([]reflect.Type, .NumIn())for := 0; < .NumIn(); ++ { [] = .In() }// No parameter annotations. Return the original types // and an identity function.iflen(.targets) == 0 {return , func( []reflect.Value) []reflect.Value {return }, nil }// Turn parameters into an fx.In struct. := []reflect.StructField{_inAnnotationField}// The following situations may occur: // 1. there was a variadic argument, so it was pre-transformed. // 2. another parameter annotation was transformed (ex: ParamTags). // so need to visit fields of the fx.In struct.iflen() > 0 && isIn([0]) { := [0]for := 1; < .NumField(); ++ { := .Field() := reflect.StructField{Name: .Name,Type: .Type,Tag: .Tag, }if -1 < len(.types) { := .types[-1]if !.Implements(.Type) {returnnil, nil, fmt.Errorf("invalid fx.From: %v does not implement %v", , .Type) } .Type = } = append(, ) } = []reflect.Type{reflect.StructOf()}return , func( []reflect.Value) []reflect.Value { := [0] [0] = reflect.New().Elem()for := 1; < .NumField(); ++ { [0].Field().Set(.Field()) }return }, nil }for , := range { := reflect.StructField{Name: fmt.Sprintf("Field%d", ),Type: , }if < len(.types) { := .types[]if !.Implements(.Type) {returnnil, nil, fmt.Errorf("invalid fx.From: %v does not implement %v", , .Type) } .Type = } = append(, ) } = []reflect.Type{reflect.StructOf()}return , func( []reflect.Value) []reflect.Value { := [0] = [:0]for := 0; < .NumIn(); ++ { = append(, .Field(+1)) }return }, nil}type annotated struct { Target interface{} Annotations []Annotation ParamTags []string ResultTags []string As [][]asType From []reflect.Type FuncPtr uintptr Hooks []*lifecycleHookAnnotation// container is used to build private scopes for lifecycle hook functions // added via fx.OnStart and fx.OnStop annotations. container *dig.Container}func ( annotated) () string {varstrings.Builder .WriteString("fx.Annotate(") .WriteString(fxreflect.FuncName(.Target))if := .ParamTags; len() > 0 {fmt.Fprintf(&, ", fx.ParamTags(%q)", ) }if := .ResultTags; len() > 0 {fmt.Fprintf(&, ", fx.ResultTags(%q)", ) }if := .As; len() > 0 {fmt.Fprintf(&, ", fx.As(%v)", ) }if := .From; len() > 0 {fmt.Fprintf(&, ", fx.From(%v)", ) }return .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 ( *annotated) () (interface{}, error) { .container = dig.New() := reflect.TypeOf(.Target)if .Kind() != reflect.Func {returnnil, fmt.Errorf("must provide constructor function, got %v (%T)", .Target, .Target) }if := .typeCheckOrigFn(); != nil {returnnil, fmt.Errorf("invalid annotation function %T: %w", .Target, ) } .applyOptionalTag()var (error []*lifecycleHookAnnotation )for , := range .Annotations {if , := .(*lifecycleHookAnnotation); { = append(, )continue }if .Target, = .build(); != nil {returnnil, } }// need to call cleanUpAsResults before applying lifecycle annotations // to exclude the original results from the hook's scope if any // fx.As annotations were applied .cleanUpAsResults()for , := range {if .Target, = .build(); != nil {returnnil, } }return .Target, nil}// applyOptionalTag checks if function being annotated is variadic// and applies optional tag to the variadic argument before// applying any other annotationsfunc ( *annotated) () { := reflect.TypeOf(.Target)if !.IsVariadic() {return } , := .currentResultTypes() := []reflect.StructField{_inAnnotationField}for := 0; < .NumIn(); ++ { := reflect.StructField{Name: fmt.Sprintf("Field%d", ),Type: .In(), }if == .NumIn()-1 {// Mark a variadic argument optional by default // so that just wrapping a function in fx.Annotate does not // suddenly introduce a required []arg dependency. .Tag = reflect.StructTag(`optional:"true"`) } = append(, ) } := reflect.StructOf() := reflect.ValueOf(.Target) := reflect.FuncOf([]reflect.Type{}, , false) := reflect.MakeFunc(, func( []reflect.Value) []reflect.Value { := [0] = [:0]for := 0; < .NumIn(); ++ { = append(, .Field(+1)) }return .CallSlice() }) .Target = .Interface()}// cleanUpAsResults does a check to see if an As annotation was applied.// If there was any fx.As annotation applied, cleanUpAsResults wraps the// function one more time to remove the results from the original function.func ( *annotated) () {// clean up orig function results if there were any As annotationsiflen(.As) < 1 {return } := .currentParamTypes() , := .currentResultTypes() := len(.As)if { ++ } := [len()-:] := reflect.ValueOf(.Target) := reflect.FuncOf(, , false) := reflect.MakeFunc(, func( []reflect.Value) ( []reflect.Value) { = .Call() = [len()-:]return }) .Target = .Interface()}// checks and returns a non-nil error if the target function:// - returns an fx.Out struct as a result and has either a ResultTags or an As annotation// - takes in an fx.In struct as a parameter and has either a ParamTags or a From annotation// - has an error result not as the last result.func ( *annotated) () error { := reflect.TypeOf(.Target) := .NumOut()for := 0; < ; ++ { := .Out()if == _typeOfError && != -1 {returnfmt.Errorf("only the last result can be an error: "+"%v (%v) returns error as result %d",fxreflect.FuncName(.Target), , ) }if .Kind() != reflect.Struct {continue }if !dig.IsOut(reflect.New(.Out()).Elem().Interface()) {continue }iflen(.ResultTags) > 0 || len(.As) > 0 {returnerrors.New("fx.Out structs cannot be annotated with fx.ResultTags or fx.As") } }for := 0; < .NumIn(); ++ { := .In()if .Kind() != reflect.Struct {continue }if !dig.IsIn(reflect.New(.In()).Elem().Interface()) {continue }iflen(.ParamTags) > 0 || len(.From) > 0 {returnerrors.New("fx.In structs cannot be annotated with fx.ParamTags or fx.From") } }returnnil}func ( *annotated) () ( []reflect.Type, bool) { := reflect.TypeOf(.Target) := .NumOut() = make([]reflect.Type, )for := 0; < ; ++ { [] = .Out()if [] == _typeOfError && == -1 { = true } }return , }func ( *annotated) () []reflect.Type { := reflect.TypeOf(.Target) := make([]reflect.Type, .NumIn())for := 0; < .NumIn(); ++ { [] = .In() }return}// 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)}// })//// 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"`)// )// )//// 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 [Annotate], [Annotated], or [Out].//// fx.Annotate(// func(..) http.Handler { ... },// fx.ResultTags(`group:"server"`),// )//// fx.Annotated{// Target: func(..) http.Handler { ... },// Group: "server",// }func ( interface{}, ...Annotation) interface{} { := annotated{Target: }for , := range {if := .apply(&); != nil {returnannotationError{target: ,err: , } } } .Annotations = return}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.