rust
46 lines · 8 steps
Validating a signup form in Axum
An Axum handler parses a submitted form, collects per-field validation errors, and returns either 422 or 201.
Explained by
highlit
1use axum::{extract::Form, http::StatusCode, response::{IntoResponse, Response}, Json};
2use serde::Deserialize;
3use serde_json::json;
4use std::collections::HashMap;
5
6#[derive(Deserialize)]
7pub struct SignupForm {
8 username: String,
9 email: String,
10 password: String,
11 age: Option<u32>,
12}
13
14pub async fn signup(Form(form): Form<SignupForm>) -> Response {
15 let mut errors: HashMap<&'static str, Vec<String>> = HashMap::new();
16
17 if form.username.trim().len() < 3 {
18 errors.entry("username").or_default().push("must be at least 3 characters".into());
19 }
20 if !form.username.chars().all(|c| c.is_alphanumeric() || c == '_') {
21 errors.entry("username").or_default().push("may only contain letters, digits and underscores".into());
22 }
23
24 if !form.email.contains('@') || !form.email.contains('.') {
25 errors.entry("email").or_default().push("is not a valid email address".into());
26 }
27
28 if form.password.len() < 8 {
29 errors.entry("password").or_default().push("must be at least 8 characters".into());
30 }
31 if !form.password.chars().any(|c| c.is_ascii_digit()) {
32 errors.entry("password").or_default().push("must contain a digit".into());
33 }
34
35 if let Some(age) = form.age {
36 if age < 13 {
37 errors.entry("age").or_default().push("must be at least 13".into());
38 }
39 }
40
41 if !errors.is_empty() {
42 return (StatusCode::UNPROCESSABLE_ENTITY, Json(json!({ "errors": errors }))).into_response();
43 }
44
45 (StatusCode::CREATED, Json(json!({ "username": form.username }))).into_response()
46}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Accumulating errors in a map lets you report every problem at once instead of failing on the first.
- 2Deriving Deserialize on a struct turns raw form bytes into typed, validated fields for free.
- 3Returning a (StatusCode, Json) tuple is the idiomatic way to pair a status with a body in Axum.
Related explainers
rust
use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::thread;
Aggregating metrics across threads in Rust
concurrency
shared-state
mutex
Intermediate
7 steps
go
package main import ( "errors"
Parsing and validating CLI flags in Go
cli-parsing
validation
error-handling
Intermediate
8 steps
java
public class ThumbnailProcessor { private static final int MAX_CONCURRENCY = 4;
Bounded parallel thumbnail rendering in Java
concurrency
thread-pool
futures
Intermediate
7 steps
rust
use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::Duration;
Building a thread pool in Rust
concurrency
channels
thread-pool
Advanced
9 steps
rust
use std::time::Instant; use tracing::info; pub struct Timer {
A scope-guard timer with Drop in Rust
raii
drop-guard
timing
Intermediate
7 steps
rust
use std::collections::HashMap; pub struct Memoizer<K, V, F> { cache: HashMap<K, V>,
A generic memoizer in Rust
memoization
generics
caching
Intermediate
6 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/validating-a-signup-form-in-axum-explained-rust-d3fe/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.