One thing someone might notice is how boilerplate we need to write comparing to other programming languages, specially for the web, however, there is some code we need to write, like types, that many of us agree are necessary to improve the development process.
We can reduce code in other areas and we have macros that allows us to do that, however we need to be careful, because macros can make our code hard to maintain. So, use macros sparingly.
Let's create a macro to reduce the amount of code we write for a handler. At first it might seem overwhelming, but, once you understand them, they can be a powerful tool.
src/handlers/function_handler.rs
:
macro_rules! function_handler {
( $handler_name:ident ($($arg:ident:$typ:ty),*) -> $body:expr) => {
pub fn $handler_name(user: LoggedUser, pool: web::Data<PgPool>, $($arg:$typ,)*)
-> impl Future<Item = HttpResponse, Error = actix_web::Error>
{
web::block(move || {
let pg_pool = pool
.get()
.map_err(|_| {
crate::errors::MyStoreError::PGConnectionError
})?;
$body(user, pg_pool)
})
.then(|res| match res {
Ok(data) => Ok(HttpResponse::Ok().json(data)),
Err(_) => Ok(HttpResponse::InternalServerError().into()),
})
}
};
}
We declare a macro with macro_rules!
, you could even create new syntax with a macro, in our case we want to create handlers following the next syntax: handler name (params) -> block of code
, that can be expressed in this way: $handler_name:ident ($($arg:ident:$typ:ty),*) -> $body:expr
, $handler_name
is an identifier, the name of our handler, then we pass the parameters, because we don't know how many of them are, we are separating them with a comma and use this syntax: $(),*
, this is to tell Rust we have infinity amount of parameters. Now we need a way to tell Rust to split parameters from block of code, I'm using an arrow to achieve that.
We identify our closure as $body
and then pass the parameters so we can use it in our handlers, specifically the logged user and the database connection.
Now compare this:
pub fn index(user: LoggedUser, pool: web::Data<PgPool>)
-> impl Future<Item = HttpResponse, Error = actix_web::Error> {
web::block(move || {
let pg_pool = pool
.get()
.map_err(|_| {
crate::errors::MyStoreError::PGConnectionError
})?;
PriceList::list(user.id, &pg_pool)
})
.then(|res| match res {
Ok(data) => Ok(HttpResponse::Ok().json(data)),
Err(error) => Err(actix_web::error::ErrorInternalServerError(error)),
})
}
with this:
function_handler!(
index () -> (|user: LoggedUser, pg_pool: PgPooledConnection| {
PriceList::list(user.id, &pg_pool)
})
);
Pretty cool, right?
Source code here.
Top comments (3)
Great use of macros, I hadn't thought to try it for web handlers! I think
macro_rules!
has been one of my favorite things to learn about in Rust.Yeah!,me too, although I had to remind me to be very careful because it could become some spaghetti monster hard to maintain, like github.com/bitex-la/bitprim-rust/b...
Oof, that looks hellish to debug - and the error messages are not helping you out much! I've started dabbling with nested macros and already it feels little too stringy for my tastes - so useful though!