reliakit_validate/
error.rs1use alloc::vec::Vec;
2use core::fmt;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct Violation {
7 pub field: Option<&'static str>,
9 pub message: &'static str,
11}
12
13impl Violation {
14 pub const fn new(message: &'static str) -> Self {
16 Self {
17 field: None,
18 message,
19 }
20 }
21
22 pub const fn with_field(field: &'static str, message: &'static str) -> Self {
24 Self {
25 field: Some(field),
26 message,
27 }
28 }
29}
30
31impl fmt::Display for Violation {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match self.field {
34 Some(field) => write!(f, "{field}: {}", self.message),
35 None => f.write_str(self.message),
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct ValidationError {
47 violations: Vec<Violation>,
48}
49
50pub type ValidateResult<T = ()> = Result<T, ValidationError>;
52
53impl ValidationError {
54 pub fn new(message: &'static str) -> Self {
56 Self {
57 violations: alloc::vec![Violation::new(message)],
58 }
59 }
60
61 pub fn field(field: &'static str, message: &'static str) -> Self {
63 Self {
64 violations: alloc::vec![Violation::with_field(field, message)],
65 }
66 }
67
68 pub fn empty() -> Self {
74 Self {
75 violations: Vec::new(),
76 }
77 }
78
79 pub fn with(mut self, violation: Violation) -> Self {
81 self.violations.push(violation);
82 self
83 }
84
85 pub fn push(&mut self, violation: Violation) {
87 self.violations.push(violation);
88 }
89
90 pub fn merge(mut self, other: Self) -> Self {
92 self.violations.extend(other.violations);
93 self
94 }
95
96 pub fn violations(&self) -> &[Violation] {
98 &self.violations
99 }
100
101 pub fn is_empty(&self) -> bool {
103 self.violations.is_empty()
104 }
105
106 pub fn len(&self) -> usize {
108 self.violations.len()
109 }
110}
111
112impl From<Violation> for ValidationError {
113 fn from(v: Violation) -> Self {
114 Self {
115 violations: alloc::vec![v],
116 }
117 }
118}
119
120impl From<&'static str> for ValidationError {
121 fn from(message: &'static str) -> Self {
122 Self::new(message)
123 }
124}
125
126impl fmt::Display for ValidationError {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 match self.violations.as_slice() {
129 [] => f.write_str("validation failed"),
130 [single] => fmt::Display::fmt(single, f),
131 violations => {
132 for (i, v) in violations.iter().enumerate() {
133 if i > 0 {
134 write!(f, "; ")?;
135 }
136 fmt::Display::fmt(v, f)?;
137 }
138 Ok(())
139 }
140 }
141 }
142}
143
144#[cfg(feature = "std")]
145impl std::error::Error for ValidationError {}
146
147#[cfg(test)]
148mod tests {
149 use super::{ValidateResult, ValidationError, Violation};
150 use alloc::string::ToString;
151
152 #[test]
153 fn violation_new() {
154 let v = Violation::new("must not be empty");
155 assert_eq!(v.field, None);
156 assert_eq!(v.message, "must not be empty");
157 }
158
159 #[test]
160 fn violation_with_field() {
161 let v = Violation::with_field("email", "invalid format");
162 assert_eq!(v.field, Some("email"));
163 assert_eq!(v.message, "invalid format");
164 }
165
166 #[test]
167 fn violation_display_no_field() {
168 assert_eq!(Violation::new("bad value").to_string(), "bad value");
169 }
170
171 #[test]
172 fn violation_display_with_field() {
173 assert_eq!(
174 Violation::with_field("age", "must be positive").to_string(),
175 "age: must be positive"
176 );
177 }
178
179 #[test]
180 fn validation_error_single_violation() {
181 let e = ValidationError::new("value is required");
182 assert_eq!(e.len(), 1);
183 assert!(!e.is_empty());
184 assert_eq!(e.to_string(), "value is required");
185 }
186
187 #[test]
188 fn validation_error_field() {
189 let e = ValidationError::field("name", "too short");
190 assert_eq!(e.violations()[0].field, Some("name"));
191 assert_eq!(e.to_string(), "name: too short");
192 }
193
194 #[test]
195 fn validation_error_empty() {
196 let e = ValidationError::empty();
197 assert!(e.is_empty());
198 assert_eq!(e.len(), 0);
199 assert_eq!(e.to_string(), "validation failed");
200 }
201
202 #[test]
203 fn validation_error_add_chaining() {
204 let e = ValidationError::empty()
205 .with(Violation::with_field("name", "too short"))
206 .with(Violation::with_field("email", "invalid format"));
207 assert_eq!(e.len(), 2);
208 }
209
210 #[test]
211 fn validation_error_push() {
212 let mut e = ValidationError::empty();
213 e.push(Violation::new("first"));
214 e.push(Violation::new("second"));
215 assert_eq!(e.len(), 2);
216 }
217
218 #[test]
219 fn validation_error_merge() {
220 let a = ValidationError::new("first");
221 let b = ValidationError::new("second");
222 let merged = a.merge(b);
223 assert_eq!(merged.len(), 2);
224 }
225
226 #[test]
227 fn validation_error_display_multiple() {
228 let e = ValidationError::empty()
229 .with(Violation::new("first error"))
230 .with(Violation::new("second error"));
231 assert_eq!(e.to_string(), "first error; second error");
232 }
233
234 #[test]
235 fn validation_error_from_violation() {
236 let e = ValidationError::from(Violation::new("bad"));
237 assert_eq!(e.len(), 1);
238 }
239
240 #[test]
241 fn validation_error_from_str() {
242 let e = ValidationError::from("bad input");
243 assert_eq!(e.violations()[0].message, "bad input");
244 }
245
246 #[test]
247 fn validate_result_type_alias() {
248 let ok: ValidateResult = Ok(());
249 let err: ValidateResult = Err(ValidationError::new("fail"));
250 assert!(ok.is_ok());
251 assert!(err.is_err());
252 }
253}