evals/evals.json
[
{
"id": 1,
"name": "param-objects-many-deps",
"description": "Tests use of dig.In parameter objects when a constructor has many dependencies",
"prompt": "I'm wiring a Go service with uber-go/dig. I have a NewServer constructor that needs *zap.Logger, *sql.DB, *redis.Client, *Config, and *MetricsRegistry. The signature is getting unwieldy. How should I clean it up?",
"trap": "Without the skill, the model keeps the long signature or wraps args in an ad-hoc struct without dig.In, missing the parameter-object pattern that lets the container fill the fields.",
"assertions": [
{"id": "1.1", "text": "Embeds dig.In in the parameter struct"},
{"id": "1.2", "text": "Constructor takes the params struct as a single argument"},
{"id": "1.3", "text": "Does NOT just keep the long parameter list as the answer"},
{"id": "1.4", "text": "Does NOT use a plain struct without dig.In (which would not be filled by the container)"},
{"id": "1.5", "text": "Mentions readability/maintainability benefit (adding a new dep is a one-line change)"}
]
},
{
"id": 2,
"name": "value-groups-for-handlers",
"description": "Tests value groups when many constructors must contribute to one slice",
"prompt": "In my Go HTTP server wired with uber-go/dig, I have several constructors (NewUserHandler, NewPostHandler, NewHealthHandler) and a NewRouter that should consume all of them. Each handler is registered independently. How do I wire this without the router knowing which handlers exist?",
"trap": "Without the skill, the model invokes each handler individually inside main() and passes a slice to NewRouter, missing the group:\"...\" tag that decouples producers from consumers.",
"assertions": [
{"id": "2.1", "text": "Each handler constructor returns a dig.Out struct (or uses dig.Group via Provide option) tagged with group:\"routes\" (or similar group name)"},
{"id": "2.2", "text": "NewRouter consumes a dig.In with a slice field tagged group:\"routes\""},
{"id": "2.3", "text": "Handlers are added with c.Provide — no manual slice assembly in main()"},
{"id": "2.4", "text": "Does NOT manually assemble the handler slice and pass it to NewRouter"},
{"id": "2.5", "text": "Mentions that group order is not guaranteed (or is silent on it; does NOT claim a specific order is guaranteed)"}
]
},
{
"id": 3,
"name": "named-values-multiple-dbs",
"description": "Tests dig.Name for multiple instances of the same type",
"prompt": "My Go app needs two *sql.DB connections — one to the primary write database and one to a read replica. Both use the same *sql.DB type. Show me how to register and consume them with uber-go/dig.",
"trap": "Without the skill, the model wraps the two connections in different types (struct PrimaryDB / struct ReadOnlyDB) instead of using dig.Name on a single type.",
"assertions": [
{"id": "3.1", "text": "Uses dig.Name(\"...\") on c.Provide for at least one of the two databases (or uses dig.Out result tags name:\"...\")"},
{"id": "3.2", "text": "Both providers register the same *sql.DB type, distinguished by name"},
{"id": "3.3", "text": "Consumer uses dig.In with name:\"primary\" / name:\"readonly\" tags"},
{"id": "3.4", "text": "Does NOT introduce wrapper types like type PrimaryDB *sql.DB just to disambiguate"},
{"id": "3.5", "text": "Does NOT register both as plain *sql.DB without names (which dig rejects at Provide time)"}
]
},
{
"id": 4,
"name": "as-to-hide-concrete",
"description": "Tests dig.As to expose only an interface to consumers",
"prompt": "I have a *PostgresDB concrete struct in my Go code that has many internal fields and methods. I want consumers to depend only on a Database interface (Query, Exec). How do I register it with uber-go/dig so consumers can never accidentally see the concrete type?",
"trap": "Without the skill, the model registers the constructor returning Database (interface) directly, which works but loses type info; or writes a separate adapter constructor — missing dig.As that does this in one line.",
"assertions": [
{"id": "4.1", "text": "Uses dig.As(new(Database)) as a Provide option on the *PostgresDB constructor"},
{"id": "4.2", "text": "The constructor itself returns the concrete *PostgresDB"},
{"id": "4.3", "text": "Consumers ask for the Database interface, not *PostgresDB"},
{"id": "4.4", "text": "Does NOT write a separate wrapper/adapter constructor that just returns the interface"},
{"id": "4.5", "text": "Mentions or demonstrates that only the interface is exposed in the graph"}
]
},
{
"id": 5,
"name": "scopes-for-request-locals",
"description": "Tests scopes for per-request dependencies",
"prompt": "In my Go web app wired with uber-go/dig, I have global services (logger, database) and per-request data (current user, request ID). How do I keep request-scoped values from leaking across concurrent requests?",
"trap": "Without the skill, the model registers everything in the root container and uses context.Value for per-request data — missing dig.Scope which provides isolated child containers.",
"assertions": [
{"id": "5.1", "text": "Uses c.Scope(\"request\") (or root.Scope) to create a child container per request"},
{"id": "5.2", "text": "Global services (logger, database) stay registered on the root"},
{"id": "5.3", "text": "Per-request providers are registered on the request scope, not on the root"},
{"id": "5.4", "text": "Mentions that the child scope inherits root providers"},
{"id": "5.5", "text": "Does NOT register per-request providers globally where they would be shared across requests"}
]
},
{
"id": 6,
"name": "container-not-passed-around",
"description": "Tests that the container stays at the composition root",
"prompt": "I'm using uber-go/dig in my Go service. My UserHandler depends on UserService and AuditLogger. Should I inject *dig.Container into UserHandler so it can resolve its own dependencies on demand?",
"trap": "Without the skill, the model agrees to pass the container, turning it into a service-locator anti-pattern that hides dependencies and breaks testability.",
"assertions": [
{"id": "6.1", "text": "Advises against passing *dig.Container into business code"},
{"id": "6.2", "text": "States that the container should only live at the composition root (main / startup)"},
{"id": "6.3", "text": "Recommends UserHandler take typed dependencies (UserService, AuditLogger) as constructor parameters"},
{"id": "6.4", "text": "Mentions service locator anti-pattern OR explains the testability/visibility downside"},
{"id": "6.5", "text": "Does NOT show example code that injects the container into a handler"}
]
},
{
"id": 7,
"name": "graph-validation-dryrun",
"description": "Tests dig.DryRun for validating the graph in CI without running constructors",
"prompt": "I want a Go test that catches missing-provider errors and cycles in my uber-go/dig wiring without actually starting database connections, HTTP servers, or any real side effects. How do I write that test?",
"trap": "Without the skill, the model spins up a real container with real constructors, or skips validation entirely — missing dig.DryRun(true) which validates types without invocation.",
"assertions": [
{"id": "7.1", "text": "Uses dig.New(dig.DryRun(true)) to build the test container"},
{"id": "7.2", "text": "Registers the same Provides as the production binary"},
{"id": "7.3", "text": "Calls c.Invoke against the composition root (or top-level dependency) to trigger validation"},
{"id": "7.4", "text": "Asserts no error is returned"},
{"id": "7.5", "text": "Does NOT actually instantiate real services in the test"}
]
},
{
"id": 8,
"name": "decorate-not-rewrite",
"description": "Tests Decorate to wrap an existing value (e.g., logger) instead of replacing the constructor",
"prompt": "In my Go app using uber-go/dig, I want every component in the 'worker' module to receive a *zap.Logger that is named 'worker' (i.e., adds a 'logger':'worker' field). Other modules should keep the unnamed logger. What's the cleanest way?",
"trap": "Without the skill, the model rewrites NewLogger or wraps every constructor manually — missing c.Decorate which transforms the value at the scope/module boundary.",
"assertions": [
{"id": "8.1", "text": "Uses Decorate (c.Decorate or scope.Decorate) on *zap.Logger"},
{"id": "8.2", "text": "The decorator returns log.Named(\"worker\") (or equivalent)"},
{"id": "8.3", "text": "Decorate is applied at the worker scope/module, not globally"},
{"id": "8.4", "text": "Does NOT modify or duplicate the original NewLogger constructor"},
{"id": "8.5", "text": "Mentions decorator scope semantics (applies to scope and descendants only) OR places Decorate inside a scope"}
]
},
{
"id": 9,
"name": "panic-recovery-option",
"description": "Tests RecoverFromPanics container option",
"prompt": "Some third-party constructors I'm registering with uber-go/dig occasionally panic on invalid configuration. I want my Go app to convert those panics into error returns from c.Invoke instead of crashing the process. How do I configure the container?",
"trap": "Without the skill, the model wraps every constructor in defer/recover — missing dig.RecoverFromPanics() which does this at the container level.",
"assertions": [
{"id": "9.1", "text": "Uses dig.New(dig.RecoverFromPanics()) (or passes the option to dig.New)"},
{"id": "9.2", "text": "Mentions or shows that the panic surfaces as a typed dig.PanicError"},
{"id": "9.3", "text": "Does NOT recommend wrapping every constructor in defer recover() manually"},
{"id": "9.4", "text": "Uses errors.As(err, &dig.PanicError{}) or equivalent to detect the panic case"}
]
},
{
"id": 10,
"name": "groups-flatten-tag",
"description": "Tests group:\",flatten\" tag when one constructor produces multiple group entries",
"prompt": "I have a NewMigrations constructor in my Go app (using uber-go/dig) that returns a slice of Migration values. I want every Migration in this slice to be visible to consumers that consume the 'migrations' group as []Migration. How do I do this without nesting?",
"trap": "Without the skill, the model registers []Migration as a single group entry, leaving consumers with [][]Migration. The right answer is the ',flatten' tag on the group.",
"assertions": [
{"id": "10.1", "text": "Uses group:\"migrations,flatten\" tag (with the flatten suffix)"},
{"id": "10.2", "text": "Result struct has a slice field []Migration with the tag"},
{"id": "10.3", "text": "Consumer receives []Migration (flat slice), not [][]Migration"},
{"id": "10.4", "text": "Does NOT silently produce a nested-slice consumer signature"}
]
},
{
"id": 11,
"name": "fx-vs-dig-when-lifecycle",
"description": "Tests recommending fx (instead of raw dig) when the user needs lifecycle/signal handling",
"prompt": "I'm starting a new Go service with uber-go/dig. The service needs to gracefully shut down on SIGTERM, run startup migrations, and start a background worker. Should I implement signal handling and start/stop sequencing on top of dig myself?",
"trap": "Without the skill, the model writes custom signal handling code on top of raw dig — missing that uber-go/fx is built specifically for this and provides fx.Lifecycle, fx.Hook, and signal-aware Run().",
"assertions": [
{"id": "11.1", "text": "Recommends migrating to or considering uber-go/fx for the lifecycle requirements"},
{"id": "11.2", "text": "Mentions that fx is built on top of dig (so existing wiring patterns transfer)"},
{"id": "11.3", "text": "Mentions fx.Lifecycle / fx.Hook (OnStart/OnStop) for graceful boot/shutdown"},
{"id": "11.4", "text": "Mentions app.Run() handling SIGINT/SIGTERM"},
{"id": "11.5", "text": "Does NOT walk the user through writing custom signal handling on top of raw dig as the primary recommendation"}
]
}
]
references/recipes.md
# Recipes — uber-go/dig
End-to-end examples that go beyond the SKILL.md basics. Each recipe is self-contained and shows a real wiring problem.
## HTTP server with route group
```go
package main
import (
"fmt"
"log"
"net/http"
"go.uber.org/dig"
)
// Each handler contributes one route to the "routes" group.
type RouteResult struct {
dig.Out
Route Route `group:"routes"`
}
type Route struct {
Pattern string
Handler http.Handler
}
func NewHealthRoute() RouteResult {
return RouteResult{Route: Route{
Pattern: "/health",
Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}),
}}
}
func NewUserRoute(repo *UserRepo) RouteResult {
return RouteResult{Route: Route{
Pattern: "/users",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
users, err := repo.List(r.Context())
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "%d users", len(users))
}),
}}
}
// The server consumes every Route registered to "routes".
type ServerParams struct {
dig.In
Routes []Route `group:"routes"`
}
func NewServer(p ServerParams) *http.Server {
mux := http.NewServeMux()
for _, r := range p.Routes {
mux.Handle(r.Pattern, r.Handler)
}
return &http.Server{Addr: ":8080", Handler: mux}
}
func main() {
c := dig.New()
must(c.Provide(NewDB)) // *sql.DB
must(c.Provide(NewUserRepo)) // *UserRepo
must(c.Provide(NewHealthRoute)) // adds to group
must(c.Provide(NewUserRoute)) // adds to group
must(c.Provide(NewServer))
err := c.Invoke(func(srv *http.Server) error {
log.Println("listening on", srv.Addr)
return srv.ListenAndServe()
})
if err != nil {
log.Fatal(err)
}
}
func must(err error) {
if err != nil {
panic(err)
}
}
```
## Two databases (read-write + read-only)
```go
type DBResult struct {
dig.Out
Primary *sql.DB `name:"primary"`
ReadOnly *sql.DB `name:"readonly"`
}
func NewDatabases(cfg *Config) (DBResult, error) {
rw, err := sql.Open("postgres", cfg.PrimaryDSN)
if err != nil {
return DBResult{}, fmt.Errorf("primary: %w", err)
}
ro, err := sql.Open("postgres", cfg.ReadOnlyDSN)
if err != nil {
rw.Close()
return DBResult{}, fmt.Errorf("readonly: %w", err)
}
return DBResult{Primary: rw, ReadOnly: ro}, nil
}
type RepoParams struct {
dig.In
Writer *sql.DB `name:"primary"`
Reader *sql.DB `name:"readonly"`
}
func NewUserRepo(p RepoParams) *UserRepo {
return &UserRepo{w: p.Writer, r: p.Reader}
}
```
## Provide as interface (`dig.As`) to hide concrete types
```go
type Cache interface {
Get(key string) (string, bool)
Set(key, value string)
}
type RedisCache struct {
client *redis.Client
metrics *Metrics // an internal field consumers should not see
}
func NewRedisCache(client *redis.Client, m *Metrics) *RedisCache {
return &RedisCache{client: client, metrics: m}
}
func (c *RedisCache) Get(key string) (string, bool) { /* ... */ }
func (c *RedisCache) Set(key, value string) { /* ... */ }
func main() {
c := dig.New()
must(c.Provide(NewRedisClient))
must(c.Provide(NewMetrics))
// Consumers see Cache, never *RedisCache or its internals.
must(c.Provide(NewRedisCache, dig.As(new(Cache))))
must(c.Invoke(func(cache Cache) {
cache.Set("hello", "world")
}))
}
```
## Request-scoped dependencies
A child scope inherits its parent's providers but adds request-local ones:
```go
root := dig.New()
must(root.Provide(NewLogger))
must(root.Provide(NewDB))
must(root.Provide(NewHandler)) // *Handler is shared; the scope inherits it
func handle(w http.ResponseWriter, req *http.Request) {
scope := root.Scope("request")
// Request-scoped values
must(scope.Provide(func() *http.Request { return req }))
must(scope.Provide(func() RequestID { return RequestID(req.Header.Get("X-Request-ID")) }))
must(scope.Decorate(func(l *zap.Logger) *zap.Logger {
return l.With(zap.String("request_id", req.Header.Get("X-Request-ID")))
}))
err := scope.Invoke(func(h *Handler) error {
return h.Serve(w, req)
})
if err != nil {
http.Error(w, err.Error(), 500)
}
}
```
The decorator only applies inside the request scope — sibling scopes (other in-flight requests) keep their own logger.
## Optional dependency for graceful degradation
```go
type WorkerParams struct {
dig.In
DB *sql.DB
Tracer trace.Tracer `optional:"true"` // app still boots without OTel
}
func NewWorker(p WorkerParams) *Worker {
w := &Worker{db: p.DB}
if p.Tracer != nil {
w.tracer = p.Tracer
} else {
w.tracer = trace.NewNoopTracerProvider().Tracer("noop")
}
return w
}
```
Reach for `optional` only when the dependency is genuinely optional — a missing DB hidden behind `optional` becomes a nil-pointer panic at first use.
## Decorate to add cross-cutting behavior
```go
// Wrap the *sql.DB with a metrics-recording wrapper everywhere.
must(c.Decorate(func(db *sql.DB, m *Metrics) *sql.DB {
return wrapWithMetrics(db, m)
}))
// Wrap the logger with service tags.
must(c.Decorate(func(log *zap.Logger, cfg *Config) *zap.Logger {
return log.With(
zap.String("service", cfg.ServiceName),
zap.String("env", cfg.Env),
)
}))
```
Decorators are scope-local. A decorator on the root applies everywhere; a decorator on a child scope only applies to that subtree.
## DryRun for graph validation in tests
```go
func TestWiringIsValid(t *testing.T) {
c := dig.New(dig.DryRun(true))
// Register everything main() registers
must := func(err error) {
require.NoError(t, err)
}
must(c.Provide(NewConfig))
must(c.Provide(NewLogger))
must(c.Provide(NewDB))
must(c.Provide(NewServer))
// Invoke the composition root: dig validates types without running constructors.
require.NoError(t, c.Invoke(func(*http.Server) {}))
}
```
This catches "no provider for \*X" failures at build time instead of in production.
## Visualizing a failed graph
```go
err := c.Invoke(run)
if err != nil {
f, _ := os.Create("graph.dot")
defer f.Close()
_ = dig.Visualize(c, f, dig.VisualizeError(err))
log.Fatalf("wiring failed (graph in graph.dot): %v", err)
}
// Render: dot -Tpng graph.dot -o graph.png
```
`VisualizeError` highlights the missing edges in red — much faster than reading the wrapped error chain.
references/testing.md
# Testing with uber-go/dig
dig containers are cheap to create. Build a fresh one per test, override what you need, drive the system with `Invoke`.
## Per-test container
```go
func TestUserService_Create(t *testing.T) {
c := dig.New()
fakeDB := &fakeDatabase{}
require.NoError(t, c.Provide(func() Database { return fakeDB }))
require.NoError(t, c.Provide(NewUserService))
require.NoError(t, c.Invoke(func(s *UserService) {
err := s.Create(context.Background(), "alice@example.com")
require.NoError(t, err)
}))
require.Len(t, fakeDB.inserted, 1)
}
```
## Shared test wiring
For larger suites, factor the common providers into a helper:
```go
func newTestContainer(t *testing.T, overrides ...func(*dig.Container)) *dig.Container {
t.Helper()
c := dig.New()
require.NoError(t, c.Provide(NewTestLogger))
require.NoError(t, c.Provide(NewInMemoryCache))
require.NoError(t, c.Provide(func() Database { return &fakeDatabase{} }))
require.NoError(t, c.Provide(NewUserService))
for _, override := range overrides {
override(c)
}
return c
}
func TestUserService_NotFound(t *testing.T) {
c := newTestContainer(t, func(c *dig.Container) {
// Replace the default DB with one that returns sql.ErrNoRows.
require.NoError(t, c.Decorate(func(db Database) Database {
return ¬FoundDB{Database: db}
}))
})
require.NoError(t, c.Invoke(func(s *UserService) {
_, err := s.Get(context.Background(), "missing")
require.ErrorIs(t, err, ErrUserNotFound)
}))
}
```
`Decorate` is the cleanest way to swap a dependency in tests — the test reads almost like production wiring with one extra line.
## Validate the production graph in CI
```go
func TestProductionGraph(t *testing.T) {
c := dig.New(dig.DryRun(true))
// Replicate every Provide() from main()
require.NoError(t, registerAll(c))
// Invoke the same root the production binary does
require.NoError(t, c.Invoke(func(*http.Server, *Worker, *MetricsExporter) {}))
}
```
`DryRun(true)` skips constructor execution — the graph is validated structurally. This catches missing-provider and type-mismatch errors without spinning up real DB connections.
## Detecting cycles before deploy
A cyclic graph fails at the first `Invoke` (or at `Provide` time when `DeferAcyclicVerification` is off, which is the default):
```go
func TestNoCycles(t *testing.T) {
c := dig.New()
require.NoError(t, registerAll(c))
err := c.Invoke(func(*App) {})
require.False(t, dig.IsCycleDetected(err), "cycle in dependency graph: %v", err)
}
```
## Asserting a constructor's error path
When a constructor returns an error, dig wraps it with the dependency path. Use `dig.RootCause` to assert the original error:
```go
func TestDBProvider_BadDSN(t *testing.T) {
c := dig.New()
require.NoError(t, c.Provide(func() *Config {
return &Config{DSN: "not a dsn"}
}))
require.NoError(t, c.Provide(NewDB))
err := c.Invoke(func(*sql.DB) {})
require.Error(t, err)
require.ErrorContains(t, dig.RootCause(err), "invalid connection string")
}
```
## Recovering from constructor panics
Wrap constructors that may panic on misuse so the test reports a typed error instead of crashing the runner:
```go
c := dig.New(dig.RecoverFromPanics())
require.NoError(t, c.Provide(func() *App {
panic("intentionally broken")
}))
err := c.Invoke(func(*App) {})
var pe dig.PanicError
require.True(t, errors.As(err, &pe))
require.Contains(t, pe.Error(), "intentionally broken")
```