rest

package
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Jun 12, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package rest provides a transport-agnostic REST API builder for go-codex.

Define routes declaratively with codec-backed request and response types; register them with a Builder to obtain a RouteHandle with typed Decode and Encode helpers. Pass those helpers to any HTTP framework (net/http, Gin, Chi, Echo) — this package does not import net/http or any framework.

Spec generation is also available: Builder.OpenAPISpec derives a complete OpenAPI 3.1 document from the registered routes.

Typical usage:

b := rest.NewBuilder(rest.Info{Title: "User API", Version: "1.0.0"})
b.AddServer("production", rest.Server{URL: "https://api.example.com"})

// Declare the route as a value — define once, pass around, register later.
var createUser = rest.NewRoute[CreateUserReq, User]("POST", "/users/{id}",
    createUserCodec, userCodec,
    rest.RouteMeta{OperationID: "createUser", Summary: "Create a user",
        ReqSchemaName: "CreateUserRequest", RespSchemaName: "User"},
    rest.PathParam{Name: "id"}.WithCodec(uuidCodec),
)

handle, err := createUser.Register(b)
handle.
    WithRequestFormats(format.JSON(createUserCodec), format.YAML(createUserCodec)).
    WithFormats(format.JSON(userCodec))

// In your HTTP handler (any framework):
req, err := handle.Decode(body)      // JSON → CreateUserReq, validates
user, err := myService.CreateUser(req)
out, err  := handle.Encode(user)     // User → JSON

// OpenAPI 3.1 spec:
doc, err := b.OpenAPISpec()
yaml, _  := doc.MarshalYAML()

Encoding is JSON only by default. Use RouteHandle.WithRequestFormats and RouteHandle.WithFormats to enable additional formats such as YAML, TOML, or templ HTML.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrRequiredParam = errors.New("required parameter missing")

ErrRequiredParam is the sentinel wrapped inside a param error when a required parameter is absent from the request. Check with errors.Is:

if errors.Is(err, rest.ErrRequiredParam) {
    http.Error(w, "missing required parameter", http.StatusBadRequest)
}

Functions

This section is empty.

Types

type BodyTooLargeError added in v0.8.0

type BodyTooLargeError struct {
	// Limit is the maximum allowed body size in bytes (from Options.MaxBodyBytes,
	// or the default 1 MiB when not configured).
	Limit int64
}

BodyTooLargeError is returned by the net/http adapter when the request body exceeds [Options.MaxBodyBytes]. Use errors.As to inspect the Limit field.

var sizeErr rest.BodyTooLargeError
if errors.As(err, &sizeErr) {
    log.Printf("body exceeded %d byte limit", sizeErr.Limit)
}

func (BodyTooLargeError) Error added in v0.8.0

func (e BodyTooLargeError) Error() string

type Builder

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

Builder accumulates route registrations and security schemes, and produces OpenAPI 3.1 specifications. It is safe to register routes from multiple goroutines as long as [Builder.Build] is not called concurrently. Create one with NewBuilder.

func NewBuilder

func NewBuilder(info Info, opts ...BuilderOption) *Builder

NewBuilder returns a Builder initialised with the given API metadata.

func (*Builder) AddGlobalSecurity added in v0.8.0

func (b *Builder) AddGlobalSecurity(reqs ...route.SecurityRequirement) *Builder

AddGlobalSecurity appends security requirements that apply to all operations by default. The requirements flow into both:

  • The OpenAPI spec (top-level security field).
  • Runtime enforcement: routes with nil RouteMeta.Security inherit these requirements at the adapter layer via RouteHandle.GlobalSecurity.

To mark a specific route as explicitly unsecured (exempt from global security), set Security to an empty slice in RouteMeta: Security: []route.SecurityRequirement{}.

func (*Builder) AddSchema

func (b *Builder) AddSchema(name string, s schema.Schema) *Builder

AddSchema registers a named schema in components/schemas. Use this to register reusable schemas (e.g. shared error types) that are referenced by SchemaName in route configs but not inlined in any codec.

func (*Builder) AddSecurityScheme added in v0.8.0

func (b *Builder) AddSecurityScheme(name string, s SecurityScheme) *Builder

AddSecurityScheme registers a named security scheme with the builder. The spec fields flow into the OpenAPI document via OpenAPISpec; Codec, when non-nil, is used by adapters to validate extracted credentials before SecurityFunc is called.

The name must match those used in route.Require calls and AddGlobalSecurity.

func (*Builder) AddServer

func (b *Builder) AddServer(name string, s Server) *Builder

AddServer appends a server entry to the OpenAPI spec in registration order. If s.Description is empty, name is used as the description (consistent with [events.Builder.AddServer]). Unlike the AsyncAPI builder, OpenAPI servers are an ordered array with no named keys — name is not stored beyond this point.

func (*Builder) OpenAPISpec

func (b *Builder) OpenAPISpec() (openapi.Document, error)

OpenAPISpec builds a complete OpenAPI 3.1 document from all registered routes. Returns an error if any non-empty SchemaName references a schema that will not be present in components/schemas (a dangling $ref).

type BuilderOption added in v0.4.0

type BuilderOption func(*Builder)

BuilderOption configures a Builder at construction time.

func WithPathCodec added in v0.4.0

func WithPathCodec(c codex.Codec[string]) BuilderOption

WithPathCodec sets a codec used to validate every path passed to Route.Register. If the path is invalid, Route.Register returns an error immediately.

Use WithPathConstraints for the common case of stacking one or more codex.Constraint values; use WithPathCodec when you need a fully-custom codex.Codec.

Example — enforce HTTP path rules:

import "github.com/DaniDeer/go-codex/validate"

b := rest.NewBuilder(info, rest.WithPathConstraints(validate.HTTPPath))

func WithPathConstraints added in v0.4.0

func WithPathConstraints(cons ...codex.Constraint[string]) BuilderOption

WithPathConstraints is a convenience wrapper around WithPathCodec that builds a codec from codex.String refined with the given constraints. Multiple constraints are applied in order; all must pass.

