[−][src]Struct amiya::middleware::Router
The middleware for request diversion by path.
Concepts
There also are some important concepts need to be described first.
Router
A Router
is some component that dispatch your Request
to different handler(inner
middleware) by looking it's path
.
So a router may store several middleware and choose zero or one of them for you when a request
comes. If Request
's path match a router table item, it delegate Context
to that
corresponding middleware and do nothing else. If no item matches, it set Response
to 404 Not Found
and no stored middleware will be executed.
Router Table
Router
has a Path => Middleware
table to decided which middleware is respond for a
request.
Each table item has different path, if you set a path twice, old one will replace the first.
Each path, is a full part in path when split by /
, that is, if you set a item "abc" => middleware A
, the path /abcde/somesub
will not be treated as a match. Only "/abc", "/abc/",
"/abc/xxxxx/yyyy/zzzz" will.
You can use at
method to edit router table, for example: at("abc")
will start a router
table item edit environment for sub path /abc
.
Any Item
There is a special router item called any
, you can set it by use at("{arg_name}")
.
This item will match when all router table items do not match the remain path, and remain path
is not just /
. That is, remain path have sub folder.
At this condition, the any
item will match next sub folder, and store this folder name as a
match result in Context
, you can use Context::arg
to get it.
see examples/arg.rs
for a example code.
Endpoint
Except router table, each has a endpoint middleware to handler condition that no more remain path can be used to determine which table item should be used.
A example:
let router = Router::new() .endpoint() .get(m!(ctx => ctx.resp.set_body("hit endpoint");)) .at("v1").is(m!(ctx => ctx.resp.set_body("hit v1");)); let main_router = Router::new().at("api").is(router); let amiya = amiya::new().uses(main_router);
The hit endpoint
will only be returned when request path is exactly /api
, because after
first match by main_router
, remain path is empty, we can't match sub path on empty string.
Fallback
With the example above, if request path is /api/
, the endpoint is not called, and because
the v1
item do not match remain path /
too, so there is a mismatch.
If we do not add any code, this request will get a 404 Not Found
response. We have two option
too add a handler for this request:
- use a empty path router table item:
.at("").uses(xxxx)
. - use a fallback handler:
.fallback().uses(xxxxx)
.
When remain path is not empty, but we can't find a matched router item, the fallback handler will be executed(only if we have set one, of course).
So if we choose the second option, the fallback is respond to all mismatched item, sometime this is what you want, and sometime not. Make sure choose the approch meets your need.
API Design
Because router can be nest, with many many levels, we need many code, many temp vars to build a multi level router. For reduce some ugly code, we designed a fluent api to construct this dendritical structure.
As described above, a Router
has three property:
- Endpoint handler
- Router table
- Fallback handler
And we have three methods foo them:
Editing Environment
Let's start at the simplest method fallback
.
When we call fallback
on a router, we do not set the middleware for this property, instead,
we enter the fallback editing environment of this router.
In a edit environment, we can use serveral method to finish this editing and exit environment.
- any method of a
MethodRouter
likeget
,post
,delete
, etc.. uses
medhod of that non-public environment type.
A finish method comsumes the environment, set the property of editing target and returns it. So we can enter other propertie's editing environment to make more changes to it.
The endpoint
editing enviorment is almost the same, except it sets the endpint handler.
But at
method has a little difference. It does not enter router table editing environmen of
self
, but enter the endpoint
editing environment of that corresponding router table item's
middleware, a Router
by default.
If we finish this editing, it returns a type representing that Router with endpoint
set by the
finish method. But we do not have to finish it. We can use is
method to use a
custom middleware in that router table item directly.
And a Router table item's endpoint editing environment also provided fallback
and at
method
to enter the default sub Router's editing environment quickly without endpoint be setted.
if we finish set this sub router, a call of done
method can actually add this item to parent's
router table and returns parent router.
example:
async fn xxx(ctx: Context<'_, ()>) -> Result { Ok(()) } // some middleware func #[rustfmt::skip] let router = Router::new() // | this enter router table item "root"'s default router's endpoint // v editing environment .at("root") // | set "root" router's endpoint only support GET method, use middleware xxx // v this will return a type representing sub router .get(m!(xxx)) // | end .fallback() // <-- enter sub router's fallback editing endpoint // | set sub router's endpoint do not consider HTTP method and use middleware xxx directly // v This method returns the type representing sub router again .uses(m!(xxx)) // | enter sub sub router's endpoint editing environment // v .at("sub") .is(m!(xxx)) // `is` make "sub" path directly uses xxx and do not need a router .done() // `done` finish "root" router editing and returns the router we created first .at("another") // we can continue add more item to the Router .is(m!(xxx)); // but for short we use a is here and finish router build.
Evert at
has a matched done
or is
, remember this, then you can use this API to build a
router tree without any temp variable.
Because that rustfmt
align code using .
, and all chain method call have same indent.
No indent means no multi level view, no level means we need to be very careful when add
new path to old router. So I recommend use #[rustfmt::skip]
to pervent
rustfmt
to format the router creating code section and indent router code by hand.
Examples
see examples/router.rs
, examples/arg.rs
and examples/subapp.rs
.
Implementations
impl<Ex> Router<Ex>
[src]
pub fn new() -> Self
[src]
Create new Router middleware.
pub fn endpoint(self) -> RouterSetter<Self, SetEndpoint, Ex>
[src]
Enter endpoint edit environment.
pub fn at<P: Into<Cow<'static, str>>>(
self,
path: P
) -> RouterSetter<RouterSetter<Self, SetTableItem, Ex>, SetEndpoint, Ex>
[src]
self,
path: P
) -> RouterSetter<RouterSetter<Self, SetTableItem, Ex>, SetEndpoint, Ex>
Add a new item for path
to router table and enter endpoint edit environment of
that item.
pub fn fallback(self) -> RouterSetter<Self, SetFallback, Ex>
[src]
Enter fallback edit environment.
Trait Implementations
impl<Ex> Default for Router<Ex>
[src]
impl<Ex> Middleware<Ex> for Router<Ex> where
Ex: Send + Sync + 'static,
[src]
Ex: Send + Sync + 'static,
Auto Trait Implementations
impl<Ex> !RefUnwindSafe for Router<Ex>
impl<Ex> Send for Router<Ex>
impl<Ex> Sync for Router<Ex>
impl<Ex> Unpin for Router<Ex>
impl<Ex> !UnwindSafe for Router<Ex>
Blanket Implementations
impl<T> Any for T where
T: 'static + ?Sized,
[src]
T: 'static + ?Sized,
impl<T> Borrow<T> for T where
T: ?Sized,
[src]
T: ?Sized,
impl<T> BorrowMut<T> for T where
T: ?Sized,
[src]
T: ?Sized,
fn borrow_mut(&mut self) -> &mut T
[src]
impl<T> From<T> for T
[src]
impl<T, U> Into<U> for T where
U: From<T>,
[src]
U: From<T>,
impl<T> Same<T> for T
type Output = T
Should always be Self
impl<T, U> TryFrom<U> for T where
U: Into<T>,
[src]
U: Into<T>,
type Error = Infallible
The type returned in the event of a conversion error.
fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>
[src]
impl<T, U> TryInto<U> for T where
U: TryFrom<T>,
[src]
U: TryFrom<T>,
type Error = <U as TryFrom<T>>::Error
The type returned in the event of a conversion error.
fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>
[src]
impl<V, T> VZip<V> for T where
V: MultiLane<T>,
V: MultiLane<T>,