di API

di

package

API reference for the di package.

T
type

Lifetime

Lifetime controls how services are instantiated.

pkg/di/container.go:14-14
type Lifetime int
S
struct

serviceEntry

pkg/di/container.go:25-33
type serviceEntry struct

Fields

Name Type Description
lifetime Lifetime
factory any
constructor any
instance any
built bool
concreteType reflect.Type
paramTypes []reflect.Type
S
struct

ResolveError

ResolveError is returned when a type cannot be resolved.

pkg/di/container.go:36-38
type ResolveError struct

Methods

Error
Method

Returns

string
func (*ResolveError) Error() string
{
	return fmt.Sprintf("di: cannot resolve type %v", e.Type)
}

Fields

Name Type Description
Type reflect.Type
S
struct

Container

Container is the dependency injection container.

pkg/di/container.go:45-54
type Container struct

Methods

validate
Method

Returns

[]error
func (*Container) validate() []error
{
	var errs []error
	visited := make(map[reflect.Type]bool)

	for typ := range c.services {
		if err := c.validateType(typ, visited); err != nil {
			errs = append(errs, err)
		}
	}
	return errs
}
validateType
Method

Parameters

visited map[reflect.Type]bool

Returns

error
func (*Container) validateType(typ reflect.Type, visited map[reflect.Type]bool) error
{
	if visited[typ] {
		return nil
	}
	visited[typ] = true

	entry, ok := c.services[typ]
	if !ok {
		return &ResolveError{Type: typ}
	}

	if entry.constructor != nil && entry.paramTypes != nil {
		for _, pt := range entry.paramTypes {
			if _, ok := c.services[pt]; !ok {
				return fmt.Errorf("di: type %v requires %v which is not registered", typ, pt)
			}
		}
	}

	if entry.factory == nil && entry.concreteType.Kind() == reflect.Struct {
		if err := c.validateStructFields(entry.concreteType, visited); err != nil {
			return err
		}
	}

	return nil
}

Parameters

visited map[reflect.Type]bool

Returns

error
func (*Container) validateStructFields(typ reflect.Type, visited map[reflect.Type]bool) error
{
	for i := 0; i < typ.NumField(); i++ {
		field := typ.Field(i)
		tag := field.Tag.Get("inject")
		if tag == "" {
			continue
		}
		if _, ok := c.services[field.Type]; !ok {
			return fmt.Errorf("di: struct %v field %s requires %v which is not registered", typ, field.Name, field.Type)
		}
	}
	return nil
}
trackCloser
Method

Parameters

instance any
func (*Container) trackCloser(instance any)
{
	if closer, ok := instance.(io.Closer); ok {
		c.closeMu.Lock()
		c.closers = append(c.closers, closer)
		c.closeMu.Unlock()
	}
}
Close
Method

Close calls Close() on all resolved services that implement io.Closer. Call this at the end of a scope (e.g. at end of HTTP request).

Returns

error
func (*Container) Close() error
{
	c.closeMu.Lock()
	closers := make([]io.Closer, len(c.closers))
	copy(closers, c.closers)
	c.closers = nil
	c.closeMu.Unlock()

	var errs []error
	for _, closer := range closers {
		if err := closer.Close(); err != nil {
			errs = append(errs, err)
		}
	}
	if len(errs) > 0 {
		return fmt.Errorf("di: close errors: %v", errs)
	}
	return nil
}
Provide
Method

Parameters

name string
instance any
func (*Container) Provide(name string, instance any)
{
	contracts.MustVerify(instance)
	c.mu.Lock()
	c.named[name] = instance
	c.mu.Unlock()
}
Get
Method

Parameters

name string

Returns

any
bool
func (*Container) Get(name string) (any, bool)
{
	c.mu.RLock()
	v, ok := c.named[name]
	c.mu.RUnlock()
	if !ok {
		return nil, false
	}
	if lp, ok := v.(*lazyProvider); ok {
		return lp.get(), true
	}
	return v, true
}
MustGet
Method

Parameters

name string

Returns

any
func (*Container) MustGet(name string) any
{
	v, ok := c.Get(name)
	if !ok {
		panic("di: dependency not found: " + name)
	}
	return v
}
Has
Method

Parameters

name string

Returns

