skp_validator_core/
result.rs

1//! Validation result types.
2
3use crate::error::ValidationErrors;
4
5/// Result type for validation operations.
6///
7/// Returns `Ok(T)` on success, or `Err(ValidationErrors)` containing
8/// all validation failures.
9pub type ValidationResult<T> = Result<T, ValidationErrors>;
10
11/// Extension trait for ValidationResult
12pub trait ValidationResultExt<T> {
13    /// Map the success value, preserving validation errors
14    fn map_valid<U, F: FnOnce(T) -> U>(self, f: F) -> ValidationResult<U>;
15
16    /// Combine with another validation result, collecting all errors
17    fn and_also<U>(self, other: ValidationResult<U>) -> ValidationResult<(T, U)>;
18
19    /// Add errors from another result without changing the success type
20    fn with_errors(self, other: ValidationResult<()>) -> ValidationResult<T>;
21}
22
23impl<T> ValidationResultExt<T> for ValidationResult<T> {
24    fn map_valid<U, F: FnOnce(T) -> U>(self, f: F) -> ValidationResult<U> {
25        self.map(f)
26    }
27
28    fn and_also<U>(self, other: ValidationResult<U>) -> ValidationResult<(T, U)> {
29        match (self, other) {
30            (Ok(t), Ok(u)) => Ok((t, u)),
31            (Err(mut e1), Err(e2)) => {
32                e1.merge(e2);
33                Err(e1)
34            }
35            (Err(e), Ok(_)) | (Ok(_), Err(e)) => Err(e),
36        }
37    }
38
39    fn with_errors(self, other: ValidationResult<()>) -> ValidationResult<T> {
40        match (self, other) {
41            (Ok(t), Ok(())) => Ok(t),
42            (Err(mut e1), Err(e2)) => {
43                e1.merge(e2);
44                Err(e1)
45            }
46            (Err(e), Ok(())) => Err(e),
47            (Ok(_), Err(e)) => Err(e),
48        }
49    }
50}
51
52/// Helper for collecting multiple validation results
53pub struct ValidationCollector {
54    errors: ValidationErrors,
55}
56
57impl ValidationCollector {
58    /// Create a new collector
59    pub fn new() -> Self {
60        Self {
61            errors: ValidationErrors::new(),
62        }
63    }
64
65    /// Add a validation result, collecting any errors
66    pub fn collect<T>(&mut self, result: ValidationResult<T>) -> Option<T> {
67        match result {
68            Ok(v) => Some(v),
69            Err(e) => {
70                self.errors.merge(e);
71                None
72            }
73        }
74    }
75
76    /// Add a validation result for a specific field
77    pub fn collect_field<T>(
78        &mut self,
79        field: impl Into<String>,
80        result: ValidationResult<T>,
81    ) -> Option<T> {
82        match result {
83            Ok(v) => Some(v),
84            Err(e) => {
85                self.errors.add_nested_errors(field, e);
86                None
87            }
88        }
89    }
90
91    /// Check if any errors were collected
92    pub fn has_errors(&self) -> bool {
93        !self.errors.is_empty()
94    }
95
96    /// Get the collected errors
97    pub fn errors(&self) -> &ValidationErrors {
98        &self.errors
99    }
100
101    /// Finish collecting and return the result
102    pub fn finish(self) -> ValidationResult<()> {
103        if self.errors.is_empty() {
104            Ok(())
105        } else {
106            Err(self.errors)
107        }
108    }
109
110    /// Finish with a value if no errors
111    pub fn finish_with<T>(self, value: T) -> ValidationResult<T> {
112        if self.errors.is_empty() {
113            Ok(value)
114        } else {
115            Err(self.errors)
116        }
117    }
118}
119
120impl Default for ValidationCollector {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use crate::ValidationError;
130
131    #[test]
132    fn test_and_also_both_ok() {
133        let r1: ValidationResult<i32> = Ok(1);
134        let r2: ValidationResult<&str> = Ok("test");
135        let combined = r1.and_also(r2);
136        assert_eq!(combined.unwrap(), (1, "test"));
137    }
138
139    #[test]
140    fn test_and_also_both_err() {
141        let mut e1 = ValidationErrors::new();
142        e1.add_field_error("a", ValidationError::new("a", "code", "Error A"));
143
144        let mut e2 = ValidationErrors::new();
145        e2.add_field_error("b", ValidationError::new("b", "code", "Error B"));
146
147        let r1: ValidationResult<i32> = Err(e1);
148        let r2: ValidationResult<&str> = Err(e2);
149        let combined = r1.and_also(r2);
150
151        let errors = combined.unwrap_err();
152        assert_eq!(errors.count(), 2);
153    }
154
155    #[test]
156    fn test_collector() {
157        let mut collector = ValidationCollector::new();
158
159        let r1: ValidationResult<i32> = Ok(42);
160        let v1 = collector.collect(r1);
161        assert_eq!(v1, Some(42));
162
163        let mut e = ValidationErrors::new();
164        e.add_field_error("x", ValidationError::new("x", "code", "Error"));
165        let r2: ValidationResult<i32> = Err(e);
166        let v2 = collector.collect(r2);
167        assert_eq!(v2, None);
168
169        assert!(collector.has_errors());
170        assert!(collector.finish().is_err());
171    }
172}