Users can mix built-in constraints from the validate package with their own:

sensorPrefix := codex.Constraint[string]{
    Name:    "sensor-prefix",
    Check:   func(v string) bool { return strings.HasPrefix(v, "/sensors/") },
    Message: func(v string) string { return fmt.Sprintf("path must start with /sensors/, got %q", v) },
}
b := rest.NewBuilder(info, rest.WithPathConstraints(validate.HTTPPath, sensorPrefix))

type CookieParam added in v0.8.0

type CookieParam struct {
	Name        string
	Description string
	Required    bool
	// Codec validates cookie parameter values at [RouteHandle.ValidateCookies] time.
	// When non-nil, the codec's schema is also used in the OpenAPI spec.
	// Nil means no runtime validation; the spec schema will be empty.
	Codec *codex.Codec[string]
}

CookieParam describes an HTTP cookie parameter for a route. It combines spec metadata with optional runtime validation via a codec.

CookieParam implements RouteOpt: pass it directly to NewRoute.

func (CookieParam) WithCodec added in v0.8.0

func (cp CookieParam) WithCodec(c codex.Codec[string]) CookieParam

WithCodec sets the validation codec and returns the updated CookieParam.

type CookieParamError added in v0.8.0

type CookieParamError struct {
	Name  string // cookie parameter name
	Value string // the value that failed validation
	Err   error  // the underlying constraint or codec error
}

CookieParamError is returned by RouteHandle.ValidateCookies when a cookie parameter value fails codec validation.

Use errors.As to extract the failing parameter name and value:

var cookieErr rest.CookieParamError
if errors.As(err, &cookieErr) {
    log.Printf("bad value for cookie %q: %q — %v", cookieErr.Name, cookieErr.Value, cookieErr.Err)
}

func (CookieParamError) Error added in v0.8.0

func (e CookieParamError) Error() string

func (CookieParamError) Unwrap added in v0.8.0

func (e CookieParamError) Unwrap() error

Unwrap allows errors.As and errors.Is to traverse the underlying constraint error.

type HeaderParam added in v0.8.0

type HeaderParam struct {
	Name        string
	Description string
	Required    bool
	// Codec validates request header parameter values at [RouteHandle.ValidateHeaders] time.
	// When non-nil, the codec's schema is also used in the OpenAPI spec.
	// Nil means no runtime validation; the spec schema will be empty.
	Codec *codex.Codec[string]
}

HeaderParam describes an HTTP header parameter for a route. It combines spec metadata with optional runtime validation via a codec.

Note: OpenAPI reserves Accept, Content-Type, and Authorization — do not declare those as HeaderParams; they belong to request body and security scheme definitions respectively.

HeaderParam implements RouteOpt: pass it directly to NewRoute.

func (HeaderParam) WithCodec added in v0.8.0

func (h HeaderParam) WithCodec(c codex.Codec[string]) HeaderParam

WithCodec sets the validation codec and returns the updated HeaderParam.

type HeaderParamError added in v0.8.0

type HeaderParamError struct {
	Name  string // header name
	Value string // the value that failed validation
	Err   error  // the underlying constraint or codec error
}

HeaderParamError is returned by RouteHandle.ValidateHeaders when a header value fails codec validation.

Use errors.As to extract the failing header name and value:

var headerErr rest.HeaderParamError
if errors.As(err, &headerErr) {
    log.Printf("bad value for header %q: %q — %v", headerErr.Name, headerErr.Value, headerErr.Err)
}

func (HeaderParamError) Error added in v0.8.0

func (e HeaderParamError) Error() string

func (HeaderParamError) Unwrap added in v0.8.0

func (e HeaderParamError) Unwrap() error

Unwrap allows errors.As and errors.Is to traverse the underlying constraint error.

type Info

type Info = openapi.Info

Info is an alias for openapi.Info. Using the alias avoids duplicating fields and keeps the two in sync automatically.

type InvalidPathError added in v0.4.0

type InvalidPathError struct {
	Path string // the path that failed validation
	Err  error  // the underlying constraint or codec error
}

InvalidPathError is returned by Route.Register when the path fails builder-level path codec validation.

Use errors.As to extract it and inspect the failing path or the underlying constraint error:

var pathErr rest.InvalidPathError
if errors.As(err, &pathErr) {
    log.Printf("bad path %q: %v", pathErr.Path, pathErr.Err)
}

func (InvalidPathError) Error added in v0.4.0

func (e InvalidPathError) Error() string

func (InvalidPathError) Unwrap added in v0.4.0

func (e InvalidPathError) Unwrap() error

Unwrap allows errors.As and errors.Is to traverse the underlying constraint error.

type InvalidPathParamError added in v0.6.0

type InvalidPathParamError struct {
	Name string // the variable name (without braces) that is not in the template
	Path string // the path template that was validated against
}

InvalidPathParamError is returned by Route.Register when a PathParam entry names a variable that does not appear in the path template.

Use errors.As to extract the offending name and the path template:

var paramErr rest.InvalidPathParamError
if errors.As(err, &paramErr) {
    log.Printf("PathParam %q not in path %q", paramErr.Name, paramErr.Path)
}

func (InvalidPathParamError) Error added in v0.6.0

func (e InvalidPathParamError) Error() string

type MissingPathVarError added in v0.4.0

type MissingPathVarError struct {
	Name string // the variable name (without braces) that had no value
}

MissingPathVarError is returned by RouteHandle.BuildPath when a {varName} placeholder in the path template has no corresponding entry in the vars map.

Use errors.As to extract the missing variable name:

var missingErr rest.MissingPathVarError
if errors.As(err, &missingErr) {
    log.Printf("caller forgot to supply path variable {%s}", missingErr.Name)
}

func (MissingPathVarError) Error added in v0.4.0

func (e MissingPathVarError) Error() string

type NotAcceptableError added in v0.8.0