bool
func (*Container) Has(name string) bool
{
	c.mu.RLock()
	_, ok := c.named[name]
	c.mu.RUnlock()
	return ok
}
Inject
Method

Parameters

target any
func (*Container) Inject(target any)
{
	val := reflect.ValueOf(target)
	if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
		return
	}
	elem := val.Elem()
	fields := injectParser.ParseStruct(target)

	c.mu.RLock()
	defer c.mu.RUnlock()

	for _, meta := range fields {
		fieldVal := elem.Field(meta.Index)
		if !fieldVal.CanSet() {
			continue
		}
		name := meta.RawTag
		if name == "" {
			name = meta.Name
		}
		if dep, ok := c.named[name]; ok {
			if lp, ok := dep.(*lazyProvider); ok {
				dep = lp.get()
			}
			depVal := reflect.ValueOf(dep)
			if depVal.Type().AssignableTo(fieldVal.Type()) {
				fieldVal.Set(depVal)
			}
		}
	}
}
Scope
Method

Scope creates a child container with fresh scoped instances. When the scope ends, call Close() to dispose scoped services.

Returns

func (*Container) Scope() *Container
{
	c.mu.RLock()
	defer c.mu.RUnlock()

	child := New()
	child.parent = c
	for k, v := range c.services {
		if v.lifetime == Scoped {
			child.services[k] = &serviceEntry{
				lifetime:     v.lifetime,
				factory:      v.factory,
				concreteType: v.concreteType,
				constructor:  v.constructor,
				paramTypes:   v.paramTypes,
			}
		} else {
			child.services[k] = v
		}
	}
	for k, v := range c.named {
		child.named[k] = v
	}
	return child
}
ProvideLazy
Method

Parameters

name string
factory func() any
func (*Container) ProvideLazy(name string, factory func() any)
{
	c.mu.Lock()
	c.named[name] = &lazyProvider{factory: factory}
	c.mu.Unlock()
}
Clone
Method

Returns

func (*Container) Clone() *Container
{
	c.mu.RLock()
	defer c.mu.RUnlock()

	clone := New()
	for k, v := range c.services {
		clone.services[k] = v
	}
	for k, v := range c.named {
		clone.named[k] = v
	}
	return clone
}
Keys
Method

Returns

[]string
func (*Container) Keys() []string
{
	c.mu.RLock()
	defer c.mu.RUnlock()

	keys := make([]string, 0, len(c.named))
	for k := range c.named {
		keys = append(keys, k)
	}
	return keys
}

Parameters

iface reflect.Type

Returns

[]any
func (*Container) ResolveAllTyped(iface reflect.Type) []any
{
	c.mu.RLock()
	defer c.mu.RUnlock()

	var result []any
	for _, v := range c.named {
		if lp, ok := v.(*lazyProvider); ok {
			v = lp.get()
		}
		if reflect.TypeOf(v).Implements(iface) {
			result = append(result, v)
		}
	}
	return result
}

Fields

Name Type Description
services map[reflect.Type]*serviceEntry
named map[string]any
mu sync.RWMutex
parent *Container
resolving map[reflect.Type]struct{}
muResolve sync.Mutex
closers []io.Closer
closeMu sync.Mutex
F
function

New

New creates a new empty Container.

Returns

pkg/di/container.go:59-65
func New() *Container

{
	return &Container{
		services:  make(map[reflect.Type]*serviceEntry),
		named:     make(map[string]any),
		resolving: make(map[reflect.Type]struct{}),
	}
}
S
struct

Builder

Builder constructs a Container with validation.

pkg/di/container.go:68-74
type Builder struct

Methods

Build
Method

Build creates a Container and validates all registrations. Returns an error if any registered type has unresolvable dependencies.

Returns

error
func (*Builder) Build() (*Container, error)
{
	c := New()
	b.mu.RLock()
	for k, v := range b.services {
		cp := *v
		c.services[k] = &cp
	}
	for k, v := range b.named {
		c.named[k] = v
	}
	b.mu.RUnlock()

	if errs := c.validate(); len(errs) > 0 {
		return c, fmt.Errorf("di: build validation failed: %v", errs)
	}
	return c, nil
}
MustBuild
Method

MustBuild is like Build but panics on validation errors.

Returns

