[][src]Trait hyperdrive::FromRequest

pub trait FromRequest: Sized {
    type Context: RequestContext;
    type Future: Future<Item = Self, Error = BoxedError> + Send;
    fn from_request_and_body(
        request: &Arc<Request<()>>,
        body: Body,
        context: Self::Context
    ) -> Self::Future; fn from_request(
        request: Request<Body>,
        context: Self::Context
    ) -> Self::Future { ... }
fn from_request_sync(
        request: Request<Body>,
        context: Self::Context
    ) -> Result<Self, BoxedError> { ... } }

Trait for asynchronous conversion from HTTP requests.

#[derive(FromRequest)]

This trait can be derived for enums to generate a request router and decoder. Here's a simple example:

use hyperdrive::{FromRequest, body::Json};

#[derive(FromRequest)]
enum Routes {
    #[get("/")]
    Index,

    #[get("/users/{id}")]
    User { id: u32 },

    #[post("/login")]
    Login {
        #[body]
        data: Json<Login>,
    },
}

#[derive(Deserialize)]
struct Login {
    email: String,
    password: String,
}

Calling Routes::from_request will result in Routes::Index for a GET / request, and in Routes::User for a GET /users/123 request, for example. A POST /login request will end up as Routes::Login, decoding the POSTed JSON body.

The generated FromRequest implementation will always use DefaultFuture<Self, BoxedError> as the associated Result type.

Note that the generated implementation will make use of .and_then() to chain asynchronous operations instead of running them in parallel using join_all. This is because it simplifies the code and doesn't require making use of boxed futures everywhere in the generated code. Multiple requests will still be handled in parallel, so this should not negatively affect performance.

In order to keep the implementation simple and user code more easily understandable, overlapping paths are not allowed (unless the paths are exactly the same, and the method differs), so the following will fail to compile:

This example deliberately fails to compile
use from_request::{FromRequest, body::Json};

#[derive(FromRequest)]  //~ ERROR: route `#[get("/users/me")]` overlaps with ...
enum Routes {
    #[get("/users/{id}")]
    User { id: u32 },

    #[get("/users/me")]
    Me,
}

To fix this, you can define a custom type implementing FromStr and use that:

use hyperdrive::FromRequest;

#[derive(FromRequest)]
enum Routes {
    #[get("/users/{id}")]
    User { id: UserId },
}

enum UserId {
    /// User by database ID.
    Id(u32),
    /// The currently logged-in user.
    Me,
}

impl FromStr for UserId {
    type Err = ParseIntError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s == "me" {
            Ok(UserId::Me)
        } else {
            Ok(UserId::Id(s.parse()?))
        }
    }
}

Implicit HEAD routes

The custom derive will create a HEAD route for every defined GET route, unless you define one yourself. If your app uses AsyncService or SyncService, those adapters will automatically take care of dropping the body from the response to HEAD requests. If you manually call FromRequest::from_request, you have to make sure no body is sent back for HEAD requests.

Extracting Request Data

The custom derive provides easy access to various kinds of data encoded in a request:

  • The Request path (/users/or/other/stuff)
  • Query parameters (?name=val)
  • The request body

Extracting Path Segments ({field} syntax)

In a route attribute, the {field} placeholder syntax will match a path segment and convert it to the type of the named field using FromStr:

#[get("/users/{id}")]

To extract multiple path segments this way, the {field...} syntax can be used at the end of the path, which will consume the rest of the path:

#[get("/static/{path...}")]

If the FromStr conversion fails, the generated FromRequest implementation will bail out with an error (in other words, this feature cannot be used to try multiple routes in sequence until one matches).

