Documentation
¶
Overview ¶
Package routing provides a declarative, type-safe HTTP router that generates an OpenAPI 3 specification as routes are registered.
The primary type is Router: a concrete router that owns request decoding, validation, response encoding, error mapping, and OpenAPI accumulation. Routes are declared with typed handlers of the form func(ctx, In) (Out, error) via the package-level generic functions (Get, Post, Put, Patch, Delete, Head). Because Go interface methods cannot be generic, registration is done with these functions rather than methods on the Router.
A Router is backed by a Backend — the pluggable seam that a concrete mux library implements. Implementations ship for chi, the net/http.ServeMux standard library (stdlib), julienschmidt/httprouter, and gin-gonic/gin. The Router builds everything on top of the Backend's primitives and never depends on the library directly, so the underlying router is swappable without touching route code:
backend := chi.NewBackend(logger, tracerProvider, metricProvider, cfg)
r := routing.New(backend, encoder, logger, tracerProvider, routing.WithTitle("My API"))
routing.Post(r, "/orgs/{orgID:uint64}/users", createUser, routing.WithSummary("Create user"))
routing.Get(r, "/orgs/{orgID:uint64}", getOrg)
r.MountOpenAPI("/openapi.json", "/docs")
Path parameters use an inline typed syntax — "/users/{id:uint64}" — which drives both runtime binding and the generated parameter schema. Query, header, cookie, and body values are bound from struct tags on the typed input.
Example ¶
Example demonstrates wiring a Router over the chi backend, registering typed routes, and mounting the generated OpenAPI spec.
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/primandproper/platform-go/v5/encoding"
loggingnoop "github.com/primandproper/platform-go/v5/observability/logging/noop"
metricsnoop "github.com/primandproper/platform-go/v5/observability/metrics/noop"
tracingnoop "github.com/primandproper/platform-go/v5/observability/tracing/noop"
"github.com/primandproper/platform-go/v5/routing"
"github.com/primandproper/platform-go/v5/routing/backends/chi"
)
// The input for creating a user. Tags decide where each field is bound:
// - path: taken from the URL, cross-checked against the {orgID:uint64} token
// - query: taken from the query string
// - json (no location tag): part of the request body
type newUserForm struct {
Name string `json:"name"`
Email string `json:"email"`
OrgID uint64 `path:"orgID"`
Notify bool `query:"notify"`
}
// The typed output. It is encoded into the response (enveloped by default).
type person struct {
Name string `json:"name"`
Email string `json:"email"`
ID uint64 `json:"id"`
}
// A typed handler: func(ctx, In) (Out, error). The framework decodes and
// validates In, calls this, then encodes Out — or maps a returned error to an
// HTTP status and error envelope.
func createPerson(_ context.Context, in newUserForm) (person, error) {
return person{ID: in.OrgID*1000 + 1, Name: in.Name, Email: in.Email}, nil
}
func fetchPerson(_ context.Context, in struct {
OrgID uint64 `path:"orgID"`
ID uint64 `path:"userID"`
}) (person, error) {
return person{ID: in.ID, Name: "Ada"}, nil
}
// Example demonstrates wiring a Router over the chi backend, registering typed
// routes, and mounting the generated OpenAPI spec.
func main() {
logger := loggingnoop.NewLogger()
tracerProvider := tracingnoop.NewTracerProvider()
metricsProvider := metricsnoop.NewMetricsProvider()
// The backend is the swappable seam: chi today, gin/etc. tomorrow. It carries
// the library-specific middleware + OpenTelemetry stack.
backend := chi.NewBackend(logger, tracerProvider, metricsProvider, &chi.Config{
ServiceName: "example-service",
})
// The Router is the declarative, OpenAPI-generating layer on top of it.
enc := encoding.NewServerEncoderDecoder(logger, tracerProvider, encoding.ContentTypeJSON)
r := routing.New(backend, enc, logger, tracerProvider,
routing.WithTitle("Users API"),
routing.WithVersion("1.0.0"),
)
// Typed registration is done with the package-level generic functions.
// Path params use an inline typed syntax: {orgID:uint64}.
routing.Post(r, "/orgs/{orgID:uint64}/users", createPerson,
routing.WithSummary("Create a user"),
routing.WithTags("users"),
)
// Group applies a shared path prefix and default tags.
r.Group("/orgs/{orgID:uint64}", func(sub *routing.Router) {
routing.Get(sub, "/users/{userID:uint64}", fetchPerson, routing.WithSummary("Fetch a user"))
}, "users")
// Serve the generated OpenAPI 3 spec (and a docs UI) on the same router.
r.MountOpenAPI("/openapi.json", "/docs")
// Registration errors (if any) surface here; check before serving.
if err := r.Err(); err != nil {
panic(err)
}
// In a real service you would hand r.Handler() to an http.Server (or the
// platform's server/http package). Here we drive one request in-process.
req := httptest.NewRequest(http.MethodPost, "/orgs/7/users?notify=true",
strings.NewReader(`{"name":"Ada","email":"ada@example.com"}`))
req.Header.Set(encoding.ContentTypeHeaderKey, "application/json")
rec := httptest.NewRecorder()
r.Handler().ServeHTTP(rec, req)
fmt.Println("status:", rec.Code)
fmt.Println("body:", strings.TrimSpace(rec.Body.String()))
}
Output: status: 201 body: {"data":{"name":"Ada","email":"ada@example.com","id":7001},"details":{"currentAccountID":"","traceID":""}}
Index ¶
- type Backend
- type ContextKey
- type Empty
- type Handler
- type Middleware
- type Option
- func WithAdditionalResponse(status int, body any, description string) Option
- func WithContentType(contentType encoding.ContentType) Option
- func WithDeprecated() Option
- func WithDescription(description string) Option
- func WithEnvelope(enabled bool) Option
- func WithMiddleware(middleware ...Middleware) Option
- func WithOperationID(id string) Option
- func WithResponseStatus(status int) Option
- func WithSecurity(scheme string, scopes ...string) Option
- func WithSummary(summary string) Option
- func WithTags(tags ...string) Option
- type ParamSpec
- type Route
- func Delete[In, Out any](r *Router, pattern string, h Handler[In, Out], opts ...Option) *Route
- func Get[In, Out any](r *Router, pattern string, h Handler[In, Out], opts ...Option) *Route
- func Head[In, Out any](r *Router, pattern string, h Handler[In, Out], opts ...Option) *Route
- func Patch[In, Out any](r *Router, pattern string, h Handler[In, Out], opts ...Option) *Route
- func Post[In, Out any](r *Router, pattern string, h Handler[In, Out], opts ...Option) *Route
- func Put[In, Out any](r *Router, pattern string, h Handler[In, Out], opts ...Option) *Route
- type Router
- func (r *Router) Backend() Backend
- func (r *Router) Err() error
- func (r *Router) Group(prefix string, fn func(sub *Router), tags ...string)
- func (r *Router) Handle(method, pattern string, handler http.Handler, middleware ...Middleware)
- func (r *Router) Handler() http.Handler
- func (r *Router) MarshalSpec() ([]byte, error)
- func (r *Router) MountOpenAPI(specPath, uiPath string)
- func (r *Router) Spec() *openapi3.Spec
- func (r *Router) Use(middleware ...Middleware)
- type RouterOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Backend ¶
type Backend interface {
// Handle registers handler for method at pattern. pattern uses the
// "/users/{id}" placeholder syntax (already stripped of any type
// annotation by the Router).
Handle(method, pattern string, handler http.Handler)
// Use installs global middleware, applied to every route.
Use(middleware ...Middleware)
// PathValue returns the value of the named path parameter for req, or ""
// if absent.
PathValue(req *http.Request, name string) string
// Handler returns the composed http.Handler for serving.
Handler() http.Handler
}
Backend is the pluggable HTTP-muxing seam beneath a Router. A concrete router library (chi, gin, ...) implements it; the Router builds every typed route, spec, and lifecycle concern on top of these primitives and never depends on the library directly.
Use must be called before Handle (many muxes, chi included, forbid adding global middleware after routes are registered).
type ContextKey ¶
type ContextKey string
ContextKey represents strings to be used in Context objects. From the docs:
"The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context."
type Empty ¶
type Empty struct{}
Empty is a placeholder type for routes that take no meaningful input or produce no response body. A route whose Out is Empty writes only a status code (no body).
type Handler ¶
Handler is a typed HTTP handler. It receives a decoded, validated input value and returns a typed output or an error. The framework handles decoding In from the request, encoding Out into the response, and mapping a returned error to an HTTP status and error envelope.
type Middleware ¶
Middleware is a standard net/http middleware function.
type Option ¶
type Option func(*routeConfig)
Option customizes a single route's registration and its generated OpenAPI operation.
func WithAdditionalResponse ¶
WithAdditionalResponse documents an additional response (e.g. a 404 with an error body).
func WithContentType ¶
func WithContentType(contentType encoding.ContentType) Option
WithContentType overrides the response content type for this route.
func WithDeprecated ¶
func WithDeprecated() Option
WithDeprecated marks the operation as deprecated.
func WithDescription ¶
WithDescription sets the operation's long description.
func WithEnvelope ¶
WithEnvelope toggles wrapping the response body in errors/http.APIResponse[Out]. Enveloping is on by default (configurable at the Router level).
func WithMiddleware ¶
func WithMiddleware(middleware ...Middleware) Option
WithMiddleware applies middleware to this route only.
func WithOperationID ¶
WithOperationID overrides the generated operation ID.
func WithResponseStatus ¶
WithResponseStatus overrides the success HTTP status (default 200, or 201 for POST).
func WithSecurity ¶
WithSecurity adds a security requirement (scheme name + optional scopes) to the operation.
func WithSummary ¶
WithSummary sets the operation's short summary.
type ParamSpec ¶
ParamSpec is a path parameter parsed out of a typed-path pattern such as "/users/{id:uint64}". Token is the resolved type token ("string" when the pattern omitted an annotation).
type Route ¶
Route is the descriptor returned by a registration call. It records the concrete method and (annotation-stripped) path the route was registered under, plus the resolved OpenAPI operation ID.
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router is the declarative, OpenAPI-generating router. It is the primary type callers use: typed routes are registered with the package-level generic functions (Get, Post, ...), which decode and validate input, encode output, and accumulate an OpenAPI 3 operation. A Router is backed by a Backend, so the underlying mux (chi, gin, ...) is swappable without changing route code.
It is a concrete type, not an interface, because typed registration must be generic and Go does not permit generic interface methods. The swappable seam is Backend; the Router is the one fixed orchestration layer above it.
func New ¶
func New( backend Backend, enc encoding.ServerEncoderDecoder, logger logging.Logger, tracerProvider tracing.TracerProvider, opts ...RouterOption, ) *Router
New builds a Router over a Backend. The backend carries all library-specific middleware and OpenTelemetry wiring; the encoder decides how request bodies are decoded and responses encoded.
func (*Router) Err ¶
Err returns a joined error of all non-fatal registration failures accumulated so far, or nil if there were none. Check it before serving.
func (*Router) Group ¶
Group creates a sub-Router that shares the backend, reflector, and error accumulator, but applies an additional path prefix and default tags to routes registered through it.
func (*Router) Handle ¶
func (r *Router) Handle(method, pattern string, handler http.Handler, middleware ...Middleware)
Handle registers a raw http.Handler on the backend — an escape hatch for routes that do not fit the typed model (static files, streaming, websockets). It records no OpenAPI operation.
func (*Router) Handler ¶
Handler returns the composed http.Handler for serving, delegating to the backend.
func (*Router) MarshalSpec ¶
MarshalSpec renders the accumulated spec as indented JSON.
func (*Router) MountOpenAPI ¶
MountOpenAPI registers two routes on the backend: specPath serves the spec as JSON, and (when uiPath is non-empty) uiPath serves a self-contained docs UI page that renders it. Both routes go through the backend, so they inherit all of its middleware and instrumentation.
Call this after all typed routes are registered so the served spec is complete.
func (*Router) Spec ¶
Spec returns the accumulated OpenAPI 3 specification. It reflects every route registered so far; call it after registration (and MountOpenAPI) is complete.
func (*Router) Use ¶
func (r *Router) Use(middleware ...Middleware)
Use installs global middleware on the backend. Call it before registering routes.
type RouterOption ¶
type RouterOption func(*routerConfig)
RouterOption configures a Router at construction.
func WithDefaultEnvelope ¶
func WithDefaultEnvelope(enabled bool) RouterOption
WithDefaultEnvelope sets whether responses are wrapped in errors/http.APIResponse[Out] by default (per-route override via WithEnvelope).
func WithInfoDescription ¶
func WithInfoDescription(description string) RouterOption
WithInfoDescription sets the OpenAPI document description.
func WithServer ¶
func WithServer(url string) RouterOption
WithServer adds a server URL to the OpenAPI document.
func WithTitle ¶
func WithTitle(title string) RouterOption
WithTitle sets the OpenAPI document title.
func WithVersion ¶
func WithVersion(version string) RouterOption
WithVersion sets the OpenAPI document version.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
backends
|
|
|
gin
Package gin provides a routing.Backend built on gin-gonic/gin.
|
Package gin provides a routing.Backend built on gin-gonic/gin. |
|
httprouter
Package httprouter provides a routing.Backend built on julienschmidt/httprouter, a fast radix-tree router.
|
Package httprouter provides a routing.Backend built on julienschmidt/httprouter, a fast radix-tree router. |
|
internal/httpmw
Package httpmw holds the plain net/http middleware stack shared by the non-chi routing backends (stdlib, httprouter, gin).
|
Package httpmw holds the plain net/http middleware stack shared by the non-chi routing backends (stdlib, httprouter, gin). |
|
stdlib
Package stdlib provides a routing.Backend built on the standard library's net/http.ServeMux.
|
Package stdlib provides a routing.Backend built on the standard library's net/http.ServeMux. |
|
Package mockrouting provides mock implementations of the routing package's interfaces (currently the Backend seam), for testing routers without a real mux library.
|
Package mockrouting provides mock implementations of the routing package's interfaces (currently the Backend seam), for testing routers without a real mux library. |