func (*Builder) MustBuild() *Container
{
	c, err := b.Build()
	if err != nil {
		panic(err.Error())
	}
	return c
}
Provide
Method

Parameters

name string
instance any
func (*Builder) Provide(name string, instance any)
{
	contracts.MustVerify(instance)
	b.mu.Lock()
	b.named[name] = instance
	b.mu.Unlock()
}

Fields

Name Type Description
services map[reflect.Type]*serviceEntry
named map[string]any
mu sync.RWMutex
validated bool
buildErrors []error
F
function

NewBuilder

NewBuilder creates a new Builder.

Returns

pkg/di/container.go:77-82
func NewBuilder() *Builder

{
	return &Builder{
		services: make(map[reflect.Type]*serviceEntry),
		named:    make(map[string]any),
	}
}
F
function

Register

Register adds a service factory to the builder.

Parameters

b
factory
func() T
lifetime
...Lifetime
pkg/di/container.go:85-94
func Register[T any](b *Builder, factory func() T, lifetime ...Lifetime)

{
	lt := Singleton
	if len(lifetime) > 0 {
		lt = lifetime[0]
	}
	typ := reflect.TypeOf((*T)(nil)).Elem()
	b.mu.Lock()
	b.services[typ] = &serviceEntry{lifetime: lt, factory: factory, concreteType: typ}
	b.mu.Unlock()
}
F
function

RegisterAs

RegisterAs registers a service factory resolving to a different concrete type.

Parameters

b
factory
func() T
lifetime
...Lifetime
pkg/di/container.go:97-106
func RegisterAs[T any](b *Builder, factory func() T, lifetime ...Lifetime)

{
	lt := Singleton
	if len(lifetime) > 0 {
		lt = lifetime[0]
	}
	typ := reflect.TypeOf((*T)(nil)).Elem()
	b.mu.Lock()
	b.services[typ] = &serviceEntry{lifetime: lt, factory: factory, concreteType: typ}
	b.mu.Unlock()
}
F
function

RegisterInstance

RegisterInstance registers a pre-created instance as a singleton.

Parameters

b
instance
T
pkg/di/container.go:109-120
func RegisterInstance[T any](b *Builder, instance T)

{
	typ := reflect.TypeOf((*T)(nil)).Elem()
	b.mu.Lock()
	b.services[typ] = &serviceEntry{
		lifetime:     Singleton,
		concreteType: typ,
		factory:      func() T { return instance },
		built:        true,
		instance:     instance,
	}
	b.mu.Unlock()
}
F
function

RegisterImpl

RegisterImpl registers a concrete type T that satisfies interface I.

Parameters

b
lifetime
...Lifetime
pkg/di/container.go:123-133
func RegisterImpl[I, T any](b *Builder, lifetime ...Lifetime)

{
	lt := Singleton
	if len(lifetime) > 0 {
		lt = lifetime[0]
	}
	iTyp := reflect.TypeOf((*I)(nil)).Elem()
	tTyp := reflect.TypeOf((*T)(nil)).Elem()
	b.mu.Lock()
	b.services[iTyp] = &serviceEntry{lifetime: lt, factory: nil, concreteType: tTyp}
	b.mu.Unlock()
}
F
function

RegisterFromFunc

RegisterFromFunc registers a type using a constructor function whose
parameters are auto-resolved from the container at resolution time.
T is the return type. The constructor must accept resolvable types.
Build() validates that all parameter types are registered.
RegisterFromFunc registers a service by constructor function with dependency injection.

Parameters

b
constructor
any
lifetime
...Lifetime
pkg/di/container.go:140-166
func RegisterFromFunc[T any](b *Builder, constructor any, lifetime ...Lifetime)

{
	lt := Singleton
	if len(lifetime) > 0 {
		lt = lifetime[0]
	}
	typ := reflect.TypeOf((*T)(nil)).Elem()

	ctorType := reflect.TypeOf(constructor)
	if ctorType.Kind() != reflect.Func {
		panic("di: RegisterFromFunc requires a function")
	}
	if ctorType.NumOut() == 0 {
		panic("di: RegisterFromFunc: constructor must return a value")
	}

	paramTypes := make([]reflect.Type, ctorType.NumIn())
	for i := 0; i < ctorType.NumIn(); i++ {
		paramTypes[i] = ctorType.In(i)
	}

	b.mu.Lock()
	b.services[typ] = &serviceEntry{
		lifetime: lt, factory: nil, concreteType: typ,
		constructor: constructor, paramTypes: paramTypes,
	}
	b.mu.Unlock()
}
F
function

