///|
struct Router {
routes : Array[Route]
openapi_routes : Array[OpenApiRoute]
}
///|
priv struct Route {
meth : @http.RequestMethod
path : String
handler : @moonback.Handler
params_schema : @json.Json
query_schema : @json.Json
headers_schema : @json.Json
body_schema : @json.Json
response_schema : @json.Json
}
///|
pub fn Router::Router() -> Router {
{ routes: [], openapi_routes: [], }
}
///|
pub fn[
P : FromParam,
Q : FromQuery,
H : FromHeaders,
B : FromBody,
R : ToResponse,
] Router::add(
self : Router,
path : String,
meth : @http.RequestMethod,
handler : (P, Q, H, B) -> R,
) -> Unit {
self.routes.push({
meth,
path,
params_schema: P::schema(),
query_schema: Q::schema(),
headers_schema: H::schema(),
body_schema: B::schema(),
response_schema: R::schema(),
handler: (req, res) => {
let response = try {
let params = req.params.to_array()
// FromParam pops from the end; reverse captures to preserve path order.
params.rev_in_place()
let params : P = FromParam::from_param(params)
let query : Q = FromQuery::from_query(req.query())
let headers : H = FromHeaders::from_headers(req.headers)
let body : B = FromBody::from_body(req.body)
ToResponse::to_response(handler(params, query, headers, body))
} catch {
ResponseMismatch(_) =>
Response(status=500, headers=@moonback.Headers::default())
HandlerError::_ as e =>
Response(
status=400,
headers=[("Content-Type", "text/plain; charset=utf-8")],
body=e.to_string(),
)
error => raise error
}
let writer = res.respond(status=response.status, headers=response.headers)
if req.meth != Head && response.body is Some(body) {
writer.write(body)
}
},
})
}
///|
pub fn[P : FromParam, Q : FromQuery, H : FromHeaders, R : ToResponse] Router::get(
self : Router,
path : String,
handler : (P, Q, H) -> R,
) -> Unit {
self.add(path, Get, (params, query, headers, _body : Unit) => {
handler(params, query, headers)
})
}
///|
pub fn[
P : FromParam,
Q : FromQuery,
H : FromHeaders,
B : FromBody,
R : ToResponse,
] Router::post(
self : Router,
path : String,
handler : (P, Q, H, B) -> R,
) -> Unit {
self.add(path, Post, handler)
}
///|
pub fn[
P : FromParam,
Q : FromQuery,
H : FromHeaders,
B : FromBody,
R : ToResponse,
] Router::put(
self : Router,
path : String,
handler : (P, Q, H, B) -> R,
) -> Unit {
self.add(path, Put, handler)
}
///|
pub fn[
P : FromParam,
Q : FromQuery,
H : FromHeaders,
B : FromBody,
R : ToResponse,
] Router::patch(
self : Router,
path : String,
handler : (P, Q, H, B) -> R,
) -> Unit {
self.add(path, Patch, handler)
}
///|
pub fn[
P : FromParam,
Q : FromQuery,
H : FromHeaders,
B : FromBody,
R : ToResponse,
] Router::delete(
self : Router,
path : String,
handler : (P, Q, H, B) -> R,
) -> Unit {
self.add(path, Delete, handler)
}
///|
pub fn[P : FromParam, Q : FromQuery, H : FromHeaders, R : ToResponse] Router::head(
self : Router,
path : String,
handler : (P, Q, H) -> R,
) -> Unit {
self.add(path, Head, (params, query, headers, _body : Unit) => {
handler(params, query, headers)
})
}
///|
pub fn[P : FromParam, Q : FromQuery, H : FromHeaders, R : ToResponse] Router::options(
self : Router,
path : String,
handler : (P, Q, H) -> R,
) -> Unit {
self.add(path, Options, (params, query, headers, _body : Unit) => {
handler(params, query, headers)
})
}
///|
pub impl @moonback.Extension for Router with fn initialize(self, ctx) {
for route in self.routes {
ctx.add_route(route.meth, route.path, route.handler)
}
for route in self.openapi_routes {
let components = route.components
let document = self.generate_openapi(
title=route.title,
version=route.version,
components?,
)
ctx.get(route.url, (_request, response) => response.send_json(document))
}
}
///|
pub extend Router with @moonback.Extension::{initialize}