1use axum::body::Bytes;
2use axum::extract::FromRequest;
3use axum::http::Request;
4use px_errors::AppError;
5use serde::de::DeserializeOwned;
6
7pub trait Validate {
8 fn validate(&self) -> Result<(), String>;
9}
10
11#[derive(Debug)]
12pub struct Validated<T>(pub T);
13
14impl<T, S> FromRequest<S> for Validated<T>
15where
16 T: DeserializeOwned + Validate + 'static,
17 S: Send + Sync,
18{
19 type Rejection = AppError;
20
21 async fn from_request(req: Request<axum::body::Body>, state: &S) -> Result<Self, AppError> {
22 let bytes = Bytes::from_request(req, state)
23 .await
24 .map_err(|e| AppError::BadRequest(format!("body read failed: {e}")))?;
25 let value: T = serde_json::from_slice(&bytes)
26 .map_err(|e| AppError::BadRequest(format!("invalid JSON: {e}")))?;
27 value.validate().map_err(AppError::ValidationError)?;
28 Ok(Validated(value))
29 }
30}