mx20022_validate/schema/
constraints.rs1use crate::error::{ValidationError, ValidationResult};
8use crate::rules::RuleRegistry;
9
10#[derive(Debug, Clone)]
15pub struct FieldConstraint {
16 pub path: String,
18 pub rule_ids: Vec<String>,
20}
21
22impl FieldConstraint {
23 pub fn new(
25 path: impl Into<String>,
26 rule_ids: impl IntoIterator<Item = impl Into<String>>,
27 ) -> Self {
28 Self {
29 path: path.into(),
30 rule_ids: rule_ids.into_iter().map(Into::into).collect(),
31 }
32 }
33}
34
35#[derive(Debug, Default)]
53pub struct ConstraintSet {
54 constraints: Vec<FieldConstraint>,
55}
56
57impl ConstraintSet {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn add(&mut self, constraint: FieldConstraint) {
65 self.constraints.push(constraint);
66 }
67
68 pub fn for_path(&self, path: &str) -> Vec<&FieldConstraint> {
70 self.constraints.iter().filter(|c| c.path == path).collect()
71 }
72
73 pub fn validate_field(
77 &self,
78 path: &str,
79 value: &str,
80 registry: &RuleRegistry,
81 ) -> ValidationResult {
82 let rule_ids: Vec<&str> = self
83 .for_path(path)
84 .into_iter()
85 .flat_map(|c| c.rule_ids.iter().map(String::as_str))
86 .collect();
87
88 if rule_ids.is_empty() {
89 return ValidationResult::default();
90 }
91
92 let errors: Vec<ValidationError> = registry.validate_field(value, path, &rule_ids);
93 ValidationResult::new(errors)
94 }
95
96 pub fn len(&self) -> usize {
98 self.constraints.len()
99 }
100
101 pub fn is_empty(&self) -> bool {
103 self.constraints.is_empty()
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::rules::RuleRegistry;
111
112 #[test]
113 fn empty_constraint_set_produces_no_errors() {
114 let cs = ConstraintSet::new();
115 let registry = RuleRegistry::with_defaults();
116 let result = cs.validate_field("/some/path", "value", ®istry);
117 assert!(result.is_valid());
118 }
119
120 #[test]
121 fn constraint_set_with_iban_validates() {
122 let mut cs = ConstraintSet::new();
123 cs.add(FieldConstraint::new("/iban", ["IBAN_CHECK"]));
124 let registry = RuleRegistry::with_defaults();
125
126 let ok = cs.validate_field("/iban", "GB82WEST12345698765432", ®istry);
128 assert!(ok.is_valid());
129
130 let fail = cs.validate_field("/iban", "NOTANIBAN", ®istry);
132 assert!(!fail.is_valid());
133 }
134
135 #[test]
136 fn constraint_set_with_bic_validates() {
137 let mut cs = ConstraintSet::new();
138 cs.add(FieldConstraint::new("/bic", ["BIC_CHECK"]));
139 let registry = RuleRegistry::with_defaults();
140
141 let ok = cs.validate_field("/bic", "AAAAGB2L", ®istry);
142 assert!(ok.is_valid());
143
144 let fail = cs.validate_field("/bic", "bad", ®istry);
145 assert!(!fail.is_valid());
146 }
147
148 #[test]
149 fn unknown_rule_id_does_not_panic() {
150 let mut cs = ConstraintSet::new();
151 cs.add(FieldConstraint::new("/f", ["NO_SUCH_RULE"]));
152 let registry = RuleRegistry::with_defaults();
153 let result = cs.validate_field("/f", "anything", ®istry);
155 assert!(result.is_valid());
156 }
157
158 #[test]
159 fn for_path_returns_correct_constraints() {
160 let mut cs = ConstraintSet::new();
161 cs.add(FieldConstraint::new("/a", ["IBAN_CHECK"]));
162 cs.add(FieldConstraint::new("/b", ["BIC_CHECK"]));
163 cs.add(FieldConstraint::new("/a", ["BIC_CHECK"]));
164
165 let a_constraints = cs.for_path("/a");
166 assert_eq!(a_constraints.len(), 2);
167
168 let b_constraints = cs.for_path("/b");
169 assert_eq!(b_constraints.len(), 1);
170
171 let c_constraints = cs.for_path("/c");
172 assert!(c_constraints.is_empty());
173 }
174
175 #[test]
176 fn len_and_is_empty() {
177 let mut cs = ConstraintSet::new();
178 assert!(cs.is_empty());
179 assert_eq!(cs.len(), 0);
180 cs.add(FieldConstraint::new("/a", ["R1"]));
181 assert!(!cs.is_empty());
182 assert_eq!(cs.len(), 1);
183 }
184}