Documentation
¶
Overview ¶
Package chi adapts api/rest route handles to github.com/go-chi/chi/v5 routers.
Each [RouteHandle] from api/rest becomes an http.HandlerFunc via Handler. Register wires it directly onto a chi.Router using the route's method and path.
Chi uses {param} placeholders identical to the go-codex path template syntax, so no path translation is needed. Path variables are extracted via chi.URLParam.
Typical usage:
b := rest.NewBuilder(rest.Info{Title: "User API", Version: "1.0.0"})
createUser, _ := rest.NewRoute[CreateReq, User]("POST", "/users", ...).Register(b)
r := chi.NewRouter()
chiadapter.Register(r, createUser, func(ctx context.Context, req CreateReq) (User, error) {
rr, _ := chiadapter.RequestFromContext(ctx)
id := chi.URLParam(rr, "id")
return svc.CreateUser(ctx, req)
}, chiadapter.Options{})
http.ListenAndServe(":8080", r)
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 and chi.URLParam.
Index ¶
- func Handler[Req, Resp any](handle *rest.RouteHandle[Req, Resp], fn HandlerFunc[Req, Resp], opts Options) http.HandlerFunc
- func HandlerLatest[Req, Resp any](handle *rest.RouteHandle[Req, Resp], src gstream.Stream[Resp], opts Options) http.HandlerFunc
- func IngestAdapter[T any](r gochi.Router, handle *rest.RouteHandle[T, struct{}], ...) ports.SourceAdapter[T]
- func PipelineAdapter[Req, Resp any](r gochi.Router, handle *rest.RouteHandle[Req, Resp], ...) ports.ToolAdapter[Req, Resp]
- func PipelineHandler[Req, Resp any](handle *rest.RouteHandle[Req, Resp], fn PipelineHandlerFunc[Req, Resp], ...) http.HandlerFunc
- func Register[Req, Resp any](r gochi.Router, handle *rest.RouteHandle[Req, Resp], fn HandlerFunc[Req, Resp], ...)
- func RegisterLatest[Req, Resp any](r gochi.Router, handle *rest.RouteHandle[Req, Resp], src gstream.Stream[Resp], ...)
- func RegisterPipeline[Req, Resp any](r gochi.Router, handle *rest.RouteHandle[Req, Resp], ...)
- func RegisterSSE[Req, Event any](r gochi.Router, 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](r gochi.Router, handle *rest.SSERouteHandle[struct{}, Event], ...) ports.SinkAdapter[Event]
- func SSEHandler[Req, Event any](handle *rest.SSERouteHandle[Req, Event], fn SSEHandlerFunc[Req, Event], ...) http.HandlerFunc
- 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 CookieOptions
- type HandlerFunc
- type IngestAdapterOptions
- type NoLatestValueError
- type Options
- type PendingCookie
- type PipelineAdapterOptions
- type PipelineFullError
- type PipelineHandlerFunc
- type PipelineNoResponseError
- type SSEAdapterOptions
- type SSEHandlerFunc
- type SSEStreamOptions
- type SSEWriteError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Handler ¶
func Handler[Req, Resp any](handle *rest.RouteHandle[Req, Resp], fn HandlerFunc[Req, Resp], opts Options) http.HandlerFunc
Handler wraps a rest.RouteHandle and a HandlerFunc into an http.HandlerFunc suitable for use with a chi.Router.
func HandlerLatest ¶ added in v0.11.0
func HandlerLatest[Req, Resp any]( handle *rest.RouteHandle[Req, Resp], src gstream.Stream[Resp], opts Options, ) http.HandlerFunc
HandlerLatest returns an http.HandlerFunc 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 silently dropped — latest value is unaffected.
Codec coverage — all HTTP layers validated ¶
Handler validates all codec layers before fn fires: body codec, query params, cookie params, header params, path params, and security. Decoded Req and all param values are validated but not used — response is always from src. Invalid requests produce the standard 400; only well-formed requests get the cached value.
func IngestAdapter ¶ added in v0.11.0
func IngestAdapter[T any]( r gochi.Router, handle *rest.RouteHandle[T, struct{}], opts IngestAdapterOptions, ) ports.SourceAdapter[T]
IngestAdapter returns a ports.SourceAdapter that accepts HTTP requests as pipeline items via a chi router. When Activate is called it registers a handler on r and runs until ctx is cancelled. Use with ports.SourcePort.Bind:
domain.SensorReadings.Bind(ctx, chi.IngestAdapter(
r, ingestHandle, chi.IngestAdapterOptions{Buffer: 8}))
func PipelineAdapter ¶ added in v0.11.0
func PipelineAdapter[Req, Resp any]( r gochi.Router, 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 on a chi router. When ports.ToolPort.Bind is called it registers the handler. Use with ports.ToolPort.Bind:
domain.OEEToolPort.Bind(ctx, chi.PipelineAdapter(r, httpHandle, chi.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.HandlerFunc
PipelineHandler wraps a PipelineHandlerFunc into an http.HandlerFunc. All codec validation, param validation, security enforcement, and observer integration follow the same path as plain Handler.
Use PipelineHandler when the handler body benefits from gstream.Tap for declarative intermediate observation, multi-step gstream.Apply, or gstream.MapErr for per-step typed error recovery.
Codec coverage — all HTTP layers ¶
Before fn is called, Handler validates body codec, all param codecs (query, cookie, header, path), and security. After fn returns, Handler validates response body, response header, and response cookie codecs.
To access path/query/cookie/header param VALUES inside the pipeline, call RequestFromContext on the ctx passed to fn (params are already validated):
chi.RegisterPipeline(r, handle,
func(ctx context.Context, body SensorBody) stream.Stream[OEEResult] {
sensorID := gochi.URLParam(chi.MustGetRouteContext(ctx), "sensorID")
s := stream.Single(ctx, body)
return stream.Tap(ctx, s, func(v SensorBody) {
slog.Info("request", "sensor", sensorID)
})
}, opts)
func Register ¶
func Register[Req, Resp any](r gochi.Router, handle *rest.RouteHandle[Req, Resp], fn HandlerFunc[Req, Resp], opts Options)
Register registers the route on r using its method and path from the route descriptor. Chi uses the same {param} placeholder syntax as go-codex path templates, so no translation is needed.
Example ¶
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
gochi "github.com/go-chi/chi/v5"
chiadapter "github.com/DaniDeer/go-codex/adapters/chi"
"github.com/DaniDeer/go-codex/api/rest"
"github.com/DaniDeer/go-codex/codex"
)
func main() {
type CreateReq struct{ Name string }
type Item struct{ ID, Name string }
reqCodec := codex.Struct[CreateReq](
codex.RequiredField("name", codex.String(),
func(r CreateReq) string { return r.Name },
func(r *CreateReq, v string) { r.Name = v },
),
)
itemCodec := codex.Struct[Item](
codex.RequiredField("id", codex.String(),
func(i Item) string { return i.ID },
func(i *Item, v string) { i.ID = v },
),
codex.RequiredField("name", codex.String(),
func(i Item) string { return i.Name },
func(i *Item, v string) { i.Name = v },
),
)
b := rest.NewBuilder(rest.Info{Title: "Example API", Version: "1.0.0"})
handle, err := rest.NewRoute[CreateReq, Item]("POST", "/items",
reqCodec, itemCodec,
rest.RouteMeta{OperationID: "createItem", RespStatus: "201"},
).Register(b)
if err != nil {
fmt.Println("register error:", err)
return
}
r := gochi.NewRouter()
chiadapter.Register(r, handle, func(_ context.Context, req CreateReq) (Item, error) {
return Item{ID: "1", Name: req.Name}, nil
}, chiadapter.Options{})
srv := httptest.NewServer(r)
defer srv.Close()
resp, _ := http.Post(srv.URL+"/items", "application/json",
strings.NewReader(`{"name":"Widget"}`))
defer resp.Body.Close()
fmt.Println(resp.StatusCode)
}
Output: 201
func RegisterLatest ¶ added in v0.11.0
func RegisterLatest[Req, Resp any]( r gochi.Router, handle *rest.RouteHandle[Req, Resp], src gstream.Stream[Resp], opts Options, )
RegisterLatest wires HandlerLatest onto a chi router. Mirrors Register.
func RegisterPipeline ¶ added in v0.11.0
func RegisterPipeline[Req, Resp any]( r gochi.Router, handle *rest.RouteHandle[Req, Resp], fn PipelineHandlerFunc[Req, Resp], opts Options, )
RegisterPipeline wires PipelineHandler onto a chi router. Mirrors Register.
func RegisterSSE ¶
func RegisterSSE[Req, Event any](r gochi.Router, handle *rest.SSERouteHandle[Req, Event], fn SSEHandlerFunc[Req, Event], opts Options)
RegisterSSE wires an rest.SSERouteHandle onto a chi router 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 ¶
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]( r gochi.Router, handle *rest.SSERouteHandle[struct{}, Event], opts SSEAdapterOptions, ) ports.SinkAdapter[Event]
SSEAdapter returns a ports.SinkAdapter that serves each SinkPort item as an SSE event to all connected clients via a chi router. When Activate is called it registers an SSEFromHub-backed handler. Use with ports.SinkPort.Bind:
domain.OEEResults.Bind(ctx, chi.SSEAdapter(r, sseHandle, chi.SSEAdapterOptions{}))
func SSEHandler ¶
func SSEHandler[Req, Event any](handle *rest.SSERouteHandle[Req, Event], fn SSEHandlerFunc[Req, Event], opts Options) http.HandlerFunc
SSEHandler wraps an rest.SSERouteHandle and a user-supplied SSEHandlerFunc into an http.HandlerFunc 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 ¶
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. A validation failure returns rest.CookieParamError without writing any header.
func WithResponseCookies ¶
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 writes Set-Cookie headers on success.
func WithResponseHeaders ¶
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.
Types ¶
type CookieOptions ¶
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.
Insecure bool
// AllowJS, when true, omits the HttpOnly attribute.
AllowJS bool
// Codec, when non-nil, validates value before the Set-Cookie header is written.
// Set via [CookieOptions.WithCodec] to avoid address-of boilerplate.
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 ¶
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 := chiadapter.SetCookie(w, "session_token", token,
chiadapter.CookieOptions{MaxAge: 3600}.WithCodec(sessionCodec))
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 and headers. Chi path params are available via chi.URLParam(r, "name").
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.
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.
// 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.
// Defaults to [stats.NoopObserver] when nil.
Observer stats.Observer
// MaxBodyBytes limits the number of bytes read from the request body.
// Zero means the default (1 MiB).
MaxBodyBytes int64
// ContentType is the expected Content-Type for body-bearing methods.
// Defaults to "application/json".
ContentType string
// MultiValueQueryParams, when true, uses [rest.RouteHandle.ValidateQueryMulti].
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 ¶
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.
func ResponseCookiesFromContext ¶
func ResponseCookiesFromContext(ctx context.Context) ([]PendingCookie, bool)
ResponseCookiesFromContext retrieves pending cookies previously stored by WithResponseCookies.
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.
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.
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 SSEAdapterOptions ¶ added in v0.11.0
type SSEAdapterOptions struct {
Options Options
SSEStreamOptions SSEStreamOptions
}
SSEAdapterOptions configures SSEAdapter.
type SSEHandlerFunc ¶
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. All connecting SSE clients share the same event stream.
Use SSEFromHub for live dashboards broadcasting the same stream to all users.
type SSEStreamOptions ¶ added in v0.11.0
type SSEStreamOptions struct {
// Topic is the SSE route path used for observer reporting and error context.
Topic string
// OnError, when non-nil, is called for write failures ([SSEWriteError]) and
// upstream stream errors.
OnError func(error)
// Observer receives per-event lifecycle events.
// [stats.Observer.RecordSubscribe] fires for each emitted event (success=true)
// or error (success=false). [stats.TraceObserver] spans wrap each send.
Observer stats.Observer
}
SSEStreamOptions configures [SSEFromStream] and SSEFromHub. Mirrors [nethttp.SSEStreamOptions].
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.
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.