Documentation ¶
Overview ¶
Package nject is a general purpose lightweight 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.
The basic idea 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.
The biggest 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.
Every provider produces or consumes 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 binding. The RUN set runs once per invoke. 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().
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() or Memoize() must be promoted to the STATIC set: if it cannot be promoted then the colection 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, when using Bind(), 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.
```go
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 30 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 injectors that return a value of type TerminalError.
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 TerminalError as a returned value if the invoke function 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 fuction. 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 ¶
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()).
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)
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
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.
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/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
Index ¶
- func DetailedError(err error) string
- func FillExisting(o *fillerOptions)
- func GenerateFromInjectionChain(...) 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) 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) MustSetCallback(binderFunction interface{})
- func (c *Collection) SetCallback(setCallbackFunc interface{}) error
- 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 Desired(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 MustMakeStructBuilder(model interface{}, opts ...FillerFuncArg) Provider
- func NonFinal(fn interface{}) Provider
- func NotCacheable(fn interface{}) Provider
- func Provide(name string, fn interface{}) Provider
- func Required(fn interface{}) Provider
- func Singleton(fn interface{}) Provider
- type Reflective
- type TerminalError
Examples ¶
- Package
- Cluster
- Collection.Append
- Collection.Bind
- Collection.Bind (Passing_in_parameters)
- Collection.DownFlows (Collection)
- Collection.DownFlows (Provider)
- Collection.ForEachProvider
- Collection.MustBindSimple
- Collection.MustBindSimpleError
- Collection.MustSetCallback
- Collection.SetCallback
- 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)
- Run
- 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( 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/nject" ) func main() { type S struct { I int } fmt.Println(nject.Run("example", func() int { return 3 }, nject.GenerateFromInjectionChain( 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/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/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/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/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.
Example ¶
package main import ( "fmt" "github.com/muir/nject/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(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 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, anoymous functions, and literal values.
Example ¶
package main import ( "fmt" "github.com/muir/nject/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/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.
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/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/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) 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/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/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/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/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/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) 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/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/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) 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.
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 // ceate 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 }
Debugging is provided to help diagnose injection issues. *Debugging is injected into every chain that consumes it.
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/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/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/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/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/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/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/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 recevies 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 {
// 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 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 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 thatn 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.
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.
Memoize only works in the STATIC provider set.
Combine Memoize with MustCache to make sure that Memoize can actually function as expected.
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 insepection.
package main import ( "fmt" "github.com/muir/nject/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 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 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 demonstrate the use of NonFinal. NonFinal is useful when manipulating lists of providers.
package main import ( "fmt" "github.com/muir/nject/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/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 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/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/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/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/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 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 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 MustChace().
Example ¶
Singleton providers get run only once even if their arguments are different.
package main import ( "fmt" "github.com/muir/nject/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 { In(i int) reflect.Type NumIn() int Out(i int) reflect.Type NumOut() int 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 TerminalError ¶
type TerminalError interface { error }
TerminalError is a standard error interface. For fallible injectors, TerminalError must 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 and (like other return values) it must be consumed by an upstream handler or the invoke function.