Documentation
¶
Overview ¶
Package dependencyinjection defines container-neutral exact-type registration, resolution and scope contracts. Plain constructors and custom Resolver or ScopeFactory implementations need no container. Import this package as di.
Authoring paths ¶
Use plain constructors and explicit ownership when composition is small. To share wiring across frameworks, implement Resolver or ScopeFactory yourself, or use the optional default container. BindFunc1 through BindFunc4 derive direct edges from constructor parameters; Bind and BindBorrowed accept explicit keys. Catalog is optional on registrars and scope factories: type-assert it for startup checks, otherwise treat keys as resolvable and fail at resolution.
The default container's injected ScopeFactory is for opening scopes after construction, from methods at operation time, never inside a factory callback. Opening a scope in a factory bypasses cycle and captive-lifetime validation and can deadlock when resolving its own in-flight construction.
Errors ¶
Use errors.Is and errors.As, not Error text, which is diagnostic only. Error preserves both category and cause and excludes values and panic payloads from text. Cancellation before callbacks, scope creation and while waiting can return bare context errors. Factory failures wrap ErrFactoryFailed and preserve their cause; cleanup failures may be joined with them. Graph-policy errors (ErrCycle, ErrCaptiveLifetime, ErrUndeclaredDependency, ErrResolverExpired and ErrConcurrentFactoryUse) describe the default container's declared-graph policy, not a requirement that every custom Resolver implements graph validation.
The default container is optional, in dependencyinjection/container. Its extraction baseline is Arc.Go services at 304e97466bca594b51a3d7bbc723f4c477a6a99c.
Example ¶
package main
import (
"fmt"
)
type report struct{ title string }
func newReport(title string) *report { return &report{title: title} }
func main() {
// Ordinary constructors are the first path. You own their resources explicitly.
value := newReport("Quarterly report")
fmt.Println(value.title)
}
Output: Quarterly report
Index ¶
- Variables
- func Bind[T any](r Registrar, lt Lifetime, factory Factory[T], deps ...Key) error
- func BindBorrowed[T any](r Registrar, lt Lifetime, factory Factory[T], deps ...Key) error
- func BindBorrowedFunc1[T, A1 any](r Registrar, lt Lifetime, fn func(context.Context, A1) (T, error)) error
- func BindBorrowedFunc2[T, A1, A2 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2) (T, error)) error
- func BindBorrowedFunc3[T, A1, A2, A3 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2, A3) (T, error)) error
- func BindBorrowedFunc4[T, A1, A2, A3, A4 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2, A3, A4) (T, error)) error
- func BindFunc1[T, A1 any](r Registrar, lt Lifetime, fn func(context.Context, A1) (T, error)) error
- func BindFunc2[T, A1, A2 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2) (T, error)) error
- func BindFunc3[T, A1, A2, A3 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2, A3) (T, error)) error
- func BindFunc4[T, A1, A2, A3, A4 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2, A3, A4) (T, error)) error
- func BindValue[T any](r Registrar, v T) error
- func Resolve[T any](ctx context.Context, r Resolver) (T, error)
- type Binding
- type CaptureContext
- type Catalog
- type ContextCheck
- type ContextChecker
- type Error
- type Factory
- type Key
- type Lifetime
- type Ownership
- type Provider
- type Registrar
- type Resolver
- type Scope
- type ScopeFactory
- type ScopeOwner
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrWrongType identifies a result incompatible with the exact requested key. ErrWrongType = errors.New("wrong service type") // ErrFactoryFailed identifies a factory error; its cause remains inspectable. ErrFactoryFailed = errors.New("service factory failed") // ErrInvalidRegistration identifies invalid binding input. ErrInvalidRegistration = errors.New("invalid service registration") // ErrDuplicate identifies duplicate bindings or declared dependencies. ErrDuplicate = errors.New("duplicate service registration") // ErrMissing identifies a missing exact type binding. ErrMissing = errors.New("service missing") // ErrCycle identifies a dependency cycle. ErrCycle = errors.New("service dependency cycle") // ErrCaptiveLifetime identifies a singleton path reaching a scoped service. ErrCaptiveLifetime = errors.New("captive service lifetime") // ErrUndeclaredDependency identifies an undeclared factory edge. ErrUndeclaredDependency = errors.New("undeclared service dependency") // ErrClosed identifies closing/closed providers or scopes. ErrClosed = errors.New("dependency injection closed") // ErrInvalidScope identifies zero, nil, or otherwise invalid scope handles. ErrInvalidScope = errors.New("invalid service scope") // ErrContextMismatch identifies guard rejection, including captured presence changes. ErrContextMismatch = errors.New("service context mismatch") // ErrResolverExpired identifies use after a factory returns. ErrResolverExpired = errors.New("factory resolver expired") // ErrConcurrentFactoryUse identifies overlapping resolution on one factory view. ErrConcurrentFactoryUse = errors.New("concurrent factory scope use") // ErrNilValue identifies nil or typed-nil produced/registered values. ErrNilValue = errors.New("nil service value") // ErrCallbackPanicked identifies factory, cleanup or context-guard panics. ErrCallbackPanicked = errors.New("service callback panicked") )
Functions ¶
func BindBorrowed ¶
BindBorrowed registers a factory whose result is never disposed by the container. Dependencies retain their own ownership. Forward interfaces this way to avoid owning the same instance twice; preserve the concrete binding's lifetime.
func BindBorrowedFunc1 ¶
func BindBorrowedFunc1[T, A1 any](r Registrar, lt Lifetime, fn func(context.Context, A1) (T, error)) error
BindBorrowedFunc1 registers a borrowed constructor and derives its direct edges. Repeated parameter types share one declared edge; parameters resolve in order.
func BindBorrowedFunc2 ¶
func BindBorrowedFunc2[T, A1, A2 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2) (T, error)) error
BindBorrowedFunc2 registers a borrowed constructor and derives its direct edges. Repeated parameter types share one declared edge; parameters resolve in order.
func BindBorrowedFunc3 ¶
func BindBorrowedFunc3[T, A1, A2, A3 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2, A3) (T, error)) error
BindBorrowedFunc3 registers a borrowed constructor and derives its direct edges. Repeated parameter types share one declared edge; parameters resolve in order.
func BindBorrowedFunc4 ¶
func BindBorrowedFunc4[T, A1, A2, A3, A4 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2, A3, A4) (T, error)) error
BindBorrowedFunc4 registers a borrowed constructor and derives its direct edges. Repeated parameter types share one declared edge; parameters resolve in order.
func BindFunc1 ¶
BindFunc1 registers an owned constructor and derives its direct edges. Repeated parameter types share one declared edge; parameters resolve in order.
func BindFunc2 ¶
func BindFunc2[T, A1, A2 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2) (T, error)) error
BindFunc2 registers an owned constructor and derives its direct edges. Repeated parameter types share one declared edge; parameters resolve in order.
func BindFunc3 ¶
func BindFunc3[T, A1, A2, A3 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2, A3) (T, error)) error
BindFunc3 registers an owned constructor and derives its direct edges. Repeated parameter types share one declared edge; parameters resolve in order.
func BindFunc4 ¶
func BindFunc4[T, A1, A2, A3, A4 any](r Registrar, lt Lifetime, fn func(context.Context, A1, A2, A3, A4) (T, error)) error
BindFunc4 registers an owned constructor and derives its direct edges. Repeated parameter types share one declared edge; parameters resolve in order.
func Resolve ¶
Resolve returns the exact registered T and validates results from any adapter. Nil/typed-nil resolvers or nil contexts yield ErrInvalidScope. Pre-canceled contexts return their bare error. Resolver errors are preserved unchanged.
Example ¶
package main
import (
"context"
"fmt"
di "github.com/cratis/fundamentals.go/dependencyinjection"
"github.com/cratis/fundamentals.go/dependencyinjection/container"
)
func main() {
var registry container.Registry
if err := di.BindValue(®istry, "configured value"); err != nil {
panic(err)
}
p, err := registry.Build()
if err != nil {
panic(err)
}
ctx := context.Background()
s, err := p.NewScope(ctx)
if err != nil {
panic(err)
}
value, err := di.Resolve[string](ctx, s)
if err != nil {
panic(err)
}
fmt.Println(value)
if err := s.Close(ctx); err != nil {
panic(err)
}
if err := p.Close(ctx); err != nil {
panic(err)
}
}
Output: configured value
Types ¶
type Binding ¶
type Binding struct {
// contains filtered or unexported fields
}
Binding is an immutable descriptor. Zero is invalid. Dependencies are copied; factories and captured values are not cloned. Use NewBinding to construct one.
func NewBinding ¶
func NewBinding[T any](lt Lifetime, own Ownership, factory Factory[T], deps ...Key) (Binding, error)
NewBinding validates and copies an exact-type factory descriptor.
func (Binding) Construct ¶
Construct invokes the factory and validates a successful result. Concrete keys require exact dynamic types; interface keys require implementation. Nil and typed-nil results yield ErrNilValue, incompatible results ErrWrongType. A failed non-nil result is returned to let adapters honor Ownership and dispose it. Construct itself never disposes values, recovers panics or applies lifetimes.
func (Binding) Dependencies ¶
Dependencies returns a copy of the direct dependency keys.
type CaptureContext ¶
type CaptureContext func(context.Context) (ContextCheck, error)
CaptureContext captures metadata and presence, not the context itself. Captures must support concurrent calls and return a non-nil check on success.
type Catalog ¶
Catalog reports exact registrations. Type-assert it on Registrar or ScopeFactory. Without it, treat a key as resolvable and report failure at resolution.
Example ¶
package main
import (
"context"
"fmt"
"sync"
di "github.com/cratis/fundamentals.go/dependencyinjection"
)
type report struct{ title string }
func newReport(title string) *report { return &report{title: title} }
// localFactory supplies application values without importing a container.
// Catalog is intentionally absent: consumers try resolution instead.
type localFactory struct{ title string }
func (f localFactory) NewScope(ctx context.Context) (di.Scope, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
return &localScope{value: newReport(f.title)}, nil
}
type localScope struct {
mu sync.Mutex
value *report
closed bool
}
func (s *localScope) Resolve(ctx context.Context, key di.Key) (any, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return nil, &di.Error{Operation: "resolve", Key: key, Kind: di.ErrClosed}
}
if err := ctx.Err(); err != nil {
return nil, err
}
if key != di.KeyFor[*report]() {
return nil, &di.Error{Operation: "resolve", Key: key, Kind: di.ErrMissing}
}
return s.value, nil
}
func (s *localScope) Close(context.Context) error {
s.mu.Lock()
defer s.mu.Unlock()
s.closed = true
return nil
}
func main() {
var factory di.ScopeFactory = localFactory{title: "Quarterly report"}
key := di.KeyFor[*report]()
resolvable := true // Without Catalog, try resolution rather than rejecting startup.
if catalog, ok := factory.(di.Catalog); ok {
resolvable = catalog.Contains(key)
}
fmt.Println(resolvable)
}
Output: true
type ContextCheck ¶
ContextCheck validates captured immutable metadata. It must support concurrent calls.
type ContextChecker ¶
ContextChecker optionally verifies operation metadata and scope validity.
type Error ¶
type Error struct {
// Operation is the failing operation.
Operation string
// Key is the exact service key, if applicable.
Key Key
// Path is a copied dependency diagnostic path.
Path []Key
// Kind is the stable failure category.
Kind error
// Cause preserves callback or cancellation failures without displaying them.
Cause error
// Panic captures a callback panic without displaying it.
Panic any
}
Error is an inspectable service failure. Text is diagnostic-only, not a stable format, and never includes service values, causes or panic payloads. Constructors copy Path; adapters constructing Error themselves must also copy retained paths.
type Factory ¶
Factory constructs a value using explicitly declared direct dependencies. Do not retain the resolver or capture hidden request-bound dependencies. Default-container views reject overlapping calls and expire on return.
type Key ¶
type Key struct {
// contains filtered or unexported fields
}
Key identifies an exact Go type, including interface and pointer distinctions. Zero is invalid as a dependency key.
func KeyOf ¶
KeyOf returns an exact key for a runtime type, for example a reflect.Call parameter. Nil types yield ErrInvalidRegistration. No assignability search is performed.
type Ownership ¶
type Ownership uint8
Ownership determines whether a container disposes a factory result.
type Provider ¶
type Provider interface {
ScopeFactory
ScopeOwner
Catalog
Close(context.Context) error
}
Provider owns singleton resources and outstanding scopes; Close drains both. Providers expose no root Resolver. Concurrent use and repeated Close are supported.
type Registrar ¶
Registrar accepts validated descriptors. Implementations copy dependency slices. Catalog is an optional capability for registration planning.
type Resolver ¶
Resolver resolves an exact key. Returned values are borrowed from their owner. Context carries operation metadata, never an ambient provider.
type Scope ¶
Scope owns a resolution lifetime. Close releases resources, not operation effects. Stop and join application work before closing. Ordinary scopes support concurrent use.
type ScopeFactory ¶
ScopeFactory opens independent scopes without retaining the supplied context.
Example ¶
package main
import (
"context"
"fmt"
"sync"
di "github.com/cratis/fundamentals.go/dependencyinjection"
)
type report struct{ title string }
func newReport(title string) *report { return &report{title: title} }
// localFactory supplies application values without importing a container.
// Catalog is intentionally absent: consumers try resolution instead.
type localFactory struct{ title string }
func (f localFactory) NewScope(ctx context.Context) (di.Scope, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
return &localScope{value: newReport(f.title)}, nil
}
type localScope struct {
mu sync.Mutex
value *report
closed bool
}
func (s *localScope) Resolve(ctx context.Context, key di.Key) (any, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return nil, &di.Error{Operation: "resolve", Key: key, Kind: di.ErrClosed}
}
if err := ctx.Err(); err != nil {
return nil, err
}
if key != di.KeyFor[*report]() {
return nil, &di.Error{Operation: "resolve", Key: key, Kind: di.ErrMissing}
}
return s.value, nil
}
func (s *localScope) Close(context.Context) error {
s.mu.Lock()
defer s.mu.Unlock()
s.closed = true
return nil
}
func main() {
var factory di.ScopeFactory = localFactory{title: "Quarterly report"}
ctx := context.Background()
scope, err := factory.NewScope(ctx)
if err != nil {
panic(err)
}
value, err := di.Resolve[*report](ctx, scope)
if err != nil {
panic(err)
}
fmt.Println(value.title)
if err := scope.Close(ctx); err != nil {
panic(err)
}
}
Output: Quarterly report
type ScopeOwner ¶
ScopeOwner verifies scope identity, including after the scope closes. Unknown, nil, typed-nil and foreign scopes are not owned.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package bindingtypes plans constructor registrations over already type-checked Go packages.
|
Package bindingtypes plans constructor registrations over already type-checked Go packages. |
|
Package container implements the optional default dependency injection provider.
|
Package container implements the optional default dependency injection provider. |
|
Package ditest provides reusable dependency injection conformance suites.
|
Package ditest provides reusable dependency injection conformance suites. |
|
internal
|
|
|
bindingcorpus/competitor
Package competitor adds constructorless, ignored structural competition.
|
Package competitor adds constructorless, ignored structural competition. |
|
bindingcorpus/declarationcompetitor
Package declarationcompetitor exposes constructorless generic competitors.
|
Package declarationcompetitor exposes constructorless generic competitors. |
|
bindingcorpus/directives
Package directives supplies valid type-attached policies.
|
Package directives supplies valid type-attached policies. |
|
bindingcorpus/forms
Package forms supplies constructor shapes.
|
Package forms supplies constructor shapes. |
|
bindingcorpus/genericcompetitor
Package genericcompetitor supplies open structural implementation families.
|
Package genericcompetitor supplies open structural implementation families. |
|
bindingcorpus/genericforward
Package genericforward exposes a closed generic convention implementation.
|
Package genericforward exposes a closed generic convention implementation. |
|
bindingcorpus/invaliddirectives
Package invaliddirectives supplies conflicting and misplaced comments.
|
Package invaliddirectives supplies conflicting and misplaced comments. |
|
bindingcorpus/render
Package render supplies a runtime fixture for hand-authored product rendering.
|
Package render supplies a runtime fixture for hand-authored product rendering. |
|
bindingcorpus/renderfailure
Package renderfailure supplies analyzed runtime ownership regressions.
|
Package renderfailure supplies analyzed runtime ownership regressions. |
|
bindingcorpus/unique
Package unique supplies a unique pointer-method-set convention pair.
|
Package unique supplies a unique pointer-method-set convention pair. |
|
value
Package value centralizes runtime validation for contracts and container adapters.
|
Package value centralizes runtime validation for contracts and container adapters. |