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