braider is a go/analysis analyzer that resolves dependency injection (DI) bindings and generates constructors and bootstrap wiring using analysis.SuggestedFix. It integrates with the standard Go toolchain, produces plain Go code with no runtime container, and is inspired by google/wire.
- Compile-time DI validation with actionable diagnostics
- Constructor generation for structs annotated with
annotation.Injectable[T] - Provider registration via
annotation.Provide[T](fn) - Variable registration via
annotation.Variable[T](value)for pre-existing variables - Interface-typed dependencies via
inject.Typed[I],provide.Typed[I], andvariable.Typed[I] - Named dependencies via
inject.Named[N],provide.Named[N], andvariable.Named[N] - Custom constructors via
inject.WithoutConstructor - Field-level DI control via
braiderstruct tags (braider:"name"/braider:"-") - App options:
app.Default(anonymous struct) andapp.Container[T](user-defined container) - Bootstrap wiring generated from a dependency graph in topological order
- Works with
braider -fixfor one-shot application of suggested fixes
Requires go 1.25+.
go install github.com/miyamo2/braider/cmd/braider@latestOr, add it as a tool dependency
go get -tool github.com/miyamo2/braider/cmd/braider@latestOr, install via Homebrew
brew install miyamo2/tap/braiderAnnotation types and option packages used in your code are provided by this module.
go get github.com/miyamo2/braider- Add annotations in your code.
- (Optional) Run the analyzer to check for configuration issues
braider ./...- Apply generated constructors and bootstrap wiring
braider -fix ./...annotation.Injectable[inject.Default]— marks structs that need constructor generation and will be exposed from the bootstrap dependency struct.annotation.Provide[provide.Default](fn)— registers provider functions exposed as fields in the bootstrap dependency struct.annotation.Variable[variable.Default](value)— registers a pre-existing variable or package-qualified identifier (e.g.,os.Stdout) as a DI dependency without generating a constructor.annotation.App[app.Default](main)— marks the entry point where bootstrap code is generated. Useapp.Defaultfor an anonymous struct orapp.Container[T]for a user-defined container type.
Injectable[T], Provide[T], and Variable[T] accept option interfaces to customize registration:
| Option | Injectable | Provide | Variable | Description |
|---|---|---|---|---|
Default |
inject.Default |
provide.Default |
variable.Default |
Default registration. |
Typed[I] |
inject.Typed[I] |
provide.Typed[I] |
variable.Typed[I] |
Register as interface type I instead of the concrete type. |
Named[N] |
inject.Named[N] |
provide.Named[N] |
variable.Named[N] |
Register with name N.Name(). N must implement namer.Namer and return a string literal. |
WithoutConstructor |
inject.WithoutConstructor |
N/A | N/A | Skip constructor generation. You must provide a manual New<Type> function. |
App[T] accepts option interfaces to customize bootstrap output:
| Option | Description |
|---|---|
app.Default |
Generate an anonymous struct with all dependencies as fields (default behavior). |
app.Container[T] |
Generate a bootstrap function that returns the user-defined container type T. Container fields are matched by type; use braider:"name" tags for named dependencies and braider:"-" to exclude fields. |
Injectable[T] struct fields can use braider struct tags for field-level DI control:
| Tag | Description |
|---|---|
braider:"<name>" |
Resolve this field using the named dependency matching <name>. |
braider:"-" |
Exclude this field from dependency injection entirely. |
Fields without a braider tag are resolved by type as usual.
Named dependency injection — use braider:"<name>" to wire a specific named provider/injector/variable to a field:
type PrimaryRepoName struct{}
func (PrimaryRepoName) Name() string { return "primaryRepo" }
var _ = annotation.Provide[provide.Named[PrimaryRepoName]](NewUserRepository)
func NewUserRepository() *UserRepository { return &UserRepository{} }
type AppService struct {
annotation.Injectable[inject.Default]
repo *UserRepository `braider:"primaryRepo"`
}Field exclusion — use braider:"-" to keep a field out of DI:
type AppService struct {
annotation.Injectable[inject.Default]
logger Logger
mu sync.Mutex `braider:"-"`
}The generated constructor will only accept logger as a parameter; mu is ignored.
Struct tags can be combined freely — some fields tagged with names, some excluded, and others resolved by type:
type AppService struct {
annotation.Injectable[inject.Default]
repo *UserRepository `braider:"primaryRepo"`
logger Logger
mu sync.Mutex `braider:"-"`
}Mixed options are supported by embedding multiple option interfaces in a single anonymous interface:
type MixedRepository struct {
annotation.Injectable[interface {
inject.Typed[Repository]
inject.Named[RepositoryName]
}]
}Custom Namer types must return a hardcoded string literal from Name():
type PrimaryDBName struct{}
func (PrimaryDBName) Name() string { return "primaryDB" }annotation.Variable[T](value) registers a pre-existing variable or package-qualified identifier as a DI dependency. Unlike Injectable and Provide, no constructor is generated or invoked — the value is used directly in the bootstrap code.
Supported argument expressions: identifiers (myVar) and package-qualified selectors (os.Stdout). Literals, function calls, and composite literals are not supported.
import (
"os"
"github.com/miyamo2/braider/pkg/annotation"
"github.com/miyamo2/braider/pkg/annotation/variable"
)
// Register os.Stdout as a *os.File dependency.
var _ = annotation.Variable[variable.Default](os.Stdout)In the generated bootstrap code, the variable is referenced directly (e.g., out := os.Stdout when used as a dependency, or _ = os.Stdout when not depended upon).
annotation.App[T](main) is generic — the type parameter T controls the shape of the bootstrap output.
app.Default — generates an anonymous struct with all dependencies as fields:
var _ = annotation.App[app.Default](main)app.Container[T] — generates a bootstrap IIFE that returns a user-defined container type T:
var _ = annotation.App[app.Container[struct {
Svc *Service
}]](main)Container fields are matched against registered dependencies by type. Use braider:"name" tags to match named dependencies. Note that braider:"-" is not permitted on container fields (it is a validation error):
var _ = annotation.App[app.Container[struct {
Primary *Database `braider:"primaryDB"`
Replica *Database `braider:"replicaDB"`
}]](main)The container type can also be a named struct from another package:
var _ = annotation.App[app.Container[container.AppContainer]](main)package main
import (
"time"
"github.com/miyamo2/braider/pkg/annotation"
"github.com/miyamo2/braider/pkg/annotation/app"
"github.com/miyamo2/braider/pkg/annotation/inject"
"github.com/miyamo2/braider/pkg/annotation/provide"
)
type Clock interface {
Now() time.Time
}
type realClock struct{}
func (realClock) Now() time.Time { return time.Now() }
var _ = annotation.Provide[provide.Typed[Clock]](NewClock)
func NewClock() *realClock { return &realClock{} }
type Service struct {
annotation.Injectable[inject.Default]
Clock Clock
}
var _ = annotation.App[app.Default](main)
func main() {}Following braider -fix ./..., the generated bootstrap code will look like this
package main
import (
"time"
"github.com/miyamo2/braider/pkg/annotation"
"github.com/miyamo2/braider/pkg/annotation/app"
"github.com/miyamo2/braider/pkg/annotation/inject"
"github.com/miyamo2/braider/pkg/annotation/provide"
)
type Clock interface {
Now() time.Time
}
type realClock struct{}
func (realClock) Now() time.Time { return time.Now() }
var _ = annotation.Provide[provide.Typed[Clock]](NewClock)
func NewClock() *realClock { return &realClock{} }
type Service struct {
annotation.Injectable[inject.Default]
Clock Clock
}
// NewService is a constructor for Service.
//
// Generated by braider. DO NOT EDIT.
func NewService(clock Clock) *Service {
return &Service{
Clock: clock,
}
}
var _ = annotation.App[app.Default](main)
func main() {
_ = dependency
}
// braider:hash:<hash>
var dependency = func() struct {
clock Clock
service *Service
} {
clock := NewClock()
service := NewService(clock)
return struct {
clock Clock
service *Service
}{
clock: clock,
service: service,
}
}()- Typed inject -- register a struct as an interface type with
inject.Typed[I] - Named inject -- register multiple instances with different names via
inject.Named[N] - Without constructor -- skip constructor generation with
inject.WithoutConstructor - Mixed options -- combine
Typed[I]andNamed[N]in a single annotation - Provide typed -- register a provider function as an interface type with
provide.Typed[I] - Variable -- register a pre-existing variable as a DI dependency with
annotation.Variable[T] - Struct tag named -- inject a named dependency into a specific field with
braider:"<name>" - Struct tag exclude -- exclude a field from DI with
braider:"-" - Container (anonymous) -- use
app.Container[T]with an anonymous struct as the bootstrap output - Container (named type) -- use
app.Container[T]with a named container type from another package
Issues and pull requests are welcome. Please keep changes focused and add tests where applicable (go test ./...).
MIT. See LICENSE.