type NotAcceptableError struct {
	// Accept is the value of the client's Accept header.
	Accept string
	// Supported lists the content types the route can produce.
	Supported []string
}

NotAcceptableError is returned by the net/http adapter when the client's Accept header does not match any of the route's registered response formats. Use errors.As to inspect the Accept and Supported fields.

var naErr rest.NotAcceptableError
if errors.As(err, &naErr) {
    log.Printf("client wants %q; route supports %v", naErr.Accept, naErr.Supported)
}

func (NotAcceptableError) Error added in v0.8.0

func (e NotAcceptableError) Error() string

type PathParam added in v0.6.0

type PathParam struct {
	Name        string
	Description string
	// Codec validates path parameter values at [RouteHandle.ValidatePathParams] and
	// [RouteHandle.BuildPath] time.
	// When non-nil, the codec's schema is also used in the OpenAPI spec.
	// Nil means no runtime validation; the spec schema will be empty.
	Codec *codex.Codec[string]
}

PathParam describes an HTTP path variable for a route (e.g. `{id}` in `/users/{id}`). It combines spec metadata with optional runtime validation via a codec.

Entry names must correspond to {varName} placeholders in the path template; unknown names cause Route.Register to return an error immediately.

PathParam is optional: the builder auto-generates a minimal parameter entry for every {varName} in the path. Only specify PathParam when you need a description or runtime validation for a specific variable.

Note: path parameters are always required by the OpenAPI specification — there is no Required field. For optional key-value parameters use QueryParam with Required: true or false as appropriate.

PathParam implements RouteOpt: pass it directly to NewRoute or NewSSERoute.

func (PathParam) WithCodec added in v0.8.0

func (p PathParam) WithCodec(c codex.Codec[string]) PathParam

WithCodec sets the validation codec and returns the updated PathParam.

type PathParamError added in v0.4.0

type PathParamError struct {
	Name  string // variable name without braces, e.g. "id"
	Value string // the value that failed validation
	Err   error  // the underlying constraint or codec error
}

PathParamError is returned by RouteHandle.BuildPath when a path variable value fails codec validation.

Use errors.As to extract the failing variable name and value:

var paramErr rest.PathParamError
if errors.As(err, &paramErr) {
    log.Printf("bad value for {%s}: %q — %v", paramErr.Name, paramErr.Value, paramErr.Err)
}

func (PathParamError) Error added in v0.4.0

func (e PathParamError) Error() string

func (PathParamError) Unwrap added in v0.4.0

func (e PathParamError) Unwrap() error

Unwrap allows errors.As and errors.Is to traverse the underlying constraint error.

type QueryParam added in v0.7.0

type QueryParam struct {
	Name        string
	Description string
	Required    bool
	// Codec validates query parameter values at [RouteHandle.ValidateQuery] time.
	// When non-nil, the codec's schema is also used in the OpenAPI spec.
	// Nil means no runtime validation; the spec schema will be empty.
	Codec *codex.Codec[string]
}

QueryParam describes a query parameter for a route. It combines spec metadata with optional runtime validation via a codec.

QueryParam implements RouteOpt: pass it directly to NewRoute.

func (QueryParam) WithCodec added in v0.8.0

func (q QueryParam) WithCodec(c codex.Codec[string]) QueryParam

WithCodec sets the validation codec and returns the updated QueryParam.

type QueryParamError added in v0.7.0

type QueryParamError struct {
	Name  string // query parameter name
	Value string // the value that failed validation
	Err   error  // the underlying constraint or codec error
}

QueryParamError is returned by RouteHandle.ValidateQuery when a query parameter value fails codec validation.

Use errors.As to extract the failing parameter name and value:

var paramErr rest.QueryParamError
if errors.As(err, &paramErr) {
    log.Printf("bad value for query param %q: %q — %v", paramErr.Name, paramErr.Value, paramErr.Err)
}

func (QueryParamError) Error added in v0.7.0

func (e QueryParamError) Error() string

func (QueryParamError) Unwrap added in v0.7.0

func (e QueryParamError) Unwrap() error

Unwrap allows errors.As and errors.Is to traverse the underlying constraint error.

type ResponseCookieParam added in v0.8.0

type ResponseCookieParam struct {
	Name        string
	Description string
	Required    bool
	// Codec validates response cookie parameter values at [RouteHandle.ValidateResponseCookies] time.
	// When non-nil, the codec's schema is also used in the OpenAPI spec.
	// Nil means no runtime validation; the spec schema will be empty.
	Codec *codex.Codec[string]
}

ResponseCookieParam describes a Set-Cookie header returned in the primary success response. It combines spec metadata with optional runtime validation via a codec that validates the cookie value string.

The adapter validates response cookie values after the handler returns and before writing the response. A codec violation results in a 500 (server contract violation). The codec schema flows into the OpenAPI response header spec as a "Set-Cookie" string header (OpenAPI 3.1 has no first-class response cookie object).

ResponseCookieParam implements RouteOpt: pass it directly to NewRoute.

func (ResponseCookieParam) WithCodec added in v0.8.0

WithCodec sets the validation codec and returns the updated ResponseCookieParam.

type ResponseCookieParamError added in v0.8.0

type ResponseCookieParamError struct {
	Name  string // cookie name
	Value string // the value that failed validation
	Err   error  // the underlying constraint or codec error
}

ResponseCookieParamError is returned by RouteHandle.ValidateResponseCookies when a response cookie value fails codec validation.

Use errors.As to extract the failing cookie name and value:

var rcErr rest.ResponseCookieParamError
if errors.As(err, &rcErr) {
    log.Printf("bad response cookie %q: %q — %v", rcErr.Name, rcErr.Value, rcErr.Err)
}

func (ResponseCookieParamError) Error added in v0.8.0

func (e ResponseCookieParamError) Error() string

func (ResponseCookieParamError) Unwrap added in v0.8.0

func (e ResponseCookieParamError) Unwrap() error

Unwrap allows errors.As and errors.Is to traverse the underlying constraint error.

type ResponseHeaderParam added in v0.8.0

