rustapi_validate/
error.rs

1//! Validation error types and JSON error format.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::fmt;
6
7/// A single field validation error.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct FieldError {
10    /// The field name that failed validation
11    pub field: String,
12    /// The validation rule code (e.g., "email", "length", "range")
13    pub code: String,
14    /// Human-readable error message
15    pub message: String,
16    /// Optional additional parameters (e.g., min/max values)
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub params: Option<HashMap<String, serde_json::Value>>,
19}
20
21impl FieldError {
22    /// Create a new field error.
23    pub fn new(field: impl Into<String>, code: impl Into<String>, message: impl Into<String>) -> Self {
24        Self {
25            field: field.into(),
26            code: code.into(),
27            message: message.into(),
28            params: None,
29        }
30    }
31
32    /// Create a field error with parameters.
33    pub fn with_params(
34        field: impl Into<String>,
35        code: impl Into<String>,
36        message: impl Into<String>,
37        params: HashMap<String, serde_json::Value>,
38    ) -> Self {
39        Self {
40            field: field.into(),
41            code: code.into(),
42            message: message.into(),
43            params: Some(params),
44        }
45    }
46}
47
48/// Internal error structure for JSON serialization.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50struct ErrorBody {
51    #[serde(rename = "type")]
52    error_type: String,
53    message: String,
54    fields: Vec<FieldError>,
55}
56
57/// Wrapper for the error response format.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59struct ErrorWrapper {
60    error: ErrorBody,
61}
62
63/// Validation error containing all field errors.
64///
65/// This type serializes to the standard RustAPI error format:
66///
67/// ```json
68/// {
69///   "error": {
70///     "type": "validation_error",
71///     "message": "Validation failed",
72///     "fields": [...]
73///   }
74/// }
75/// ```
76#[derive(Debug, Clone)]
77pub struct ValidationError {
78    /// Collection of field-level validation errors
79    pub fields: Vec<FieldError>,
80    /// Custom error message (default: "Validation failed")
81    pub message: String,
82}
83
84impl ValidationError {
85    /// Create a new validation error with field errors.
86    pub fn new(fields: Vec<FieldError>) -> Self {
87        Self {
88            fields,
89            message: "Validation failed".to_string(),
90        }
91    }
92
93    /// Create a validation error with a custom message.
94    pub fn with_message(fields: Vec<FieldError>, message: impl Into<String>) -> Self {
95        Self {
96            fields,
97            message: message.into(),
98        }
99    }
100
101    /// Create a validation error for a single field.
102    pub fn field(field: impl Into<String>, code: impl Into<String>, message: impl Into<String>) -> Self {
103        Self::new(vec![FieldError::new(field, code, message)])
104    }
105
106    /// Check if there are any validation errors.
107    pub fn is_empty(&self) -> bool {
108        self.fields.is_empty()
109    }
110
111    /// Get the number of field errors.
112    pub fn len(&self) -> usize {
113        self.fields.len()
114    }
115
116    /// Add a field error.
117    pub fn add(&mut self, error: FieldError) {
118        self.fields.push(error);
119    }
120
121    /// Convert validator errors to our format.
122    pub fn from_validator_errors(errors: validator::ValidationErrors) -> Self {
123        let mut field_errors = Vec::new();
124
125        for (field, error_kinds) in errors.field_errors() {
126            for error in error_kinds {
127                let code = error.code.to_string();
128                let message = error
129                    .message
130                    .as_ref()
131                    .map(|m| m.to_string())
132                    .unwrap_or_else(|| format!("Validation failed for field '{}'", field));
133
134                let params = if error.params.is_empty() {
135                    None
136                } else {
137                    let mut map = HashMap::new();
138                    for (key, value) in &error.params {
139                        if let Ok(json_value) = serde_json::to_value(value) {
140                            map.insert(key.to_string(), json_value);
141                        }
142                    }
143                    Some(map)
144                };
145
146                field_errors.push(FieldError {
147                    field: field.to_string(),
148                    code,
149                    message,
150                    params,
151                });
152            }
153        }
154
155        Self::new(field_errors)
156    }
157}
158
159impl fmt::Display for ValidationError {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        write!(f, "{}: {} field error(s)", self.message, self.fields.len())
162    }
163}
164
165impl std::error::Error for ValidationError {}
166
167impl Serialize for ValidationError {
168    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
169    where
170        S: serde::Serializer,
171    {
172        let wrapper = ErrorWrapper {
173            error: ErrorBody {
174                error_type: "validation_error".to_string(),
175                message: self.message.clone(),
176                fields: self.fields.clone(),
177            },
178        };
179        wrapper.serialize(serializer)
180    }
181}
182
183impl<'de> Deserialize<'de> for ValidationError {
184    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
185    where
186        D: serde::Deserializer<'de>,
187    {
188        let wrapper = ErrorWrapper::deserialize(deserializer)?;
189        Ok(Self {
190            fields: wrapper.error.fields,
191            message: wrapper.error.message,
192        })
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn field_error_creation() {
202        let error = FieldError::new("email", "email", "Invalid email format");
203        assert_eq!(error.field, "email");
204        assert_eq!(error.code, "email");
205        assert_eq!(error.message, "Invalid email format");
206        assert!(error.params.is_none());
207    }
208
209    #[test]
210    fn validation_error_serialization() {
211        let error = ValidationError::new(vec![
212            FieldError::new("email", "email", "Invalid email format"),
213        ]);
214
215        let json = serde_json::to_value(&error).unwrap();
216        
217        assert_eq!(json["error"]["type"], "validation_error");
218        assert_eq!(json["error"]["message"], "Validation failed");
219        assert_eq!(json["error"]["fields"][0]["field"], "email");
220    }
221
222    #[test]
223    fn validation_error_display() {
224        let error = ValidationError::new(vec![
225            FieldError::new("email", "email", "Invalid email"),
226            FieldError::new("age", "range", "Out of range"),
227        ]);
228
229        assert_eq!(error.to_string(), "Validation failed: 2 field error(s)");
230    }
231}