1 unstable release

0.1.0 Jun 13, 2020

#35 in #dispatch

MIT license

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