Documentation ¶
Overview ¶
Package nject is a general purpose dependency injection framework. It provides wrapping, pruning, and indirect variable passing. It is type safe and using it requires no type assertions. There are two main injection APIs: Run and Bind. Bind is designed to be used at program initialization and does as much work as possible then rather than during main execution.
List of providers ¶
The API for nject is a list of providers (injectors) that are run in order. The final function in the list must be called. The other functions are called if their value is consumed by a later function that must be called. Here is a simple example:
func main() { nject.Run("example", context.Background, // provides context.Context log.Default, // provides *log.Logger ":80", // a constant string http.NewServeMux, // provides *http.ServeMux func(mux *http.ServeMux) http.Handler { mux.HandleFunc("/missing", http.NotFound) return mux }, http.ListenAndServe, // uses a string and http.Handler ) }
In this example, context.Background and log.Default are not invoked because their outputs are not used by the final function (http.ListenAndServe).
How to use ¶
The basic idea of nject is to assemble a Collection of providers and then use that collection to supply inputs for functions that may use some or all of the provided types.
One big win from dependency injection with nject is the ability to reshape various different functions into a single signature. For example, having a bunch of functions with different APIs all bound as http.HandlerFunc is easy.
Providers produce or consume data. The data is distinguished by its type. If you want to three different strings, then define three different types:
type myFirst string type mySecond string type myThird string
Then you can have a function that does things with the three types:
func myStringFunc(first myFirst, second mySecond) myThird { return myThird(string(first) + string(second)) }
The above function would be a valid injector or final function in a provider Collection. For example:
var result string Sequence("example sequence", func() mySecond { return "2nd" } myStringFunc, ).Run("example run", func(s myThird) { result = string(s) }, myFirst("1st")) fmt.Println(result)
This creates a sequence and executes it. Run injects a myFirst value and the sequence of providers runs: genSecond() injects a mySecond and myStringFunc() combines the myFirst and mySecond to create a myThird. Then the function given in run saves that final value. The expected output is
1st2nd
Collections ¶
Providers are grouped as into linear sequences. When building an injection chain, the providers are grouped into several sets: LITERAL, STATIC, RUN. The LITERAL and STATIC sets run once per initialization. The RUN set runs once per invocation. Providers within a set are executed in the order that they were originally specified. Providers whose outputs are not consumed are omitted unless they are marked Required().
Collections are bound with Bind(&invocationFunction, &initializationFunction). The invocationFunction is expected to be used over and over, but the initializationFunction is expected to be used less frequently. The STATIC set is re-invoked each time the initialization function is run.
The LITERAL set is just the literal values in the collection.
The STATIC set is composed of the cacheable injectors.
The RUN set if everything else.
Injectors ¶
All injectors have the following type signature:
func(input value(s)) output values(s)
None of the input or output parameters may be anonymously-typed functions. An anoymously-typed function is a function without a named type.
Injectors whose output values are not used by a downstream handler are dropped from the handler chain. They are not invoked. Injectors that have no output values are a special case and they are always retained in the handler chain.
Cached injectors ¶
In injector that is annotated as Cacheable() may promoted to the STATIC set. An injector that is annotated as MustCache() must be promoted to the STATIC set: if it cannot be promoted then the collection is deemed invalid.
An injector may not be promoted to the STATIC set if it takes as input data that comes from a provider that is not in the STATIC or LITERAL sets. For example, arguments to the invocation function, if the invoke function takes an int as one of its inputs, then no injector that takes an int as an argument may be promoted to the STATIC set.
Injectors in the STATIC set will be run exactly once per set of input values. If the inputs are consistent, then the output will be a singleton. This is true across injection chains.
If the following provider is used in multiple chains, as long as the same integer is injected, all chains will share the same pointer.
Provide("square", MustCache(func(int i) *int { j := i*i return &j }))
Memoized injectors ¶
Injectors in the STATIC set are only run for initialization. For some things, like opening a database, that may still be too often. Injectors that are marked Memoized must be promoted to the static set.
Memoized injectors are only run once per combination of inputs. Their outputs are remembered. If called enough times with different arguments, memory will be exhausted.
Memoized injectors may not have more than 90 inputs.
Memoized injectors may not have any inputs that are go maps, slices, or functions. Arrays, structs, and interfaces are okay. This requirement is recursive so a struct that that has a slice in it is not okay.
Fallible injectors ¶
Fallible injectors are special injectors that change the behavior of the injection chain if they return error. Fallible injectors in the RUN set, that return error will terminate execution of the injection chain.
A non-wrapper function that returns nject.TerminalError is a fallible injector.
func(input value(s)) (output values(s), TerminalError)
The TerminalError does not have to be the last return value. The nject package converts TerminalError objects into error objects so only the fallible injector should use TerminalError. Anything that consumes the TerminalError should do so by consuming error instead.
Fallible injectors can be in both the STATIC set and the RUN set. Their behavior is a bit different.
If a non-nil value is returned as the TerminalError from a fallible injector in the RUN set, none of the downstream providers will be called. The provider chain returns from that point with the TerminalError as a return value. Since all return values must be consumed by a middleware provider or the bound invoke function, fallible injectors must come downstream from a middleware handler that takes error as a returned value if the invoke function (function that runs a bound injection chain) does not return error. If a fallible injector returns nil for the TerminalError, the other output values are made available for downstream handlers to consume. The other output values are not considered return values and are not available to be consumed by upstream middleware handlers. The error returned by a fallible injector is not available downstream.
If a non-nil value is returned as the TerminalError from a fallible injector in the STATIC set, the rest of the STATIC set will be skipped. If there is an init function and it returns error, then the value returned by the fallible injector will be returned via init function. Unlike fallible injectors in the RUN set, the error output by a fallible injector in the STATIC set is available downstream (but only in the RUN set -- nothing else in the STATIC set will execute).
Some examples:
func staticInjector(i int, s string) int { return i+7 } func injector(r *http.Request) string { return r.FormValue("x") } func fallibleInjector(i int) nject.TerminalError { if i > 10 { return fmt.Errorf("limit exceeded") } return nil }
Wrap functions and middleware ¶
A wrap function interrupts the linear sequence of providers. It may or may invoke the remainder of the sequence that comes after it. The remainder of the sequence is provided to the wrap function as a function that it may call. The type signature of a wrap function is a function that receives an function as its first parameter. That function must be of an anonymous type:
// wrapFunction func(innerFunc, input value(s)) return value(s) // innerFunc func(output value(s)) returned value(s)
For example:
func wrapper(inner func(string) int, i int) int { j := inner(fmt.Sprintf("%d", i) return j * 2 }
When this wrappper function runs, it is responsible for invoking the rest of the provider chain. It does this by calling inner(). The parameters to inner are available as inputs to downstream providers. The value(s) returned by inner come from the return values of other wrapper functions and from the return value(s) of the final function.
Wrap functions can call inner() zero or more times.
The values returned by wrap functions must be consumed by another upstream wrap function or by the init function (if using Bind()).
Wrap functions have a small amount of runtime overhead compared to other kinds of functions: one call to reflect.MakeFunc().
Wrap functions serve the same role as middleware, but are usually easier to write.
Wrap functions that invoke inner() multiple times in parallel are are not well supported at this time and such invocations must have the wrap function decorated with Parallel().
Final functions ¶
Final functions are simply the last provider in the chain. They look like regular Go functions. Their input parameters come from other providers. Their return values (if any) must be consumed by an upstream wrapper function or by the init function (if using Bind()).
func(input value(s)) return values(s)
Wrap functions that return error should take error as a returned value so that they do not mask a downstream error. Wrap functions should not return TerminalError because they internally control if the downstream chain is called.
func GoodExample(inner func() error) error { if err := DoSomething(); err != nil { // skip remainder of injection chain return err } err := inner() return err } func BadExampleMasksDownstreamError(inner func()) error { if err := DoSomething(); err != nil { // skip remainder of injection chain return err } inner() // nil is returned even if a downsteam injector returns error return nil }
Literal values ¶
Literal values are values in the provider chain that are not functions.
Invalid provider chains ¶
Provider chains can be invalid for many reasons: inputs of a type not provided earlier in the chain; annotations that cannot be honored (eg. MustCache & Memoize); return values that are not consumed; functions that take or return functions with an anymous type other than wrapper functions; A chain that does not terminate with a function; etc. Bind() and Run() will return error when presented with an invalid provider chain.
Panics ¶
Bind() and Run() will return error rather than panic. After Bind()ing an init and invoke function, calling them will not panic unless a provider panic()s
A wrapper function can be used to catch panics and turn them into errors. When doing that, it is important to propagate any errors that are coming up the chain. If there is no guaranteed function that will return error, one can be added with Shun().
func CatchPanic(inner func() error) (err error) { defer func() { if r := recover(); r != nil { if e, ok := r.(error); ok { err = errors.Wrapf(e, "panic error from %s", string(debug.Stack())) } else { err = errors.Errorf("panic caught!\n%s\n%s", fmt.Sprint(r), string(debug.Stack())) } } }() return inner() } var ErrorOfLastResort = nject.Shun(func() error { return nil })
Chain evaluation ¶
Bind() uses a complex and somewhat expensive O(n^2) set of rules to evaluate which providers should be included in a chain and which can be dropped. The goal is to keep the ones you want and remove the ones you don't want. Bind() tries to figure this out based on the dependencies and the annotations.
MustConsume, not Desired: Only include if at least one output is transitively consumed by a Required or Desired chain element and all outputs are consumed by some other provider.
Not MustConsume, not Desired: only include if at least one output is transitively consumed by a Required or Desired provider.
Not MustConsume, Desired: Include if all inputs are available.
MustConsume, Desired: Only include if all outputs are transitively consumed by a required or Desired chain element.
When there are multiple providers of a type, Bind() tries to get it from the closest provider.
Providers that have unmet dependencies will be eliminated from the chain unless they're Required.
Best practices ¶
The remainder of this document consists of suggestions for how to use nject.
Contributions to this section would be welcome. Also links to blogs or other discussions of using nject in practice.
For tests ¶
The best practice for using nject inside a large project is to have a few common chains that everyone imports.
Most of the time, these common chains will be early in the sequence of providers. Customization of the import chains happens in many places.
This is true for services, libraries, and tests.
For tests, a wrapper that includes the standard chain makes it easier to write tests. See github.com/memsql/ntest for helper functions and more examples.
var CommonChain = nject.Sequence("common", context.Background, log.Default, things, used, in, this, project, ) func RunTest(t *testing.T, testInjectors ...any) { err := nject.Run("RunTest", t, CommonChain, nject.Sequence(t.Name(), testInjectors...)) assert.NoError(t, err, nject.DetailedError(err)) } func TestSomething(t *testing.T) { t.RunTest(t, Extra, Things, func( ctx context.Context, log *log.Logger, etc Etcetera, ) { assert.NotNil(t, ctx) }) }
Displaying errors ¶
If nject cannot bind or run a chain, it will return error. The returned error is generally very good, but it does not contain the full debugging output.
The full debugging output can be obtained with the DetailedError function. If the detailed error shows that nject has a bug, note that part of the debug output includes a regression test that can be turned into an nject issue. Remove the comments to hide the original type names.
err := nject.Run("some chain", some, injectors) if err != nil { if details := nject.DetailedError(err); details != err.Error() { log.Println("Detailed error", details) } log.Fatal(err) }
Reorder ¶
The Reorder() decorator allows injection chains to be fully or partially reordered. Reorder is currently limited to a single pass and does not know which injectors are ultimately going to be included in the final chain. It is likely that if you mark your entire chain with Reorder, you'll have unexpected results. On the other hand, Reorder provides safe and easy way to solve some common problems.
For example: providing optional options to an injected dependency.
var ThingChain = nject.Sequence("thingChain", nject.Shun(DefaultThingOptions), ThingProvider, } func DefaultThingOptions() []ThingOption { return []ThingOption{ StanardThingOption, } } func ThingProvider(options []ThingOption) *Thing { return thing.Make(options...) }
Because the default options are marked as Shun, they'll only be included if they have to be included. If a user of thingChain wants to override the options, they simply need to mark their override as Reorder. To make this extra friendly, a helper function to do the override can be provided and used.
func OverrideThingOptions(options ...ThingOption) nject.Provider { return nject.Reorder(func() []ThingOption) { return options } } nject.Run("run", ThingChain, OverrideThingOptions(thing.Option1, thing.Option2), )
Self-cleaning ¶
Recommended best practice is to have injectors shutdown the things they themselves start. They should do their own cleanup.
Inside tests, an injector can use t.Cleanup() for this.
For services, something like t.Cleanup can easily be built:
type CleanupList struct { list *[]func() error func (l CleanupList) Cleanup(f func() error) { *l.list = append(*l.list, f) } func CleaningService(inner func(CleanupList) error) (finalErr error) { list := make([]func() error, 0, 64) defer func() { for i := len(list); i >= 0; i-- { err := list[i]() if err != nil && finalErr == nil { finalErr = err } } }() return inner(CleanupList{list: &list}) } func ThingProvider(cleaningService CleanupList) *Thing { thing := things.New() thing.Start() cleaningService.Cleanup(thing.Stop) return thing }
Alternatively, any wrapper function can do it's own cleanup in a defer that it defines. Wrapper functions have a small runtime performance penalty, so if you have more than a couple of providers that need cleanup, it makes sense to include something like CleaningService.
Forcing inclusion ¶
The normal direction of forced inclusion is that an upstream provider is required because a downstream provider uses a type produced by the upstream provider.
There are times when the relationship needs to be reversed. For example, a type gets modified by a downstream injector. The simplest option is to combine the providers into one function.
Another possibility is to mark the upstream provider with MustConsume and have it produce a type that is only consumed by the downstream provider.
Lastly, the providers can be grouped with Cluster so that they'll be included or excluded as a group.
Example ¶
Example shows what gets included and what does not for several injection chains. These examples are meant to show the subtlety of what gets included and why.
package main import ( "errors" "fmt" "strings" "github.com/muir/nject" ) func main() { // This demonstrates displaying the elements of a chain using an error // returned by the final element. fmt.Println(nject.Run("empty-chain", nject.Provide("Names", func(d *nject.Debugging) error { return errors.New(strings.Join(d.NamesIncluded, ", ")) }))) // This demonstrates that wrappers will be included if they are closest // provider of a return type that is required. Names is included in // the upwards chain even though ReflectError could provide the error that // Run() wants. fmt.Println(nject.Run("overwrite", nject.Required(nject.Provide("InjectErrorDownward", func() error { return errors.New("overwrite me") })), nject.Provide("Names", func(inner func() error, d *nject.Debugging) error { inner() return errors.New(strings.Join(d.NamesIncluded, ", ")) }), nject.Provide("ReflectError", func(err error) error { return err }))) // This demonstrates that the closest provider will be chosen over one farther away. // Otherwise InInjector would be included instead of BoolInjector and IntReinjector. fmt.Println(nject.Run("multiple-choices", nject.Provide("IntInjector", func() int { return 1 }), nject.Provide("BoolInjector", func() bool { return true }), nject.Provide("IntReinjector", func(bool) int { return 2 }), nject.Provide("IntConsumer", func(i int, d *nject.Debugging) error { return errors.New(strings.Join(d.NamesIncluded, ", ")) }))) }
Output: Debugging, empty-chain invoke func, Run()error, Names Debugging, overwrite invoke func, Run()error, InjectErrorDownward, Names, ReflectError Debugging, multiple-choices invoke func, Run()error, BoolInjector, IntReinjector, IntConsumer
Example (Transaction) ¶
This example explores injecting a database handle or transaction only when they're used.
package main import ( "context" "database/sql" "errors" "fmt" "github.com/muir/nject" ) // InjectDB injects both an *sql.DB and an *sql.Tx if they're needed. // Errors from opening and closing the database can be returned // so a consumer of downstream errors is necessary. // A context.Context is used in the creation of the transaction // inject that earlier in the chain. txOptions can be nil. If a // transaction is injected, it will be automatically committed if the // returned error from downstream is nil. It will be rolled back if // the returned error is not nil. func InjectDB(driver, uri string, txOptions *sql.TxOptions) *nject.Collection { return nject.Sequence("database-sequence", driverType(driver), uriType(uri), txOptions, // We tag the db injector as MustConsume so that we don't inject // the database unless there is a consumer for it. When a wrapper // returns error, it should usually consume error too and pass // that error along, otherwise it can mask a downstream error. nject.MustConsume(nject.Provide("db", injectDB)), // We tag the tx injector as MustConsume so that we don't inject // the transaction unless there is a consumer for it. When a wrapper // returns error, it should usually consume error too and pass // that error along, otherwise it can mask a downstream error. nject.MustConsume(nject.Provide("tx", injectTx)), // Since injectTx or injectDB consumes an error, this provider // will supply that error if there is no other downstream supplier. nject.Shun(nject.Provide("fallback error", fallbackErrorSource)), ) } type ( driverType string uriType string ) func injectDB(inner func(*sql.DB) error, driver driverType, uri uriType) (finalError error) { db, err := sql.Open(string(driver), string(uri)) if err != nil { return err } defer func() { err := db.Close() if err != nil && finalError == nil { finalError = err } }() return inner(db) } func injectTx(inner func(*sql.Tx) error, ctx context.Context, db *sql.DB, opts *sql.TxOptions) (finalError error) { tx, err := db.BeginTx(ctx, opts) if err != nil { return err } defer func() { if finalError == nil { finalError = tx.Commit() if errors.Is(finalError, sql.ErrTxDone) { finalError = nil } } else { _ = tx.Rollback() } }() return inner(tx) } // This has to be nject.TerminalError instead of error so that // it gets consumed upstream instead of downstream func fallbackErrorSource() nject.TerminalError { fmt.Println("fallback error returns nil") return nil } // This example explores injecting a database // handle or transaction only when they're used. func main() { // InjectDB will want a context and will return an error upstream := func(inner func(context.Context) error) { err := inner(context.Background()) if err != nil { fmt.Println(err.Error()) } } fmt.Println("No database used...") nject.MustRun("A", upstream, InjectDB("dummy", "ignored", nil), func() { fmt.Println("final-func") }) fmt.Println("\nDatabase used...") nject.MustRun("B", upstream, InjectDB("dummy", "ignored", nil), func(db *sql.DB) error { // nolint:sqlclosecheck _, _ = db.Prepare("ignored") // database opens are lazy so this triggers the logging fmt.Println("final-func") return nil }) fmt.Println("\nTransaction used...") nject.MustRun("C", upstream, InjectDB("dummy", "ignored", nil), func(_ *sql.Tx) { fmt.Println("final-func") }) }
Output: No database used... final-func Database used... db open final-func db close Transaction used... db open tx begin fallback error returns nil final-func tx committed db close
Index ¶
- func DetailedError(err error) string
- func FillExisting(o *fillerOptions)
- func GenerateFromInjectionChain(name string, ...) generatedFromInjectionChain
- func MustBind(c *Collection, invokeFunc interface{}, initFunc interface{})deprecated
- func MustBindSimple(c *Collection, name string) func()deprecated
- func MustBindSimpleError(c *Collection, name string) func() errordeprecated
- func MustRun(name string, providers ...interface{})
- func MustSetCallback(c *Collection, binderFunction interface{})deprecated
- func ProvideRequireGap(provided []reflect.Type, required []reflect.Type) []reflect.Type
- func Run(name string, providers ...interface{}) error
- type Collection
- func (c *Collection) Append(name string, funcs ...interface{}) *Collection
- func (c *Collection) Bind(invokeFunc interface{}, initFunc interface{}) error
- func (c *Collection) Condense(treatErrorAsTerminal bool) (Provider, error)
- func (c Collection) DownFlows() ([]reflect.Type, []reflect.Type)
- func (c Collection) ForEachProvider(f func(Provider))
- func (c *Collection) MustBind(invokeFunc interface{}, initFunc interface{})
- func (c *Collection) MustBindSimple() func()
- func (c *Collection) MustBindSimpleError() func() error
- func (c *Collection) MustCondense(treatErrorAsTerminal bool) Provider
- func (c *Collection) MustSetCallback(binderFunction interface{})
- func (c *Collection) SetCallback(setCallbackFunc interface{}) error
- func (c Collection) String() string
- func (c Collection) UpFlows() ([]reflect.Type, []reflect.Type)
- type Debugging
- type FillerFuncArg
- func PostActionByName(name string, function interface{}, opts ...PostActionFuncArg) FillerFuncArg
- func PostActionByTag(tagValue string, function interface{}, opts ...PostActionFuncArg) FillerFuncArg
- func PostActionByType(function interface{}, opts ...PostActionFuncArg) FillerFuncArg
- func WithMethodCall(methodName string) FillerFuncArg
- func WithTag(tag string) FillerFuncArg
- type PostActionFuncArg
- type Provider
- func Cacheable(fn interface{}) Provider
- func ConsumptionOptional(fn interface{}) Provider
- func Curry(originalFunction interface{}, pointerToCurriedFunction interface{}) (Provider, error)
- func Desired(fn interface{}) Provider
- func InsertAfterNamed(target string, fn interface{}) Provider
- func InsertBeforeNamed(target string, fn interface{}) Provider
- func Loose(fn interface{}) Provider
- func MakeStructBuilder(model interface{}, optArgs ...FillerFuncArg) (Provider, error)
- func Memoize(fn interface{}) Provider
- func MustCache(fn interface{}) Provider
- func MustConsume(fn interface{}) Provider
- func MustCurry(originalFunction interface{}, pointerToCurriedFunction interface{}) Provider
- func MustMakeStructBuilder(model interface{}, opts ...FillerFuncArg) Provider
- func MustSaveTo(varPointers ...interface{}) Provider
- func NonFinal(fn interface{}) Provider
- func NotCacheable(fn interface{}) Provider
- func OverridesError(fn interface{}) Provider
- func Parallel(fn interface{}) Provider
- func Provide(name string, fn interface{}) Provider
- func Reorder(fn interface{}) Provider
- func ReplaceNamed(target string, fn interface{}) Provider
- func Required(fn interface{}) Provider
- func SaveTo(varPointers ...interface{}) (Provider, error)
- func Shun(fn interface{}) Provider
- func Singleton(fn interface{}) Provider
- type Reflective
- type ReflectiveArgs
- type ReflectiveInvoker
- type ReflectiveWrapper
- type TerminalError
Examples ¶
- Package
- Package (Transaction)
- Cluster
- Collection.Append
- Collection.Bind
- Collection.Bind (Passing_in_parameters)
- Collection.Condense
- Collection.DownFlows (Collection)
- Collection.DownFlows (Provider)
- Collection.ForEachProvider
- Collection.MustBindSimple
- Collection.MustBindSimpleError
- Collection.MustSetCallback
- Collection.SetCallback
- Collection.String
- Collection.UpFlows
- Curry
- GenerateFromInjectionChain
- Memoize
- MustBindSimple
- MustBindSimpleError
- MustSetCallback
- NonFinal
- NotCacheable
- PostActionByName
- PostActionByTag
- PostActionByTag (Conversion)
- PostActionByTag (WihtoutPointers)
- PostActionByTag (WithInterfaces)
- PostActionByType
- Provide
- Provide (Literal)
- Provide (Regular_injector)
- Provide (Wrapper_and_fallible_injectors)
- Reorder
- ReplaceNamed
- Run
- SaveTo
- Sequence
- Singleton
- WithMethodCall
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DetailedError ¶
DetailedError transforms errors into strings. If the error happens to be an error returned by Bind() or something that called Bind() then it will return a much more detailed error than just calling err.Error()
func FillExisting ¶
func FillExisting(o *fillerOptions)
FillExisting changes the behavior of MakeStructBuilder so that it fills fields in a struct that it receives from upstream in the provider chain rather than starting fresh with a new structure.
EXPERIMENTAL: this is currently considered experimental and could be removed in a future release. If you are using this, please open a pull request to remove this comment.
func GenerateFromInjectionChain ¶
func GenerateFromInjectionChain( name string, f func(chainBefore Collection, chainAfter Collection) (selfReplacement Provider, err error), ) generatedFromInjectionChain
GenerateFromInjectionChain creates a very special provider from a function that examines the injection chain and then returns a replacement provider. The first parameter for the function is a Collection representing all the providers that are earlier in the chain from from the new special provider; the second parameter is a Collection representing all the providers that are later in the chain from the new special provider.
Example ¶
ExampleGeneratedFromInjectionChain demonstrates how a special provider can be generated that builds types that are missing from an injection chain.
package main import ( "fmt" "reflect" "github.com/muir/nject" ) func main() { type S struct { I int } fmt.Println(nject.Run("example", func() int { return 3 }, nject.GenerateFromInjectionChain( "example", func(before nject.Collection, after nject.Collection) (nject.Provider, error) { full := before.Append("after", after) inputs, outputs := full.DownFlows() var n []interface{} for _, missing := range nject.ProvideRequireGap(outputs, inputs) { if missing.Kind() == reflect.Struct || (missing.Kind() == reflect.Ptr && missing.Elem().Kind() == reflect.Struct) { vp := reflect.New(missing) fmt.Println("Building filler for", missing) builder, err := nject.MakeStructBuilder(vp.Elem().Interface()) if err != nil { return nil, err } n = append(n, builder) } } return nject.Sequence("build missing models", n...), nil }), func(s S, sp *S) { fmt.Println(s.I, sp.I) }, )) }
Output: Building filler for nject_test.S Building filler for *nject_test.S 3 3 <nil>
func MustBind
deprecated
func MustBind(c *Collection, invokeFunc interface{}, initFunc interface{})
MustBind is a wrapper for Collection.Bind(). It panic()s if Bind() returns error.
Deprecated: use the method on Collection instead
func MustBindSimple
deprecated
func MustBindSimple(c *Collection, name string) func()
MustBindSimple binds a collection with an invoke function that takes no arguments and returns no arguments. It panic()s if Bind() returns error.
Deprecated: use the method on Collection instead
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { f := nject.MustBindSimple( nject.Sequence("example", func() int { return 7 }, func(i int) { fmt.Println(i) }, ), "bind-name") f() f() }
Output: 7 7
func MustBindSimpleError
deprecated
func MustBindSimpleError(c *Collection, name string) func() error
MustBindSimpleError binds a collection with an invoke function that takes no arguments and returns error.
Deprecated: use the method on Collection instead
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { f := nject.MustBindSimpleError( nject.Sequence("example", func() int { return 7 }, func(i int) error { fmt.Println(i) return nil }, ), "bind-name") fmt.Println(f()) }
Output: 7 <nil>
func MustRun ¶
func MustRun(name string, providers ...interface{})
MustRun is a wrapper for Run(). It panic()s if Run() returns error.
func MustSetCallback
deprecated
func MustSetCallback(c *Collection, binderFunction interface{})
MustSetCallback is a wrapper for Collection.SetCallback(). It panic()s if SetCallback() returns error.
Deprecated: use the method on Collection instead
Example ¶
SetCallback invokes a function passing a function that can be used to invoke a Collection
package main import ( "fmt" "github.com/muir/nject" ) func main() { var cb func(string) nject.MustSetCallback( nject.Sequence("example", func() int { return 3 }, func(s string, i int) { fmt.Println("got", s, i) }, ), func(f func(string)) { cb = f }) cb("foo") cb("bar") }
Output: got foo 3 got bar 3
func ProvideRequireGap ¶
ProvideRequireGap identifies types that are required but are not provided.
func Run ¶
Run is different from bind: the provider chain is run, not bound to functions.
The only return value from the final function that is captured by Run() is error. Run will return that error value. If the final function does not return error, then run will return nil if it was able to execute the collection and function. Run can return error because the final function returned error or because the provider chain was not valid.
Nothing is pre-computed with Run(): the run-time cost from nject is higher than calling an invoke function defined by Bind().
Predefined Collection objects are considered providers along with InjectItems, functions, and literal values.
Each call to Run() with unique providers may leak a small amount of memory, creating durable type maps and closures to handle memoization and singletons.
Example ¶
Run is the simplest way to use the nject framework. Run simply executes the provider chain that it is given.
package main import ( "fmt" "github.com/muir/nject" ) func main() { providerChain := nject.Sequence("example sequence", "a literal string value", func(s string) int { return len(s) }) nject.Run("example", providerChain, func(i int, s string) { fmt.Println(i, len(s)) }) }
Output: 22 22
Types ¶
type Collection ¶
type Collection struct {
// contains filtered or unexported fields
}
Collection holds a sequence of providers and sub-collections. A Collection implements the Provider interface and can be used anywhere a Provider is required.
func Cluster ¶
func Cluster(name string, providers ...interface{}) *Collection
Cluster is a variation on Sequence() with the additional behavior that all of the providers in the in the cluster will be included or excluded as a group. This doesn't apply to providers that cannot be included at all. It also downgrades providers that are in the cluster that would normally be considered desired because they don't return anything and aren't wrappers: they're no longer automatically considered desired because doing so would imply the entire Cluster is is desired.
A "Cluster" with only one member is not really a cluster and will not be treated as a cluster.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { chain := nject.Sequence("overall", func() string { return "example string" }, nject.Cluster("first-cluster", func(s string) int32 { return int32(len(s)) }, func() int64 { fmt.Println("included even though no consumer") return 0 }, func(i int32) { fmt.Println("auto-desired in 1st cluster") }, func(i int32) int64 { return int64(i) }, ), nject.Cluster("second-cluster", func(s string) uint32 { return uint32(len(s)) }, func(i uint32) { fmt.Println("auto-desired in 2nd cluster") }, func(i int64, u uint32) uint64 { return uint64(uint32(i) + u) }, ), ) _ = nject.Run("does not consume uint64", chain, func(s string) { fmt.Println("no need for data from clusters") }, ) _ = nject.Run("consumes uint64", chain, func(u uint64) { fmt.Println("got value that needed both chains -", u) }, ) }
Output: no need for data from clusters included even though no consumer auto-desired in 1st cluster auto-desired in 2nd cluster got value that needed both chains - 28
func Sequence ¶
func Sequence(name string, providers ...interface{}) *Collection
Sequence creates a Collection of providers. Each collection must have a name. The providers can be: functions, variables, literal values, Collections, *Collections, or Providers.
Functions must match one of the expected patterns.
Injectors specified here will be separated into two sets: ones that are run once per bound chain (STATIC); and ones that are run for each invocation (RUN). Memoized functions are in the STATIC chain but they only get run once per input combination. Literal values are inserted before the STATIC chain starts.
Each set will run in the order they were given here. Providers whose output is not consumed are skipped unless they are marked with Required. Providers that produce no output are always run.
Previsously created *Collection objects are considered providers along with *Provider, named functions, anonymous functions, and literal values.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { seq := nject.Sequence("example", func() string { return "foo" }, func(s string) { fmt.Println(s) }, ) nject.Run("run", seq) }
Output: foo
func (*Collection) Append ¶
func (c *Collection) Append(name string, funcs ...interface{}) *Collection
Append adds additional providers onto an existing collection to create a new collection. The additional providers may be value literals, functions, Providers, or *Collections. The original collection is not modified.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { one := nject.Sequence("first sequence", func() string { return "foo" }, func(s string) error { fmt.Println("from one,", s) // the return value means this provider isn't // automatically desired return nil }, ) two := one.Append("second sequence", nject.Sequence("third sequence", func() int { return 3 }, ), func(s string, i int) { fmt.Println("from two,", s, i) }, ) fmt.Println(nject.Run("one", one)) fmt.Println(nject.Run("two", two)) }
Output: from one, foo <nil> from two, foo 3 <nil>
func (*Collection) Bind ¶
func (c *Collection) Bind(invokeFunc interface{}, initFunc interface{}) error
Bind expects to receive two function pointers for functions that are not yet defined. Bind defines the functions. The first function is called to invoke the Collection of providers.
The inputs to the invoke function are passed into the provider chain. The value returned from the invoke function comes from the values returned by the provider chain (from middleware and from the final func).
The second function is optional. It is called to initialize the provider chain. Once initialized, any further calls to the initialize function are ignored.
The inputs to the initialization function are injected into the head of the provider chain. The static portion of the provider chain will run once. The values returned from the initialization function come from the values available after the static portion of the provider chain runs. For example, if the static portion of an injection chain consists of:
func(int) string { ... } func(string) int64 { ... }
Then the return value from the initialization could include int, int64, and string but no other types.
Bind pre-computes as much as possible so that the invokeFunc is fast.
Each call to Bind() with unique providers may leak a small amount of memory, creating durable type maps and closures to handle memoization and singletons. Calls to the invokeFunc do not leak memory except where there are new inputs to providers marked Memoize().
Example ¶
Bind does as much work before invoke as possible.
package main import ( "fmt" "github.com/muir/nject" ) func main() { providerChain := nject.Sequence("example sequence", func(s string) int { return len(s) }, func(i int, s string) { fmt.Println(s, i) }) var aInit func(string) var aInvoke func() providerChain.Bind(&aInvoke, &aInit) aInit("string comes from init") aInit("ignored since invoke is done") aInvoke() aInvoke() var bInvoke func(string) providerChain.Bind(&bInvoke, nil) bInvoke("string comes from invoke") bInvoke("not a constant") }
Output: string comes from init 22 string comes from init 22 string comes from invoke 24 not a constant 14
Example (Passing_in_parameters) ¶
Parameters can be passed to both the init and then invoke functions when using Bind.
package main import ( "fmt" "github.com/muir/nject" ) func main() { chain := nject.Sequence("example", nject.Provide("static-injector", // This will be a static injector because its input // will come from the bind init function func(s string) int { return len(s) }), nject.Provide("regular-injector", // This will be a regular injector because its input // will come from the bind invoke function func(i int32) int64 { return int64(i) }), nject.Provide("final-injector", // This will be the last injector in the chain and thus // is the final injector and it must be included func(i int64, j int) int32 { fmt.Println(i, j) return int32(i) + int32(j) }), ) var initFunc func(string) var invokeFunc func(int32) int32 fmt.Println(chain.Bind(&invokeFunc, &initFunc)) initFunc("example thirty-seven character string") fmt.Println(invokeFunc(10)) }
Output: <nil> 10 37 47
func (*Collection) Condense ¶ added in v1.1.0
func (c *Collection) Condense(treatErrorAsTerminal bool) (Provider, error)
Condense transforms a collection into a single provider. The inputs to the provider are what's required for the last function in the Collection to be invoked given the rest of the Collection.
At this time, the last function in the collection may not be a wrap function. Wrap functions within the condensed collection only wrap the rest of the functions within the condensed collection.
All types returned by the last function in the collection or or returned by wrap functions are returned by the condensed provider.
The condensed provider is bound with Collection.Bind() at the time that Condense() is called.
If treatErrorAsTerminal is true then a returned error will be treated as a TerminalError. Otherwise it is treated as a a regular error being provided into the downward chain.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { var counter int one := nject.Required(nject.Sequence("one", func() int { counter++; return counter }, func(b bool) string { return map[bool]string{ true: "t", false: "f", }[b] }, func(s string, i int) string { return fmt.Sprintf("%s-%d", s, i) }).MustCondense(false)) fmt.Println(nject.Run("t", func() bool { return true }, one, func(s string) { fmt.Println(s) }, func() bool { return false }, one, func(s string) { fmt.Println(s) }, )) }
Output: t-1 f-2 <nil>
func (Collection) DownFlows ¶
func (c Collection) DownFlows() ([]reflect.Type, []reflect.Type)
DownFlows provides the net unresolved flows down the injection chain. If a type is used both as input and as output for the same provider, then that type counts as an input only.
Example (Collection) ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { sequence := nject.Sequence("two providers", func(_ int, _ int64) float32 { return 0 }, func(_ int, _ string) float64 { return 0 }, ) inputs, outputs := sequence.DownFlows() fmt.Println("inputs", inputs) fmt.Println("outputs", outputs) }
Output: inputs [int int64 string] outputs [float32 float64]
Example (Provider) ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { sequence := nject.Sequence("one provider", func(_ int, _ string) float64 { return 0 }) inputs, outputs := sequence.DownFlows() fmt.Println("inputs", inputs) fmt.Println("outputs", outputs) }
Output: inputs [int string] outputs [float64]
func (Collection) ForEachProvider ¶
func (c Collection) ForEachProvider(f func(Provider))
ForEachProvider iterates over the Providers within a Collection invoking a function.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { seq := nject.Sequence("example", func() int { return 10 }, func(_ int, _ string) {}, ) seq.ForEachProvider(func(p nject.Provider) { fmt.Println(p.DownFlows()) }) }
Output: [] [int] [int string] []
func (*Collection) MustBind ¶
func (c *Collection) MustBind(invokeFunc interface{}, initFunc interface{})
MustBind is a wrapper for Collection.Bind(). It panic()s if Bind() returns error.
func (*Collection) MustBindSimple ¶
func (c *Collection) MustBindSimple() func()
MustBindSimple binds a collection with an invoke function that takes no arguments and returns no arguments. It panic()s if Bind() returns error.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { f := nject.Sequence("example", func() int { return 7 }, func(i int) { fmt.Println(i) }, ).MustBindSimple() f() f() }
Output: 7 7
func (*Collection) MustBindSimpleError ¶
func (c *Collection) MustBindSimpleError() func() error
MustBindSimpleError binds a collection with an invoke function that takes no arguments and returns no arguments. It panic()s if Bind() returns error.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { f := nject.Sequence("example", func() int { return 7 }, func(i int) error { fmt.Println(i) return nil }, ).MustBindSimpleError() fmt.Println(f()) }
Output: 7 <nil>
func (*Collection) MustCondense ¶ added in v1.1.0
func (c *Collection) MustCondense(treatErrorAsTerminal bool) Provider
MustCondense panics if Condense fails
func (*Collection) MustSetCallback ¶
func (c *Collection) MustSetCallback(binderFunction interface{})
MustSetCallback is a wrapper for Collection.SetCallback(). It panic()s if SetCallback() returns error.
Example ¶
SetCallback invokes a function passing a function that can be used to invoke a Collection
package main import ( "fmt" "github.com/muir/nject" ) func main() { var cb func(string) nject.Sequence("example", func() int { return 3 }, func(s string, i int) { fmt.Println("got", s, i) }, ).SetCallback(func(f func(string)) { cb = f }) cb("foo") cb("bar") }
Output: got foo 3 got bar 3
func (*Collection) SetCallback ¶
func (c *Collection) SetCallback(setCallbackFunc interface{}) error
SetCallback expects to receive a function as an argument. SetCallback() will call that function. That function in turn should take one or two functions as arguments. The first argument must be an invoke function (see Bind). The second argument (if present) must be an init function. The invoke func (and the init func if present) will be created by SetCallback() and passed to the function SetCallback calls.
If there is an init function, it must be called once before the invoke function is ever called. Calling the invoke function will invoke the the sequence of providers.
Whatever arguments the invoke and init functions take will be passed into the chain. Whatever values the invoke function returns must be produced by the injection chain.
Example ¶
SetCallback invokes a function passing a function that can be used to invoke a Collection
package main import ( "fmt" "github.com/muir/nject" ) func main() { var cb func(string) fmt.Println(nject.Sequence("example", func() int { return 3 }, func(s string, i int) { fmt.Println("got", s, i) }, ).SetCallback(func(f func(string)) { cb = f })) cb("foo") cb("bar") }
Output: <nil> got foo 3 got bar 3
func (Collection) String ¶ added in v0.2.1
func (c Collection) String() string
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { one := nject.Sequence("sequence", func() string { return "foo" }, func(s string) error { fmt.Println("from one,", s) // the return value means this provider isn't // automatically desired return nil }, ) fmt.Println(one) }
Output: sequence: func() string func(string) error
func (Collection) UpFlows ¶
func (c Collection) UpFlows() ([]reflect.Type, []reflect.Type)
UpFlows provides the net unresolved flows up the injection chain. If a type is used both as value it consumes as a return value and also as a value that it in turn returns, then the up flow for that provider will be counted only by what it consumes.
Providers that return TerminalError are a special case and count as producing error.
Example ¶
package main import ( "fmt" "reflect" "github.com/muir/nject" ) func main() { var errorType = reflect.TypeOf((*error)(nil)).Elem() errorIsReturned := func(c nject.Provider) bool { _, produce := c.UpFlows() for _, t := range produce { if t == errorType { return true } } return false } collection1 := nject.Sequence("one", func() string { return "yah" }, func(s string) nject.TerminalError { if s == "yah" { return fmt.Errorf("oops") } return nil }, func(s string) { fmt.Println(s) }, ) collection2 := nject.Sequence("two", func() string { return "yah" }, func(s string) { fmt.Println(s) }, ) collection3 := nject.Sequence("three", func(inner func() string) error { s := inner() if s == "foo" { return fmt.Errorf("not wanting foo") } return nil }, func() string { return "foo" }, ) fmt.Println("collection1 returns error?", errorIsReturned(collection1)) fmt.Println("collection2 returns error?", errorIsReturned(collection2)) fmt.Println("collection3 returns error?", errorIsReturned(collection3)) }
Output: collection1 returns error? true collection2 returns error? false collection3 returns error? true
type Debugging ¶
type Debugging struct { // Included is a list of the providers included in the chain. // // The format is: // "${groupName} ${className} ${providerNameShape}" Included []string // NamesIncluded is a list of the providers included in the chain. // The format is: // "${providerName} NamesIncluded []string // IncludeExclude is a list of all of the providers supplied to // create the chain. Why each was included or not explained. // "INCLUDED ${groupName} ${className} ${providerNameShape} BECAUSE ${whyProviderWasInclude}" // "EXCLUDED ${groupName} ${className} ${providerNameShape} BECAUSE ${whyProviderWasExcluded}" IncludeExclude []string // Trace is an nject internal debugging trace that details the // decision process to decide which providers are included in the // chain. Trace string // Reproduce is a Go source string that attempts to somewhat anonymize // a provider chain as a unit test. This output is nearly runnable // code. It may need a bit of customization to fully capture a situation. Reproduce string // Outer is only present within chains generated with Branch(). It is a reference // to the Debugging from the main (or outer) injection chain Outer *Debugging }
Debugging is provided to help diagnose injection issues. *Debugging is injected into every chain that consumes it. Injecting debugging into any change can slow down the processing of all other chains because debugging is controlled with a global.
type FillerFuncArg ¶
type FillerFuncArg func(*fillerOptions)
FillerFuncArg is a functional argument for MakeStructBuilder
func PostActionByName ¶
func PostActionByName(name string, function interface{}, opts ...PostActionFuncArg) FillerFuncArg
PostActionByName arranges to call a function passing in the field that has a matching name. PostActionByName happens before PostActionByType and after PostActionByTag calls.
EXPERIMENTAL: this is currently considered experimental and could be removed in a future release. If you are using this, please open a pull request to remove this comment.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { type S struct { I int32 J int32 } fmt.Println(nject.Run("example", func() int32 { return 10 }, func() *[]int { var x []int return &x }, nject.MustMakeStructBuilder(S{}, nject.PostActionByName("I", func(i int, a *[]int) { *a = append(*a, i+1) }), nject.PostActionByName("J", func(i int64, a *[]int) { *a = append(*a, int(i)-1) }), ), func(_ S, a *[]int) { fmt.Println(*a) }, )) }
Output: [11 9] <nil>
func PostActionByTag ¶
func PostActionByTag(tagValue string, function interface{}, opts ...PostActionFuncArg) FillerFuncArg
PostActionByTag establishes a tag value that indicates that after the struct is built or filled, a function should be called passing a pointer to the tagged field to the function. The function must take as an input parameter a pointer to the type of the field or it must take as an input paraemter an interface type that the field implements. interface{} is allowed. This function will be added to the injection chain after the function that builds or fills the struct. If there is also a WithMethodCall, this function will run before that.
EXPERIMENTAL: this is currently considered experimental and could be removed in a future release. If you are using this, please open a pull request to remove this comment.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { type S struct { I int `nject:"square-me"` } nject.Run("example", func() int { return 4 }, nject.MustMakeStructBuilder(&S{}, nject.PostActionByTag("square-me", func(i *int) { *i *= *i }, nject.WithFill(true))), func(s *S) { fmt.Println(s.I) }, ) }
Output: 16
Example (Conversion) ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { type S struct { I int32 `nject:"rollup"` J int32 `nject:"rolldown"` } fmt.Println(nject.Run("example", func() int32 { return 10 }, func() *[]int { var x []int return &x }, nject.MustMakeStructBuilder(S{}, nject.PostActionByTag("rollup", func(i int, a *[]int) { *a = append(*a, i+1) }), nject.PostActionByTag("rolldown", func(i int64, a *[]int) { *a = append(*a, int(i)-1) }), ), func(_ S, a *[]int) { fmt.Println(*a) }, )) }
Output: [11 9] <nil>
Example (WihtoutPointers) ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { type S struct { I int `nject:"square-me"` } nject.Run("example", func() int { return 4 }, nject.MustMakeStructBuilder(S{}, nject.PostActionByTag("square-me", func(i int) { fmt.Println(i * i) })), func(s S) { fmt.Println(s.I) }, ) }
Output: 16 4
Example (WithInterfaces) ¶
package main import ( "fmt" "github.com/muir/nject" ) type Causer interface { Unwrap() error Error() string } type MyError struct { err error } func (err MyError) Error() string { return "MY: " + err.err.Error() } func (err MyError) Unwrap() error { return err.err } var _ Causer = MyError{} func main() { type S struct { Error Causer `nject:"print-error,print-cause"` } fmt.Println(nject.Run("example", func() error { return fmt.Errorf("an injected error") }, func(err error) Causer { return MyError{err: err} }, nject.MustMakeStructBuilder(S{}, nject.PostActionByTag("print-error", func(err error) { fmt.Println(err) }), nject.PostActionByTag("print-cause", func(err Causer) { fmt.Println("Cause:", err.Unwrap()) }), ), func(s S) { fmt.Println("Done") }, )) }
Output: MY: an injected error Cause: an injected error Done <nil>
func PostActionByType ¶
func PostActionByType(function interface{}, opts ...PostActionFuncArg) FillerFuncArg
PostActionByType arranges to call a function for every field in struct that is being filled where the type of the field in the struct exactly matches the first input parameter of the provided function. PostActionByType calls are made after PostActionByTag calls, but before WithMethodCall invocations.
If there is no match to the type of the function, then the function is not invoked.
EXPERIMENTAL: this is currently considered experimental and could be removed in a future release. If you are using this, please open a pull request to remove this comment.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { type S struct { I int32 J int64 } fmt.Println(nject.Run("example", func() int32 { return 10 }, func() int64 { return 20 }, func() *[]int { var x []int return &x }, nject.MustMakeStructBuilder(&S{}, nject.PostActionByType(func(i int32, a *[]int) { *a = append(*a, int(i)) }, nject.WithFill(true)), nject.PostActionByType(func(i *int32, a *[]int) { *i += 5 }, nject.WithFill(true)), ), func(s *S, a *[]int) { fmt.Println(*a, s.I, s.J) }, )) }
Output: [15] 15 20 <nil>
func WithMethodCall ¶
func WithMethodCall(methodName string) FillerFuncArg
WithMethodCall looks up a method on the struct being filled or built and adds a method invocation to the dependency chain. The method can be any kind of function provider (the last function, a wrapper, etc). If there is no method of that name, then MakeStructBuilder will return an error.
EXPERIMENTAL: this is currently considered experimental and could be removed in a future release. If you are using this, please open a pull request to remove this comment.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) type S struct { I int } func (s *S) Square() { s.I *= s.I } func (s S) Print() { fmt.Println(s.I) } func main() { nject.Run("example", func() int { return 4 }, nject.MustMakeStructBuilder(&S{}, nject.WithMethodCall("Square"), nject.WithMethodCall("Print")), func(s *S) { fmt.Println("end") }, ) }
Output: 16 end
func WithTag ¶
func WithTag(tag string) FillerFuncArg
WithTag sets the struct tag to use for per-struct-field directives in MakeStructBuilder. The default tag is "nject"
EXPERIMENTAL: this is currently considered experimental and could be removed in a future release. If you are using this, please open a pull request to remove this comment.
type PostActionFuncArg ¶
type PostActionFuncArg func(*postActionOption)
PostActionFuncArg are functional arguments to PostActionByTag, PostActionByName, and PostActionByType.
func MatchToOpenInterface ¶
func MatchToOpenInterface(b bool) PostActionFuncArg
MatchToOpenInterface requires that the post action function have exactly one open interface type (interface{}) in its arguments list. A pointer to the field will be passed to the interface parameter.
EXPERIMENTAL: this is currently considered experimental and could be removed in a future release. If you are using this, please open a pull request to remove this comment.
func WithFill ¶
func WithFill(b bool) PostActionFuncArg
WithFill overrides the default behaviors of PostActionByType, PostActionByName, and PostActionByTag with respect to the field being automatically filled. By default, if there is a post-action that that receives a pointer to the field, then the field will not be filled from the injection chain.
EXPERIMENTAL: this is currently considered experimental and could be removed in a future release. If you are using this, please open a pull request to remove this comment.
type Provider ¶
type Provider interface { String() string // For single providers, DownFlows includes all inputs and // all outputs. For collections, Downflows only includes // the net inputs and net outputs. DownFlows() (inputs []reflect.Type, outputs []reflect.Type) // For single providers, Upflows includes all consumes and // all returns. For collections, Upflows only includes // the net consumes and returns. // // Providers that return TerminalError are a special case and count as // producing error. UpFlows() (consume []reflect.Type, produce []reflect.Type) // contains filtered or unexported methods }
Provider is an individual injector (function, constant, or wrapper). Functions that take injectors, take interface{}. Functions that return invjectors return Provider so that methods can be attached.
func Cacheable ¶
func Cacheable(fn interface{}) Provider
Cacheable creates an inject item and annotates it as allowed to be in the STATIC chain. Without this annotation, MustCache, or Memoize, a provider will be in the RUN chain.
When used on an existing Provider, it creates an annotated copy of that provider.
func ConsumptionOptional ¶
func ConsumptionOptional(fn interface{}) Provider
ConsumptionOptional creates a new provider and annotates it as allowed to have some of its return values ignored. Without this annotation, a wrap function will not be included if some of its return values are not consumed.
In the downward direction, optional consumption is the default.
When used on an existing Provider, it creates an annotated copy of that provider.
func Curry ¶ added in v1.2.0
Curry generates a Required Provider that prefills arguments to a function to create a new function that needs fewer args.
Only arguments with a unique (to the function) type can be curried.
The original function and the curried function must have the same outputs.
The first curried input may not be a function.
EXPERIMENTAL: This is currently considered experimental and could be removed or moved to another package. If you're using this, open a pull request to remove this comment.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { lotsOfUnchangingArgs := func(s string, i int, u uint) string { return fmt.Sprintf("%s-%d-%d", s, i, u) } var shorthand func(i int) string fmt.Println(nject.Run("example", func() string { return "foo" }, func() uint { return 33 }, nject.MustCurry(lotsOfUnchangingArgs, &shorthand), func() { fmt.Println("actual injection goal") }, )) fmt.Println(shorthand(10)) }
Output: actual injection goal <nil> foo-10-33
func Desired ¶
func Desired(fn interface{}) Provider
Desired creates a new provider and annotates it as desired: it will be included in the provider chain unless doing so creates an un-met dependency.
Injectors and wrappers that have no outputs are automatically considered desired.
When used on an existing Provider, it creates an annotated copy of that provider.
func InsertAfterNamed ¶ added in v1.6.0
InsertAfterNamed will edit the set of injectors, inserting the provided injector after the target injector, which is identified by the name it was given with Provide(). That injector can be a Collection. This re-arrangement happens very early in the injection chain processing, before Reorder or injector selection. If target does not exist, the injection chain is deemed invalid.
func InsertBeforeNamed ¶ added in v1.6.0
InsertBeforeNamed will edit the set of injectors, inserting the provided injector before the target injector, which is identified by the name it was given with Provide(). That injector can be a Collection. This re-arrangement happens very early in the injection chain processing, before Reorder or injector selection. If target does not exist, the injection chain is deemed invalid.
func Loose ¶
func Loose(fn interface{}) Provider
Loose annotates a wrap function to indicate that when trying to match types against the outputs and return values from this provider, an in-exact match is acceptable. This matters when inputs and returned values are specified as interfaces. With the Loose annotation, an interface can be matched to the outputs and/or return values of this provider if the output/return value implements the interface.
By default, an exact match of types is required for all providers.
func MakeStructBuilder ¶
func MakeStructBuilder(model interface{}, optArgs ...FillerFuncArg) (Provider, error)
MakeStructBuilder generates a Provider that wants to receive as arguments all of the fields of the struct and returns the struct as what it provides.
The input model must be a struct: if not MakeStructFiller will panic. Model may be a pointer to a struct or a struct. Unexported fields are always ignored. Passing something other than a struct or pointer to a struct to MakeStructBuilder results is an error. Unknown tag values is an error.
Struct tags can be used to control the behavior: the argument controls the name of the struct tag used.
The following struct tags are pre-defined. User-created struct tags (created with PostActionByTag) may not uses these names:
"whole" & "blob": indicate that an embedded struct should be filled as a blob rather than field-by-field.
"field" & "fields": indicates that an embedded struct should be filled field-by-field. This is the default and the tag exists for clarity.
"-" & "skip": the field should not be filled and it should should ignore a PostActionByType and PostActionByName matches. PostActionByTag would still apply.
"nofill": the field should not be filled, but all PostActions still apply. "nofill" overrides other behviors including defaults set with post-actions.
"fill": normally if there is a PostActionByTag match, then the field will not be filled from the provider chain. "fill" overrides that behavior. "fill" overrides other behaviors including defaults set with post-actions.
If you just want to provide a value variable, use FillVars() instead.
func Memoize ¶
func Memoize(fn interface{}) Provider
Memoize creates a new InjectItem that is tagged as Cacheable further annotated so that it only executes once per input parameter values combination. This cache is global among all Sequences. Memoize can only be used on functions whose inputs are valid map keys (interfaces, arrays (not slices), structs, pointers, and primitive types). It is further restrict that it cannot handle private (not exported) fields inside structs.
Memoized providers will remember every combination of imputs they have ever seen. This can exhaust all memory.
By default, Memozied providers are Cacheable, but that doesn't force the provider into the STATIC set where it runs infrequently. Combine Memoize with MustCache to make sure that Memoize is actually in the STATIC set where it probably won't exhaust all memory.
Use NotCacheable to exclude Memoized providers from the STATIC set. Remember: they'll remember every combination of inputs.
When used on an existing Provider, it creates an annotated copy of that provider.
As long as consistent injection chains are used Memoize + MustCache can guarantee singletons.
Example ¶
Memoize implies Chacheable. To make sure that Memoize can actually function as desired, also mark functions with MustCache. With the same inputs, cached answers are always used. The cache lookup examines the values passed, but does not do a deep inspection.
package main import ( "fmt" "github.com/muir/nject" ) func main() { type aStruct struct { ValueInStruct int } structProvider := nject.Memoize(func(ip *int, i int) *aStruct { return &aStruct{ ValueInStruct: i * *ip, } }) exampleInt := 7 ip := &exampleInt _ = nject.Run("chain1", 2, ip, structProvider, func(s *aStruct) { fmt.Println("first input", s.ValueInStruct, "value set to 22") s.ValueInStruct = 22 }, ) _ = nject.Run("chain2", 3, ip, structProvider, func(s *aStruct) { fmt.Println("different inputs", s.ValueInStruct) }, ) exampleInt = 33 _ = nject.Run("chain3", 2, ip, structProvider, func(s *aStruct) { fmt.Println("same object as first", s.ValueInStruct) }, ) }
Output: first input 14 value set to 22 different inputs 21 same object as first 22
func MustCache ¶
func MustCache(fn interface{}) Provider
MustCache creates an Inject item and annotates it as required to be in the STATIC set. If it cannot be placed in the STATIC set then any collection that includes it is invalid.
func MustConsume ¶
func MustConsume(fn interface{}) Provider
MustConsume creates a new provider and annotates it as needing to have all of its output values consumed. If any of its output values cannot be consumed then the provider will be excluded from the chain even if that renders the chain invalid.
A that is received by a provider and then provided by that same provider is not considered to have been consumed by that provider.
For example:
// All outputs of A must be consumed Provide("A", MustConsume(func() string) { return "" } ), // Since B takes a string and provides a string it // does not count as suming the string that A provided. Provide("B", func(string) string { return "" }), // Since C takes a string but does not provide one, it // counts as consuming the string that A provided. Provide("C", func(string) int { return 0 }),
MustConsume works only in the downward direction of the provider chain. In the upward direction (return values) all values must be consumed.
When used on an existing Provider, it creates an annotated copy of that provider.
func MustCurry ¶ added in v1.2.0
func MustCurry(originalFunction interface{}, pointerToCurriedFunction interface{}) Provider
MustCurry calls Curry and panics if Curry returns error
func MustMakeStructBuilder ¶
func MustMakeStructBuilder(model interface{}, opts ...FillerFuncArg) Provider
MustMakeStructBuilder wraps a panic around failed MakeStructBuilder calls
EXPERIMENTAL: this is currently considered experimental and could be removed in a future release. If you are using this, please open a pull request to remove this comment.
func MustSaveTo ¶ added in v1.1.0
func MustSaveTo(varPointers ...interface{}) Provider
MustSaveTo calls FillVars and panics if FillVars returns an error
EXPERIMENTAL: This is currently considered experimental and could be removed or moved to another package. If you're using this, open a pull request to remove this comment.
func NonFinal ¶
func NonFinal(fn interface{}) Provider
NonFinal annotates a provider to say that it shouldn't be considered the final provider in a list of providers. This is to make it possible to insert a provider into a list of providers late in the chain without actually being the final provider. It's easy to insert a final at the start of the chain -- you simply list it first. It's easy to insert a final provider. Without NonFinal, it's hard or impossible to insert a provider very late in the chain. If NonFinal providers are invoked, they will be called before the final provider.
Example ¶
This demonstrates the use of NonFinal. NonFinal is useful when manipulating lists of providers.
package main import ( "fmt" "github.com/muir/nject" ) func main() { seq := nject.Sequence("example", func() string { return "some string" }, func(i int, s string) { fmt.Println("final", i, s) }, ) fmt.Println(nject.Run("almost incomplete", seq, nject.NonFinal(func() int { return 20 }), )) }
Output: final 20 some string <nil>
func NotCacheable ¶
func NotCacheable(fn interface{}) Provider
NotCacheable creates an inject item and annotates it as not allowed to be in the STATIC chain. With this annotation, Cacheable is ignored and MustCache causes an invalid chain.
When used on an existing Provider, it creates an annotated copy of that provider.
Example ¶
ExampleNotCacheable is a function to demonstrate the use of NotCacheable and MustConsume.
package main import ( "database/sql" "github.com/muir/nject" ) type ( driverName string dataSourceName string ) // openDBErrorReturnRequired is a provider that opens a database. Surface it seems // fine but it has a problem: what if nothing below it returns error? // nolint:deadcode,unused func openDBErrorReturnRequired(inner func(*sql.DB) error, driver driverName, name dataSourceName) error { db, err := sql.Open(string(driver), string(name)) if err != nil { return err } defer db.Close() return inner(db) } // openDBCollection is a collection of providers that open a database but do not // assume that a something farther down the chain will return error. Since this collection // may be used nievely in a context where someone is trying to cache things, // NotCacheable is used to make sure that we do not cache the open. // We use MustConsume and a private type on the open to make sure that if the open happens, // the close will happen too. type mustCloseDB bool // private type var openDBCollection = nject.Sequence("open-database", nject.NotCacheable(nject.MustConsume( func(driver driverName, name dataSourceName) (*sql.DB, mustCloseDB, nject.TerminalError) { db, err := sql.Open(string(driver), string(name)) if err != nil { return nil, false, err } return db, false, nil })), func(inner func(*sql.DB), db *sql.DB, _ mustCloseDB) { defer db.Close() inner(db) }, ) // ExampleNotCacheable is a function to demonstrate the use of NotCacheable and // MustConsume. func main() { // If someone tries to make things faster by marking everything as Cacheable, // the NotCacheable in openDBCollection() will prevent an inappropriate move to the // static chain of the database open. _ = nject.Cacheable(nject.Sequence("big collection", // Many providers driverName("postgres"), dataSourceName("postgresql://username:password@host:port/databasename"), openDBCollection, // Many other providers here )) }
Output:
func OverridesError ¶ added in v0.5.0
func OverridesError(fn interface{}) Provider
OverridesError marks a provider that is okay for that provider to override error returns. Without this decorator, a wrapper that returns error but does not expect to receive an error will cause the injection chain compilation to fail.
A common mistake is to have an wrapper that accidentally returns error. It looks like this:
func AutoCloseThing(inner func(someType), param anotherType) error { thing, err := getThing(param) if err != nil { return err } defer thing.Close() inner(thing) return nil }
The above function has two problems. The big problem is that it will override any returned errors coming up from below in the call chain by returning nil. The fix for this is to have the inner function return error. If you aren't sure there will be something below that will definitely return error, then you can inject something to provide a nil error. Put the following at the end of the sequence:
nject.Shun(nject.NotFinal(func () error { return nil }))
The second issue is that thing.Close() probably returns error. A correct wrapper for this looks like this:
func AutoCloseThing(inner func(someType) error, param anotherType) (err error) { var thing someType thing, err = getThing(param) if err != nil { return err } defer func() { e := thing.Close() if err == nil && e != nil { err = e } }() return inner(thing) }
func Parallel ¶ added in v1.5.0
func Parallel(fn interface{}) Provider
Parallel annotates a wrap function to indicate that the inner function may be invoked in parallel.
At the current time, support for this is very limited. Returned values cannot be propagated across such a call and the resulting lack of initialization can cause a panic.
func Provide ¶
Provide wraps an individual provider. It allows the provider to be named. The return value is chainable with with annotations like Cacheable() and Required(). It can be included in a collection. When providers are not named, they get their name from their position in their collection combined with the name of the collection they are in.
When used on an existing Provider, it creates an annotated copy of that provider.
Example ¶
Provide does one job: it names an otherwise anonymous function so that it easier to identify if there is an error creating an injection chain.
package main import ( "fmt" "github.com/muir/nject" ) func main() { fmt.Println(nject.Run("failure1", func(s string) int { return 4 }, )) fmt.Println(nject.Run("failure2", nject.Provide("create-int", func(s string) int { return 4 }), )) }
Output: final-func: failure1(0) [func(string) int]: required but has no match for its input parameter string final-func: create-int [func(string) int]: required but has no match for its input parameter string
Example (Literal) ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { fmt.Println(nject.Run("literals", nject.Provide("an int", 7), "I am a literal string", // naked literal work too nject.Provide("I-am-a-final-func", func(s string, i int) { fmt.Println("final:", s, i) }), )) }
Output: final: I am a literal string 7 <nil>
Example (Regular_injector) ¶
package main import ( "fmt" "strconv" "github.com/muir/nject" ) func main() { fmt.Println(nject.Run("regular", func() int { return 7 }, nject.Provide("convert-int-to-string", func(i int) string { return strconv.Itoa(i) }, ), func(s string) { fmt.Println(s) }, )) }
Output: 7 <nil>
Example (Wrapper_and_fallible_injectors) ¶
This demonstrates multiple types of injectors including a wrapper and a fallible injector
package main import ( "fmt" "github.com/muir/nject" ) func main() { shouldFail := true seq := nject.Sequence("fallible", nject.Provide("example-wrapper", func(inner func() (string, error)) { s, err := inner() fmt.Println("string:", s, "error:", err) }), nject.Provide("example-injector", func() bool { return shouldFail }), nject.Provide("example-fallible-injector", func(b bool) (string, nject.TerminalError) { if b { return "", fmt.Errorf("oops, failing") } return "example", nil }), nject.Provide("example-final-injector", func(s string) string { return "final: " + s }), ) fmt.Println(nject.Run("failure", seq)) shouldFail = false fmt.Println(nject.Run("success", seq)) }
Output: string: error: oops, failing oops, failing string: final: example error: <nil> <nil>
func Reorder ¶ added in v0.6.0
func Reorder(fn interface{}) Provider
Reorder annotates a provider to say that its position in the injection chain is not fixed. Such a provider will be placed after it's inputs are available and before it's outputs are consumed.
If there are multiple pass-through providers (that is to say, ones that both consume and provide the same type) that pass through the same type, then the ordering among these re-orderable providers will be in their original order with respect to each other.
When reordering, only exact type matches are considered. Reorder does not play well with Loose().
Functions marked reorder are currently inelligible for the STATIC set.
Note: reordering will happen too late for UpFlows(), DownFlows(), and GenerateFromInjectionChain() to correctly capture the final shape.
Reorder should be considered experimental in the sense that the rules for placement of such providers are likely to be adjusted as feedback arrives.
Example ¶
This demonstrates how it to have a default that gets overridden by by later inputs using Reorder
package main import ( "fmt" "github.com/muir/nject" ) func main() { type string2 string seq1 := nject.Sequence("example", nject.Shun(func() string { fmt.Println("fallback default included") return "fallback default" }), func(s string) string2 { return "<" + string2(s) + ">" }, ) seq2 := nject.Sequence("later inputs", // for this to work, it must be reordered to be in front // of the string->string2 provider nject.Reorder(func() string { return "override value" }), ) fmt.Println(nject.Run("combination", seq1, seq2, func(s string2) { fmt.Println(s) }, )) }
Output: <override value> <nil>
func ReplaceNamed ¶ added in v1.6.0
ReplaceNamed will edit the set of injectors, replacing target injector, identified by the name it was given with Provide(), with the injector provided here. This replacement happens very early in the injection chain processing, before Reorder or injector selection. If target does not exist, the injection chain is deemed invalid.
Example ¶
This demonstrates how it to have a default that gets overridden by by later inputs using ReplaceNamed
package main import ( "fmt" "github.com/muir/nject" ) func main() { type string2 string seq1 := nject.Sequence("example", nject.Provide("default-string", func() string { fmt.Println("fallback default included") return "fallback default" }), func(s string) string2 { return "<" + string2(s) + ">" }, ) seq2 := nject.Sequence("later inputs", nject.ReplaceNamed("default-string", func() string { return "override value" }), ) fmt.Println(nject.Run("combination", seq1, seq2, func(s string2) { fmt.Println(s) }, )) }
Output: <override value> <nil>
func Required ¶
func Required(fn interface{}) Provider
Required creates a new provider and annotates it as required: it will be included in the provider chain even if its outputs are not used.
When used on an existing Provider, it creates an annotated copy of that provider.
func SaveTo ¶ added in v1.1.0
SaveTo generates a required provider. The input parameters to FillVars must be pointers. The generated provider takes as inputs the types needed to assign through the pointers.
If you want to fill a struct, use MakeStructBuilder() instead.
The first argument to FillVars may not be a pointer to a function.
Example ¶
package main import ( "fmt" "github.com/muir/nject" ) func main() { var s string var i int fmt.Println(nject.Run("example", func() string { return "one" }, func() int { return 3 }, nject.MustSaveTo(&s, &i)), s, i) }
Output: <nil> one 3
func Shun ¶
func Shun(fn interface{}) Provider
Shun creates a new provider and annotates it as not desired: even if it appears to be needed because another provider uses its output, the chain will be built without it if possible.
func Singleton ¶
func Singleton(fn interface{}) Provider
Singleton marks a provider as a forced singleton. The provider will be invoked only once even if it is included in multiple different Sequences. It will be in the the STATIC chain. There is no check that the input arguments available at the time the provider would be called are consistent from one invocation to the next. The provider will be called exactly once with whatever inputs are provided the in the first chain that invokes the provider.
An alternative way to get singleton behavior is with Memoize() combined with MustCache().
Example ¶
Singleton providers get run only once even if their arguments are different.
package main import ( "fmt" "github.com/muir/nject" ) func main() { type aStruct struct { ValueInStruct int } structProvider := nject.Singleton(func(s string, i int) *aStruct { return &aStruct{ ValueInStruct: len(s) * i, } }) _ = nject.Run("chain1", "four", 4, structProvider, func(a *aStruct, s string, i int) { fmt.Printf("inputs are %s and %d, value is %d\n", s, i, a.ValueInStruct) }, ) _ = nject.Run("chain2", "seven", 5, structProvider, func(a *aStruct, s string, i int) { fmt.Printf("inputs are %s and %d, value is %d\n", s, i, a.ValueInStruct) }, ) }
Output: inputs are four and 4, value is 16 inputs are seven and 5, value is 16
type Reflective ¶
type Reflective interface { ReflectiveArgs Call(in []reflect.Value) []reflect.Value }
Reflective is an alternative provider interface. Normally, providers are are functions or data elements to be injected. If the provider is a Reflective then the methods of Reflective will be called to simulate the Reflective being a function.
type ReflectiveArgs ¶ added in v1.1.0
type ReflectiveArgs interface { In(i int) reflect.Type NumIn() int Out(i int) reflect.Type NumOut() int }
ReflectiveArgs is the part of a Reflective that defines the inputs and outputs.
type ReflectiveInvoker ¶ added in v1.1.0
type ReflectiveInvoker interface { ReflectiveArgs Set(func([]reflect.Value) []reflect.Value) }
ReflectiveInvoker is an alternative provider interface that can be used for invoke and initialize functions. The key for those functions is that their implementation is provided by Collection.Bind.
type ReflectiveWrapper ¶ added in v1.1.0
type ReflectiveWrapper interface { Reflective Inner() ReflectiveArgs }
ReflectiveWrapper is a special variant of Reflective where the type of the first input is described by Inner(). In(0) must return the type of func([]reflect.Type) []reflect.Type.
When Call() is invoked, In(0) must be as described by Inner().
func MakeReflectiveWrapper ¶ added in v1.1.0
func MakeReflectiveWrapper( downIn []reflect.Type, upOut []reflect.Type, downOut []reflect.Type, upIn []reflect.Type, function func([]reflect.Value) []reflect.Value, ) ReflectiveWrapper
MakeReflectiveWrapper is a utility to create a ReflectiveWrapper
The first argument, downIn, is the types that must be received in the down chain and provided to function. This does not include the func([]reflec.Value) []reflect.Value that is actually used for the first argument.
The second argument, upOut, is the types that are returned on the up chain.
The third argument, downOut, is the types provided in the call to the inner function and thus are passed down the down chain.
The forth argument, upIn, is the types returned by the call to the inner function and thus are received from the up chain.
When function is called, the first argument will be a reflect.Value, of course, that is the value of a function that takes []reflect.Value and returns []reflect.Value.
EXPERIMENTAL: this is currently considered experimental and could be removed in a future release. If you're using this, please open a pull request to remove this comment.
type TerminalError ¶
type TerminalError interface { error }
TerminalError is a standard error interface. For fallible injectors, TerminalError must be one of the return values.
A non-nil return value terminates the handler call chain. The TerminalError return value gets converted to a regular error value (type=error) and (like other return values) it must be consumed by an upstream handler or the invoke function. Essentially marking an error return as a TerminalError causes special behavior but the effective type is just error.
Functions that return just TerminalError count as having no outputs and thus they are treated as specially required if they're in the RUN set.
Note: wrapper functions should not return TerminalError because such a return value would not be automatically converted into a regular error.