middleware

package
v1.22.1 Latest Latest
Warning

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

Go to latest
Published: Jun 22, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package middleware defines nexus's cross-transport middleware model.

Two shapes coexist here:

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

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

Constants

This section is empty.

Variables

AllTransports is the common declaration for write-once middleware.

View Source
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

func ClientIPFromCtx(ctx context.Context) string

ClientIPFromCtx returns the IP a transport stashed via WithClientIP, or empty when absent.

func WithClientIP added in v1.12.0

func WithClientIP(ctx context.Context, ip string) context.Context

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) Handle added in v1.12.0

func (f Func) Handle(rc *RequestCtx, n Next) error

func (Func) Name added in v1.12.0

func (f Func) Name() string

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.

func Builtin

func Builtin(name, desc string) Info

Builtin constructs a builtin Info entry (rarely needed — Builtins has the common ones). Use for project-specific middleware you consider standard.

func Custom

func Custom(name, desc string) Info

Custom constructs a custom Info entry.

type Kind

type Kind string
const (
	KindBuiltin Kind = "builtin"
	KindCustom  Kind = "custom"
)

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.

const (
	PhaseRecover Phase = iota // outermost: panic boundary
	PhaseObserve              // trace, metrics, request-id
	PhaseCORS                 // skipped by the WS frame chain
	PhaseRateLimit
	PhaseAuth  // identity resolution
	PhaseAuthz // Requires(...) gates
	PhaseApp   // user nexus.Use(...), default
)

type RejectHook added in v1.12.0

type RejectHook func(c *httpx.Ctx, status int, err error) bool

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.

const (
	TransportREST Transport = iota
	TransportGraphQL
	TransportWebSocket
)

func (Transport) String added in v1.12.0

func (t Transport) String() string

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}".

Jump to

Keyboard shortcuts

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