slush

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Mar 20, 2026 License: MIT Imports: 6 Imported by: 0

README

slush

CI codecov Go Report Card CodeQL Go Reference Go Version Release

Services behind gates.

A type-safe service locator where every lookup passes through guards—composable access checks that validate context before returning the service.

Guarded Lookup

// Register with guards
slush.Register[Database](db).
    Guard(requirePermission("db:read"))

// Lookup validates context
db, err := slush.Use[Database](ctx)
if errors.Is(err, slush.ErrAccessDenied) {
    // Guard rejected the request
}

Guards are just functions: func(context.Context) error. Chain them, compose them, bring your own validation.

Install

go get github.com/zoobz-io/slush

Requires Go 1.24+.

Quick Start

package main

import (
    "context"
    "errors"
    "log"

    "github.com/zoobz-io/slush"
)

// Define service contracts as interfaces
type Database interface {
    Query(ctx context.Context, sql string) ([]Row, error)
}

type Mailer interface {
    Send(ctx context.Context, to, subject, body string) error
}

func main() {
    // Create services (you own lifecycle)
    db := postgres.New(cfg.DatabaseURL)
    defer db.Close()

    mailer := sendgrid.New(cfg.APIKey)

    // Register by interface type, configure guards
    slush.Register[Database](db).
        Guard(requireRole("service"))

    slush.Register[Mailer](mailer).
        Guard(requireRole("service")).
        Guard(requirePermission("mailer:send"))

    // Start your server...
}

func handleRequest(ctx context.Context) error {
    // Context carries caller identity
    db, err := slush.Use[Database](ctx)
    if err != nil {
        return err // ErrNotFound or ErrAccessDenied
    }

    rows, err := db.Query(ctx, "SELECT ...")
    // ...
}

func requireRole(role string) slush.Guard {
    return func(ctx context.Context) error {
        if !hasRole(ctx, role) {
            return errors.New("missing role: " + role)
        }
        return nil
    }
}

Capabilities

Feature Description
Register[T] Store service by interface type, returns handle for configuration
Handle.Guard Add access checks, chainable
Use[T] Retrieve service, runs all guards first
Services() Enumerate registered services with sentinel metadata
capitan events Emits on register, access, denied, not-found

Why slush?

  • Type-safe generics — No interface{} casting at call sites
  • Guards are composable — Chain checks fluently: .Guard(a).Guard(b).Guard(c)
  • Context flows through — Guards receive request context for validation
  • You own lifecycle — Register initialized services; slush just finds them
  • Enumerable registryServices() exposes FQDNs and sentinel metadata

Cryptographic Guards with sctx

Combine slush with sctx for cryptographically-verified service access:

// Bootstrap: create sctx admin with your PKI
admin, _ := sctx.NewAdminService[AppMeta](privateKey, trustedCAs)

// Create a guard that requires specific permissions
dbGuard, _ := admin.CreateGuard(ctx, adminToken, "db:read", "db:write")

// Bridge sctx guard to slush guard
slush.Register[Database](db).
    Guard(func(ctx context.Context) error {
        token := sctx.TokenFromContext(ctx)
        return dbGuard.Validate(ctx, token)
    })

// In handlers: context carries the cryptographic token
// Guard validates signature + permissions before service access
db, err := slush.Use[Database](ctx)

The sctx guard validates:

  • Token signature (proves possession of private key)
  • Token expiry and replay protection
  • Required permissions present in token's context

Ecosystem

slush integrates with the zoobz-io toolkit:

  • sentinelServices() returns sentinel metadata for each registered implementation
  • capitan — Events emitted on slush.registered, slush.accessed, slush.denied, slush.not_found
  • sctx — Cryptographic context for guards that verify identity and permissions

Contributing

See CONTRIBUTING.md. Run make help for available commands.

License

MIT — see LICENSE.

Documentation

Overview

Package slush provides a guarded service locator with cryptographic access control.

Services are registered by interface type and retrieved via context-aware lookups. Guards control access to services, enabling cryptographic validation through external packages like sctx.

Basic usage:

// Register a service
slush.Register[Database](db).
    Guard(sctx.RequireClaim("db:access"))

// Retrieve in handler
db, err := slush.Use[Database](ctx)

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotFound     = errors.New("slush: service not registered")
	ErrAccessDenied = errors.New("slush: access denied")
)

Sentinel errors.

View Source
var (
	SignalRegistered = capitan.NewSignal("slush.registered", "Service registered")
	SignalAccessed   = capitan.NewSignal("slush.accessed", "Service accessed")
	SignalDenied     = capitan.NewSignal("slush.denied", "Service access denied")
	SignalNotFound   = capitan.NewSignal("slush.not_found", "Service not found")
)

Capitan signals for service lifecycle events.

View Source
var (
	KeyInterface = capitan.NewStringKey("interface")
	KeyImpl      = capitan.NewStringKey("impl")
	KeyError     = capitan.NewStringKey("error")
)

Capitan keys for event fields.

View Source
var ErrInvalidKey = errors.New("slush: invalid key")

ErrInvalidKey is returned when an invalid key is provided.

Functions

func Freeze

func Freeze(k Key)

Freeze prevents further service registration. Panics if key is invalid.

func MustUse

func MustUse[T any](ctx context.Context) T

MustUse is Use that panics on error. For bootstrap/init code only.

func Use

func Use[T any](ctx context.Context) (T, error)

Use retrieves service T, running all guards first. Returns ErrNotFound if the service is not registered. Returns ErrAccessDenied (joined with guard error) if any guard fails.

Types

type Guard

type Guard func(ctx context.Context) error

Guard validates context before service access. Return nil to permit access, or an error to deny.

type Handle

type Handle[T any] struct {
	// contains filtered or unexported fields
}

Handle configures access control for a registered service.

func Register

func Register[T any](k Key, impl T) *Handle[T]

Register stores impl keyed by interface type T and returns a Handle for configuration. The same type can be registered multiple times; subsequent calls overwrite previous registrations. Panics if Start has not been called, key is invalid, or registry is frozen.

func (*Handle[T]) Guard

func (h *Handle[T]) Guard(g Guard) *Handle[T]

Guard adds an access control check. Chainable. Guards are evaluated in order; first error stops evaluation. Safe to call concurrently with Use.

type Key

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

Key grants the capability to register services. Only obtainable via Start(); the unexported inner pointer prevents forgery.

func Start

func Start() Key

Start initializes the service registry and returns a Key for registration. Panics if called more than once.

type ServiceInfo

type ServiceInfo struct {
	Interface  string            // Interface FQDN (the lookup key)
	Impl       string            // Implementation FQDN
	Metadata   sentinel.Metadata // Sentinel metadata from cache
	GuardCount int               // Number of guards configured
}

ServiceInfo describes a registered service for enumeration.

func Services

func Services(k Key) ([]ServiceInfo, error)

Services returns information about all registered services. Metadata is populated from sentinel's cache via Lookup. Returns ErrInvalidKey if the key is invalid.

Jump to

Keyboard shortcuts

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