router

package
v0.1.136 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package router is the Līdza control plane's HTTP router: the API routes an application registers, plus the built-in ones every app serves.

Index

Constants

View Source
const APIPrefix = "/api/"

APIPrefix is the path prefix reserved for the Go control plane. Everything under it is handled by the Router; everything else belongs to the frontend.

View Source
const MaxBodyBytes = 1 << 20

MaxBodyBytes is the largest JSON request body a typed handler accepts.

View Source
const MaxUploadBytes = 32 << 20

MaxUploadBytes is the largest body an upload route (In = File) accepts unless UploadLimit sets another.

Variables

This section is empty.

Functions

func Builtins

func Builtins() []string

Builtins lists the patterns of the routes every app serves.

func Error

func Error(w http.ResponseWriter, status int, msg string)

Error writes a JSON error body: {"error": msg}.

func ErrorCode

func ErrorCode(status int, code, format string, a ...any) error

ErrorCode is Errorf with a code the client can switch on: the reply is {"error": message, "code": code}.

func Errorf

func Errorf(status int, format string, a ...any) error

Errorf builds an HTTPError.

func JSON

func JSON(w http.ResponseWriter, status int, v any)

JSON writes v as a JSON response with the given status code.

func NotFound

func NotFound(what string) error

NotFound is a 404 with the given message.

func Route

func Route[In, Out any](r *Router, pattern string, h Handler[In, Out], mw ...middleware.Middleware)

Route registers a typed handler at a ServeMux pattern. The request body is decoded from JSON into In (up to MaxBodyBytes) for methods that carry one; when In implements validate.Validator the body is validated and a failure replies 422 with the field errors. Out is encoded as JSON with status 200 (or the status set on the request); None replies 204. Errors: *HTTPError sends its status, *validate.Errors sends 422, anything else sends 500 and is logged, its text never reaching the client.

With In = File the route is an upload: the body is the file itself, not JSON, handed to the handler unread (see File).

func Stream

func Stream[In, Event any](r *Router, pattern string, h StreamHandler[In, Event], mw ...middleware.Middleware)

Stream registers a streaming handler at a ServeMux pattern. The body is decoded and validated as Route does it. The reply is server-sent events (text/event-stream): each send is one event whose data is the Event as JSON, and the stream closes with an "end" event once the handler returns nil. The generated clients read it as an async iterable (TS) or a Stream (Dart):

router.Stream(r, "POST /api/v1/summaries", summarize)

func summarize(ctx context.Context, req *router.Request[schema.SummaryInput], send func(string) error) error {
	_, err := llm.From(ctx).Stream(ctx, llm.Request{
		Messages: []llm.Message{{Role: llm.User, Content: req.Body.Prompt}},
	}, send)
	return err
}

An error the handler returns before its first send replies as Route does, with the status and a JSON body. After it the headers are gone: the error is sent as an "error" event, its data the same body plus "status", and the stream closes. send fails once the client has gone or the request's deadline (App.StreamTimeout for a client that accepts text/event-stream) has passed; return then. send may be called from several goroutines, not after the handler returns.

func UploadLimit

func UploadLimit(n int64) middleware.Middleware

UploadLimit sets the largest body, in bytes, the upload routes it wraps accept; MaxUploadBytes otherwise.

func WriteError

func WriteError(w http.ResponseWriter, req *http.Request, err error)

WriteError answers a raw handler's error the way typed handlers do: an HTTPError with its status and message, validation errors as a 422 with fields, anything else as a 500 whose text stays on the server.

Types

type File

type File struct {
	// Body reads the uploaded bytes, bounded by the route's limit.
	Body io.Reader
	// ContentType is the request's Content-Type, application/octet-stream
	// when it has none.
	ContentType string
	// Name is the file name the client sent in Content-Disposition (the
	// generated client sends a File's name); empty otherwise. It is the
	// client's word: never use it as a path or a storage key.
	Name string
	// Size is the Content-Length, or -1 when the client did not send one.
	Size int64
}

File is the In type of an upload route: the request body is the file itself, sent raw (not JSON, not multipart form data). The generated client takes a File or Blob for it, with the path parameters:

router.Route(r, "PUT /api/v1/posts/{id}/image", uploadImage, router.UploadLimit(10<<20))

func uploadImage(ctx context.Context, req *router.Request[router.File]) (storage.Object, error) {
	return storage.From(ctx).Put(ctx, "posts/"+req.Param("id")+"/image", req.Body.Body,
		storage.PutOptions{ContentType: req.Body.ContentType})
}

await api.uploadImage({ id }, file, { onProgress: (sent, total) => ... })

A body over the limit replies 413: at once when Content-Length says so, or when the handler returns the read error.

type HTTPError

