crescent

Crescent: A web framework for MoonBit.

http
server
web
framework
async
ai-friendly
moon add bobzhang/crescent@0.11.0
Download zip
Author
Version
0.11.0
License
Apache-2.0
Last updated
23 days ago
Downloads
447

Dependencies

README

#Crescent

A web framework for MoonBit. Native-first, type-safe, AI-agent friendly.

Hard fork of oboard/mocket by oboard. Credits at the bottom.

Target: native only. Crescent's HTTP/WebSocket runtime depends on moonbitlang/async/{http,socket,websocket}, which are not available on the wasm or js targets. Use moon build/moon test (which default to the preferred-target: "native" declared in moon.mod.json) or pass --target native explicitly.

#Install

moon add bobzhang/crescent

#Hello World

///|
async fn main {
let app = @crescent.Mocket()
app.get("/", _ => "Hello, Crescent!")
app.serve(port=4000)
}

moon run . --target native # Visit http://localhost:4000


#Building a Todo API

This walkthrough builds a complete REST API step by step.

#Define your types

Types with derive(ToJson, FromJson) are your API contract. The same types compile to native (backend) and JS (frontend) — no code generation needed.

///|
struct Todo {
id : Int
title : String
done : Bool
} derive(ToJson, FromJson)

///|
struct CreateTodo {
title : String
} derive(FromJson)

///|
#warnings("-unnecessary_annotation")
struct JsonDemoUser {
name : String
age : Int
} derive(ToJson, FromJson, Eq)

///|
test "json_value sets content-type and serializes body" {
let user = JsonDemoUser::{ name: "Alice", age: 30 }
let res = HttpResponse::ok().json_value(user)
assert_eq(
res.headers.get("Content-Type"),
Some("application/json; charset=utf-8"),
)
assert_eq(@utf8.decode(res.raw_body), "{\"name\":\"Alice\",\"age\":30}")
}

#Wire up the routes

All handlers automatically map errors to JSON — bad JSON returns 400, raise HttpError(...) returns a structured error, anything else returns 500. You never write error-handling boilerplate.

///|
fn build_app() -> @crescent.Mocket {
let app = @crescent.Mocket()
let todos : Array[Todo] = [{ id: 1, title: "Learn MoonBit", done: false }]
let next_id = Ref(2)

// List all
app.get("/api/todos", _ => HttpResponse::ok().json_value(todos))

// Get by ID — require_param_int auto-returns 400 for "abc"
app.get("/api/todos/:id", event => {
let id = event.require_param_int("id")
for todo in todos {
if todo.id == id {
return HttpResponse::ok().json_value(todo)
}
}
raise HttpError(NotFound, "todo \{id} not found")
})

// Create — event.json() auto-returns 400 for invalid JSON
app.post("/api/todos", event => {
let input : CreateTodo = event.json()
let todo = Todo::{ id: next_id.val, title: input.title, done: false }
next_id.val 1
todos.push(todo)
HttpResponse::created().json_value(todo)
})

// Health check — get_raw for handlers that never raise
app.get_raw("/health", fn(_) noraise { "ok" })

app
}

#Add middleware

Register middleware before routes. They execute in onion order (first registered = outermost layer).

///|
fn build_app() -> @crescent.Mocket {
let app = @crescent.Mocket()

// Security headers on every response
app.use_middleware(@crescent.security_headers())

// Unique request ID for distributed tracing
app.use_middleware(@crescent.request_id())

// Request logging
app.use_middleware(fn(event, next) {
let start = @async.now()
let res = next()
let ms = @async.now() - start
println("\{event.req.http_method} \{event.req.url} \{ms}ms")
res
})

// Auth only on /api routes
app.use_middleware(
fn(event, next) {
match event.req.get_header("Authorization") {
Some(_) => next()
None => HttpResponse::unauthorized()
}
},
base_path="/api",
)

// ... register routes ...
app
}

///|
async test "security headers middleware" {
let app = Mocket::new()
app.use_middleware(security_headers())
app.get_raw("/test", fn(_) noraise { "ok" })
let client = TestClient::new(app)
let res = client.get("/test")
assert_eq(res.headers.get("X-Content-Type-Options"), Some("nosniff"))
assert_eq(res.headers.get("X-Frame-Options"), Some("DENY"))
}

///|
async test "request ID middleware" {
let app = Mocket::new()
app.use_middleware(request_id())
app.get_raw("/test", fn(_) noraise { "ok" })
let client = TestClient::new(app)
let res = client.get("/test")
assert_true(res.headers.get("X-Request-Id") is Some(_))
}

#Test without a network

TestClient dispatches requests in-process. No ports, no sockets, fast CI.

///|
async test "create a todo" {
let app = build_app()
let client = TestClient(app)

let res = client.post("/api/todos", body=b"{\"title\":\"Write tests\"}")
assert_eq(res.status, Created)

let todo : Todo = res.body_json()
assert_eq(todo.title, "Write tests")
assert_eq(todo.done, false)
}

///|
async test "invalid ID returns 400" {
let client = TestClient(build_app())
let res = client.get("/api/todos/abc")
assert_eq(res.status, BadRequest)
}

///|
async test "missing todo returns 404" {
let client = TestClient(build_app())
let res = client.get("/api/todos/999")
assert_eq(res.status, NotFound)
}

///|
async test "bad JSON returns 400" {
let client = TestClient(build_app())
let res = client.post("/api/todos", body=b"not json")
assert_eq(res.status, BadRequest)
}

///|
async test "security headers are present" {
let client = TestClient(build_app())
let res = client.get("/health")
assert_eq(res.headers.get("X-Content-Type-Options"), Some("nosniff"))
}

///|
#warnings("-unnecessary_annotation")
struct TodoInput {
title : String
} derive(FromJson, ToJson)

///|
async test "typed handler auto-maps errors" {
let app = Mocket::new()
app.post("/todos", event => {
let input : TodoInput = event.json()
HttpResponse::created().json_value(input)
})
let client = TestClient::new(app)

// Valid request
let res = client.post("/todos", body=b"{\"title\":\"Learn MoonBit\"}")
assert_eq(res.status, Created)

// Invalid JSON -> automatic 400
let res2 = client.post("/todos", body=b"not json")
assert_eq(res2.status, BadRequest)
}

#Start the server

///|
async fn main {
let app = build_app()
println("Listening on http://localhost:4000")
app.serve(port=4000)
}

Try it:

curl localhost:4000/api/todos curl -X POST localhost:4000/api/todos -d '{"title":"Write docs"}' curl localhost:4000/api/todos/1 curl localhost:4000/api/todos/abc # 400: must be a valid integer curl -X POST localhost:4000/api/todos -d 'not json' # 400: parse error curl localhost:4000/health # "ok"


#CRUD with resource()

When your API follows REST conventions, register all 5 routes in one call:

app.resource("/api/todos", ResourceConfig(
list = fn(_) { HttpResponse::ok().json_value(all_todos()) },
get = fn(e) {
let id = e.require_param_int("id")
HttpResponse::ok().json_value(find_todo(id))
},
create = fn(e) {
let input : CreateTodo = e.json()
HttpResponse::created().json_value(insert_todo(input))
},
update = fn(e) {
let id = e.require_param_int("id")
let input : CreateTodo = e.json()
HttpResponse::ok().json_value(update_todo(id, input))
},
delete = fn(e) {
let id = e.require_param_int("id")
delete_todo(id)
HttpResponse::no_content()
},
))

