gioc

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 9, 2026 License: MIT Imports: 4 Imported by: 0

README

   █████████  █████    ███████      █████████    
  ███░░░░░███░░███   ███░░░░░███   ███░░░░░███   
 ███     ░░░  ░███  ███     ░░███ ███     ░░░    
░███          ░███ ░███      ░███░███            
░███    █████ ░███ ░███      ░███░███            
░░███  ░░███  ░███ ░░███     ███ ░░███     ███   
 ░░█████████  █████ ░░░███████░   ░░█████████    
  ░░░░░░░░░  ░░░░░    ░░░░░░░      ░░░░░░░░░     
    

A lightweight, type-safe inversion-of-control container for Go.

gioc organises dependencies into modules, resolves the full dependency graph at startup, and wires every provider in topological order — catching circular dependencies before your application runs.

go get github.com/0x626f/gioc

Releases are tagged with semantic versioning. Pin a release with:

go get github.com/0x626f/gioc@v0.1.0

Concepts

Concept Description
Token A string that uniquely identifies a provider within a module. Derived automatically from the type name or set explicitly.
Provider Describes how to create one dependency — either a pre-built value (ValueProvider) or a constructor function (FactoryProvider).
Module Groups related providers. A module can import other modules to access their providers.
Container Holds all modules, validates the graph, and instantiates everything on Run.
Scope Singleton (one shared instance) or Prototype (new instance per consumer).

Quick start

package main

import (
    "fmt"
    "github.com/0x626f/gioc"
)

type Config struct{ DSN string }
type DB     struct{ DSN string }
type App    struct{ DB *DB }

func main() {
    configToken := "Config"
    dbToken     := "DB"

    mod := gioc.NewModule("app")
    mod.Provide(
        gioc.ValueProvider[*Config](configToken, &Config{DSN: "postgres://localhost/mydb"}, false),

        gioc.FactoryProvider[*DB](dbToken, gioc.Factory[*DB]{
            Injects:    gioc.Inject(configToken),
            ValueScope: gioc.Singleton,
            Constructor: func(deps gioc.Injections) (*DB, error) {
                cfg, err := gioc.Resolve[*Config](configToken, deps)
                if err != nil {
                    return nil, err
                }
                return &DB{DSN: cfg.DSN}, nil
            },
        }, false),

        gioc.FactoryProvider[*App]("", gioc.Factory[*App]{
            Injects:    gioc.Inject(dbToken),
            ValueScope: gioc.Singleton,
            Constructor: func(deps gioc.Injections) (*App, error) {
                db, err := gioc.Resolve[*DB](dbToken, deps)
                if err != nil {
                    return nil, err
                }
                return &App{DB: db}, nil
            },
        }, false),
    )

    c := gioc.NewContainer()
    c.AddModules(mod)
    if err := c.Run(); err != nil {
        panic(err)
    }

    app, err := gioc.Get[*App](c, "App", mod)
    if err != nil {
        panic(err)
    }

    fmt.Println("container wired successfully", app.DB.DSN)
}

Providers

ValueProvider

Wraps an already-constructed value. Always Singleton — every consumer receives the same pointer.

gioc.ValueProvider[*Config]("", &Config{DSN: "..."}, true)
//                           ^     ^                  ^
//                           |     value              exportable
//                           token (empty = derived from type)

FactoryProvider

Constructs the instance via a function. Supports both Singleton and Prototype scopes.

gioc.FactoryProvider[*Service]("", gioc.Factory[*Service]{
    Injects:     gioc.Inject("Logger", "Database"),
    ValueScope:  gioc.Singleton,
    Constructor: func(deps gioc.Injections) (*Service, error) {
        log, _ := gioc.Resolve[*Logger]("Logger", deps)
        db,  _ := gioc.Resolve[*Database]("Database", deps)
        return &Service{Log: log, DB: db}, nil
    },
}, false)

The same factory can be built with NewFactory:

factory := gioc.NewFactory[*Service](
    gioc.Inject("Logger", "Database"),
    gioc.Singleton,
    func(deps gioc.Injections) (*Service, error) {
        log, _ := gioc.Resolve[*Logger]("Logger", deps)
        db,  _ := gioc.Resolve[*Database]("Database", deps)
        return &Service{Log: log, DB: db}, nil
    },
)

Token auto-derivation

When the token argument is an empty string, it is derived from the type name:

gioc.CreateToken[*MyService]() // → "MyService"
gioc.CreateToken[MyService]()  // → "MyService"

Modules

