iocdi

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2025 License: MIT Imports: 6 Imported by: 2

README

7Q-Station-Manager: IoC/DI module

ci

A small, reflection-based dependency injection container for Go. It lets you:

  • Register beans by ID either by type (structs normalized to pointers) or by instance
  • Discover dependencies from struct fields tagged with di.inject:"<id>"
  • Build the container to instantiate and inject dependencies
  • Optionally source string literals (like config paths) via a global LiteralProvider
  • Resolve beans safely at runtime, including a generic helper ResolveAs[T]

This project is intentionally minimal and aims to be straightforward to read and extend.

Installation

  • Go 1.20+
  • Add to your project: go get github.com/ColonelBlimp/iocdi

Quick start

  1. Define your types and tag the fields you want injected using di.inject.
    type Service struct {
        Config *Config `di.inject:"ServiceBeanConfig"`
        Logger *Logger `di.inject:"ServiceBeanLogger"`
    }

    type Config struct {
        WorkingDir string `di.inject:"WorkingDir"`
    }

    type Logger struct{ _ byte } // non-zero sized to ensure distinct instances
  1. Register beans and build the container.
    c := iocdi.New()
    _ = c.Register("ServiceBean", reflect.TypeOf((*Service)(nil)))
    _ = c.RegisterInstance("servicebeanconfig", &Config{})
    _ = c.RegisterInstance("ServiceBeanLogger", &Logger{})

    // You can register simple values as instances:
    _ = c.RegisterInstance("workingdir", "/var/app")

    // Finalize (idempotent). Build can also be triggered automatically by ResolveSafe.
    if err := c.Build(); err != nil { panic(err) }
  1. Resolve your service.
    v, err := c.ResolveSafe("bervicebean")
    if err != nil { panic(err) }
    svc := v.(*Service)
    // svc.Config and svc.Logger are injected; svc.Config.WorkingDir == "/var/app"
Generic helper: ResolveAs

Use the generic helper for type-safe resolution:

    logger, err := iocdi.ResolveAs[*Logger](c, "Servicebeanlogger")
    if err != nil { /* handle */ }

LiteralProvider for strings

You can provide string dependencies at injection time without pre-registering them via a global hook:

    iocdi.SetLiteralProvider(func(id string, t reflect.Type) (any, bool, error) {
        if id == "WorkingDir" { return "/workspace", true, nil }
        return nil, false, nil
    })

When Build() runs and encounters a missing string dependency (e.g., WorkingDir), the container will query the provider and inject the returned value. If you later register a bean with the same ID, that takes precedence and the provider is not called.

Note: The LiteralProvider is intended for strings only. You can extend the approach if you need more scalar types.

Registration rules

  • Register(type): supports struct or pointer-to-struct types; simple kinds (e.g., string) are not supported here
  • RegisterInstance(id, value): supports any value; struct values are normalized to pointers for consistent injection
  • Field injection is explicit: only exported fields with the di.inject tag are considered
  • Supported dependency field types:
    • Pointer-to-structs (e.g., *Config)
    • string (optionally fulfilled by LiteralProvider)

Build, resolve, and lifecycle

  • Build is idempotent and populates any missing struct instances
  • Registration is closed after a successful Build
  • ResolveSafe ensures Build is called on first use; Resolve panics on errors (prefer ResolveSafe)

Cycle detection

The container performs DFS-based cycle detection and returns a descriptive error path (e.g., A -> B -> A).

Concurrency notes

  • Build is guarded; registration and build use internal locking
  • Resolution after build uses read locks for safety
  • The global LiteralProvider is stored via atomic.Value for race-free reads and safe updates; set it before building to avoid surprises

Limitations (by design)

  • Only pointer-to-struct and string fields are discovered for injection
  • Interfaces and non-pointer struct fields are not supported by the built-in discovery
  • Tag-only injection: untagged fields are ignored, even if a compatible bean exists

Testing

Run tests with: go test ./...

The suite includes injection scenarios, literal provider behavior, cycle detection, and Resolve/ResolveAs coverage.

License

MIT.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrBeanIdParamIsEmpty   = errors.New("beanID parameter is empty")
	ErrBeanTypeParamIsNil   = errors.New("beanType parameter is nil")
	ErrBeanParamIsNil       = errors.New("bean parameter is nil")
	ErrBeanTypeNotSupported = errors.New("beanType is not supported")
	ErrRegistrationClosed   = errors.New("container already built; registration is closed")
)

Functions

func ResolveAs

func ResolveAs[T any](c *Container, beanID string) (T, error)

ResolveAs returns a bean instance by its ID and casts it to type T. It ensures the container is built before resolving and returns an error on failure.

func SetLiteralProvider

func SetLiteralProvider(p LiteralProvider)

SetLiteralProvider installs a global literal provider hook. A typical implementation might read env vars, files, flags, or other configuration sources.

Types

type Container

type Container struct {
	// contains filtered or unexported fields
}

func New

func New() *Container

func (*Container) Build

func (c *Container) Build() (err error)

Build finalizes the container by verifying all required dependencies are registered, instantiating all registered beans, and injecting dependencies.

If the container has already been built, this method is a no-op.

func (*Container) Register

func (c *Container) Register(beanID string, beanType reflect.Type) error

Register registers a bean by its reflect.Type. If the type is a struct, it will be normalized to a pointer-to-struct for consistent injection semantics. The 'beanID' parameter is case-sensitive with regard to the bean identifier and the coresponding receiving bean tag. The case of the bean identifier must match the case of the tag in the receiving bean.

This method only supports registering structs and pointers to structs; simple types (e.g., string) must be registered as instances using RegisterInstance.

func (*Container) RegisterInstance

func (c *Container) RegisterInstance(beanID string, instance any) error

RegisterInstance registers a concrete instance for type T. The instance is treated as a singleton. Struct instances are normalized to pointers. The 'beanID' parameter is case-sensitive with regard to the bean identifier and the coresponding receiving bean tag. The case of the bean identifier must match the case of the tag in the receiving bean.

func (*Container) Resolve

func (c *Container) Resolve(beanID string) any

Resolve returns a bean instance by its ID or panics if it cannot be resolved. Prefer ResolveSafe in production code to handle errors gracefully.

func (*Container) ResolveSafe

func (c *Container) ResolveSafe(beanID string) (any, error)

ResolveSafe returns a bean instance by its ID. It ensures the container is built before resolving and returns an error on failure.

type Initializer

type Initializer interface {
	Initialize() error
}

Initializer is an optional interface that a bean may implement to perform additional initialization after all of its dependencies have been injected.

Beans implementing this interface must define Initialize() error. The container will call Initialize() during Build(), after dependency injection has completed. If Initialize returns an error, Build() will fail with that error.

Note: This interface is intentionally defined in the root iocdi package with no imports and no references to internal container types to avoid introducing cyclic dependencies when implemented by beans in other modules/packages.

type LiteralProvider

type LiteralProvider func(id string, targetType reflect.Type) (value any, found bool, err error)

LiteralProvider is a hook invoked when a dependency with a given id is missing. - id: the value of the `di.inject` tag for the missing dependency - targetType: the type expected for that dependency (e.g., reflect.TypeOf("") for string) Returns: - value: the literal value to use for injection - found: whether a value is available - err: any error occurred while sourcing the value (e.g., parsing, I/O)

Jump to

Keyboard shortcuts

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