Skip to main content

ai_lib_rust/structured/
error.rs

1//! Error types for structured output validation.
2
3use std::fmt;
4
5/// Validation error with location information.
6///
7/// Contains details about what failed and where in the data structure.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct ValidationError {
10    /// Error message describing what went wrong
11    pub message: String,
12    /// JSON path to the error location (e.g., "user.name", "items[0].price")
13    pub path: Option<String>,
14    /// The invalid value that caused the error
15    pub value: Option<serde_json::Value>,
16}
17
18impl ValidationError {
19    /// Create a new validation error.
20    ///
21    /// # Arguments
22    ///
23    /// * `message` - Error description
24    /// * `path` - Optional JSON path to the error location
25    /// * `value` - Optional invalid value
26    pub fn new(
27        message: impl Into<String>,
28        path: Option<String>,
29        value: Option<serde_json::Value>,
30    ) -> Self {
31        Self {
32            message: message.into(),
33            path,
34            value,
35        }
36    }
37
38    /// Create an error with a path.
39    pub fn with_path(message: impl Into<String>, path: String) -> Self {
40        Self {
41            message: message.into(),
42            path: Some(path),
43            value: None,
44        }
45    }
46
47    /// Create an error without path.
48    pub fn without_path(message: impl Into<String>) -> Self {
49        Self {
50            message: message.into(),
51            path: None,
52            value: None,
53        }
54    }
55}
56
57impl fmt::Display for ValidationError {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match &self.path {
60            Some(path) => write!(f, "{}: {}", path, self.message),
61            None => write!(f, "{}", self.message),
62        }
63    }
64}
65
66impl std::error::Error for ValidationError {}
67
68/// Result of validation operation.
69#[derive(Debug, Clone, PartialEq)]
70pub struct ValidationResult {
71    /// Whether validation passed
72    pub valid: bool,
73    /// List of validation errors (empty if valid)
74    pub errors: Vec<ValidationError>,
75    /// Validated/parsed data (None if invalid)
76    pub data: Option<serde_json::Value>,
77}
78
79impl ValidationResult {
80    /// Create a successful validation result.
81    pub fn success(data: serde_json::Value) -> Self {
82        Self {
83            valid: true,
84            errors: Vec::new(),
85            data: Some(data),
86        }
87    }
88
89    /// Create a failed validation result.
90    pub fn failure(errors: Vec<ValidationError>) -> Self {
91        Self {
92            valid: false,
93            errors,
94            data: None,
95        }
96    }
97
98    /// Create a failure from a single error.
99    pub fn from_error(error: ValidationError) -> Self {
100        Self {
101            valid: false,
102            errors: vec![error],
103            data: None,
104        }
105    }
106
107    /// Create a failure from error messages (without paths).
108    pub fn from_messages(messages: Vec<String>) -> Self {
109        Self {
110            valid: false,
111            errors: messages
112                .into_iter()
113                .map(ValidationError::without_path)
114                .collect(),
115            data: None,
116        }
117    }
118
119    /// Check if validation passed.
120    pub fn is_valid(&self) -> bool {
121        self.valid
122    }
123
124    /// Get the validated data.
125    ///
126    /// Returns None if validation failed.
127    pub fn data(&self) -> Option<&serde_json::Value> {
128        self.data.as_ref()
129    }
130
131    /// Get errors as formatted strings.
132    pub fn error_messages(&self) -> Vec<String> {
133        self.errors.iter().map(|e| e.to_string()).collect()
134    }
135
136    /// Merge multiple validation results.
137    ///
138    /// Returns success only if all results are successful.
139    pub fn merge(results: Vec<ValidationResult>) -> Self {
140        let mut all_errors = Vec::new();
141        let mut all_valid = true;
142        let mut final_data = None;
143        let any_results = !results.is_empty();
144
145        for result in results {
146            if !result.valid {
147                all_valid = false;
148                all_errors.extend(result.errors);
149            } else if final_data.is_none() {
150                final_data = result.data;
151            }
152        }
153
154        Self {
155            valid: all_valid,
156            errors: all_errors,
157            data: if all_valid && any_results {
158                final_data
159            } else {
160                None
161            },
162        }
163    }
164
165    /// Convert to Result, merging all errors if invalid.
166    pub fn into_result(self) -> Result<serde_json::Value, Vec<ValidationError>> {
167        if self.valid {
168            Ok(self.data.unwrap_or(serde_json::Value::Null))
169        } else {
170            Err(self.errors)
171        }
172    }
173}
174
175impl From<ValidationError> for ValidationResult {
176    fn from(error: ValidationError) -> Self {
177        Self::from_error(error)
178    }
179}
180
181impl From<Vec<ValidationError>> for ValidationResult {
182    fn from(errors: Vec<ValidationError>) -> Self {
183        Self::failure(errors)
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_validation_error_display_without_path() {
193        let error = ValidationError::without_path("Invalid type");
194        assert_eq!(error.to_string(), "Invalid type");
195        assert!(error.path.is_none());
196    }
197
198    #[test]
199    fn test_validation_error_display_with_path() {
200        let error = ValidationError::with_path("Invalid type", "user.name".to_string());
201        assert_eq!(error.to_string(), "user.name: Invalid type");
202        assert_eq!(error.path, Some("user.name".to_string()));
203    }
204
205    #[test]
206    fn test_validation_result_success() {
207        let data = serde_json::json!({"name": "Alice"});
208        let result = ValidationResult::success(data.clone());
209
210        assert!(result.is_valid());
211        assert_eq!(result.data(), Some(&data));
212        assert!(result.errors.is_empty());
213    }
214
215    #[test]
216    fn test_validation_result_failure() {
217        let errors = vec![
218            ValidationError::with_path("Missing field", "user.name".to_string()),
219            ValidationError::with_path("Invalid type", "user.age".to_string()),
220        ];
221        let result = ValidationResult::failure(errors.clone());
222
223        assert!(!result.is_valid());
224        assert!(result.data.is_none());
225        assert_eq!(result.errors.len(), 2);
226    }
227
228    #[test]
229    fn test_validation_result_from_error() {
230        let error = ValidationError::without_path("Test error");
231        let result = ValidationResult::from_error(error.clone());
232
233        assert!(!result.is_valid());
234        assert_eq!(result.errors.len(), 1);
235        let first_error = &result.errors[0];
236        assert!(first_error.path.is_none());
237    }
238
239    #[test]
240    fn test_validation_result_from_messages() {
241        let messages = vec!["Error 1".to_string(), "Error 2".to_string()];
242        let result = ValidationResult::from_messages(messages);
243
244        assert!(!result.is_valid());
245        assert_eq!(result.errors.len(), 2);
246        assert!(result.errors[0].path.is_none());
247    }
248
249    #[test]
250    fn test_validation_result_merge_all_success() {
251        let result1 = ValidationResult::success(serde_json::json!(1));
252        let result2 = ValidationResult::success(serde_json::json!(2));
253
254        let merged = ValidationResult::merge(vec![result1, result2]);
255        assert!(merged.is_valid());
256    }
257
258    #[test]
259    fn test_validation_result_merge_one_failure() {
260        let result1 = ValidationResult::success(serde_json::json!(1));
261        let error = ValidationError::without_path("Test error");
262        let result2 = ValidationResult::from_error(error);
263
264        let merged = ValidationResult::merge(vec![result1, result2]);
265        assert!(!merged.is_valid());
266        assert_eq!(merged.errors.len(), 1);
267    }
268
269    #[test]
270    fn test_validation_result_into_result_success() {
271        let data = serde_json::json!({"test": 123});
272        let result = ValidationResult::success(data.clone());
273
274        assert_eq!(result.into_result(), Ok(data));
275    }
276
277    #[test]
278    fn test_validation_result_into_result_failure() {
279        let errors = vec![ValidationError::without_path("Test error")];
280        let result = ValidationResult::failure(errors.clone());
281
282        assert_eq!(result.into_result(), Err(errors));
283    }
284
285    #[test]
286    fn test_error_messages() {
287        let errors = vec![
288            ValidationError::with_path("Error 1", "path1".to_string()),
289            ValidationError::without_path("Error 2"),
290        ];
291        let result = ValidationResult::failure(errors);
292
293        let messages = result.error_messages();
294        assert_eq!(messages.len(), 2);
295        assert_eq!(messages[0], "path1: Error 1");
296        assert_eq!(messages[1], "Error 2");
297    }
298}