infraMod := gioc.NewModule("infra")
infraMod.Provide(
    gioc.ValueProvider[*Logger]("", &Logger{}, true), // exported
)

appMod := gioc.NewModule("app")
appMod.Import(infraMod)   // providers of infraMod are visible here
appMod.Provide(
    gioc.FactoryProvider[*Service]("", gioc.Factory[*Service]{
        Injects: gioc.Inject(gioc.CreateToken[Logger]()),
        Constructor: func(deps gioc.Injections) (*Service, error) {
            log, _ := gioc.Resolve[*Logger](gioc.CreateToken[Logger](), deps)
            return &Service{Log: log}, nil
        },
    }, false),
)

Global modules

Mark a module as global to make its providers available to every other module in the container — no explicit Import call needed.

sharedMod := gioc.NewModule("shared").Global()
sharedMod.Provide(gioc.ValueProvider[*Logger]("", &Logger{}, false))

appMod := gioc.NewModule("app") // no Import(sharedMod) required
appMod.Provide(gioc.FactoryProvider[*Service]("", gioc.Factory[*Service]{
    Injects: gioc.Inject(gioc.CreateToken[Logger]()),
    Constructor: func(deps gioc.Injections) (*Service, error) {
        log, _ := gioc.Resolve[*Logger](gioc.CreateToken[Logger](), deps)
        return &Service{Log: log}, nil
    },
}, false))

c := gioc.NewContainer()
c.AddModules(sharedMod, appMod) // sharedMod's Logger is injected into appMod automatically
c.Run()

Global modules are initialised before regular modules, so their singletons are ready when the rest of the container starts up. If the same token is registered both locally and in a global module, the local provider takes priority.

Chaining

NewModule, Global, Import, and Provide all return *Module, so they can be chained:

gioc.NewModule("app").
    Import(infraMod).
    Provide(providerA, providerB)

Resolving dependencies in constructors

Resolve — find by token in Injections

Constructor: func(deps gioc.Injections) (*Service, error) {
    db, err := gioc.Resolve[*Database]("Database", deps)
    if err != nil {
        return nil, err
    }
    return &Service{DB: db}, nil
},

Use MustResolve when a missing or incorrectly typed dependency should panic:

Constructor: func(deps gioc.Injections) (*Service, error) {
    db := gioc.MustResolve[*Database]("Database", deps)
    return &Service{DB: db}, nil
},

Container.Resolve — fetch a raw provider after Run

Singleton providers return the instance created during Run; prototype providers create a fresh instance for each call.

inj, err := c.Resolve("Database", appMod)
if err != nil {
    return err
}
_ = inj

Get — fetch and unwrap after Run

Get combines Container.Resolve with type assertion when callers want a typed value directly. If no module context is provided, it searches the container's root modules in registration order.

db, err := gioc.Get[*Database](c, "Database", appMod)
if err != nil {
    return err
}

Require — panic-guard at the top of a constructor

Constructor: func(deps gioc.Injections) (*Service, error) {
    gioc.Require(deps, "Logger", "Database") // panics if either is missing
    ...
},

Injections also exposes methods for non-generic lookup and validation:

instance, err := deps.Resolve("Logger")
required := deps.MustResolve("Database")
deps.Require("Logger", "Database")

Scopes

// Singleton — one instance shared across all consumers
gioc.Factory[*Pool]{ValueScope: gioc.Singleton, Constructor: ...}

// Prototype — new instance created for every consumer
gioc.Factory[*Request]{ValueScope: gioc.Prototype, Constructor: ...}

Error handling

Run returns typed errors that can be inspected:

if err := c.Run(); err != nil {
    var cycleErr *gioc.CircularInjectionError
    var depErr   *gioc.DependencyError
    switch {
    case errors.As(err, &cycleErr):
        log.Fatalf("cycle detected: %v", cycleErr)
    case errors.As(err, &depErr):
        log.Fatalf("missing dependency: %v", depErr)
    }
}
Error type Cause
CircularInjectionError A cycle was found in the module import graph or the provider dependency graph.
DependencyError A required token is not registered, a constructor returned an error, or an Injection could not be cast to the expected type.

Full wiring example

type AppConfig struct{ DSN string }
type DBPool   struct{ DSN string }
type UserRepo struct{ Pool *DBPool }
type UserSvc  struct{ Repo *UserRepo }

const (
    tokenConfig  = "AppConfig"
    tokenPool    = "DBPool"
    tokenRepo    = "UserRepo"
    tokenSvc     = "UserSvc"
)