This registers GET /api/todos, GET /api/todos/:id, POST /api/todos, PUT /api/todos/:id, DELETE /api/todos/:id. All handlers omitted from ResourceConfig(...) are simply not registered.

///|
#warnings("-unnecessary_annotation")
struct ResItem {
id : Int
name : String
} derive(ToJson, FromJson, Eq)

///|
impl Show for ResItem with output(self, logger) {
logger.write_string("ResItem(\{self.id}, \{self.name})")
}

///|
#warnings("-unnecessary_annotation")
struct CreateResItem {
name : String
} derive(FromJson, ToJson)

///|
async test "resource CRUD" {
let items : Array[ResItem] = [{ id: 1, name: "Alpha" }]
let app = Mocket::new()
app.resource("/items", {
list: Some(_ => HttpResponse::ok().json_value(items)),
get: Some(event => {
let id = event.require_param_int("id")
for item in items {
if item.id == id {
return HttpResponse::ok().json_value(item)
}
}
raise HttpError::HttpError(NotFound, "not found")
}),
create: Some(event => {
let input : CreateResItem = event.json()
let item = ResItem::{ id: 2, name: input.name }
items.push(item)
HttpResponse::created().json_value(item)
}),
update: None,
delete: None,
})
let client = TestClient::new(app)

// List
let res = client.get("/items")
assert_eq(res.status, OK)
let list : Array[ResItem] = res.body_json()
assert_eq(list.length(), 1)

// Create
let res2 = client.post("/items", body=b"{\"name\":\"Beta\"}")
assert_eq(res2.status, Created)
let created : ResItem = res2.body_json()
assert_eq(created.name, "Beta")

// Get by ID
let res3 = client.get("/items/1")
assert_eq(res3.status, OK)
let item : ResItem = res3.body_json()
assert_eq(item, { id: 1, name: "Alpha" })

// Not found
let res4 = client.get("/items/999")
assert_eq(res4.status, NotFound)
}


#Route Groups

Share a path prefix and middleware across related routes:

app.group("/api/v1", fn(api) {
api.use_middleware(require_auth())

api.get("/users", _ => HttpResponse::ok().json_value(users))
api.get("/users/:id", fn(event) {
let id = event.require_param_int("id")
HttpResponse::ok().json_value(find_user(id))
})
api.post("/users", fn(event) {
let user : CreateUser = event.json()
HttpResponse::created().json_value(save_user(user))
})
})
// Routes: GET /api/v1/users, GET /api/v1/users/:id, POST /api/v1/users
// require_auth() only runs for /api/v1/* requests

#Route Patterns

PatternExample URLCaptures
/users/users(exact match)
/users/:id/users/42event.param("id") = "42"
/users/:id/posts/:pid/users/1/posts/99two params
/files/*/files/readme.txtone segment in _
/static/**/static/css/main.cssany depth in _

Matching uses a radix tree — O(path length), not O(number of routes).

///|
test "compiled route matching" {
let route = CompiledRoute::compile("/users/:id/posts/:postId")
assert_eq(
route.match_path("/users/42/posts/99"),
Some({ "id": "42", "postId": "99" }),
)
assert_eq(route.match_path("/users/42"), None)
}

///|
test "static routes are flagged" {
let static_route = CompiledRoute::compile("/api/health")
assert_true(static_route.is_static)
let dynamic_route = CompiledRoute::compile("/api/:version")
assert_true(dynamic_route.is_static == false)
}


#WebSocket

app.ws("/chat", fn(event) {
match event {
Open(peer) => {
println("Connected: \{peer.to_string()}")
peer.subscribe("chat-room")
}
Message(peer, Text(msg)) =>
peer.publish("chat-room", msg) // broadcast to all subscribers
Message(peer, Binary(data)) =>
peer.binary(data) // echo binary back
Close(peer) =>
println("Disconnected: \{peer.to_string()}")
}
})

WebSocketPeer methods: text(msg), binary(data), subscribe(channel), unsubscribe(channel), publish(channel, msg).

#Static Files

app.static_assets("/assets", @static_file.StaticFileProvider(path="./public"))

Features: ETag caching, If-Modified-Since / If-None-Match support, Accept-Encoding content negotiation, path traversal protection, directory index fallback (index.html, index.htm, ...).

#Cookies

// Set with attributes
event.res.set_cookie("session", "abc123",
max_age=3600, http_only=true, same_site=Lax)

// Read
let user = match event.req.get_cookie("session") {
Some(cookie) => find_user_by_session(cookie.value)
None => anonymous_user()
}

// Delete (sets Max-Age=0)
event.res.delete_cookie("session")

///|
test "set and format cookie" {
let res = HttpResponse::new(OK)
res.set_cookie(
"session",
"abc123",
max_age=3600,
http_only=true,
same_site=Lax,
)
assert_true(res.cookies.get("session") is Some(_))
}

#HTTP Client (Fetch)

Make outbound HTTP requests from your handlers:

app.get("/api/weather/:city", fn(event) {
let city = event.require_param("city")
let res = @fetch.get("https://api.weather.example/v1/\{city}")
let weather : WeatherData = res.read_body()
HttpResponse::ok().json_value(weather)
})

Methods: @fetch.get, @fetch.post, @fetch.put, @fetch.patch, @fetch.delete, @fetch.head. All accept optional data, headers, credentials, and mode parameters.

#CORS

// Allow all origins (default)
app.use_middleware(@cors.handle_cors())

// Restrict to specific origin
app.use_middleware(@cors.handle_cors(
origin="https://myapp.com",
methods="GET,POST",
credentials=true,
max_age=3600,
))

#Server Configuration

#Request limits

app.serve_with(port=4000, NativeServeOptions(
max_connections=1000, // concurrent connection limit
max_request_body_bytes=1_048_576, // 1MB body size limit (413 if exceeded)
request_body_read_timeout_ms=5000, // 5s read timeout (408 if exceeded)
))

#WebSocket options

app.serve_with(port=4000, NativeServeOptions(
websocket_max_message_bytes=65536, // max inbound message size
websocket_outgoing_queue_capacity=100, // outbound buffer per connection
websocket_overflow_policy=DropOldest, // or DropLatest
websocket_read_timeout_ms=30000, // close idle connections after 30s
))

#Graceful shutdown

let shutdown = @async.Queue()

// In another task: shutdown.put(()) to stop the server
app.serve_until(port=4000, shutdown~)

#Serve on an existing server

let addr = @socket.Addr::parse("0.0.0.0:4000")
let server = @http.Server::new(addr, reuse_addr=true)
app.serve_on(server)


#Response Helpers

HttpResponse::ok() // 200
HttpResponse::created() // 201
HttpResponse::no_content() // 204
HttpResponse::bad_request() // 400
HttpResponse::unauthorized() // 401
HttpResponse::forbidden() // 403
HttpResponse::not_found() // 404
HttpResponse::error(BadRequest, "message") // JSON error body
HttpResponse::redirect("/new-path") // 301
HttpResponse::redirect_temporary("/temp") // 302
HttpResponse::redirect_307("/preserve") // 307 (preserves method)
HttpResponse::redirect_308("/permanent") // 308 (permanent, preserves method)

// Fluent chaining
HttpResponse::ok()
.header("Cache-Control", "max-age=3600")
.json_value(data)

