rust
57 lines · 7 steps
A validating JSON extractor in Axum
Wrap Axum's Json extractor so request bodies are both deserialized and validated before your handler runs.
Explained by
highlit
1use axum::{
2 async_trait,
3 extract::{rejection::JsonRejection, FromRequest, Request},
4 http::StatusCode,
5 response::{IntoResponse, Response},
6 Json,
7};
8use serde::de::DeserializeOwned;
9use serde_json::json;
10use validator::Validate;
11
12pub struct ValidatedJson<T>(pub T);
13
14#[async_trait]
15impl<T, S> FromRequest<S> for ValidatedJson<T>
16where
17 T: DeserializeOwned + Validate,
18 S: Send + Sync,
19{
20 type Rejection = ValidationRejection;
21
22 async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
23 let Json(value) = Json::<T>::from_request(req, state).await?;
24 value.validate()?;
25 Ok(ValidatedJson(value))
26 }
27}
28
29pub enum ValidationRejection {
30 Json(JsonRejection),
31 Invalid(validator::ValidationErrors),
32}
33
34impl From<JsonRejection> for ValidationRejection {
35 fn from(rejection: JsonRejection) -> Self {
36 Self::Json(rejection)
37 }
38}
39
40impl From<validator::ValidationErrors> for ValidationRejection {
41 fn from(errors: validator::ValidationErrors) -> Self {
42 Self::Invalid(errors)
43 }
44}
45
46impl IntoResponse for ValidationRejection {
47 fn into_response(self) -> Response {
48 match self {
49 Self::Json(rejection) => rejection.into_response(),
50 Self::Invalid(errors) => (
51 StatusCode::UNPROCESSABLE_ENTITY,
52 Json(json!({ "message": "validation failed", "errors": errors })),
53 )
54 .into_response(),
55 }
56 }
57}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Implementing FromRequest lets you build custom extractors that run logic before a handler ever executes.
- 2Composing an existing extractor inside your own keeps deserialization concerns separate from validation.
- 3A dedicated rejection type with IntoResponse gives each failure mode its own tailored HTTP response.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-validating-json-extractor-in-axum-explained-rust-98d2/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.