this/core/validation/
extractor.rs1use super::config::EntityValidationConfig;
7use axum::{
8 Json,
9 extract::{FromRequest, Request},
10 http::StatusCode,
11 response::{IntoResponse, Response},
12};
13use serde_json::{Value, json};
14
15pub trait ValidatableEntity {
19 fn validation_config(operation: &str) -> EntityValidationConfig;
21}
22
23pub struct Validated<T>(pub Value, std::marker::PhantomData<T>);
35
36impl<T> Validated<T> {
37 pub fn new(payload: Value) -> Self {
39 Self(payload, std::marker::PhantomData)
40 }
41
42 pub fn into_inner(self) -> Value {
44 self.0
45 }
46}
47
48impl<T> std::ops::Deref for Validated<T> {
50 type Target = Value;
51
52 fn deref(&self) -> &Self::Target {
53 &self.0
54 }
55}
56
57impl<S, T> FromRequest<S> for Validated<T>
58where
59 S: Send + Sync,
60 T: ValidatableEntity + Send + Sync,
61{
62 type Rejection = Response;
63
64 async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
65 let method = req.method().clone();
67
68 let Json(payload): Json<Value> = match Json::from_request(req, state).await {
70 Ok(json) => json,
71 Err(e) => {
72 return Err((
73 StatusCode::BAD_REQUEST,
74 Json(json!({
75 "error": "Invalid JSON",
76 "details": e.to_string()
77 })),
78 )
79 .into_response());
80 }
81 };
82
83 let operation = match method.as_str() {
85 "POST" => "create",
86 "PUT" | "PATCH" => "update",
87 _ => "create", };
89
90 let config = T::validation_config(operation);
92
93 match config.validate_and_filter(payload) {
95 Ok(validated_payload) => Ok(Validated::new(validated_payload)),
96 Err(errors) => Err((
97 StatusCode::UNPROCESSABLE_ENTITY,
98 Json(json!({
99 "error": "Validation failed",
100 "errors": errors
101 })),
102 )
103 .into_response()),
104 }
105 }
106}