nject

package module
Version: v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 21, 2022 License: MIT Imports: 11 Imported by: 7

README

nject - dependency injection

GoDoc unit tests report card codecov

Install:

go get github.com/muir/nject

Prior to release 0.20, nject was bundled with other packages. Those other packages are now in their own repos: npoint, nserve, and nvelope. Additionally, npoint was split apart so that the gorilla dependency is separate and is in nape.


This package provides type-safe dependency injection without requiring users to do type assertions.

Dependencies are injected via a call chain: list functions to be called that take and return various parameters. The functions will be called in order using the return values from earlier functions as parameters for later functions.

Parameters are identified by their types. To have two different int parameters, define custom types.

Type safety is checked before any functions are called.

Functions whose outputs are not used are not called. Functions may "wrap" the rest of the list so that they can choose to invoke the remaing list zero or more times.

Chains may be pre-compiled into closures so that they have very little runtime penealty.

example
func example() {
	// Sequences can be reused.
	providerChain := Sequence("example sequence",
        // Constants can be injected.
        "a literal string value",
        // This function will be run if something downstream needs an int
        func(s string) int {
            return len(s)
        })
	Run("example",
        providerChain,
        // The last function in the list is always run.  This one needs
        // and int and a string.  The string can come from the constant
        // and the int from the function in the provider chain.
        func(i int, s string) {
            fmt.Println(i, len(s))
        })
}
Main APIs

Nject provides two main APIs: Bind() and Run().

Bind() is used when performance matters: given a chain of providers, it will write two functions: one to initialize the chain and another to invoke it. As much as possible, all dependency injection work is done at the time of binding and initialization so that the invoke function operates with very little overhead. The chain is initialized when the initialize function is called. The chain is run when the invoke function is called. Bind() does not run the chain.

Run() is used when ad-hoc injection is desired and performance is not critical. Run is appropriate when starting servers and running tests. It is not reccomended for http endpoint handlers. Run exectes the chain immediately.

Identified by type

Rather than naming values, inputs and outputs are identified by their types.
Since Go makes it easy to create new types, this turns out to be quite easy to use.

Types of providers

Multiple types of providers are supported.

Literal values

You can provide a constant if you have one.

Injectors

Regular functions can provide values. Injectors will be called at initialization time when they're marked as cacheable or at invocation time if they're not.

Injectors can be memoized.

Injectors can return a special error type that stops the chain.

Injectors can use data produced by earlier injectors simply by having a function parameter that matches the type of a return value of an earlier injector.

Wrappers

Wrappers are special functions that are responsible for invoking the part of the injection chain that comes after themselves. They do this by calling an inner() function that the nject framework defines for them.

Any arguments to the inner() function are injected as values available further down the chain. Any return values from inner() must be returned by the final function in the chain or from another wrapper futher down the chain.

Composition

Collections of injectors may be composed by including them in other collections.

Debugging injector chains

If you chain sucessfully binds but does not do what you expect, add something into your chain that recevies the nject.Debugging type:

func(d *nject.Debugging) {
	fmt.Println("Injectors included\n", d.Included)
}

If you chain does not bind, then Debugging won't help. Injection chain errors attempt to be self-explanatory, but sometimes that's not enough.

If you're building your injection sequence dynamically, it may be useful to print the injection chain. It has a String() method.

The following use nject to provide nicer APIs:

  • nfigure: configuration and flag processing
  • nvelope: injection chains for building endpoints
  • nchi: http router on top of httprouter with a go-chi-like API
  • nserve: injection chains for for starting and stopping servers
  • nvalid: enforce that http endpoints conform to Swagger definitions
  • npoint: dependency injection wrappers for binding http endpoint handlers
  • nape: dependency injection wrappers for binding http endpoint handlers using gorillia/mux
Development status

This repo represents continued development of Blue Owl's nject base. Blue Owl's code has been in production use for years and has been unchanged for years. The core of nject is mostly unchanged since taking over development from Blue Owl.

With the version 0.2.0 release, nject is anticipated to be stable.

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.

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 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 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, 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 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 (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().

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"
)

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

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func DetailedError

func DetailedError(err error) string

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

func ProvideRequireGap(provided []reflect.Type, required []reflect.Type) []reflect.Type

ProvideRequireGap identifies types that are required but are not provided.

func Run

func Run(name string, providers ...interface{}) error

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.

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(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"
)

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 producting 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
	// 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

	// 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 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 {
	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
	// producting 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 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.

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 Cachable, 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 insepection.

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 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

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 accidently 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 Provide

func Provide(name string, fn interface{}) Provider

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.

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 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

func SaveTo(varPointers ...interface{}) (Provider, error)

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.

func MakeReflective

func MakeReflective(
	inputs []reflect.Type,
	outputs []reflect.Type,
	function func([]reflect.Value) []reflect.Value,
) Reflective

MakeReflective is a simple utility to create a Reflective

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 implmentation 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 recevied 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 agument, 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 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.

Note: wrapper functions should not return TerminalError because such a return value would not be automatically converted into a regular error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL