moonapi

moonapi — a typed web framework for MoonBit (← FastAPI): routing, typed extractors, descriptor-driven validation, multi-version OpenAPI/Swagger (2.0 / 3.0 / 3.1) with security schemes, dependency injection, OAuth2 password-bearer with self-built HS256 JWT and scopes, per-operation security enforcement, multipart/urlencoded form and file extractors, response_model filtering, background tasks, sub-application mounting, a CORS/gzip(real DEFLATE)/exception-handler middleware stack with per-status handlers, Server-Sent Events, and WebSocket routes, on the moonasgi SEAM.

fastapi
web
framework
openapi
swagger
asgi
moonbit
Download zip
Author
Version
0.8.0
License
Apache-2.0
Last updated
10 hours ago
Downloads
526

Dependencies

#moonapi

A typed web framework for MoonBit — ← FastAPI.

Check and Test License mooncakes

moonapi builds an AsgiApp from typed routes and generates its own OpenAPI / Swagger document — the role FastAPI plays for Python. It depends only on moonasgi, so it's backend-agnostic (routing and OpenAPI run in-process on every backend); a server such as mooncat runs the resulting app.

flowchart LR routes["typed routes<br/>App::get / post / …"] --> app["**moonapi** App"] app -->|"App::to_asgi()"| asgi(["moonasgi AsgiApp"]) app -->|"App::openapi()"| spec["OpenAPI 2.0 / 3.0 / 3.1"] asgi --> cat["mooncat serves it"]

#Quickstart

let app = @moonapi.App::new()
app.get("/", _ctx => @moonapi.text(200, "Hello from moonapi!"))
app.get("/users/:id", ctx => @moonapi.text(200, "user " + ctx.param("id").unwrap()),
summary="fetch a user")
app.post("/users", _ctx => @moonapi.text(201, "created"))

// One set of routes → every mainstream spec version:
let v31 = app.openapi_json(version=OpenApi31) // OpenAPI 3.1.0
let v30 = app.openapi_json(version=OpenApi30) // OpenAPI 3.0.3
let v20 = app.openapi_json(version=Swagger20) // Swagger 2.0
let docs_page = @moonapi.swagger_ui() // a Swagger UI page

// Serve it (native, via mooncat):
@mooncat.serve(app.to_asgi(), port=8000)