ResolveType

ResolveType resolves a service from the container, panicking on failure.

Parameters

Returns

T
pkg/di/container.go:259-265
func ResolveType[T any](c *Container) T

{
	result, err := tryResolveType(c, reflect.TypeOf((*T)(nil)).Elem())
	if err != nil {
		panic(err.Error())
	}
	return result.Interface().(T)
}
F
function

TryResolveType

TryResolveType resolves a service from the container, returning an error on failure.

Parameters

Returns

T
error
pkg/di/container.go:268-275
func TryResolveType[T any](c *Container) (T, error)

{
	var zero T
	result, err := tryResolveType(c, reflect.TypeOf((*T)(nil)).Elem())
	if err != nil {
		return zero, err
	}
	return result.Interface().(T), nil
}
F
function

tryResolveType

Parameters

Returns

pkg/di/container.go:277-354
func tryResolveType(c *Container, typ reflect.Type) (reflect.Value, error)

{
	c.mu.RLock()
	entry, ok := c.services[typ]
	c.mu.RUnlock()

	if !ok {
		if c.parent != nil {
			return tryResolveType(c.parent, typ)
		}
		return reflect.Value{}, &ResolveError{Type: typ}
	}

	c.muResolve.Lock()
	if _, resolving := c.resolving[typ]; resolving {
		c.muResolve.Unlock()
		return reflect.Value{}, fmt.Errorf("di: circular dependency detected for type %v", typ)
	}
	c.resolving[typ] = struct{}{}
	c.muResolve.Unlock()

	defer func() {
		c.muResolve.Lock()
		delete(c.resolving, typ)
		c.muResolve.Unlock()
	}()

	switch entry.lifetime {
	case Singleton:
		if entry.built {
			return reflect.ValueOf(entry.instance), nil
		}
		c.mu.Lock()
		if entry.built {
			c.mu.Unlock()
			return reflect.ValueOf(entry.instance), nil
		}
		c.mu.Unlock()
		result, err := invokeEntry(c, entry)
		if err != nil {
			return reflect.Value{}, err
		}
		c.mu.Lock()
		entry.instance = result.Interface()
		entry.built = true
		c.mu.Unlock()
		c.trackCloser(entry.instance)
		return result, nil
	case Transient:
		result, err := invokeEntry(c, entry)
		if err != nil {
			return reflect.Value{}, err
		}
		c.trackCloser(result.Interface())
		return result, nil
	case Scoped:
		if entry.built {
			return reflect.ValueOf(entry.instance), nil
		}
		c.mu.Lock()
		if entry.built {
			c.mu.Unlock()
			return reflect.ValueOf(entry.instance), nil
		}
		c.mu.Unlock()
		result, err := invokeEntry(c, entry)
		if err != nil {
			return reflect.Value{}, err
		}
		c.mu.Lock()
		entry.instance = result.Interface()
		entry.built = true
		c.mu.Unlock()
		c.trackCloser(entry.instance)
		return result, nil
	default:
		return reflect.Value{}, &ResolveError{Type: typ}
	}
}
F
function

invokeEntry

Parameters

Returns

pkg/di/container.go:385-394
func invokeEntry(c *Container, entry *serviceEntry) (reflect.Value, error)

{
	if entry.constructor != nil && entry.paramTypes != nil {
		return invokeConstructor(c, entry)
	}
	if entry.factory != nil {
		results := reflect.ValueOf(entry.factory).Call(nil)
		return results[0], nil
	}
	return constructValue(c, entry.concreteType)
}
F
function

invokeConstructor

Parameters

Returns

pkg/di/container.go:396-408
func invokeConstructor(c *Container, entry *serviceEntry) (reflect.Value, error)

{
	ctorVal := reflect.ValueOf(entry.constructor)
	args := make([]reflect.Value, len(entry.paramTypes))
	for i, pt := range entry.paramTypes {
		resolved, err := resolveByType(c, pt)
		if err != nil {
			return reflect.Value{}, fmt.Errorf("di: cannot resolve param %d (%v) of constructor for %v: %w", i, pt, entry.concreteType, err)
		}
		args[i] = resolved
	}
	results := ctorVal.Call(args)
	return results[0], nil
}
F
function

