DEV Community

Ayush
Ayush

Posted on • Originally published at programmershideaway.xyz on

6 1

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.

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

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

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay