[−][src]Trait hyperdrive::FromRequest
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:
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.
Required methods
fn from_request_and_body(
request: &Arc<Request<()>>,
body: Body,
context: Self::Context
) -> Self::Future
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.
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 thehttp
crate, containing ahyper::Body
.context
: User-defined context.
fn from_request_sync(
request: Request<Body>,
context: Self::Context
) -> Result<Self, BoxedError>
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.