Sign in

    pony

    Lightweight HTTP web framework and router for MoonBit, inspired by Chi

    web
    framework
    http
    router
    server
    middleware
    rest
    api
    json
    auth
    jwt
    cors
    Download zip
    Author
    Version
    0.5.0
    License
    Apache-2.0
    Last updated
    15 days ago
    Downloads
    75

    #jaredzhou/pony

    A lightweight HTTP web framework for MoonBit, inspired by Go's Chi.

    #Installation

    moon add jaredzhou/pony # latest moon add jaredzhou/pony@0.3.0 # pin version

    A complete example project is available at moon-examples/todo.

    #Quick Start

    ///|
    fn main {
    let r = Router::Router()

    // GET /hello => 200 "Hello, world!"
    r.add(HttpMethod::Get, "/hello", ctx => {
    ctx.write_text(status_ok, "Hello, world!")
    })

    // GET /greet/pony => 200 "Hello, pony!"
    r.add(HttpMethod::Get, "/greet/{name}", ctx => {
    let name = ctx.try_param("name").unwrap_or("stranger")
    ctx.write_text(status_ok, "Hello, \(name)!")
    })

    start("127.0.0.1:3000", r)
    }

    #Features

    #Router

    The router is a radix tree with priority matching. Routes can be registered for specific HTTP methods or all methods via any().

    let r = Router::Router()

    // Static route — exact path match
    r.add(HttpMethod::Get, "/ping", pong_handler)

    // Path parameter {param} — matches a single segment
    r.add(HttpMethod::Get, "/users/{id}", user_handler)

    // Wildcard * — matches everything after the prefix
    r.add(HttpMethod::Get, "/static/*", static_handler)

    // Multiple parameters in one pattern
    r.add(HttpMethod::Get, "/users/{userId}/posts/{postId}", post_handler)

    // Match all HTTP methods
    r.any("/health", health_handler)

    // Route-specific middleware (applied before the handler)
    r.add(
    HttpMethod::Get,
    "/admin",
    mws=[admin_auth],
    admin_handler,
    )

    // Sub-router mounting — all routes under /api/v1
    let api = Router::Router()
    api.add(HttpMethod::Get, "/status", status_handler)
    r.mount("/api/v1", api)

    // Custom 404 / 405 handlers
    r.set_not_found(ctx => {
    ctx.reply_error(ApiError::not_found("page not found"))
    })
    r.set_method_not_allowed(ctx => {
    ctx.write_json(status_method_not_allowed, { "error": "method not allowed" })
    })

    MethodDescription
    r.add(method, pattern, handler)Add a route for a specific HTTP method
    r.any(pattern, handler)Match all HTTP methods
    r.mount(prefix, sub_router)Mount a sub-router under a prefix
    r.use_mw(middleware)Add global middleware
    r.set_not_found(handler)Custom 404 handler
    r.set_method_not_allowed(handler)Custom 405 handler

    Route patterns:

    PatternExampleMatches
    Static/ping/ping only
    Param {name}/users/{id}/users/42, /users/alice
    Wildcard */static/*/static/css/app.css, /static/js/main.js

    #Server

    let server = @pony.Server::new("127.0.0.1:3000", router)
    .with_timeout(read=5000, write=10000)
    server.start()!

    #Request Context

    Every accessor comes in two forms: a raising version and a try_ variant that returns Option.

    Raising versions are generic over the @pony.FromStr trait — annotate the target type and the value is parsed for you. A missing key raises MissingParam/MissingQuery/MissingHeader/MissingForm; a present value that fails to parse raises InvalidValue (mapped to HTTP 400). Catch and pass directly to reply_error:

    ///|
    let id : Int = ctx.param("id") catch {
    e => {
    ctx.reply_error(e)
    return
    }
    }

    ///|
    let token : String = ctx.header("Authorization") catch {
    e => {
    ctx.reply_error(e)
    return
    }
    }

    ///|
    let page : Int = ctx.query("page") catch { _ => 1 }

    Built-in FromStr impls: String (identity), Int, Int64, UInt, UInt64, Double, Bool. Implement it for your own types to use them with the typed accessors:

    ///|
    struct UserId(Int)

    ///|
    pub impl @pony.FromStr for UserId with fn from_str(s) {
    UserId(@pony.FromStr::from_str(s))
    }

    ///|
    let uid : UserId = ctx.param("id") catch { ... }

    Use the try_ variant when a default fallback makes sense — it always returns the raw String?:

    ///|
    let page = ctx.try_param("page").unwrap_or("1")

    ///|
    let q = ctx.try_query("q").unwrap_or("")

    ///|
    let name = ctx.try_form("name").unwrap_or("")

    JSON body:

    ///|
    struct LoginReq {
    username : String
    password : String
    } derive(FromJson)

    ///|
    let req : LoginReq = ctx.json() catch {
    _ => {
    ctx.reply_error(ApiError::invalid_argument("invalid JSON body"))
    return
    }
    }

    Response helpers:

    ctx.set_content_type("application/json")
    ctx.write_text(status_ok, "Hello")
    ctx.write_json(status_ok, {"key": "value"})
    ctx.reply_ok({"status": "ok"}) // 200 JSON
    ctx.reply_error(ApiError::invalid_argument("bad input")) // 400 JSON
    ctx.reply_error(ApiError::not_found("not found")) // 404 JSON
    ctx.reply_error(ApiError::permission_denied("not allowed")) // 403 JSON
    ctx.redirect("/login") // 302
    ctx.no_content() // 204

    Raising versionOption / non-raising versionDescription
    ctx.param("id") → Tctx.try_param("id") → String?Path parameter — T : FromStr
    ctx.query("q") → Tctx.try_query("q") → String?Query string value — T : FromStr
    ctx.header("Accept") → Tctx.try_header("Accept") → String?Request header — T : FromStr
    ctx.form("name") → Tctx.try_form("name") → String?Form field (async) — T : FromStr
    ctx.wildcard() → Stringctx.try_wildcard() → String?Wildcard path capture
    ctx.json[T]() → T—JSON body deserialization — T : FromJson
    ctx.form_file("file") → FileHeaderctx.try_form_file("file") → FileHeader?Uploaded file header

    #Extension Store

    Type-safe key-value store for passing request-scoped data between middleware and handlers. Keys are marker types (empty structs), values are inferred from usage.

    Custom auth middleware example:

    ///|
    // Marker type — an empty struct used as a type-safe key
    struct UserId {}

    ///|
    // Middleware: extract user_id from header and store in context
    fn auth_middleware(next : Handler) -> Handler {
    ctx => {
    match ctx.try_header("X-User-Id") {
    Some(user_id) => ctx.set_ext(UserId{}, user_id)
    None => {
    ctx.reply_error(ApiError::unauthenticated("missing X-User-Id header"))
    return
    }
    }
    next(ctx)
    }
    }

    ///|
    fn main {
    let r = Router::Router()
    r.use_mw(auth_middleware)

    r.add(HttpMethod::Get, "/me", ctx => {
    let user_id : String = ctx.get_ext(UserId{}) catch {
    e => {
    ctx.reply_error(e)
    return
    }
    }
    ctx.reply_ok({ "user_id": user_id })
    })

    start("127.0.0.1:3000", r)
    }

    Built-in markers (from @pony):

    ctx.set_ext(@pony.RequestId{}, "req-abc")
    ctx.set_ext(@pony.UserId{}, "user-42")

    let uid = ctx.get_ext(@pony.UserId{}) catch {
    e => {
    ctx.reply_error(e)
    return
    }
    }

    MethodDescription
    ctx.set_ext(marker, value)Store a typed value
    ctx.get_ext(marker)Retrieve, raises PonyError::MissingExt if absent
    ctx.try_get_ext(marker)Retrieve, returns Option
    ctx.remove_ext(marker)Remove a stored value

    #Middleware

    Built-in via jaredzhou/pony/mw:

    r.use_mw(@mw.logger())
    r.use_mw(@mw.cors(
    allow_origins=["*"],
    allow_methods=["GET", "POST"],
    ))
    r.use_mw(@mw.jwt(new_hmac_sha256(secret)))

    MiddlewareDescription
    logger()Request logging (method, path, status, duration)
    cors()CORS headers with configurable origins, methods, headers, max-age
    jwt(signing_method)JWT bearer token auth, stores sub claim via set_ext(UserId, …)

    #Error handling

    PonyError covers only errors raised by the framework itself — the context accessors. It implements ToApiError, so you can pass it directly to reply_error:

    ///|
    pub suberror PonyError {
    MissingParam(String)
    MissingQuery(String)
    MissingForm(String)
    MissingHeader(String)
    MissingExt(String)
    ExtDecodeError(String, String)
    MissingFormFile(String)
    InvalidValue(String, String)
    }

    For business errors, construct an ApiError directly with one of the 16 convenience constructors — one per canonical error code (cancelled, unknown, invalid_argument, deadline_exceeded, not_found, already_exists, permission_denied, resource_exhausted, failed_precondition, aborted, out_of_range, unimplemented, internal, unavailable, data_loss, unauthenticated):

    ctx.reply_error(ApiError::invalid_argument("invalid id"))
    ctx.reply_error(ApiError::not_found("list not found"))
    ctx.reply_error(ApiError::permission_denied("access denied"))

    Custom error types — implement ToApiError for your own error types:

    ///|
    pub impl @pony.ToApiError for MyError with fn to_api_error(self : MyError) -> @pony.ApiError {
    match self {
    MyError::NotFound(m) => @pony.ApiError::not_found(m)
    MyError::Forbidden(m) => @pony.ApiError::permission_denied(m)
    }
    }

    // Now you can pass MyError directly:
    ctx.reply_error(my_error)

    #File Upload

    Handle multipart file uploads with parse_multipart_form, then access files via form_file and fields via form:

    ///|
    r.add(HttpMethod::Post, "/upload", async ctx => {
    // Parse the multipart body (call once per request)
    ctx.parse_multipart_form()!

    // Read regular form fields
    let title : String = ctx.form("title") catch {
    e => { ctx.reply_error(e); return }
    }

    // Access uploaded files
    let file = ctx.form_file("avatar") catch {
    e => { ctx.reply_error(e); return }
    }

    // File metadata available immediately
    println("received: \{file.filename} (\{file.size} bytes, \{file.content_type})")

    // Read file content (async)
    let data = file.bytes()

    ctx.reply_ok({
    "title": title,
    "filename": file.filename,
    "size": file.size,
    })
    })

    MethodReturnsDescription
    ctx.parse_multipart_form()UnitParse multipart body (call before accessing files/fields)
    ctx.form("key")T raise PonyErrorForm field value, parsed via FromStr (use try_form for raw String?)
    ctx.form_file("key")FileHeader raise PonyErrorSingle uploaded file (use try_form_file for Option)
    ctx.form_files("key")Array[FileHeader]All files for a multi-file field
    file.bytes()BytesRead full file content
    file.filenameStringOriginal filename
    file.sizeInt64Uncompressed file size
    file.content_typeString?MIME type (e.g. "image/png")
    file.path()String?Temp file path if spilled to disk

    #License

    Apache-2.0

    Closer

    pub trait Closer {
    fn close(Self) -> Unit
    }

    Close the resource. Safe to call multiple times.

    FromStr

    pub(open) trait FromStr {
    fn from_str(String) -> Self raise
    }

    Parses a raw request value (path param, query, header, form field) into a typed value. Used as the constraint of the typed accessors Context::param, Context::query, Context::header and Context::form, so callers pick the target type at the call site:

    let id : Int = ctx.param("id") // parse failure -> InvalidValue (400)

    let name : String = ctx.param("name") // identity, never fails

    This is a local mirror of @string.FromStr (which lacks a String impl that pony cannot add from outside). Implement it for your own types to use them with the typed accessors:

    pub impl @pony.FromStr for MyType with fn from_str(s) {
    ...
    }

    Example

    test {
    let n : Int = @pony.FromStr::from_str("42") catch { _ => -1 }
    inspect(n, content="42")
    let b : Bool = @pony.FromStr::from_str("true") catch { _ => false }
    inspect(b, content="true")
    }
    impl FromStr for Bool
    impl FromStr for Int
    impl FromStr for Int64
    impl FromStr for UInt
    impl FromStr for UInt64
    impl FromStr for Double
    impl FromStr for String

    ReaderCloser

    pub trait ReaderCloser :
    Reader
    + Closer {
    }

    A reader that can be closed.

    ToApiError

    pub(open) trait ToApiError {
    fn to_api_error(Self) -> ApiError
    }

    Types that can be converted to an ApiError for use with reply_error. reply_error accepts any T : ToApiError, so you can pass PonyError, ApiError, or your own error types directly.

    WriterCloser

    pub trait WriterCloser :
    Writer
    + Closer {
    }

    A writer that can be closed.

    ExtError

    pub(all) suberror ExtError {
    Missing
    DecodeError(key~ : String, msg~ : String)
    } derive(Eq, ToJson,
    Debug
    )

    ExtError::equal

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

    ExtError::not_equal

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

    ExtError::to_json

    fn ExtError::to_json(ExtError) -> Json

    ExtError::to_repr

    MultipartError

    pub(all) suberror MultipartError {
    MissingBoundary
    MissingName
    MalformedBody(String)
    FileTooLarge(String, Int64)
    FileIOError(String)
    } derive(
    Debug
    )

    Errors that may occur during multipart parsing.

    PonyError

    pub(all) suberror PonyError {
    MissingParam(String)
    MissingQuery(String)
    MissingForm(String)
    MissingHeader(String)
    MissingExt(String)
    ExtDecodeError(String, String)
    MissingFormFile(String)
    InvalidValue(String, String)
    } derive(ToJson,
    Debug
    )

    Errors raised by the framework itself, from the context accessor methods (param, query, form, header, form_file, get_ext, wildcard) when a value is missing or cannot be parsed into the requested type. Business errors are not part of PonyError — construct an ApiError directly with one of its convenience constructors, e.g. ApiError::not_found(msg).

    PonyError::to_api_error

    fn PonyError::to_api_error(self : PonyError) -> ApiError

    PonyError::to_json

    fn PonyError::to_json(PonyError) -> Json

    RouterError

    pub suberror RouterError {
    InvalidPattern(http_method~ : String, path~ : String, reason~ : String)
    } derive(ToJson,
    Debug
    )

    impl Show for RouterError

    RouterError::output

    fn RouterError::output(self : RouterError, logger : &Logger) -> Unit

    RouterError::to_json

    fn RouterError::to_json(RouterError) -> Json

    RouterError::to_string

    fn RouterError::to_string(self : RouterError) -> String

    ApiError

    pub(all) struct ApiError {
    code : Int
    message : String
    details : Array[Map[String, String]]?
    } derive(ToJson,
    Debug
    )

    ApiError::aborted

    fn ApiError::aborted(msg : String) -> ApiError

    ApiError::already_exists

    fn ApiError::already_exists(msg : String) -> ApiError

    ApiError::cancelled

    fn ApiError::cancelled(msg : String) -> ApiError

    Convenience constructors for the 16 canonical ApiError codes. Each one is equivalent to ApiError::new(<code>, msg) with the code baked in, so the code cannot be mistyped. For custom codes, keep using ApiError::new together with register.

    ApiError::data_loss

    fn ApiError::data_loss(msg : String) -> ApiError

    ApiError::deadline_exceeded

    fn ApiError::deadline_exceeded(msg : String) -> ApiError

    ApiError::failed_precondition

    fn ApiError::failed_precondition(msg : String) -> ApiError

    ApiError::internal

    fn ApiError::internal(msg : String) -> ApiError

    ApiError::invalid_argument

    fn ApiError::invalid_argument(msg : String) -> ApiError

    ApiError::new

    fn ApiError::new(code : Int, message : String) -> ApiError

    ApiError::not_found

    fn ApiError::not_found(msg : String) -> ApiError

    ApiError::out_of_range

    fn ApiError::out_of_range(msg : String) -> ApiError

    ApiError::permission_denied

    fn ApiError::permission_denied(msg : String) -> ApiError

    ApiError::resource_exhausted

    fn ApiError::resource_exhausted(msg : String) -> ApiError

    ApiError::to_api_error

    fn ApiError::to_api_error(self : ApiError) -> ApiError

    ApiError::to_http_status

    fn ApiError::to_http_status(code : Int) -> Int

    ApiError::to_json

    fn ApiError::to_json(ApiError) -> Json

    ApiError::to_repr

    ApiError::unauthenticated

    fn ApiError::unauthenticated(msg : String) -> ApiError

    ApiError::unavailable

    fn ApiError::unavailable(msg : String) -> ApiError

    ApiError::unimplemented

    fn ApiError::unimplemented(msg : String) -> ApiError

    ApiError::unknown

    fn ApiError::unknown(msg : String) -> ApiError

    Context

    pub(all) struct Context {
    http_method : HttpMethod
    url_str : String
    url :
    URL

    route_path : String
    req_headers : HttpHeaders
    resp_headers : HttpHeaders
    queries : Values
    form_values : Values?
    multipart_form : MultipartForm?
    exts : ExtStore
    path_params : Array[ParamKV]
    req_body : &
    Reader

    resp_writer :
    ServerConnection

    }

    impl Show for Context

    Context::cleanup

    async fn Context::cleanup(self : Context) -> Unit

    Context::form

    async fn[T : FromStr] Context::form(self : Context, key : String) -> T raise PonyError

    Context::form_file

    fn Context::form_file(self : Context, key : String) -> FileHeader raise PonyError

    Context::form_files

    fn Context::form_files(self : Context, key : String) -> Array[FileHeader]

    Context::get_ext

    fn[K, V] Context::get_ext(self : Context, _marker : K) -> V raise PonyError

    Context::header

    fn[T : FromStr] Context::header(self : Context, key : String) -> T raise PonyError

    Context::json

    Context::no_content

    async fn Context::no_content(self : Context) -> Unit

    Context::output

    fn Context::output(self : Context, out : &Logger) -> Unit

    Context::param

    fn[T : FromStr] Context::param(self : Context, name : String) -> T raise PonyError

    Context::parse_multipart_form

    async fn Context::parse_multipart_form(self : Context, max_memory? : Int64, writers? : Map[String, FileWriter]) -> Unit

    Context::query

    fn[T : FromStr] Context::query(self : Context, key : String) -> T raise PonyError

    Context::redirect

    async fn Context::redirect(self : Context, url : String, http_status? : Int, msg? : String) -> Unit

    Context::remove_ext

    fn[K] Context::remove_ext(self : Context, _marker : K) -> Unit

    Context::reply_error

    async fn[T : ToApiError] Context::reply_error(self : Context, e : T) -> Unit

    Context::reply_ok

    async fn[T : ToJson] Context::reply_ok(self : Context, value : T) -> Unit

    Context::set_content_type

    fn Context::set_content_type(self : Context, ct : String) -> Unit

    Context::set_ext

    fn[K, V] Context::set_ext(self : Context, _marker : K, v : V) -> Unit

    Context::set_header

    fn Context::set_header(self : Context, key : String, value : String) -> Unit

    Context::to_string

    fn Context::to_string(self : Context) -> String

    Context::try_form

    async fn Context::try_form(self : Context, key : String) -> String?

    Context::try_form_file

    fn Context::try_form_file(self : Context, key : String) -> FileHeader?

    Context::try_get_ext

    fn[K, V] Context::try_get_ext(self : Context, _marker : K) -> V?

    Context::try_header

    fn Context::try_header(self : Context, key : String) -> String?

    Context::try_param

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

    Context::try_query

    fn Context::try_query(self : Context, key : String) -> String?

    Context::try_wildcard

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

    Context::wildcard

    fn Context::wildcard(self : Context) -> String raise PonyError

    Context::write_bytes

    async fn Context::write_bytes(self : Context, http_status : Int, msg? : String, body : Bytes) -> Unit

    Context::write_json

    async fn[T : ToJson] Context::write_json(self : Context, http_status : Int, msg? : String, v : T) -> Unit

    Context::write_text

    async fn Context::write_text(self : Context, http_status : Int, msg? : String, text : String) -> Unit

    Endpoint

    Endpoint::to_repr

    ExtStore

    pub(all) struct ExtStore(Array[(String,
    Any
    )])

    ExtStore::get

    fn[K, V] ExtStore::get(self : ExtStore, _marker : K) -> V raise ExtError

    ExtStore::new

    fn ExtStore::new() -> ExtStore

    ExtStore::remove

    fn[K] ExtStore::remove(self : ExtStore, _marker : K) -> Unit

    ExtStore::set

    fn[K, V] ExtStore::set(self : ExtStore, _marker : K, v : V) -> Unit

    ExtStore::try_get

    fn[K, V] ExtStore::try_get(self : ExtStore, _marker : K) -> V?

    FileBackend

    type FileBackend

    Where the uploaded file content lives.

    FileHeader

    pub(all) struct FileHeader {
    filename : String
    size : Int64
    content_type : String?
    header : PartHeaders
    backend : FileBackend
    }

    Metadata for a single uploaded file, plus its content. Fields mirror Go's mime/multipart.FileHeader.

    FileHeader::bytes

    async fn FileHeader::bytes(self : FileHeader) -> Bytes

    Read the entire file content as Bytes.

    • For files that fit in memory: returns immediately.
    • For files spilled to disk: reads from the temp file.
    • For External files: returns empty — file was streamed to a custom writer.

    Must be called from an async context.

    FileHeader::open

    Open a reader for the file content.

    • Memory: returns a reader backed by the in-memory bytes.
    • Disk: reads the temp file and returns a memory-backed reader.
    • External: returns an empty reader — file was streamed to a custom writer.

    FileHeader::path

    fn FileHeader::path(self : FileHeader) -> String?

    Return the file path for on-disk files, or None for in-memory/External.

    FileWriter

    pub(all) struct FileWriter(
    File
    )

    Wraps an @fs.File as a WriterCloser for multipart uploads.

    Handler

    pub(all) struct Handler(async (Context) -> Unit) derive(
    Debug
    )

    Handler::to_repr

    pub struct Header(Map[String, Array[String]])

    Header::add

    fn Header::add(h : Header, key : String, value : String) -> Unit

    Header::copy

    fn Header::copy(h : Header) -> Header

    Header::del

    fn Header::del(h : Header, key : String) -> Unit

    Header::get

    fn Header::get(h : Header, key : String) -> String?

    Header::has

    fn Header::has(h : Header, key : String) -> Bool

    Header::set

    fn Header::set(h : Header, key : String, value : String) -> Unit

    Header::values

    fn Header::values(h : Header, key : String) -> Array[String]

    HttpHeaders

    Case-insensitive HTTP headers backed by @http.Headers.

    HttpHeaders::get

    fn HttpHeaders::get(self : HttpHeaders, key : String) -> String?

    Return the value for key (case-insensitive lookup), or None.

    HttpHeaders::merge_in_place

    Merge entries from other into self.

    HttpHeaders::new

    HttpHeaders::set

    fn HttpHeaders::set(self : HttpHeaders, key : String, value : String) -> Unit

    Set the value for key (case-insensitive).

    HttpHeaders::to_map

    Return the underlying header map.

    HttpMethod

    pub(all) enum HttpMethod {
    Get
    Head
    Post
    Put
    Delete
    Connect
    Options
    Trace
    Patch
    Any
    } derive(Compare, Eq, Hash, ToJson,
    Debug
    )

    impl Show for HttpMethod

    HttpMethod::compare

    fn HttpMethod::compare(HttpMethod, HttpMethod) -> Int

    HttpMethod::equal

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

    HttpMethod::hash

    fn HttpMethod::hash(self : HttpMethod) -> Int

    HttpMethod::hash_combine

    fn HttpMethod::hash_combine(HttpMethod, Hasher) -> Unit

    HttpMethod::not_equal

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

    HttpMethod::op_ge

    fn HttpMethod::op_ge(x : HttpMethod, y : HttpMethod) -> Bool

    HttpMethod::op_gt

    fn HttpMethod::op_gt(x : HttpMethod, y : HttpMethod) -> Bool

    HttpMethod::op_le

    fn HttpMethod::op_le(x : HttpMethod, y : HttpMethod) -> Bool

    HttpMethod::op_lt

    fn HttpMethod::op_lt(x : HttpMethod, y : HttpMethod) -> Bool

    HttpMethod::output

    fn HttpMethod::output(self : HttpMethod, logger : &Logger) -> Unit

    HttpMethod::to_json

    fn HttpMethod::to_json(HttpMethod) -> Json

    HttpMethod::to_string

    fn HttpMethod::to_string(self : HttpMethod) -> String

    Middleware

    pub(all) struct Middleware((Handler) -> Handler) derive(
    Debug
    )

    MultipartForm

    type MultipartForm

    Accumulator for a parsed multipart form. Holds both regular fields (via Values) and file uploads.

    Node

    impl Show for Node

    Node::output

    fn Node::output(self : Node, logger : &Logger) -> Unit

    Node::to_repr

    Node::to_string

    fn Node::to_string(self : Node) -> String

    NodeType

    impl Show for NodeType

    NodeType::output

    fn NodeType::output(self : NodeType, logger : &Logger) -> Unit

    NodeType::to_repr

    NodeType::to_string

    fn NodeType::to_string(self : NodeType) -> String

    ParamKV

    type ParamKV

    Part

    pub(all) struct Part {
    source : &
    Reader

    delimiter : Bytes
    headers : PartHeaders
    buf :
    Buffer

    body_eof : Bool
    }

    A single part in a multipart stream.

    Owns a lookback buffer for boundary scanning. When the body reaches the next boundary delimiter, read_chunk returns None and the remaining bytes after the boundary are recoverable via take_remaining().

    Part::content_type

    fn Part::content_type(self : Part) -> String?

    Return the Content-Type of the part, if present.

    Part::form_name

    fn Part::form_name(self : Part) -> (String, String?)

    Extract (name, filename?) from the Content-Disposition header.

    Part::headers

    fn Part::headers(self : Part) -> PartHeaders

    Return the part's MIME headers.

    PartHeaders

    pub(all) struct PartHeaders(Map[String, String])

    Case-insensitive MIME part headers.

    Keys are normalised to lower-case on insertion and lookup.

    PartHeaders::get

    fn PartHeaders::get(self : PartHeaders, key : String) -> String?

    Return the value for key (case-insensitive), or None.

    PartHeaders::has

    fn PartHeaders::has(self : PartHeaders, key : String) -> Bool

    Return true if key is present.

    PartHeaders::is_empty

    fn PartHeaders::is_empty(self : PartHeaders) -> Bool

    Return true if there are no entries.

    PartHeaders::new

    RequestId

    pub(all) struct RequestId {
    }

    Marker type for per-request extension data (e.g. request ID).

    RouteResult

    pub enum RouteResult {
    Found(Endpoint, Array[String])
    MethodNotAllowed
    NotFound
    }

    Router

    pub struct Router {
    tree : Node
    middlewares : Array[Middleware]
    not_found_handler : Handler?
    method_not_allowed_handler : Handler?
    } derive(
    Debug
    )

    impl Show for Router

    Router::Router

    fn Router::Router() -> Router

    Router::add

    fn Router::add(self : Router, meth : HttpMethod, pattern : String, name? : String, mws? : Array[Middleware], handler : Handler) -> Unit raise RouterError

    Add a new route to the router

    Parameters

    • meth: HTTP method
    • pattern: URL pattern, must start with /
    • mws: Optional array of middlewares, applied in order before the handler only applies to this route
    • name: Optional name for the route
    • handler: Handler function

    Raises

    • RouterError::InvalidPattern: If the pattern does not start with / or is otherwise invalid

    Router::any

    fn Router::any(self : Router, pattern : String, name? : String, mws? : Array[Middleware], handler : Handler) -> Unit raise RouterError

    Router::handler

    async fn Router::handler(self : Router, ctx : Context) -> Unit

    Router::mount

    fn Router::mount(self : Router, prefix : String, sub : Router) -> Unit raise RouterError

    Mount a sub-router under the given prefix using a prefix/* wildcard route, like chi does. The sub-router stays alive — routes added to it after mount are automatically picked up.

    Router::output

    fn Router::output(self : Router, logger : &Logger) -> Unit

    Router::set_method_not_allowed

    fn Router::set_method_not_allowed(self : Router, handler : Handler) -> Unit

    Router::set_not_found

    fn Router::set_not_found(self : Router, handler : Handler) -> Unit

    Router::to_repr

    Router::to_string

    fn Router::to_string(self : Router) -> String

    Router::use_mw

    fn Router::use_mw(self : Router, mw : Middleware) -> Unit

    Server

    pub struct Server {
    router : Router
    addr : String
    read_timeout : Int?
    write_timeout : Int?
    request_id : Int
    }

    Server::Server

    fn Server::Server(addr : String, router : Router) -> Server

    Server::start

    async fn Server::start(self : Server) -> Unit

    Server::with_timeout

    fn Server::with_timeout(self : Server, read? : Int, write? : Int) -> Server

    UserId

    pub(all) struct UserId {
    }

    Marker type for per-user extension data (e.g. authenticated user ID).

    Values

    pub(all) struct Values(Map[String, Array[String]])

    Values::get

    fn Values::get(self : Values, key : String) -> String?

    Values::get_all

    fn Values::get_all(self : Values, key : String) -> Array[String]

    Values::new

    fn Values::new() -> Values

    aborted

    let aborted : Int

    already_exists

    let already_exists : Int

    cancelled

    let cancelled : Int

    canonicalHeaderKey

    fn canonicalHeaderKey(key : String) -> String

    data_loss

    let data_loss : Int

    deadline_exceeded

    let deadline_exceeded : Int

    default_max_memory

    let default_max_memory : Int64

    Default memory threshold: 32 MiB. Files larger than this are written to temporary disk files (via MultipartForm::spill_to_disk).

    failed_precondition

    let failed_precondition : Int

    internal

    let internal : Int

    invalid_argument

    let invalid_argument : Int

    new

    fn new() -> Header

    not_found

    let not_found : Int

    let ok : Int

    out_of_range

    let out_of_range : Int

    permission_denied

    let permission_denied : Int

    register

    fn register(code : Int, http_status : Int) -> Unit

    resource_exhausted

    let resource_exhausted : Int

    start

    async fn start(addr : String, router : Router) -> Unit

    status_accepted

    let status_accepted : Int

    status_already_reported

    let status_already_reported : Int

    status_bad_gateway

    let status_bad_gateway : Int

    status_bad_request

    let status_bad_request : Int

    status_conflict

    let status_conflict : Int

    status_continue

    let status_continue : Int

    status_created

    let status_created : Int

    status_early_hints

    let status_early_hints : Int

    status_expectation_failed

    let status_expectation_failed : Int

    status_failed_dependency

    let status_failed_dependency : Int

    status_forbidden

    let status_forbidden : Int

    status_found

    let status_found : Int

    status_gateway_timeout

    let status_gateway_timeout : Int

    status_gone

    let status_gone : Int

    status_http_version_not_supported

    let status_http_version_not_supported : Int

    status_im_used

    let status_im_used : Int

    status_insufficient_storage

    let status_insufficient_storage : Int

    status_internal_server_error

    let status_internal_server_error : Int

    status_length_required

    let status_length_required : Int

    status_locked

    let status_locked : Int

    status_loop_detected

    let status_loop_detected : Int

    status_method_not_allowed

    let status_method_not_allowed : Int

    status_misdirected_request

    let status_misdirected_request : Int

    status_moved_permanently

    let status_moved_permanently : Int

    status_multi_status

    let status_multi_status : Int

    status_multiple_choices

    let status_multiple_choices : Int

    status_network_authentication_required

    let status_network_authentication_required : Int

    status_no_content

    let status_no_content : Int

    status_non_authoritative_info

    let status_non_authoritative_info : Int

    status_not_acceptable

    let status_not_acceptable : Int

    status_not_extended

    let status_not_extended : Int

    status_not_found

    let status_not_found : Int

    status_not_implemented

    let status_not_implemented : Int

    status_not_modified

    let status_not_modified : Int

    status_ok

    let status_ok : Int

    status_partial_content

    let status_partial_content : Int

    status_payment_required

    let status_payment_required : Int

    status_permanent_redirect

    let status_permanent_redirect : Int

    status_precondition_failed

    let status_precondition_failed : Int

    status_precondition_required

    let status_precondition_required : Int

    status_processing

    let status_processing : Int

    status_proxy_auth_required

    let status_proxy_auth_required : Int

    status_request_entity_too_large

    let status_request_entity_too_large : Int

    status_request_header_fields_too_large

    let status_request_header_fields_too_large : Int

    status_request_timeout

    let status_request_timeout : Int

    status_request_uri_too_long

    let status_request_uri_too_long : Int

    status_requested_range_not_satisfiable

    let status_requested_range_not_satisfiable : Int

    status_reset_content

    let status_reset_content : Int

    status_see_other

    let status_see_other : Int

    status_service_unavailable

    let status_service_unavailable : Int

    status_switching_protocols

    let status_switching_protocols : Int

    status_temporary_redirect

    let status_temporary_redirect : Int

    status_text

    fn status_text(code : Int) -> String

    status_too_early

    let status_too_early : Int

    status_too_many_requests

    let status_too_many_requests : Int

    status_unauthorized

    let status_unauthorized : Int

    let status_unavailable_for_legal_reasons : Int

    status_unprocessable_entity

    let status_unprocessable_entity : Int

    status_unsupported_media_type

    let status_unsupported_media_type : Int

    status_upgrade_required

    let status_upgrade_required : Int

    status_use_proxy

    let status_use_proxy : Int

    status_variant_also_negotiates

    let status_variant_also_negotiates : Int

    to_api_error

    fn to_api_error(e : PonyError) -> ApiError

    Convert a PonyError to an ApiError. Kept for backward compatibility — delegates to the ToApiError trait.

    unauthenticated

    let unauthenticated : Int

    unavailable

    let unavailable : Int

    unimplemented

    let unimplemented : Int

    unknown

    let unknown : Int