diff --git a/app.go b/app.go index a56ae3c4b..0942d9870 100644 --- a/app.go +++ b/app.go @@ -56,112 +56,6 @@ type Option interface { apply(*module) } -// Provide registers any number of constructor functions, teaching the -// application how to instantiate various types. The supplied constructor -// function(s) may depend on other types available in the application, must -// return one or more objects, and may return an error. For example: -// -// // Constructs type *C, depends on *A and *B. -// func(*A, *B) *C -// -// // Constructs type *C, depends on *A and *B, and indicates failure by -// // returning an error. -// func(*A, *B) (*C, error) -// -// // Constructs types *B and *C, depends on *A, and can fail. -// func(*A) (*B, *C, error) -// -// The order in which constructors are provided doesn't matter, and passing -// multiple Provide options appends to the application's collection of -// constructors. Constructors are called only if one or more of their returned -// types are needed, and their results are cached for reuse (so instances of a -// type are effectively singletons within an application). Taken together, -// these properties make it perfectly reasonable to Provide a large number of -// constructors even if only a fraction of them are used. -// -// See the documentation of the In and Out types for advanced features, -// including optional parameters and named instances. -// -// Constructor functions should perform as little external interaction as -// possible, and should avoid spawning goroutines. Things like server listen -// loops, background timer loops, and background processing goroutines should -// instead be managed using Lifecycle callbacks. -func Provide(constructors ...interface{}) Option { - return provideOption{ - Targets: constructors, - Stack: fxreflect.CallerStack(1, 0), - } -} - -type provideOption struct { - Targets []interface{} - Stack fxreflect.Stack -} - -func (o provideOption) apply(mod *module) { - for _, target := range o.Targets { - mod.provides = append(mod.provides, provide{ - Target: target, - Stack: o.Stack, - }) - } -} - -func (o provideOption) String() string { - items := make([]string, len(o.Targets)) - for i, c := range o.Targets { - items[i] = fxreflect.FuncName(c) - } - return fmt.Sprintf("fx.Provide(%s)", strings.Join(items, ", ")) -} - -// Invoke registers functions that are executed eagerly on application start. -// Arguments for these invocations are built using the constructors registered -// by Provide. Passing multiple Invoke options appends the new invocations to -// the application's existing list. -// -// Unlike constructors, invocations are always executed, and they're always -// run in order. Invocations may have any number of returned values. If the -// final returned object is an error, it's assumed to be a success indicator. -// All other returned values are discarded. -// -// Typically, invoked functions take a handful of high-level objects (whose -// constructors depend on lower-level objects) and introduce them to each -// other. This kick-starts the application by forcing it to instantiate a -// variety of types. -// -// To see an invocation in use, read through the package-level example. For -// advanced features, including optional parameters and named instances, see -// the documentation of the In and Out types. -func Invoke(funcs ...interface{}) Option { - return invokeOption{ - Targets: funcs, - Stack: fxreflect.CallerStack(1, 0), - } -} - -type invokeOption struct { - Targets []interface{} - Stack fxreflect.Stack -} - -func (o invokeOption) apply(mod *module) { - for _, target := range o.Targets { - mod.invokes = append(mod.invokes, invoke{ - Target: target, - Stack: o.Stack, - }) - } -} - -func (o invokeOption) String() string { - items := make([]string, len(o.Targets)) - for i, f := range o.Targets { - items[i] = fxreflect.FuncName(f) - } - return fmt.Sprintf("fx.Invoke(%s)", strings.Join(items, ", ")) -} - // Error registers any number of errors with the application to short-circuit // startup. If more than one error is given, the errors are combined into a // single error. @@ -860,161 +754,6 @@ func (app *App) dotGraph() (DotGraph, error) { return DotGraph(b.String()), err } -func (m *module) provide(p provide) { - if m.app.err != nil { - return - } - - constructor := p.Target - if _, ok := constructor.(Option); ok { - m.app.err = fmt.Errorf("fx.Option should be passed to fx.New directly, "+ - "not to fx.Provide: fx.Provide received %v from:\n%+v", - constructor, p.Stack) - return - } - - var info dig.ProvideInfo - opts := []dig.ProvideOption{ - dig.FillProvideInfo(&info), - dig.Export(true), - } - defer func() { - var ev fxevent.Event - - switch { - case p.IsSupply: - ev = &fxevent.Supplied{ - TypeName: p.SupplyType.String(), - Err: m.app.err, - } - - default: - outputNames := make([]string, len(info.Outputs)) - for i, o := range info.Outputs { - outputNames[i] = o.String() - } - - ev = &fxevent.Provided{ - ConstructorName: fxreflect.FuncName(p.Target), - OutputTypeNames: outputNames, - Err: m.app.err, - } - } - m.app.log.LogEvent(ev) - }() - - c := m.scope - switch constructor := constructor.(type) { - case annotationError: - // fx.Annotate failed. Turn it into an Fx error. - m.app.err = fmt.Errorf( - "encountered error while applying annotation using fx.Annotate to %s: %+v", - fxreflect.FuncName(constructor.target), constructor.err) - return - - case annotated: - ctor, err := constructor.Build() - if err != nil { - m.app.err = fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", constructor, p.Stack, err) - return - } - - if err := c.Provide(ctor, opts...); err != nil { - m.app.err = fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", constructor, p.Stack, err) - return - } - - case Annotated: - ann := constructor - switch { - case len(ann.Group) > 0 && len(ann.Name) > 0: - m.app.err = fmt.Errorf( - "fx.Annotated may specify only one of Name or Group: received %v from:\n%+v", - ann, p.Stack) - return - case len(ann.Name) > 0: - opts = append(opts, dig.Name(ann.Name)) - case len(ann.Group) > 0: - opts = append(opts, dig.Group(ann.Group)) - } - - if err := c.Provide(ann.Target, opts...); err != nil { - m.app.err = fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", ann, p.Stack, err) - return - } - - default: - if reflect.TypeOf(constructor).Kind() == reflect.Func { - ft := reflect.ValueOf(constructor).Type() - - for i := 0; i < ft.NumOut(); i++ { - t := ft.Out(i) - - if t == reflect.TypeOf(Annotated{}) { - m.app.err = fmt.Errorf( - "fx.Annotated should be passed to fx.Provide directly, "+ - "it should not be returned by the constructor: "+ - "fx.Provide received %v from:\n%+v", - fxreflect.FuncName(constructor), p.Stack) - return - } - } - } - - if err := c.Provide(constructor, opts...); err != nil { - m.app.err = fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", fxreflect.FuncName(constructor), p.Stack, err) - return - } - } -} - -func (m *module) executeInvokes() error { - for _, invoke := range m.invokes { - if err := m.executeInvoke(invoke); err != nil { - return err - } - } - - for _, m := range m.modules { - if err := m.executeInvokes(); err != nil { - return err - } - } - return nil -} - -func (m *module) executeInvoke(i invoke) (err error) { - fn := i.Target - fnName := fxreflect.FuncName(i.Target) - - m.app.log.LogEvent(&fxevent.Invoking{FunctionName: fnName}) - defer func() { - m.app.log.LogEvent(&fxevent.Invoked{ - FunctionName: fnName, - Err: err, - Trace: fmt.Sprintf("%+v", i.Stack), // format stack trace as multi-line - }) - }() - - c := m.scope - switch fn := fn.(type) { - case Option: - return fmt.Errorf("fx.Option should be passed to fx.New directly, "+ - "not to fx.Invoke: fx.Invoke received %v from:\n%+v", - fn, i.Stack) - - case annotated: - af, err := fn.Build() - if err != nil { - return err - } - - return c.Invoke(af) - default: - return c.Invoke(fn) - } -} - type withTimeoutParams struct { log fxevent.Logger hook string diff --git a/invoke.go b/invoke.go new file mode 100644 index 000000000..c7c089499 --- /dev/null +++ b/invoke.go @@ -0,0 +1,94 @@ +// Copyright (c) 2019-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 ( + "fmt" + "strings" + + "go.uber.org/fx/internal/fxreflect" +) + +// Invoke registers functions that are executed eagerly on application start. +// Arguments for these invocations are built using the constructors registered +// by Provide. Passing multiple Invoke options appends the new invocations to +// the application's existing list. +// +// Unlike constructors, invocations are always executed, and they're always +// run in order. Invocations may have any number of returned values. If the +// final returned object is an error, it's assumed to be a success indicator. +// All other returned values are discarded. +// +// Typically, invoked functions take a handful of high-level objects (whose +// constructors depend on lower-level objects) and introduce them to each +// other. This kick-starts the application by forcing it to instantiate a +// variety of types. +// +// To see an invocation in use, read through the package-level example. For +// advanced features, including optional parameters and named instances, see +// the documentation of the In and Out types. +func Invoke(funcs ...interface{}) Option { + return invokeOption{ + Targets: funcs, + Stack: fxreflect.CallerStack(1, 0), + } +} + +type invokeOption struct { + Targets []interface{} + Stack fxreflect.Stack +} + +func (o invokeOption) apply(mod *module) { + for _, target := range o.Targets { + mod.invokes = append(mod.invokes, invoke{ + Target: target, + Stack: o.Stack, + }) + } +} + +func (o invokeOption) String() string { + items := make([]string, len(o.Targets)) + for i, f := range o.Targets { + items[i] = fxreflect.FuncName(f) + } + return fmt.Sprintf("fx.Invoke(%s)", strings.Join(items, ", ")) +} +func runInvoke(c container, i invoke) error { + fn := i.Target + switch fn := fn.(type) { + case Option: + return fmt.Errorf("fx.Option should be passed to fx.New directly, "+ + "not to fx.Invoke: fx.Invoke received %v from:\n%+v", + fn, i.Stack) + + case annotated: + af, err := fn.Build() + if err != nil { + return err + } + + return c.Invoke(af) + default: + return c.Invoke(fn) + } +} diff --git a/module.go b/module.go index d73f483ed..47d99ecea 100644 --- a/module.go +++ b/module.go @@ -24,8 +24,20 @@ import ( "fmt" "go.uber.org/dig" + "go.uber.org/fx/fxevent" + "go.uber.org/fx/internal/fxreflect" ) +// A container represents a set of constructors to provide +// dependencies, and a set of functions to invoke once all the +// dependencies have been initialized. +// +// This definition corresponds to the dig.Container and dig.Scope. +type container interface { + Invoke(interface{}, ...dig.InvokeOption) error + Provide(interface{}, ...dig.ProvideOption) error +} + // Module is a named group of zero or more fx.Options. func Module(name string, opts ...Option) Option { mo := moduleOption{ @@ -101,3 +113,64 @@ func (m *module) provideAll() { m.provideAll() } } + +func (m *module) provide(p provide) { + if m.app.err != nil { + return + } + + var info dig.ProvideInfo + if err := runProvide(m.scope, p, dig.FillProvideInfo(&info), dig.Export(true)); err != nil { + m.app.err = err + } + var ev fxevent.Event + switch { + case p.IsSupply: + ev = &fxevent.Supplied{ + TypeName: p.SupplyType.String(), + Err: m.app.err, + } + + default: + outputNames := make([]string, len(info.Outputs)) + for i, o := range info.Outputs { + outputNames[i] = o.String() + } + + ev = &fxevent.Provided{ + ConstructorName: fxreflect.FuncName(p.Target), + OutputTypeNames: outputNames, + Err: m.app.err, + } + } + m.app.log.LogEvent(ev) +} + +func (m *module) executeInvokes() error { + for _, invoke := range m.invokes { + if err := m.executeInvoke(invoke); err != nil { + return err + } + } + + for _, m := range m.modules { + if err := m.executeInvokes(); err != nil { + return err + } + } + return nil +} + +func (m *module) executeInvoke(i invoke) (err error) { + fnName := fxreflect.FuncName(i.Target) + m.app.log.LogEvent(&fxevent.Invoking{ + FunctionName: fnName, + }) + err = runInvoke(m.scope, i) + m.app.log.LogEvent(&fxevent.Invoked{ + FunctionName: fnName, + Err: err, + Trace: fmt.Sprintf("%+v", i.Stack), // format stack trace as multi-line + }) + return err +} diff --git a/provide.go b/provide.go new file mode 100644 index 000000000..52b01e317 --- /dev/null +++ b/provide.go @@ -0,0 +1,155 @@ +// Copyright (c) 2022 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 ( + "fmt" + "reflect" + "strings" + + "go.uber.org/dig" + "go.uber.org/fx/internal/fxreflect" +) + +// Provide registers any number of constructor functions, teaching the +// application how to instantiate various types. The supplied constructor +// function(s) may depend on other types available in the application, must +// return one or more objects, and may return an error. For example: +// +// // Constructs type *C, depends on *A and *B. +// func(*A, *B) *C +// +// // Constructs type *C, depends on *A and *B, and indicates failure by +// // returning an error. +// func(*A, *B) (*C, error) +// +// // Constructs types *B and *C, depends on *A, and can fail. +// func(*A) (*B, *C, error) +// +// The order in which constructors are provided doesn't matter, and passing +// multiple Provide options appends to the application's collection of +// constructors. Constructors are called only if one or more of their returned +// types are needed, and their results are cached for reuse (so instances of a +// type are effectively singletons within an application). Taken together, +// these properties make it perfectly reasonable to Provide a large number of +// constructors even if only a fraction of them are used. +// +// See the documentation of the In and Out types for advanced features, +// including optional parameters and named instances. +// +// Constructor functions should perform as little external interaction as +// possible, and should avoid spawning goroutines. Things like server listen +// loops, background timer loops, and background processing goroutines should +// instead be managed using Lifecycle callbacks. +func Provide(constructors ...interface{}) Option { + return provideOption{ + Targets: constructors, + Stack: fxreflect.CallerStack(1, 0), + } +} + +type provideOption struct { + Targets []interface{} + Stack fxreflect.Stack +} + +func (o provideOption) apply(mod *module) { + for _, target := range o.Targets { + mod.provides = append(mod.provides, provide{ + Target: target, + Stack: o.Stack, + }) + } +} + +func (o provideOption) String() string { + items := make([]string, len(o.Targets)) + for i, c := range o.Targets { + items[i] = fxreflect.FuncName(c) + } + return fmt.Sprintf("fx.Provide(%s)", strings.Join(items, ", ")) +} + +func runProvide(c container, p provide, opts ...dig.ProvideOption) error { + constructor := p.Target + if _, ok := constructor.(Option); ok { + return fmt.Errorf("fx.Option should be passed to fx.New directly, "+ + "not to fx.Provide: fx.Provide received %v from:\n%+v", + constructor, p.Stack) + } + + switch constructor := constructor.(type) { + case annotationError: + // fx.Annotate failed. Turn it into an Fx error. + return fmt.Errorf( + "encountered error while applying annotation using fx.Annotate to %s: %+v", + fxreflect.FuncName(constructor.target), constructor.err) + + case annotated: + ctor, err := constructor.Build() + if err != nil { + return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", constructor, p.Stack, err) + } + + if err := c.Provide(ctor, opts...); err != nil { + return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", constructor, p.Stack, err) + } + + case Annotated: + ann := constructor + switch { + case len(ann.Group) > 0 && len(ann.Name) > 0: + return fmt.Errorf( + "fx.Annotated may specify only one of Name or Group: received %v from:\n%+v", + ann, p.Stack) + case len(ann.Name) > 0: + opts = append(opts, dig.Name(ann.Name)) + case len(ann.Group) > 0: + opts = append(opts, dig.Group(ann.Group)) + } + + if err := c.Provide(ann.Target, opts...); err != nil { + return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", ann, p.Stack, err) + } + + default: + if reflect.TypeOf(constructor).Kind() == reflect.Func { + ft := reflect.ValueOf(constructor).Type() + + for i := 0; i < ft.NumOut(); i++ { + t := ft.Out(i) + + if t == reflect.TypeOf(Annotated{}) { + return fmt.Errorf( + "fx.Annotated should be passed to fx.Provide directly, "+ + "it should not be returned by the constructor: "+ + "fx.Provide received %v from:\n%+v", + fxreflect.FuncName(constructor), p.Stack) + } + } + } + + if err := c.Provide(constructor, opts...); err != nil { + return fmt.Errorf("fx.Provide(%v) from:\n%+vFailed: %v", fxreflect.FuncName(constructor), p.Stack, err) + } + } + return nil +}