205 lines
5.6 KiB
Markdown
205 lines
5.6 KiB
Markdown
# poem-route-macro
|
|
|
|
Provides a simple macro to ease the definition of routes in [Poem].
|
|
|
|
## Example
|
|
|
|
Here is an example use of the macro to construct a Poem [`Route`] that has a number of end-points
|
|
and a nested static files endpoint.
|
|
|
|
```rust
|
|
use poem::{endpoint::StaticFilesEndpoint, EndpointExt, IntoEndpoint, Route};
|
|
|
|
fn build_routes(my_data: MyData) -> impl IntoEndpoint {
|
|
define_routes!(Route::new(), {
|
|
// Nest a static files endpoint
|
|
*"/static" { StaticFilesEndpoint::new("./static") }
|
|
|
|
// Standard routes
|
|
"/" index GET
|
|
"/pastes" paste::pastes GET
|
|
"/pastes/:id" paste::paste GET POST
|
|
|
|
// A nested route for administration
|
|
*"/admin" { admin::build_routes() }
|
|
})
|
|
.data(my_data)
|
|
}
|
|
|
|
// Handlers names are constructed by prefixing the method (followed by an underscore):
|
|
|
|
// This handler is used in the GET "/" route, so we prefix 'get_' to it's name
|
|
pub fn get_index() { ... }
|
|
|
|
mod paste {
|
|
// Corresponds to: paste::pastes GET
|
|
pub fn get_pastes() { ... }
|
|
|
|
// Correspond to: paste::paste GET POST
|
|
pub fn get_paste(_: Path<PasteId>) { ... }
|
|
pub fn post_paste(_: Path<PasteId>) { ... }
|
|
}
|
|
```
|
|
|
|
The above will generate the following code:
|
|
|
|
```rust
|
|
fn build_routes() -> Route {
|
|
Route::new()
|
|
.nest("/static", StaticFilesEndpoint::new("./static"))
|
|
.at("/", poem::get(index::get_index))
|
|
.at("/pastes", poem::get(paste::get_pastes))
|
|
.at("/pastes/:id", poem::get(paste::get_paste).post(post_paste))
|
|
.nest("/admin", admin::build_routes())
|
|
}
|
|
```
|
|
|
|
## How to use
|
|
|
|
The optional first argument to the `define_routes` macro is the expression to which all the routes
|
|
are applied in a builder pattern. If this expression is missing, it defaults to [`Route::new()`].
|
|
As such, these two are equivalent:
|
|
|
|
```rust
|
|
fn build_routes_no_expr() -> Route {
|
|
define_routes!({
|
|
"/" index GET
|
|
})
|
|
}
|
|
|
|
fn build_routes_with_expr() -> Route {
|
|
define_routes!(Route::new(), {
|
|
"/" index GET
|
|
})
|
|
}
|
|
```
|
|
|
|
This can be useful, as you might want to have something _before_ the endpoints are added
|
|
builder-style to the router.
|
|
|
|
After the optional expression, the routes are specified, wrapped in braces.
|
|
|
|
```rust
|
|
define_routes!({
|
|
// routes go here
|
|
})
|
|
```
|
|
|
|
There are two routes supported: nested and normal.
|
|
|
|
### Nested Endpoints
|
|
|
|
A nested route is indicated by an asterisk, followed by the path on which to nest the endpoint.
|
|
|
|
```rust
|
|
define_routes!({
|
|
// A nested route under "/static", e.g. "/static/foo"
|
|
*"/static" { my_nested_router }
|
|
})
|
|
```
|
|
|
|
After the path string there should be an expression block, being some Rust statements in braces.
|
|
This code should evaluate to something that implements [`IntoEndpoint`].
|
|
|
|
This can be useful to nest multiple routers together:
|
|
|
|
```rust
|
|
fn admin_routes() -> Route {
|
|
define_route!({
|
|
"/" admin::index GET
|
|
"/users" admin::users GET POST
|
|
})
|
|
}
|
|
|
|
fn post_routes() -> Route {
|
|
define_route!({
|
|
"/" posts::posts GET
|
|
"/:id" posts::post GET POST
|
|
})
|
|
}
|
|
|
|
fn build_routes() -> Route {
|
|
define_route!({
|
|
*"/admin" { admin_routes() }
|
|
*"/posts" { post_routes() }
|
|
})
|
|
}
|
|
```
|
|
|
|
### Normal Endpoints
|
|
|
|
Normal endpoints are specified by the path string, then the handler name template, followed by a
|
|
list of methods. The handler name template is a Rust path, such as `handler` or `module::handler`.
|
|
This path is more like that found in a `use` statement, in that it cannot include generic parameters
|
|
in any of its segments.
|
|
|
|
The handler name template is modified for each method by prefixing the method, lower-case, to the
|
|
last element in the handler name template, separated by an underscore. For example, if the handler
|
|
name template was `s3::bucket` and the methods where `GET` and `POST`, the handler names generated
|
|
will be `s3::get_bucket` and `s3::post_bucket`
|
|
|
|
Consider the following set of handlers:
|
|
|
|
```rust
|
|
// Let's define a handler in our current module that we'll use for the "/" route.
|
|
#[handler]
|
|
fn get_index() -> poem::Result<()> { todo!() }
|
|
|
|
// Define a couple more handlers for signing in under "/user/signin"
|
|
#[handler]
|
|
fn get_user_signin() -> poem::Result<()> { todo!() }
|
|
#[handler]
|
|
fn post_user_signin() -> poem::Result<()> { todo!() }
|
|
|
|
// Now let's define a couple of handlers for S3 buckets in their own module.
|
|
mod s3 {
|
|
#[handler]
|
|
pub fn get_bucket() -> poem::Result<()> { todo!() }
|
|
|
|
#[handler]
|
|
pub fn post_bucket() -> poem::Result<()> { todo!() }
|
|
}
|
|
```
|
|
|
|
We can then wire-up these handlers with the `define_route` as follows:
|
|
|
|
```rust
|
|
fn build_routes() -> Route {
|
|
define_route!({
|
|
// Handler template 'index' becomes 'get_index'
|
|
"/" index GET
|
|
|
|
// Handler template 'user_signin' becomes 'get_user_signin' and 'post_user_signin'
|
|
"/user/signin" user_signin GET POST
|
|
|
|
// Wire up the S3 bucket handlers the same way.
|
|
"/s3/:bucket" s3::bucket GET POST
|
|
})
|
|
}
|
|
```
|
|
|
|
## Grammar
|
|
|
|
The grammar for this simple routing table DSL is given in the following rough eBNF:
|
|
|
|
```ebnf
|
|
body = { EXPR "," } "{" routes "}" ;
|
|
|
|
routes = route { route } ;
|
|
|
|
route = "*" LIT_STR EXPR_BLOCK
|
|
| LIT_STR path methods
|
|
;
|
|
|
|
path = IDENT { "::" IDENT } ;
|
|
|
|
methods = method { method } ;
|
|
|
|
method = "GET" | "POST" | "DELETE" | "PUT" ;
|
|
```
|
|
|
|
[Poem]: https://github.com/poem-web/poem
|
|
[`Route`]: https://docs.rs/poem/latest/poem/struct.Route.html
|
|
[`Route::new()`]: https://docs.rs/poem/latest/poem/struct.Route.html#method.new
|
|
[`IntoEndpoint`]: https://docs.rs/poem/latest/poem/endpoint/trait.IntoEndpoint.html
|