// infrastructure module — global, AppConfig is visible to all modules automatically
infraMod := gioc.NewModule("infra").Global()
infraMod.Provide(
    gioc.ValueProvider[*AppConfig](tokenConfig, &AppConfig{DSN: "postgres://prod"}, false),
)

// services module — no Import needed; AppConfig comes from the global infra module
svcMod := gioc.NewModule("services")
svcMod.Provide(
    gioc.FactoryProvider[*DBPool](tokenPool, gioc.Factory[*DBPool]{
        Injects:    gioc.Inject(tokenConfig),
        ValueScope: gioc.Singleton,
        Constructor: func(deps gioc.Injections) (*DBPool, error) {
            cfg, _ := gioc.Resolve[*AppConfig](tokenConfig, deps)
            return &DBPool{DSN: cfg.DSN}, nil
        },
    }, true),
    gioc.FactoryProvider[*UserRepo](tokenRepo, gioc.Factory[*UserRepo]{
        Injects:    gioc.Inject(tokenPool),
        ValueScope: gioc.Singleton,
        Constructor: func(deps gioc.Injections) (*UserRepo, error) {
            pool, _ := gioc.Resolve[*DBPool](tokenPool, deps)
            return &UserRepo{Pool: pool}, nil
        },
    }, true),
    gioc.FactoryProvider[*UserSvc](tokenSvc, gioc.Factory[*UserSvc]{
        Injects:    gioc.Inject(tokenRepo),
        ValueScope: gioc.Singleton,
        Constructor: func(deps gioc.Injections) (*UserSvc, error) {
            repo, _ := gioc.Resolve[*UserRepo](tokenRepo, deps)
            return &UserSvc{Repo: repo}, nil
        },
    }, true),
)