type ResponseHeaderParam struct {
	Name        string
	Description string
	Required    bool
	// Codec validates response header parameter values at [RouteHandle.ValidateResponseHeaders] time.
	// When non-nil, the codec's schema is also used in the OpenAPI spec.
	// Nil means no runtime validation; the spec schema will be empty.
	Codec *codex.Codec[string]
}

ResponseHeaderParam describes an HTTP header returned in the primary success response. It combines spec metadata with optional runtime validation via a codec.

The adapter validates response headers after the handler returns and before writing the response. A codec violation results in a 500 (server contract violation). The codec schema flows into the OpenAPI response header spec automatically.

ResponseHeaderParam implements RouteOpt: pass it directly to NewRoute.

func (ResponseHeaderParam) WithCodec added in v0.8.0

WithCodec sets the validation codec and returns the updated ResponseHeaderParam.

type ResponseHeaderParamError added in v0.8.0

type ResponseHeaderParamError struct {
	Name  string // header name
	Value string // the value that failed validation
	Err   error  // the underlying constraint or codec error
}

ResponseHeaderParamError is returned by RouteHandle.ValidateResponseHeaders when a response header value fails codec validation.

Use errors.As to extract the failing header name and value:

var rhErr rest.ResponseHeaderParamError
if errors.As(err, &rhErr) {
    log.Printf("bad response header %q: %q — %v", rhErr.Name, rhErr.Value, rhErr.Err)
}

func (ResponseHeaderParamError) Error added in v0.8.0

func (e ResponseHeaderParamError) Error() string

func (ResponseHeaderParamError) Unwrap added in v0.8.0

func (e ResponseHeaderParamError) Unwrap() error

Unwrap allows errors.As and errors.Is to traverse the underlying constraint error.

type ResponseMeta

type ResponseMeta struct {
	Status      string // e.g. "400", "404", "default"
	Description string
	Schema      *schema.Schema // nil for description-only responses (e.g. 404)
	SchemaName  string         // non-empty → $ref in spec
}

ResponseMeta describes one additional response entry for a route (errors, redirects, etc.). The primary success response is derived from the response codec and RespStatus/RespDescription/RespSchemaName in RouteMeta.

ResponseMeta implements RouteOpt: pass it directly to NewRoute.

type Route added in v0.8.0

type Route[Req, Resp any] struct {
	// contains filtered or unexported fields
}

Route is a declarative HTTP route spec: method, path, codecs, and options. It is a value type — define it once, store it, pass it around, and register it with one or more Builder instances via Route.Register.

Create a Route with NewRoute.

func NewRoute added in v0.8.0

func NewRoute[Req, Resp any](
	method, path string,
	reqCodec codex.Codec[Req],
	respCodec codex.Codec[Resp],
	opts ...RouteOpt,
) Route[Req, Resp]

NewRoute creates a Route spec from method, path, codecs, and variadic opts. NewRoute is infallible — it only captures the spec. Validation (path codec, PathParam template consistency) runs at Route.Register time.

Pass any combination of RouteMeta, PathParam, QueryParam, CookieParam, HeaderParam, ResponseHeaderParam, ResponseCookieParam, and ResponseMeta as opts. All opts are optional.

NewRoute is a free function (not a method) because Go requires type parameters to appear on free functions, not on method receivers.

Typical usage:

var createUser = rest.NewRoute[CreateUserReq, User]("POST", "/users",
    createUserCodec, userCodec,
    rest.RouteMeta{OperationID: "createUser", Summary: "Create a user"},
)

// Later, register with a builder:
handle, err := createUser.Register(b)
Example
package main

import (
	"fmt"

	"github.com/DaniDeer/go-codex/api/rest"
	"github.com/DaniDeer/go-codex/codex"
)

func main() {
	type CreateUserReq struct{ Name string }
	type User struct{ ID, Name string }

	reqCodec := codex.Struct[CreateUserReq](
		codex.RequiredField("name", codex.String(),
			func(r CreateUserReq) string { return r.Name },
			func(r *CreateUserReq, v string) { r.Name = v },
		),
	)
	userCodec := codex.Struct[User](
		codex.RequiredField("id", codex.String(),
			func(u User) string { return u.ID },
			func(u *User, v string) { u.ID = v },
		),
		codex.RequiredField("name", codex.String(),
			func(u User) string { return u.Name },
			func(u *User, v string) { u.Name = v },
		),
	)

	b := rest.NewBuilder(rest.Info{Title: "User API", Version: "1.0.0"})

	// NewRoute declares a typed route as a value — no builder coupling.
	route := rest.NewRoute[CreateUserReq, User]("POST", "/users",
		reqCodec, userCodec,
		rest.RouteMeta{OperationID: "createUser", Summary: "Create a user"},
	)

	handle, err := route.Register(b)
	if err != nil {
		fmt.Println("register error:", err)
		return
	}

	// Decode a request body.
	req, err := handle.Decode([]byte(`{"name":"Alice"}`))
	if err != nil {
		fmt.Println("decode error:", err)
		return
	}
	fmt.Println(req.Name)
}
Output:
Alice

func (Route[Req, Resp]) ClientHandle added in v0.10.0

func (r Route[Req, Resp]) ClientHandle() *RouteHandle[Req, Resp]

ClientHandle returns a RouteHandle for client-side use without registering with a Builder. No path codec validation and no spec registration occur.

Use ClientHandle when only the client side needs codec and route definitions (no OpenAPI spec, no server), or when sharing a Route definition between server and client in the same binary.

The returned handle has the same Decode / Encode / EncodeRequest / DecodeResponse codec helpers and the same parameter validation methods as a handle returned by Route.Register.

Example — client-only usage:

var getUser = rest.NewRoute[GetUserReq, User]("GET", "/users/{id}",
    getUserReqCodec, userCodec,
    rest.PathParam{Name: "id"}.WithCodec(uuidCodec),
)