constructValue

Parameters

Returns

pkg/di/container.go:410-453
func constructValue(c *Container, typ reflect.Type) (reflect.Value, error)

{
	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
	}
	if typ.Kind() != reflect.Struct {
		return reflect.Value{}, fmt.Errorf("di: auto-construction requires struct type, got %v", typ)
	}

	val := reflect.New(typ)

	ctorVal := val.MethodByName("Init")
	if ctorVal.IsValid() {
		ctorType := ctorVal.Type()
		args := make([]reflect.Value, ctorType.NumIn())
		for i := 0; i < ctorType.NumIn(); i++ {
			resolved, err := resolveByType(c, ctorType.In(i))
			if err != nil {
				return reflect.Value{}, fmt.Errorf("di: cannot resolve param %d of Init: %w", i, err)
			}
			args[i] = resolved
		}
		ctorVal.Call(args)
	} else {
		elem := val.Elem()
		for i := 0; i < typ.NumField(); i++ {
			field := typ.Field(i)
			tag := field.Tag.Get("inject")
			if tag == "" && !field.Anonymous {
				continue
			}
			fieldVal := elem.Field(i)
			if !fieldVal.CanSet() || !field.IsExported() {
				continue
			}
			resolved, err := resolveByType(c, fieldVal.Type())
			if err != nil {
				return reflect.Value{}, fmt.Errorf("di: cannot resolve field %s: %w", field.Name, err)
			}
			fieldVal.Set(resolved)
		}
	}

	return val, nil
}
F
function

resolveByType

Parameters

Returns

pkg/di/container.go:455-466
func resolveByType(c *Container, typ reflect.Type) (reflect.Value, error)

{
	c.mu.RLock()
	entry, ok := c.services[typ]
	c.mu.RUnlock()
	if !ok && c.parent != nil {
		return resolveByType(c.parent, typ)
	}
	if !ok {
		return reflect.Value{}, &ResolveError{Type: typ}
	}
	return invokeEntry(c, entry)
}
S
struct

lazyProvider

pkg/di/container.go:609-613
type lazyProvider struct

Methods

get
Method

Returns

any
func (*lazyProvider) get() any
{
	l.once.Do(func() {
		l.value = l.factory()
	})
	return l.value
}

Fields

Name Type Description
once sync.Once
value any
factory func() any
S
struct

testDB

pkg/di/container_test.go:10-12
type testDB struct

Fields

Name Type Description
Name string
S
struct

testService

pkg/di/container_test.go:14-17
type testService struct

Fields

Name Type Description
DB *testDB inject:"db"
Logger string inject:"logger"
F
function

TestContainer_ProvideAndGet

Parameters

pkg/di/container_test.go:19-37
func TestContainer_ProvideAndGet(t *testing.T)

{
	c := New()
	db := &testDB{Name: "test"}
	c.Provide("db", db)

	got, ok := c.Get("db")
	if !ok {
		t.Fatal("expected to find 'db'")
	}

	gotDB, ok := got.(*testDB)
	if !ok {
		t.Fatal("expected *testDB type")
	}

	if gotDB.Name != "test" {
		t.Errorf("got %q, want %q", gotDB.Name, "test")
	}
}
F
function

TestContainer_Has

Parameters

pkg/di/container_test.go:39-50
func TestContainer_Has(t *testing.T)

{
	c := New()
	c.Provide("exists", "value")

	if !c.Has("exists") {
		t.Error("Has should return true for 'exists'")
	}

	if c.Has("missing") {
		t.Error("Has should return false for 'missing'")
	}
}
F
function

TestContainer_Inject

Parameters

pkg/di/container_test.go:52-68
func TestContainer_Inject(t *testing.T)

{
	c := New()
	db := &testDB{Name: "injected"}
	c.Provide("db", db)
	c.Provide("logger", "stdout")

	svc := &testService{}
	c.Inject(svc)

	if svc.DB != db {
		t.Errorf("DB not injected correctly")
	}

	if svc.Logger != "stdout" {
		t.Errorf("Logger: got %q, want %q", svc.Logger, "stdout")
	}
}
F
function