c := gioc.NewContainer()
c.AddModules(infraMod, svcMod)
if err := c.Run(); err != nil {
    log.Fatal(err)
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Get

func Get[T any](container *Container, token Token, modules ...*Module) (T, error)

Get resolves token from the container as T.

func MustResolve

func MustResolve[T any](token Token, injections Injections) T

MustResolve returns token from injections as T or panics.

func Require

func Require(injections Injections, tokens ...Token)

Require panics if any token is missing from injections.

func Resolve

func Resolve[T any](token Token, injections Injections) (T, error)

Resolve returns token from injections as T.

Types

type CircularInjectionError

type CircularInjectionError struct {

	// Tokens is the cycle path.
	Tokens []Token
	// contains filtered or unexported fields
}

CircularInjectionError reports a module or provider cycle.

func (*CircularInjectionError) Error

func (err *CircularInjectionError) Error() string

Error returns the formatted cycle error.

type Container

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

Container owns modules and resolves provider dependencies.

func NewContainer

func NewContainer() *Container

NewContainer returns an empty Container.

func (*Container) AddModules

func (container *Container) AddModules(modules ...*Module) error

AddModules registers modules before Run.

func (*Container) Resolve

func (container *Container) Resolve(token Token, modules ...*Module) (*Injection, error)

Resolve returns token from the given modules after Run.

func (*Container) Run

func (container *Container) Run() (err error)

Run validates modules and creates provider instances.

type DependencyError

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

DependencyError reports an invalid or missing dependency.

func (*DependencyError) Error

func (err *DependencyError) Error() string

Error returns the formatted dependency error.

type Factory

type Factory[T any] struct {
	// Injects lists constructor dependency tokens.
	Injects []Token

	// ValueScope selects Singleton or Prototype behavior.
	ValueScope Scope

	// Constructor builds the value from resolved dependencies.
	Constructor func(Injections) (T, error)
}

Factory defines how to build a provider value.

func NewFactory

func NewFactory[T any](injects []Token, valueScope Scope, constructor func(Injections) (T, error)) Factory[T]

NewFactory returns a Factory.

type FactoryProviderInjection

type FactoryProviderInjection[T any] struct {
	Key     Token
	Export  bool
	Factory Factory[T]
	// contains filtered or unexported fields
}

FactoryProviderInjection provides values from a Factory.

func FactoryProvider

func FactoryProvider[T any](token Token, factory Factory[T], exportable bool) *FactoryProviderInjection[T]

FactoryProvider returns a provider backed by factory.

func (*FactoryProviderInjection[T]) AssignOn

func (provider *FactoryProviderInjection[T]) AssignOn(module *Module)

AssignOn binds the provider to module.

func (*FactoryProviderInjection[T]) AssignedTo

func (provider *FactoryProviderInjection[T]) AssignedTo() *Module

AssignedTo returns the owning module.

func (*FactoryProviderInjection[T]) Create

func (provider *FactoryProviderInjection[T]) Create(injections Injections) (*Injection, error)

Create builds or returns the provider value.

func (*FactoryProviderInjection[T]) Exportable

func (provider *FactoryProviderInjection[T]) Exportable() bool

Exportable reports whether importers can use this provider.

func (*FactoryProviderInjection[T]) Injections

func (provider *FactoryProviderInjection[T]) Injections() []Token

Injections returns the factory dependency tokens.

func (*FactoryProviderInjection[T]) Scope

func (provider *FactoryProviderInjection[T]) Scope() Scope

Scope returns the configured lifecycle.

func (*FactoryProviderInjection[T]) Token

func (provider *FactoryProviderInjection[T]) Token() Token

Token returns the configured or derived provider token.

type IProvider

type IProvider interface {
	// Token returns the provider token.
	Token() Token

	// Injections returns dependency tokens required by Create.
	Injections() []Token

	// Create builds the provider value from resolved dependencies.
	Create(Injections) (*Injection, error)

	// Exportable reports whether importers can use this provider.
	Exportable() bool

	// Scope returns the provider lifecycle.
	Scope() Scope

	// AssignOn binds the provider to a module.
	AssignOn(module *Module)

	// AssignedTo returns the owning module.
	AssignedTo() *Module
}

IProvider describes a value the container can create.

type Injection

type Injection struct {
	Token    Token
	Instance any
}

Injection stores a resolved instance with its token.

type Injections

type Injections []*Injection

Injections is the dependency set passed to a factory constructor.

func (Injections) MustResolve

func (injections Injections) MustResolve(token Token) any

MustResolve returns token from injections as any or panics.

func (Injections) Require

func (injections Injections) Require(tokens ...Token)

Require panics if any token is missing from injections.

func (Injections) Resolve

func (injections Injections) Resolve(token Token) (any, error)

Resolve returns token from injections as any.

type Module

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

Module groups providers and imports.

func NewModule

func NewModule(token Token) *Module

NewModule returns an empty module with token.

func (*Module) Global

func (module *Module) Global() *Module

Global makes this module visible to all modules.

func (*Module) Import

func (module *Module) Import(modules ...*Module) *Module

Import makes exported providers from modules visible here.

func (*Module) Provide

func (module *Module) Provide(providers ...IProvider) *Module

Provide registers providers on this module.

func (*Module) Token

func (module *Module) Token() Token

Token returns the module token.

type Scope

type Scope uint8

Scope controls provider instance reuse.

const (
	// Singleton reuses one instance.
	Singleton Scope = iota

	// Prototype creates a new instance per request.
	Prototype
)

type Token

type Token = string

Token identifies a provider or module.

func CreateToken

func CreateToken[T any]() Token

CreateToken returns the struct name of T as a Token.

func Inject

func Inject(injections ...Token) []Token

Inject returns dependency tokens for a factory.

func NewToken

func NewToken(token string) Token

NewToken returns token as a Token.

type Tokenized

type Tokenized interface {
	Token() Token
}

Tokenized exposes a Token.

type ValueProviderInjection

type ValueProviderInjection[T any] struct {
	Key    Token
	Export bool
	Value  T
	// contains filtered or unexported fields
}

ValueProviderInjection provides an existing singleton value.

func ValueProvider

func ValueProvider[T any](token Token, value T, exportable bool) *ValueProviderInjection[T]

ValueProvider returns a singleton provider for value.

func (*ValueProviderInjection[T]) AssignOn

func (provider *ValueProviderInjection[T]) AssignOn(module *Module)

AssignOn binds the provider to module.

func (*ValueProviderInjection[T]) AssignedTo

func (provider *ValueProviderInjection[T]) AssignedTo() *Module

AssignedTo returns the owning module.

func (*ValueProviderInjection[T]) Create

func (provider *ValueProviderInjection[T]) Create(Injections) (*Injection, error)

Create returns the stored value as an Injection.

func (*ValueProviderInjection[T]) Exportable

func (provider *ValueProviderInjection[T]) Exportable() bool

Exportable reports whether importers can use this provider.

func (*ValueProviderInjection[T]) Injections

func (provider *ValueProviderInjection[T]) Injections() []Token

Injections returns nil for value providers.

func (*ValueProviderInjection[T]) Scope

func (provider *ValueProviderInjection[T]) Scope() Scope

Scope returns Singleton for value providers.

func (*ValueProviderInjection[T]) Token

func (provider *ValueProviderInjection[T]) Token() Token

Token returns the configured or derived provider token.

Jump to

Keyboard shortcuts

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