zoobzio January 24, 2026 Edit this page

Troubleshooting

Common issues and how to resolve them.

ErrNotFound: Service Not Registered

Symptom:

db, err := slush.Use[Database](ctx)
// err is slush.ErrNotFound

Causes and solutions:

1. Service was never registered

Ensure Register is called before Use:

// At startup
slush.Register[Database](db)

// Later
db, _ := slush.Use[Database](ctx)  // Works

2. Registered with different type

The type parameter must match exactly:

type Database interface { Query() }
type DB interface { Query() }  // Different type!

slush.Register[Database](impl)
slush.Use[DB](ctx)  // ErrNotFound - DB != Database

3. Test didn't register the service

Tests reset the registry. Each test must register needed services:

func TestHandler(t *testing.T) {
    slush.Reset()
    // Forgot to register!

    _, err := slush.Use[Database](ctx)  // ErrNotFound
}

4. Race condition at startup

If Use is called before registration completes:

// Wrong: concurrent startup
go startServer()      // Calls Use[Database]
go initServices()     // Calls Register[Database]

// Right: sequential initialization
initServices()
startServer()

ErrAccessDenied: Guard Rejected Request

Symptom:

db, err := slush.Use[Database](ctx)
// err wraps slush.ErrAccessDenied

Debugging steps:

1. Check the wrapped error

ErrAccessDenied wraps the guard's error:

if errors.Is(err, slush.ErrAccessDenied) {
    // Unwrap to see guard's reason
    fmt.Println(err)  // "slush: access denied: missing permission: db:read"
}

2. Verify context has required values

Guards typically check context values:

func requireAuth(ctx context.Context) error {
    if auth.UserFromContext(ctx) == nil {
        return errors.New("unauthenticated")
    }
    return nil
}

Ensure middleware sets these values before the handler runs.

3. Check guard order

Guards run in registration order. An early guard may reject before a later guard that would pass:

slush.Register[Database](db).
    Guard(requireAdmin).   // Fails first
    Guard(requireAuth)     // Never reached

4. Add debug logging to guards

Temporarily log guard execution:

func requireAuth(ctx context.Context) error {
    user := auth.UserFromContext(ctx)
    log.Printf("requireAuth: user=%v", user)
    if user == nil {
        return errors.New("unauthenticated")
    }
    return nil
}

5. Listen to capitan events

slush emits SignalDenied with error details:

capitan.Hook(slush.SignalDenied, func(e *capitan.Event) {
    iface, _ := e.String(slush.KeyInterface)
    errMsg, _ := e.String(slush.KeyError)
    log.Printf("Access denied: %s - %s", iface, errMsg)
})

Panic in MustUse

Symptom:

db := slush.MustUse[Database](ctx)
// panic: slush: service not registered

Solution:

MustUse is for bootstrap code where failure is unrecoverable. Use Use with error handling for runtime lookups:

// Bootstrap (panic is appropriate)
func main() {
    initServices()
    logger := slush.MustUse[Logger](ctx)  // OK to panic
}

// Runtime (handle errors)
func handleRequest(ctx context.Context) error {
    db, err := slush.Use[Database](ctx)
    if err != nil {
        return err  // Don't panic
    }
}

Tests Interfering with Each Other

Symptom:

Tests pass individually but fail when run together.

Cause:

Global registry state leaking between tests.

Solution:

Reset at start and end of each test:

func TestA(t *testing.T) {
    slush.Reset()
    defer slush.Reset()
    // ...
}

func TestB(t *testing.T) {
    slush.Reset()
    defer slush.Reset()
    // ...
}

Or use the testing helper:

import slushtesting "github.com/zoobzio/slush/testing"

func TestA(t *testing.T) {
    slushtesting.ResetRegistry(t)  // Handles cleanup
    // ...
}

Service Returns Wrong Implementation

Symptom:

Use returns an unexpected implementation.

Cause:

Later registration overwrote earlier one:

slush.Register[Database](postgresDB)
slush.Register[Database](mockDB)  // Overwrites!

db, _ := slush.Use[Database](ctx)  // Returns mockDB

Solution:

Register each interface type once. If you need multiple implementations, use different interface types or a factory pattern.

Guards Not Being Called

Symptom:

Guard function never executes.

Possible causes:

1. Guard added after Use

handle := slush.Register[Database](db)
slush.Use[Database](ctx)  // No guards yet!
handle.Guard(myGuard)     // Too late

2. Service retrieved from cache

slush doesn't cache—every Use runs guards. If guards aren't running, check that you're calling slush.Use, not using a cached reference.

3. Using wrong service type

slush.Register[Database](db).Guard(myGuard)
slush.Use[OtherDatabase](ctx)  // Different type, no guards

Debugging with Services()

List all registered services:

for _, svc := range slush.Services() {
    fmt.Printf("Interface: %s\n", svc.Interface)
    fmt.Printf("Impl: %s\n", svc.Impl)
    fmt.Printf("Guards: %d\n", svc.GuardCount)
    fmt.Println("---")
}

Next Steps