TestContainer_Clone

Parameters

pkg/di/container_test.go:70-88
func TestContainer_Clone(t *testing.T)

{
	c := New()
	c.Provide("key", "value")

	clone := c.Clone()
	clone.Provide("new", "added")

	if !clone.Has("key") {
		t.Error("clone should have 'key'")
	}

	if !clone.Has("new") {
		t.Error("clone should have 'new'")
	}

	if c.Has("new") {
		t.Error("original should not have 'new'")
	}
}
F
function

TestContainer_Keys

Parameters

pkg/di/container_test.go:90-99
func TestContainer_Keys(t *testing.T)

{
	c := New()
	c.Provide("a", 1)
	c.Provide("b", 2)

	keys := c.Keys()
	if len(keys) != 2 {
		t.Errorf("got %d keys, want 2", len(keys))
	}
}
F
function

TestContainer_MustGet_Panic

Parameters

pkg/di/container_test.go:101-111
func TestContainer_MustGet_Panic(t *testing.T)

{
	c := New()

	defer func() {
		if r := recover(); r == nil {
			t.Error("MustGet should panic for missing key")
		}
	}()

	c.MustGet("missing")
}
F
function

TestResolve

Parameters

pkg/di/container_test.go:113-124
func TestResolve(t *testing.T)

{
	c := New()
	c.Provide("num", 42)

	got, ok := Resolve[int](c, "num")
	if !ok {
		t.Fatal("expected to resolve 'num'")
	}
	if got != 42 {
		t.Errorf("got %d, want 42", got)
	}
}
F
function

TestResolve_TypeMismatch

Parameters

pkg/di/container_test.go:126-134
func TestResolve_TypeMismatch(t *testing.T)

{
	c := New()
	c.Provide("num", 42)

	_, ok := Resolve[string](c, "num")
	if ok {
		t.Error("should return false for type mismatch")
	}
}
F
function

TestMustResolve_Panic

Parameters

pkg/di/container_test.go:136-146
func TestMustResolve_Panic(t *testing.T)

{
	c := New()

	defer func() {
		if r := recover(); r == nil {
			t.Error("MustResolve should panic for missing key")
		}
	}()

	MustResolve[int](c, "missing")
}
F
function

TestBuilder_RegisterAndResolveType

Parameters

pkg/di/container_test.go:148-157
func TestBuilder_RegisterAndResolveType(t *testing.T)

{
	b := NewBuilder()
	Register(b, func() *testDB { return &testDB{Name: "built"} })

	c := b.MustBuild()
	db := ResolveType[*testDB](c)
	if db.Name != "built" {
		t.Errorf("got %q, want %q", db.Name, "built")
	}
}
F
function

TestBuilder_TransientLifetime

Parameters

pkg/di/container_test.go:159-169
func TestBuilder_TransientLifetime(t *testing.T)

{
	b := NewBuilder()
	Register(b, func() *testDB { return &testDB{Name: "fresh"} }, Transient)

	c := b.MustBuild()
	a := ResolveType[*testDB](c)
	b2 := ResolveType[*testDB](c)
	if a == b2 {
		t.Error("Transient should return new instances")
	}
}
F
function

TestBuilder_SingletonLifetime

Parameters

pkg/di/container_test.go:171-181
func TestBuilder_SingletonLifetime(t *testing.T)

{
	b := NewBuilder()
	Register(b, func() *testDB { return &testDB{Name: "singleton"} }, Singleton)

	c := b.MustBuild()
	a := ResolveType[*testDB](c)
	b2 := ResolveType[*testDB](c)
	if a != b2 {
		t.Error("Singleton should return same instance")
	}
}
F
function

TestResolveType_PanicOnMissing

Parameters

pkg/di/container_test.go:183-194
func TestResolveType_PanicOnMissing(t *testing.T)

{
	b := NewBuilder()
	c := b.MustBuild()

	defer func() {
		if r := recover(); r == nil {
			t.Error("ResolveType should panic on missing type")
		}
	}()

	ResolveType[*testDB](c)
}
F
function

TestBuilder_ProvideNamed

Parameters

pkg/di/container_test.go:196-209
func TestBuilder_ProvideNamed(t *testing.T)