type HTTPError struct {
	Status  int
	Message string
	// Code, when set, names the error for programs ("wrong_password"),
	// so a client tells apart two errors with one status without reading
	// the message. It is sent as "code" beside "error".
	Code string
}

HTTPError is an error with a status code. Route sends {"error": Message} with that status; the message is what the client sees.

func (*HTTPError) Error

func (e *HTTPError) Error() string

type Handler

type Handler[In, Out any] func(ctx context.Context, req *Request[In]) (Out, error)

Handler is a typed handler: JSON in, JSON out, errors mapped to status codes by Route.

type Health

type Health struct {
	Status  string    `json:"status"`
	Version string    `json:"version"`
	Time    time.Time `json:"time"`
}

Health is the response body of GET /api/v1/health.

type Mount

type Mount struct {
	Prefix  string
	Handler http.Handler
}

Mount is a handler served outside /api, at a path prefix, before the frontend: the admin pages, a webhook endpoint, a file server.

type None

type None struct{}

None is the In type of a handler without a request body and the Out type of one that replies 204 No Content.

type Request

type Request[In any] struct {
	// Body is the decoded JSON body; the zero value for None or when the
	// method carries no body.
	Body In
	// Raw is the underlying request, for headers, path values and query.
	Raw *http.Request
	// contains filtered or unexported fields
}

Request is what a typed handler receives.

func (*Request[In]) Header

func (r *Request[In]) Header() http.Header

Header returns response headers to send with the reply.

func (*Request[In]) Param

func (r *Request[In]) Param(name string) string

Param returns a path parameter from the pattern, e.g. {id}.

func (*Request[In]) Query

func (r *Request[In]) Query(name string) string

Query returns a query string parameter.

func (*Request[In]) SetCookie

func (r *Request[In]) SetCookie(c *http.Cookie)

SetCookie adds a cookie to the reply. On a request that came over HTTPS it is sent Secure even when c.Secure is false.

func (*Request[In]) Status

func (r *Request[In]) Status(code int)

Status sets the response status for a successful reply (default 200, or 204 when Out is None).

type Router

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

Router registers and serves API handlers. It wraps net/http's ServeMux, so patterns take the "METHOD /path/{param}" form.

func New

func New() *Router

New returns a Router with the built-in routes registered.

func (*Router) Group

func (r *Router) Group(prefix string, mw ...middleware.Middleware) *Router

Group returns a router for the routes at prefix and below it ("/api/v1/notes" covers "/api/v1/notes" and "/api/v1/notes/{id}") with middleware of its own, run after the parent's. Routes registered on the group use full patterns, as on the parent:

notes := r.Group("/api/v1/notes", auth.Require())
router.Route(notes, "GET /api/v1/notes", listNotes)

Every route lands on the root router's ServeMux with its full pattern, the group's middleware wrapped around that route alone. ServeMux precedence therefore holds across groups: the more specific pattern wins wherever it was registered ("GET /api/v1/notes/recent" in a group over "GET /api/v1/notes/{id}" on the parent), and two conflicting patterns panic at registration. A path no route matches is a 404 (a 405 with Allow for a wrong method) without the group's middleware.

The prefix may hold wildcards; the group's patterns repeat them and the handlers read their values:

post := r.Group("/api/v1/posts/{id}", auth.Require())
router.Route(post, "GET /api/v1/posts/{id}/tags", listTags) // req.Param("id")

A pattern outside the prefix panics.

func (*Router) Handle

func (r *Router) Handle(pattern string, h http.Handler)

Handle registers a handler for a ServeMux pattern. On a group the pattern's path must lie at the group's prefix or below it.

func (*Router) HandleFunc

func (r *Router) HandleFunc(pattern string, h http.HandlerFunc)

HandleFunc registers a handler function for a ServeMux pattern.

func (*Router) Handler

func (r *Router) Handler() http.Handler

Handler returns the router with its middleware applied. A group serves through the root router: its Handler is the root's.

func (*Router) Mount

func (r *Router) Mount(prefix string, h http.Handler)

Mount serves h at prefix and below it, outside /api, ahead of the frontend ("/admin/" for the admin pages). The app's middleware and services apply; the router's own do not, nor a group's.

func (*Router) Mounts

func (r *Router) Mounts() []Mount

Mounts lists what Mount registered.

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP implements http.Handler.

func (*Router) Use

func (r *Router) Use(mw ...middleware.Middleware)

Use adds middleware around every route, outermost first. Call it before the router serves its first request; later calls have no effect.

type StreamHandler

type StreamHandler[In, Event any] func(ctx context.Context, req *Request[In], send func(Event) error) error

StreamHandler is a typed streaming handler: the request as Route decodes it, and send, which writes one event to the client and flushes it.

Jump to

Keyboard shortcuts

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