mx20022_model/common/
validate.rs1#[derive(Debug, Clone, PartialEq)]
40pub struct ConstraintViolation {
41 pub path: String,
44 pub message: String,
46 pub kind: ConstraintKind,
48}
49
50#[derive(Debug, Clone, PartialEq)]
55pub struct ConstraintError {
56 pub kind: ConstraintKind,
58 pub message: String,
60}
61
62impl core::fmt::Display for ConstraintError {
63 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
64 f.write_str(&self.message)
65 }
66}
67
68impl std::error::Error for ConstraintError {}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum ConstraintKind {
73 MinLength,
75 MaxLength,
77 Pattern,
79 MinInclusive,
81 MaxInclusive,
83 TotalDigits,
85 FractionDigits,
87}
88
89pub trait Validatable {
99 fn validate_constraints(&self, path: &str, violations: &mut Vec<ConstraintViolation>);
104}
105
106pub trait IsoMessage: Validatable {
113 fn message_type(&self) -> &'static str;
115
116 fn root_path(&self) -> &'static str;
118
119 fn validate_message(&self) -> Vec<ConstraintViolation> {
124 let mut violations = Vec::new();
125 self.validate_constraints(self.root_path(), &mut violations);
126 violations
127 }
128}
129
130impl<T: Validatable> Validatable for Option<T> {
133 fn validate_constraints(&self, path: &str, violations: &mut Vec<ConstraintViolation>) {
134 if let Some(ref inner) = self {
135 inner.validate_constraints(path, violations);
136 }
137 }
138}
139
140impl<T: Validatable> Validatable for Vec<T> {
141 fn validate_constraints(&self, path: &str, violations: &mut Vec<ConstraintViolation>) {
142 for (i, item) in self.iter().enumerate() {
143 let snap = violations.len();
144 item.validate_constraints("", violations);
145 if violations.len() > snap {
146 let pfx = format!("{path}[{i}]");
147 for v in &mut violations[snap..] {
148 v.path.insert_str(0, &pfx);
149 }
150 }
151 }
152 }
153}
154
155impl Validatable for String {
157 fn validate_constraints(&self, _path: &str, _violations: &mut Vec<ConstraintViolation>) {}
158}
159
160impl Validatable for bool {
162 fn validate_constraints(&self, _path: &str, _violations: &mut Vec<ConstraintViolation>) {}
163}
164
165impl<T: Validatable> Validatable for super::ChoiceWrapper<T> {
166 fn validate_constraints(&self, path: &str, violations: &mut Vec<ConstraintViolation>) {
167 self.inner.validate_constraints(path, violations);
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn constraint_error_display() {
177 let err = ConstraintError {
178 kind: ConstraintKind::Pattern,
179 message: "value does not match pattern [A-Z]{3,3}".to_string(),
180 };
181 assert_eq!(err.to_string(), "value does not match pattern [A-Z]{3,3}");
182 }
183
184 #[test]
185 fn constraint_error_debug() {
186 let err = ConstraintError {
187 kind: ConstraintKind::MaxLength,
188 message: "too long".to_string(),
189 };
190 let debug = format!("{err:?}");
191 assert!(debug.contains("ConstraintError"));
192 assert!(debug.contains("MaxLength"));
193 }
194
195 #[test]
196 fn constraint_error_eq() {
197 let a = ConstraintError {
198 kind: ConstraintKind::MinLength,
199 message: "too short".to_string(),
200 };
201 let b = ConstraintError {
202 kind: ConstraintKind::MinLength,
203 message: "too short".to_string(),
204 };
205 let c = ConstraintError {
206 kind: ConstraintKind::MaxLength,
207 message: "too short".to_string(),
208 };
209 assert_eq!(a, b);
210 assert_ne!(a, c);
211 }
212
213 #[test]
214 fn constraint_error_is_std_error() {
215 let err = ConstraintError {
216 kind: ConstraintKind::Pattern,
217 message: "bad pattern".to_string(),
218 };
219 let _: &dyn std::error::Error = &err;
220 }
221
222 struct AlwaysViolates;
226 impl Validatable for AlwaysViolates {
227 fn validate_constraints(&self, path: &str, violations: &mut Vec<ConstraintViolation>) {
228 violations.push(ConstraintViolation {
229 path: path.to_string(),
230 message: "always fails".to_string(),
231 kind: ConstraintKind::Pattern,
232 });
233 }
234 }
235
236 #[test]
237 fn option_none_produces_no_violations() {
238 let val: Option<AlwaysViolates> = None;
239 let mut v = vec![];
240 val.validate_constraints("/root", &mut v);
241 assert!(v.is_empty());
242 }
243
244 #[test]
245 fn option_some_delegates_with_path() {
246 let val: Option<AlwaysViolates> = Some(AlwaysViolates);
247 let mut v = vec![];
248 val.validate_constraints("/root/field", &mut v);
249 assert_eq!(v.len(), 1);
250 assert_eq!(v[0].path, "/root/field");
251 }
252
253 #[test]
254 fn vec_empty_produces_no_violations() {
255 let val: Vec<AlwaysViolates> = vec![];
256 let mut v = vec![];
257 val.validate_constraints("/root", &mut v);
258 assert!(v.is_empty());
259 }
260
261 #[test]
262 fn vec_indexes_path_correctly() {
263 let val = vec![AlwaysViolates, AlwaysViolates, AlwaysViolates];
264 let mut v = vec![];
265 val.validate_constraints("/root/items", &mut v);
266 assert_eq!(v.len(), 3);
267 assert_eq!(v[0].path, "/root/items[0]");
268 assert_eq!(v[1].path, "/root/items[1]");
269 assert_eq!(v[2].path, "/root/items[2]");
270 }
271
272 #[test]
273 fn choice_wrapper_delegates_with_same_path() {
274 let val = super::super::ChoiceWrapper {
275 inner: AlwaysViolates,
276 };
277 let mut v = vec![];
278 val.validate_constraints("/root/choice", &mut v);
279 assert_eq!(v.len(), 1);
280 assert_eq!(v[0].path, "/root/choice");
281 }
282
283 #[test]
284 fn string_produces_no_violations() {
285 let val = "hello".to_string();
286 let mut v = vec![];
287 val.validate_constraints("/root", &mut v);
288 assert!(v.is_empty());
289 }
290}