Reputation: 1854
The Rouille hello world example shows how to use the router!
macro for a fixed set of routes.
The following example code illustrates the need to be able to "bootstrap" routes from a database or from pluggable code - which I've currently been able to do with the Iron web framework:
pub struct Route {
pub http_method: String,
pub url_path: String,
pub callback_func: fn(_: &mut Request) -> IronResult<Response>,
}
impl Route {
pub fn new(m: String, u: String, f: fn(_: &mut Request) -> IronResult<Response>) -> Route {
Route {
http_method: m,
url_path: u,
callback_func: f,
}
}
}
fn main() {
// router is an Iron middleware
let mut router = Router::new();
// prepare routes for bootstrapping into the Iron router
let r1 = Route::new("get".to_string(), "/*".to_string(), my_callback_func);
let r2 = Route::new("get".to_string(), "/".to_string(), my_callback_func);
let mut routes = Vec::new();
routes.push(r1);
routes.push(r2);
for route in routes.iter_mut() {
if route.http_method == "get" {
// passes each route to the Iron router
router.get(&route.url_path, (&*route).callback_func);
} // else if, put, post, delete...
}
Iron::new(router).http("localhost:3000").unwrap();
}
fn my_callback_func(_: &mut Request) -> IronResult<Response> {
//...
}
Although I'm reading up on macros in Rust, I do not have a good enough understanding of Rouille's router!
macro, Rust or macros in general, to figure out how to achieve the equivalent with Rouille.
Upvotes: 0
Views: 449
Reputation: 431001
If you examine the main source of the router
macro, it's long-ish but not supremely complicated:
($request:expr, $(($method:ident) ($($pat:tt)+) => $value:block,)* _ => $def:expr) => {
{
let request = &$request;
// ignoring the GET parameters (everything after `?`)
let request_url = request.url();
let request_url = {
let pos = request_url.find('?').unwrap_or(request_url.len());
&request_url[..pos]
};
let mut ret = None;
$({
if ret.is_none() && request.method() == stringify!($method) {
ret = router!(__check_pattern request_url $value $($pat)+);
}
})+
if let Some(ret) = ret {
ret
} else {
$def
}
}
};
In words, it takes a request, zero-or-more patterns to match, and a default. It gets ahold of the URL, then dispatches to the other arms of the macro to see if the URL matches the path and does some recursive trickery to define some variables with components of the path. Whichever arm matches first will set the return value, and if nothing matches, the default will be used.
Unfortunately, the macro expects ident
s for the methods and the paths, so basically you cannot use it with expressions. This means we can't pass in variables or literals like "foo"
. This makes it very difficult for you.
So, we do what all good programmers do: copy and paste the code. Lifting chunks out of the macro and repurpose them:
#[macro_use]
extern crate rouille;
use rouille::Request;
use rouille::Response;
struct Route(&'static str, &'static str, fn(&Request) -> Response);
fn main() {
let routes = [
Route("GET", "/one", do_one),
Route("GET", "/two", do_two),
];
rouille::start_server("0.0.0.0:9080", move |request| {
let mut result = None;
let request = &request;
// ignoring the GET parameters (everything after `?`)
let request_url = request.url();
let request_url = {
let pos = request_url.find('?').unwrap_or(request_url.len());
&request_url[..pos]
};
for &Route(method, path, f) in &routes {
if result.is_none() {
// This checking of the path is terrible, limited, and hacky
if request.method() == method && request_url.ends_with(path) {
result = Some(f(request));
}
}
}
result.unwrap_or_else(|| Response::text("Default!"))
});
}
fn do_one(_: &Request) -> Response {
Response::text("hello world one")
}
fn do_two(_: &Request) -> Response {
Response::text("hello world two")
}
This runs the various handlers for /one
, /two
and everything else.
I'm no expert in Rouille, in fact I've never used it before today, but it certainly seems like you are trying to use it for something beyond what it is currently designed for. Perhaps this is deliberate and the authors are attempting to present a very opinionated tool. Perhaps it is accidental and they haven't thought of this use case. Perhaps it is temporary, and they just haven't gotten around to it.
Either way, I'd suggest asking the authors. If it's not an intended use-case, they can update the project docs to clearly state so. Otherwise they might create issues to implement the feature, and you could be instrumental in helping design it.
Upvotes: 2