handle := getUser.ClientHandle()
user, err := nethttp.Call(ctx, http.DefaultClient, "https://api.example.com",
    handle, GetUserReq{}, map[string]string{"id": userID}, nethttp.CallOptions{})

func (Route[Req, Resp]) Register added in v0.8.0

func (r Route[Req, Resp]) Register(b *Builder) (*RouteHandle[Req, Resp], error)

Register registers the route with b and returns a RouteHandle.

If the builder was created with WithPathCodec or WithPathConstraints, the path is validated immediately and an error is returned if it fails — no route is registered in that case.

Any PathParam entry whose name does not appear as a {varName} placeholder in the path template causes Register to return an error immediately.

Use RouteHandle.WithRequestFormats and RouteHandle.WithFormats after Register to configure multi-format request/response handling.

type RouteHandle

type RouteHandle[Req, Resp any] struct {
	// Descriptor is the live route.Route descriptor. It is updated in place
	// by [WithRequestFormats] and [WithFormats] so that spec generation
	// always reflects the latest configuration.
	Descriptor route.Route

	// Decode deserialises and validates a JSON request body into Req.
	// All Refine constraints on the request codec run automatically.
	Decode func(body []byte) (Req, error)

	// Encode serialises Resp to JSON bytes.
	Encode func(resp Resp) ([]byte, error)

	// EncodeRequest serialises Req to JSON bytes for use as an outgoing HTTP
	// request body. It is the complement of Decode and is used by client-side
	// adapters (e.g. nethttp.Call) to encode the typed request before sending.
	EncodeRequest func(req Req) ([]byte, error)

	// DecodeResponse deserialises and validates a JSON response body into Resp.
	// It is the complement of Encode and is used by client-side adapters to
	// decode the server response into a typed value.
	DecodeResponse func(body []byte) (Resp, error)

	// RequestFormats, when non-empty, lists the formats the route accepts for
	// request body decoding. The adapter uses this slice for content negotiation:
	// it picks the format matching the client's Content-Type header and decodes
	// the request body with it. When empty, the adapter falls back to JSON (via
	// Decode) and enforces opts.ContentType.
	RequestFormats []format.Format[Req]

	// Formats, when non-empty, lists the formats the route can produce.
	// The adapter uses this slice for content negotiation: it picks the format
	// matching the client's Accept header and encodes the response with it.
	// When empty, the adapter falls back to JSON (via Encode).
	Formats []format.Format[Resp]

	// SecuritySchemes maps scheme name to SecurityScheme (with runtime Codec).
	// Populated from Builder.AddSecurityScheme when Register is called.
	// Adapters use this map to extract and validate credentials per scheme.
	SecuritySchemes map[string]SecurityScheme

	// GlobalSecurity holds the builder-level security requirements that apply
	// when Descriptor.Security is nil (i.e. the route inherits global security).
	// Adapters resolve the effective requirements as:
	//   reqs := handle.Descriptor.Security
	//   if reqs == nil { reqs = handle.GlobalSecurity }
	// Set via [Builder.AddGlobalSecurity]. nil when no global security is declared.
	GlobalSecurity []route.SecurityRequirement
	// contains filtered or unexported fields
}

RouteHandle is returned by Route.Register. It holds the spec descriptor and codec-backed Decode/Encode helpers.

Decode and Encode use JSON encoding. For body-less methods (GET, HEAD, DELETE), Decode can still be called if the request carries a body, but typical REST usage will not call it.

func (*RouteHandle[Req, Resp]) BuildPath added in v0.4.0

func (h *RouteHandle[Req, Resp]) BuildPath(vars map[string]string) (string, error)

BuildPath substitutes {varName} placeholders in the route's path template with the values provided in vars, validating each against its registered codec (if any).

All template variables must be present in vars; missing variables return an error. Values are validated before substitution; codec failures return a PathParamError that identifies the variable name and the failing value. Keys in vars that do not appear in the template are silently ignored.

If the builder was created with WithPathCodec or WithPathConstraints, the final assembled path is also validated against that codec. A failure returns an InvalidPathError with the concrete path (not the template).

Example:

path, err := getUserRoute.BuildPath(map[string]string{"id": "f47ac10b-..."})
// path = "/users/f47ac10b-..."

func (*RouteHandle[Req, Resp]) PathParamNames added in v0.8.0

func (h *RouteHandle[Req, Resp]) PathParamNames() []string

PathParamNames returns the names of all registered path parameters. Adapters use this to build the map required by RouteHandle.ValidatePathParams.

func (*RouteHandle[Req, Resp]) ValidateCookies added in v0.8.0

func (h *RouteHandle[Req, Resp]) ValidateCookies(params map[string]string) error

ValidateCookies validates cookie parameter values against their registered codecs.

For each CookieParam that has a non-nil Codec, the corresponding value in params is validated. Returns a CookieParamError on the first failure. When CookieParam.Required is true and the key is absent, ValidateCookies returns a CookieParamError wrapping ErrRequiredParam. Extra keys are ignored.

Example:

cookies := map[string]string{"session_token": r.Cookie("session_token").Value}
if err := myRoute.ValidateCookies(cookies); err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
}

func (*RouteHandle[Req, Resp]) ValidateHeaders added in v0.8.0

func (h *RouteHandle[Req, Resp]) ValidateHeaders(params map[string]string) error

ValidateHeaders validates HTTP header values against their registered codecs.

For each HeaderParam that has a non-nil Codec, the corresponding value in params is validated. Returns a HeaderParamError on the first failure. When HeaderParam.Required is true and the key is absent, ValidateHeaders returns a HeaderParamError wrapping ErrRequiredParam. Extra keys are ignored.

Example:

headers := map[string]string{"X-Request-ID": r.Header.Get("X-Request-ID")}
if err := myRoute.ValidateHeaders(headers); err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
}

func (*RouteHandle[Req, Resp]) ValidatePathParams added in v0.8.0

func (h *RouteHandle[Req, Resp]) ValidatePathParams(vars map[string]string) error

