// 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" ) // Decorate specifies one or more decorator functions to an Fx application. // // # Decorator functions // // Decorator functions let users augment objects in the graph. // They can take in zero or more dependencies that must be provided to the // application with fx.Provide, and produce one or more values that can be used // by other fx.Provide and fx.Invoke calls. // // fx.Decorate(func(log *zap.Logger) *zap.Logger { // return log.Named("myapp") // }) // fx.Invoke(func(log *zap.Logger) { // log.Info("hello") // // Output: // // {"level": "info","logger":"myapp","msg":"hello"} // }) // // The following decorator accepts multiple dependencies from the graph, // augments and returns one of them. // // fx.Decorate(func(log *zap.Logger, cfg *Config) *zap.Logger { // return log.Named(cfg.Name) // }) // // Similar to fx.Provide, functions passed to fx.Decorate may optionally return // an error as their last result. // If a decorator returns a non-nil error, it will halt application startup. // // fx.Decorate(func(conn *sql.DB, cfg *Config) (*sql.DB, error) { // if err := conn.Ping(); err != nil { // return sql.Open("driver-name", cfg.FallbackDB) // } // return conn, nil // }) // // Decorators support both, fx.In and fx.Out structs, similar to fx.Provide and // fx.Invoke. // // type Params struct { // fx.In // // Client usersvc.Client `name:"readOnly"` // } // // type Result struct { // fx.Out // // Client usersvc.Client `name:"readOnly"` // } // // fx.Decorate(func(p Params) Result { // ... // }) // // Decorators can be annotated with the fx.Annotate function, but not with the // fx.Annotated type. Refer to documentation on fx.Annotate() to learn how to // use it for annotating functions. // // fx.Decorate( // fx.Annotate( // func(client usersvc.Client) usersvc.Client { // // ... // }, // fx.ParamTags(`name:"readOnly"`), // fx.ResultTags(`name:"readOnly"`), // ), // ) // // Decorators support augmenting, filtering, or replacing value groups. // To decorate a value group, expect the entire value group slice and produce // the new slice. // // type HandlerParam struct { // fx.In // // Log *zap.Logger // Handlers []Handler `group:"server" // } // // type HandlerResult struct { // fx.Out // // Handlers []Handler `group:"server" // } // // fx.Decorate(func(p HandlerParam) HandlerResult { // var r HandlerResult // for _, handler := range p.Handlers { // r.Handlers = append(r.Handlers, wrapWithLogger(p.Log, handler)) // } // return r // }), // // Decorators can not add new values to the graph, // only modify or replace existing ones. // Types returned by a decorator that are not already in the graph // will be ignored. // // # Decorator scope // // Modifications made to the Fx graph with fx.Decorate are scoped to the // deepest fx.Module inside which the decorator was specified. // // fx.Module("mymodule", // fx.Decorate(func(log *zap.Logger) *zap.Logger { // return log.Named("myapp") // }), // fx.Invoke(func(log *zap.Logger) { // log.Info("decorated logger") // // Output: // // {"level": "info","logger":"myapp","msg":"decorated logger"} // }), // ), // fx.Invoke(func(log *zap.Logger) { // log.Info("plain logger") // // Output: // // {"level": "info","msg":"plain logger"} // }), // // Decorations specified in the top-level fx.New call apply across the // application and chain with module-specific decorators. // // fx.New( // // ... // fx.Decorate(func(log *zap.Logger) *zap.Logger { // return log.With(zap.Field("service", "myservice")) // }), // // ... // fx.Invoke(func(log *zap.Logger) { // log.Info("outer decorator") // // Output: // // {"level": "info","service":"myservice","msg":"outer decorator"} // }), // // ... // fx.Module("mymodule", // fx.Decorate(func(log *zap.Logger) *zap.Logger { // return log.Named("myapp") // }), // fx.Invoke(func(log *zap.Logger) { // log.Info("inner decorator") // // Output: // // {"level": "info","logger":"myapp","service":"myservice","msg":"inner decorator"} // }), // ), // ) func Decorate(decorators ...interface{}) Option { return decorateOption{ Targets: decorators, Stack: fxreflect.CallerStack(1, 0), } } type decorateOption struct { Targets []interface{} Stack fxreflect.Stack } func (o decorateOption) apply(mod *module) { for _, target := range o.Targets { mod.decorators = append(mod.decorators, decorator{ Target: target, Stack: o.Stack, }) } } func (o decorateOption) String() string { items := make([]string, len(o.Targets)) for i, f := range o.Targets { items[i] = fxreflect.FuncName(f) } return fmt.Sprintf("fx.Decorate(%s)", strings.Join(items, ", ")) } // decorator is a single decorator used in Fx. type decorator struct { // Decorator provided to Fx. Target interface{} // Stack trace of where this provide was made. Stack fxreflect.Stack // Whether this decorator was specified via fx.Replace IsReplace bool ReplaceType reflect.Type // set only if IsReplace } func runDecorator(c container, d decorator, opts ...dig.DecorateOption) (err error) { decorator := d.Target defer func() { if err != nil { err = fmt.Errorf("fx.Decorate(%v) from:\n%+vFailed: %w", decorator, d.Stack, err) } }() switch decorator := decorator.(type) { case annotated: if dcor, derr := decorator.Build(); derr == nil { err = c.Decorate(dcor, opts...) } default: err = c.Decorate(decorator, opts...) } return }