dependencyinjection

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 7 Imported by: 0

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

Examples

Constants

This section is empty.

Variables

View Source
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 Bind

func Bind[T any](r Registrar, lt Lifetime, factory Factory[T], deps ...Key) error

Bind registers an owned factory and its direct dependencies.

func BindBorrowed

func BindBorrowed[T any](r Registrar, lt Lifetime, factory Factory[T], deps ...Key) error

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

func BindFunc1[T, A1 any](r Registrar, lt Lifetime, fn func(context.Context, A1) (T, error)) error

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 BindValue

func BindValue[T any](r Registrar, v T) error

BindValue validates and registers an externally owned borrowed singleton.

func Resolve

func Resolve[T any](ctx context.Context, r Resolver) (T, error)

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(&registry, "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

func (b Binding) Construct(ctx context.Context, r Resolver) (any, error)

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

func (b Binding) Dependencies() []Key

Dependencies returns a copy of the direct dependency keys.

func (Binding) Key

func (b Binding) Key() Key

Key returns the exact service type.

func (Binding) Lifetime

func (b Binding) Lifetime() Lifetime

Lifetime returns the requested caching policy.

func (Binding) Ownership

func (b Binding) Ownership() Ownership

Ownership returns who disposes the result.

func (Binding) Validate

func (b Binding) Validate() error

Validate rejects zero descriptors, invalid policies, nil factories and invalid or repeated dependency keys. It does not invoke factories or inspect the graph.

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

type Catalog interface{ Contains(Key) bool }

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

type ContextCheck func(context.Context) error

ContextCheck validates captured immutable metadata. It must support concurrent calls.

type ContextChecker

type ContextChecker interface{ CheckContext(context.Context) error }

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.

func (*Error) Error

func (e *Error) Error() string

Error returns only operation, type identity, and category.

func (*Error) Unwrap

func (e *Error) Unwrap() []error

Unwrap preserves both category and cause for errors.Is/errors.As.

type Factory

type Factory[T any] func(context.Context, Resolver) (T, error)

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 KeyFor

func KeyFor[T any]() Key

KeyFor returns the exact key for T, including interface types.

func KeyOf

func KeyOf(t reflect.Type) (Key, error)

KeyOf returns an exact key for a runtime type, for example a reflect.Call parameter. Nil types yield ErrInvalidRegistration. No assignability search is performed.

func (Key) String

func (k Key) String() string

String returns a diagnostic package-qualified identity, or <invalid> for zero. It is not a serialization format or stable service identifier.

func (Key) Type

func (k Key) Type() reflect.Type

Type returns the exact reflected type, or nil for the zero key.

type Lifetime

type Lifetime uint8

Lifetime determines caching and resource lifetime.

const (
	// Singleton caches one successful value per provider; transitive dependencies are root-owned.
	Singleton Lifetime = iota + 1
	// Scoped caches one successful value per scope.
	Scoped
	// Transient constructs on every resolution.
	Transient
)

type Ownership

type Ownership uint8

Ownership determines whether a container disposes a factory result.

const (
	// Owned transfers the result to the container, including non-nil failed results.
	Owned Ownership = iota + 1
	// Borrowed never transfers disposal responsibility, even on failure.
	Borrowed
)

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

type Registrar interface{ Register(Binding) error }

Registrar accepts validated descriptors. Implementations copy dependency slices. Catalog is an optional capability for registration planning.

type Resolver

type Resolver interface {
	Resolve(context.Context, Key) (any, error)
}

Resolver resolves an exact key. Returned values are borrowed from their owner. Context carries operation metadata, never an ambient provider.

type Scope

type Scope interface {
	Resolver
	Close(context.Context) error
}

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

type ScopeFactory interface {
	NewScope(context.Context) (Scope, error)
}

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

type ScopeOwner interface{ Owns(Scope) bool }

ScopeOwner verifies scope identity, including after the scope closes. Unknown, nil, typed-nil and foreign scopes are not owned.

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.

Jump to

Keyboard shortcuts

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