#What's here (v0)

  • RoutingApp::get/post/put/patch/delete/route, :param path segments extracted into Context::param, correct 404 (no path) vs 405 (path but not method). A route takes the operation arguments FastAPI's path operations take — summary, description, tags, deprecated, operation_id, status_code, responses, name, include_in_schema, and an openapi_extra fragment merged over the generated operation object — and App::url_for(name, params) resolves a named route back to its path (← url_path_for), mount prefix included.
  • RoutersRouter collects routes away from any application, and App::include_router(router, prefix=, tags=, security=, dependencies=, responses=, deprecated=, include_in_schema=) folds them in as the app's own (← FastAPI's APIRouter / include_router). The arguments given at the join reach every route in the group: tags, security requirements and dependencies lead the route's own, group responses are documented under it, and deprecated / include_in_schema mark or hide the whole set. A router is a value, so the same one can be included twice under different prefixes.
  • Descriptor tree — a runtime Schema / Param / Endpoint tree (a one-first-class-value substitute for FastAPI's from-signature reflection) that a typed route carries. Walked once to (a) emit complete OpenAPI request/response body schemas — objects, arrays, scalars, required, nullable (type: [t, "null"] in 3.1, nullable in 3.0, x-nullable in 2.0), a Map[String, T] as additionalProperties, Any as the empty schema, a format such as binary, and defaults, with named models hoisted under components/schemas and referenced by $ref — and (b) drive request validation off the same tree. A Param carries the same constraints a Field does, plus a default and an alias, so a query parameter's bounds are both documented and enforced, and a 422 points at the name the client actually sent. User structs describe themselves with derive(ToJson) + a T::schema() associated function + a one-line ToSchema bridge (the mctl-friendly shape).
  • Multi-version OpenAPIApp::openapi / openapi_json emit Swagger 2.0, OpenAPI 3.0.3, and OpenAPI 3.1.0 from the same routes and descriptors (3.x requestBody + components/schemas; 2.0 body-parameter + definitions), because a good FastAPI is not pinned to one spec version.
  • Typed body extractorsContext::body[T] deserialises the JSON body into a derive(FromJson) struct; Context::body_validated[T] first checks it against the endpoint descriptor and returns either the built value or a FastAPI-shaped 422 error list — schema emission, validation, and deserialisation all off one descriptor.
  • Dependency injection — a Container (provider registry + dependency_overrides) with request-scoped, one-shot resolution (per-request caching) and yield-style teardown run LIFO around the handler — the explicit MoonBit equivalent of FastAPI's Depends. App::depends(container.erase()) attaches one to the application, and any route builder takes dependencies=["key"] (← dependencies=[Depends(...)]): the keys resolve before the handler and are torn down after it, whatever the handler did. The teardown is handed the error that ended the request, so cleanup can tell a rollback from a commit.
  • OAuth2 + JWT security — a /token password-grant endpoint issues an HS256 JWT (create_access_token), and an OAuth2PasswordBearer reads the Authorization: Bearer header, verifies the token, and enforces scopes: 401 on a missing or invalid/expired token, 403 when a valid token lacks a required scope. The SHA-256 / HMAC-SHA256 pair is self-built (crypto.mbt), checked against the NIST and RFC 4231 vectors; the alg: "none" downgrade is refused and signatures compare in constant time.
  • Form & file extractorsContext::form parses both an application/x-www-form-urlencoded body (percent- and +-decoded) and a multipart/form-data body, splitting the boundary stream into FormFields and byte-exact UploadFiles (filename + content-type + size + the part's own headers + raw bytes). A body is attacker-controlled and already buffered, so FormLimits bounds what one may spend — at most max_parts parts of max_part_size bytes, Starlette's 1000 × 1 MiB by default — and a body over either bound comes back as None, refused whole rather than truncated. Context::oauth2_password_form reads the OAuth2 password form off it.
  • response_modelfilter_response / json_model validate a handler's return value against a declared Schema and project it down to exactly the model's fields, so a route can hold a richer object internally than it exposes (an id, a password hash) and still emit only what it promised.
  • Middleware & exception handlers — an outer middleware chain (App::middleware) with cors(...) (preflight + actual-request headers, configurable origins/methods/headers, credentials, exposed headers) and gzip(...) (a real DEFLATE compressor, below), plus exception handlers (App::exception_handler): a handler raises an HttpException and the app maps it to a response, falling through to a built-in {"detail": ...} for HttpException and a 500 for anything else. App::add_status_handler(code, ...) swaps in a custom response for any error status — a routing 404 / 405 or a raised exception's status — so an app can serve its own error pages.
  • Per-operation security — a route carries security=[SecurityRequirement::new(scheme, scopes=[...])], and every scheme shape has a guard: secure_oauth2, secure_oauth2_code, secure_bearer, secure_api_key (header / query / cookie), secure_basic, secure_digest, secure_openid. Each surfaces the scheme in the spec and registers the check that runs before the handler — 401 with the WWW-Authenticate challenge the scheme owes (a scoped bearer route names the scope it wanted), 403 on a missing scope or a rejected API key. Every one takes description= and auto_error= (with the flag off, the guard admits an anonymous caller and leaves the decision to the route). A bare add_security_scheme documents without enforcing (FastAPI's split between a scheme and a wired dependency).
  • Background tasks — a background-aware route (App::route_bg) receives a BackgroundTasks queue; add_task defers work that the app runs, in order, after the response is sent (← FastAPI's BackgroundTasks), so a slow write never delays the client.
  • Sub-application mountingApp::mount(prefix, subapp) composes routers: a request under prefix is routed by the sub-app with the prefix stripped (its own middleware, security, and background tasks apply), and the sub-app's routes and security schemes fold into the parent's merged OpenAPI document under the prefix. Mounts nest. App::mount_handler(prefix, handler) mounts a foreign moonasgi Handler the same way — a third-party component, or static files — which the app routes to but does not document.
  • Streaming responsesApp::stream(path, handler) / App::route_stream(verb, ...) register a route returning a moonasgi.StreamingResponse, whose chunks reach the client as separate body events — a client reads the first long before the last one exists. App::handle_with_stream is the chunk-level view a test reads. A middleware is typed buffered-in, buffered-out, so one that rewrites the body (gzip) collapses the reply to a single chunk rather than cutting new bytes at boundaries that no longer describe them.
  • Server-Sent EventsServerSentEvent frames per the WHATWG event-stream format (id / event / retry / multi-line data / : comments); sse_response is a text/event-stream stream of those frames, one chunk per event, so each dispatches on arrival. Hand it to App::stream. (An event stream delivered as one body is not an event stream — it is a file shaped like one.)
  • WebSocket routesApp::websocket(path, handler) over the moonasgi WS SEAM. The handler drives a WebSocket (accept / receive / send / close); it's a synchronous core, so drive_websocket runs it against an in-memory frame queue in a test and App::to_asgi serves it over the async transport.
  • Security schemes — the six OpenAPI shapes (OAuth2 password and authorization-code flows, HTTP bearer / basic / digest, an API key in a header / query / cookie, and OpenID Connect) are emitted in each dialect's form: components/securitySchemes in 3.x, securityDefinitions in 2.0, where an http scheme becomes the standard apiKey-in-Authorization workaround and OpenID Connect — which 2.0 cannot express — is left out rather than described as something it is not.
  • Swagger UIswagger_ui() returns a ready-to-serve documentation page.
  • Responsestext, html, json and json_model helpers over moonasgi.Response, plus redirect(url, status=307) (← RedirectResponse; the URL is encoded over the characters a URI reserves for structure, so an already-encoded URL passes through and a smuggled CRLF cannot open a header) and file_response(content, filename=, media_type=, inline=) (← FileResponse: a media type guessed from the extension, Content-Length, and a Content-Disposition that falls back to RFC 6266's filename* when the name will not survive quoting). It takes bytes rather than a path because moonapi has no filesystem — the same app runs on wasm, js and native.
  • Cookiesset_cookie(resp, name, value, max_age=, expires=, path=, domain=, secure=, http_only=, same_site=) and delete_cookie(...) (← response.set_cookie / delete_cookie). Each returns a new response carrying one more Set-Cookie, which is the correct wire shape: two cookies are two headers, never one folded field. Names, values and attributes are stripped of the octets RFC 6265 forbids, so a value cannot forge an attribute or open a header of its own; delete_cookie expires by both Max-Age=0 and a 1970 date. Context::cookie reads them back.
  • Status constants — the 63 HTTP_* and 15 WS_* names FastAPI re-exports from Starlette (HTTP_404_NOT_FOUND, WS_1008_POLICY_VIOLATION), so a route table shows a deliberate 307 rather than a bare number.

Verified across all backends (wasm, wasm-gc, js, native) in CI, 0 warnings under --deny-warn.

#Design notes

Two places make an explicit, documented trade-off rather than a silent shortcut:

  • GZip produces a valid RFC 1952 gzip stream — correct header, CRC-32, and ISIZE — around a real RFC 1951 DEFLATE payload: LZ77 back-reference matching (a 32 KiB window, hash-chain match finder) coded with the fixed Huffman table (deflate.mbt), so the body actually shrinks. A companion inflate decodes it, so the encoder is round-trip-verified on every backend, and the system gzip reads the output in CI. The one increment left is dynamic Huffman (a per-block code fit to the data) for a tighter ratio; fixed Huffman already delivers a genuine ratio (roughly 19× on a repetitive body).
  • WebSocket handlers are a synchronous core: the same handler runs in a test and under a server. Because the core can't suspend on the async transport (MoonBit runs async only in an async context), the serving shell buffers the client's inbound frames, runs the handler, then emits its frames. Content and order are preserved — exact for echo, broadcast, and request-reply — but it doesn't interleave live per-frame with the client.

#Roadmap (transliterating FastAPI)

The descriptor tree (Endpoint / Param / Schema) drives OpenAPI body schemas and validation; on top sit the typed derive(FromJson) body extractors, the dependency-injection container, the OAuth2 password-bearer security layer with self-built HS256 JWT, the Form / File extractors, and response_model filtering. On the middleware side: CORS, a real DEFLATE gzip, exception and per-status handlers, Server-Sent Events, and WebSocket routes. This release wires per-operation security from the declared schemes (enforced, and emitted into the spec), background tasks that run after the response, and sub-application mounting with a merged OpenAPI document. Next: RS256 / ES256 signing (a self-built RSA/ECDSA bignum stack); dynamic-Huffman DEFLATE for a tighter ratio; static files and templates; and codegen'd request schemas via moonctl.

#License

Apache-2.0.

ApiHandler

type ApiHandler = (Context) ->
Response
raise

A moonapi route handler: request context in, response out. It may raise an HttpException (or any error) instead of returning — the app catches it and maps it to a response through the registered exception handlers, so handlers read like FastAPI's raise HTTPException(...) rather than threading a Result back by hand. A plain non-raising closure is still a valid handler.

BackgroundHandler

type BackgroundHandler = (Context, BackgroundTasks) ->
Response
raise

A background-aware handler: it additionally receives the request's BackgroundTasks queue, so it can schedule work to run after its response is sent (← declaring a BackgroundTasks parameter in FastAPI).

ExceptionHandler

type ExceptionHandler = (Context, Error) ->
Response
?

An exception handler: given the request context and the raised error, return Some(response) to handle it or None to defer to the next handler. The explicit MoonBit form of FastAPI's @app.exception_handler(ExcType) — the None case stands in for "this handler isn't registered for that type".

StreamHandler

A streaming route's handler: request context in, a chunked response out (← returning a StreamingResponse from a FastAPI path operation). Every chunk becomes one body event on the wire, so a client reads the first long before the last one exists — the point of streaming, and what a single buffered Response cannot express.

WsHandler

type WsHandler = (WebSocket) -> Unit

A WebSocket route handler: given the connection, drive the exchange. Usually accept, then a receive loop, then close.

ToSchema

pub(open) trait ToSchema {
fn to_schema(Self) -> Schema
}

The impl-able version of a type's descriptor. A user struct implements it — the value is ignored; it exists so descriptor-carrying code can be generic over "a type that knows its own schema". The primary, mctl-friendly shape is still a plain associated function T::schema() -> Schema (no instance needed, mirroring FastAPI referencing the model class); this trait bridges to it.

HttpException

pub(all) suberror HttpException {
HttpException(status~ : Int, detail~ : Json, headers~ : Array[(String, String)])
}

An HTTP error a handler can raise to short-circuit with a status and body (← FastAPI's HTTPException). detail is any JSON (a string is the common case); headers are added to the response (e.g. a WWW-Authenticate challenge). Caught by the app and mapped to a response.

JwtError

pub suberror JwtError {
MalformedToken(String)
UnsupportedAlg(String)
BadSignature
Expired
NotYetValid
}

A JWT verification failure. Each way a token can be rejected is reported distinctly so the Security layer can map it to the right status and a caller can log precisely which check failed.

Address

pub(all) struct Address {
city : String
zip : String
} derive(Eq, ToJson)

A nested value type, to show a $ref chain: User.address references this under components/schemas.
impl ToSchema for Address

Address::schema

fn Address::schema() -> Schema

The Address descriptor.

ApiInfo

pub(all) struct ApiInfo {
title : String
api_version : String
description : String
terms_of_service : String
contact : Contact?
license : License?
servers : Array[Server]
}

What the document says about the API itself — FastAPI's info block plus servers. An app carries one (App::describe sets it) so that every emission of the document, including the route enable_docs registers, agrees.

ApiInfo::new

fn ApiInfo::new() -> ApiInfo

The defaults an app starts with.

ApiKey

pub struct ApiKey {
loc : ApiKeyIn
name : String
verify : (String) -> Bool
}

An API-key scheme: the parameter name the key arrives under, where it arrives, and the predicate that accepts a presented key. FastAPI's APIKeyHeader only extracts and leaves the check to a dependency; a route guard needs both, so the check travels with the scheme.

ApiKey::cookie

fn ApiKey::cookie(name : String, verify : (String) -> Bool) -> ApiKey

An API key read from the cookie name (← APIKeyCookie).

ApiKey::header

fn ApiKey::header(name : String, verify : (String) -> Bool) -> ApiKey

An API key read from the request header name (← APIKeyHeader).

ApiKey::query

fn ApiKey::query(name : String, verify : (String) -> Bool) -> ApiKey

An API key read from the query parameter name (← APIKeyQuery).

ApiKey::read

fn ApiKey::read(self : ApiKey, ctx : Context) -> String?

The key this request presents for the scheme, None when it carries none.

ApiKey::scheme

fn ApiKey::scheme(self : ApiKey) -> SecurityScheme

The apiKey scheme object this key is documented as.

ApiKeyIn

pub(all) enum ApiKeyIn {
KeyHeader
KeyQuery
KeyCookie
} derive(Eq)

Where an API key travels (← APIKeyHeader / APIKeyQuery / APIKeyCookie).

App

pub struct App {
routes : Array[Route]
ws_routes : Array[WsRoute]
middlewares : Array[((
Request
) ->
Response
) -> ((
Request
) ->
Response
)]
exception_handlers : Array[(Context, Error) ->
Response
?]
security_schemes : Array[DeclaredScheme]
enforcers : Map[String, (Context, SecurityScopes, Int64) -> Result[AuthenticatedUser,
Response
]]
status_handlers : Map[Int, (Context) ->
Response
]
mounts : Array[(String, Mount)]
startup_hooks : Array[() -> Unit raise]
shutdown_hooks : Array[() -> Unit raise]
info : ApiInfo
clock : () -> Int64
deps : Deps?
}

A moonapi application: routes, WebSocket routes, an outer middleware chain, exception handlers, mounted sub-applications, the security schemes surfaced in the OpenAPI document (with optional runtime enforcers), per-status exception handlers, and the verification clock. Compiles to a moonasgi AsgiApp any server (mooncat) can run.

App::add_security_scheme

fn App::add_security_scheme(self : App, name : String, scheme : SecurityScheme, description? : String) -> Unit

Declare a security scheme under name, surfaced in the emitted OpenAPI document (components/securitySchemes in 3.x, securityDefinitions in Swagger 2.0) so the generated spec describes how to authenticate. description is the prose shown beside it in the documentation UI.

This declares only. A route naming this scheme in its security is documented as protected and left unguarded, exactly as a FastAPI scheme that no path operation depends on is — the secure_* functions are the ones that wire an enforcer.

App::add_status_handler

fn App::add_status_handler(self : App, status : Int, handler : (Context) ->
Response
) -> Unit

Register a per-status exception handler (← FastAPI's add_exception_handler keyed by an HTTP status code). Whenever an error path yields status — a routing 404 / 405, or a raised HttpException (or the fallback 500) — the handler's response replaces the default, so an app can serve a custom error page. Successful handler returns are never rewritten.

App::delete

fn App::delete(self : App, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a DELETE route.

App::depends

fn App::depends(self : App, deps : Deps) -> Unit

Give the app the dependency container its routes resolve their declared dependencies through (← FastAPI's Depends wiring, which a route names and the application supplies). The container's value type is erased on the way in, because a route-level dependency runs for its effect and its value is never handed to the handler — the same thing FastAPI's dependencies=[...] does with the values it builds.

A route that declares a dependency the container does not provide is answered with a 500: the setup the route promised did not happen, and running the handler as though it had is worse than saying so.

App::describe

fn App::describe(self : App, title? : String, api_version? : String, description? : String, terms_of_service? : String, contact? : Contact?, license? : License?, servers? : Array[Server]) -> Unit

Set what the OpenAPI document says about this API (← FastAPI's FastAPI(title=…,description=…, contact=…, license_info=…, servers=…)). App::openapi and the /openapi.json route enable_docs registers both read it, so the document a client fetches and the one a test builds cannot describe different APIs.

App::enable_docs

fn App::enable_docs(self : App, openapi_url? : String?, docs_url? : String?, redoc_url? : String?, version? : OpenApiVersion) -> Unit

Serve the app's own documentation, the way FastAPI does out of the box: the OpenAPI document at openapi_url, Swagger UI at docs_url, and ReDoc at redoc_url. Pass None for any of the three to leave it off. The three routes are kept out of the document they serve, so enabling docs does not change the spec a client reads.

It is a call rather than a default because registering routes behind the app's back would surprise anyone mounting this app under a prefix.

App::exception_handler

fn App::exception_handler(self : App, h : (Context, Error) ->
Response
?) -> Unit

Register an exception handler. On a raised error the handlers are tried in registration order; the first to return Some(response) wins. An unhandled error falls through to the built-in mapping — an HttpException becomes its own status / detail, anything else a 500.

App::get

fn App::get(self : App, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a GET route. A shorthand for route(Get, ...) carrying the same documentation and security arguments.

App::handle

Route a request to its handler through the middleware chain, returning 404 when no path matches and 405 when a path matches but no method does. Any error a handler raises is mapped to a response by the exception handlers. Background tasks a handler scheduled are dropped on this path; use handle_with_background (as to_asgi does) to run them.

App::handle_with_background

Route a request through the middleware chain and return both the response and the background queue the handler filled — the caller (to_asgi, or a test) runs the queue after the response is sent. handle is the plain-response wrapper over this.

App::handle_with_stream

Route a request through the middleware chain and return the reply in its streamed form, plus the background queue the handler filled. to_asgi sends each chunk as its own body event; a test reads chunks to see where the boundaries fell. A route that is not a streaming one comes back as a single chunk, so this answers every request, not only the streamed ones.

A middleware is typed buffered-in, buffered-out, so the chain is run over the joined body and the chunks are kept only when what came back is what went in. A middleware that rewrote the body — gzip — has produced something the old boundaries no longer describe, and cutting the new bytes at them would send a corrupt stream.

App::include_router

fn App::include_router(self : App, router : Router, prefix? : String, tags? : Array[String], security? : Array[SecurityRequirement], dependencies? : Array[String], responses? : Array[ResponseSpec], deprecated? : Bool, include_in_schema? : Bool) -> Unit

Fold router's routes into this app (FastAPI's include_router, and the same name — include is a reserved word). Each is registered under prefix and becomes one of the app's own routes, and the arguments given here reach every one of them:

  • tags, security and dependencies are placed before the route's own, so a group's tag leads and a group-wide requirement or dependency cannot be dropped by a route that adds one of its own;
  • responses are documented under the route's, which therefore wins any status both name;
  • deprecated marks the whole group, and include_in_schema=false hides it, neither of which a route can undo.

App::lifespan_handler

This app's hooks as a moonasgi LifespanHandler — the synchronous core, which is what makes boot and teardown testable on every backend without a server. Mounted apps are included: a mount is part of the composition being started, and nothing else would ever drive its hooks.

App::middleware

Add an outer middleware. Middlewares wrap the router as an onion; the first registered is the outermost (it sees the request first and the response last). cors(...) and gzip(...) are middlewares.

App::mount

fn App::mount(self : App, prefix : String, subapp : App) -> Unit

Mount a sub-application under prefix (← FastAPI's app.mount(prefix, sub)). A request whose path lies under prefix is routed by subapp with the prefix stripped (its own middleware, security, and background tasks apply), and the sub-app's routes appear under prefix in the merged OpenAPI document with its security schemes folded into the parent's.

App::mount_handler

fn App::mount_handler(self : App, prefix : String, handler : (
Request
) ->
Response
) -> Unit

Mount a foreign moonasgi handler under prefix (← FastAPI mounting a plain ASGI app, app.mount("/static", StaticFiles(...))). A request under prefix is handed to handler with the prefix stripped, exactly as for a sub-app.

A handler is not a moonapi application, so it contributes nothing to the OpenAPI document and has no lifespan of its own to run — the app only routes to it. Mounts are tried in registration order, whichever kind they are.

App::new

fn App::new() -> App

Create an empty application. The verification clock defaults to 0 (Unix epoch); a server sets a real one with App::set_clock, and tests inject a fixed time so token expiry is deterministic.

App::on_shutdown

fn App::on_shutdown(self : App, hook : () -> Unit raise) -> Unit

Run hook when the server shuts down, after the last request has been served (← FastAPI's on_event("shutdown")). Hooks run in reverse registration order, so a resource is released before whatever it was opened from. A hook that raises does not stop the others — shutdown reports the first failure once the rest have run.

App::on_startup

fn App::on_startup(self : App, hook : () -> Unit raise) -> Unit

Run hook when the server starts, before it accepts the first request (← FastAPI's on_event("startup")). Hooks run in the order they were registered; one that raises aborts startup, and the server is told why.

App::openapi

fn App::openapi(self : App, version? : OpenApiVersion, title? : String, api_version? : String, description? : String, terms_of_service? : String, contact? : Contact?, license? : License?, servers? : Array[Server]) -> Json

Build the app's OpenAPI document as a Json value, walking the registered routes — this app's and every mounted sub-app's, each under its prefix — into paths, methods and operations. version picks the dialect: Swagger 2.0, OpenAPI 3.0.3 or 3.1.0 off the identical routes. Routes registered with include_in_schema=false are left out.

App::openapi_json

fn App::openapi_json(self : App, version? : OpenApiVersion) -> String

The app's OpenAPI / Swagger document for version, stringified.

App::patch

fn App::patch(self : App, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a PATCH route: change part of the addressed resource.

App::post

fn App::post(self : App, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a POST route — the verb that carries a request body, so this is the one most often given an endpoint describing that body.

App::put

fn App::put(self : App, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a PUT route: replace the addressed resource wholesale.

App::route

fn App::route(self : App, verb : Method, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a route for an explicit method. An optional endpoint descriptor makes the route fully typed (its parameters, request body, and responses surface in the OpenAPI document and drive validation); security attaches per-operation requirements (emitted as OpenAPI security and enforced before the handler when their scheme has an enforcer); dependencies names provider keys resolved through the app's container (App::depends) before the handler and torn down after it, whatever the handler did.

The rest describe the operation: summary and description are its prose, operation_id the stable handle client generators name their method after, status_code the status its success response is documented under, responses further documented responses, name the handle App::url_for resolves, and openapi_extra a fragment merged over the generated operation object — the same keyword arguments FastAPI's path operations take.

App::route_bg

fn App::route_bg(self : App, verb : Method, path : String, handler : (Context, BackgroundTasks) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a background-aware route: the handler additionally receives the request's BackgroundTasks queue, whose thunks the app runs after the response is sent (← declaring a BackgroundTasks parameter in FastAPI).

App::route_stream

fn App::route_stream(self : App, verb : Method, path : String, handler : (Context) ->
StreamingResponse
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a streaming route (← a FastAPI path operation returning a StreamingResponse). The handler's chunks reach the client as separate body events, so a long or open-ended reply starts arriving before it is finished.

A stream is only streamed as far as the app can honestly keep it one: moonasgi types a middleware as buffered response in, buffered response out, so a middleware that rewrites the body — gzip does — collapses the reply to a single chunk. Everything else about the route is ordinary; it documents, validates and enforces security exactly as route does.

App::secure_api_key

fn App::secure_api_key(self : App, name : String, key : ApiKey, description? : String, auto_error? : Bool) -> Unit

Declare an API-key scheme under name and wire it as a runtime enforcer (← FastAPI's Security(APIKeyHeader(name=...))). The scheme appears in the document as apiKey in the header, query or cookie the key names, and a route requiring name is refused with 403 unless it presents a key the scheme accepts.

App::secure_basic

fn App::secure_basic(self : App, name : String, basic : BasicAuth, description? : String, auto_error? : Bool) -> Unit

Declare an HTTP Basic scheme under name and wire it as a runtime enforcer (← Security(HTTPBasic())). A route requiring name is refused with 401 and a Basic challenge unless it presents credentials the scheme accepts.

App::secure_bearer

fn App::secure_bearer(self : App, name : String, secret : String, bearer_format? : String, description? : String, auto_error? : Bool) -> Unit

Declare a plain HTTP bearer scheme under name and wire it as a runtime enforcer (← Security(HTTPBearer())). The token is verified as an HS256 JWT against secret at the app clock's time, and the route's required scopes are checked against the token's — the same guard the OAuth2 flows use, without an OAuth2 flow to advertise.

App::secure_digest

fn App::secure_digest(self : App, name : String, digest : DigestAuth, description? : String, auto_error? : Bool) -> Unit

Declare an HTTP Digest scheme under name and wire it as a runtime enforcer (← Security(HTTPDigest())). A route requiring name is refused with 403 unless it presents a Digest credential the scheme accepts.

App::secure_oauth2

fn App::secure_oauth2(self : App, name : String, bearer : OAuth2PasswordBearer, scopes? : Array[(String, String)], description? : String, auto_error? : Bool) -> Unit

Declare an OAuth2 password-bearer scheme under name and wire it as a runtime enforcer. Like add_security_scheme it surfaces the scheme in the OpenAPI document (with the advertised scopes and description), and additionally registers the guard a route names in its security: the app pulls the bearer token, verifies it against bearer's secret at the app clock's time, and checks the route's required scopes — returning 401/403 before the handler runs.

auto_error=false admits the request instead of refusing it (← the argument every FastAPI security class takes, whose dependency then yields None and leaves the decision to the route).

App::secure_oauth2_code

fn App::secure_oauth2_code(self : App, name : String, code : OAuth2CodeBearer, scopes? : Array[(String, String)], description? : String, auto_error? : Bool) -> Unit

Declare an OAuth2 authorization-code scheme under name and wire it as a runtime enforcer (← Security(OAuth2AuthorizationCodeBearer(...))). The document describes the browser redirect and token endpoints and the advertised scopes; the guard verifies the presented bearer token exactly as the password flow's does, since by the time a request arrives the two flows have produced the same credential.

App::secure_openid

fn App::secure_openid(self : App, name : String, url : String, secret : String, description? : String, auto_error? : Bool) -> Unit

Declare an OpenID Connect scheme under name and wire it as a runtime enforcer (← Security(OpenIdConnect(openIdConnectUrl=...))). url is the discovery document a client reads every other parameter from; secret is what the ID token presented as a bearer credential is verified against.

Swagger 2.0 has no openIdConnect type, so this scheme is absent from a 2.0 document rather than described as something it is not.

App::set_clock

fn App::set_clock(self : App, clock : () -> Int64) -> Unit

Set the clock the app reads to verify token expiry when enforcing route security (Unix seconds). A native server passes the wall clock; a test passes a fixed function so expiry is deterministic.

App::stream

fn App::stream(self : App, path : String, handler : (Context) ->
StreamingResponse
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a streaming GET route — the verb a stream is nearly always read over, and what an SSE endpoint is. A shorthand for route_stream(Get, ...).

App::to_asgi

fn App::to_asgi(self : App) -> (async (
Scope
, async () ->
Event
, async (
Event
) -> Unit) -> Unit)

Compile the app to a moonasgi AsgiApp a server can run: drain the request body, route it, and stream the response back over the SEAM.

App::url_for

fn App::url_for(self : App, name : String, params? : Map[String, String]) -> String?

The path of the route registered under name, with its :name segments filled from params (← FastAPI's url_path_for). Mounted sub-applications are searched too, so what comes back already carries the mount prefix — the path a client would call, not the one the sub-app knows itself by.

None when nothing is registered under that name, or when params is missing one the path needs. Values are substituted as given: a value with a / in it lands as extra path segments, so encode before calling if that matters.

App::websocket

fn App::websocket(self : App, path : String, handler : (WebSocket) -> Unit) -> Unit

Register a WebSocket route (← FastAPI's @app.websocket(path)). The path matches with the same :name segment rules as HTTP routes.

AuthenticatedUser

pub(all) struct AuthenticatedUser {
subject : String
scopes : Array[String]
claims : Map[String, Json]
}

The identity a verified token carries, injected into a protected handler (← the object FastAPI's get_current_user returns). subject is the sub claim, scopes the granted scopes, and claims the whole verified payload for anything else the handler needs (exp, custom claims).

AuthenticatedUser::has_scope

fn AuthenticatedUser::has_scope(self : AuthenticatedUser, scope : String) -> Bool

Whether this user was granted scope.

BackgroundTasks

pub struct BackgroundTasks {
tasks : Array[() -> Unit]
}

A queue of deferred thunks (← FastAPI's BackgroundTasks). A background-aware route receives one per request, calls add_task to enqueue work, and the app runs the queue — in enqueue order — after the response has been sent.

BackgroundTasks::add_task

fn BackgroundTasks::add_task(self : BackgroundTasks, task : () -> Unit) -> Unit

Enqueue a thunk to run after the response is sent. Tasks run in the order they were added, each after the previous returns (← BackgroundTasks.add_task).

BackgroundTasks::len

fn BackgroundTasks::len(self : BackgroundTasks) -> Int

How many tasks are queued — the app checks this to skip the drain when a route scheduled nothing.

BackgroundTasks::new

An empty task queue.

BackgroundTasks::run

fn BackgroundTasks::run(self : BackgroundTasks) -> Unit

Run every queued task in order, then clear the queue. Called by the app once the response has been handed to the transport, so a task's latency never delays the client. Idempotent: a second call runs nothing.

BasicAuth

pub struct BasicAuth {
realm : String
verify : (HttpBasicCredentials) -> Bool
}

An HTTP Basic scheme (← FastAPI's HTTPBasic): the realm named in the challenge, and the predicate that accepts a presented username/password.

BasicAuth::challenge

fn BasicAuth::challenge(self : BasicAuth) -> String

The WWW-Authenticate value this scheme challenges with.

BasicAuth::new

fn BasicAuth::new(verify : (HttpBasicCredentials) -> Bool, realm? : String) -> BasicAuth

Build an HTTP Basic scheme. A realm is optional and, when given, names the protection space in the challenge so a browser can tell one login from another.

Constraint

pub(all) enum Constraint {
Minimum(Double)
Maximum(Double)
ExclusiveMinimum(Double)
ExclusiveMaximum(Double)
MultipleOf(Double)
MinLength(Int)
MaxLength(Int)
Pattern(String)
MinItems(Int)
MaxItems(Int)
} derive(Eq)

A value constraint on a field (JSON-Schema keyword ↔ Pydantic argument).

Contact

pub(all) struct Contact {
name : String
url : String
email : String
}

Build the OpenAPI / Swagger document for the app as a Json value, walking the registered routes once into paths → methods → operations. OpenAPI contact info (← FastAPI's contact): every field optional, emitted only when non-empty.

Container

pub(all) struct Container[V] {
providers : Map[String, Provider[V]]
overrides : Map[String, Provider[V]]
}

The provider registry: key -> Provider, plus a separate overrides map that shadows it. Overrides are FastAPI's app.dependency_overrides — a test swaps a real dependency (a live DB session) for a fake without touching the routes. A registered override always wins over the base provider.

Container::clear_override

fn[V] Container::clear_override(self : Container[V], key : String) -> Unit

Drop the override for key (no-op if none), restoring the base provider.

Container::clear_overrides

fn[V] Container::clear_overrides(self : Container[V]) -> Unit

Drop every override — the usual test teardown that returns the container to its production wiring.

Container::erase

fn[V] Container::erase(self : Container[V]) -> Deps

This container with its value type erased, ready for App::depends. The providers, the per-request cache, the sub-dependency resolution and the LIFO teardown are all the container's own; erasure hides only the value.

Container::new

fn[V] Container::new() -> Container[V]

An empty container.

Container::open_scope

fn[V] Container::open_scope(self : Container[V]) -> Scope[V]

Open a fresh request scope over this container.

Container::override_

fn[V] Container::override_(self : Container[V], key : String, factory : () -> V, teardown? : (V, Error?) -> Unit) -> Container[V]

Register a dependency override for key — FastAPI's app.dependency_overrides[dep] = fake. Takes precedence over the base provider until cleared.

Container::provide

fn[V] Container::provide(self : Container[V], key : String, factory : () -> V, teardown? : (V, Error?) -> Unit) -> Container[V]

Register a base provider under key (last registration wins), returning the container so registrations can chain.

Container::provide_using

fn[V] Container::provide_using(self : Container[V], key : String, factory : (Scope[V]) -> V, teardown? : (V, Error?) -> Unit) -> Container[V]

Register a base provider whose factory resolves sub-dependencies through the request scope it is handed (FastAPI's nested Depends). Otherwise like provide.

Container::run

Run handler inside a fresh request scope, then tear the scope down — the setup/teardown pair wrapped around a handler, exactly as a FastAPI yield dependency brackets the request. The handler resolves whatever it needs through the scope; every dependency built during the call is released (LIFO) once it returns, then the response is handed back.

A handler that raises is released the same way, and the error reaches the teardowns before it is re-raised for the app's exception handlers to map: cleanup that only ran on the happy path would leak exactly when it matters.

Context

pub(all) struct Context {
request :
Request

params : Map[String, String]
}

The per-request context handed to a handler: the raw request plus the path parameters extracted from the matched route (:name segments).
fn Context::api_key_cookie(self : Context, name : String) -> String?

The API key carried in the cookie name (← FastAPI's APIKeyCookie), or None.

Context::api_key_header

fn Context::api_key_header(self : Context, name : String) -> String?

The API key carried in the request header name (← FastAPI's APIKeyHeader), or None. Header names are matched against the request's lower-cased headers.

Context::api_key_query

fn Context::api_key_query(self : Context, name : String) -> String?

The API key carried in the query parameter name (← FastAPI's APIKeyQuery), or None.

Context::bearer_token

fn Context::bearer_token(self : Context) -> String?

Pull the bearer token out of the Authorization: Bearer <token> header, None if the header is absent or isn't a bearer credential. The scheme name is matched case-insensitively, as RFC 6750 requires.

Context::body

Deserialise the JSON request body into a user type T, None when the body is absent, is not valid JSON, or does not shape-match T. This is the unchecked, best-effort extractor — the total counterpart of writing item: Item on a FastAPI handler when you don't want the framework's 422. T describes itself with derive(@json.FromJson); the deserialisation is core's, so it stays faithful to the JSON shape without any reflection.

Context::body_json

fn Context::body_json(self : Context) -> Json?

Parse the request body as JSON, returning None for an empty body or one that does not parse — the total counterpart of FastAPI reading a JSON body.

Context::body_validated

fn[T :
FromJson
] Context::body_validated(self : Context, schema : Schema) -> Result[T, Array[ValidationError]]

The validated typed-body extractor — the faithful equivalent of FastAPI declaring a pydantic model parameter: the body is checked against the endpoint's descriptor schema (the same tree that emits the OpenAPI body schema), and only if it conforms is it deserialised into T. On failure it yields the FastAPI-shaped ValidationError list (located under ["body",...]), ready for unprocessable; on success it yields the built T.

Reusing validate_schema here is the point of the descriptor tree: schema emission, request validation, and typed deserialisation are all driven off one source of truth, exactly as pydantic derives all three from one model. Because validation runs first, @json.from_json is reached only for a shape-conforming value; the final catch keeps the extractor total for the residual cases a scalar schema cannot express (e.g. an out-of-range integer).

Context::cookie

fn Context::cookie(self : Context, name : String) -> String?

Look up a cookie by name from the request Cookie header, which is a ; -separated list of key=value pairs. Surrounding spaces are trimmed; None if there is no Cookie header or the name is absent.

Context::digest_credentials

fn Context::digest_credentials(self : Context) -> String?

The credential of an Authorization: Digest <parameters> header — the comma-separated parameter list, verbatim (← FastAPI's HTTPDigest). None when the header is absent or names another scheme; the scheme name is matched case-insensitively, as RFC 7235 requires.

Context::form

fn Context::form(self : Context, limits? : FormLimits) -> FormData?

The parsed request form — FastAPI's Form(...) / File(...) parameters. Dispatches on the Content-Type: a multipart/form-data body is split on its boundary into fields and files, an application/x-www-form-urlencoded body is decoded into fields. Any other (or absent) content type yields an empty form rather than raising, so the extractor stays total.

None when the body breaks limits — more parts than max_parts, or a part longer than max_part_size. The form is refused whole rather than truncated: a handler given the first thousand parts of a larger form would answer a request nobody sent. An empty Some is the other answer, and means the request carried no form at all.

Context::http_basic

fn Context::http_basic(self : Context) -> HttpBasicCredentials?

The HTTP Basic credentials on this request (← FastAPI's HTTPBasic dependency), or None.

Context::json_field

fn Context::json_field(self : Context, name : String) -> Json?

Pull a single field out of a JSON object body by name, None if the body is absent, not an object, or lacks the field.

Context::oauth2_password_form

fn Context::oauth2_password_form(self : Context) -> OAuth2PasswordRequestForm?

Read an OAuth2PasswordRequestForm off the request's urlencoded (or multipart) body. None when neither username nor password is present — the body isn't a password-grant form at all — and equally when the body is too large to be one, since a grant carries six fields and nothing that big is a login.

Context::param

fn Context::param(self : Context, name : String) -> String?

Look up a path parameter by name.

Context::query

fn Context::query(self : Context, name : String) -> String?

Look up a query-string parameter by name, e.g. ?limit=10&q=cat%20dog. Keys and values are percent/plus-decoded, so q above reads back as cat dog. When a key repeats, the first occurrence wins; use query_all to read every one.

Context::query_all

fn Context::query_all(self : Context, name : String) -> Array[String]

Every value given for name, in the order they appear — ?tag=a&tag=b reads back as ["a", "b"]. Empty when the key is absent.

CorsConfig

pub(all) struct CorsConfig {
allow_origins : Array[String]
allow_all_origins : Bool
allow_methods : Array[String]
allow_headers : Array[String]
allow_all_headers : Bool
allow_credentials : Bool
expose_headers : Array[String]
max_age : Int
}

CORS policy (← Starlette's CORSMiddleware). Origins, methods, and headers are allow-lists; the *_all flags open a dimension wholesale. Per the Fetch standard, allow_credentials forbids the * wildcard in the reflected Access-Control-Allow-Origin, so with credentials the request origin is echoed back instead.

DeclaredScheme

type DeclaredScheme

A security scheme as an application declares it: the name routes refer to it by, the kind that shapes the emitted object, and the prose beside it (← the description= every FastAPI security class takes).

Dep

pub(all) enum Dep {
Greeting(String)
} derive(Eq)

The dependency value type of the greet app. A sum type wrapping every dependency this app injects — the explicit, exhaustive stand-in for FastAPI resolving heterogeneous Depends values dynamically.

Deps

pub struct Deps {
open : () -> DepsScope
}

A container with its dependency value type erased (Container::erase builds one), so a non-generic App can hold one. Resolving through it runs a provider's factory and records its teardown exactly as Scope::get does; what it cannot do is hand the value back — which is precisely what a route-level dependency does not need, since FastAPI's dependencies=[...] discards the values it builds and keeps only their effects.

DepsScope

type DepsScope

One request's resolution scope over an erased container: resolve a key (false when nothing provides it), then close, handing any failure to the teardowns.

DigestAuth

pub struct DigestAuth {
realm : String
verify : (String) -> Bool
}

An HTTP Digest scheme (← FastAPI's HTTPDigest, which likewise carries the credential and no more): verify decides whether the Digest parameter string a client sent is acceptable. Computing the RFC 7616 response digest is the application's, since only it holds the password store.

DigestAuth::challenge

fn DigestAuth::challenge(self : DigestAuth) -> String

The WWW-Authenticate value this scheme challenges with.

DigestAuth::new

fn DigestAuth::new(verify : (String) -> Bool, realm? : String) -> DigestAuth

Build an HTTP Digest scheme, optionally naming the protection realm.

EcdsaPrivateKey

pub(all) struct EcdsaPrivateKey {
d :
BigInt

}

An ECDSA P-256 private key: the scalar d. The ES256 signing key.

EcdsaPrivateKey::from_hex

fn EcdsaPrivateKey::from_hex(d_hex : String) -> EcdsaPrivateKey

Build a P-256 private key from its hex-encoded scalar.

EcdsaPrivateKey::public_key

The public key Q = d·G for this private key.

EcdsaPublicKey

An ECDSA P-256 public key: the curve point (x, y). The ES256 verification key.

EcdsaPublicKey::from_hex

fn EcdsaPublicKey::from_hex(x_hex : String, y_hex : String) -> EcdsaPublicKey

Build a P-256 public key from the hex-encoded affine coordinates — e.g. the two halves of openssl's uncompressed pub point after its 04 prefix.

Ed25519PrivateKey

pub(all) struct Ed25519PrivateKey {
seed : Bytes
}

An Ed25519 (EdDSA) private key: the 32-byte secret seed. The signing key for the JWT EdDSA algorithm (RFC 8037).

Ed25519PrivateKey::from_hex

fn Ed25519PrivateKey::from_hex(hex : String) -> Ed25519PrivateKey

Build an Ed25519 private key from its 32-byte seed in hex.

Ed25519PrivateKey::public_key

The public key matching this private key: A = [s]B.

Ed25519PublicKey

pub(all) struct Ed25519PublicKey {
key : Bytes
}

An Ed25519 (EdDSA) public key: the 32-byte compressed point. The verification key for the JWT EdDSA algorithm (RFC 8037).

Ed25519PublicKey::from_hex

fn Ed25519PublicKey::from_hex(hex : String) -> Ed25519PublicKey

Build an Ed25519 public key from its 32-byte hex encoding.

Endpoint

pub(all) struct Endpoint {
params : Array[Param]
request_body : Schema?
request_required : Bool
responses : Array[ResponseSpec]
} derive(Eq)

The runtime endpoint descriptor a route can carry — the one first-class value that replaces FastAPI reading a handler's signature. Walked once for the OpenAPI operation (parameters + requestBody + responses, with every named object hoisted into components/schemas) and for request validation.

Endpoint::new

fn Endpoint::new(params? : Array[Param], request_body? : Schema?, request_required? : Bool, responses? : Array[ResponseSpec]) -> Endpoint

Build an endpoint descriptor. Everything is optional: a bare Endpoint::new() describes an endpoint with no parameters, no body, and (on emit) a default 200 OK response.

Endpoint::validate

fn Endpoint::validate(self : Endpoint, ctx : Context) -> Array[ValidationError]

Validate an inbound request ctx against this endpoint descriptor: every declared parameter (path / query / header / cookie) plus the JSON request body, all off the same descriptor tree that emits the OpenAPI operation. Returns the accumulated errors — an empty array means the request conforms, otherwise pass them to unprocessable for a FastAPI-shaped 422.

Field

pub(all) struct Field {
name : String
schema : Schema
required : Bool
description : String
constraints : Array[Constraint]
default : Json?
} derive(Eq)

One field of an object schema: its name, its schema, whether it is required, an optional description, its value constraints, and the default that stands in when a body leaves it out.

Field::is_required

fn Field::is_required(self : Field) -> Bool

Whether a body must carry this field. A field with a default never must — its absence is answered by the default, exactly as a Python default argument is, so it is left out of the emitted required list and not reported missing.

Field::new

fn Field::new(name : String, schema : Schema, required? : Bool, description? : String, constraints? : Array[Constraint], default? : Json) -> Field

Build a field. required defaults to true (FastAPI treats a field without a default as required). constraints are the Pydantic-style value constraints (ge/le/min_length/pattern/…) that are both emitted into the field's OpenAPI schema and enforced when validating an inbound value. default is emitted into the schema and makes the field optional, since a value that has one is never missing.

FormData

pub(all) struct FormData {
fields : Array[FormField]
files : Array[UploadFile]
} derive(Eq)

A parsed form: its plain fields and its uploaded files, in body order. A multipart part is a file when its Content-Disposition carries a filename; otherwise it's a field. An urlencoded body only ever yields fields.

FormData::field

fn FormData::field(self : FormData, name : String) -> String?

The value of the first field named name, None if absent — the common Form(...) lookup.

FormData::field_all

fn FormData::field_all(self : FormData, name : String) -> Array[String]

Every value submitted under name, in order — an HTML form can repeat a field (checkbox groups, multi-selects), and FastAPI surfaces those as a list.

FormData::file

fn FormData::file(self : FormData, name : String) -> UploadFile?

The first uploaded file under name, None if absent — the File(...) lookup.

FormField

pub(all) struct FormField {
name : String
value : String
} derive(Eq)

A plain form field: a name and its decoded text value.

FormLimits

pub(all) struct FormLimits {
max_parts : Int
max_part_size : Int
} derive(Eq)

What a submitted form may cost before it is refused: how many parts it may carry, and how many bytes any one part may hold.

A request body is attacker-controlled and, on this SEAM, fully buffered before a route ever sees it. Unbounded, a body that is nothing but boundaries becomes as many parts as it has bytes — every one of them an allocation the app made on the sender's say-so, on top of the body it already holds.

FormLimits::new

fn FormLimits::new(max_parts? : Int, max_part_size? : Int) -> FormLimits

Limits with Starlette's defaults — 1000 parts of at most 1 MiB — or either bound overridden.

GreetReq

pub(all) struct GreetReq {
name : String
} derive(Eq, ToJson,
FromJson
)

The request body of POST /greet. derive(@json.FromJson) lets Context::body_validated build it after the descriptor accepts the payload; its schema and struct fields agree (both require name), so a schema-valid body always deserialises.

GreetReq::schema

fn GreetReq::schema() -> Schema

The GreetReq descriptor — one required string field.

HttpBasicCredentials

pub(all) struct HttpBasicCredentials {
username : String
password : String
}

The credentials carried in an HTTP Basic Authorization header (← FastAPI's HTTPBasicCredentials): the username and password from base64(username:password).

License

pub(all) struct License {
name : String
url : String
}

OpenAPI license info (← FastAPI's license_info): name required, url optional.

Method

pub(all) enum Method {
Get
Post
Put
Patch
Delete
Head
Options
} derive(Eq)

HTTP methods a moonapi route can bind to.

Mount

type Mount

What a mount prefix routes to: a moonapi sub-application, whose routes, security schemes and lifespan hooks fold into the parent's, or a foreign moonasgi handler, which the parent can only forward requests to.

NewUser

pub(all) struct NewUser {
name : String
email : String
age : Int
} derive(Eq, ToJson)

The demo request model — the body of POST /users. age is optional.
impl ToSchema for NewUser

NewUser::schema

fn NewUser::schema() -> Schema

The NewUser descriptor.

OAuth2CodeBearer

pub struct OAuth2CodeBearer {
authorization_url : String
token_url : String
refresh_url : String
secret : String
}

An OAuth2 authorization-code bearer scheme (← FastAPI's OAuth2AuthorizationCodeBearer): the authorization_url a browser is sent to, the token_url the code is exchanged at, an optional refresh_url, and the shared HS256 secret a presented token is verified against.

OAuth2CodeBearer::new

fn OAuth2CodeBearer::new(authorization_url : String, token_url : String, secret : String, refresh_url? : String) -> OAuth2CodeBearer

Build an authorization-code bearer scheme. refresh_url is left out of the document when empty, since a provider that offers no refresh endpoint should not be described as having one.

OAuth2CodeBearer::scheme

fn OAuth2CodeBearer::scheme(self : OAuth2CodeBearer, scopes? : Array[(String, String)]) -> SecurityScheme

The security scheme that describes this flow, advertising scopes.

OAuth2PasswordBearer

pub(all) struct OAuth2PasswordBearer {
token_url : String
secret : String
}

An OAuth2 password-bearer security scheme (← FastAPI's OAuth2PasswordBearer). Holds the token_url (the endpoint that issues tokens, surfaced to API docs) and the shared HS256 secret used to verify presented tokens.

OAuth2PasswordBearer::authenticate

fn OAuth2PasswordBearer::authenticate(self : OAuth2PasswordBearer, ctx : Context, now_secs : Int64, scopes? : Array[String]) -> Result[AuthenticatedUser,
Response
]

Authenticate a request against this scheme and enforce scopes (← FastAPI's Security(get_current_user, scopes=[...])). On success returns the AuthenticatedUser; otherwise the response to return:

  • no bearer token -> 401 {"detail":"Not authenticated"}
  • malformed / bad-signature / expired / not-yet-valid token -> 401 {"detail":"Could not validate credentials"}
  • valid token missing a required scope -> 403 {"detail":"Not enough permissions"}

Both 401s carry the challenge the required scopes describe — Bearer on its own, or Bearer scope="..." — so a client learns what it should have asked for.

now_secs is the verification time (Unix seconds), passed in so the check stays pure and testable on every backend.

OAuth2PasswordBearer::new

fn OAuth2PasswordBearer::new(token_url : String, secret : String) -> OAuth2PasswordBearer

Build a password-bearer scheme pointing at the token endpoint at token_url, verifying tokens with secret.

OAuth2PasswordBearer::scheme

fn OAuth2PasswordBearer::scheme(self : OAuth2PasswordBearer, scopes? : Array[(String, String)]) -> SecurityScheme

Build the security scheme that describes this password-bearer flow (← the object FastAPI derives from OAuth2PasswordBearer). scopes are the (name, description) pairs advertised in the OpenAPI document.

OAuth2PasswordRequestForm

pub(all) struct OAuth2PasswordRequestForm {
grant_type : String
username : String
password : String
scopes : Array[String]
client_id : String?
client_secret : String?
} derive(Eq)

The parsed OAuth2 password-grant form (← FastAPI's OAuth2PasswordRequestForm). The token endpoint reads username / password to authenticate and scopes (the space-delimited scope field, split into a list) to stamp into the issued token. grant_type is "password" for this flow; client_id / client_secret are optional confidential-client credentials.

ObjectSchema

pub(all) struct ObjectSchema {
name : String
fields : Array[Field]
description : String
} derive(Eq)

The body of an object schema: its component name (empty = an anonymous inline object; non-empty = hoisted to components/schemas and referenced), its ordered fields, and an optional description.

OpenApiVersion

pub(all) enum OpenApiVersion {
Swagger20
OpenApi30
OpenApi31
} derive(Eq)

Target OpenAPI / Swagger document version. moonapi emits every mainstream version from the same route descriptors — a "good FastAPI" is not pinned to one spec version.

Param

pub(all) struct Param {
name : String
loc : ParamLoc
schema : Schema
required : Bool
description : String
constraints : Array[Constraint]
default : Json?
alias_ : String
} derive(Eq)

A single request parameter descriptor: its name, location, scalar schema, whether it is required, an optional description, its value constraints, the default that stands in when the request omits it, and the alias it travels under on the wire. Path parameters are always required (OpenAPI requires it); the constructor keeps the caller's value but validation treats path params as mandatory.

Param::is_required

fn Param::is_required(self : Param) -> Bool

Whether a request must carry this parameter. One with a default never must — its absence is answered by the default, exactly as a Python default argument is.

Param::key

fn Param::key(self : Param) -> String

The name this parameter travels under: its alias when it has one, else its own name. This is what is read off the request, what the document calls it, and what a validation error's loc points at — all three being the client's view of the parameter.

Param::new

fn Param::new(name : String, loc : ParamLoc, schema? : Schema, required? : Bool, description? : String, constraints? : Array[Constraint], default? : Json, alias_? : String) -> Param

Build a parameter descriptor. schema defaults to a string and required to true. constraints are the Pydantic-style bounds FastAPI reads off a Query(ge=…, max_length=…) — emitted into the parameter's documented schema and enforced on the inbound value. default is emitted and makes the parameter optional, since a value that has one is never missing. alias is the name the parameter travels under when that differs from the one the code calls it (← Query(alias="item-query")).

ParamLoc

pub(all) enum ParamLoc {
InPath
InQuery
InHeader
InCookie
} derive(Eq)

Where a parameter is carried, the OpenAPI in locations: the path, the query string, a header, or a cookie.

Provider

pub(all) struct Provider[V] {
factory : (Scope[V]) -> V
teardown : (V, Error?) -> Unit
}

A provider: a keyed factory that builds a request-scoped dependency value, with an optional teardown run after the handler (FastAPI's yield dependencies, whose post-yield body is cleanup). The factory runs at most once per request scope; the teardown receives the produced value and the error that ended the request, None when it succeeded — the exception a FastAPI yield dependency sees when it wraps its yield in a try. The factory is handed the Scope so it can resolve sub-dependencies through it — FastAPI's Depends(a) where a itself declares Depends(b).

Provider::new

fn[V] Provider::new(factory : () -> V, teardown? : (V, Error?) -> Unit) -> Provider[V]

Build a leaf provider whose factory needs nothing else. teardown defaults to a no-op — the common "plain value, nothing to release" case.

Provider::scoped

fn[V] Provider::scoped(factory : (Scope[V]) -> V, teardown? : (V, Error?) -> Unit) -> Provider[V]

Build a provider whose factory resolves other dependencies through the request Scope it is handed — the sub-dependency case (FastAPI's nested Depends).

ResponseSpec

pub(all) struct ResponseSpec {
status : Int
description : String
body : Schema?
} derive(Eq)

A single response descriptor: the HTTP status, a human description, and an optional body schema (None for an empty body, e.g. 204).

ResponseSpec::new

fn ResponseSpec::new(status : Int, description? : String, body? : Schema?) -> ResponseSpec

Build a response descriptor. description defaults to "OK" and there is no body unless one is given.

Route

type Route

A route stores one background-aware handler; a plain ApiHandler is wrapped to ignore the queue. security is the per-operation requirements (emitted as OpenAPI security and enforced before the handler when their scheme has an enforcer).

Router

pub struct Router {
routes : Array[Route]
ws_routes : Array[WsRoute]
}

A collection of routes built away from any application and folded into one with App::include_router (← FastAPI's APIRouter). It carries the registration surface of an App and nothing else: middleware, mounts, security schemes, documentation and lifespan belong to the application that includes it.

Router::delete

fn Router::delete(self : Router, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a DELETE route on the router.

Router::get

fn Router::get(self : Router, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a GET route on the router.

Router::new

fn Router::new() -> Router

An empty router. The prefix and the attributes its routes share are given at App::include_router rather than here, so one router can be included twice — under a second prefix, or on another app with different tags.

Router::patch

fn Router::patch(self : Router, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a PATCH route on the router.

Router::post

fn Router::post(self : Router, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a POST route on the router.

Router::put

fn Router::put(self : Router, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a PUT route on the router.

Router::route

fn Router::route(self : Router, verb : Method, path : String, handler : (Context) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a route on the router for an explicit method. Takes what App::route takes and means the same by it; the route reaches an application when the router is included.

Router::route_bg

fn Router::route_bg(self : Router, verb : Method, path : String, handler : (Context, BackgroundTasks) ->
Response
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a background-aware route on the router — App::route_bg, deferred to whichever application includes it.

Router::stream

fn Router::stream(self : Router, path : String, handler : (Context) ->
StreamingResponse
raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint?, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unit

Register a streaming GET route on the router — App::stream, deferred to whichever application includes it.

Router::websocket

fn Router::websocket(self : Router, path : String, handler : (WebSocket) -> Unit) -> Unit

Register a WebSocket route on the router (← APIRouter.websocket). It takes the including prefix like any other route; the operation attributes do not apply, since a WebSocket route is not an OpenAPI operation.

RsaPrivateKey

An RSA private key: modulus n, public exponent e, and private exponent d (the CRT parameters are not needed for the plain m^d mod n path). The RS256 signing key.

RsaPrivateKey::from_hex

fn RsaPrivateKey::from_hex(n_hex : String, e_hex : String, d_hex : String) -> RsaPrivateKey

Build a private key from hex-encoded modulus, public exponent, and private exponent.

RsaPrivateKey::public_key

fn RsaPrivateKey::public_key(self : RsaPrivateKey) -> RsaPublicKey

The public half of a private key — for verifying what it signs.

RsaPublicKey

An RSA public key: modulus n and public exponent e (RFC 8017). The RS256 verification key. Core's BigInt carries the modular arithmetic, so the signature scheme is a straight transcription of PKCS#1 v1.5 with no vendored C.

RsaPublicKey::from_hex

fn RsaPublicKey::from_hex(n_hex : String, e_hex : String) -> RsaPublicKey

Build a public key from hex-encoded modulus and exponent — e.g. openssl's rsa -modulus output and 10001.

SameSite

pub(all) enum SameSite {
Strict
Lax
Unrestricted
} derive(Eq)

How far a cross-site request may carry a cookie. Strict sends it only on same-site requests, Lax also on a top-level navigation (the default, and what stops a cross-site form post from carrying a session), Unrestricted sends it everywhere — it is the wire value None, which browsers honour only on a Secure cookie.

Schema

pub(all) enum Schema {
SStr
SInt
SFloat
SBool
SNull
SArray(Schema)
SObject(ObjectSchema)
SEnum(Schema, Array[Json])
SNullable(Schema)
SMap(Schema)
SAny
SFormat(Schema, String)
} derive(Eq)

A JSON-Schema type descriptor — the runtime, first-class value that stands in for FastAPI's from-signature reflection. One Schema tree is walked once to (a) emit a complete OpenAPI request/response body schema — objects, arrays and scalars, with required, and named objects hoisted under components/schemas and referenced by $ref — and (b) drive validation of an inbound JSON value. This is the explicit, MoonBit-idiomatic equivalent of pydantic's type-driven magic (cf. Rust serde + macros, Go struct tags + codegen). A named object carries its fields inline, so the same tree is fully self-describing for validation; the name is used only to deduplicate it into components on emit.

Schema::array

fn Schema::array(item : Schema) -> Schema

An array schema whose elements all conform to item.

Schema::binary

fn Schema::binary() -> Schema

The schema of file content: a string with OpenAPI's binary format — what an upload is described as, and what a multipart file field carries.

Schema::format

fn Schema::format(base : Schema, format : String) -> Schema

base refined by an OpenAPI format. The format is documentation: it tells a reader and a client generator what the string holds, and validation still checks only base, which is what pydantic does for a format it has no validator for.

Schema::map

fn Schema::map(value : Schema) -> Schema

An object with no declared fields whose values all conform to value (← dict[str, T]). Schema::map(SAny) is the free-form object.

Schema::nullable

fn Schema::nullable(inner : Schema) -> Schema

A schema that also admits null (← Optional[T]). Wrapping rather than a flag because the three dialects express it three different ways.

Schema::object

fn Schema::object(name : String, fields : Array[Field]) -> Schema

A named object schema: hoisted to components/schemas under name and referenced by $ref wherever it is used.

Scope

pub struct Scope[V] {
container : Container[V]
cache : Map[String, V]
building : Map[String, Bool]
teardowns : Array[(Error?) -> Unit]
}

A request-scoped resolution scope. Each dependency is built at most once and its value cached for the life of the scope (FastAPI's per-request dependency cache), and each built value's teardown is recorded to run — in reverse registration order (LIFO) — when the scope closes. Open one per request, resolve dependencies through it, then close it (or use Container::run).

Scope::close

fn[V] Scope::close(self : Scope[V], failure? : Error) -> Unit

Run every recorded teardown in LIFO order and clear them, so a closed scope is inert. Mirrors FastAPI unwinding yield dependencies in reverse — the last opened is torn down first. failure is the error that ended the request and is handed to every teardown, so cleanup can tell a failed request from a successful one and roll back rather than commit.

Scope::get

fn[V] Scope::get(self : Scope[V], key : String) -> V?

Resolve key within this scope: return the already-built instance if the dependency was resolved earlier in the same request; otherwise run its factory once, cache the value, register its teardown, and return it. None when no provider (or override) is registered for key.

SecurityRequirement

pub(all) struct SecurityRequirement {
scheme : String
scopes : Array[String]
} derive(Eq)

A security requirement on a route: the scheme name (which must match a name declared with App::add_security_scheme / App::secure_oauth2) and the scopes the caller must hold. Emitted as one {scheme: [scopes]} entry of the operation's OpenAPI security array.

SecurityRequirement::new

fn SecurityRequirement::new(scheme : String, scopes? : Array[String]) -> SecurityRequirement

Require scheme with the given scopes (default: none — authentication with no scope check). App::get(..., security=[SecurityRequirement::new("OAuth2",scopes=["items"])]) reads like FastAPI's Security(oauth2, scopes=["items"]).

SecurityScheme

pub(all) enum SecurityScheme {
OAuth2Password(token_url~ : String, scopes~ : Array[(String, String)])
OAuth2Code(authorization_url~ : String, token_url~ : String, refresh_url~ : String, scopes~ : Array[(String, String)])
HttpBearer(bearer_format~ : String)
ApiKeyHeader(name~ : String)
ApiKeyQuery(name~ : String)
ApiKeyCookie(name~ : String)
HttpBasic
HttpDigest
OpenIdConnect(url~ : String)
}

A security scheme describing how a client authenticates. Mirrors the OpenAPI scheme types: the two OAuth2 flows a browser or a service actually uses (password and authorization code), HTTP bearer / basic / digest, an API key in a header / query / cookie, and OpenID Connect discovery.

SecurityScopes

pub struct SecurityScopes {
scopes : Array[String]
}

The scopes a route required at the point its security guard runs (← FastAPI's SecurityScopes). A guard reads them to check what the caller was granted and to build the WWW-Authenticate challenge a 401 owes the client.

SecurityScopes::challenge

fn SecurityScopes::challenge(self : SecurityScopes) -> String

The WWW-Authenticate value a bearer challenge carries: Bearer on its own, or Bearer scope="a b" when the route requires scopes, which is how RFC 6750 §3 tells a client what it was missing.

SecurityScopes::list

fn SecurityScopes::list(self : SecurityScopes) -> Array[String]

The required scopes, in declaration order.

SecurityScopes::new

fn SecurityScopes::new(scopes? : Array[String]) -> SecurityScopes

The scopes required at one call site; empty means authentication only.

SecurityScopes::scope_str

fn SecurityScopes::scope_str(self : SecurityScopes) -> String

The scopes as OAuth2's single space-delimited string (← SecurityScopes.scope_str).

Server

pub(all) struct Server {
url : String
description : String
}

A servers entry (← FastAPI's servers): a base URL and an optional description.

ServerSentEvent

pub(all) struct ServerSentEvent {
data : String
event : String?
id : String?
retry : Int?
comment : String?
}

One Server-Sent Event. data is the payload (multi-line data is split into several data: lines per the spec); event names the type an EventSource.addEventListener binds to; id sets lastEventId for reconnection; retry is the client's reconnection delay in milliseconds; comment emits :-prefixed lines (a keep-alive ping carries only this).

ServerSentEvent::data

fn ServerSentEvent::data(data : String) -> ServerSentEvent

A data-only event — the common case (data: ...\n\n).

ServerSentEvent::encode

fn ServerSentEvent::encode(self : ServerSentEvent) -> String

Encode this event as its wire frame: optional comment / id / event / retry fields, then one data: line per line of data, terminated by the blank line that dispatches the event.

ServerSentEvent::keep_alive

fn ServerSentEvent::keep_alive(comment? : String) -> ServerSentEvent

A comment-only keep-alive frame (: <text>\n\n) — no event is dispatched, but the bytes keep the connection warm through proxies.

ServerSentEvent::new

fn ServerSentEvent::new(data : String, event? : String?, id? : String?, retry? : Int?, comment? : String?) -> ServerSentEvent

A fully-specified event. Any field left None is omitted from the frame.

UploadFile

pub(all) struct UploadFile {
name : String
filename : String
content_type : String
content : Bytes
headers : Array[(String, String)]
} derive(Eq)

An uploaded file from a multipart part: the form-field name it came under, the client's filename, its declared content_type (empty when the part carried no Content-Type), the raw content bytes exactly as received, and the part's own headers with their names lowercased, in body order.

content_type is kept as its own field because nearly every caller wants it and nothing else; headers is there for the rest — a Content-Transfer-Encoding a handler must honour, a checksum a client attached — which otherwise had nowhere to be read from.

UploadFile::header

fn UploadFile::header(self : UploadFile, name : String) -> String?

Look up one of the part's own headers by name, None if it carried no such header. Names are matched lowercased, the same convention as @moonasgi.Request::header.

UploadFile::size

fn UploadFile::size(self : UploadFile) -> Int

The uploaded size in bytes (← FastAPI's UploadFile.size). Derived rather than stored: a field could be set to disagree with content, and a size that lies about the bytes beside it is worse than no size at all.

User

pub(all) struct User {
id : Int
name : String
address : Address
tags : Array[String]
} derive(Eq, ToJson)

The demo response model — returned by both user routes. Nests Address and carries an array of tags, so its emitted schema exercises objects, $refs, and arrays together.
impl ToSchema for User

User::schema

fn User::schema() -> Schema

The User descriptor.

ValidationError

pub(all) struct ValidationError {
loc : Array[String]
msg : String
kind : String
}

One entry in a 422 response's detail array, mirroring FastAPI / pydantic v2: where the error is (loc, e.g. ["query", "q"]), a human msg, and a machine kind (serialised as the JSON key type).

ValidationError::missing

fn ValidationError::missing(loc : Array[String]) -> ValidationError

The canonical "a required parameter was not supplied" error located at loc, matching FastAPI's {"type": "missing", "msg": "Field required"}.

ValidationError::type_error

fn ValidationError::type_error(loc : Array[String], kind : String, msg : String) -> ValidationError

A type/parse error located at loc, e.g. kind = "int_parsing" with the matching pydantic message — the shape FastAPI reports for a value of the wrong type.

WebSocket

pub struct WebSocket {
inbox : Array[WsMessage]
cursor : Int
params : Map[String, String]
subprotocols : Array[String]
accepted : Bool
closed : Bool
outbox : Array[
Event
]
}

The handler's view of a WebSocket connection. It reads client frames off an inbound queue and records its own actions (accept / send / close) into an outbound event log the transport replays. params are the matched :name path segments, as with an HTTP Context.

WebSocket::accept

fn WebSocket::accept(self : WebSocket, subprotocol? : String?, headers? : Array[(String, String)]) -> Unit

Accept the handshake (← await websocket.accept()), optionally selecting a subprotocol and adding response headers. Idempotent: a second call is a no-op, so accept-once handlers stay simple.

WebSocket::close

fn WebSocket::close(self : WebSocket, code? : Int, reason? : String) -> Unit

Close the connection with a status code (default 1000, normal closure) and reason. Idempotent.

WebSocket::offered_subprotocols

fn WebSocket::offered_subprotocols(self : WebSocket) -> Array[String]

The subprotocols the client offered (the Sec-WebSocket-Protocol list).

WebSocket::param

fn WebSocket::param(self : WebSocket, name : String) -> String?

Look up a matched path parameter by name.

WebSocket::receive

fn WebSocket::receive(self : WebSocket) -> WsMessage?

Pull the next client frame, None once the client has sent them all (the disconnect). The receive a handler loops on.

WebSocket::receive_bytes

fn WebSocket::receive_bytes(self : WebSocket) -> Bytes?

The next client frame as bytes: Some(b) for a binary frame, None on a text frame or the disconnect.

WebSocket::receive_text

fn WebSocket::receive_text(self : WebSocket) -> String?

The next client frame as text: Some(s) for a text frame, None on a binary frame or the disconnect (← await websocket.receive_text()).

WebSocket::send_bytes

fn WebSocket::send_bytes(self : WebSocket, bytes : Bytes) -> Unit

Send a binary frame to the client.

WebSocket::send_text

fn WebSocket::send_text(self : WebSocket, text : String) -> Unit

Send a text frame to the client (← await websocket.send_text(...)).

WebSocket::sent

The events the handler emitted, in order — the transcript a test asserts on.

WsMessage

pub(all) enum WsMessage {
WsText(String)
WsBinary(Bytes)
} derive(Eq)

One inbound WebSocket message: a text frame or a binary frame.

WsRoute

type WsRoute

HTTP_100_CONTINUE

let HTTP_100_CONTINUE : Int

Continue: the headers are acceptable, send the body.

HTTP_101_SWITCHING_PROTOCOLS

let HTTP_101_SWITCHING_PROTOCOLS : Int

Switching Protocols: the server is changing to the protocol Upgrade asked for.

HTTP_102_PROCESSING

let HTTP_102_PROCESSING : Int

Processing: WebDAV, the request is under way and the reply will follow.

HTTP_103_EARLY_HINTS

let HTTP_103_EARLY_HINTS : Int

Early Hints: preload links sent ahead of the real response.

HTTP_200_OK

let HTTP_200_OK : Int

OK.

HTTP_201_CREATED

let HTTP_201_CREATED : Int

Created: the request made a new resource, named in Location.

HTTP_202_ACCEPTED

let HTTP_202_ACCEPTED : Int

Accepted: taken for processing, outcome not yet known.

HTTP_203_NON_AUTHORITATIVE_INFORMATION

let HTTP_203_NON_AUTHORITATIVE_INFORMATION : Int

Non-Authoritative Information: an intermediary altered the origin's payload.

HTTP_204_NO_CONTENT

let HTTP_204_NO_CONTENT : Int

No Content: succeeded, and there is nothing to send back.

HTTP_205_RESET_CONTENT

let HTTP_205_RESET_CONTENT : Int

Reset Content: succeeded; the client should clear the form that sent it.

HTTP_206_PARTIAL_CONTENT

let HTTP_206_PARTIAL_CONTENT : Int

Partial Content: the byte ranges Range asked for.

HTTP_207_MULTI_STATUS

let HTTP_207_MULTI_STATUS : Int

Multi-Status: WebDAV, one status per member of a collection.

HTTP_208_ALREADY_REPORTED

let HTTP_208_ALREADY_REPORTED : Int

Already Reported: WebDAV, this member was enumerated earlier in the reply.

HTTP_226_IM_USED

let HTTP_226_IM_USED : Int

IM Used: the body is the result of applying delta encodings.

HTTP_300_MULTIPLE_CHOICES

let HTTP_300_MULTIPLE_CHOICES : Int

Multiple Choices: several representations, pick one.

HTTP_301_MOVED_PERMANENTLY

let HTTP_301_MOVED_PERMANENTLY : Int

Moved Permanently: use Location from now on.

HTTP_302_FOUND

let HTTP_302_FOUND : Int

Found: a temporary move. Clients rewrite POST to GET here, which is why 307 exists.

HTTP_303_SEE_OTHER

let HTTP_303_SEE_OTHER : Int

See Other: fetch the outcome with a GET at Location — the redirect after a form post.

HTTP_304_NOT_MODIFIED

let HTTP_304_NOT_MODIFIED : Int

Not Modified: the client's cached copy is still current.

HTTP_305_USE_PROXY

let HTTP_305_USE_PROXY : Int

Use Proxy: deprecated by RFC 9110.

HTTP_306_RESERVED

let HTTP_306_RESERVED : Int

Reserved: never standardised, and reserved so nothing else claims it.

HTTP_307_TEMPORARY_REDIRECT

let HTTP_307_TEMPORARY_REDIRECT : Int

Temporary Redirect: like 302, but the method and body must be kept.

HTTP_308_PERMANENT_REDIRECT

let HTTP_308_PERMANENT_REDIRECT : Int

Permanent Redirect: like 301, but the method and body must be kept.

HTTP_400_BAD_REQUEST

let HTTP_400_BAD_REQUEST : Int

Bad Request: malformed enough that the server will not act on it.

HTTP_401_UNAUTHORIZED

let HTTP_401_UNAUTHORIZED : Int

Unauthorized: not authenticated. The reply must carry WWW-Authenticate.

HTTP_402_PAYMENT_REQUIRED

let HTTP_402_PAYMENT_REQUIRED : Int

Payment Required: reserved.

HTTP_403_FORBIDDEN

let HTTP_403_FORBIDDEN : Int

Forbidden: authenticated, and still not allowed.

HTTP_404_NOT_FOUND

let HTTP_404_NOT_FOUND : Int

Not Found.

HTTP_405_METHOD_NOT_ALLOWED

let HTTP_405_METHOD_NOT_ALLOWED : Int

Method Not Allowed: the path exists under another verb, listed in Allow.

HTTP_406_NOT_ACCEPTABLE

let HTTP_406_NOT_ACCEPTABLE : Int

Not Acceptable: nothing on offer matches the request's Accept.

HTTP_407_PROXY_AUTHENTICATION_REQUIRED

let HTTP_407_PROXY_AUTHENTICATION_REQUIRED : Int

Proxy Authentication Required.

HTTP_408_REQUEST_TIMEOUT

let HTTP_408_REQUEST_TIMEOUT : Int

Request Timeout: the client took too long to send it.

HTTP_409_CONFLICT

let HTTP_409_CONFLICT : Int

Conflict: it clashes with the resource's current state.

HTTP_410_GONE

let HTTP_410_GONE : Int

Gone: it existed and was removed on purpose.

HTTP_411_LENGTH_REQUIRED

let HTTP_411_LENGTH_REQUIRED : Int

Length Required: Content-Length is missing and this server insists on it.

HTTP_412_PRECONDITION_FAILED

let HTTP_412_PRECONDITION_FAILED : Int

Precondition Failed: an If-* header did not hold.

HTTP_413_REQUEST_ENTITY_TOO_LARGE

let HTTP_413_REQUEST_ENTITY_TOO_LARGE : Int

Content Too Large — Starlette's name keeps RFC 7231's "Request Entity Too Large".

HTTP_414_REQUEST_URI_TOO_LONG

let HTTP_414_REQUEST_URI_TOO_LONG : Int

URI Too Long — Starlette's name keeps RFC 7231's "Request-URI Too Long".

HTTP_415_UNSUPPORTED_MEDIA_TYPE

let HTTP_415_UNSUPPORTED_MEDIA_TYPE : Int

Unsupported Media Type: the body's Content-Type is one this route cannot read.

HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE

let HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE : Int

Range Not Satisfiable: no part of the requested range exists.

HTTP_417_EXPECTATION_FAILED

let HTTP_417_EXPECTATION_FAILED : Int

Expectation Failed: the Expect header cannot be met.

HTTP_418_IM_A_TEAPOT

let HTTP_418_IM_A_TEAPOT : Int

I'm a Teapot: RFC 2324's joke, kept because clients test for it.

HTTP_421_MISDIRECTED_REQUEST

let HTTP_421_MISDIRECTED_REQUEST : Int

Misdirected Request: this connection is not authoritative for that authority.

HTTP_422_UNPROCESSABLE_ENTITY

let HTTP_422_UNPROCESSABLE_ENTITY : Int

Unprocessable Content: well-formed, and it fails validation — what unprocessable returns.

HTTP_423_LOCKED

let HTTP_423_LOCKED : Int

Locked: WebDAV, the resource is locked.

HTTP_424_FAILED_DEPENDENCY

let HTTP_424_FAILED_DEPENDENCY : Int

Failed Dependency: WebDAV, a request this one depended on failed.

HTTP_425_TOO_EARLY

let HTTP_425_TOO_EARLY : Int

Too Early: replaying this early-data request could be a replay attack.

HTTP_426_UPGRADE_REQUIRED

let HTTP_426_UPGRADE_REQUIRED : Int

Upgrade Required: resend over the protocol named in Upgrade.

HTTP_428_PRECONDITION_REQUIRED

let HTTP_428_PRECONDITION_REQUIRED : Int

Precondition Required: the server refuses to act on an unconditional write.

HTTP_429_TOO_MANY_REQUESTS

let HTTP_429_TOO_MANY_REQUESTS : Int

Too Many Requests: rate limited; Retry-After says when to come back.

HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE

let HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE : Int

Request Header Fields Too Large.
let HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS : Int

Unavailable For Legal Reasons.

HTTP_500_INTERNAL_SERVER_ERROR

let HTTP_500_INTERNAL_SERVER_ERROR : Int

Internal Server Error: the fallback for an error the app did not map.

HTTP_501_NOT_IMPLEMENTED

let HTTP_501_NOT_IMPLEMENTED : Int

Not Implemented: the server does not support the method at all.

HTTP_502_BAD_GATEWAY

let HTTP_502_BAD_GATEWAY : Int

Bad Gateway: an upstream sent something invalid.

HTTP_503_SERVICE_UNAVAILABLE

let HTTP_503_SERVICE_UNAVAILABLE : Int

Service Unavailable: down or overloaded, and expected back.

HTTP_504_GATEWAY_TIMEOUT

let HTTP_504_GATEWAY_TIMEOUT : Int

Gateway Timeout: an upstream did not answer in time.

HTTP_505_HTTP_VERSION_NOT_SUPPORTED

let HTTP_505_HTTP_VERSION_NOT_SUPPORTED : Int

HTTP Version Not Supported.

HTTP_506_VARIANT_ALSO_NEGOTIATES

let HTTP_506_VARIANT_ALSO_NEGOTIATES : Int

Variant Also Negotiates: the negotiation is configured in a circle.

HTTP_507_INSUFFICIENT_STORAGE

let HTTP_507_INSUFFICIENT_STORAGE : Int

Insufficient Storage: WebDAV, no room to store the representation.

HTTP_508_LOOP_DETECTED

let HTTP_508_LOOP_DETECTED : Int

Loop Detected: WebDAV, the traversal is cyclic.

HTTP_510_NOT_EXTENDED

let HTTP_510_NOT_EXTENDED : Int

Not Extended.

HTTP_511_NETWORK_AUTHENTICATION_REQUIRED

let HTTP_511_NETWORK_AUTHENTICATION_REQUIRED : Int

Network Authentication Required: a captive portal wants a login first.

WS_1000_NORMAL_CLOSURE

let WS_1000_NORMAL_CLOSURE : Int

Normal Closure: the purpose the connection was opened for is fulfilled.

WS_1001_GOING_AWAY

let WS_1001_GOING_AWAY : Int

Going Away: the peer is shutting down or navigating off the page.

WS_1002_PROTOCOL_ERROR

let WS_1002_PROTOCOL_ERROR : Int

Protocol Error.

WS_1003_UNSUPPORTED_DATA

let WS_1003_UNSUPPORTED_DATA : Int

Unsupported Data: a frame of a type this endpoint cannot accept.

WS_1005_NO_STATUS_RCVD

let WS_1005_NO_STATUS_RCVD : Int

No Status Received: reported locally when a close frame carried no code.

WS_1006_ABNORMAL_CLOSURE

let WS_1006_ABNORMAL_CLOSURE : Int

Abnormal Closure: reported locally when the connection died without a close frame.

WS_1007_INVALID_FRAME_PAYLOAD_DATA

let WS_1007_INVALID_FRAME_PAYLOAD_DATA : Int

Invalid Frame Payload Data: a text frame that is not valid UTF-8, say.

WS_1008_POLICY_VIOLATION

let WS_1008_POLICY_VIOLATION : Int

Policy Violation: the generic refusal when no other code fits.

WS_1009_MESSAGE_TOO_BIG

let WS_1009_MESSAGE_TOO_BIG : Int

Message Too Big.

WS_1010_MANDATORY_EXT

let WS_1010_MANDATORY_EXT : Int

Mandatory Extension: the client required an extension the server did not negotiate.

WS_1011_INTERNAL_ERROR

let WS_1011_INTERNAL_ERROR : Int

Internal Error: the server hit an unexpected condition.

WS_1012_SERVICE_RESTART

let WS_1012_SERVICE_RESTART : Int

Service Restart.

WS_1013_TRY_AGAIN_LATER

let WS_1013_TRY_AGAIN_LATER : Int

Try Again Later: overloaded; come back.

WS_1014_BAD_GATEWAY

let WS_1014_BAD_GATEWAY : Int

Bad Gateway: the gateway got an invalid response upstream.

WS_1015_TLS_HANDSHAKE

let WS_1015_TLS_HANDSHAKE : Int

TLS Handshake: reported locally when the handshake failed.

base64url_decode

fn base64url_decode(s : String) -> Bytes

Decode a base64url string (padding optional) back to bytes, remapping -/_ to +// first. Lenient about missing padding, the way JWT segments are written.

base64url_encode

fn base64url_encode(data : Bytes) -> String

base64url encoding (RFC 4648 §5, no padding): standard base64 with +// remapped to -/_ and trailing = dropped — the alphabet JWT uses for its header, payload, and signature segments.

check_constraints

fn check_constraints(value : Json, constraints : Array[Constraint], loc : Array[String], errs : Array[ValidationError]) -> Unit

Enforce the constraints on an inbound value, appending a Pydantic-shaped ValidationError (located at loc) for each violation. A constraint that does not apply to the value's kind (a length bound on a number, say) is simply skipped, as pydantic does.

constant_time_eq

fn constant_time_eq(a : Bytes, b : Bytes) -> Bool

A constant-time byte-string equality: it inspects every byte of both inputs regardless of where they first differ, so an attacker can't recover a valid signature byte-by-byte from response timing. Unequal lengths return false at once (length isn't secret). Used to compare JWT signatures.

cors

fn cors(allow_origins? : Array[String], allow_all_origins? : Bool, allow_methods? : Array[String], allow_headers? : Array[String], allow_all_headers? : Bool, allow_credentials? : Bool, expose_headers? : Array[String], max_age? : Int) -> (((
Request
) ->
Response
) -> ((
Request
) ->
Response
))

A CORS middleware for the given policy. It answers preflight OPTIONS requests (those carrying Access-Control-Request-Method) directly with a 204 and the negotiated Access-Control-* headers, and decorates every other cross-origin response with Access-Control-Allow-Origin (plus Vary: Origin, exposed headers, and the credentials flag). A request with no Origin, or one from a disallowed origin, passes through untouched.

create_access_token

fn create_access_token(subject : String, secret : String, now_secs : Int64, scopes? : Array[String], expires_in_secs? : Int64, extra? : Map[String, Json]) -> String

Mint an HS256 access token for subject (← FastAPI's create_access_token). Stamps sub, iat (= now_secs), exp (= now_secs + expires_in_secs), and, when non-empty, scopes; extra merges in any further claims. Times are Unix seconds. secret is the shared HS256 key.
fn delete_cookie(resp :
Response
, name : String, path? : String, domain? : String, secure? : Bool, http_only? : Bool, same_site? : SameSite?) ->
Response

Expire the cookie named name (← FastAPI's response.delete_cookie): an empty value with Max-Age=0 and a date in the past, so a browser that honours only one of the two still drops it.

A cookie is identified by name, domain and path together, so path and domain must match what set it — otherwise this writes a second, differently scoped cookie and the original survives.

demo_app

fn demo_app() -> App

A demo application exercising the descriptor tree end to end: POST /users takes a typed NewUser body and returns a User; GET /users/:id takes a typed integer path param and returns a User. Both validate off their descriptor and both surface fully-typed bodies (with components/schemas $refs) in openapi.json.

drive_websocket

fn drive_websocket(handler : (WebSocket) -> Unit, inbound : Array[WsMessage], params? : Map[String, String], subprotocols? : Array[String]) -> Array[
Event
]

Run a WebSocket handler against an in-memory frame queue and return the events it emitted — the synchronous test driver (the WS half of a TestClient). Feed the client's frames as inbound; get back the handler's accept / send / close sequence.

ecdsa_p256_sha256_sign

fn ecdsa_p256_sha256_sign(msg : Bytes, key : EcdsaPrivateKey) -> Bytes

ECDSA P-256 sign with SHA-256 — the ES256 signing primitive (FIPS 186-4 §6.4.1) with the deterministic nonce of RFC 6979. Returns the raw r || s (two 32-byte big-endian integers), the encoding JWT uses. Deterministic, so the same message and key always produce the same signature.

ecdsa_p256_sha256_verify

fn ecdsa_p256_sha256_verify(msg : Bytes, sig : Bytes, key : EcdsaPublicKey) -> Bool

ECDSA P-256 verify with SHA-256 — the ES256 primitive (FIPS 186-4 §6.4.2). sig is the raw r || s (two 32-byte big-endian integers), the encoding JWT uses (not ASN.1 DER). Returns whether the signature is valid for msg under key: r, s in range, then u1·G + u2·Q has x-coordinate ≡ r (mod n).

ed25519_public_from_seed

fn ed25519_public_from_seed(seed : Bytes) -> Bytes

Derive the 32-byte compressed public key A = [s]B from a 32-byte Ed25519 seed.

ed25519_sign

fn ed25519_sign(seed : Bytes, msg : Bytes) -> Bytes

Ed25519 signing (RFC 8032 §5.1.6), deterministic. seed is the 32-byte secret key. Returns the 64-byte R || S signature: r = SHA-512(prefix || M)mod l, R = [r]B, k = SHA-512(R || A || M) mod l, S = (r + k·s) mod l.

ed25519_verify

fn ed25519_verify(pub_key : Bytes, msg : Bytes, sig : Bytes) -> Bool

Ed25519 signature verification (RFC 8032 §5.1.7). sig is the 64-byte R || S, pub_key the 32-byte compressed public point. Checks [S]B = R +[k]A with k = SHA-512(R || A || M) mod l.

file_response

fn file_response(content : Bytes, filename? : String, media_type? : String, status? : Int, inline? : Bool) ->
Response

A file download built from bytes already in hand (← FastAPI's FileResponse).

It takes the content rather than a path because moonapi has no filesystem of its own — the same app runs on wasm, js and native, and only the server knows how to read a file on any of them. What this adds is the envelope: a media type guessed from filename's extension unless media_type names one, Content-Length, and a Content-Disposition that tells the browser to save the file (or, with inline, to display it) under that name. An empty filename leaves the disposition off entirely.

filter_response

fn filter_response(model : Schema, value : Json) -> Result[Json, Array[ValidationError]]

Validate value against model and, if it conforms, return it filtered down to the model's declared fields (extras dropped, nested objects and arrays projected too). On a mismatch return the located ValidationErrors under ["response"] — the same shape request validation produces. This is FastAPI's response_model: the outgoing shape is the model, not whatever the handler happened to build.

greet_app

fn greet_app(container : Container[Dep]) -> App

Build the greet application over a caller-supplied dependency container, so a test can register dependency_overrides on the same container before or between requests. POST /greet resolves the "greeting" dependency, reads a validated GreetReq body, and answers {"message": "<greeting>, <name>"}; a malformed body gets a FastAPI-shaped 422. The dependency scope brackets each request, so any yield teardown runs once the handler returns.

gzip

A GZip middleware: responses at least min_size bytes are re-encoded as gzip when the client sent Accept-Encoding: gzip and the response isn't already content-encoded. Sets Content-Encoding: gzip, updates Content-Length, and adds Vary: Accept-Encoding.

The gzip stream is a complete RFC 1952 container — correct header, CRC-32, and ISIZE — around a real RFC 1951 DEFLATE payload: LZ77 back-references coded with the fixed Huffman table (deflate.mbt), so the body actually shrinks. Dynamic Huffman would tighten the ratio further and is the documented next step.

hmac_sha256

fn hmac_sha256(key : Bytes, msg : Bytes) -> Bytes

HMAC-SHA256 (RFC 2104): a keyed MAC over sha256. A key longer than the 64-byte block is hashed first; a shorter key is zero-padded. The message is authenticated as H((K ⊕ opad) ∥ H((K ⊕ ipad) ∥ msg)). Checked against RFC 4231 test case 2. This is the signature function behind JWT HS256.

html

fn html(status : Int, body : String) ->
Response

An HTML response — what the documentation pages are served as.

http_error

fn http_error(status : Int, detail : String, headers? : Array[(String, String)]) -> HttpException

Build an HttpException with a string detail — the common case — and optional extra headers. raise http_error(404, "Item not found") reads like FastAPI's raise HTTPException(404, "Item not found").

inflate

fn inflate(data : Bytes) -> Bytes

Decode a raw DEFLATE stream (stored and fixed-Huffman blocks). Used to round-trip deflate_encode in the tests; back-references copy byte-by-byte so overlapping (run-length) matches inflate correctly.

json

fn json(status : Int, value : Json) ->
Response

A JSON response serialised from a Json value.

json_model

fn json_model(status : Int, model : Schema, value : Json) ->
Response

A JSON response whose body is value filtered through model. On success a status response carrying only the model's declared fields; on a model mismatch a 500 whose body lists the response-validation errors — the return value didn't match what the route promised, which is a server-side fault.

jwt_sign

fn jwt_sign(claims : Map[String, Json], secret : String) -> String

Sign a claims set as a compact JWT using HS256. The header is fixed to {"alg":"HS256","typ":"JWT"}; claims is serialised as the JSON payload (exp / iat / nbf / sub / scopes go in as ordinary entries); secret is the shared HS256 key. Returns header.payload.signature, each segment base64url-encoded.

jwt_sign_eddsa

fn jwt_sign_eddsa(claims : Map[String, Json], key : Ed25519PrivateKey) -> String

Sign a claims set as a compact JWT using EdDSA (Ed25519, RFC 8037). The header is fixed to {"alg":"EdDSA","typ":"JWT"}; key is the Ed25519 private key. Returns header.payload.signature, each segment base64url-encoded.

jwt_sign_es256

fn jwt_sign_es256(claims : Map[String, Json], key : EcdsaPrivateKey) -> String

Sign a claims set as a compact JWT using ES256 (ECDSA P-256 / SHA-256 with the deterministic RFC 6979 nonce). The header is fixed to {"alg":"ES256","typ":"JWT"}; key is the P-256 private key. Returns header.payload.signature, each segment base64url-encoded.

jwt_sign_rs256

fn jwt_sign_rs256(claims : Map[String, Json], key : RsaPrivateKey) -> String

Sign a claims set as a compact JWT using RS256 (RSASSA-PKCS1-v1_5 + SHA-256). The header is fixed to {"alg":"RS256","typ":"JWT"}; key is the RSA private key. Returns header.payload.signature, each segment base64url-encoded.

jwt_verify

fn jwt_verify(token : String, secret : String, now_secs : Int64) -> Map[String, Json] raise JwtError

Verify a compact HS256 JWT and return its claims. Checks, in order: three segments; header alg is HS256; the HMAC-SHA256 signature matches (compared in constant time); exp (if present) is strictly after now_secs; nbf (if present) is at or before now_secs. now_secs is the verification time as a Unix timestamp in seconds (JWT NumericDate). Raises the matching JwtError on any failure; a tampered payload or signature fails at BadSignature.

jwt_verify_eddsa

fn jwt_verify_eddsa(token : String, key : Ed25519PublicKey, now_secs : Int64) -> Map[String, Json] raise JwtError

Verify a compact EdDSA (Ed25519) JWT and return its claims (RFC 8037). Checks three segments; the header alg is EdDSA; the Ed25519 signature verifies against key; and the exp / nbf time claims. Raises the matching JwtError; a tampered payload or signature fails at BadSignature.

jwt_verify_es256

fn jwt_verify_es256(token : String, key : EcdsaPublicKey, now_secs : Int64) -> Map[String, Json] raise JwtError

Verify a compact ES256 JWT and return its claims. Checks three segments; the header alg is ES256; the ECDSA-P256 / SHA-256 signature (raw r || s, the JWS encoding) verifies against key; and the exp / nbf time claims. Raises the matching JwtError; a tampered payload or signature fails at BadSignature.

jwt_verify_rs256

fn jwt_verify_rs256(token : String, key : RsaPublicKey, now_secs : Int64) -> Map[String, Json] raise JwtError

Verify a compact RS256 JWT and return its claims. Checks three segments; the header alg is RS256; the RSASSA-PKCS1-v1_5 signature verifies against key; and the exp / nbf time claims. Raises the matching JwtError; a tampered payload or signature fails at BadSignature.

oauth2_app

fn oauth2_app(now : () -> Int64, secret? : String) -> App

Build the OAuth2 demo application. secret is the shared HS256 key; now supplies the current Unix time (seconds) for both issuing and verifying, so a caller controls time in tests. Tokens live for one hour.

parse_basic_auth

fn parse_basic_auth(header : String) -> HttpBasicCredentials?

Parse an HTTP Basic Authorization header value into credentials (← FastAPI's HTTPBasic), or None. Matches the Basic scheme case-insensitively (RFC 7617), base64-decodes the rest, and splits on the first colon so a password may itself contain colons.

redirect

fn redirect(url : String, status? : Int) ->
Response

A redirect to url (← FastAPI's RedirectResponse). The body is empty and Location carries the target.

307 is the default because it is the redirect that keeps the request's method and body — the one a POST can safely follow, which 302 historically is not. Use 303 to send a client to a GET after a write, and 301 / 308 for a move that is permanent.

The URL is percent-encoded over the characters a URI reserves for structure, so an already-encoded URL passes through unchanged and a \r\n smuggled into one cannot open a header of its own.

redoc_ui

fn redoc_ui(spec_url? : String, title? : String) -> String

A self-contained ReDoc page rendering the document served at spec_url — the second reading of the same spec FastAPI serves at /redoc, three-panel and built for reading rather than for trying calls out.

rsa_pkcs1_sha256_sign

fn rsa_pkcs1_sha256_sign(msg : Bytes, key : RsaPrivateKey) -> Bytes

RSASSA-PKCS1-v1_5 sign with SHA-256 — the RS256 signing primitive (RFC 8017 §8.2.1): s = EM^d mod n, returned as a fixed-width big-endian octet string.

rsa_pkcs1_sha256_verify

fn rsa_pkcs1_sha256_verify(msg : Bytes, sig : Bytes, key : RsaPublicKey) -> Bool

RSASSA-PKCS1-v1_5 verify with SHA-256 — the RS256 verification primitive (RFC 8017 §8.2.2): recover m = s^e mod n and compare it, in constant time, to the expected EMSA-PKCS1-v1_5 encoding of msg. Rejects a signature that is not the modulus width or is >= n.

schema_of

fn[T : ToSchema] schema_of(x : T) -> Schema

The schema of any ToSchema type, without needing a value of it materialised at the call site beyond the one handed in — the generic entry point.
fn set_cookie(resp :
Response
, name : String, value : String, max_age? : Int, expires? : String, path? : String, domain? : String, secure? : Bool, http_only? : Bool, same_site? : SameSite?) ->
Response

Add a Set-Cookie header to resp (← FastAPI's response.set_cookie), returning the response that carries it; the original is untouched, so a handler can hand the same base response to two callers.

max_age is the lifetime in seconds and expires an HTTP-date; giving neither makes it a session cookie. path defaults to /, and passing "" omits the attribute so the cookie scopes to the request's own directory. http_only keeps it away from scripts, secure keeps it off plaintext connections, and same_site defaults to Lax — the browser default, and the one that stops a cross-site form post from carrying a session.

sha256

fn sha256(msg : Bytes) -> Bytes

SHA-256 (FIPS 180-4): hash an arbitrary byte string to a 32-byte digest. The full message schedule and 64-round compression over 512-bit blocks with the standard length-padding, checked against the NIST vectors for "" and "abc". The building block for hmac_sha256, and through it JWT HS256.

sha512

fn sha512(msg : Bytes) -> Bytes

SHA-512 (FIPS 180-4). Core ships no such hash, so it is hand-written like sha256 but over 64-bit words with 80 rounds. It is the digest Ed25519 signs over. Messages here are far under 2^64 bits, so the 128-bit length field's high half is always zero.

sse_response

fn sse_response(events : Array[ServerSentEvent], status? : Int, headers? : Array[(String, String)]) ->
StreamingResponse

A text/event-stream response carrying events, one frame per chunk — hand it to App::stream and each reaches the client on its own. Sets Cache-Control: no-cache and Connection: keep-alive, the headers an SSE endpoint sends so intermediaries don't buffer or close the stream.

swagger_ui

fn swagger_ui(spec_url? : String, title? : String) -> String

A self-contained Swagger UI page rendering the document served at spec_url.

text

fn text(status : Int, body : String) ->
Response

A plain-text response.

token_response

fn token_response(access_token : String) ->
Response

The 200 token response body {"access_token": ..., "token_type": "bearer"} — the OAuth2 password-grant reply FastAPI's token endpoint returns.

unprocessable

A 422 Unprocessable Entity response whose application/json body lists the validation errors, exactly as FastAPI reports a failed request.

validate_schema

fn validate_schema(schema : Schema, value : Json, loc : Array[String], errs : Array[ValidationError]) -> Unit

Validate a JSON value against the descriptor schema, appending FastAPI-shaped errors to errs (located at loc). This is what "the descriptor drives validation" means: the very tree that emits the OpenAPI body schema also decides whether an inbound body conforms — one source of truth, exactly as pydantic derives both from one model. A named object is validated against its inline fields, so no $ref resolution is needed here.

validation_error_body

fn validation_error_body(errors : Array[ValidationError]) -> Json

The {"detail": [ ... ]} body FastAPI returns when request validation fails, built from a list of ValidationErrors.

with_constraints

fn with_constraints(j : Json, constraints : Array[Constraint], version : OpenApiVersion) -> Json

Merge the constraints into a scalar/array schema object for OpenAPI emission. exclusiveMinimum / exclusiveMaximum are numeric under OpenAPI 3.1 (JSON-Schema 2020-12) but a boolean flag alongside minimum / maximum under Swagger 2.0 and OpenAPI 3.0, so the form is version-aware.