///|
test "response helpers" {
let ok = HttpResponse::ok()
assert_eq(ok.status_code, OK)

let created = HttpResponse::created()
assert_eq(created.status_code, Created)

let not_found = HttpResponse::not_found()
assert_eq(not_found.status_code, NotFound)

let no_content = HttpResponse::no_content()
assert_eq(no_content.status_code, NoContent)

let bad_request = HttpResponse::bad_request()
assert_eq(bad_request.status_code, BadRequest)
}

///|
test "fluent response building" {
let res = HttpResponse::ok()
.header("X-Custom", "value")
.header("Cache-Control", "max-age=3600")
assert_eq(res.headers.get("X-Custom"), Some("value"))
assert_eq(res.headers.get("Cache-Control"), Some("max-age=3600"))
}

///|
test "redirect helpers" {
let r301 = HttpResponse::redirect("/new")
assert_eq(r301.status_code, MovedPermanently)
assert_eq(r301.headers.get("Location"), Some("/new"))

let r302 = HttpResponse::redirect_temporary("/temp")
assert_eq(r302.status_code, Found)
}

#Error Handling

All get/post/put/patch/delete handlers catch errors automatically:

What you writeWhat the client gets
raise HttpError(BadRequest, "invalid email")400 {"error":{"status":400,"message":"invalid email"}}
raise HttpError(NotFound, "not found")404 with JSON body
event.json() on bad input400 with parse error message
event.require_param_int("id") on "abc"400 "must be a valid integer"
Any unhandled error500 Internal Server Error

For handlers that should never raise (health checks, plain text), use get_raw:

app.get_raw("/health", fn(_) noraise { "ok" })

For custom error responses, use try_json:

app.post("/users", fn(event) {
match event.try_json() {
Ok(user) => HttpResponse::ok().json_value(user)
Err(msg) => HttpResponse::error(BadRequest, "Invalid user: \{msg}")
}
})

#Built-in Middleware

MiddlewareWhat it does
security_headers()X-Content-Type-Options: nosniff, X-Frame-Options: DENY, X-XSS-Protection: 0, Referrer-Policy: strict-origin-when-cross-origin
request_id()Adds X-Request-Id header; preserves incoming IDs for distributed tracing. Access via event.request_id()
@cors.handle_cors()Full CORS support: preflight OPTIONS handling, configurable origins/methods/credentials

Writing custom middleware:

///|
fn rate_limiter() -> Middleware {
let count = Ref(0)
fn(event, next) {
count.val 1
if count.val > 100 {
return HttpResponse::error(TooManyRequests, "slow down")
}
next()
}
}


#API Quick Reference

#Parameters

MethodReturnsOn missing/invalid
event.param("name")String?None
event.param_int("id")Int?None
event.param_int64("id")Int64?None
event.require_param("name")Stringraises 400
event.require_param_int("id")Intraises 400
event.require_param_int64("id")Int64raises 400

///|
test "param and param_int" {
let event = MocketEvent::{
req: HttpRequest::new(Get, "/", {}, raw_body=b""),
res: HttpResponse::new(OK),
params: { "id": "42", "name": "alice" },
}
assert_eq(event.param("name"), Some("alice"))
assert_eq(event.param_int("id"), Some(42))
assert_eq(event.param("missing"), None)
}

///|
test "require_param raises on missing" {
let event = MocketEvent::{
req: HttpRequest::new(Get, "/", {}, raw_body=b""),
res: HttpResponse::new(OK),
params: {},
}
let result = try! event.require_param_int("id")
assert_true(result is Err(_))
}

#Body & Query

MethodReturnsNotes
event.json[T]()TRaises on invalid JSON
event.try_json[T]()Result[T, String]For custom error handling
event.req.body[T]()TVia BodyReader trait (String, Bytes, Json)
event.req.get_query("key")String?URL-decoded, cached
event.req.query_params()Map[String, String]All query params, cached
event.req.path()StringPath without query string, cached
event.req.content_type()String?Content-Type header value
event.request_id()String?Requires request_id() middleware

///|
#warnings("-unnecessary_annotation")
struct ReadmeCreateUser {
name : String
age : Int
} derive(FromJson)

///|
test "json parsing from request body" {
let req = HttpRequest::new(
Post,
"/users",
{},
raw_body=b"{\"name\":\"Bob\",\"age\":25}",
)
let user : ReadmeCreateUser = req.json()
assert_eq(user.name, "Bob")
assert_eq(user.age, 25)
}

///|
test "try_json returns Result" {
let req = HttpRequest::new(Post, "/", {}, raw_body=b"not json")
let result : Result[ReadmeCreateUser, String] = req.try_json()
assert_true(result is Err(_))
}

///|
test "path extracts from request target" {
let req = HttpRequest::new(Get, "/api/users?q=test", {}, raw_body=b"")
let path = req.path()
assert_eq(path, "/api/users")
}

///|
test "query_params cached and decoded" {
let req = HttpRequest::new(
Get,
"/search?q=hello%20world&lang=en",
{},
raw_body=b"",
)
assert_eq(req.get_query("q"), Some("hello world"))
assert_eq(req.get_query("lang"), Some("en"))
assert_eq(req.get_query("missing"), None)
}

#HttpMethod Enum

Pattern match on the request method — no string comparisons:

match event.req.http_method {
Get => "read"
Post | Put | Patch => "write"
Delete => "delete"
_ => "other"
}

///|
test "HttpMethod round-trip" {
let meth : HttpMethod = Post
assert_eq(meth.to_method_string(), "POST")
assert_eq(HttpMethod::from_string("POST"), Post)
}

///|
test "HttpMethod pattern matching" {
let req = HttpRequest::new(Get, "/", {}, raw_body=b"")
let label = match req.http_method {
Get => "read"
Post => "write"
_ => "other"
}
assert_eq(label, "read")
}

#Performance

  • Radix tree routing — O(path length) dynamic route lookup
  • Pre-compiled patterns — route templates parsed at registration, not per request
  • Cached parsing — path and query string parsed once per request
  • Zero-alloc headers — case-insensitive ASCII comparison without string allocation
  • Direct byte outputBytes and HttpResponse skip the intermediate buffer

#Packages

bobzhang/crescent — Core: routing, middleware, serving, WebSocket bobzhang/crescent/http — HTTP protocol: headers, dates, URL encoding bobzhang/crescent/cors — CORS middleware bobzhang/crescent/fetch — HTTP client bobzhang/crescent/static_file — Static file provider (filesystem) bobzhang/crescent/uri — RFC 3986 URI parser

#Credits

Crescent is a hard fork of oboard/mocket by oboard. The original framework established the core architecture: Express-style routing, onion middleware, WebSocket support, static file serving, and the async-native design built on moonbitlang/async.

#License

Apache-2.0

#
HttpHandler

type HttpHandler = async (MocketEvent) -> &Responder noraise

The async handler type for HTTP route callbacks.

#
Middleware

type Middleware = async (MocketEvent, async () -> &Responder noraise) -> &Responder noraise

A middleware function that receives a request event and a next continuation, and returns a response.

#
MiddlewareNext

type MiddlewareNext = async () -> &Responder noraise

The continuation function passed to middleware, calling the next handler in the chain.

#
WebSocketHandler

type WebSocketHandler = (WebSocketEvent) -> Unit

Handler function type for WebSocket route events.

#
BodyReader

