Cookbook / Requests and responses
Validation
Check incoming data and answer 422 with a per-field list of problems.
Implement Validate for a request type, then call req.valid_json() (or valid_form() / valid_query()) instead of req.json(). Every failing field is reported at once, so clients can fix the whole form in one round trip.
use axeon::validate::{is_email, Validate, ValidationErrors};
use axeon::{Json, Server, ServerError};
use serde::Deserialize;
#[derive(Deserialize)]
struct SignUp {
email: String,
password: String,
age: u8,
}
impl Validate for SignUp {
fn validate(&self) -> Result<(), ValidationErrors> {
let mut errors = ValidationErrors::new();
errors.check(is_email(&self.email), "email", "must be an email address");
errors.check(self.password.len() >= 12, "password", "must be at least 12 characters");
errors.check(self.age >= 18, "age", "must be 18 or older");
errors.into_result()
}
}
fn main() {
let mut app = Server::new();
app.post("/signup", |req| async move {
let form: SignUp = req.valid_json()?;
Ok::<_, ServerError>((201, Json(form.email)))
});
app.listen("127.0.0.1:3000").expect("server failed to start");
}
curl -i -X POST http://127.0.0.1:3000/signup \
-H 'content-type: application/json' \
-d '{"email":"nope","password":"short","age":12}'
The response is 422 Unprocessable Entity with one entry per field:
{
"error": {
"fields": {
"age": ["must be 18 or older"],
"email": ["must be an email address"],
"password": ["must be at least 12 characters"]
},
"message": "Validation failed",
"status": 422
}
}
Notes#
errors.check(condition, field, message)records a problem when the condition is false. Call it as many times as you like for the same field.- Your checks can be any Rust code: query the database, compare two fields, call a regex.