Documentation
¶
Index ¶
- func RefuseOnErr(h http.Handler, a ErrorChecker) http.Handler
- func RefuseOnErrWith(h http.Handler, a ErrorChecker, errFn ErrorHandler) http.Handler
- func SplitMW(mws []MW, errs ErrorSink) ([]Middleware, []MW)
- func Wrap(chain []Middleware, h http.Handler) http.Handler
- type Adapter
- type DocCarrier
- type EngineProvider
- type ErrorChecker
- type ErrorHandler
- type ErrorSink
- type ListError
- type MW
- type Middleware
- type RegistryProvider
- type RegistrySnapshot
- type RouteDocMeta
- type RouteRecord
- type Router
- func (r *Router) Engine() any
- func (r *Router) Err() error
- func (r *Router) Group(prefix string, mws ...MW) Adapter
- func (r *Router) Handle(method, p string, h http.Handler, mws ...MW)
- func (r *Router) HandleFunc(method, p string, h http.HandlerFunc, mws ...MW)
- func (r *Router) Registry() RegistrySnapshot
- func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
- func (r *Router) Use(mws ...MW)
- func (r *Router) With(mws ...MW) Adapter
- type ScopeID
- type ScopeKind
- type ScopeRecord
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RefuseOnErr ¶
func RefuseOnErr(h http.Handler, a ErrorChecker) http.Handler
RefuseOnErr wraps h so that every incoming request first checks a.Err().
If a.Err() returns a non-nil error, the request is rejected with "503 Service Unavailable" and a generic plain-text body; h is never called. If a.Err() returns nil, the request is forwarded to h without modification.
RefuseOnErr is safe to call with a nil h or a nil a:
- nil a: the wrapper delegates to h (no check is possible).
- nil h: every request is rejected (via errNoHandler).
func RefuseOnErrWith ¶
func RefuseOnErrWith(h http.Handler, a ErrorChecker, errFn ErrorHandler) http.Handler
RefuseOnErrWith is like RefuseOnErr but accepts a custom ErrorHandler.
If errFn is nil, the default generic 503 handler is used (identical to RefuseOnErr behavior).
func SplitMW ¶
func SplitMW(mws []MW, errs ErrorSink) ([]Middleware, []MW)
SplitMW separates portable net/http middleware from native MW.
- Portable middleware is returned as a slice of Middleware (to be wrapped around handlers). - Native middleware is returned as MWs to be applied via MW.Apply(driver).
Any nil MW is recorded as an error (if errs != nil).
Types ¶
type Adapter ¶
type Adapter interface {
http.Handler
// Use registers middleware on the current router scope.
//
// The middleware is applied to all routes registered on this router and on any sub-routers created
// from it via Group or With (unless a derived router is intentionally isolated by the adapter).
//
// Portable middleware is always accepted. Adapter-native middleware is accepted only when the underlying
// adapter supports it; otherwise the router records an error retrievable via Err().
//
// Use does not panic; invalid inputs are reported via Err().
Use(mws ...MW)
// Group creates a new router that prefixes all routes with prefix and applies the given middleware
// to routes registered on the group.
//
// The returned Router inherits all middleware previously registered on the parent router, then applies
// the group's middleware, then any per-route middleware.
//
// Group does not panic; invalid prefix/pattern inputs or unsupported middleware are reported via Err()
// on the returned Router (and typically also the parent, depending on implementation).
Group(prefix string, mws ...MW) Adapter
// With returns a derived router scope that adds middleware to subsequently registered routes without
// mutating the receiver.
//
// With is useful for temporary scoping:
// r.With(Auth()).HandleFunc("GET", "/private", h)
//
// Middleware ordering still follows:
// Global (Use) → Group (if any) → With → Per-route → Handler.
//
// With does not panic; unsupported middleware is reported via Err() on the returned Router.
With(mws ...MW) Adapter
// Handle registers an http.Handler for the given HTTP method and path pattern.
//
// method is typically an uppercase token such as "GET", "POST", "PUT", "DELETE", "OPTIONS".
// Adapters may normalize method casing.
//
// path uses the Transwarp canonical pattern syntax (e.g. "/domain/subdomain/{id}.json").
//
// mws are per-route middleware applied after any global/group/With middleware.
//
// Handle does not panic; invalid patterns or unsupported adapter features are reported via Err().
Handle(method, path string, h http.Handler, mws ...MW)
// HandleFunc is like Handle but accepts an http.HandlerFunc.
HandleFunc(method, path string, h http.HandlerFunc, mws ...MW)
// Err returns accumulated configuration errors encountered during registration.
//
// Err MUST be checked before serving traffic. A non-nil error indicates that at least one route
// or middleware could not be applied as requested, and runtime behavior may be incomplete.
//
// Implementations typically return a multi-error type; callers should treat it as opaque and log it.
Err() error
}
Adapter is a framework-agnostic HTTP router abstraction.
Adapter provides a small, stable API for registering routes and middleware while allowing applications to "bring their own router" from the Go ecosystem (stdlib mux, chi, gin, echo, fiber, etc.) through adapter packages.
Transwarp uses a canonical path-pattern syntax based on Go 1.22+ ServeMux routing:
- Path parameters use braces: /users/{id}
- Literal suffixes are supported: /items/{id}.json, /items.json
- Query strings are not part of route matching.
Middleware ¶
Adapter supports two classes of middleware through the MW type:
- Portable net/http middleware (func(http.Handler) http.Handler)
- Adapter-native middleware (e.g., gin/echo/fiber middleware), wrapped as MW by transwarp/mw/* packages.
Middleware application order is deterministic:
Global (Use) → Group → Per-route → Handler.
Errors and validation ¶
Some operations can fail at registration time (invalid patterns, unsupported adapter-native middleware, etc.). Adapter implementations MUST NOT panic on invalid inputs. Instead, they accumulate configuration errors which are returned by Err().
If Err() is non-nil, the Adapter should be considered unusable for serving production traffic. Server wrappers (including transwarp/server) are expected to refuse to start when Err() != nil. For a belt-and-suspenders runtime guard (useful in staging/tests), you can wrap the live handler with RefuseOnErr so requests are rejected when Err() becomes non-nil:
if err := r.Err(); err != nil {
log.Fatalf("router setup failed: %v", err)
}
http.ListenAndServe(":8080", RefuseOnErr(r, r))
Concurrency ¶
Route registration is expected to happen during initialization before the Adapter is used to serve requests. Unless explicitly documented by a specific adapter, Adapter methods are not guaranteed to be safe for concurrent use with ServeHTTP.
type DocCarrier ¶
type DocCarrier interface {
MW
DocMeta() RouteDocMeta
}
DocCarrier is implemented by MWs that carry documentation metadata.
Core recognises DocCarrier MWs and strips them from the middleware chain before SplitMW runs, so they never reach the driver or the handler wrapper. This keeps [Err] clean: a DocCarrier does not trigger [ErrNativeMWUnsupported].
Concrete DocCarrier implementations live in openapi/v3 or a dedicated doc package so the adapter package itself has no dependency on any OpenAPI types.
type EngineProvider ¶
type EngineProvider interface {
Engine() any
}
EngineProvider is an optional escape hatch for retrieving the underlying framework engine/router.
The concrete type returned by Engine() depends on the adapter (e.g., *gin.Engine, *echo.Echo, *fiber.App, chi.Router, *http.ServeMux).
WARNING: Using EngineProvider couples your application to a specific router implementation and may reduce portability. Prefer the Router API when possible.
type ErrorChecker ¶
type ErrorChecker interface {
Err() error
}
ErrorChecker is the minimal interface required by the safe-serve helpers.
*Router (and therefore any Adapter) satisfies this interface automatically. Third-party types that expose an Err() error method also satisfy it, making the helpers router-agnostic.
type ErrorHandler ¶
type ErrorHandler func(err error, w http.ResponseWriter, r *http.Request)
ErrorHandler is a user-supplied callback invoked by RefuseOnErrWith when the ErrorChecker reports a non-nil error, or when no underlying http.Handler is provided.
Implementations MUST write a response (status + body). The error passed to the handler is guaranteed to be non-nil.
type MW ¶
type MW interface {
// Apply attempts to attach this middleware spec to the given driver scope.
//
// For portable middleware, Apply is typically a no-op (portable middleware is applied by core).
// For native middleware, Apply may use driver.Kind(), driver.Caps(), and driver.Engine().
Apply(d drv.Drv) error
// String returns a human-readable description for diagnostics.
String() string
}
MW is a middleware specification accepted by Adapter.Use/Group/With/Handle.
An MW may represent either:
- Portable net/http middleware (Middleware), wrapped via HTTP(...), or
- Driver-native middleware (gin/echo/fiber/chi-native types) wrapped by router-specific packages.
Core Transwarp is responsible for:
- Applying portable middleware by wrapping handlers in deterministic order
- Applying native middleware only where the driver can actually express it; otherwise record Err()
Portable and native middleware are applied in separate layers. Portable middleware is wrapped around handlers by Transwarp core. Native middleware is applied via driver Scope. Interleaving portable and native middleware in a single Use/Group/With call does not preserve insertion interleaving; ordering is preserved within each category only.
MW.Apply MUST NOT panic; it must return an error on incompatibility.
func HTTPNamed ¶
func HTTPNamed(name string, mw Middleware) MW
HTTPNamed is like HTTP but allows setting a diagnostic name (recommended).
type Middleware ¶
Middleware is portable net/http middleware.
It follows the standard convention:
mw := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// before
next.ServeHTTP(w, r)
// after
})
}
type RegistryProvider ¶
type RegistryProvider interface {
Registry() RegistrySnapshot
}
RegistryProvider is an optional interface implemented by adapters that can expose a read-only view of the registered routes and scopes.
It is intentionally separate from Adapter to preserve the stability of the core API. Consumers (e.g., OpenAPI generators) should use a type assertion:
if rp, ok := a.(adapter.RegistryProvider); ok { snap := rp.Registry() }
type RegistrySnapshot ¶
type RegistrySnapshot struct {
Scopes []ScopeRecord
Routes []RouteRecord
}
RegistrySnapshot is an immutable copy of the adapter's registration state at a point in time.
The slices are safe to iterate without holding any locks and will not change after being returned.
type RouteDocMeta ¶
type RouteDocMeta struct {
// OperationID overrides the auto-generated operationId.
// Last non-empty value in the scope chain wins.
OperationID string
// Summary is a short human-readable description of the operation.
// Last non-empty value in the scope chain wins.
Summary string
// Tags groups operations for tooling (Swagger UI, code generators, etc.).
// Tags accumulate across scopes: Group tags + With tags + route tags compose additively.
// Duplicates are removed preserving first-seen order.
Tags []string
// Deprecated marks the operation as deprecated in the generated spec.
// True if any scope or route annotation sets it to true.
Deprecated bool
}
RouteDocMeta holds OpenAPI documentation metadata associated with a route or scope.
Values are collected from DocCarrier MWs passed to Use/Group/With/Handle and merged through the scope hierarchy using the rules in MergeDocMeta.
The zero value is valid and means "no annotation".
func MergeDocMeta ¶
func MergeDocMeta(a, b RouteDocMeta) RouteDocMeta
MergeDocMeta merges b into a and returns the result.
Merge rules (per spec):
- OperationID: last non-empty wins (most-specific scope/route overrides)
- Summary: last non-empty wins
- Tags: append + deduplicate, first-seen order preserved across scopes
- Deprecated: true if either a or b is true (monotonic — never goes back to false)
type RouteRecord ¶
type RouteRecord struct {
Seq uint64
ScopeID ScopeID
MethodRaw string
Method string
IsAny bool
PathRaw string
Path string
FullPath string
// Doc holds the merged OpenAPI metadata for this route, accumulated from
// DocCarrier MWs passed to Use/Group/With and per-route Handle calls.
// The zero value means no annotation was provided.
Doc RouteDocMeta
Errors []error
}
RouteRecord describes a single attempted route registration.
FullPath is the resolved path after group prefixes are applied. Path is the normalized, route-local pattern. Doc carries the merged documentation metadata from scope + route annotations.
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router is the core Adapter implementation over a driver.Drv.
Users consume it through concrete adapter packages (adapter/stdlib, adapter/chi, ...).
func (*Router) Group ¶
Group creates a derived router with a prefixed path scope and optional middleware.
func (*Router) HandleFunc ¶
func (r *Router) HandleFunc(method, p string, h http.HandlerFunc, mws ...MW)
func (*Router) Registry ¶
func (r *Router) Registry() RegistrySnapshot
Registry returns a read-only snapshot of all scopes and attempted route registrations.
The returned snapshot is safe to use without holding any locks.
func (*Router) Use ¶
Use registers middleware on the current router scope.
Portable middleware is stored and applied by wrapping handlers during Handle(). DocCarrier MWs are extracted and stored as local doc metadata for this scope. Non-portable (native) MWs are recorded as errors (not supported in this core adapter).
type ScopeID ¶
type ScopeID uint32
ScopeID identifies a scope in a RegistrySnapshot.
ScopeID values are stable only within a single process lifetime.
const NoParentScopeID ScopeID = 0
NoParentScopeID is the sentinel ScopeID used to indicate that a scope has no parent.
In RegistrySnapshot, the root scope has ParentID == NoParentScopeID.
type ScopeRecord ¶
type ScopeRecord struct {
Seq uint64
ID ScopeID
// ParentID points to the parent scope, or NoParentScopeID for the root scope.
ParentID ScopeID
Kind ScopeKind
PrefixRaw string
Prefix string
FullPath string
Errors []error
}
ScopeRecord describes a router scope produced by Group/With (or the root).
FullPath is the resolved prefix at this scope (after parent prefixes). Prefix is the normalized prefix delta applied by a Group (empty for Root/With).