Documentation
¶
Overview ¶
Package middleware defines nexus's cross-transport middleware model.
Two shapes coexist here:
Info — the static descriptor the registry stores and the dashboard renders. Just metadata: name, kind (builtin/custom), description. Every resolver / route that uses a middleware contributes its name to the endpoint's Middleware list; Info tells the dashboard how to label each entry.
Middleware — an executable BUNDLE. Carries one realization per transport (Gin for REST + WS upgrades, Graph for GraphQL field resolution). Factories like ratelimit.NewMiddleware produce one, and nexus.Use(mw) accepts it on any registration regardless of transport. Transports pick the field they can honor and ignore the rest.
Keeping these side-by-side means dashboard readers get a uniform view (name + kind + description) while implementers get a uniform API (write once, use everywhere).
Index ¶
- Variables
- func ClientIPFromCtx(ctx context.Context) string
- func WithClientIP(ctx context.Context, ip string) context.Context
- func WithRejectHook(ctx context.Context, h RejectHook) context.Context
- type Func
- type Handler
- type Info
- type Kind
- type Middleware
- type Next
- type Phase
- type RejectHook
- type RequestCtx
- func (rc *RequestCtx) ClientIP() string
- func (rc *RequestCtx) Get(key any) (any, bool)
- func (rc *RequestCtx) Header(key string) string
- func (rc *RequestCtx) Path() string
- func (rc *RequestCtx) Reject(status int, err error) error
- func (rc *RequestCtx) RejectJSON(status int, body any) error
- func (rc *RequestCtx) Set(key, val any)
- func (rc *RequestCtx) SetHeader(key, val string)
- func (rc *RequestCtx) WithContext(ctx context.Context)
- type Transport
- type TransportSet
Constants ¶
This section is empty.
Variables ¶
var AllTransports = Transports(TransportREST, TransportGraphQL, TransportWebSocket)
AllTransports is the common declaration for write-once middleware.
var Builtins = []Info{ {Name: "auth", Kind: KindBuiltin, Description: "Bearer token / session validation"}, {Name: "cors", Kind: KindBuiltin, Description: "CORS preflight + header policy"}, {Name: "rate-limit", Kind: KindBuiltin, Description: "Request rate limiting"}, {Name: "request-id", Kind: KindBuiltin, Description: "Attach X-Request-ID per request"}, {Name: "logger", Kind: KindBuiltin, Description: "Structured request logger"}, {Name: "recovery", Kind: KindBuiltin, Description: "Panic recovery"}, {Name: "permission", Kind: KindBuiltin, Description: "RBAC permission check"}, {Name: "csrf", Kind: KindBuiltin, Description: "CSRF token validation"}, {Name: "compression", Kind: KindBuiltin, Description: "Response compression"}, }
Builtins are well-known names nexus pre-registers. When your code attaches a middleware with one of these names, the dashboard labels it "builtin"; any other name falls back to "custom".
Functions ¶
func ClientIPFromCtx ¶ added in v1.12.0
ClientIPFromCtx returns the IP a transport stashed via WithClientIP, or empty when absent.
func WithClientIP ¶ added in v1.12.0
WithClientIP returns ctx carrying ip. Transports (the gin REST handler, the GraphQL adapter, the WS upgrade) call this so per-IP middleware can find it.
func WithRejectHook ¶ added in v1.12.0
func WithRejectHook(ctx context.Context, h RejectHook) context.Context
WithRejectHook returns ctx carrying h, to be invoked by the gin carrier on Reject. The global REST middleware that owns the policy (e.g. auth's ginAuthMiddleware) installs it per request.
Types ¶
type Func ¶ added in v1.12.0
type Func struct {
// contains filtered or unexported fields
}
Func is the closure adapter — most middleware is one of these.
func NewFunc ¶ added in v1.12.0
func NewFunc(name string, set TransportSet, fn func(rc *RequestCtx, next Next) error) Func
NewFunc builds a Func Handler.
func (Func) Transports ¶ added in v1.12.0
func (f Func) Transports() TransportSet
type Handler ¶ added in v1.12.0
type Handler interface {
Name() string
Transports() TransportSet
Handle(rc *RequestCtx, next Next) error
}
Handler is the unified middleware shape (see docs/design/middleware-redesign.md §3). Transitional name — it becomes Middleware in step 5, once the current Middleware struct is renamed to Bundle. A single implementation serves every transport it declares in Transports().
func AsHandler ¶ added in v1.12.0
func AsHandler(mw Middleware) Handler
AsHandler wraps a legacy bundle as a Handler so existing nexus.Use(...) bundles flow through the new pipeline and participate in fail-closed (step 3).
type Info ¶ added in v0.3.0
type Info struct {
Name string `json:"name"`
Kind Kind `json:"kind"`
Description string `json:"description,omitempty"`
}
Info is the registry entry shown in the dashboard — pure metadata, no execution. Every Middleware bundle carries an Info so its name is self-describing when attached.
type Middleware ¶
type Middleware struct {
Name string
Description string
Kind Kind // defaults to KindCustom when unset by factories
Gin httpx.HandlerFunc // REST + WS upgrade path
Graph graph.FieldMiddleware
}
Middleware is an executable bundle with per-transport realizations. A single definition serves REST (Gin), GraphQL (Graph), and WebSocket (WS — runs at upgrade time; per-frame hooks are out of scope for v1). Leave a field nil when the middleware doesn't make sense for that transport (e.g. graphql-specific auth might only set Graph).
The Info() companion returns the metadata the registry stores — factories pre-populate it so dashboard listings "just work" without users touching the static side.
func FromHandler ¶ added in v1.12.0
func FromHandler(h Handler) Middleware
FromHandler turns one unified Handler into a transport bundle, generating exactly the realizations the Handler declares it can serve (redesign §3–4). A Handler that declares AllTransports gets both a Gin and a Graph realization from a SINGLE implementation — this is what lets the built-ins (auth, ratelimit, …) drop their duplicated Gin/Graph pairs.
The returned bundle's Name/Kind come from the Handler; callers may set Description (and override Kind) on the value before attaching:
mw := middleware.FromHandler(h) mw.Description = "30 rpm, per-IP" mw.Kind = middleware.KindBuiltin
The current execution path is unchanged: REST/WS still run mw.Gin in the gin chain, GraphQL still wraps mw.Graph around the resolver. FromHandler is the authoring layer; the carriers below bridge each transport into the neutral RequestCtx the Handler sees.
func (Middleware) AsInfo ¶ added in v0.3.0
func (m Middleware) AsInfo() Info
AsInfo returns the registry-side metadata for this bundle, defaulting the kind to Custom when a factory didn't supply one.
type Next ¶ added in v1.12.0
type Next func(*RequestCtx) error
Next advances the chain. Returning an error (or calling rc.Reject) short-circuits; the active carrier translates the outcome per transport.
type Phase ¶ added in v1.12.0
type Phase int
Phase orders middleware uniformly across transports (see docs/design/middleware-redesign.md §7). Lower runs further out. Built-ins pick a phase; nexus.Use defaults to PhaseApp.
Declared now so step 3's chain builder can sort by it; nothing reads Phase in steps 1–2.
type RejectHook ¶ added in v1.12.0
RejectHook lets a REST-aware extension customise how RequestCtx.Reject renders on gin — running app-supplied callbacks that need the raw *httpx.Ctx, which the neutral carrier can't expose. The extension registers one via WithRejectHook; the gin carrier invokes it before its default JSON abort. (Most extensions don't need this: rc.RejectJSON already emits a custom JSON envelope through the neutral handle — reach for a RejectHook only when you need the gin.Context itself.)
Return true when the hook fully handled the response (the carrier then does nothing more); return false to fall through to the default abort — so a hook can claim only the statuses it owns and leave the rest alone.
This is the one HTTP-leaning escape that keeps package middleware free of any extension dependency: the hook closure lives in the extension; only the plumbing lives here.
type RequestCtx ¶ added in v1.12.0
type RequestCtx struct {
Context context.Context
Transport Transport
// contains filtered or unexported fields
}
RequestCtx is the transport-neutral request handle middleware sees (§3.2). Middleware never touches gin.Context or graph.ResolveParams — those are carrier internals.
func (*RequestCtx) ClientIP ¶ added in v1.12.0
func (rc *RequestCtx) ClientIP() string
ClientIP returns the request's client IP via a single shared implementation.
func (*RequestCtx) Get ¶ added in v1.12.0
func (rc *RequestCtx) Get(key any) (any, bool)
Get reads a value stashed by Set.
func (*RequestCtx) Header ¶ added in v1.12.0
func (rc *RequestCtx) Header(key string) string
Header returns the named request header, transport-backed.
func (*RequestCtx) Path ¶ added in v1.12.0
func (rc *RequestCtx) Path() string
Path returns the REST route or GraphQL field path.
func (*RequestCtx) Reject ¶ added in v1.12.0
func (rc *RequestCtx) Reject(status int, err error) error
Reject short-circuits the chain. Returns the error so callers can write `return rc.Reject(401, ErrUnauthenticated)`; the carrier renders the transport-appropriate response (a default {"error": msg} body on REST/WS, the error surfaced on GraphQL).
func (*RequestCtx) RejectJSON ¶ added in v1.14.0
func (rc *RequestCtx) RejectJSON(status int, body any) error
RejectJSON short-circuits the chain with a CUSTOM response body on REST/WS, rendered as JSON with the given status — the neutral way for an extension to emit its own error envelope without importing gin. GraphQL has no per-field response body, so there RejectJSON only short-circuits (a GraphQL-facing ErrorHandler should branch on rc.Transport and return a wrapped error instead). Returns a non-nil error so callers can write `return rc.RejectJSON(401, MyEnvelope{...})` uniformly.
func (*RequestCtx) Set ¶ added in v1.12.0
func (rc *RequestCtx) Set(key, val any)
Set shares a typed value across the chain without internal-package coupling.
func (*RequestCtx) SetHeader ¶ added in v1.12.0
func (rc *RequestCtx) SetHeader(key, val string)
SetHeader sets a response header on REST/WS (e.g. Retry-After). On GraphQL — which has no per-field response headers — it is a no-op, so middleware can call it unconditionally without branching on transport.
func (*RequestCtx) WithContext ¶ added in v1.12.0
func (rc *RequestCtx) WithContext(ctx context.Context)
WithContext replaces the underlying context — the functional equivalent of stashing a value on gin.Context for downstream middleware and the handler.
type Transport ¶ added in v1.12.0
type Transport uint8
Transport identifies which wire protocol a request arrived on.
type TransportSet ¶ added in v1.12.0
type TransportSet uint8
TransportSet is a bitset over Transport. The zero value is the empty set.
func Transports ¶ added in v1.12.0
func Transports(ts ...Transport) TransportSet
Transports builds a set from its members.
func (TransportSet) Has ¶ added in v1.12.0
func (s TransportSet) Has(t Transport) bool
Has reports whether t is a member of the set.
func (TransportSet) String ¶ added in v1.12.0
func (s TransportSet) String() string
String renders the set for fail-closed error messages, e.g. "{REST, GraphQL}".