///|
/// Convert JS method string to @router.Method
/// Unknown methods are logged (if debug enabled) and default to GET
fn js_method_to_router_method(meth : String) -> @router.Method {
match meth {
"GET" => @router.Method::Get
"POST" => @router.Method::Post
"PUT" => @router.Method::Put
"DELETE" => @router.Method::Delete
"PATCH" => @router.Method::Patch
"HEAD" => @router.Method::Head
"OPTIONS" => @router.Method::Options
"CONNECT" => @router.Method::Connect
"TRACE" => @router.Method::Trace
"QUERY" => @router.Method::Query
_ => {
if is_debug() {
println(
"[mars] Warning: Unknown HTTP method '\{meth}' for routing, treating as GET",
)
}
@router.Method::Get
}
}
}
///|
/// Build default headers for not found responses
fn build_not_found_headers() -> Map[String, String] {
{ "Content-Type": "text/plain" }
}
///|
/// Build default headers for error responses
fn build_error_headers() -> Map[String, String] {
{ "Content-Type": "text/plain" }
}
///|
pub type AppHandler = (JsRequest) -> @js_async.Promise[JsResponse]
///|
/// Keep imports used on JS target for deny-warn checks.
fn keep_js_target_imports() -> Unit {
ignore(@async_io.Encoding::UTF8)
ignore(@encoding.UTF8)
}
///|
/// Convert Server app to a platform handler function
pub fn Server::to_handler(self : Server) -> AppHandler {
self.to_handler_with_env(EmptyEnv::new(), ExecutionContext::new())
}
///|
/// FFI to trigger coroutine scheduler
/// moonbitlang/async's Promise::from_async doesn't reschedule after spawn,
/// so we need to manually trigger it for JS target
extern "js" fn ffi_reschedule() -> Unit =
#| () => {
#| // Use moonbitlang/async's event_loop reschedule for proper coroutine context
#| moonbitlang$async$internal$event_loop$$reschedule();
#| }
///|
/// Convert Server app to a platform handler function with environment and execution context
pub fn[E : Env, C : ExecCtx] Server::to_handler_with_env(
self : Server,
env : E,
exec_ctx : C,
) -> AppHandler {
keep_js_target_imports()
fn(js_request : JsRequest) -> @js_async.Promise[JsResponse] {
// Create async handler using Promise::from_async
let promise = @js_async.Promise::from_async(async fn() -> JsResponse {
// Convert JsRequest to @http.Request
let request = js_request_to_http_request(js_request)
let meth_str = js_request_method(js_request)
let meth = js_method_to_router_method(meth_str)
// We need to capture the response in a ref
let response_ref : Ref[JsResponse?] = { val: None }
let resolve = fn(resp : JsResponse) { response_ref.val = Some(resp) }
let routing_path = strip_query_string(request.path)
let dispatch_result = self.match_and_dispatch(meth, routing_path, fn(
params,
) {
Context::with_env_js(
request, params, js_request, resolve, env, exec_ctx,
)
})
let dispatch = match dispatch_result {
RequestDispatchResult::NotFound =>
return js_response_new("Not Found", 404, build_not_found_headers())
RequestDispatchResult::Dispatched(d) => d
}
match dispatch_fallback_status(dispatch) {
Some(500) =>
return js_response_new(
"Internal Server Error",
500,
build_error_headers(),
)
Some(404) => ()
_ => ()
}
// Return response resolved by Context, or fallback when not sent.
match response_ref.val {
Some(resp) => resp
None =>
if dispatch.response_sent {
js_response_new("Internal Server Error", 500, build_error_headers())
} else {
js_response_new("Not Found", 404, build_not_found_headers())
}
}
})
// Trigger coroutine scheduler for JS target
ffi_reschedule()
promise
}
}
///|
/// Convert Server app to a fetch handler function
/// Kept for backward compatibility
pub fn Server::to_fetch_handler(
self : Server,
) -> (JsRequest) -> @js_async.Promise[JsResponse] {
self.to_handler()
}
///|
/// Convert Server app to a fetch handler function with environment and execution context
/// Kept for backward compatibility
pub fn[E : Env, C : ExecCtx] Server::to_fetch_handler_with_env(
self : Server,
env : E,
exec_ctx : C,
) -> (JsRequest) -> @js_async.Promise[JsResponse] {
self.to_handler_with_env(env, exec_ctx)
}