tokio-rs/axum

Way to customize error responses for `TypedHeader` in `axum-extras`

Closed this issue · 1 comments

  • I have looked for existing issues (including closed) about this

Feature Request

Add a way to customize the error responses for TypedHeader.

Motivation

Currently, failed TypedHeader extractors always return code 400 with a plain text message. This works alright for many cases, but it may be more applicable to return a different error code, a JSON response, or a full 400 error page.

Proposal

My initial thought TypedHeader<T> generic over an error type that implements From<TypedHeaderError> and IntoResponse, such as the below for the existing TypedHeaderRejection:

#[non_exhaustive]
pub struct TypedHeaderError<T> {
    /// True if the header was present but incorrectly formed
    pub is_present: bool,
    /// The error created by the header extractor
    pub error: headers::Error,
    phantom: PhantomData<T>,
}

impl<T: Header> From<TypedHeaderError<T>> for TypedHeaderRejection {
    fn from(value: TypedHeaderError<T>) -> Self {
        Self {
            name: T::name(),
            reason: if value.is_present {
                // Report a more precise rejection for the missing header case.
                TypedHeaderRejectionReason::Missing
            } else {
                TypedHeaderRejectionReason::Error(value.error)
            },
        }
    }
}

The problem with this is that the current TypedHeader<T>(pub T) can't be made public over anything else without having a PhantomData field, which would break the API of function parameter destructuring like TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>.

Alternatives

I don't know the best way to resolve the above issue outside of adding a new type. Const generics can be used without an extra PhantomData to customize only the error code:

#[derive(Debug)]
pub struct TypedHeader<T, const CODE: u16 = 400>(pub T);

#[derive(Debug)]
pub struct TypedHeaderRejection<const CODE: u16 = 400> {
    name: &'static HeaderName,
    reason: Reason,
}

// ...

impl<const CODE: u16> IntoResponse for TypedHeaderRejection<CODE> {
    fn into_response(self) -> Response {
        (StatusCode::from_u16(CODE).unwrap(), self.to_string()).into_response()
    }
}

But that doesn't allow for setting a custom output type in any way.

You have some options now, the docs mention two:

  • You can use a Result<TypedHeader<T>, Error>, but that also means you have to take the whole result as an argument and match it inside the handler (or a helper function)
  • Customize the error by wrapping it, see example, see there with_rejection.rs

I think the second option here is what you're looking for?