[][src]Struct amiya::middleware::Router

pub struct Router<Ex> { /* fields omitted */ }

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:

  1. use a empty path router table item: .at("").uses(xxxx).
  2. 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.

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]

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]

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]

impl<T> Borrow<T> for T where
    T: ?Sized
[src]

impl<T> BorrowMut<T> for T where
    T: ?Sized
[src]

impl<T> From<T> for T[src]

impl<T, U> Into<U> for T where
    U: From<T>, 
[src]

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]

type Error = Infallible

The type returned in the event of a conversion error.

impl<T, U> TryInto<U> for T where
    U: TryFrom<T>, 
[src]

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.

impl<V, T> VZip<V> for T where
    V: MultiLane<T>,