pub(open) trait BodyReader {
from_request(req : HttpRequest) -> Self raise
}

Trait for types that can be deserialized from an HTTP request body.
impl BodyReader for Bytes
impl BodyReader for Json

#
Responder

pub(open) trait Responder {
options(Self, res : HttpResponse) -> Unit
output(Self, buf :
Buffer
) -> Unit
output_bytes(Self) -> Bytes?
}

Trait for types that can be sent as an HTTP response body.
impl Responder for String
impl Responder for Bytes
impl Responder for Json
impl Responder for ToJson

#
ServeStaticProvider

pub(open) trait ServeStaticProvider {
async get_meta(Self, id : String) -> StaticAssetMeta? noraise
async get_contents(Self, id : String) -> &Responder noraise
get_type(Self, ext : String) -> String?
get_encodings(Self) -> Map[String, String]
get_index_names(Self) -> Array[String]
get_fallthrough(Self) -> Bool
}

Trait for providing static file serving capabilities, including asset lookup, content retrieval, and MIME type resolution.

#
ExecError

pub suberror ExecError derive(
Debug
)

Error type for execution failures such as subprocess errors.

#
FetchError

pub suberror FetchError {
RequestFailed(String)
} derive(
Debug
)

Error type for fetch operations, wrapping a failure message.

#
HttpError

pub(all) suberror HttpError {
HttpError(StatusCode, String)
} derive(
Debug
)

Error type for handlers that want automatic HTTP error responses.

#
IOError

pub suberror IOError derive(
Debug
)

Error type for I/O operations such as file reads or writes.

#
NativeServeError

pub suberror NativeServeError {
InvalidMaxConnections(Int)
InvalidMaxRequestBodyBytes(Int)
InvalidRequestBodyReadTimeoutMs(Int)
InvalidWebSocketMaxMessageBytes(Int)
InvalidWebSocketOutgoingQueueCapacity(Int)
InvalidWebSocketReadTimeoutMs(Int)
} derive(ToJson,
Debug
)

Error type for invalid native server configuration values.

#
NetworkError

pub suberror NetworkError derive(
Debug
)

Error type for network-related failures such as connection errors.

#
CompiledRoute

pub struct CompiledRoute {
template : String
segments : Array[RouteSegment]
is_static : Bool
}

A pre-compiled route pattern, parsed once at registration time.

#
CompiledRoute::compile

fn CompiledRoute::compile(template : String) -> CompiledRoute

Compiles a route template string into a CompiledRoute.

#
CompiledRoute::match_path

fn CompiledRoute::match_path(self : CompiledRoute, path : String) -> Map[String, StringView]?

Matches a compiled route against a request path, returning extracted parameters or None.

#
CookieItem

pub(all) struct CookieItem {
name : String
value : String
max_age : Int?
path : String?
domain : String?
secure : Bool?
http_only : Bool?
same_site : SameSiteOption?

fn new(name~ : String, value~ : String, max_age? : Int, path? : String, domain? : String, secure? : Bool, http_only? : Bool, same_site? : SameSiteOption) -> CookieItem
} derive(Eq)

Represents an HTTP cookie with its name, value, and optional attributes.
impl Show for CookieItem

#
CookieItem::new

fn CookieItem::new(name~ : String, value~ : String, max_age? : Int, path? : String, domain? : String, secure? : Bool, http_only? : Bool, same_site? : SameSiteOption) -> CookieItem

Creates a new CookieItem with the given name, value, and optional attributes.

#
FetchCredentials

pub(all) enum FetchCredentials {
Omit
SameOrigin
Include
} derive(Eq,
Debug
)

Controls whether credentials (cookies, TLS certificates) are sent with a fetch request.

NOTE: On the native target, this option is currently accepted but not enforced — cookies/TLS certs must be managed manually via headers. Kept for future compatibility and for the JS target.

#
FetchCredentials::to_string

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

Returns the string representation of a FetchCredentials value for use in fetch request options.

#
FetchMode

pub(all) enum FetchMode {
Cors
NoCors
SameOrigin
Navigate
} derive(Eq,
Debug
)

Controls the CORS mode for a fetch request.

NOTE: On the native target, this option is currently accepted but not enforced — there is no browser sandbox. Kept for compatibility with the JS target and for future CORS-aware client implementations.

#
FetchMode::to_string

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

Returns the string representation of a FetchMode value for use in fetch request options.

#
Html

type Html

impl Responder for Html

#
HttpMethod

pub(all) enum HttpMethod {
Get
Head
Post
Put
Patch
Delete
Options
Trace
Connect
Other(String)
} derive(Eq)

HTTP request methods as a type-safe enum.

Using this enum instead of raw strings prevents typos like "DLETE" or "OPTONS" and enables pattern matching in handlers.
impl Show for HttpMethod

#
HttpMethod::from_string

fn HttpMethod::from_string(s : String) -> HttpMethod

Parses a string into an HttpMethod.

#
HttpMethod::to_method_string

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

Converts the enum to its HTTP method string representation.

#
HttpRequest

pub(all) struct HttpRequest {
http_method : HttpMethod
url : String
headers : Map[String, String]
raw_body : Bytes
// private fields

fn new(http_method : HttpMethod, url : String, headers : Map[String, String], raw_body? : Bytes) -> HttpRequest
}

An incoming HTTP request containing the method, URL, headers, and body.

#
HttpRequest::body

fn[T : BodyReader] HttpRequest::body(self : HttpRequest) -> T raise

Deserializes the request body into a value of type T via the BodyReader trait.

#
HttpRequest::content_type

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

Returns the Content-Type header value, or None if not set.

#
HttpRequest::from_method_string

fn HttpRequest::from_method_string(http_method : String, url : String, headers : Map[String, String], raw_body : Bytes) -> HttpRequest

Creates a new HttpRequest from a method string, parsing it into an HttpMethod.
fn HttpRequest::get_cookie(self : HttpRequest, name : String) -> CookieItem?

Retrieves a cookie by name from the request's Cookie header, if present.

#
HttpRequest::get_header

fn HttpRequest::get_header(self : HttpRequest, name : String) -> String?

Returns the value of a request header by name (case-insensitive), or None if not found.

#
HttpRequest::get_query

fn HttpRequest::get_query(self : HttpRequest, key : String) -> String?

Looks up a single query parameter by key, returning None if not found. Uses the cached query params — calling this multiple times with different keys only parses the URL once.

#
HttpRequest::json

Parses the request body as JSON and deserializes into type T.

#
HttpRequest::method_string

fn HttpRequest::method_string(self : HttpRequest) -> String

Returns the HTTP method as a string (e.g., "GET", "POST").

#
HttpRequest::new

fn HttpRequest::new(http_method : HttpMethod, url : String, headers : Map[String, String], raw_body? : Bytes) -> HttpRequest

Creates a new HttpRequest with the given method, URL, headers, and optional body.

#
HttpRequest::path

fn HttpRequest::path(self : HttpRequest) -> String

Returns the path component of the request URL, excluding query string and fragment.

#
HttpRequest::query_params

fn HttpRequest::query_params(self : HttpRequest) -> Map[String, String]

Parses the query string into a map of key-value pairs.

Returns a fresh copy on every call — mutating the returned map does NOT affect subsequent get_query() calls. The underlying parse is cached, so calling this multiple times only parses the URL once.

#
HttpRequest::query_string