{
	b := NewBuilder()
	b.Provide("db", &testDB{Name: "named"})

	c := b.MustBuild()
	got, ok := c.Get("db")
	if !ok {
		t.Fatal("expected named dep 'db'")
	}
	db := got.(*testDB)
	if db.Name != "named" {
		t.Errorf("got %q, want %q", db.Name, "named")
	}
}
I
interface

Worker

pkg/di/container_test.go:211-213
type Worker interface

Methods

Work
Method

Returns

string
func Work(...)
S
struct
Implements: Worker

GoodWorker

pkg/di/container_test.go:215-217
type GoodWorker struct

Methods

Work
Method

Returns

string
func (*GoodWorker) Work() string
{
	return "working hard"
}
S
struct
Implements: Worker

LazyWorker

pkg/di/container_test.go:223-225
type LazyWorker struct

Methods

Work
Method

Returns

string
func (*LazyWorker) Work() string
{
	return "working smart"
}
S
struct

BrokenWorker

pkg/di/container_test.go:231-233
type BrokenWorker struct
F
function

TestContainer_ProvideWithContracts

Parameters

pkg/di/container_test.go:235-255
func TestContainer_ProvideWithContracts(t *testing.T)

{
	c := New()

	t.Run("Valid implementation", func(t *testing.T) {
		defer func() {
			if r := recover(); r != nil {
				t.Errorf("Provide should not panic for valid worker: %v", r)
			}
		}()
		c.Provide("good", &GoodWorker{})
	})

	t.Run("Invalid implementation panics", func(t *testing.T) {
		defer func() {
			if r := recover(); r == nil {
				t.Error("Provide should panic for broken worker")
			}
		}()
		c.Provide("broken", &BrokenWorker{})
	})
}
F
function

TestResolveAll

Parameters

pkg/di/container_test.go:257-282
func TestResolveAll(t *testing.T)

{
	c := New()
	c.Provide("good", &GoodWorker{})
	c.Provide("lazy", &LazyWorker{})
	c.Provide("other", "not a worker")

	workers := ResolveAll[Worker](c)
	if len(workers) != 2 {
		t.Fatalf("expected 2 workers, got %d", len(workers))
	}

	foundGood := false
	foundLazy := false
	for _, w := range workers {
		switch w.Work() {
		case "working hard":
			foundGood = true
		case "working smart":
			foundLazy = true
		}
	}

	if !foundGood || !foundLazy {
		t.Error("ResolveAll did not find all expected workers")
	}
}
F
function

TestContainer_Scope

Parameters

pkg/di/container_test.go:284-292
func TestContainer_Scope(t *testing.T)

{
	c := New()
	c.Provide("shared", "value")

	child := c.Scope()
	if !child.Has("shared") {
		t.Error("child should inherit named deps from parent")
	}
}
F
function

TestContainer_ProvideLazy

Parameters

pkg/di/container_test.go:294-324
func TestContainer_ProvideLazy(t *testing.T)

{
	c := New()
	called := 0
	c.ProvideLazy("lazy", func() any {
		called++
		return "computed"
	})

	if called != 0 {
		t.Error("lazy factory should not be called on registration")
	}

	v, ok := c.Get("lazy")
	if !ok {
		t.Fatal("expected to find 'lazy'")
	}
	if v != "computed" {
		t.Errorf("got %v, want %q", v, "computed")
	}
	if called != 1 {
		t.Error("lazy factory should be called once on first access")
	}

	v2, _ := c.Get("lazy")
	if v2 != "computed" {
		t.Error("lazy factory should return cached value")
	}
	if called != 1 {
		t.Error("lazy factory should only be called once")
	}
}
S
struct

Config

pkg/di/container_test.go:328-330
type Config struct

Fields

Name Type Description
DSN string
S
struct

UserService

pkg/di/container_test.go:332-335
type UserService struct

Fields

Name Type Description
DB *testDB
Cfg *Config
F
function

NewUserService

Parameters

db
cfg

Returns

pkg/di/container_test.go:337-339
func NewUserService(db *testDB, cfg *Config) UserService

{
	return UserService{DB: db, Cfg: cfg}
}
F
function

TestRegisterFromFunc

Parameters

pkg/di/container_test.go:341-355
func TestRegisterFromFunc(t *testing.T)

