howdy/controller
Controllers group HTTP routes under a common path prefix.
import howdy/controller.{type Context}
pub fn user_controller() {
controller.new("user")
|> controller.get("/all", fn(ctx: Context) { controller.text(ctx, "all") })
|> controller.get("/:id", fn(ctx: Context) { ... })
|> controller.post("/", fn(ctx: Context) { ... })
}
Types
A controller under construction, with a typed guard result.
Call build before mounting a guarded builder.
pub opaque type Builder(guarded)
Everything a handler needs to answer a request.
pub type Context =
context.Context(Nil)
A named group of routes.
pub type Controller =
Builder(Nil)
A context carrying the successful result of a controller guard.
pub type GuardedContext(guarded) =
context.Context(guarded)
A function that answers a request.
pub type Handler =
fn(context.Context(Nil)) -> response.Response(content.Content)
A function that runs around a handler. See howdy/middleware.
pub type Middleware =
fn(
context.Context(Nil),
fn(context.Context(Nil)) -> response.Response(content.Content),
) -> response.Response(content.Content)
The rest of the chain a middleware hands the request on to.
pub type Next =
fn(context.Context(Nil)) -> response.Response(content.Content)
A single route: method, path pattern (already prefixed) and handler.
annotations hold values other packages attach with annotate, keyed by
the package that owns them; the router ignores them.
pub type Route {
Route(
method: http.Method,
segments: List(String),
handler: fn(context.Context(Nil)) -> response.Response(
content.Content,
),
annotations: List(#(String, dynamic.Dynamic)),
)
}
Constructors
-
Route( method: http.Method, segments: List(String), handler: fn(context.Context(Nil)) -> response.Response( content.Content, ), annotations: List(#(String, dynamic.Dynamic)), )
Values
pub fn annotate(
controller: Builder(guarded),
key: String,
value: dynamic.Dynamic,
) -> Builder(guarded)
Attach value under key to the route added most recently. This is for
packages that describe routes, such as howdy_openapi, not for handlers:
the router never reads annotations. Use a key naming your package, and
read it back with annotation. A later value for the same key replaces
the earlier one. Panics if the controller has no routes yet.
pub fn annotation(
route: Route,
key: String,
) -> Result(dynamic.Dynamic, Nil)
The value attached to a route under key with annotate.
pub fn body_reader(
ctx: context.Context(guarded),
limit limit: Int,
) -> context.Reader
Read the request body a piece at a time with context.read_chunk, for a
body too large to hold in memory, such as a file written to disk as it
arrives. A body over limit bytes is refused.
pub fn build(controller: Builder(guarded)) -> Builder(Nil)
Finish a builder so controllers with different guard types can be mounted together. Each route already captures its guard; no guard runs at build time.
pub fn delete(
controller: Builder(guarded),
path: String,
handler: fn(context.Context(guarded)) -> response.Response(
content.Content,
),
) -> Builder(guarded)
pub fn get(
controller: Builder(guarded),
path: String,
handler: fn(context.Context(guarded)) -> response.Response(
content.Content,
),
) -> Builder(guarded)
pub fn guarded(
prefix: String,
check: fn(context.Context(Nil)) -> Result(
guarded,
service.Error,
),
) -> Builder(guarded)
Create a controller whose guard runs before every matched handler.
An error returns the standard service error response and stops execution.
The successful value is available to every route as ctx.guard.
pub fn html(
ctx: context.Context(guarded),
body: String,
) -> response.Response(content.Content)
A 200 response with an HTML body.
pub fn json(
ctx: context.Context(guarded),
body: json.Json,
) -> response.Response(content.Content)
A 200 response with a JSON body.
pub fn middleware(
controller: Builder(guarded),
middleware: fn(
context.Context(Nil),
fn(context.Context(Nil)) -> response.Response(content.Content),
) -> response.Response(content.Content),
) -> Builder(guarded)
Add middleware that runs for every route in the controller, no matter where in the pipeline it is added. The first added is the outermost.
pub fn new(prefix: String) -> Builder(Nil)
Create a controller. Every route added to it is mounted under prefix.
Leading and trailing slashes are ignored, so "user", "/user" and
"/user/" are equivalent. "api/user" mounts under two segments.
pub fn param(
ctx: context.Context(guarded),
name: String,
) -> Result(String, Nil)
Look up a path parameter captured by a :name or *name segment.
pub fn patch(
controller: Builder(guarded),
path: String,
handler: fn(context.Context(guarded)) -> response.Response(
content.Content,
),
) -> Builder(guarded)
pub fn post(
controller: Builder(guarded),
path: String,
handler: fn(context.Context(guarded)) -> response.Response(
content.Content,
),
) -> Builder(guarded)
pub fn put(
controller: Builder(guarded),
path: String,
handler: fn(context.Context(guarded)) -> response.Response(
content.Content,
),
) -> Builder(guarded)
pub fn read_body(
ctx: context.Context(guarded),
limit limit: Int,
) -> Result(BitArray, context.BodyError)
Read the whole request body, up to limit bytes.
pub fn route(
controller: Builder(guarded),
method: http.Method,
path: String,
handler: fn(context.Context(guarded)) -> response.Response(
content.Content,
),
) -> Builder(guarded)
Add a route for any method. A path segment starting with : captures
one segment as a parameter. A final segment starting with * captures
the rest of the path, joined with /, and matches even when nothing
remains: "/files/*path" matches /files, /files/a and /files/a/b.
pub fn routes(controller: Builder(Nil)) -> List(Route)
The routes of a controller, in the order they were declared. Each handler is already wrapped in the controller’s middleware.
pub fn segments(path: String) -> List(String)
Split a path into non-empty segments. "/user//all/" -> ["user", "all"].
pub fn status(
ctx: context.Context(guarded),
code: Int,
) -> response.Response(content.Content)
A response with the given status and no body.
pub fn text(
ctx: context.Context(guarded),
body: String,
) -> response.Response(content.Content)
A 200 response with a UTF-8 text body.
pub fn with_status(
response: response.Response(content.Content),
code: Int,
) -> response.Response(content.Content)
Change the status of a response built with one of the helpers above.
pub fn wrap(
handler: fn(context.Context(Nil)) -> response.Response(
content.Content,
),
middleware: fn(
context.Context(Nil),
fn(context.Context(Nil)) -> response.Response(content.Content),
) -> response.Response(content.Content),
) -> fn(context.Context(Nil)) -> response.Response(
content.Content,
)
Wrap a handler in one middleware.
pub fn wrap_all(
handler: fn(context.Context(Nil)) -> response.Response(
content.Content,
),
middleware: List(
fn(
context.Context(Nil),
fn(context.Context(Nil)) -> response.Response(
content.Content,
),
) -> response.Response(content.Content),
),
) -> fn(context.Context(Nil)) -> response.Response(
content.Content,
)
Wrap a handler in several middleware. The first in the list is the outermost.