ValidatePathParams validates path variable values against their registered codecs. For each PathParam that has a non-nil Codec, the corresponding value in vars is validated. Returns a PathParamError on the first failure. Extra keys in vars are silently ignored.

Adapters build the vars map using RouteHandle.PathParamNames and the path-variable extraction provided by their router (e.g. r.PathValue).

func (*RouteHandle[Req, Resp]) ValidateQuery added in v0.7.0

func (h *RouteHandle[Req, Resp]) ValidateQuery(params map[string]string) error

ValidateQuery validates query parameter values against their registered codecs.

For each QueryParam that has a non-nil Codec, the corresponding value in params is validated. Returns a QueryParamError on the first failure. When QueryParam.Required is true and the key is absent, ValidateQuery returns a QueryParamError wrapping ErrRequiredParam. Extra keys are ignored.

Example:

errs := listRoute.ValidateQuery(map[string]string{
    "page": r.URL.Query().Get("page"),
    "limit": r.URL.Query().Get("limit"),
})

func (*RouteHandle[Req, Resp]) ValidateQueryMulti added in v0.8.0

func (h *RouteHandle[Req, Resp]) ValidateQueryMulti(params map[string][]string) error

ValidateQueryMulti validates query parameter values against their registered codecs using a multi-value map (such as [url.Values] returned by r.URL.Query()).

For each QueryParam that has a non-nil Codec, the first value in the slice for that key is validated. This mirrors the behaviour of [ValidateQuery] but accepts the raw map[string][]string directly — useful when handling repeated query keys such as "?tags=a&tags=b".

Returns a QueryParamError on the first failure. When QueryParam.Required is true and the key is absent or empty, ValidateQueryMulti returns a QueryParamError wrapping ErrRequiredParam. Extra keys are silently ignored.

func (*RouteHandle[Req, Resp]) ValidateResponseCookies added in v0.8.0

func (rh *RouteHandle[Req, Resp]) ValidateResponseCookies(cookies map[string]string) error

ValidateResponseCookies validates the cookie values collected by the handler against their registered ResponseCookieParam codecs. Returns a ResponseCookieParamError on the first failure. When ResponseCookieParam.Required is true and the key is absent, ValidateResponseCookies returns a ResponseCookieParamError wrapping ErrRequiredParam.

The net/http adapter calls this automatically after the handler returns and before writing the response. A failure indicates a server-side contract violation and results in a 500 response.

func (*RouteHandle[Req, Resp]) ValidateResponseHeaders added in v0.8.0

func (rh *RouteHandle[Req, Resp]) ValidateResponseHeaders(headers map[string]string) error

ValidateResponseHeaders validates HTTP response header values against their registered codecs.

For each ResponseHeaderParam that has a non-nil Codec, the corresponding value in headers is validated. Returns a ResponseHeaderParamError on the first failure. When ResponseHeaderParam.Required is true and the key is absent, ValidateResponseHeaders returns a ResponseHeaderParamError wrapping ErrRequiredParam.

The net/http adapter calls this automatically after the handler returns and before writing the response. A failure indicates a server-side contract violation and results in a 500 response.

func (*RouteHandle[Req, Resp]) WithFormats added in v0.8.0

func (h *RouteHandle[Req, Resp]) WithFormats(fmts ...format.Format[Resp]) *RouteHandle[Req, Resp]

WithFormats registers the formats the route can produce for content negotiation. The adapter picks the format matching the client's Accept header; a mismatch returns HTTP 406 NotAcceptableError.