{
	b := NewBuilder()
	Register(b, func() *testDB { return &testDB{Name: "pg"} })
	Register(b, func() *Config { return &Config{DSN: "host=localhost"} })
	RegisterFromFunc[UserService](b, NewUserService, Scoped)

	c := b.MustBuild()
	svc := ResolveType[UserService](c)
	if svc.DB.Name != "pg" {
		t.Errorf("DB.Name = %q, want %q", svc.DB.Name, "pg")
	}
	if svc.Cfg.DSN != "host=localhost" {
		t.Errorf("Cfg.DSN = %q, want %q", svc.Cfg.DSN, "host=localhost")
	}
}
F
function

TestRegisterFromFunc_MissingDep

Parameters

pkg/di/container_test.go:357-366
func TestRegisterFromFunc_MissingDep(t *testing.T)

{
	b := NewBuilder()
	Register(b, func() *testDB { return &testDB{Name: "pg"} })
	RegisterFromFunc[UserService](b, NewUserService, Scoped)

	_, err := b.Build()
	if err == nil {
		t.Fatal("expected build error for missing Config dependency")
	}
}
S
struct

closableService

pkg/di/container_test.go:370-372
type closableService struct

Methods

Close
Method

Returns

error
func (*closableService) Close() error
{
	c.closed = true
	return nil
}

Fields

Name Type Description
closed bool
F
function

TestScopedContainer_Close

Parameters

pkg/di/container_test.go:379-395
func TestScopedContainer_Close(t *testing.T)

{
	b := NewBuilder()
	Register(b, func() *closableService { return &closableService{} }, Scoped)

	c := b.MustBuild()
	scope := c.Scope()
	svc := ResolveType[*closableService](scope)
	if svc.closed {
		t.Error("service should not be closed yet")
	}
	if err := scope.Close(); err != nil {
		t.Fatalf("Close: %v", err)
	}
	if !svc.closed {
		t.Error("service should be closed after scope.Close()")
	}
}
F
function

TestContainer_Close_Singleton

Parameters

pkg/di/container_test.go:397-412
func TestContainer_Close_Singleton(t *testing.T)

{
	b := NewBuilder()
	Register(b, func() *closableService { return &closableService{} }, Singleton)

	c := b.MustBuild()
	svc := ResolveType[*closableService](c)
	if svc.closed {
		t.Error("service should not be closed yet")
	}
	if err := c.Close(); err != nil {
		t.Fatalf("Close: %v", err)
	}
	if !svc.closed {
		t.Error("singleton implementing io.Closer should be closed on container close")
	}
}
F
function

TestContainer_Close_NonCloser

Parameters

pkg/di/container_test.go:414-423
func TestContainer_Close_NonCloser(t *testing.T)

{
	b := NewBuilder()
	Register(b, func() *testDB { return &testDB{Name: "ok"} })

	c := b.MustBuild()
	ResolveType[*testDB](c)
	if err := c.Close(); err != nil {
		t.Fatalf("Close on non-closer should not error: %v", err)
	}
}
F
function

Resolve

Resolve retrieves a typed dependency from the container by name.

Parameters

name
string

Returns

T
bool
pkg/di/resolve.go:4-15
func Resolve[T any](c *Container, name string) (T, bool)

{
	var zero T
	v, ok := c.Get(name)
	if !ok {
		return zero, false
	}
	typed, ok := v.(T)
	if !ok {
		return zero, false
	}
	return typed, true
}
F
function

MustResolve

MustResolve retrieves a typed dependency by name and panics if not found.

Parameters

name
string

Returns

T
pkg/di/resolve.go:18-24
func MustResolve[T any](c *Container, name string) T

{
	v, ok := Resolve[T](c, name)
	if !ok {
		panic("di: cannot resolve " + name)
	}
	return v
}
F
function

ResolveAll

ResolveAll finds all named dependencies that implement the given interface T.

Parameters

Returns

[]T
pkg/di/resolve.go:27-41
func ResolveAll[T any](c *Container) []T

{
	c.mu.RLock()
	defer c.mu.RUnlock()

	var result []T
	for _, v := range c.named {
		if lp, ok := v.(*lazyProvider); ok {
			v = lp.get()
		}
		if t, ok := v.(T); ok {
			result = append(result, t)
		}
	}
	return result
}