fn HttpRequest::query_string(self : HttpRequest) -> String?

Returns the raw query string from the request URL, or None if absent.

#
HttpRequest::try_json

fn[T :
FromJson
] HttpRequest::try_json(self : HttpRequest) -> Result[T, String]

Tries to parse the request body as JSON, returning Ok(T) on success or Err(message) on failure.

#
HttpResponse

pub(all) struct HttpResponse {
status_code : StatusCode
headers : Map[String, String]
cookies : Map[String, CookieItem]
raw_body : Bytes

fn new(status_code : StatusCode, headers? : Map[String, String], cookies? : Map[String, CookieItem], raw_body? : Bytes) -> HttpResponse
}

An outgoing HTTP response containing the status code, headers, cookies, and body.

#
HttpResponse::bad_request

fn HttpResponse::bad_request() -> HttpResponse

Creates a 400 Bad Request response.

#
HttpResponse::body

fn HttpResponse::body(self : HttpResponse, body : &Responder) -> HttpResponse

Sets the response body from any Responder and returns the response for chaining.

Applies the responder's options() (e.g. sets Content-Type) and uses the output_bytes() fast path when available to avoid an intermediate buffer copy.

#
HttpResponse::created

fn HttpResponse::created() -> HttpResponse

Creates a 201 Created response.
fn HttpResponse::delete_cookie(self : HttpResponse, key : String) -> Unit

Deletes a cookie by setting its value to empty and max-age to 0.

#
HttpResponse::error

fn HttpResponse::error(status : StatusCode, message : String) -> HttpResponse

Creates an error response with a JSON body containing status and message.

#
HttpResponse::forbidden

fn HttpResponse::forbidden() -> HttpResponse

Creates a 403 Forbidden response.

#
HttpResponse::header

fn HttpResponse::header(self : HttpResponse, name : String, value : String) -> HttpResponse

Sets a response header and returns self for fluent chaining.

Uses case-insensitive matching: setting Content-Type will replace any existing content-type or CONTENT-TYPE header.

#
HttpResponse::internal_server_error

fn HttpResponse::internal_server_error() -> HttpResponse

Creates a 500 Internal Server Error response.

#
HttpResponse::json

fn HttpResponse::json(self : HttpResponse, obj : &ToJson) -> HttpResponse

Sets the response body to the JSON representation of the given value. Also sets Content-Type: application/json; charset=utf-8.

#
HttpResponse::json_value

fn[T : ToJson] HttpResponse::json_value(self : HttpResponse, value : T) -> HttpResponse

Serializes a typed value as JSON, sets Content-Type, and returns the response.

Uses case-insensitive header matching: any existing content-type / CONTENT-TYPE header is replaced, not duplicated.

#
HttpResponse::new

fn HttpResponse::new(status_code : StatusCode, headers? : Map[String, String], cookies? : Map[String, CookieItem], raw_body? : Bytes) -> HttpResponse

Creates a new HttpResponse with the given status code and optional headers, cookies, and body.

#
HttpResponse::no_content

fn HttpResponse::no_content() -> HttpResponse

Creates a 204 No Content response.

#
HttpResponse::not_found

fn HttpResponse::not_found() -> HttpResponse

Creates a 404 Not Found response.

#
HttpResponse::ok

Creates a 200 OK response.

#
HttpResponse::read_body

fn[T : BodyReader] HttpResponse::read_body(self : HttpResponse) -> T raise

Deserializes the response body into a value of type T via the BodyReader trait.

#
HttpResponse::redirect

fn HttpResponse::redirect(location : String) -> HttpResponse

Returns a 301 Moved Permanently redirect response.

#
HttpResponse::redirect_307

fn HttpResponse::redirect_307(location : String) -> HttpResponse

Returns a 307 Temporary Redirect response (preserves method).

#
HttpResponse::redirect_308

fn HttpResponse::redirect_308(location : String) -> HttpResponse

Returns a 308 Permanent Redirect response (preserves method).

#
HttpResponse::redirect_temporary

fn HttpResponse::redirect_temporary(location : String) -> HttpResponse

Returns a 302 Found (temporary) redirect response.
fn HttpResponse::set_cookie(self : HttpResponse, name : String, value : String, max_age? : Int, path? : String, domain? : String, secure? : Bool, http_only? : Bool, same_site? : SameSiteOption) -> Unit

Sets a cookie on the response with the given name, value, and optional attributes.

#
HttpResponse::to_responder

fn HttpResponse::to_responder(self : HttpResponse) -> &Responder

Converts this response into a Responder trait object for use as a handler return value.

#
HttpResponse::unauthorized

fn HttpResponse::unauthorized() -> HttpResponse

Creates a 401 Unauthorized response.

#
Mocket

pub struct Mocket {
// private fields

fn new(base_path? : String) -> Mocket
}

The main application type that holds route mappings, middleware, and WebSocket handlers.

Routing state is split by access pattern:
  • static_routesMap[method, Map[path, handler]] for O(1) static lookup
  • dynamic_routes — radix tree for O(path_length) dynamic lookup
  • route_keys — flat list of (method, path) for introspection via routes()

#
Mocket::all