Extracting the request body (#[body] attribute)

Putting #[body] on a field of a variant will deserialize the request body using the FromBody trait and store the result in the annotated field:

#[post("/login")]
Login {
    #[body]
    data: Json<Login>,
},

The type of the field must implement FromBody. The body module contains predefined adapters implementing that trait, which work with any type implementing Deserialize.

Extracting query parameters (#[query_params] attribute)

The route attribute cannot match or extract query parameters (?name=val). Instead, query parameters can be extracted by marking a field in the struct with the #[query_params] attribute:

use hyperdrive::FromRequest;

#[derive(FromRequest)]
enum Routes {
    #[get("/users")]
    UserList {
        #[query_params]
        pagination: Option<Pagination>,
    },
}

#[derive(Deserialize)]
struct Pagination {
    start_id: u32,
    count: u32,
}

A request like GET /users?start_id=42&count=10 would thus end up with a corresponding Pagination object, while GET /users would store None in the pagination field.

The type of the #[query_params] field must implement serde's Deserialize trait and the conversion will be performed using the serde_urlencoded crate.

Guards

Guards can be used to prevent a route from being called when a condition is not fulfilled (for example, when the user isn't logged in). They can also extract arbitrary data from the request headers (eg. a session ID, or the User-Agent string).

All fields that are neither mentioned in the route path nor annotated with an attribute are considered guards and thus must implement the Guard trait.

use hyperdrive::{FromRequest, Guard};

struct User {
    id: u32,
    // ...
}

impl Guard for User {
    // (omitted for brevity)
}

#[derive(FromRequest)]
enum Route {
    #[get("/login")]
    LoginForm,

    #[get("/staff")]
    Staff {
        // Require a logged-in user to make this request
        user: User,
    },
}

Forwarding

A field whose type implements FromRequest can be marked with #[forward]. The library will then generate code that invokes this nested FromRequest implementation.

This feature can not be combined with #[body] inside the same variant, since both consume the request body.

Currently, this is limited to FromRequest implementations that use the same RequestContext as the outer type (ie. no automatic AsRef conversion will take place).

A variant or struct defining a #[forward] field does not have to define a route. If no other route matches, this variant will automatically be created, and is considered a fallback route.

Combined with generics, this feature can be used to make request wrappers that attach a guard or a guard group to any type implementing FromRequest:

use hyperdrive::{FromRequest, Guard};

struct User;
impl Guard for User {
    // (omitted for brevity)
}

#[derive(FromRequest)]
struct Authenticated<T> {
    user: User,

    #[forward]
    inner: T,
}

Changing the Context type

By default, the generated code will use NoContext as the associated Context type. You can change this to any other type that implements RequestContext by putting a #[context(MyContext)] attribute on the type:

use hyperdrive::{FromRequest, RequestContext};

#[derive(RequestContext)]
struct MyContext {
    db: MyDatabaseConnection,
}

#[derive(FromRequest)]
#[context(MyContext)]
enum Routes {
    #[get("/users")]
    UserList,
}

For more info on this, refer to the RequestContext trait.

Associated Types

type Context: RequestContext

A context parameter passed to from_request.

This can be used to pass application-specific data like a logger or a database connection around.

If no context is needed, this should be set to NoContext, which is a context type that can be obtained from any RequestContext via AsRef.

type Future: Future<Item = Self, Error = BoxedError> + Send

The future returned by from_request.

Because impl Trait cannot be used inside traits (and named existential types aren't yet stable), the type here might not be nameable. In that case, you can set it to DefaultFuture<Self, BoxedError> and box the returned future.

Loading content...

Required methods

fn from_request_and_body(
    request: &Arc<Request<()>>,
    body: Body,
    context: Self::Context
) -> Self::Future

Creates a Self from an HTTP request, asynchronously.

This takes the request metadata, body, and a user-defined context. Only the body is consumed.

Implementations of this function must not block, since this function is always run on a futures executor. If you need to perform blocking I/O or long-running computations, you can call tokio_threadpool::blocking.

Parameters

  • request: HTTP request data (headers, path, method, etc.).
  • body: The streamed HTTP body.
  • context: The user-defined context.
Loading content...

Provided methods

fn from_request(request: Request<Body>, context: Self::Context) -> Self::Future

Create a Self from an HTTP request, asynchronously.

This consumes the request and the context.

Implementations of this function must not block, since this function is always run on a futures executor. If you need to perform blocking I/O or long-running computations, you can call tokio_threadpool::blocking.

A blocking wrapper around this method is provided by from_request_sync.

Parameters

  • request: An HTTP request from the http crate, containing a hyper::Body.
  • context: User-defined context.

fn from_request_sync(
    request: Request<Body>,
    context: Self::Context
) -> Result<Self, BoxedError>

Create a Self from an HTTP request, synchronously.

This is a blocking version of from_request. The provided default implementation will internally create a single-threaded tokio runtime to perform the conversion and receive the request body.

Note that this does not provide a way to write a blocking version of from_request. Implementors of this trait must always implement from_request in a non-blocking fashion, even if they also implement this method.

Loading content...

Implementors

Loading content...