DEV Community

Ayush
Ayush

Posted on • Originally published at programmershideaway.xyz on

Validating Json Request in axum

I have been playing around with axum, and it has been quite a fun web framework. While using it, I came across what is a relatively common use case of validating the request JSON. However, I could not find any extractor for this. Thus I decided to write my own and share it with everyone. While I haven't put it in a crate, feel free to use the code as you wish.

Axum Extractors

An extractor allows us to pick apart the incoming request to get the parts our HTTP handler needs. More about extractors can be found here. There are two important traits when talking about extractors:

  1. FromRequestParts: This is used if the extractor does not need access to the request body.
  2. FromRequest: This is used if the extractor does need access to the request body. (we will be using this)

Implementation

I will use validator crate for the actual validation.

Here is the code for our validated JSON extractor:

use axum::{async_trait, extract::FromRequest, Json, RequestExt};
use hyper::{Request, StatusCode};
use validator::Validate;

pub struct ValidatedJson<J>(pub J);

#[async_trait]
impl<S, B, J> FromRequest<S, B> for ValidatedJson<J>
where
    B: Send + 'static,
    S: Send + Sync,
    J: Validate + 'static,
    Json<J>: FromRequest<(), B>,
{
    type Rejection = (StatusCode, &'static str);

    async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
        let Json(data) = req
            .extract::<Json<J>, _>()
            .await
            .map_err(|_| (StatusCode::BAD_REQUEST, "Invalid JSON body"))?;
        data.validate()
            .map_err(|_| (StatusCode::BAD_REQUEST, "Invalid JSON body"))?;
        Ok(Self(data))
    }
}
Enter fullscreen mode Exit fullscreen mode

I am using (StatusCode, &'static str) for error response since all the responses in my server are of this type. Feel free to use whatever you prefer.

It is important to note that extractors can use other extractors themselves. So we do not need to replicate the Json extractor.

Conclusion

As you can see, writing a custom extractor is relatively straightforward, especially when compared to writing a tower middleware.

Top comments (1)

Collapse
 
zeroows profile image
Abdulrhman A. AlKhodiry

You can use this on the new Axum (7.3).

async fn create_user(payload: Result<Json<Value>, JsonRejection>) {
    match payload {
        Ok(payload) => {
            // We got a valid JSON payload
        }
        Err(JsonRejection::MissingJsonContentType(_)) => {
            // Request didn't have `Content-Type: application/json`
            // header
        }
        Err(JsonRejection::JsonDataError(_)) => {
            // Couldn't deserialize the body into the target type
        }
        Err(JsonRejection::JsonSyntaxError(_)) => {
            // Syntax error in the body
        }
        Err(JsonRejection::BytesRejection(_)) => {
            // Failed to extract the request body
        }
        Err(_) => {
            // `JsonRejection` is marked `#[non_exhaustive]` so match must
            // include a catch-all case.
        }
    }
}

let app = Router::new().route("/users", post(create_user));
Enter fullscreen mode Exit fullscreen mode