Documentation
¶
Overview ¶
Package nethttp adapts api/rest route handles to net/http handlers.
Each [RouteHandle] from api/rest becomes an http.Handler via Handler. Register wires it directly onto an http.ServeMux using the Go 1.22+ method-prefixed pattern ("POST /users", "GET /users/{id}", etc.).
Typical usage:
b := rest.NewBuilder(rest.Info{Title: "User API", Version: "1.0.0"})
createUser, _ := rest.NewRoute[CreateReq, User]("POST", "/users", ...).Register(b)
mux := http.NewServeMux()
nethttp.Register(mux, createUser, func(ctx context.Context, req CreateReq) (User, error) {
// Access path params via the embedded request:
r, _ := nethttp.RequestFromContext(ctx)
id := r.PathValue("id")
return svc.CreateUser(ctx, req)
}, nethttp.Options{})
http.ListenAndServe(":8080", mux)
Error responses use the JSON body {"error":"<message>"} by default: 400 for decode/validation failures, 500 for handler or encode errors. Override via Options.ErrorHandler.
For body-less methods (GET, HEAD, DELETE) the handler function is called with the zero value of Req. Access path and query parameters through RequestFromContext.
Index ¶
- func Call[Req, Resp any](ctx context.Context, client *http.Client, baseURL string, ...) (Resp, error)
- func CallAdapter[Req, Resp any](client *http.Client, baseURL string, handle *rest.RouteHandle[Req, Resp], ...) ports.IOAdapter[Req, Resp]
- func CallHandle[Req, Resp any](ctx context.Context, client *http.Client, baseURL string, ...) (Resp, error)
- func DrainCallAdapter[Req, Resp any](client *http.Client, baseURL string, handle *rest.RouteHandle[Req, Resp], ...) ports.SinkAdapter[Req]
- func Handler[Req, Resp any](handle *rest.RouteHandle[Req, Resp], fn HandlerFunc[Req, Resp], opts Options) http.Handler
- func HandlerLatest[Req, Resp any](handle *rest.RouteHandle[Req, Resp], src gstream.Stream[Resp], opts Options) http.Handler
- func IngestAdapter[T any](mux *http.ServeMux, handle *rest.RouteHandle[T, struct{}], ...) ports.SourceAdapter[T]
- func LatestAdapter[Resp any](mux *http.ServeMux, handle *rest.RouteHandle[struct{}, Resp], opts Options) ports.LatestAdapter[Resp]
- func PipelineAdapter[Req, Resp any](mux *http.ServeMux, handle *rest.RouteHandle[Req, Resp], ...) ports.ToolAdapter[Req, Resp]
- func PipelineHandler[Req, Resp any](handle *rest.RouteHandle[Req, Resp], fn PipelineHandlerFunc[Req, Resp], ...) http.Handler
- func PollAdapter[Req, Resp any](client *http.Client, baseURL string, handle *rest.RouteHandle[Req, Resp], ...) ports.SourceAdapter[Resp]
- func Register[Req, Resp any](mux *http.ServeMux, handle *rest.RouteHandle[Req, Resp], ...)
- func RegisterLatest[Req, Resp any](mux *http.ServeMux, handle *rest.RouteHandle[Req, Resp], ...)
- func RegisterPipeline[Req, Resp any](mux *http.ServeMux, handle *rest.RouteHandle[Req, Resp], ...)
- func RegisterSSE[Req, Event any](mux *http.ServeMux, handle *rest.SSERouteHandle[Req, Event], ...)
- func RequestFromContext(ctx context.Context) (*http.Request, bool)
- func ResponseHeadersFromContext(ctx context.Context) (http.Header, bool)
- func SSEAdapter[Event any](mux *http.ServeMux, handle *rest.SSERouteHandle[struct{}, Event], ...) ports.SinkAdapter[Event]
- func SSEHandler[Req, Event any](handle *rest.SSERouteHandle[Req, Event], fn SSEHandlerFunc[Req, Event], ...) http.Handler
- func SetCookie(w http.ResponseWriter, name, value string, opts CookieOptions) error
- func WithResponseCookies(ctx context.Context, cookies ...PendingCookie)
- func WithResponseHeaders(ctx context.Context, h http.Header)
- type CallOptions
- type CallStreamOptions
- type CookieOptions
- type DrainCallOptions
- type ErrorPatternResponse
- type HandlerFunc
- type IngestAdapterOptions
- type NoLatestValueError
- type Options
- type PendingCookie
- type PipelineAdapterOptions
- type PipelineFullError
- type PipelineHandlerFunc
- type PipelineNoResponseError
- type PollStreamOptions
- type RequestBuildError
- type RequestError
- type ResponseBodyError
- type SSEAdapterOptions
- type SSEConnectError
- type SSEHandlerFunc
- type SSEParseError
- type SSEStreamOptions
- type SSEWriteError
- type UnexpectedStatusError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Call ¶ added in v0.10.0
func Call[Req, Resp any]( ctx context.Context, client *http.Client, baseURL string, handle *rest.RouteHandle[Req, Resp], req Req, vars map[string]string, opts CallOptions, ) (Resp, error)
Call executes a typed HTTP request for the given route handle against baseURL.
The concrete URL is built as baseURL + handle.BuildPath(vars) + "?" + queryString. For body-bearing methods (POST, PUT, PATCH) req is JSON-encoded as the request body; for other methods (GET, HEAD, DELETE) req is ignored.
All parameters are validated against their registered codecs before the request is sent: path variables via rest.RouteHandle.BuildPath, query parameters via rest.RouteHandle.ValidateQuery, cookies via rest.RouteHandle.ValidateCookies, and request headers via rest.RouteHandle.ValidateHeaders. A validation failure returns the corresponding rest error type (e.g. rest.PathParamError, rest.QueryParamError) without sending any request.
Security requirements: if the route declares non-nil Security (or inherits global security), CallOptions.CredentialFunc is called to obtain the Authorization headers. A nil CredentialFunc on a secured route is not an error — the request is sent without credential injection; use CallOptions.ExtraHeaders to supply static credentials instead.
On a 2xx response the body is decoded into Resp using the route's response codec. On a non-2xx response UnexpectedStatusError is returned.
CallOptions.Observer receives stats.Observer.RecordRequest after every call (success or failure) with the route path template (not the concrete URL), status code, and total duration. Per-field validation errors are reported separately via stats.Observer.RecordValidationError.
Example — GET with path variable:
handle := getUserRoute.ClientHandle()
user, err := nethttp.Call(ctx, http.DefaultClient, "https://api.example.com",
handle, struct{}{}, map[string]string{"id": "f47ac10b"},
nethttp.CallOptions{Observer: obs})
Example — POST with body and bearer token:
handle := createUserRoute.ClientHandle()
resp, err := nethttp.Call(ctx, http.DefaultClient, "https://api.example.com",
handle, createReq, nil,
nethttp.CallOptions{
CredentialFunc: func(ctx context.Context, reqs []route.SecurityRequirement) (http.Header, error) {
h := make(http.Header)
h.Set("Authorization", "Bearer "+token)
return h, nil
},
})
Example ¶
// Define the route — use ClientHandle() when no OpenAPI spec is needed.
type Item struct{ ID, Name string }
itemCodec := codex.Struct[Item](
codex.OptionalField("id", codex.String(),
func(i Item) string { return i.ID },
func(i *Item, v string) { i.ID = v },
),
codex.RequiredField("name", codex.String().Refine(validate.NonEmptyString),
func(i Item) string { return i.Name },
func(i *Item, v string) { i.Name = v },
),
)
getRoute := rest.NewRoute[getReq, Item]("GET", "/items/{id}",
codex.Struct[getReq](), itemCodec,
rest.PathParam{Name: "id"}.WithCodec(codex.String().Refine(validate.NonEmptyString)),
).ClientHandle()
// Validate path params before any HTTP call.
_, err := nethttp.Call(context.Background(), http.DefaultClient, "https://api.example.com",
getRoute, getReq{}, map[string]string{"id": ""},
nethttp.CallOptions{})
if err != nil {
var pathErr rest.PathParamError
if errors.As(err, &pathErr) {
fmt.Printf("param %q rejected: %v\n", pathErr.Name, pathErr.Err)
}
}
Output: param "id" rejected: constraint failed (non-empty): expected non-empty string
func CallAdapter ¶ added in v0.11.0
func CallAdapter[Req, Resp any]( client *http.Client, baseURL string, handle *rest.RouteHandle[Req, Resp], opts CallStreamOptions, ) ports.IOAdapter[Req, Resp]
CallAdapter returns a ports.IOAdapter that sends each item as an HTTP request, emitting responses downstream. Use with ports.IOPort.Bind:
domain.Calibration.Bind(ctx, nethttp.CallAdapter(
httpClient, "http://svc:8080", calibHandle, nethttp.CallStreamOptions{}))
func CallHandle ¶ added in v0.12.0
func CallHandle[Req, Resp any]( ctx context.Context, client *http.Client, baseURL string, handle *rest.RouteHandle[Req, Resp], req Req, opts CallOptions, ) (Resp, error)
CallHandle is the single-call convenience wrapper around Call: it derives the path vars AND CallOptions.QueryParams/[HeaderParams]/ [CookieParams] from req automatically, using the route's role-aware merge-field accessors (rest.RouteHandle.PathMergeFields/ [QueryMergeFields]/[HeaderMergeFields]/[CookieMergeFields]) and codex.EncodeVars — one line instead of building each map by hand.
Any entry already present in opts.QueryParams/HeaderParams/CookieParams takes PRECEDENCE over the corresponding derived value for the same key — this lets a caller override a struct field's value, or add ad-hoc params the struct doesn't declare, without losing the one-line convenience for the common case. opts fields left nil are populated entirely from the derived values (or left nil if the route declares no merge fields for that role).
Call remains available as the lower-level escape hatch for callers that build the maps themselves — e.g. no merge fields declared, path vars from a non-struct source, or a route shared between multiple unrelated Req shapes.
Example:
handle := getUserActivity.ClientHandle()
activity, err := nethttp.CallHandle(ctx, client, baseURL, handle,
GetUserActivityReq{ID: userID, Filter: "logins"}, nethttp.CallOptions{})
func DrainCallAdapter ¶ added in v0.11.0
func DrainCallAdapter[Req, Resp any]( client *http.Client, baseURL string, handle *rest.RouteHandle[Req, Resp], opts DrainCallOptions, ) ports.SinkAdapter[Req]
DrainCallAdapter returns a ports.SinkAdapter that calls an HTTP endpoint for each item (fire-and-forget; response discarded). Use with ports.SinkPort.Bind:
domain.Events.Bind(ctx, nethttp.DrainCallAdapter(
client, "http://audit-svc", auditHandle, nethttp.DrainCallOptions{}))
func Handler ¶
func Handler[Req, Resp any](handle *rest.RouteHandle[Req, Resp], fn HandlerFunc[Req, Resp], opts Options) http.Handler
Handler wraps a rest.RouteHandle and a HandlerFunc into an http.Handler.
For body-bearing methods (POST, PUT, PATCH) the request body is read, decoded, and validated using the route's codec before fn is called. For other methods (GET, HEAD, DELETE) fn is called with the zero value of Req.
On success the response is JSON-encoded and written with the HTTP status from the route descriptor's primary response (the first entry in Responses).
Pass a zero-value Options{} for default behaviour (JSON error envelope, 1 MiB body limit, application/json Content-Type check, no-op observer).
func HandlerLatest ¶ added in v0.11.0
func HandlerLatest[Req, Resp any]( handle *rest.RouteHandle[Req, Resp], src gstream.Stream[Resp], opts Options, ) http.Handler
HandlerLatest returns an http.Handler that responds to every request with the most recently emitted value from src.
A background goroutine reads src.Values and atomically stores each value. On the first request before any value is available, the handler calls opts.ErrorHandler with HTTP 503 and NoLatestValueError. Errors from src.Errors are reported to opts.Observer but do not affect responses.
Use HandlerLatest for "get current OEE", "get latest sensor reading", or any "current state" REST endpoint backed by a continuously running stream pipeline.
Codec coverage — all HTTP layers validated ¶
Handler validates all codec layers before the fn fires: body codec, query params, cookie params, header params, path params, and security. The decoded [Req] value and all param values are validated but not used for computation — the response is always the latest stream value. This ensures only well-formed requests receive a cached response; invalid requests produce the standard 400.
func IngestAdapter ¶ added in v0.11.0
func IngestAdapter[T any]( mux *http.ServeMux, handle *rest.RouteHandle[T, struct{}], opts IngestAdapterOptions, ) ports.SourceAdapter[T]
IngestAdapter returns a ports.SourceAdapter that accepts HTTP requests as pipeline items. When Activate is called it registers a handler with mux. Use with ports.SourcePort.Bind:
domain.SensorReadings.Bind(ctx, nethttp.IngestAdapter(
mux, ingestHandle, nethttp.IngestAdapterOptions{Buffer: 8}))
func LatestAdapter ¶ added in v0.12.0
func LatestAdapter[Resp any]( mux *http.ServeMux, handle *rest.RouteHandle[struct{}, Resp], opts Options, ) ports.LatestAdapter[Resp]
LatestAdapter returns a ports.LatestAdapter that serves a ports.LatestPort's cached value as a GET endpoint — the port-based successor to HandlerLatest/RegisterLatest (which own their own cache cell; the port owns it here). Use with ports.LatestPort.Bind:
handle, _ := domain.Latest.PluginRESTPattern(domain.LatestPattern)
must(domain.Latest.Bind(ctx, nethttp.LatestAdapter(mux, handle, nethttp.Options{})))
go domain.Latest.Feed(ctx, readings)
Before the first value arrives the handler responds 503 Service Unavailable with NoLatestValueError (same semantics as HandlerLatest). All codec layers (params, security) validate exactly as with Handler.
func PipelineAdapter ¶ added in v0.11.0
func PipelineAdapter[Req, Resp any]( mux *http.ServeMux, handle *rest.RouteHandle[Req, Resp], opts PipelineAdapterOptions, ) ports.ToolAdapter[Req, Resp]
PipelineAdapter returns a ports.ToolAdapter that registers the pipeline function as an HTTP endpoint via PipelineHandler. When ports.ToolPort.Bind is called it registers the handler with mux. Use with ports.ToolPort.Bind:
domain.OEEToolPort.Bind(ctx, nethttp.PipelineAdapter(mux, httpHandle,
nethttp.PipelineAdapterOptions{}))
func PipelineHandler ¶ added in v0.11.0
func PipelineHandler[Req, Resp any]( handle *rest.RouteHandle[Req, Resp], fn PipelineHandlerFunc[Req, Resp], opts Options, ) http.Handler
PipelineHandler wraps a PipelineHandlerFunc into an http.Handler. All codec validation, param validation, security enforcement, and observer integration follow the same path as plain Handler — PipelineHandler is a thin wrapper that adapts the function signature and collects the result via gstream.Collect.
Use PipelineHandler when the handler body benefits from:
- gstream.Tap for declarative intermediate observation (log/metrics/audit)
- gstream.Apply for multi-step forge function composition
- gstream.MapErr for per-step typed error recovery
For simple one-step handlers, use plain Handler for lower overhead.
Codec coverage — all HTTP layers ¶
Before fn is called, Handler has already validated and decoded:
- Request body (→ req Req)
- Query, cookie, header, path params (all registered rest.Param codecs)
- Security credentials + SecurityFunc
After fn returns, Handler validates:
- Response body (handle.Encode)
- Response header and cookie params (ValidateResponseHeaders / ValidateResponseCookies)
Accessing path/query/cookie/header param values inside the pipeline ¶
The decoded [Req] value (body) is passed directly to fn. To access path, query, cookie, or header param values inside the pipeline (already codec- validated by Handler), call RequestFromContext on the ctx passed to fn:
nethttp.RegisterPipeline(mux, handle,
func(ctx context.Context, body SensorBody) stream.Stream[OEEResult] {
r, _ := nethttp.RequestFromContext(ctx)
sensorID := r.PathValue("sensorID") // already validated
s := stream.Single(ctx, body)
s = stream.Tap(ctx, s, func(v SensorBody) {
slog.Info("request", "sensor", sensorID, "value", v.Value)
})
return stream.Apply(ctx, s, oeeCalcFn, opts)
}, opts)
Response headers and cookies inside the pipeline ¶
Call WithResponseHeaders or WithResponseCookies anywhere inside the pipeline fn (including within gstream.Tap or forge functions). The maps are reference types stored in ctx — writes in the pipeline goroutines are visible to Handler after gstream.Collect returns. This is safe for sequential pipelines (Single → Apply chain). Parallel pipelines that write to response headers concurrently should use a mutex or avoid this pattern.
func PollAdapter ¶ added in v0.11.0
func PollAdapter[Req, Resp any]( client *http.Client, baseURL string, handle *rest.RouteHandle[Req, Resp], req Req, interval time.Duration, opts PollStreamOptions, ) ports.SourceAdapter[Resp]
PollAdapter returns a ports.SourceAdapter that polls an HTTP endpoint at interval, emitting each response. Use with ports.SourcePort.Bind:
domain.Configs.Bind(ctx, nethttp.PollAdapter(
client, "http://config-svc", configHandle, ConfigReq{}, 5*time.Minute, nethttp.PollStreamOptions{}))
func Register ¶
func Register[Req, Resp any](mux *http.ServeMux, handle *rest.RouteHandle[Req, Resp], fn HandlerFunc[Req, Resp], opts Options)
Register registers the route on mux using its method and path from the route descriptor. It uses the Go 1.22+ enhanced ServeMux pattern "METHOD /path" so each registration is scoped to a single method.
Pass a zero-value Options{} for default behaviour.
func RegisterLatest ¶ added in v0.11.0
func RegisterLatest[Req, Resp any]( mux *http.ServeMux, handle *rest.RouteHandle[Req, Resp], src gstream.Stream[Resp], opts Options, )
RegisterLatest wires HandlerLatest onto mux using the route's method and path. Mirrors Register.
func RegisterPipeline ¶ added in v0.11.0
func RegisterPipeline[Req, Resp any]( mux *http.ServeMux, handle *rest.RouteHandle[Req, Resp], fn PipelineHandlerFunc[Req, Resp], opts Options, )
RegisterPipeline wires PipelineHandler onto mux. Mirrors Register.
func RegisterSSE ¶ added in v0.8.0
func RegisterSSE[Req, Event any](mux *http.ServeMux, handle *rest.SSERouteHandle[Req, Event], fn SSEHandlerFunc[Req, Event], opts Options)
RegisterSSE wires an rest.SSERouteHandle onto mux as a GET SSE endpoint.
func RequestFromContext ¶
RequestFromContext retrieves the *http.Request stored in ctx by Handler. Returns false if the context was not created by this package.
func ResponseHeadersFromContext ¶ added in v0.8.0
ResponseHeadersFromContext retrieves response headers previously stored by WithResponseHeaders. Returns false if no headers were set.
func SSEAdapter ¶ added in v0.11.0
func SSEAdapter[Event any]( mux *http.ServeMux, handle *rest.SSERouteHandle[struct{}, Event], opts SSEAdapterOptions, ) ports.SinkAdapter[Event]
SSEAdapter returns a ports.SinkAdapter that serves each item from the SinkPort as an SSE event to all connected clients. Use with ports.SinkPort.Bind:
domain.OEEResults.Bind(ctx, nethttp.SSEAdapter(mux, sseHandle, nethttp.SSEAdapterOptions{}))
func SSEHandler ¶ added in v0.8.0
func SSEHandler[Req, Event any](handle *rest.SSERouteHandle[Req, Event], fn SSEHandlerFunc[Req, Event], opts Options) http.Handler
SSEHandler wraps an rest.SSERouteHandle and a user-supplied SSEHandlerFunc into an http.Handler that streams Server-Sent Events.
The handler sets Content-Type: text/event-stream, Cache-Control: no-cache, and Connection: keep-alive, then calls fn. The send func provided to fn validates the event via the codec, encodes it as JSON, writes "data: <json>\n\n" to the response, and flushes. If the event fails validation, send returns an error without writing anything.
fn should honour ctx.Done() for clean client-disconnect handling.
func SetCookie ¶ added in v0.8.0
func SetCookie(w http.ResponseWriter, name, value string, opts CookieOptions) error
SetCookie writes a Set-Cookie header on w with secure defaults: Secure, HttpOnly, SameSite=Strict, Path="/".
If CookieOptions.Codec is non-nil, value is validated first using that codec. A validation failure returns a rest.CookieParamError without writing any header — the same error type returned by rest.RouteHandle.ValidateCookies on the read side.
Example — symmetric read/write validation from a single codec:
sessionCodec := codex.String().Refine(validate.MinLen(32))
// Read (adapter validates automatically via CookieParam):
rest.CookieParam{Name: "session_token"}.WithCodec(sessionCodec)
// Write (handler sets the cookie with the same codec):
if err := nethttp.SetCookie(w, "session_token", token,
nethttp.CookieOptions{MaxAge: 3600}.WithCodec(sessionCodec),
); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
func WithResponseCookies ¶ added in v0.8.0
func WithResponseCookies(ctx context.Context, cookies ...PendingCookie)
WithResponseCookies deposits one or more PendingCookie values into ctx. Handler validates their values against the route's [ResponseCookieParam] codecs and then writes Set-Cookie headers on success. Call this inside a HandlerFunc to emit response cookies.
func WithResponseHeaders ¶ added in v0.8.0
WithResponseHeaders copies the key-value pairs from h into the response header map stored in ctx (pre-allocated by Handler before calling the HandlerFunc). Call this inside a HandlerFunc to emit response headers such as Location, ETag, or custom headers without direct access to http.ResponseWriter.
resp, _ := svc.Create(ctx, req)
h := make(http.Header)
h.Set("Location", "/users/"+resp.ID)
nethttp.WithResponseHeaders(ctx, h) // mutates the header map in ctx
Types ¶
type CallOptions ¶ added in v0.10.0
type CallOptions struct {
// QueryParams appends query string parameters to the URL.
// Each value is validated against its registered [rest.QueryParam] codec (if any)
// before the request is sent.
QueryParams map[string]string
// CookieParams adds cookies to the outgoing request.
// Each value is validated against its registered [rest.CookieParam] codec (if any)
// before the request is sent.
CookieParams map[string]string
// HeaderParams adds declared request headers to the outgoing request.
// Each value is validated against its registered [rest.HeaderParam] codec (if any)
// before the request is sent.
//
// Do not pass the Authorization header here — use [CallOptions.ExtraHeaders] or
// [CallOptions.CredentialFunc] for security credentials.
HeaderParams map[string]string
// ExtraHeaders adds arbitrary HTTP headers to the outgoing request without
// codec validation. Use for non-declared headers such as X-Request-ID,
// User-Agent, or static Authorization values.
ExtraHeaders http.Header
// CredentialFunc, when non-nil, is called for routes that declare non-nil
// Security requirements. It receives the effective security requirements and
// must return headers to merge into the outgoing request (e.g. Authorization).
// Return a non-nil error to abort the call before the request is sent.
//
// Use [CallOptions.ExtraHeaders] for simple static credentials;
// use CredentialFunc for structured or dynamic credential injection —
// it mirrors the server-side SecurityFunc pattern.
CredentialFunc func(ctx context.Context, reqs []route.SecurityRequirement) (http.Header, error)
// Observer, when non-nil, receives per-call lifecycle events.
// [stats.Observer.RecordRequest] is called on every code path — including
// early-exit validation failures — with the HTTP method, route path template
// (not the concrete URL), HTTP status code, and total duration.
// Status 0 is used when validation fails before any HTTP request is sent
// (path var, query, cookie, or header codec failure; credential func error;
// or request build error). This allows observers to count all call attempts,
// including those that never reach the network.
// Per-field validation errors are reported via [stats.Observer.RecordValidationError].
// Defaults to [stats.NoopObserver] when nil.
Observer stats.Observer
}
CallOptions configures an outgoing HTTP request made via Call.
type CallStreamOptions ¶ added in v0.11.0
type CallStreamOptions struct {
// Vars substitutes {varName} placeholders in the route's path template.
//
// When nil, path/query/header/cookie vars are derived PER-ITEM from each
// item's own merge-field-declared struct fields (the same convenience
// [CallHandle] provides). When set to a non-nil map (including an
// explicitly empty one), that map is used as-is for every request
// (static vars only) — the escape hatch, unchanged from prior behavior.
Vars map[string]string
CallOpts CallOptions
// Buffer is the output Stream channel buffer size. Default 0.
Buffer int
}
CallStreamOptions configures CallAdapter.
type CookieOptions ¶ added in v0.8.0
type CookieOptions struct {
// Path is the cookie path. Defaults to "/" when empty.
Path string
// Domain scopes the cookie to a specific host. Defaults to the current host.
Domain string
// MaxAge is the cookie lifetime in seconds.
// 0 means session cookie (deleted when browser closes).
// Negative means delete the cookie immediately.
MaxAge int
// SameSite controls the cross-site request behaviour.
// Defaults to [http.SameSiteStrictMode] when zero.
SameSite http.SameSite
// Insecure, when true, omits the Secure attribute.
// Use only for non-TLS environments such as localhost development.
// Default: false (Secure is always set).
Insecure bool
// AllowJS, when true, omits the HttpOnly attribute, making the cookie
// accessible via document.cookie. Required for patterns such as
// CSRF tokens that must be read by client-side JavaScript.
// Default: false (HttpOnly is always set).
AllowJS bool
// Codec, when non-nil, validates value before the Set-Cookie header is
// written. Use the same [codex.Codec] as the matching [rest.CookieParam]
// for symmetric read/write validation from a single definition.
// Set via [CookieOptions.WithCodec] to avoid address-of boilerplate.
//
// If validation fails, SetCookie returns [rest.CookieParamError] and does
// NOT write the Set-Cookie header.
Codec *codex.Codec[string]
}
CookieOptions configures the security attributes and optional value validation applied by SetCookie.
Safe defaults: Secure=true, HttpOnly=true, SameSite=Strict, Path="/". Use the opt-in fields to relax specific attributes for legitimate use cases.
func (CookieOptions) WithCodec ¶ added in v0.8.0
func (o CookieOptions) WithCodec(c codex.Codec[string]) CookieOptions
WithCodec sets the validation codec and returns the updated CookieOptions. Avoids the temporary-variable + address-of pattern required when setting Codec inline:
err := nethttp.SetCookie(w, "session_token", token,
nethttp.CookieOptions{MaxAge: 3600}.WithCodec(sessionCodec))
type DrainCallOptions ¶ added in v0.11.0
type DrainCallOptions struct {
// Vars substitutes {varName} placeholders in the route's path template.
//
// When nil, path/query/header/cookie vars are derived PER-ITEM from each
// item's own merge-field-declared struct fields (the same convenience
// [CallHandle] provides) — every item may resolve to a different
// concrete path/query/header/cookie set. When set to a non-nil map
// (including an explicitly empty one), that map is used as-is for every
// item (today's static-vars behavior, unchanged) — this remains the
// escape hatch for routes with no merge fields or a route shared across
// unrelated Req shapes.
Vars map[string]string
OnError func(error)
CallOpts CallOptions
}
DrainCallOptions configures DrainCallAdapter.
type ErrorPatternResponse ¶ added in v0.12.0
type ErrorPatternResponse struct {
// StatusCode is the HTTP response status code returned by the server.
StatusCode int
// Value is the decoded typed error payload (the pattern's declared B type).
Value any
// Body is the raw response body returned by the server.
Body []byte
}
ErrorPatternResponse is returned by Call instead of UnexpectedStatusError when the response status matches a route-declared rest.ErrorPattern (tagged with the default rest.ErrorRespond action) and the body decodes successfully via that pattern's declared codec.
Use errors.As to extract the decoded typed payload:
var epr nethttp.ErrorPatternResponse
if errors.As(err, &epr) {
payload, ok := epr.Value.(MyErrorPayload)
...
}
func (ErrorPatternResponse) Error ¶ added in v0.12.0
func (e ErrorPatternResponse) Error() string
func (ErrorPatternResponse) LogValue ¶ added in v0.12.0
func (e ErrorPatternResponse) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type HandlerFunc ¶
HandlerFunc is the typed application handler called by Handler. ctx is the request context. req is the decoded request value; for body-less methods it is the zero value of Req. Use RequestFromContext to access the underlying *http.Request for path parameters, headers, or other request metadata.
type IngestAdapterOptions ¶ added in v0.11.0
IngestAdapterOptions configures IngestAdapter.
type NoLatestValueError ¶ added in v0.11.0
type NoLatestValueError struct {
// Path is the route path (from RouteHandle.Descriptor.Path).
Path string
}
NoLatestValueError is passed to Options.ErrorHandler (status 503) by HandlerLatest when the background stream has not yet produced a value.
Use errors.As to distinguish this from other handler errors:
opts.ErrorHandler = func(w http.ResponseWriter, r *http.Request, status int, err error) {
var nlv nethttp.NoLatestValueError
if errors.As(err, &nlv) {
http.Error(w, "service warming up", http.StatusServiceUnavailable)
return
}
// default handling …
}
func (NoLatestValueError) Error ¶ added in v0.11.0
func (e NoLatestValueError) Error() string
func (NoLatestValueError) LogValue ¶ added in v0.11.0
func (e NoLatestValueError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type Options ¶
type Options struct {
// ErrorHandler, when non-nil, is called instead of the default JSON error
// envelope when a request fails. status is the suggested HTTP status code
// (400 or 500). Implementations must write the response header and body.
ErrorHandler func(w http.ResponseWriter, r *http.Request, status int, err error)
// Observer, when non-nil, receives per-request lifecycle events: request
// counts with latency and HTTP status, and per-field validation errors.
// Defaults to [stats.NoopObserver] when nil.
Observer stats.Observer
// MaxBodyBytes limits the number of bytes read from the request body for
// body-bearing methods (POST, PUT, PATCH). Zero means the default (1 MiB).
// Requests exceeding the limit are rejected with 400 Bad Request.
MaxBodyBytes int64
// ContentType is the expected Content-Type for body-bearing methods (POST,
// PUT, PATCH). When non-empty, requests whose Content-Type does not match
// (ignoring parameters such as "; charset=utf-8") are rejected with
// 415 Unsupported Media Type. Defaults to "application/json".
ContentType string
// MultiValueQueryParams, when true, passes the raw multi-value query map
// (map[string][]string from r.URL.Query()) to [rest.RouteHandle.ValidateQueryMulti]
// instead of the flat single-value map. Use when your routes use repeated query
// keys such as "?tags=a&tags=b". When false (default), the first value per key
// is validated via [rest.RouteHandle.ValidateQuery].
MultiValueQueryParams bool
// SecurityFunc, when non-nil, is called for routes that declare a non-nil
// Security field (via [rest.RouteMeta.Security] or global security), after
// parameter validation but before the handler fn.
//
// Return a non-nil error to reject the request with 401 Unauthorized.
// reqs contains the route's declared security requirements (scheme names +
// scopes). The adapter has already extracted and codec-validated the credential
// from the request before calling SecurityFunc.
//
// Example — JWT bearer verification:
//
// opts.SecurityFunc = func(ctx context.Context, r *http.Request, reqs []route.SecurityRequirement) error {
// token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
// return jwtlib.VerifyScopes(token, reqs)
// }
SecurityFunc func(ctx context.Context, r *http.Request, reqs []route.SecurityRequirement) error
}
type PendingCookie ¶ added in v0.8.0
type PendingCookie struct {
Name string
Value string
Opts CookieOptions
}
PendingCookie is a cookie queued to be validated and written as a Set-Cookie response header by Handler. Create one inside a HandlerFunc and deposit it via WithResponseCookies.
func ResponseCookiesFromContext ¶ added in v0.8.0
func ResponseCookiesFromContext(ctx context.Context) ([]PendingCookie, bool)
ResponseCookiesFromContext retrieves pending cookies previously stored by WithResponseCookies. Returns false if no cookies were queued.
type PipelineAdapterOptions ¶ added in v0.11.0
type PipelineAdapterOptions struct {
Options Options
}
PipelineAdapterOptions configures PipelineAdapter.
type PipelineFullError ¶ added in v0.11.0
type PipelineFullError struct {
// Path is the route path (from RouteHandle.Descriptor.Path).
Path string
// Capacity is cap(dst) at the time of the rejection.
Capacity int
}
PipelineFullError is passed to Options.ErrorHandler (status 503) by [HandlerIngest] when the destination channel is full and the incoming request cannot be enqueued without blocking.
The Capacity field reports cap(dst) to help callers tune buffer sizing.
func (PipelineFullError) Error ¶ added in v0.11.0
func (e PipelineFullError) Error() string
func (PipelineFullError) LogValue ¶ added in v0.11.0
func (e PipelineFullError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type PipelineHandlerFunc ¶ added in v0.11.0
PipelineHandlerFunc is a handler function that implements its logic as a gstream.Stream. It must emit exactly one value (the HTTP response). Use gstream.Single to wrap the decoded Req as the pipeline source.
Error handling:
- If Stream.Errors fires, the first error becomes the HTTP error response.
- If no value is produced before ctx is cancelled, PipelineNoResponseError is returned.
- If the pipeline emits more than one value, only the first is used.
type PipelineNoResponseError ¶ added in v0.11.0
type PipelineNoResponseError struct {
// Path is the route path (from RouteHandle.Descriptor.Path).
Path string
}
PipelineNoResponseError is returned by PipelineHandler when [stream.Collect] returns with no values — either the pipeline emitted nothing, or the request context was cancelled before the pipeline produced a result.
func (PipelineNoResponseError) Error ¶ added in v0.11.0
func (e PipelineNoResponseError) Error() string
func (PipelineNoResponseError) LogValue ¶ added in v0.11.0
func (e PipelineNoResponseError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type PollStreamOptions ¶ added in v0.11.0
type PollStreamOptions struct {
// Vars, when non-nil, substitutes {varName} placeholders in the route's path template.
// The same map is used for every poll (static path vars only).
Vars map[string]string
Observer stats.Observer
Buffer int
}
PollStreamOptions configures PollAdapter.
type RequestBuildError ¶ added in v0.10.0
type RequestBuildError struct {
// Err is the underlying error from [http.NewRequestWithContext].
Err error
}
RequestBuildError is returned by Call when constructing the outgoing *http.Request fails (e.g. malformed base URL or context already cancelled).
Use errors.As to extract the underlying error for slog logging:
var buildErr nethttp.RequestBuildError
if errors.As(err, &buildErr) {
slog.Error("failed to build request", "cause", buildErr.Err)
}
func (RequestBuildError) Error ¶ added in v0.10.0
func (e RequestBuildError) Error() string
func (RequestBuildError) LogValue ¶ added in v0.11.0
func (e RequestBuildError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type RequestError ¶ added in v0.10.0
type RequestError struct {
// Method is the HTTP method (e.g. "GET", "POST").
Method string
// Path is the route path template (e.g. "/users/{id}").
Path string
// Err is the underlying transport error from [http.Client.Do].
Err error
}
RequestError is returned by Call when executing the HTTP call fails (network error, DNS failure, TLS error, or context cancellation).
Use errors.As to extract the structured fields for slog logging:
var reqErr nethttp.RequestError
if errors.As(err, &reqErr) {
slog.Error("http call failed",
"method", reqErr.Method,
"path", reqErr.Path,
"cause", reqErr.Err,
)
}
func (RequestError) Error ¶ added in v0.10.0
func (e RequestError) Error() string
func (RequestError) LogValue ¶ added in v0.11.0
func (e RequestError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type ResponseBodyError ¶ added in v0.10.0
type ResponseBodyError struct {
// Err is the underlying error from reading the response body.
Err error
}
ResponseBodyError is returned by Call when reading the HTTP response body fails after a successful connection.
Use errors.As to extract the underlying error for slog logging:
var bodyErr nethttp.ResponseBodyError
if errors.As(err, &bodyErr) {
slog.Error("failed to read response body", "cause", bodyErr.Err)
}
func (ResponseBodyError) Error ¶ added in v0.10.0
func (e ResponseBodyError) Error() string
func (ResponseBodyError) LogValue ¶ added in v0.11.0
func (e ResponseBodyError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type SSEAdapterOptions ¶ added in v0.11.0
type SSEAdapterOptions struct {
Options Options
SSEStreamOptions SSEStreamOptions
}
SSEAdapterOptions configures SSEAdapter.
type SSEConnectError ¶ added in v0.11.0
type SSEConnectError struct {
// URL is the SSE endpoint URL.
URL string
// Attempt is the 1-based reconnect attempt number.
Attempt int
// Err is the underlying connection error (network, TLS, non-200 status, etc.).
Err error
}
SSEConnectError is sent to [Stream.Errors] by [SSEClientStream] when an HTTP connection attempt to the SSE endpoint fails. The stream retries after backoff; this error is informational per reconnect attempt.
func (SSEConnectError) Error ¶ added in v0.11.0
func (e SSEConnectError) Error() string
func (SSEConnectError) LogValue ¶ added in v0.11.0
func (e SSEConnectError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type SSEHandlerFunc ¶ added in v0.8.0
type SSEHandlerFunc[Req, Event any] func(ctx context.Context, req Req, send func(Event) error) error
SSEHandlerFunc is the typed application handler called by SSEHandler. ctx is the request context (cancelled when the client disconnects). req is the decoded request (zero value for body-less GET requests). send encodes, validates, and writes one SSE event; it returns an error if the event fails codec validation or if the underlying write fails.
func SSEFromHub ¶ added in v0.11.0
func SSEFromHub[Req, Event any]( hub *gstream.BroadcastHub[Event], opts SSEStreamOptions, ) SSEHandlerFunc[Req, Event]
SSEFromHub returns an SSEHandlerFunc backed by a shared gstream.BroadcastHub. Each connecting client subscribes to the hub and receives items from that moment forward; subscriptions are cleaned up on disconnect.
Use SSEFromHub for live dashboards broadcasting the same stream to all users:
hub := stream.NewBroadcastHub(ctx, oeeStream, 32)
nethttp.RegisterSSE(mux, dashboardRoute,
nethttp.SSEFromHub[struct{}, OEEResult](hub,
nethttp.SSEStreamOptions{Topic: dashboardRoute.Descriptor.Path, Observer: obs}),
nethttp.Options{Observer: obs})
type SSEParseError ¶ added in v0.11.0
type SSEParseError struct {
// URL is the SSE endpoint URL.
URL string
// Line is the raw SSE "data:" line that failed (without the "data: " prefix).
Line string
// Err is the underlying decode error.
Err error
}
SSEParseError is sent to [Stream.Errors] by [SSEClientStream] when an SSE data line cannot be decoded using the provided format — malformed JSON, failed codec validation, or other decode failure.
func (SSEParseError) Error ¶ added in v0.11.0
func (e SSEParseError) Error() string
func (SSEParseError) LogValue ¶ added in v0.11.0
func (e SSEParseError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type SSEStreamOptions ¶ added in v0.11.0
type SSEStreamOptions struct {
// Topic is the SSE route path used for observer reporting and error context.
// Set this to handle.Descriptor.Path when wiring via SSEHandler/RegisterSSE.
Topic string
// OnError, when non-nil, is called for write failures ([SSEWriteError]) and
// any errors forwarded from the upstream stream.
OnError func(error)
// Observer receives per-event lifecycle events.
// [stats.Observer.RecordSubscribe] is called with success=true on each
// emitted event and success=false on write or stream errors.
// [stats.TraceObserver] spans wrap each send attempt when implemented.
Observer stats.Observer
}
SSEStreamOptions configures [SSEFromStream] and SSEFromHub.
type SSEWriteError ¶ added in v0.11.0
type SSEWriteError struct {
// Path is the route path (from SSERouteHandle.Descriptor.Path).
Path string
// Err is the underlying write error.
Err error
}
SSEWriteError is passed to SSEStreamOptions.OnError by [SSEFromStream] and SSEFromHub when writing a server-sent event to the HTTP response fails — typically because the client disconnected.
func (SSEWriteError) Error ¶ added in v0.11.0
func (e SSEWriteError) Error() string
func (SSEWriteError) LogValue ¶ added in v0.11.0
func (e SSEWriteError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type UnexpectedStatusError ¶ added in v0.10.0
type UnexpectedStatusError struct {
// Method is the HTTP method used for the request (e.g. "GET", "POST").
Method string
// Path is the route path template, not the concrete URL (e.g. "/users/{id}").
// Use this for log grouping and metrics — it does not contain the base URL or
// concrete path variable values.
Path string
// StatusCode is the HTTP response status code returned by the server.
StatusCode int
// Body is the raw response body returned by the server (may be nil or empty).
Body []byte
}
UnexpectedStatusError is returned by Call when the server responds with a non-2xx HTTP status code.
Use errors.As to extract the structured fields for slog logging:
var statusErr nethttp.UnexpectedStatusError
if errors.As(err, &statusErr) {
slog.Error("unexpected response",
"method", statusErr.Method,
"path", statusErr.Path,
"status", statusErr.StatusCode,
"body", string(statusErr.Body),
)
}
func (UnexpectedStatusError) Error ¶ added in v0.10.0
func (e UnexpectedStatusError) Error() string
func (UnexpectedStatusError) LogValue ¶ added in v0.11.0
func (e UnexpectedStatusError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.