package fx
import (
"fmt"
"go.uber.org/dig"
"go.uber.org/fx/fxevent"
"go.uber.org/fx/internal/fxreflect"
"go.uber.org/multierr"
)
type container interface {
Invoke(interface {}, ...dig .InvokeOption ) error
Provide(interface {}, ...dig .ProvideOption ) error
Decorate(interface {}, ...dig .DecorateOption ) error
}
func Module (name string , opts ...Option ) Option {
mo := moduleOption {
name : name ,
location : fxreflect .CallerStack (1 , 2 )[0 ],
options : opts ,
}
return mo
}
type moduleOption struct {
name string
location fxreflect .Frame
options []Option
}
func (o moduleOption ) String () string {
return fmt .Sprintf ("fx.Module(%q, %v)" , o .name , o .options )
}
func (o moduleOption ) apply (mod *module ) {
trace := append ([]string {fmt .Sprintf ("%v (%v)" , o .location , o .name )}, mod .trace ...)
newModule := &module {
name : o .name ,
parent : mod ,
trace : trace ,
app : mod .app ,
}
for _ , opt := range o .options {
opt .apply (newModule )
}
mod .modules = append (mod .modules , newModule )
}
type module struct {
parent *module
name string
trace []string
scope scope
provides []provide
invokes []invoke
decorators []decorator
modules []*module
app *App
log fxevent .Logger
fallbackLogger fxevent .Logger
logConstructor *provide
}
type scope interface {
Decorate(f interface {}, opts ...dig .DecorateOption ) error
Invoke(f interface {}, opts ...dig .InvokeOption ) error
Provide(f interface {}, opts ...dig .ProvideOption ) error
Scope(name string , opts ...dig .ScopeOption ) *dig .Scope
String() string
}
func (m *module ) build (app *App , root *dig .Container ) {
if m .parent == nil {
m .scope = root
} else {
parentScope := m .parent .scope
m .scope = parentScope .Scope (m .name )
m .log = m .parent .log
}
if m .logConstructor != nil {
m .fallbackLogger , m .log = m .log , new (logBuffer )
}
for _ , mod := range m .modules {
mod .build (app , root )
}
}
func (m *module ) provideAll () {
for _ , p := range m .provides {
m .provide (p )
}
for _ , m := range m .modules {
m .provideAll ()
}
}
func (m *module ) provide (p provide ) {
if m .app .err != nil {
return
}
if p .IsSupply {
m .supply (p )
return
}
funcName := fxreflect .FuncName (p .Target )
var info dig .ProvideInfo
opts := []dig .ProvideOption {
dig .FillProvideInfo (&info ),
dig .Export (!p .Private ),
dig .WithProviderBeforeCallback (func (bci dig .BeforeCallbackInfo ) {
m .log .LogEvent (&fxevent .BeforeRun {
Name : funcName ,
Kind : "provide" ,
ModuleName : m .name ,
})
}),
dig .WithProviderCallback (func (ci dig .CallbackInfo ) {
m .log .LogEvent (&fxevent .Run {
Name : funcName ,
Kind : "provide" ,
ModuleName : m .name ,
Runtime : ci .Runtime ,
Err : ci .Error ,
})
}),
}
if err := runProvide (m .scope , p , opts ...); err != nil {
m .app .err = err
}
outputNames := make ([]string , len (info .Outputs ))
for i , o := range info .Outputs {
outputNames [i ] = o .String ()
}
m .log .LogEvent (&fxevent .Provided {
ConstructorName : funcName ,
StackTrace : p .Stack .Strings (),
ModuleTrace : append ([]string {p .Stack [0 ].String ()}, m .trace ...),
ModuleName : m .name ,
OutputTypeNames : outputNames ,
Err : m .app .err ,
Private : p .Private ,
})
}
func (m *module ) supply (p provide ) {
typeName := p .SupplyType .String ()
opts := []dig .ProvideOption {
dig .Export (!p .Private ),
dig .WithProviderBeforeCallback (func (bci dig .BeforeCallbackInfo ) {
m .log .LogEvent (&fxevent .BeforeRun {
Name : fmt .Sprintf ("stub(%v)" , typeName ),
Kind : "supply" ,
ModuleName : m .name ,
})
}),
dig .WithProviderCallback (func (ci dig .CallbackInfo ) {
m .log .LogEvent (&fxevent .Run {
Name : fmt .Sprintf ("stub(%v)" , typeName ),
Kind : "supply" ,
Runtime : ci .Runtime ,
ModuleName : m .name ,
})
}),
}
if err := runProvide (m .scope , p , opts ...); err != nil {
m .app .err = err
}
m .log .LogEvent (&fxevent .Supplied {
TypeName : typeName ,
StackTrace : p .Stack .Strings (),
ModuleTrace : append ([]string {p .Stack [0 ].String ()}, m .trace ...),
ModuleName : m .name ,
Err : m .app .err ,
})
}
func (m *module ) installAllEventLoggers () {
if m .logConstructor != nil {
if buffer , ok := m .log .(*logBuffer ); ok {
if err := m .installEventLogger (buffer ); err != nil {
m .app .err = multierr .Append (m .app .err , err )
m .log = m .fallbackLogger
buffer .Connect (m .log )
}
}
m .fallbackLogger = nil
} else if m .parent != nil {
m .log = m .parent .log
}
for _ , mod := range m .modules {
mod .installAllEventLoggers ()
}
}
func (m *module ) installEventLogger (buffer *logBuffer ) (err error ) {
p := m .logConstructor
fname := fxreflect .FuncName (p .Target )
defer func () {
m .log .LogEvent (&fxevent .LoggerInitialized {
Err : err ,
ConstructorName : fname ,
})
}()
if err := m .scope .Provide (p .Target ); err != nil {
return fmt .Errorf ("fx.WithLogger(%v) from:\n%+v\nin Module: %q\nFailed: %w" ,
fname , p .Stack , m .name , err )
}
return m .scope .Invoke (func (log fxevent .Logger ) {
m .log = log
buffer .Connect (log )
})
}
func (m *module ) invokeAll () error {
for _ , m := range m .modules {
if err := m .invokeAll (); err != nil {
return err
}
}
for _ , invoke := range m .invokes {
if err := m .invoke (invoke ); err != nil {
return err
}
}
return nil
}
func (m *module ) invoke (i invoke ) (err error ) {
fnName := fxreflect .FuncName (i .Target )
m .log .LogEvent (&fxevent .Invoking {
FunctionName : fnName ,
ModuleName : m .name ,
})
err = runInvoke (m .scope , i )
m .log .LogEvent (&fxevent .Invoked {
FunctionName : fnName ,
ModuleName : m .name ,
Err : err ,
Trace : fmt .Sprintf ("%+v" , i .Stack ),
})
return err
}
func (m *module ) decorateAll () error {
for _ , d := range m .decorators {
if err := m .decorate (d ); err != nil {
return err
}
}
for _ , m := range m .modules {
if err := m .decorateAll (); err != nil {
return err
}
}
return nil
}
func (m *module ) decorate (d decorator ) (err error ) {
if d .IsReplace {
return m .replace (d )
}
funcName := fxreflect .FuncName (d .Target )
var info dig .DecorateInfo
opts := []dig .DecorateOption {
dig .FillDecorateInfo (&info ),
dig .WithDecoratorBeforeCallback (func (bci dig .BeforeCallbackInfo ) {
m .log .LogEvent (&fxevent .BeforeRun {
Name : funcName ,
Kind : "decorate" ,
ModuleName : m .name ,
})
}),
dig .WithDecoratorCallback (func (ci dig .CallbackInfo ) {
m .log .LogEvent (&fxevent .Run {
Name : funcName ,
Kind : "decorate" ,
ModuleName : m .name ,
Runtime : ci .Runtime ,
Err : ci .Error ,
})
}),
}
err = runDecorator (m .scope , d , opts ...)
outputNames := make ([]string , len (info .Outputs ))
for i , o := range info .Outputs {
outputNames [i ] = o .String ()
}
m .log .LogEvent (&fxevent .Decorated {
DecoratorName : funcName ,
StackTrace : d .Stack .Strings (),
ModuleTrace : append ([]string {d .Stack [0 ].String ()}, m .trace ...),
ModuleName : m .name ,
OutputTypeNames : outputNames ,
Err : err ,
})
return err
}
func (m *module ) replace (d decorator ) error {
typeName := d .ReplaceType .String ()
opts := []dig .DecorateOption {
dig .WithDecoratorBeforeCallback (func (bci dig .BeforeCallbackInfo ) {
m .log .LogEvent (&fxevent .BeforeRun {
Name : fmt .Sprintf ("stub(%v)" , typeName ),
Kind : "replace" ,
ModuleName : m .name ,
})
}),
dig .WithDecoratorCallback (func (ci dig .CallbackInfo ) {
m .log .LogEvent (&fxevent .Run {
Name : fmt .Sprintf ("stub(%v)" , typeName ),
Kind : "replace" ,
ModuleName : m .name ,
Runtime : ci .Runtime ,
Err : ci .Error ,
})
}),
}
err := runDecorator (m .scope , d , opts ...)
m .log .LogEvent (&fxevent .Replaced {
ModuleName : m .name ,
StackTrace : d .Stack .Strings (),
ModuleTrace : append ([]string {d .Stack [0 ].String ()}, m .trace ...),
OutputTypeNames : []string {typeName },
Err : err ,
})
return err
}
The pages are generated with Golds v0.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 .