moonapi

    moonapi — a typed web framework for MoonBit (\u2190 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 scopes, per-operation security enforcement, multipart and urlencoded form extractors, response_model filtering, background tasks, sub-application mounting, a CORS/gzip/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
    Version
    0.13.1
    License
    Apache-2.0
    Last updated
    2 hours ago
    Downloads
    169

    #moonapi

    A typed web framework for MoonBit — ← FastAPI.

    Check and Test License mooncakes

    Moved on mooncakes from Lfan-ke/moonapi to moonbitstack/moonapi.

    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)

    • Routing — App::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.
    • Routers — Router 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 OpenAPI — App::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 extractors — Context::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 token itself is mooncred's and the algorithms are mooncrypt's; moonapi names HS256 and hands over a key. alg: "none" cannot be expressed, the verifier's algorithm is the one that counts rather than the token's, and signatures compare in constant time — all of that is mooncred's to guarantee, and it does.
    • Form & file extractors — Context::form parses both an application/x-www-form-urlencoded body (percent- and +-decoded) and a multipart/form-data body, splitting the boundary stream into fields and byte-exact uploads (filename + content-type + size + the part's own headers + raw bytes). Reading the body is moonhttp/mime's; a body is attacker-controlled and already buffered, so @mime.Limits bounds what one may spend — a thousand parts of a megabyte 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_model — filter_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(...) (moonzip's compressor), 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 mounting — App::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 responses — App::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 Events — moonhttp/sse frames an event 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 routes — App::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.
    • Documentation UI — App::enable_docs() mounts /openapi.json, /docs and /redoc, all three with include_in_schema=false so turning documentation on does not change the document. swagger_ui() and redoc_ui() return the pages alone, for an app that serves them under its own paths or behind its own guard.
    • Lifespan hooks — App::on_startup / App::on_shutdown (← FastAPI's lifespan), driven over the ASGI lifespan protocol. Startup in registration order, shutdown in reverse, so a resource is released before whatever opened it. App::lifespan_handler runs the same thing in-process, for a test with no socket.
    • Responses — text, 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.
    • Conditional and partial serving — Context::serve(content, ...) is file_response with the request weighed: 304 when the client's copy is current, 412 when a precondition fails, 206 for a range (one Content-Range, or multipart/byteranges for several), 416 for a range that is not there. This is what makes a download resumable. The entity-tags, the five precondition fields and the range arithmetic are moonhttp's conditional and range, so it is RFC 9110 §13 and §14 rather than a guess. Everything turns on etag=; left out, one is hashed from the content, which costs a pass over the body — pass @conditional.stamp(length~, modified~) for one stat's worth instead.
    • Data URLs — data_response(url) serves what a data: URL carries, under the media type it names; data_url(content, media_type=) writes one. This is what canvas.toDataURL() and FileReader.readAsDataURL() hand back, and how a client posts an image inside a JSON field. Not a data URL yields None, so a handler tells "no picture" from "not a picture" without touching base64.
    • Cookies — set_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. expires is a @moondate.Moment, because an expiry is a moment and the format says how to write one; delete_cookie expires by both Max-Age=0 and the epoch. Context::cookie reads them back. The cookie itself — both headers, each read and written, and the stripping of the octets RFC 6265 forbids so a value cannot forge an attribute or open a header of its own — is moonhttp's cookie package, which a client uses from the other side.
    • 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.

    #Configuration

    Nothing here decides for you twice. Every bound is an argument with a published default, and every default is the one the mainstream framework uses.

    ctx.query("tag") // the preset bound
    ctx.query("tag", limits=@mime.Limits::new(parts=100000))
    ctx.form(limits=@mime.Limits::new(part_size=8 << 20)) // an upload endpoint

    create_access_token(sub, secret, now, extra={ "tenant": Json::string("acme") })
    sse_response(events, headers=[("cache-control", "no-store")])
    sse_response(events, space=false)

    SettingDefaultWhy that one
    query_limits.parts1000qs, and therefore Express, allows a thousand query parameters
    query_limits.part_size64 KiBWell past the 8 KiB the common servers allow a whole request line
    form's limits@mime.limitsStarlette's max_files and max_fields, with python-multipart's part size
    expires_in_secs3600The hour every OAuth2 example issues
    space on a streamonThe space after a colon is universal on the wire and stripped by every reader

    #Where a setting can arrive twice

    create_access_token computes sub, iat, exp and scopes from its arguments, and extra adds claims alongside them. sse_response sets the three headers a stream needs, and headers adds its own. Where the two name the same thing, the rule is published rather than implied:

    FunctionWho wins by defaultWhat happens when both speak
    create_access_tokenthe argumentsaborts — a token whose subject is not the subject passed in is a mistake in the program
    sse_responsethe caller's headerreplaces ours, so a response never carries two content-type headers

    Both take wins to turn the direction around and clash to choose between merging in silence, aborting, and handing the decision to a callback of your own. They are the same wins and clash mooncred uses, with the same meanings.

    #Design notes

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

    • GZip is moonzip's, whose output zlib reads and whose reader takes what zlib writes. What lives here is the middleware around it: when to compress, and which headers to set.
    • 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, the Form / File extractors, and response_model filtering. On the middleware side: CORS, gzip, exception and per-status handlers, Server-Sent Events, and WebSocket routes.

    0.9.0 is where moonapi stopped being several libraries at once. The cryptography went to mooncrypt, the token format to mooncred, the wire formats to moonhttp, the compression to moonzip, and the JSON to moonjson — 2 329 lines out of the framework and into libraries anything can use, with every existing test still passing. What is left is routing, extraction, validation, OpenAPI, injection and middleware, which is what a web framework is.

    0.13.1 finished the file story. Context::serve weighs the conditional fields and the Range before answering, so a download resumes and a repeat request costs a header rather than a body. The tags, the fields and the arithmetic are moonhttp's; the four answers and the envelope are here.

    Next: splitting what remains into packages, so a program that wants the router does not link the OpenAPI writer; static files and templates; and codegen'd request schemas via moonctl.

    #License

    Apache-2.0.

    ApiHandler

    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

    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).

    One cookie, as Set-Cookie states it.

    An attribute left None is left out of the header, and means what leaving it out means: no Expires and no Max-Age is a session cookie, no Domain is the origin host alone, no Path is the request's own directory.

    Event

    One event on the wire.

    A field left None is left out of the frame. data carrying newlines goes out as several data: lines and comes back joined, which is the format's own rule and the reason a payload with a blank line in it cannot be sent as one.

    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".

    Form

    A parsed form: its text fields and its files.

    Limits

    What a form is allowed to be.

    A body arrives from whoever sent it, so both bounds are needed: a million empty parts and one enormous part are different ways of asking a server to allocate more than it has.

    SameSite

    How far a cross-site request may carry a cookie.

    Strict sends it only on a same-site request, Lax also on a top-level navigation, and Unrestricted sends it everywhere. Unrestricted is the wire value None, spelled differently here because None is the empty option; a browser honours it only on a Secure cookie.

    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.

    Upload

    A file from a multipart/form-data body.

    The content is held whole. Streaming a part to disk instead of into memory is what limits exists to make unnecessary until it is built.

    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.

    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::equal

    fn Address::equal(Address, Address) -> Bool

    Address::not_equal

    fn Address::not_equal(x : Address, y : Address) -> Bool

    Address::schema

    fn Address::schema() -> Schema

    The Address descriptor.

    Address::to_json

    fn Address::to_json(Address) -> Json

    Address::to_schema

    fn Address::to_schema(_self : Address) -> Schema

    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).

    ApiKeyIn::equal

    fn ApiKeyIn::equal(ApiKeyIn, ApiKeyIn) -> Bool

    ApiKeyIn::not_equal

    fn ApiKeyIn::not_equal(x : ApiKeyIn, y : ApiKeyIn) -> Bool

    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

    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).

    Constraint::equal

    fn Constraint::equal(Constraint, Constraint) -> Bool

    Constraint::not_equal

    fn Constraint::not_equal(x : Constraint, y : Constraint) -> Bool

    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. Spaces around a name or a value are the separators'; 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

    The posted form — FastAPI's Form(...) and File(...) parameters.

    Reading the body is moonhttp/mime's job; what belongs here is knowing that a request has a Content-Type and handing it over. None means the form broke its bounds and was refused whole: a handler given the first thousand parts of a larger form would be answering a request nobody sent. An empty form is the other answer, and means the request carried none.

    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, limits? :
    Limits
    ) -> 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, limits? :
    Limits
    ) -> 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.

    Context::serve

    fn Context::serve(self : Context, content : Bytes, filename? : String, media_type? : String, inline? : Bool, etag? :
    Tag
    , modified? :
    Moment
    , ranges? : Bool) ->
    Response

    [file_response] with the request weighed first (RFC 9110 §13, §14):

    • 304 — the client's copy is current.
    • 412 — a precondition it stated does not hold.
    • 206 — it asked for part, one Content-Range or a multipart/byteranges.
    • 416 — it asked for what is not there.

    Everything turns on etag. Left out, one is hashed from the content, which costs a pass over the body; pass @conditional.stamp(length~, modified~) for one stat's worth instead, or any tag of your own.

    ranges=false withdraws the offer in Accept-Ranges.

    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.

    Dep::equal

    fn Dep::equal(Dep, Dep) -> Bool

    Dep::not_equal

    fn Dep::not_equal(x : Dep, y : Dep) -> Bool

    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.

    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::equal

    fn Endpoint::equal(Endpoint, Endpoint) -> Bool

    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::not_equal

    fn Endpoint::not_equal(x : Endpoint, y : Endpoint) -> Bool

    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::equal

    fn Field::equal(Field, Field) -> Bool

    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.

    Field::not_equal

    fn Field::not_equal(x : Field, y : Field) -> Bool

    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::equal

    fn GreetReq::equal(GreetReq, GreetReq) -> Bool

    GreetReq::not_equal

    fn GreetReq::not_equal(x : GreetReq, y : GreetReq) -> Bool

    GreetReq::schema

    fn GreetReq::schema() -> Schema

    The GreetReq descriptor — one required string field.

    GreetReq::to_json

    fn GreetReq::to_json(GreetReq) -> Json

    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.

    Method::equal

    fn Method::equal(Method, Method) -> Bool

    Method::not_equal

    fn Method::not_equal(x : Method, y : Method) -> Bool

    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::equal

    fn NewUser::equal(NewUser, NewUser) -> Bool

    NewUser::not_equal

    fn NewUser::not_equal(x : NewUser, y : NewUser) -> Bool

    NewUser::schema

    fn NewUser::schema() -> Schema

    The NewUser descriptor.

    NewUser::to_json

    fn NewUser::to_json(NewUser) -> Json

    NewUser::to_schema

    fn NewUser::to_schema(_self : NewUser) -> Schema

    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.

    OAuth2PasswordRequestForm::equal

    OAuth2PasswordRequestForm::not_equal

    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.

    ObjectSchema::equal

    ObjectSchema::not_equal

    fn ObjectSchema::not_equal(x : ObjectSchema, y : ObjectSchema) -> Bool

    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.

    OpenApiVersion::equal

    OpenApiVersion::not_equal

    fn OpenApiVersion::not_equal(x : OpenApiVersion, y : OpenApiVersion) -> Bool

    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::equal

    fn Param::equal(Param, Param) -> Bool

    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")).

    Param::not_equal

    fn Param::not_equal(x : Param, y : Param) -> Bool

    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.

    ParamLoc::equal

    fn ParamLoc::equal(ParamLoc, ParamLoc) -> Bool

    ParamLoc::not_equal

    fn ParamLoc::not_equal(x : ParamLoc, y : ParamLoc) -> Bool

    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::equal

    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.

    ResponseSpec::not_equal

    fn ResponseSpec::not_equal(x : ResponseSpec, y : ResponseSpec) -> Bool

    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.

    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)
    SJson(Json)
    } 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::equal

    fn Schema::equal(Schema, Schema) -> Bool

    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::json

    fn Schema::json(document : Json) -> Schema

    A schema written as JSON Schema itself (2020-12, which is what OpenAPI 3.1 is). Use it for what the other constructors cannot say; it goes into the document unchanged.

    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::not_equal

    fn Schema::not_equal(x : Schema, y : Schema) -> Bool

    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::equal

    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"]).

    SecurityRequirement::not_equal

    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.

    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::equal

    fn User::equal(User, User) -> Bool

    User::not_equal

    fn User::not_equal(x : User, y : User) -> Bool

    User::schema

    fn User::schema() -> Schema

    The User descriptor.

    User::to_json

    fn User::to_json(User) -> Json

    User::to_schema

    fn User::to_schema(_self : User) -> Schema

    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.

    WsMessage::equal

    fn WsMessage::equal(WsMessage, WsMessage) -> Bool

    WsMessage::not_equal

    fn WsMessage::not_equal(x : WsMessage, y : WsMessage) -> Bool

    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.

    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.

    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], wins? :
    Wins
    , clash? :
    OnClash
    [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.

    extra adds claims. Where it names one the arguments already set — sub, iat, exp, scopes — the arguments win and saying it twice aborts, because a token whose subject is not the subject passed in is a mistake in the program rather than a choice about configuration. wins=Extra hands the decision to extra, and clash can silence it or take a callback.

    data_response

    fn data_response(url : String, filename? : String, status? : Int, inline? : Bool) ->
    Response
    ?

    Serve what a data: URL carries (RFC 2397), under the media type it names — what canvas.toDataURL() and FileReader.readAsDataURL() hand back.

    None when it is not a data URL, so a handler tells "no picture" from "not a picture" without touching base64.

    data_url

    fn data_url(content : Bytes, media_type? : String) -> String

    The content as a data: URL, for embedding it instead of costing a second request.
    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.

    file_response

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

    A download (← FastAPI's FileResponse): a media type from filename's extension unless media_type names one, Content-Length, and a Content-Disposition. No filename, no disposition.

    Bytes rather than a path: the same app runs on four backends and only the server can read a file on any of them.

    Always the whole thing. [Context::serve] weighs the request first.

    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.

    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").

    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.

    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.

    query_limits

    How much of a query string is a query string.

    A thousand parameters is what qs allows Express by default, and far past anything a link carries; sixty-four kilobytes for one value is well past the eight the common servers allow a whole request line. Beyond either, the request is an attempt to make the server allocate.

    An endpoint that genuinely takes more says so: ctx.query("tag", limits=@mime.Limits::new(parts=100000)).

    redirect

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

    A redirect to url (← FastAPI's RedirectResponse).

    307 keeps the method and body, which 302 historically does not. Use 303 after a write, 301 / 308 for a permanent move.

    The URL is escaped as a whole URI reference, so an encoded one is not encoded twice and a \r\n 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.

    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? :
    Moment
    , 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 the moment it dies; giving neither makes it a session cookie. path defaults to /, and None 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.

    sse_response

    fn sse_response(events : Array[
    Event
    ], status? : Int, headers? : Array[(String, String)], space? : Bool, wins? :
    Wins
    , clash? :
    OnClash
    [Array[(String, String)]]) ->
    StreamingResponse

    A text/event-stream response, one frame to a chunk.

    A chunk per frame is the whole point: a stream that arrives as one body is not a stream, it is a file shaped like one. A client dispatches an event when it reads that event's blank line, so the frames have to reach it separately for anything to happen before the last one is written.

    Framing an event is moonhttp/sse's job. What belongs here is the response around the frames, and the three headers that stop an intermediary buffering or closing the stream.

    The three headers a stream needs are set here. One named in headers too replaces ours rather than joining it, because a response carrying two content-type headers is not a response with a choice in it. wins=Base keeps ours, and clash can abort or take a callback instead — worth setting when overriding content-type, which stops the stream being a 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.