Crescent: A web framework for MoonBit.
Dependencies
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.
moon add bobzhang/crescent///|
async fn main {
let app = @crescent.Mocket()
app.get("/", _ => "Hello, Crescent!")
app.serve(port=4000)
}moon run . --target native
# Visit http://localhost:4000///|
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}")
}///|
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
}///|
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(_))
}///|
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)
}///|
async fn main {
let app = build_app()
println("Listening on http://localhost:4000")
app.serve(port=4000)
}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" 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()
},
))///|
#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)
} 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| Pattern | Example URL | Captures |
|---|---|---|
| /users | /users | (exact match) |
| /users/:id | /users/42 | event.param("id") = "42" |
| /users/:id/posts/:pid | /users/1/posts/99 | two params |
| /files/* | /files/readme.txt | one segment in _ |
| /static/** | /static/css/main.css | any depth in _ |
///|
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)
} 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()}")
}
}) app.static_assets("/assets", @static_file.StaticFileProvider(path="./public")) // 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(_))
} 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)
}) // 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,
)) 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)
)) 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
)) let shutdown = @async.Queue()
// In another task: shutdown.put(()) to stop the server
app.serve_until(port=4000, shutdown~) let addr = @socket.Addr::parse("0.0.0.0:4000")
let server = @http.Server::new(addr, reuse_addr=true)
app.serve_on(server) 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)
}| What you write | What 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 input | 400 with parse error message |
| event.require_param_int("id") on "abc" | 400 "must be a valid integer" |
| Any unhandled error | 500 Internal Server Error |
app.get_raw("/health", fn(_) noraise { "ok" }) app.post("/users", fn(event) {
match event.try_json() {
Ok(user) => HttpResponse::ok().json_value(user)
Err(msg) => HttpResponse::error(BadRequest, "Invalid user: \{msg}")
}
})| Middleware | What 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 |
///|
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()
}
}| Method | Returns | On missing/invalid |
|---|---|---|
| event.param("name") | String? | None |
| event.param_int("id") | Int? | None |
| event.param_int64("id") | Int64? | None |
| event.require_param("name") | String | raises 400 |
| event.require_param_int("id") | Int | raises 400 |
| event.require_param_int64("id") | Int64 | raises 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(_))
}| Method | Returns | Notes |
|---|---|---|
| event.json[T]() | T | Raises on invalid JSON |
| event.try_json[T]() | Result[T, String] | For custom error handling |
| event.req.body[T]() | T | Via 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() | String | Path 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)
} 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")
}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 parserimpl BodyReader for Stringimpl BodyReader for FixedArray[Byte]impl BodyReader for Bytesimpl BodyReader for Array[Byte]impl BodyReader for Jsonpub(open) trait Responder {
options(Self, res : HttpResponse) -> Unit
output(Self, buf : Buffer) -> Unit
output_bytes(Self) -> Bytes?
}fn output_bytes(_self : String) -> Bytes?fn output_bytes(self : Bytes) -> Bytes?impl Responder for StringViewfn output_bytes(_self : StringView) -> Bytes?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
}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)impl Show for CookieItemfn CookieItem::new(name~ : String, value~ : String, max_age? : Int, path? : String, domain? : String, secure? : Bool, http_only? : Bool, same_site? : SameSiteOption) -> CookieItemtype Htmlpub(all) enum HttpMethod {
Get
Head
Post
Put
Patch
Delete
Options
Trace
Connect
Other(String)
} derive(Eq)impl Show for HttpMethodpub(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
}impl Responder for HttpRequestfn HttpRequest::from_method_string(http_method : String, url : String, headers : Map[String, String], raw_body : Bytes) -> HttpRequestfn HttpRequest::new(http_method : HttpMethod, url : String, headers : Map[String, String], raw_body? : Bytes) -> HttpRequestpub(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
}impl Responder for HttpResponsefn HttpResponse::new(status_code : StatusCode, headers? : Map[String, String], cookies? : Map[String, CookieItem], raw_body? : Bytes) -> HttpResponsefn HttpResponse::set_cookie(self : HttpResponse, name : String, value : String, max_age? : Int, path? : String, domain? : String, secure? : Bool, http_only? : Bool, same_site? : SameSiteOption) -> Unitfn Mocket::all_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unitfn Mocket::connect(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unitfn Mocket::connect_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unitfn Mocket::delete(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unitfn Mocket::delete_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unitasync fn Mocket::dispatch(self : Mocket, http_method : String, url : String, headers : Map[String, String], body : Bytes) -> (StatusCode, Map[String, String], Bytes)fn Mocket::get_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unitfn Mocket::head_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unitfn Mocket::on(self : Mocket, event : String, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unitfn Mocket::options(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder) -> Unitfn Mocket::options_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unitfn Mocket::patch_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unitfn Mocket::post_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unitfn Mocket::put_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unitasync fn Mocket::serve_on_until_with(self : Mocket, server : Server, shutdown : Queue[Unit], options : NativeServeOptions) -> Unitasync fn Mocket::serve_on_with(self : Mocket, server : Server, options : NativeServeOptions) -> Unitasync fn Mocket::serve_until_with(self : Mocket, port~ : Int, shutdown : Queue[Unit], options : NativeServeOptions) -> Unitfn Mocket::set_not_found_handler(self : Mocket, handler : async (MocketEvent) -> &Responder noraise) -> Unitfn Mocket::trace_raw(self : Mocket, path : String, handler : async (MocketEvent) -> &Responder noraise) -> Unitfn Mocket::use_middleware(self : Mocket, middleware : async (MocketEvent, async () -> &Responder noraise) -> &Responder noraise, base_path? : String) -> Unitpub(all) struct MocketEvent {
req : HttpRequest
res : HttpResponse
params : Map[String, StringView]
}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
}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) -> NativeServeOptionsimpl Show for NativeWebSocketOverflowPolicytype RadixRouter[T]fn[T] RadixRouter::insert(self : RadixRouter[T], http_method : String, route : CompiledRoute, handler : T) -> Unitfn[T] RadixRouter::search(self : RadixRouter[T], http_method : String, path : String) -> (T, Map[String, StringView])?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
}fn ResourceConfig::new(list? : async (MocketEvent) -> &Responder, get? : async (MocketEvent) -> &Responder, create? : async (MocketEvent) -> &Responder, update? : async (MocketEvent) -> &Responder, delete? : async (MocketEvent) -> &Responder) -> ResourceConfigimpl Show for SameSiteOptionimpl ToJson for SameSiteOptionpub(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
}fn StaticAssetMeta::new(asset_type? : String, etag? : String, mtime? : Int64, path? : String, size? : Int64, encoding? : String) -> StaticAssetMetapub(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)impl Show for StatusCodeasync fn TestClient::delete(self : TestClient, path : String, headers? : Map[String, String]) -> TestResponseasync fn TestClient::get(self : TestClient, path : String, headers? : Map[String, String]) -> TestResponseasync fn TestClient::post(self : TestClient, path : String, body? : Bytes, headers? : Map[String, String]) -> TestResponseasync fn TestClient::put(self : TestClient, path : String, body? : Bytes, headers? : Map[String, String]) -> TestResponseasync fn TestClient::request(self : TestClient, meth : String, path : String, headers~ : Map[String, String], body~ : Bytes) -> TestResponsepub enum WebSocketAggregatedMessage {
Text(String)
Binary(Bytes)
}pub enum WebSocketEvent {
Open(WebSocketPeer)
Message(WebSocketPeer, WebSocketAggregatedMessage)
Close(WebSocketPeer)
}pub struct WebSocketPeer {
// private fields
fn new(connection_id~ : String, params? : Map[String, String]) -> WebSocketPeer
}fn async_run(f : async () -> Unit noraise) -> Unitasync fn fetch(url : String, body? : String, http_method : HttpMethod, data? : &Responder, headers? : Map[String, String], credentials? : FetchCredentials, mode? : FetchMode) -> HttpResponsefn security_headers() -> (async (MocketEvent, async () -> &Responder noraise) -> &Responder noraise)Crescent: A web framework for MoonBit.
Dependencies