use serde::de::DeserializeOwned;
pub use crate::forms::ValidationErrors;
pub trait Validate: Sized {
fn validate(&mut self) -> Result<(), ValidationErrors>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Valid<T>(pub T);
impl<T> std::ops::Deref for Valid<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
#[derive(Debug)]
pub enum ValidRejection {
Malformed(String),
Invalid(ValidationErrors),
}
impl axum::response::IntoResponse for ValidRejection {
fn into_response(self) -> axum::response::Response {
use axum::Json;
use http::StatusCode;
match self {
ValidRejection::Malformed(msg) => (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({ "code": "malformed_body", "error": msg })),
)
.into_response(),
ValidRejection::Invalid(errs) => {
let mut body = serde_json::Map::new();
body.insert("code".into(), serde_json::json!("validation_error"));
for (field, messages) in errs.fields {
body.insert(field, serde_json::json!(messages));
}
if !errs.non_field.is_empty() {
body.insert("non_field_errors".into(), serde_json::json!(errs.non_field));
}
(
StatusCode::BAD_REQUEST,
Json(serde_json::Value::Object(body)),
)
.into_response()
}
}
}
}
impl<T, S> axum::extract::FromRequest<S> for Valid<T>
where
T: DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = ValidRejection;
async fn from_request(req: axum::extract::Request, state: &S) -> Result<Self, Self::Rejection> {
let axum::Json(mut value) = axum::Json::<T>::from_request(req, state)
.await
.map_err(|e| ValidRejection::Malformed(e.body_text()))?;
value.validate().map_err(ValidRejection::Invalid)?;
Ok(Valid(value))
}
}
pub fn check_min_length(errs: &mut ValidationErrors, field: &str, value: &str, n: usize) {
if value.chars().count() < n {
errs.add(
field,
if n == 1 {
"This field cannot be blank.".to_string()
} else {
format!("Must be at least {n} characters.")
},
);
}
}
pub fn check_max_length(errs: &mut ValidationErrors, field: &str, value: &str, n: usize) {
let len = value.chars().count();
if len > n {
errs.add(
field,
format!("Must be at most {n} characters (got {len})."),
);
}
}
pub fn check_text_format(errs: &mut ValidationErrors, field: &str, value: &str, format: &str) {
if let Err(e) = crate::orm::validators::validate_text_format(format, value) {
errs.add(field, e.to_string());
}
}
pub fn check_choices(errs: &mut ValidationErrors, field: &str, value: &str, allowed: &[&str]) {
if !allowed.contains(&value) {
errs.add(field, format!("Must be one of: {}.", allowed.join(", ")));
}
}
pub fn check_min(errs: &mut ValidationErrors, field: &str, value: f64, n: f64) {
if value < n {
errs.add(field, format!("Must be at least {n}."));
}
}
pub fn check_max(errs: &mut ValidationErrors, field: &str, value: f64, n: f64) {
if value > n {
errs.add(field, format!("Must be at most {n}."));
}
}