Derive Macro axum::extract::FromRequest

source ·
#[derive(FromRequest)]
{
    // Attributes available to this derive:
    #[from_request]
}
Available on crate feature macros only.
Expand description

Derive an implementation of FromRequest.

Supports generating two kinds of implementations:

  1. One that extracts each field individually.
  2. Another that extracts the whole type at once via another extractor.

§Each field individually

By default #[derive(FromRequest)] will call FromRequest::from_request for each field:

use axum_macros::FromRequest;
use axum::{
    extract::Extension,
    body::Bytes,
};
use axum_extra::{
    TypedHeader,
    headers::ContentType,
};

#[derive(FromRequest)]
struct MyExtractor {
    state: Extension<State>,
    content_type: TypedHeader<ContentType>,
    request_body: Bytes,
}

#[derive(Clone)]
struct State {
    // ...
}

async fn handler(extractor: MyExtractor) {}

This requires that each field is an extractor (i.e. implements FromRequest).

Note that only the last field can consume the request body. Therefore this doesn’t compile:

use axum_macros::FromRequest;
use axum::body::Bytes;

#[derive(FromRequest)]
struct MyExtractor {
    // only the last field can implement `FromRequest`
    // other fields must only implement `FromRequestParts`
    bytes: Bytes,
    string: String,
}

§Extracting via another extractor

You can use #[from_request(via(...))] to extract a field via another extractor, meaning the field itself doesn’t need to implement FromRequest:

use axum_macros::FromRequest;
use axum::{
    extract::Extension,
    body::Bytes,
};
use axum_extra::{
    TypedHeader,
    headers::ContentType,
};

#[derive(FromRequest)]
struct MyExtractor {
    // This will extracted via `Extension::<State>::from_request`
    #[from_request(via(Extension))]
    state: State,
    // and this via `TypedHeader::<ContentType>::from_request`
    #[from_request(via(TypedHeader))]
    content_type: ContentType,
    // Can still be combined with other extractors
    request_body: Bytes,
}

#[derive(Clone)]
struct State {
    // ...
}

async fn handler(extractor: MyExtractor) {}

Note this requires the via extractor to be a generic newtype struct (a tuple struct with exactly one public field) that implements FromRequest:

pub struct ViaExtractor<T>(pub T);

// impl<T, S> FromRequest<S> for ViaExtractor<T> { ... }

More complex via extractors are not supported and require writing a manual implementation.

§Optional fields

#[from_request(via(...))] supports Option<_> and Result<_, _> to make fields optional:

use axum_macros::FromRequest;
use axum_extra::{
    TypedHeader,
    headers::{ContentType, UserAgent},
    typed_header::TypedHeaderRejection,
};

#[derive(FromRequest)]
struct MyExtractor {
    // This will extracted via `Option::<TypedHeader<ContentType>>::from_request`
    #[from_request(via(TypedHeader))]
    content_type: Option<ContentType>,
    // This will extracted via
    // `Result::<TypedHeader<UserAgent>, TypedHeaderRejection>::from_request`
    #[from_request(via(TypedHeader))]
    user_agent: Result<UserAgent, TypedHeaderRejection>,
}

async fn handler(extractor: MyExtractor) {}

§The rejection

By default axum::response::Response will be used as the rejection. You can also use your own rejection type with #[from_request(rejection(YourType))]:

use axum::{
    extract::{
        rejection::{ExtensionRejection, StringRejection},
        FromRequest,
    },
    Extension,
    response::{Response, IntoResponse},
};

#[derive(FromRequest)]
#[from_request(rejection(MyRejection))]
struct MyExtractor {
    state: Extension<String>,
    body: String,
}

struct MyRejection(Response);

// This tells axum how to convert `Extension`'s rejections into `MyRejection`
impl From<ExtensionRejection> for MyRejection {
    fn from(rejection: ExtensionRejection) -> Self {
        // ...
    }
}

// This tells axum how to convert `String`'s rejections into `MyRejection`
impl From<StringRejection> for MyRejection {
    fn from(rejection: StringRejection) -> Self {
        // ...
    }
}

// All rejections must implement `IntoResponse`
impl IntoResponse for MyRejection {
    fn into_response(self) -> Response {
        self.0
    }
}

§The whole type at once

By using #[from_request(via(...))] on the container you can extract the whole type at once, instead of each field individually:

use axum_macros::FromRequest;
use axum::extract::Extension;

// This will extracted via `Extension::<State>::from_request`
#[derive(Clone, FromRequest)]
#[from_request(via(Extension))]
struct State {
    // ...
}

async fn handler(state: State) {}

The rejection will be the “via extractors”’s rejection. For the previous example that would be axum::extract::rejection::ExtensionRejection.

You can use a different rejection type with #[from_request(rejection(YourType))]:

use axum_macros::FromRequest;
use axum::{
    extract::{Extension, rejection::ExtensionRejection},
    response::{IntoResponse, Response},
    Json,
    http::StatusCode,
};
use serde_json::json;

// This will extracted via `Extension::<State>::from_request`
#[derive(Clone, FromRequest)]
#[from_request(
    via(Extension),
    // Use your own rejection type
    rejection(MyRejection),
)]
struct State {
    // ...
}

struct MyRejection(Response);

// This tells axum how to convert `Extension`'s rejections into `MyRejection`
impl From<ExtensionRejection> for MyRejection {
    fn from(rejection: ExtensionRejection) -> Self {
        let response = (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(json!({ "error": "Something went wrong..." })),
        ).into_response();

        MyRejection(response)
    }
}

// All rejections must implement `IntoResponse`
impl IntoResponse for MyRejection {
    fn into_response(self) -> Response {
        self.0
    }
}

async fn handler(state: State) {}

This allows you to wrap other extractors and easily customize the rejection:

use axum_macros::FromRequest;
use axum::{
    extract::{Extension, rejection::JsonRejection},
    response::{IntoResponse, Response},
    http::StatusCode,
};
use serde_json::json;
use serde::Deserialize;

// create an extractor that internally uses `axum::Json` but has a custom rejection
#[derive(FromRequest)]
#[from_request(via(axum::Json), rejection(MyRejection))]
struct MyJson<T>(T);

struct MyRejection(Response);

impl From<JsonRejection> for MyRejection {
    fn from(rejection: JsonRejection) -> Self {
        let response = (
            StatusCode::INTERNAL_SERVER_ERROR,
            axum::Json(json!({ "error": rejection.to_string() })),
        ).into_response();

        MyRejection(response)
    }
}

impl IntoResponse for MyRejection {
    fn into_response(self) -> Response {
        self.0
    }
}

#[derive(Deserialize)]
struct Payload {}

async fn handler(
    // make sure to use `MyJson` and not `axum::Json`
    MyJson(payload): MyJson<Payload>,
) {}

§Known limitations

Generics are only supported on tuple structs with exactly on field. Thus this doesn’t work

#[derive(axum_macros::FromRequest)]
struct MyExtractor<T> {
    thing: Option<T>,
}