adapter

package
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Feb 20, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Index

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).

func Wrap

func Wrap(chain []Middleware, h http.Handler) http.Handler

Wrap applies middleware in order around h (first is outermost).

Given chain [A,B,C] and handler H => A(B(C(H))).

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:

  1. Portable net/http middleware (func(http.Handler) http.Handler)
  2. 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 ErrorSink

type ErrorSink interface {
	Add(error)
}

type ListError

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

func (*ListError) Add

func (e *ListError) Add(err error)

func (*ListError) Error

func (e *ListError) Error() string

func (*ListError) Unwrap

func (e *ListError) Unwrap() []error

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 HTTP

func HTTP(mw Middleware) MW

HTTP wraps a portable net/http middleware as an MW.

func HTTPNamed

func HTTPNamed(name string, mw Middleware) MW

HTTPNamed is like HTTP but allows setting a diagnostic name (recommended).

type Middleware

type Middleware func(http.Handler) http.Handler

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 New

func New(d drv.Drv) *Router

New constructs a new core router adapter over a driver.

func (*Router) Engine

func (r *Router) Engine() any

func (*Router) Err

func (r *Router) Err() error

func (*Router) Group

func (r *Router) Group(prefix string, mws ...MW) Adapter

Group creates a derived router with a prefixed path scope and optional middleware.

func (*Router) Handle

func (r *Router) Handle(method, p string, h http.Handler, mws ...MW)

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) ServeHTTP

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

func (*Router) Use

func (r *Router) Use(mws ...MW)

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).

func (*Router) With

func (r *Router) With(mws ...MW) Adapter

With returns a derived router scope that adds middleware without mutating the receiver.

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 ScopeKind

type ScopeKind uint8

ScopeKind is the kind of router scope.

const (
	ScopeRoot ScopeKind = iota
	ScopeGroup
	ScopeWith
)

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).

Directories

Path Synopsis
chi module
echo module
gin module
stdlib module

Jump to

Keyboard shortcuts

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