howdy/openapi/endpoint
Endpoints are handlers that declare their inputs with schemas, so the
OpenAPI document shows exactly what each route reads. Mount them on an
ordinary controller with endpoint.get, endpoint.post and the rest.
import howdy/openapi/endpoint.{type Endpoint}
import howdy/openapi/schema
pub fn controller() -> controller.Controller {
controller.new("user")
|> endpoint.get("/:id", by_id())
}
fn by_id() -> Endpoint(Nil) {
use <- endpoint.describe([
endpoint.summary("Find a user"),
endpoint.response(200, "The user", user.schema()),
endpoint.error(404, "No user has this id"),
])
use id <- endpoint.path("id", schema.int())
use ctx <- endpoint.handle
user_service.find(id)
|> service.respond(ctx, schema.to_json(_, user.schema()))
}
Each input runs in order before the handler. A missing or malformed
parameter answers 400, as howdy/param and howdy/query do. A body
that is not JSON answers 400; one that does not match its schema
answers 422 with an error for every field, as howdy/validate does.
Like schema objects, an endpoint is documented by running its inputs
with placeholder values. Do the work inside handle, not between the
inputs, so documenting an endpoint never runs it.
Types
Values
pub fn body(
input: schema.Schema(a),
next: fn(a) -> Endpoint(guarded),
) -> Endpoint(guarded)
A JSON request body, up to body.default_limit bytes.
pub fn body_with_limit(
input: schema.Schema(a),
limit: Int,
next: fn(a) -> Endpoint(guarded),
) -> Endpoint(guarded)
A JSON request body, up to limit bytes.
pub fn delete(
controller: controller.Builder(guarded),
path: String,
endpoint: Endpoint(guarded),
) -> controller.Builder(guarded)
pub fn deprecated() -> Doc
pub fn describe(
docs: List(Doc),
next: fn() -> Endpoint(guarded),
) -> Endpoint(guarded)
Document the endpoint. Usually the first line of it.
pub fn description(text: String) -> Doc
A longer description. OpenAPI allows Markdown here.
pub fn empty_response(status: Int, description: String) -> Doc
A response without a body, such as 204.
pub fn error(status: Int, description: String) -> Doc
An error response with Howdy’s error body, {"error": "..."}, as
service.error_response sends. Endpoints with inputs document their
400, and those with a body their 422, without being told.
pub fn get(
controller: controller.Builder(guarded),
path: String,
endpoint: Endpoint(guarded),
) -> controller.Builder(guarded)
pub fn handle(
handler: fn(context.Context(guarded)) -> response.Response(
content.Content,
),
) -> Endpoint(guarded)
The handler, which runs once every input has been read.
pub fn header(
name: String,
input: schema.Schema(a),
next: fn(a) -> Endpoint(guarded),
) -> Endpoint(guarded)
A required request header.
pub fn operation_id(id: String) -> Doc
The operation’s id, which client generators use as a function name.
Without one it is the method and path, with parameters named after
by: get_user for GET /user, get_user_by_id for GET /user/:id.
Ids must be unique; building a document with a repeated one panics.
pub fn optional_header(
name: String,
input: schema.Schema(a),
next: fn(option.Option(a)) -> Endpoint(guarded),
) -> Endpoint(guarded)
A request header that may be left out.
pub fn optional_query(
name: String,
input: schema.Schema(a),
next: fn(option.Option(a)) -> Endpoint(guarded),
) -> Endpoint(guarded)
A query parameter that may be left out.
pub fn patch(
controller: controller.Builder(guarded),
path: String,
endpoint: Endpoint(guarded),
) -> controller.Builder(guarded)
pub fn path(
name: String,
input: schema.Schema(a),
next: fn(a) -> Endpoint(guarded),
) -> Endpoint(guarded)
A path parameter captured by a :name or *name segment. Parameters in
the route that an endpoint does not read are documented as strings.
pub fn post(
controller: controller.Builder(guarded),
path: String,
endpoint: Endpoint(guarded),
) -> controller.Builder(guarded)
pub fn put(
controller: controller.Builder(guarded),
path: String,
endpoint: Endpoint(guarded),
) -> controller.Builder(guarded)
pub fn query(
name: String,
input: schema.Schema(a),
next: fn(a) -> Endpoint(guarded),
) -> Endpoint(guarded)
A required query parameter. With a schema.list it takes every
occurrence of the key, and is [] when there are none.
pub fn response(
status: Int,
description: String,
body: schema.Schema(a),
) -> Doc
A response with a JSON body described by body. Pass the same schema to
schema.to_json when building the response, so they cannot disagree.
pub fn route(
controller: controller.Builder(guarded),
method: http.Method,
path: String,
endpoint: Endpoint(guarded),
) -> controller.Builder(guarded)
Add an endpoint to a controller, like controller.route.
pub fn security(scheme: String) -> Doc
Require a security scheme declared on the document, such as one added
with openapi.bearer_auth. Documentation only: enforce it with a guard
or middleware.
pub fn tag(name: String) -> Doc
Group the endpoint under a tag. Without one, an endpoint is tagged with
the first fixed segment of its path, such as user for /user/:id.
pub fn wrap(
endpoint: Endpoint(guarded),
middleware: fn(
context.Context(Nil),
fn(context.Context(Nil)) -> response.Response(content.Content),
) -> response.Response(content.Content),
) -> Endpoint(guarded)
Run middleware around one endpoint, like middleware.wrap for a
handler. It runs after the controller guard. Middleware sees the request
without the guard’s value, as middleware always does, and the endpoint
gets the value back with whatever the middleware changed.