fn Mocket::all(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unit

Registers a handler matching all HTTP methods with automatic error-to-JSON mapping.

#
Mocket::all_raw

fn Mocket::all_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Registers a noraise handler that matches all HTTP methods.

#
Mocket::connect

fn Mocket::connect(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unit

Registers a CONNECT handler with automatic error-to-JSON mapping.

#
Mocket::connect_raw

fn Mocket::connect_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Registers a noraise CONNECT handler.

#
Mocket::delete

fn Mocket::delete(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unit

Registers a DELETE handler with automatic error-to-JSON mapping.

#
Mocket::delete_raw

fn Mocket::delete_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Registers a noraise DELETE handler.

#
Mocket::dispatch

async fn Mocket::dispatch(self : Mocket, http_method : String, url : String, headers : Map[String, String], body : Bytes) -> (StatusCode, Map[String, String], Bytes)

Dispatches a synthetic HTTP request through the full routing + middleware pipeline and returns (status_code, headers, body_bytes).

#
Mocket::get

fn Mocket::get(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unit

Registers a GET handler with automatic error-to-JSON mapping.

#
Mocket::get_raw

fn Mocket::get_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Registers a noraise GET handler. For most use cases, prefer get().

#
Mocket::group

fn Mocket::group(self : Mocket, base_path : String, configure : (Mocket) -> Unit) -> Unit

Creates a route group with a shared base path prefix, merging its routes and middleware into the app.

#
Mocket::has_not_found_handler

fn Mocket::has_not_found_handler(self : Mocket) -> Bool

Returns true if a custom not-found handler has been registered.

#
Mocket::head

fn Mocket::head(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unit

Registers a HEAD handler with automatic error-to-JSON mapping.

#
Mocket::head_raw

fn Mocket::head_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Registers a noraise HEAD handler.

#
Mocket::new

fn Mocket::new(base_path? : String) -> Mocket

Creates a new Mocket application instance with an optional base path prefix.

#
Mocket::on

fn Mocket::on(self : Mocket, event : String, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Registers a route handler for the given HTTP method and path.

#
Mocket::options

fn Mocket::options(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unit

Registers an OPTIONS handler with automatic error-to-JSON mapping.

#
Mocket::options_raw

fn Mocket::options_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Registers a noraise OPTIONS handler.

#
Mocket::patch

fn Mocket::patch(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unit

Registers a PATCH handler with automatic error-to-JSON mapping.

#
Mocket::patch_raw

fn Mocket::patch_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Registers a noraise PATCH handler.

#
Mocket::post

fn Mocket::post(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unit

Registers a POST handler with automatic error-to-JSON mapping.

#
Mocket::post_raw

fn Mocket::post_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Registers a noraise POST handler.

#
Mocket::put

fn Mocket::put(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unit

Registers a PUT handler with automatic error-to-JSON mapping.

#
Mocket::put_raw

fn Mocket::put_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Registers a noraise PUT handler.

#
Mocket::resource

fn Mocket::resource(self : Mocket, path : String, config : ResourceConfig) -> Unit

Registers standard REST routes (list, get, create, update, delete) for a resource path.

#
Mocket::routes

fn Mocket::routes(self : Mocket) -> Iter[(String, String)]

Returns an iterator over the registered route keys as (method, path) pairs.

#
Mocket::serve

async fn Mocket::serve(self : Mocket, port~ : Int) -> Unit

Starts serving HTTP requests on the given port with default options.

#
Mocket::serve_blocking

fn Mocket::serve_blocking(self : Mocket, port~ : Int) -> Unit

Starts serving HTTP requests on the given port, blocking the current thread.

#
Mocket::serve_on

async fn Mocket::serve_on(self : Mocket, server :
Server
) -> Unit

Starts serving HTTP requests on the given server with default options.

#
Mocket::serve_on_until

async fn Mocket::serve_on_until(self : Mocket, server :
Server
, shutdown :
Queue
[Unit]) -> Unit

Stop serving when shutdown receives a unit or is closed. shutdown.put(()) wakes one waiter; shutdown.close() broadcasts to all waiters. Shutdown is cancellation-based: active requests and websocket sessions are aborted rather than drained to completion.

#
Mocket::serve_on_until_with

async fn Mocket::serve_on_until_with(self : Mocket, server :
Server
, shutdown :
Queue
[Unit], options : NativeServeOptions) -> Unit

Stop serving when shutdown receives a unit or is closed. shutdown.put(()) wakes one waiter; shutdown.close() broadcasts to all waiters. Shutdown is cancellation-based: active requests and websocket sessions are aborted rather than drained to completion. max_connections, if present, limits the number of client connections handled in parallel by the underlying @http.Server. max_request_body_bytes, if present, rejects oversized request bodies with 413 Request Entity Too Large. request_body_read_timeout_ms, if present, rejects slow request bodies with 408 Request Timeout. websocket_max_message_bytes, if present, closes websocket connections with 1009 MessageTooBig when an inbound message exceeds that many bytes. websocket_outgoing_queue_capacity, if present, bounds buffered outbound websocket messages per connection. websocket_overflow_policy, if present, chooses whether a full outbound websocket queue drops the oldest or latest message. websocket_read_timeout_ms, if present, closes websocket connections after that many milliseconds waiting for the next inbound message.

#
Mocket::serve_on_with

async fn Mocket::serve_on_with(self : Mocket, server :
Server
, options : NativeServeOptions) -> Unit

Serve using explicit native async runtime options. max_connections, if present, limits the number of client connections handled in parallel by the underlying @http.Server. max_request_body_bytes, if present, rejects oversized request bodies with 413 Request Entity Too Large. request_body_read_timeout_ms, if present, rejects slow request bodies with 408 Request Timeout. websocket_max_message_bytes, if present, closes websocket connections with 1009 MessageTooBig when an inbound message exceeds that many bytes. websocket_outgoing_queue_capacity, if present, bounds buffered outbound websocket messages per connection. websocket_overflow_policy, if present, chooses whether a full outbound websocket queue drops the oldest or latest message. websocket_read_timeout_ms, if present, closes websocket connections after that many milliseconds waiting for the next inbound message.

#
Mocket::serve_until

async fn Mocket::serve_until(self : Mocket, port~ : Int, shutdown :
Queue
[Unit]) -> Unit

Stop serving when shutdown receives a unit or is closed. shutdown.put(()) wakes one waiter; shutdown.close() broadcasts to all waiters. Shutdown is cancellation-based: active requests and websocket sessions are aborted rather than drained to completion.

#
Mocket::serve_until_with

async fn Mocket::serve_until_with(self : Mocket, port~ : Int, shutdown :
Queue
[Unit], options : NativeServeOptions) -> Unit

Stop serving when shutdown receives a unit or is closed. shutdown.put(()) wakes one waiter; shutdown.close() broadcasts to all waiters. Shutdown is cancellation-based: active requests and websocket sessions are aborted rather than drained to completion. max_connections, if present, limits the number of client connections handled in parallel by the underlying @http.Server. max_request_body_bytes, if present, rejects oversized request bodies with 413 Request Entity Too Large. request_body_read_timeout_ms, if present, rejects slow request bodies with 408 Request Timeout. websocket_max_message_bytes, if present, closes websocket connections with 1009 MessageTooBig when an inbound message exceeds that many bytes. websocket_outgoing_queue_capacity, if present, bounds buffered outbound websocket messages per connection. websocket_overflow_policy, if present, chooses whether a full outbound websocket queue drops the oldest or latest message. websocket_read_timeout_ms, if present, closes websocket connections after that many milliseconds waiting for the next inbound message.

#
Mocket::serve_with

async fn Mocket::serve_with(self : Mocket, port~ : Int, options : NativeServeOptions) -> Unit

Serve on port using explicit native async runtime options. max_connections, if present, limits the number of client connections handled in parallel by the underlying @http.Server. max_request_body_bytes, if present, rejects oversized request bodies with 413 Request Entity Too Large. request_body_read_timeout_ms, if present, rejects slow request bodies with 408 Request Timeout. websocket_max_message_bytes, if present, closes websocket connections with 1009 MessageTooBig when an inbound message exceeds that many bytes. websocket_outgoing_queue_capacity, if present, bounds buffered outbound websocket messages per connection. websocket_overflow_policy, if present, chooses whether a full outbound websocket queue drops the oldest or latest message. websocket_read_timeout_ms, if present, closes websocket connections after that many milliseconds waiting for the next inbound message.

#
Mocket::set_not_found_handler

fn Mocket::set_not_found_handler(self : Mocket, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Sets a custom handler for 404 Not Found responses.

#
Mocket::static_assets

fn Mocket::static_assets(self : Mocket, path : String, provider : &ServeStaticProvider) -> Unit

Mounts a static asset provider at the given path, serving files with ETag/Last-Modified caching and content negotiation.

#
Mocket::trace

fn Mocket::trace(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unit

Registers a TRACE handler with automatic error-to-JSON mapping.

#
Mocket::trace_raw

fn Mocket::trace_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unit

Registers a noraise TRACE handler.

#
Mocket::use_middleware

fn Mocket::use_middleware(self : Mocket, middleware : async (MocketEvent, async () -> &Responder noraise) -> &Responder noraise, base_path? : String) -> Unit

Adds a middleware to the app, optionally scoped to a base path prefix.

#
Mocket::ws

fn Mocket::ws(self : Mocket, path : String, handler : (WebSocketEvent) -> Unit) -> Unit

Registers a WebSocket handler for the given path, matched regardless of HTTP method.

Dynamic patterns (:param, *, **) are inserted into the same radix tree implementation used for HTTP routes, giving them consistent precedence: static > param > wildcard > globstar. Re-registering the same path overrides the previous handler.

#
MocketEvent

pub(all) struct MocketEvent {
req : HttpRequest
res : HttpResponse
params : Map[String, StringView]
}

The event passed to each HTTP handler, bundling the request, response, and route parameters.

#
MocketEvent::json

Parses the request body as JSON and deserializes into type T. Shorthand for event.req.json().

#
MocketEvent::param

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

Returns a route parameter value as a String, or None if not found.

The value is NOT URL-decoded — it's returned exactly as it appeared in the URL. For a decoded value (e.g. hello%20worldhello world), use param_decoded().

#
MocketEvent::param_decoded

fn MocketEvent::param_decoded(self : MocketEvent, name : String) -> String?

Returns a URL-decoded route parameter value, or None if not found.

For example, if the URL is /search/hello%20world and the route is /search/:query, this returns Some("hello world").

#
MocketEvent::param_int

fn MocketEvent::param_int(self : MocketEvent, name : String) -> Int?

Returns a route parameter parsed as an Int, or None if not found or not a valid integer.

#
MocketEvent::param_int64

fn MocketEvent::param_int64(self : MocketEvent, name : String) -> Int64?

Returns a route parameter parsed as an Int64, or None if not found or not valid.

#
MocketEvent::request_id

fn MocketEvent::request_id(self : MocketEvent) -> String?

Returns the request ID for this event, or None if the middleware is not active.

#
MocketEvent::require_param

fn MocketEvent::require_param(self : MocketEvent, name : String) -> String raise

Returns a route parameter as a String, or raises HttpError(BadRequest) if missing.

#
MocketEvent::require_param_int

fn MocketEvent::require_param_int(self : MocketEvent, name : String) -> Int raise

Returns a route parameter parsed as an Int, or raises HttpError(BadRequest) if missing or not a valid integer.

#
MocketEvent::require_param_int64

fn MocketEvent::require_param_int64(self : MocketEvent, name : String) -> Int64 raise

Returns a route parameter parsed as an Int64, or raises HttpError(BadRequest) if missing or not a valid 64-bit integer.

#
MocketEvent::try_json

fn[T :
FromJson
] MocketEvent::try_json(self : MocketEvent) -> Result[T, String]

Tries to parse the request body as JSON, returning Ok(T) on success or Err(message) on failure. Shorthand for event.req.try_json().

#
NativeServeOptions

pub struct NativeServeOptions {
max_connections : Int?
max_request_body_bytes : Int?
request_body_read_timeout_ms : Int?
websocket_outgoing_queue_capacity : Int?
websocket_overflow_policy : NativeWebSocketOverflowPolicy?
websocket_read_timeout_ms : Int?
websocket_max_message_bytes : Int?

fn new(max_connections? : Int, max_request_body_bytes? : Int, request_body_read_timeout_ms? : Int, websocket_outgoing_queue_capacity? : Int, websocket_overflow_policy? : NativeWebSocketOverflowPolicy, websocket_read_timeout_ms? : Int, websocket_max_message_bytes? : Int) -> NativeServeOptions
}

Configuration options for the native async HTTP server runtime.

#
NativeServeOptions::new

fn NativeServeOptions::new(max_connections? : Int, max_request_body_bytes? : Int, request_body_read_timeout_ms? : Int, websocket_outgoing_queue_capacity? : Int, websocket_overflow_policy? : NativeWebSocketOverflowPolicy, websocket_read_timeout_ms? : Int, websocket_max_message_bytes? : Int) -> NativeServeOptions

max_connections, if present, bounds concurrent accepted connections. max_request_body_bytes, if present, rejects oversized request bodies with 413 Request Entity Too Large. request_body_read_timeout_ms, if present, rejects slow request bodies with 408 Request Timeout. websocket_max_message_bytes, if present, closes websocket connections with 1009 MessageTooBig when an inbound message exceeds that many bytes. websocket_outgoing_queue_capacity, if present, bounds buffered outbound websocket messages per connection. websocket_overflow_policy, if present, chooses whether a full outbound websocket queue drops the oldest or latest message. websocket_read_timeout_ms, if present, closes websocket connections after that many milliseconds waiting for the next inbound message.

#
NativeWebSocketOverflowPolicy

pub(all) enum NativeWebSocketOverflowPolicy {
DropOldest
DropLatest
} derive(Eq,
Debug
)

Policy for handling a full outbound WebSocket message queue.

#
RadixRouter

type RadixRouter[T]

#
RadixRouter::insert

fn[T] RadixRouter::insert(self : RadixRouter[T], http_method : String, route : CompiledRoute, handler : T) -> Unit

Inserts a route handler under the given HTTP method.

Routes whose pattern contains a ** globstar followed by additional segments (e.g. /files/**/meta) cannot be represented in the radix tree and are stored in a per-method fallback list that is scanned linearly during dispatch. All other routes are added to the per-method radix tree.

#
RadixRouter::is_empty

fn[T] RadixRouter::is_empty(self : RadixRouter[T]) -> Bool

Returns true if no routes have been registered in this router.

#
RadixRouter::merge

fn[T] RadixRouter::merge(self : RadixRouter[T], other : RadixRouter[T]) -> Unit

Merges all routes from another router into this one.

#
RadixRouter::new

fn[T] RadixRouter::new() -> RadixRouter[T]

Creates a new empty radix router with no registered routes.

#
RadixRouter::search

fn[T] RadixRouter::search(self : RadixRouter[T], http_method : String, path : String) -> (T, Map[String, StringView])?

Searches for a handler matching the given HTTP method and path.

#
ResourceConfig

pub(all) struct ResourceConfig {
list : async (MocketEvent) -> &Responder?
get : async (MocketEvent) -> &Responder?
create : async (MocketEvent) -> &Responder?
update : async (MocketEvent) -> &Responder?
delete : async (MocketEvent) -> &Responder?

fn new(list? : async (MocketEvent) -> &Responder, get? : async (MocketEvent) -> &Responder, create? : async (MocketEvent) -> &Responder, update? : async (MocketEvent) -> &Responder, delete? : async (MocketEvent) -> &Responder) -> ResourceConfig
}

Configuration for registering a RESTful resource with standard CRUD routes.

#
ResourceConfig::new

fn ResourceConfig::new(list? : async (MocketEvent) -> &Responder, get? : async (MocketEvent) -> &Responder, create? : async (MocketEvent) -> &Responder, update? : async (MocketEvent) -> &Responder, delete? : async (MocketEvent) -> &Responder) -> ResourceConfig

Creates a new ResourceConfig with optional CRUD handlers.

#
RouteSegment

pub(all) enum RouteSegment {
Static(String)
Param(String)
Wildcard
GlobStar
} derive(Eq)

A single segment of a pre-compiled route pattern.

#
SameSiteOption

pub(all) enum SameSiteOption {
Lax
Strict
SameSiteNone
} derive(Eq,
Debug
)

The SameSite attribute for cookies, controlling cross-site request behavior.

#
StaticAssetMeta

pub(all) struct StaticAssetMeta {
asset_type : String?
etag : String?
mtime : Int64?
path : String?
size : Int64?
encoding : String?

fn new(asset_type? : String, etag? : String, mtime? : Int64, path? : String, size? : Int64, encoding? : String) -> StaticAssetMeta
}

Metadata describing a static asset including its content type, ETag, modification time, and size.

#
StaticAssetMeta::new

fn StaticAssetMeta::new(asset_type? : String, etag? : String, mtime? : Int64, path? : String, size? : Int64, encoding? : String) -> StaticAssetMeta

Creates a new StaticAssetMeta with optional fields for asset type, ETag, mtime, path, size, and encoding.

#
StaticResolvedResponder

type StaticResolvedResponder

#
StatusCode

pub(all) enum StatusCode {
Continue
SwitchingProtocols
Processing
EarlyHints
OK
Created
Accepted
NonAuthoritativeInfo
NoContent
ResetContent
PartialContent
MultiStatus
AlreadyReported
IMUsed
MultipleChoices
MovedPermanently
Found
SeeOther
NotModified
UseProxy
TemporaryRedirect
PermanentRedirect
BadRequest
Unauthorized
PaymentRequired
Forbidden
NotFound
MethodNotAllowed
NotAcceptable
ProxyAuthRequired
RequestTimeout
Conflict
Gone
LengthRequired
PreconditionFailed
RequestEntityTooLarge
RequestUriTooLong
UnsupportedMediaType
RequestedRangeNotSatisfiable
ExpectationFailed
Teapot
MisdirectedRequest
UnprocessableEntity
Locked
FailedDependency
TooEarly
UpgradeRequired
PreconditionRequired
TooManyRequests
RequestHeaderFieldsTooLarge
UnavailableForLegalReasons
InternalServerError
NotImplemented
BadGateway
ServiceUnavailable
GatewayTimeout
HttpVersionNotSupported
VariantAlsoNegotiates
InsufficientStorage
LoopDetected
NotExtended
NetworkAuthenticationRequired
Custom(Int)
} derive(Eq, ToJson,
Debug
)

HTTP status codes as registered with IANA. See: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
impl Show for StatusCode

#
StatusCode::from_int

fn StatusCode::from_int(i : Int) -> StatusCode

Converts an integer HTTP status code to a StatusCode enum value.

#
StatusCode::to_int

fn StatusCode::to_int(self : StatusCode) -> Int

Returns the integer value of this HTTP status code.

#
TestClient

pub struct TestClient {
// private fields

fn new(app : Mocket) -> TestClient
}

A test HTTP client that dispatches requests directly without network I/O.

#
TestClient::delete

async fn TestClient::delete(self : TestClient, path : String, headers? : Map[String, String]) -> TestResponse

Sends a synthetic DELETE request.

#
TestClient::get

async fn TestClient::get(self : TestClient, path : String, headers? : Map[String, String]) -> TestResponse

Sends a synthetic GET request to the given path.

#
TestClient::new

fn TestClient::new(app : Mocket) -> TestClient

Creates a new test client wrapping the given app.

#
TestClient::post

async fn TestClient::post(self : TestClient, path : String, body? : Bytes, headers? : Map[String, String]) -> TestResponse

Sends a synthetic POST request with optional body.

#
TestClient::put

async fn TestClient::put(self : TestClient, path : String, body? : Bytes, headers? : Map[String, String]) -> TestResponse

Sends a synthetic PUT request with optional body.

#
TestClient::request

async fn TestClient::request(self : TestClient, meth : String, path : String, headers~ : Map[String, String], body~ : Bytes) -> TestResponse

Dispatches a synthetic request through the full routing and middleware pipeline.

#
TestResponse

pub struct TestResponse {
status : StatusCode
headers : Map[String, String]
body_bytes : Bytes
}

The response from a TestClient request, containing status, headers, and body.

#
TestResponse::body_json

Parses the response body as JSON into a typed value.

#
TestResponse::body_text

fn TestResponse::body_text(self : TestResponse) -> String

Returns the response body decoded as a UTF-8 string.

#
TryJsonTestUser

type TryJsonTestUser derive(Eq, ToJson,
FromJson
)

#
WebSocketAggregatedMessage

pub enum WebSocketAggregatedMessage {
Text(String)
Binary(Bytes)
}

A fully assembled WebSocket message, either text or binary.

#
WebSocketEvent

pub enum WebSocketEvent {
Open(WebSocketPeer)
Message(WebSocketPeer, WebSocketAggregatedMessage)
Close(WebSocketPeer)
}

Events delivered to a WebSocket handler: open, message, or close.

#
WebSocketPeer

pub struct WebSocketPeer {
// private fields

fn new(connection_id~ : String, params? : Map[String, String]) -> WebSocketPeer
}

Represents a connected WebSocket client with its connection ID, subscribed channels, and route parameters captured from the upgrade URL.

#
WebSocketPeer::binary

fn WebSocketPeer::binary(self : WebSocketPeer, message : Bytes) -> Unit

Sends a binary message to this WebSocket peer.

#
WebSocketPeer::new

fn WebSocketPeer::new(connection_id~ : String, params? : Map[String, String]) -> WebSocketPeer

Creates a new WebSocketPeer with the given connection ID and optional route parameters (extracted from dynamic WebSocket routes like /ws/:room).

#
WebSocketPeer::param

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

Returns a route parameter captured from the WebSocket upgrade URL, or None if the parameter was not present.

For example, a route /ws/:room matched against /ws/lobby makes peer.param("room") return Some("lobby").

#
WebSocketPeer::publish

fn WebSocketPeer::publish(self : WebSocketPeer, channel : String, message : String) -> Unit

Publishes a text message to a pub/sub channel on behalf of this peer.

#
WebSocketPeer::subscribe

fn WebSocketPeer::subscribe(self : WebSocketPeer, channel : String) -> Unit

Subscribes this WebSocket peer to the given pub/sub channel.

#
WebSocketPeer::text

fn WebSocketPeer::text(self : WebSocketPeer, message : String) -> Unit

Sends a text message to this WebSocket peer.

#
WebSocketPeer::to_string

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

Returns a string representation of this WebSocket peer.

#
WebSocketPeer::unsubscribe

fn WebSocketPeer::unsubscribe(self : WebSocketPeer, channel : String) -> Unit

Unsubscribes this WebSocket peer from the given pub/sub channel.

#
async_run

fn async_run(f : async () -> Unit noraise) -> Unit

Runs an async function to completion, blocking the current thread. Prefer async fn main over calling this directly.
fn cookie_to_string(cookie : Array[CookieItem]) -> String

Serializes an array of cookie items into a semicolon-separated string.

#
fetch

async fn fetch(url : String, body? : String, http_method : HttpMethod, data? : &Responder, headers? : Map[String, String], credentials? : FetchCredentials, mode? : FetchMode) -> HttpResponse

Sends an HTTP request to the given URL and returns the response.

#
html

fn html(html : &Show) -> &Responder

Creates an HTML responder from any Show value, setting the Content-Type to text/html.
fn parse_cookie(cookie : StringView) -> Map[String, CookieItem]

Parses a raw cookie header string into a map of cookie names to CookieItem values.

#
request_id

fn request_id() -> (async (MocketEvent, async () -> &Responder noraise) -> &Responder noraise)

Returns a middleware that assigns a unique X-Request-Id header to every request and response.

#
security_headers

fn security_headers() -> (async (MocketEvent, async () -> &Responder noraise) -> &Responder noraise)

Returns a middleware that sets common security response headers.

#
text

fn text(text : &Show) -> &Responder

Creates a plain-text responder from any Show value, setting the Content-Type to text/plain.