pony

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

web
framework
http
router
server
middleware
rest
api
json
auth
jwt
cors
moon add jaredzhou/pony@0.4.0
Download zip
Author
Version
0.4.0
License
Apache-2.0
Last updated
13 days ago
Downloads
49
README

#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]() → TJSON 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 = FromStr::from_str("42") catch { _ => -1 }
inspect(n, content="42")
let b : Bool = 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
)

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

#
RouterError

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

impl Show for RouterError

#
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_http_status

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

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

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

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

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

Case-insensitive HTTP headers with lowercased keys.

#
HttpHeaders::from_map

fn HttpHeaders::from_map(m : Map[String, String]) -> HttpHeaders

#
HttpHeaders::get

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

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

#
HttpHeaders::merge_in_place

fn HttpHeaders::merge_in_place(self : HttpHeaders, other : Map[String, String]) -> Unit

Merge entries from other into self, lowercasing keys.

#
HttpHeaders::new

#
HttpHeaders::set

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

Set the value for key, lowercasing the key first.

#
HttpHeaders::to_map

fn HttpHeaders::to_map(self : HttpHeaders) -> Map[String, String]

Return the underlying Map[String, String].

#
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

#
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

#
NodeType

impl Show for NodeType

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