di
packageAPI reference for the di
package.
Imports
(7)Lifetime
Lifetime controls how services are instantiated.
type Lifetime int
serviceEntry
type serviceEntry struct
Fields
| Name | Type | Description |
|---|---|---|
| lifetime | Lifetime | |
| factory | any | |
| constructor | any | |
| instance | any | |
| built | bool | |
| concreteType | reflect.Type | |
| paramTypes | []reflect.Type |
Uses
ResolveError
ResolveError is returned when a type cannot be resolved.
type ResolveError struct
Methods
Returns
func (*ResolveError) Error() string
{
return fmt.Sprintf("di: cannot resolve type %v", e.Type)
}
Fields
| Name | Type | Description |
|---|---|---|
| Type | reflect.Type |
Container
Container is the dependency injection container.
type Container struct
Methods
Returns
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
}
Parameters
Returns
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
Returns
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
}
Parameters
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 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
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
}
Parameters
func (*Container) Provide(name string, instance any)
{
contracts.MustVerify(instance)
c.mu.Lock()
c.named[name] = instance
c.mu.Unlock()
}
Parameters
Returns
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
}
Parameters
Returns
func (*Container) MustGet(name string) any
{
v, ok := c.Get(name)
if !ok {
panic("di: dependency not found: " + name)
}
return v
}
Parameters
Returns
func (*Container) Has(name string) bool
{
c.mu.RLock()
_, ok := c.named[name]
c.mu.RUnlock()
return ok
}
Parameters
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 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
}
Parameters
func (*Container) ProvideLazy(name string, factory func() any)
{
c.mu.Lock()
c.named[name] = &lazyProvider{factory: factory}
c.mu.Unlock()
}
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
}
Returns
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
Returns
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 |
New
New creates a new empty Container.
Returns
func New() *Container
{
return &Container{
services: make(map[reflect.Type]*serviceEntry),
named: make(map[string]any),
resolving: make(map[reflect.Type]struct{}),
}
}
Builder
Builder constructs a Container with validation.
type Builder struct
Methods
Build creates a Container and validates all registrations. Returns an error if any registered type has unresolvable dependencies.
Returns
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 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
}
Parameters
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 |
NewBuilder
NewBuilder creates a new Builder.
Returns
func NewBuilder() *Builder
{
return &Builder{
services: make(map[reflect.Type]*serviceEntry),
named: make(map[string]any),
}
}
Register
Register adds a service factory to the builder.
Parameters
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()
}
RegisterAs
RegisterAs registers a service factory resolving to a different concrete type.
Parameters
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()
}
RegisterInstance
RegisterInstance registers a pre-created instance as a singleton.
Parameters
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()
}
RegisterImpl
RegisterImpl registers a concrete type T that satisfies interface I.
Parameters
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()
}
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
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()
}
ResolveType
ResolveType resolves a service from the container, panicking on failure.
Parameters
Returns
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)
}
TryResolveType
TryResolveType resolves a service from the container, returning an error on failure.
Parameters
Returns
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
}
tryResolveType
Parameters
Returns
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}
}
}
invokeEntry
Parameters
Returns
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)
}
invokeConstructor
Parameters
Returns
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
}
constructValue
Parameters
Returns
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
}
resolveByType
Parameters
Returns
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)
}
testDB
type testDB struct
Fields
| Name | Type | Description |
|---|---|---|
| Name | string |
testService
type testService struct
Fields
| Name | Type | Description |
|---|---|---|
| DB | *testDB | inject:"db" |
| Logger | string | inject:"logger" |
TestContainer_ProvideAndGet
Parameters
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")
}
}
TestContainer_Has
Parameters
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'")
}
}
TestContainer_Inject
Parameters
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")
}
}
TestContainer_Clone
Parameters
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'")
}
}
TestContainer_Keys
Parameters
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))
}
}
TestContainer_MustGet_Panic
Parameters
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")
}
TestResolve
Parameters
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)
}
}
TestResolve_TypeMismatch
Parameters
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")
}
}
TestMustResolve_Panic
Parameters
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")
}
TestBuilder_RegisterAndResolveType
Parameters
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")
}
}
TestBuilder_TransientLifetime
Parameters
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")
}
}
TestBuilder_SingletonLifetime
Parameters
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")
}
}
TestResolveType_PanicOnMissing
Parameters
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)
}
TestBuilder_ProvideNamed
Parameters
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")
}
}
Worker
type Worker interface
Methods
BrokenWorker
type BrokenWorker struct
TestContainer_ProvideWithContracts
Parameters
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{})
})
}
TestResolveAll
Parameters
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")
}
}
TestContainer_Scope
Parameters
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")
}
}
TestContainer_ProvideLazy
Parameters
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")
}
}
Config
type Config struct
Fields
| Name | Type | Description |
|---|---|---|
| DSN | string |
NewUserService
Returns
func NewUserService(db *testDB, cfg *Config) UserService
{
return UserService{DB: db, Cfg: cfg}
}
Uses
TestRegisterFromFunc
Parameters
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")
}
}
TestRegisterFromFunc_MissingDep
Parameters
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")
}
}
closableService
type closableService struct
Methods
Fields
| Name | Type | Description |
|---|---|---|
| closed | bool |
TestScopedContainer_Close
Parameters
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()")
}
}
TestContainer_Close_Singleton
Parameters
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")
}
}
TestContainer_Close_NonCloser
Parameters
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)
}
}
Resolve
Resolve retrieves a typed dependency from the container by name.
Parameters
Returns
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
}
MustResolve
MustResolve retrieves a typed dependency by name and panics if not found.
Parameters
Returns
func MustResolve[T any](c *Container, name string) T
{
v, ok := Resolve[T](c, name)
if !ok {
panic("di: cannot resolve " + name)
}
return v
}
ResolveAll
ResolveAll finds all named dependencies that implement the given interface T.
Parameters
Returns
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
}