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