1use std::collections::HashMap;
26
27use axum::extract::{FromRequest, Request};
28use axum::response::{IntoResponse, Response};
29
30pub struct Validated<T>(T);
40
41impl<T> Validated<T> {
42 pub(crate) const fn new(value: T) -> Self {
44 Self(value)
45 }
46
47 #[must_use]
49 pub fn into_inner(self) -> T {
50 self.0
51 }
52}
53
54impl<T> std::ops::Deref for Validated<T> {
55 type Target = T;
56 fn deref(&self) -> &T {
57 &self.0
58 }
59}
60
61impl<T> AsRef<T> for Validated<T> {
62 fn as_ref(&self) -> &T {
63 &self.0
64 }
65}
66
67pub trait ValidateExt: validator::Validate + Sized {
75 fn validate(self) -> crate::AutumnResult<Validated<Self>> {
82 if let Err(errors) = validator::Validate::validate(&self) {
83 return Err(validation_errors_to_autumn_error(&errors));
84 }
85 Ok(Validated::new(self))
86 }
87}
88
89impl<T: validator::Validate> ValidateExt for T {}
90
91pub struct Valid<T>(pub T);
112
113impl<S, T, Inner> FromRequest<S> for Valid<Inner>
114where
115 S: Send + Sync,
116 Inner: FromRequest<S> + AsValidatable<Inner = T>,
117 Inner::Rejection: IntoResponse,
118 T: validator::Validate,
119{
120 type Rejection = Response;
121
122 async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
123 let inner = Inner::from_request(req, state)
124 .await
125 .map_err(IntoResponse::into_response)?;
126
127 let value = inner.as_validatable();
128 if let Err(errors) = validator::Validate::validate(value) {
129 return Err(
130 crate::AutumnError::validation(validation_errors_to_map(&errors)).into_response(),
131 );
132 }
133
134 Ok(Self(inner))
135 }
136}
137
138pub trait AsValidatable {
141 type Inner;
143 fn as_validatable(&self) -> &Self::Inner;
145}
146
147impl<T> AsValidatable for axum::Json<T> {
148 type Inner = T;
149 fn as_validatable(&self) -> &T {
150 &self.0
151 }
152}
153
154impl<T> AsValidatable for crate::extract::Json<T> {
155 type Inner = T;
156 fn as_validatable(&self) -> &T {
157 &self.0
158 }
159}
160
161impl<T> AsValidatable for axum::extract::Form<T> {
162 type Inner = T;
163 fn as_validatable(&self) -> &T {
164 &self.0
165 }
166}
167
168impl<T> AsValidatable for crate::extract::Form<T> {
169 type Inner = T;
170 fn as_validatable(&self) -> &T {
171 &self.0
172 }
173}
174
175impl<T> AsValidatable for axum::extract::Query<T> {
176 type Inner = T;
177 fn as_validatable(&self) -> &T {
178 &self.0
179 }
180}
181
182impl<T> AsValidatable for crate::extract::Query<T> {
183 type Inner = T;
184 fn as_validatable(&self) -> &T {
185 &self.0
186 }
187}
188
189fn validation_errors_to_map(errors: &validator::ValidationErrors) -> HashMap<String, Vec<String>> {
191 errors
192 .field_errors()
193 .into_iter()
194 .map(|(field, errs)| {
195 let messages = errs
196 .iter()
197 .map(|e| {
198 e.message.as_ref().map_or_else(
199 || format!("validation failed: {}", e.code),
200 ToString::to_string,
201 )
202 })
203 .collect();
204 (field.to_string(), messages)
205 })
206 .collect()
207}
208
209fn validation_errors_to_autumn_error(errors: &validator::ValidationErrors) -> crate::AutumnError {
215 crate::AutumnError::validation(validation_errors_to_map(errors))
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn validated_deref() {
224 let v = Validated::new(42);
225 assert_eq!(*v, 42);
226 }
227
228 #[test]
229 fn validated_into_inner() {
230 let v = Validated::new("hello".to_string());
231 let s = v.into_inner();
232 assert_eq!(s, "hello");
233 }
234
235 #[test]
236 fn validated_as_ref() {
237 let v = Validated::new(vec![1, 2, 3]);
238 let r: &Vec<i32> = v.as_ref();
239 assert_eq!(r.len(), 3);
240 }
241
242 #[test]
243 fn validation_errors_to_map_basic() {
244 #[derive(validator::Validate)]
245 struct TestForm {
246 #[validate(length(min = 5))]
247 name: String,
248 }
249
250 let form = TestForm {
251 name: "ab".to_string(),
252 };
253 let errors = validator::Validate::validate(&form).unwrap_err();
254 let map = validation_errors_to_map(&errors);
255
256 assert!(map.contains_key("name"));
257 assert_eq!(map["name"].len(), 1);
258 assert_eq!(map["name"][0], "validation failed: length");
259 }
260
261 #[test]
262 fn validate_ext_ok() {
263 #[derive(validator::Validate)]
264 struct GoodInput {
265 #[validate(length(min = 1))]
266 value: String,
267 }
268
269 let input = GoodInput {
270 value: "hello".into(),
271 };
272 let validated = input.validate();
273 assert!(validated.is_ok());
274 assert_eq!(validated.unwrap().value, "hello");
275 }
276
277 #[test]
278 fn validate_ext_err() {
279 #[derive(validator::Validate)]
280 struct BadInput {
281 #[validate(length(min = 5))]
282 value: String,
283 }
284
285 let input = BadInput { value: "hi".into() };
286 let result = input.validate();
287 assert!(result.is_err());
288 }
289
290 #[test]
291 fn validation_errors_convert_to_autumn_error() {
292 #[derive(validator::Validate)]
293 struct Form {
294 #[validate(email)]
295 email: String,
296 }
297
298 let form = Form {
299 email: "not-an-email".into(),
300 };
301 let errors = validator::Validate::validate(&form).unwrap_err();
302 let autumn_err = validation_errors_to_autumn_error(&errors);
303 assert_eq!(
304 autumn_err.status(),
305 axum::http::StatusCode::UNPROCESSABLE_ENTITY
306 );
307 }
308
309 #[test]
310 fn validation_errors_to_map_fallback_message() {
311 let mut errors = validator::ValidationErrors::new();
312 let error = validator::ValidationError::new("custom_code");
314 errors.add("my_field", error);
315
316 let map = validation_errors_to_map(&errors);
317
318 assert!(map.contains_key("my_field"));
319 assert_eq!(map["my_field"][0], "validation failed: custom_code");
320 }
321}