1 unstable release
0.1.0 | Jun 13, 2020 |
---|
#35 in #dispatch
21KB
301 lines
match_request
Macros for creating request routers in hyper.
Request paths can be matched using a more convenient readable syntax, or using full regular expressions. Specific methods can be listed under each path.
The return value of a match can be anything, though you'll usually want to return a boxed function to use as a view.
Pattern syntax
Matching a request path:
use match_request::match_request;
use hyper::Method;
let path = "/user/home";
let (value, params) = match_request!(&Method::GET, path, {
"/login" => {
GET => "serve login form",
},
"/user/home" => {
GET => "serve home page",
}
}).unwrap();
assert_eq!(value, "serve home page");
Matching against multiple methods:
use match_request::match_request;
use hyper::Method;
let path = "/example";
let (value, params) = match_request!(&Method::DELETE, path, {
"/login" => {
GET => "serve login form",
POST => "attempt login",
},
"/example" => {
GET => "serve example page",
DELETE => "delete example page",
}
}).unwrap();
assert_eq!(value, "delete example page");
Named path parameters:
use match_request::match_request;
use hyper::Method;
let path = "/posts/2020/my-blog-post";
let (value, params) = match_request!(&Method::GET, path, {
"/posts/:year/:slug" => {
GET => "serve blog post",
},
}).unwrap();
assert_eq!(params.get("year"), Some("2020"));
assert_eq!(params.get("slug"), Some("my-blog-post"));
Capturing the path tail:
use match_request::match_request;
use hyper::Method;
let path = "/static/vendor/img/icon.png";
let (value, params) = match_request!(&Method::GET, path, {
"/static/*" => {
GET => "serve static assets",
},
}).unwrap();
// NOTE: the leading `/` is included
assert_eq!(params.tail(), Some("/vendor/img/icon.png"));
Example router
use match_request::{match_request, Error, Params};
use hyper::{Request, Response, Body};
use futures::future::{Future, BoxFuture};
// A boxed type definition for your async views.
type BoxedView = Box<
dyn Fn(Request<Body>, Params) ->
BoxFuture<'static, Response<Body>>
>;
// Like a regular `match` expression, `match_request` requires all
// arms to return the same type. As each `async fn` will have a different
// type, you'll likely need to put them in a Box first. This example macro
// is one way to do it.
macro_rules! view {
($closure:expr) => {{
#[allow(unused_mut)]
let mut closure = $closure;
let b: BoxedView = Box::new(move |req, params| {
Box::pin(closure(req, params))
});
b
}};
}
// An example request router.
async fn router(req: Request<Body>) -> Result<Response<Body>, Error> {
let method = req.method();
let path = req.uri().path();
// Attempt to match the request to a view.
let (handler, params) = match_request!(method, path, {
"/foo/bar" => {
GET => view!(foo_bar),
},
"/user/:name" => {
GET => view!(user_profile),
}
})?;
// Execute the view.
Ok(handler(req, params).await)
}
// Example views...
async fn foo_bar(_req: Request<Body>, _params: Params) -> Response<Body> {
Response::new(Body::from("Foo bar"))
}
async fn user_profile(_req: Request<Body>, params: Params) -> Response<Body> {
// Extracting a parameter from the path.
let name = params.get("name").unwrap();
Response::new(Body::from(format!("Profile for {}", name)))
}
Dependencies
~12MB
~211K SLoC