When empty, the adapter defaults to JSON (via Encode). The first format is used when the client sends Accept: */*.

Calling WithFormats also updates the OpenAPI spec: the primary response will list all registered content types.

Example:

route.WithFormats(
    adapttempl.Format(propsCodec, pageComponent),  // Accept: text/html
    format.JSON(respCodec),                         // Accept: application/json
    format.YAML(respCodec),                         // Accept: application/yaml
)

func (*RouteHandle[Req, Resp]) WithRequestFormats added in v0.8.0

func (h *RouteHandle[Req, Resp]) WithRequestFormats(fmts ...format.Format[Req]) *RouteHandle[Req, Resp]

WithRequestFormats registers the formats the route accepts for request body decoding. The adapter performs content negotiation using the client's Content-Type header; a mismatch returns HTTP 415 UnsupportedMediaTypeError.

Calling WithRequestFormats also updates the OpenAPI spec: the request body will list all accepted content types.

Example:

route, err := rest.NewRoute[CreateItemReq, Item]("POST", "/items",
    reqCodec, respCodec,
    rest.RouteMeta{OperationID: "createItem"}).Register(b)
route.WithRequestFormats(
    format.JSON(reqCodec),  // Content-Type: application/json
    format.YAML(reqCodec),  // Content-Type: application/yaml
)

type RouteMeta added in v0.8.0

type RouteMeta struct {
	OperationID string
	Summary     string
	Description string
	Tags        []string

	// ReqSchemaName, when non-empty, emits a $ref for the request body schema
	// in the spec and registers the schema under that name in components/schemas.
	// Has no effect on SSE routes (GET — no request body).
	ReqSchemaName string

	// RespStatus is the HTTP status code for the primary success response.
	// Defaults to "201" for POST, "200" for all other methods.
	RespStatus string

	// RespDescription is the description for the primary success response.
	RespDescription string

	// RespSchemaName, when non-empty, emits a $ref for the response schema.
	RespSchemaName string

	// Security, when non-nil, overrides global security for this operation.
	// Pass an empty slice to declare "no auth required" for this route.
	// nil (default) inherits global security declared via Builder.AddGlobalSecurity.
	Security []route.SecurityRequirement
}

RouteMeta holds documentation and response metadata for a route registration. It controls spec output (OpenAPI operation fields and the primary success response). Pass it as a variadic option to NewRoute or NewSSERoute.

RouteMeta implements RouteOpt.

type RouteOpt added in v0.8.0

type RouteOpt interface {
	// contains filtered or unexported methods
}

RouteOpt is the sealed interface for variadic NewRoute and NewSSERoute options.

The following types implement RouteOpt:

  • RouteMeta — operation metadata (ID, summary, description, schema names, response status)
  • PathParam — path template variable with optional codec and description
  • QueryParam — query parameter with optional codec, description, and required flag
  • CookieParam — cookie parameter with optional codec, description, and required flag
  • HeaderParam — request header with optional codec, description, and required flag
  • ResponseHeaderParam — response header with optional codec for server-side validation
  • ResponseCookieParam — response Set-Cookie with optional codec for server-side validation
  • ResponseMeta — additional response entries (error codes, redirects, etc.)

type SSERoute added in v0.8.0

type SSERoute[Req, Event any] struct {
	// contains filtered or unexported fields
}

SSERoute is a declarative Server-Sent Events route spec: path, codecs, and options. It is a value type — define it once and register it with SSERoute.Register.

Create an SSERoute with NewSSERoute.

func NewSSERoute added in v0.8.0

func NewSSERoute[Req, Event any](
	path string,
	reqCodec codex.Codec[Req],
	eventCodec codex.Codec[Event],
	opts ...RouteOpt,
) SSERoute[Req, Event]

NewSSERoute creates an SSERoute spec from path, codecs, and variadic opts. NewSSERoute is infallible — it only captures the spec. Validation runs at SSERoute.Register time.

The route is always GET and appears in the OpenAPI spec with Content-Type text/event-stream.

NewSSERoute is a free function (not a method) because Go requires type parameters to appear on free functions, not on method receivers.

Typical usage:

var notifRoute = rest.NewSSERoute[struct{}, Notification]("/notifications",
    emptyCodec, notifCodec,
    rest.RouteMeta{OperationID: "streamNotifications"},
)

handle, err := notifRoute.Register(b)

func (SSERoute[Req, Event]) Register added in v0.8.0

func (s SSERoute[Req, Event]) Register(b *Builder) (*SSERouteHandle[Req, Event], error)

Register registers the SSE route with b and returns an SSERouteHandle.

Path validation follows the same rules as Route.Register. Use SSERouteHandle.WithFormats after Register to configure non-JSON event serialisation formats.

type SSERouteHandle added in v0.8.0

type SSERouteHandle[Req, Event any] struct {
	// Descriptor is the live route.Route descriptor.
	Descriptor route.Route

	// Decode deserialises and validates a JSON request body into Req.
	// For SSE (GET) routes, this is rarely called — read path and query
	// parameter values from your HTTP framework's request context instead.
	Decode func(body []byte) (Req, error)

	// EncodeEvent serialises one event value to JSON bytes.
	// Used as the fallback encoder when Formats is empty.
	EncodeEvent func(e Event) ([]byte, error)

	// ValidateEvent runs the event codec constraints on e without serialising.
	// The adapter calls this inside the send func before encoding.
	ValidateEvent func(e Event) error

	// Formats, when non-empty, lists the formats available for encoding
	// event data. The adapter picks the first format (or the JSON fallback).
	Formats []format.Format[Event]

	// SecuritySchemes maps scheme name to SecurityScheme (with runtime Codec).
	// Populated from Builder.AddSecurityScheme when Register is called.
	// Adapters use this map to extract and validate credentials per scheme.
	SecuritySchemes map[string]SecurityScheme

	// GlobalSecurity holds the builder-level security requirements that apply
	// when Descriptor.Security is nil (i.e. the route inherits global security).
	// Adapters resolve the effective requirements as:
	//   reqs := handle.Descriptor.Security
	//   if reqs == nil { reqs = handle.GlobalSecurity }
	// Set via [Builder.AddGlobalSecurity]. nil when no global security is declared.
	GlobalSecurity []route.SecurityRequirement
	// contains filtered or unexported fields
}

SSERouteHandle is returned by SSERoute.Register. It holds the route descriptor and typed helpers for decoding requests and encoding SSE events.

The adapter uses EncodeEvent to serialise each event to JSON and ValidateEvent to reject invalid values before they are written to the client. When Formats is non-empty the adapter may use an explicit format for event data serialisation (e.g. JSON or YAML inside the data field).

func (*SSERouteHandle[Req, Event]) BuildPath added in v0.8.0

func (h *SSERouteHandle[Req, Event]) BuildPath(vars map[string]string) (string, error)

BuildPath substitutes {varName} placeholders in the route's path template with the values provided in vars, validating each against its registered codec (if any). Follows the same contract as RouteHandle.BuildPath.

func (*SSERouteHandle[Req, Event]) PathParamNames added in v0.8.0

func (h *SSERouteHandle[Req, Event]) PathParamNames() []string

PathParamNames returns the names of all registered path parameters. Adapters use this to build the map required by SSERouteHandle.ValidatePathParams.

func (*SSERouteHandle[Req, Event]) ValidateCookies added in v0.8.0

func (h *SSERouteHandle[Req, Event]) ValidateCookies(params map[string]string) error

ValidateCookies validates cookie parameter values against their registered codecs. Mirrors RouteHandle.ValidateCookies for SSE routes.

func (*SSERouteHandle[Req, Event]) ValidateHeaders added in v0.8.0

func (h *SSERouteHandle[Req, Event]) ValidateHeaders(params map[string]string) error

ValidateHeaders validates HTTP header values against their registered codecs. Mirrors RouteHandle.ValidateHeaders for SSE routes.

func (*SSERouteHandle[Req, Event]) ValidatePathParams added in v0.8.0

func (h *SSERouteHandle[Req, Event]) ValidatePathParams(vars map[string]string) error

ValidatePathParams validates path variable values against their registered codecs. Mirrors RouteHandle.ValidatePathParams for SSE routes.

func (*SSERouteHandle[Req, Event]) ValidateQuery added in v0.8.0

func (h *SSERouteHandle[Req, Event]) ValidateQuery(params map[string]string) error

ValidateQuery validates query parameter values against their registered codecs. Mirrors RouteHandle.ValidateQuery for SSE routes.

func (*SSERouteHandle[Req, Event]) ValidateQueryMulti added in v0.8.0

func (h *SSERouteHandle[Req, Event]) ValidateQueryMulti(params map[string][]string) error

ValidateQueryMulti validates query parameter values using a multi-value map. Mirrors RouteHandle.ValidateQueryMulti for SSE routes.

func (*SSERouteHandle[Req, Event]) ValidateResponseCookies added in v0.8.0

func (h *SSERouteHandle[Req, Event]) ValidateResponseCookies(cookies map[string]string) error

ValidateResponseCookies validates response cookie values against their registered codecs. Only entries with a non-nil Codec are validated. Returns a ResponseCookieParamError on the first invalid value. When ResponseCookieParam.Required is true and the key is absent, returns a ResponseCookieParamError wrapping ErrRequiredParam.

func (*SSERouteHandle[Req, Event]) ValidateResponseHeaders added in v0.8.0

func (h *SSERouteHandle[Req, Event]) ValidateResponseHeaders(headers map[string]string) error

ValidateResponseHeaders validates HTTP response header values against their registered codecs. Only entries with a non-nil Codec are validated. Returns a ResponseHeaderParamError on the first invalid value. When ResponseHeaderParam.Required is true and the key is absent, returns a ResponseHeaderParamError wrapping ErrRequiredParam.

func (*SSERouteHandle[Req, Event]) WithFormats added in v0.8.0

func (h *SSERouteHandle[Req, Event]) WithFormats(fmts ...format.Format[Event]) *SSERouteHandle[Req, Event]

WithFormats registers the formats available for encoding SSE event data. The adapter uses the first format; when empty, events are encoded as JSON.

This mirrors RouteHandle.WithFormats for SSE routes and [ChannelHandle.WithFormats] for event channels. Call it after NewSSERoute to configure non-JSON event serialisation:

notifRoute = notifRoute.WithFormats(
    adapttempl.Format(notifCodec, notifFragment), // HTML fragments over SSE
)

type SecurityCredentialError added in v0.8.0

type SecurityCredentialError struct {
	Scheme string // security scheme name
	Err    error  // codec constraint error
}

SecurityCredentialError is returned when credential format validation via SecurityScheme.Codec fails. It is distinct from SecurityError, which wraps rejections from SecurityFunc.

Use errors.As to extract the scheme name and underlying constraint error:

var credErr rest.SecurityCredentialError
if errors.As(err, &credErr) {
    log.Printf("security scheme %q: invalid credential: %v", credErr.Scheme, credErr.Err)
}

func (SecurityCredentialError) Error added in v0.8.0

func (e SecurityCredentialError) Error() string

func (SecurityCredentialError) Unwrap added in v0.8.0

func (e SecurityCredentialError) Unwrap() error

Unwrap allows errors.As and errors.Is to traverse the underlying constraint error.

type SecurityError added in v0.8.0

type SecurityError struct {
	Err error
}

SecurityError is returned when SecurityFunc rejects a request. It is distinct from SecurityCredentialError, which covers codec format failures.

Use errors.As to extract the underlying error from SecurityFunc:

var secErr rest.SecurityError
if errors.As(err, &secErr) {
    log.Printf("security check failed: %v", secErr.Err)
}

func (SecurityError) Error added in v0.8.0

func (e SecurityError) Error() string

func (SecurityError) Unwrap added in v0.8.0

func (e SecurityError) Unwrap() error

Unwrap allows errors.As and errors.Is to traverse the underlying SecurityFunc error.

type SecurityScheme added in v0.8.0

type SecurityScheme struct {
	route.SecurityScheme
	// Codec, when non-nil, validates the extracted raw credential string.
	// Nil means no format validation; SecurityFunc receives the request as-is.
	//
	// Use [SecurityScheme.WithCodec] to set this field inline without a temporary
	// variable: rest.SecurityScheme{SecurityScheme: route.BearerScheme("JWT")}.WithCodec(c)
	Codec *codex.Codec[string]
}

SecurityScheme combines route.SecurityScheme spec metadata with optional runtime credential extraction and format validation.

AddSecurityScheme registers a SecurityScheme with the builder. The spec fields flow into the OpenAPI document; Codec, when non-nil, is used by adapters to validate the raw credential string before SecurityFunc is called.

The adapter extracts the raw credential from the request based on the scheme Type and location fields:

  • http bearer / openIdConnect / oauth2: strips "Bearer " from the Authorization header
  • http basic: strips "Basic " from the Authorization header
  • apiKey: reads from the header / query / cookie named Name according to In

A codec validation failure causes the adapter to return a SecurityCredentialError with HTTP 401, without invoking SecurityFunc.

func (SecurityScheme) WithCodec added in v0.8.0

WithCodec returns a copy of s with Codec set to c. It avoids the temporary-variable + address-of pattern required when setting Codec inline:

b.AddSecurityScheme("bearer", rest.SecurityScheme{
    SecurityScheme: route.BearerScheme("JWT"),
}.WithCodec(codex.String().Refine(validate.BearerToken)))

type Server

type Server = openapi.Server

Server is an alias for openapi.Server.

type UnsupportedMediaTypeError added in v0.8.0

type UnsupportedMediaTypeError struct {
	// Got is the actual Content-Type value sent by the client (without parameters).
	Got string
	// Supported lists the media types the adapter accepts. Contains one entry
	// for routes with a fixed content type; multiple entries when
	// [RouteHandle.WithRequestFormats] is used.
	Supported []string
}

UnsupportedMediaTypeError is returned by the net/http adapter when the request Content-Type does not match any accepted media type. Use errors.As to inspect the Got and Supported fields.

var ctErr rest.UnsupportedMediaTypeError
if errors.As(err, &ctErr) {
    log.Printf("got %q, supported: %v", ctErr.Got, ctErr.Supported)
}

func (UnsupportedMediaTypeError) Error added in v0.8.0

Jump to

Keyboard shortcuts

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