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
- Testing Guide — Write reliable tests
- Guards Guide — Build correct guards
- capitan Integration — Event-based debugging