armature_validation/
pipe.rs

1// Validation pipe for automatic request validation
2
3use crate::{Validate, ValidationErrors};
4use armature_core::{Error, HttpRequest, HttpResponse};
5use serde::de::DeserializeOwned;
6
7/// Validation pipe that validates request bodies
8pub struct ValidationPipe;
9
10impl ValidationPipe {
11    /// Validate and parse request body
12    pub fn parse<T>(req: &HttpRequest) -> Result<T, Error>
13    where
14        T: DeserializeOwned + Validate,
15    {
16        // Parse JSON
17        let parsed: T = serde_json::from_slice(&req.body)
18            .map_err(|e| Error::BadRequest(format!("Invalid JSON: {}", e)))?;
19
20        // Validate
21        parsed
22            .validate()
23            .map_err(|errors| Error::BadRequest(format!("Validation failed: {:?}", errors)))?;
24
25        Ok(parsed)
26    }
27
28    /// Transform validation errors to HTTP response
29    pub fn error_response(errors: ValidationErrors) -> HttpResponse {
30        HttpResponse::from_parts(
31            400,
32            std::collections::HashMap::from([(
33                "Content-Type".to_string(),
34                "application/json".to_string(),
35            )]),
36            errors.to_json().to_string().into_bytes(),
37        )
38    }
39}
40
41/// Macro to validate a DTO in a handler
42#[macro_export]
43macro_rules! validate {
44    ($dto:expr) => {{
45        $dto.validate()
46            .map_err(|errors| armature_core::Error::BadRequest(format!("{:?}", errors)))?
47    }};
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use crate::{Validate, ValidationError};
54    use serde::{Deserialize, Serialize};
55
56    #[derive(Debug, Deserialize, Serialize)]
57    struct TestDto {
58        name: String,
59        age: i32,
60    }
61
62    impl Validate for TestDto {
63        fn validate(&self) -> Result<(), Vec<ValidationError>> {
64            let mut errors = Vec::new();
65
66            if self.name.is_empty() {
67                errors.push(ValidationError::new("name", "Name is required"));
68            }
69
70            if self.age < 0 {
71                errors.push(ValidationError::new("age", "Age must be positive"));
72            }
73
74            if errors.is_empty() {
75                Ok(())
76            } else {
77                Err(errors)
78            }
79        }
80    }
81
82    #[test]
83    fn test_validation_pipe() {
84        let valid_dto = TestDto {
85            name: "John".to_string(),
86            age: 30,
87        };
88
89        let json = serde_json::to_vec(&valid_dto).unwrap();
90        let mut req = HttpRequest::new("POST".to_string(), "/test".to_string());
91        req.body = json;
92
93        let result: Result<TestDto, Error> = ValidationPipe::parse(&req);
94        assert!(result.is_ok());
95    }
96}