231 lines
6.0 KiB
Go
231 lines
6.0 KiB
Go
|
// 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"
|
||
|
|
||
|
"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
|
||
|
Decorate(interface{}, ...dig.DecorateOption) error
|
||
|
}
|
||
|
|
||
|
// Module is a named group of zero or more fx.Options.
|
||
|
// A Module creates a scope in which certain operations are taken
|
||
|
// place. For more information, see [Decorate], [Replace], or [Invoke].
|
||
|
func Module(name string, opts ...Option) Option {
|
||
|
mo := moduleOption{
|
||
|
name: name,
|
||
|
options: opts,
|
||
|
}
|
||
|
return mo
|
||
|
}
|
||
|
|
||
|
type moduleOption struct {
|
||
|
name string
|
||
|
options []Option
|
||
|
}
|
||
|
|
||
|
func (o moduleOption) String() string {
|
||
|
return fmt.Sprintf("fx.Module(%q, %v)", o.name, o.options)
|
||
|
}
|
||
|
|
||
|
func (o moduleOption) apply(mod *module) {
|
||
|
// This get called on any submodules' that are declared
|
||
|
// as part of another module.
|
||
|
|
||
|
// 1. Create a new module with the parent being the specified
|
||
|
// module.
|
||
|
// 2. Apply child Options on the new module.
|
||
|
// 3. Append it to the parent module.
|
||
|
newModule := &module{
|
||
|
name: o.name,
|
||
|
parent: mod,
|
||
|
app: mod.app,
|
||
|
}
|
||
|
for _, opt := range o.options {
|
||
|
opt.apply(newModule)
|
||
|
}
|
||
|
mod.modules = append(mod.modules, newModule)
|
||
|
}
|
||
|
|
||
|
type module struct {
|
||
|
parent *module
|
||
|
name string
|
||
|
scope scope
|
||
|
provides []provide
|
||
|
invokes []invoke
|
||
|
decorators []decorator
|
||
|
modules []*module
|
||
|
app *App
|
||
|
}
|
||
|
|
||
|
// scope is a private wrapper interface for dig.Container and dig.Scope.
|
||
|
// We can consider moving this into Fx using type constraints after Go 1.20
|
||
|
// is released and 1.17 is deprecated.
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// builds the Scopes using the App's Container. Note that this happens
|
||
|
// after applyModules' are called because the App's Container needs to
|
||
|
// be built for any Scopes to be initialized, and applys' should be called
|
||
|
// before the Container can get initialized.
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
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(),
|
||
|
ModuleName: m.name,
|
||
|
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),
|
||
|
ModuleName: m.name,
|
||
|
OutputTypeNames: outputNames,
|
||
|
Err: m.app.err,
|
||
|
}
|
||
|
}
|
||
|
m.app.log.LogEvent(ev)
|
||
|
}
|
||
|
|
||
|
func (m *module) executeInvokes() error {
|
||
|
for _, m := range m.modules {
|
||
|
if err := m.executeInvokes(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, invoke := range m.invokes {
|
||
|
if err := m.executeInvoke(invoke); 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,
|
||
|
ModuleName: m.name,
|
||
|
})
|
||
|
err = runInvoke(m.scope, i)
|
||
|
m.app.log.LogEvent(&fxevent.Invoked{
|
||
|
FunctionName: fnName,
|
||
|
ModuleName: m.name,
|
||
|
Err: err,
|
||
|
Trace: fmt.Sprintf("%+v", i.Stack), // format stack trace as multi-line
|
||
|
})
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (m *module) decorate() (err error) {
|
||
|
for _, decorator := range m.decorators {
|
||
|
var info dig.DecorateInfo
|
||
|
err := runDecorator(m.scope, decorator, dig.FillDecorateInfo(&info))
|
||
|
outputNames := make([]string, len(info.Outputs))
|
||
|
for i, o := range info.Outputs {
|
||
|
outputNames[i] = o.String()
|
||
|
}
|
||
|
|
||
|
if decorator.IsReplace {
|
||
|
m.app.log.LogEvent(&fxevent.Replaced{
|
||
|
ModuleName: m.name,
|
||
|
OutputTypeNames: outputNames,
|
||
|
Err: err,
|
||
|
})
|
||
|
} else {
|
||
|
|
||
|
m.app.log.LogEvent(&fxevent.Decorated{
|
||
|
DecoratorName: fxreflect.FuncName(decorator.Target),
|
||
|
ModuleName: m.name,
|
||
|
OutputTypeNames: outputNames,
|
||
|
Err: err,
|
||
|
})
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
for _, m := range m.modules {
|
||
|
if err := m.decorate(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|