RabitLogic/mbit/core does not have a README file
pub trait IRoutes {
fn get(String, Array[async (Context) -> Unit]) -> Unit
fn post(String, Array[async (Context) -> Unit]) -> Unit
fn put(String, Array[async (Context) -> Unit]) -> Unit
fn del(String, Array[async (Context) -> Unit]) -> Unit
fn patch(String, Array[async (Context) -> Unit]) -> Unit
fn head(String, Array[async (Context) -> Unit]) -> Unit
fn options(String, Array[async (Context) -> Unit]) -> Unit
fn any(String, Array[async (Context) -> Unit]) -> Unit
fn handle(Array[Method], String, Array[async (Context) -> Unit]) -> Unit
fn use_mw(async (Context) -> Unit) -> Unit
}pub(all) struct Config {
values : Map[String, ConfigValue]
env : AppEnvironment
env_prefix : String
}let cfg = Config::from_env()
let port = cfg.get_int("PORT", default=8080)pub(all) enum ConfigValue {
ConfigString(String)
ConfigInt(Int64)
ConfigBool(Bool)
ConfigFloat(Double)
} derive(Debug)pub(all) struct Context {
req : Request
reader : &Reader
conn : ResponseConn
params : Map[String, String]
query_cache : Map[String, String]?
body : String?
body_bytes : Bytes?
post_form_cache : Map[String, String]?
handlers : Array[async (Context) -> Unit]
index : Int
status_code : Int
written : Bool
aborted : Bool
size : Int
store : Map[String, Json]
errors : Array[String]
headers : Map[String, String]
}let session = ctx.cookie("session_id")ctx.data_from_reader(200, content_length, "application/octet-stream", reader)ctx.file(200, "application/pdf", "./reports/annual.pdf")match ctx.negotiate_format(["application/json", "text/html"]) {
Some("application/json") => ctx.json(200, data)
Some("text/html") => ctx.html(200, "<h1>Hello</h1>")
_ => () // 406 already set
}match ctx.form_file("avatar") {
Some(content) => ctx.save_uploaded_file(content, "./uploads/avatar.png")
None => ctx.abort_with_status(400, "No file uploaded")
}ctx.set_cookie("session_id", "abc123", max_age=3600, path="/", http_only=true)// Basic SSE
ctx.sse("message", "hello world")ctx.sse_keepalive("ping")ctx.stream(200, "text/event-stream", fn(write) {
for i = 0; i < 10; i = i + 1 {
write("data: chunk " + i.to_string() + "\n\n")
}
})ctx.string_f(200, "Hello {0}, you are {1} years old", ["Alice", "30"])ctx.xml(200, Json::object(H([
("user", Json::object(H([
("name", Json::string("Alice")),
("age", Json::string("30")),
]))),
])))
// Output: <root><user><name>Alice</name><age>30</age></user></root>pub(all) struct Engine {
router : Router
handle_method_not_allowed : Bool
redirect_trailing_slash : Bool
redirect_fixed_path : Bool
remove_extra_slash : Bool
use_raw_path : Bool
unescape_path_values : Bool
max_multipart_memory : Int64
remote_ip_headers : Array[String]
trusted_platform : String
forwarded_by_client_ip : Bool
trusted_proxies : Array[String]
}app.any("/api/health", [health_check])app.get("/api/articles", [list_articles])
app.get("/api/articles/:id", [auth, get_article]) // with middlewarelet api = app.group("/api")
api.use(auth_middleware)
api.get("/profile", [get_profile])app.handle([GET, POST], "/api/form", [handle_form])app.health()for route in app.routes() {
println("\{route.method} \{route.path}")
}app.run_default()app.run_fd(3) // Listen on fd 3 passed by systemdfn Engine::run_listener(_self : Engine, _handler : async (Request, ServerConnection) -> Unit) -> Unitapp.run_listener(fn(request, reader, conn) {
// custom pre-processing
app.router.dispatch(request, reader, conn)
})app.run_tls("0.0.0.0:443", "./cert.pem", "./key.pem")app.run_unix("/tmp/mbit.sock")app.no_route([not_found_handler])
app.run(addr)app.run_with_shutdown("0.0.0.0:8080")async fn Engine::serve_http(self : Engine, req : Request, reader : &Reader, conn : ServerConnection) -> Unit// In a health check or admin endpoint:
app.shutdown(cleanup=fn() {
println("Cleaning up resources...")
})app.static_file("/favicon.ico", "./assets/favicon.ico")app.static_files_engine("/assets", "./public")app.use(@mbit.logger())
app.use(@mbit.cors(@mbit.CORSConfig::default()))let rules = [FieldRules("username", [Required, Min(3), Max(20)]),
FieldRules("email", [Required, Email]),
FieldRules("age", [Min(0), Max(150)])]group.any("/health", [health_check])let api = app.group("/api/v1")
println(api.base_path()) // "/api/v1"let assets = app.group("/assets")
assets.static_file("/favicon.ico", "./public/favicon.ico")let assets = app.group("/assets")
assets.static("/css", "./public/css")let assets = app.group("/assets")
assets.static_fs("/css", "./public/css")pub(all) struct Logger {
}Logger::debug("Cache miss", fields=[("key", "user:42")])pub(all) enum MbitError {
BindError(String)
ValidationError(String)
NotFound(String)
InternalError(String)
} derive(Debug)fn MetricsCollector::record(self : MetricsCollector, status : Int, path : String, latency_ms : Int64) -> Unitasync fn ResponseConn::send_response(self : ResponseConn, code : Int, reason : String, extra_headers? : Map[String, String]) -> Unitpub(all) struct RouteInfo {
meth : String
path : String
handler_count : Int
}router.any("/api/health", [health_check])async fn Router::dispatch(self : Router, req : Request, reader : &Reader, conn : ServerConnection) -> Boollet routes = router.routes()
for route in routes {
println("\{route.method} \{route.path}")
}pub(all) enum Rule {
Required
Min(Int)
Max(Int)
Len(Int)
Pattern(String)
OneOf(Array[String])
Email
URL
Custom((String) -> String?)
}pub(all) struct ServerConfig {
host : String
port : Int
database_url : String
redis_url : String
log_level : String
log_format : String
secret_key : String
cors_origins : Array[String]
max_upload_size : Int64
rate_limit : Int
rate_limit_window : Int
tls_cert_file : String
tls_key_file : String
environment : AppEnvironment
debug : Bool
}pub(all) struct TestConn {
status : Int
reason : String
headers : Map[String, String]
body : String
header_sent : Bool
ended : Bool
}pub(all) enum WebSocketMessage {
Text(String)
Binary(String)
Close(Int, String)
Ping(String)
Pong(String)
} derive(Debug)struct ContactForm {
name : String
email : String
message : String
} derive(FromJson)
let form : ContactForm? = bind_form(ctx)struct Headers {
content_type : String
authorization : String
} derive(FromJson)
let hdrs : Headers? = bind_header(ctx)struct LoginRequest {
username : String
password : String
} derive(FromJson)
let req : LoginRequest? = bind_json(ctx)struct ArticleParams {
id : String
slug : String
} derive(FromJson)
// Route: /article/:id/:slug
let params : ArticleParams? = bind_path(ctx)fn clean_path(path : String) -> Stringlet ctx = create_test_context("GET", "/test")
assert_eq(ctx.method(), "GET")fn debug_print(msg : String) -> Unitfn debug_print_default() -> Unitfn debug_print_route(meth : String, path : String) -> Unitfn disable_bind_validation() -> Unitfn enable_bind_validation() -> Unitfn guess_content_type(path : String) -> Stringctx.json(200, Json::object(h([
("message", Json::string("hello")),
("code", Json::number(200.0)),
])))fn is_bind_validation_enabled() -> Boolapp.use(logger())let metrics = MetricsCollector::new()
app.use(metrics_middleware(metrics))
app.get("/metrics", [fn(ctx) { ctx.json(200, metrics.to_json()) }])app.use(recovery())fn search(node : Node, segments : Array[String], idx : Int, params : Map[String, String]) -> MatchResult?fn status_text(code : Int) -> Stringapp.use(structured_logger(StructuredLogConfig::default()))fn[T] validate(_value : T) -> String?let values = Map([("username", "alice"), ("email", "alice@example.com")])
let rules = [
FieldRules::new("username", [Required, Min(3)]),
FieldRules::new("email", [Required, Email]),
]
let errors = validate_map(values, rules)
if errors.has_errors() {
ctx.json(400, errors.to_json())
return
}async fn validate_or_abort(ctx : Context, values : Map[String, String], rules : Array[FieldRules]) -> Boollet values = Map([("username", "alice")])
let rules = [FieldRules::new("username", [Required, Min(3)])]
if !validate_or_abort(ctx, values, rules) {
return
}
// Proceed with valid data...app.get("/ws", [websocket_handler(fn(ws) {
ws.send_text("Hello via WebSocket!")
loop {
match ws.read() {
Some(WebSocketMessage::Text(t)) => ws.send_text("Echo: " + t)
Some(WebSocketMessage::Close(_, _)) => break
_ => ()
}
}
})])A Gin-inspired web framework for MoonBit — fast, lightweight, idiomatic.
Dependencies