1use crate::rules::{
2 CharacterFilter, CharacterLimits, DisplayMask, PatternFilters, PositionFilter, PositionRange,
3};
4use serde::{Deserialize, Serialize};
5use std::sync::Arc;
6use thiserror::Error;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct AllowedValues {
10 pub values: Vec<String>,
11 pub allow_empty: bool,
12 pub case_insensitive: bool,
13}
14
15impl AllowedValues {
16 pub fn new(values: Vec<String>) -> Self {
17 Self {
18 values,
19 allow_empty: true,
20 case_insensitive: false,
21 }
22 }
23
24 pub fn allow_empty(mut self, allow_empty: bool) -> Self {
25 self.allow_empty = allow_empty;
26 self
27 }
28
29 pub fn case_insensitive(mut self, case_insensitive: bool) -> Self {
30 self.case_insensitive = case_insensitive;
31 self
32 }
33
34 pub fn matches(&self, text: &str) -> bool {
35 if self.case_insensitive {
36 self.values
37 .iter()
38 .any(|allowed| allowed.eq_ignore_ascii_case(text))
39 } else {
40 self.values.iter().any(|allowed| allowed == text)
41 }
42 }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub enum CharacterFilterSettings {
47 Alphabetic,
48 Numeric,
49 Alphanumeric,
50 Exact(char),
51 OneOf(Vec<char>),
52 Regex(String),
53}
54
55impl CharacterFilterSettings {
56 pub fn resolve(&self) -> CharacterFilter {
57 match self {
58 Self::Alphabetic => CharacterFilter::Alphabetic,
59 Self::Numeric => CharacterFilter::Numeric,
60 Self::Alphanumeric => CharacterFilter::Alphanumeric,
61 Self::Exact(ch) => CharacterFilter::Exact(*ch),
62 Self::OneOf(chars) => CharacterFilter::OneOf(chars.clone()),
63 Self::Regex(pattern) => {
64 #[cfg(feature = "regex")]
65 {
66 match regex::Regex::new(pattern) {
67 Ok(regex) => CharacterFilter::Custom(Arc::new(move |ch| {
68 regex.is_match(&ch.to_string())
69 })),
70 Err(_) => CharacterFilter::Custom(Arc::new(|_| false)),
71 }
72 }
73 #[cfg(not(feature = "regex"))]
74 {
75 let _ = pattern;
76 CharacterFilter::Custom(Arc::new(|_| false))
77 }
78 }
79 }
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct PositionFilterSettings {
85 pub positions: PositionRange,
86 pub filter: CharacterFilterSettings,
87}
88
89impl PositionFilterSettings {
90 pub fn resolve(&self) -> PositionFilter {
91 PositionFilter::new(self.positions.clone(), self.filter.resolve())
92 }
93}
94
95#[derive(Debug, Clone, Default, Serialize, Deserialize)]
96pub struct PatternSettings {
97 pub filters: Vec<PositionFilterSettings>,
98 pub description: Option<String>,
99}
100
101impl PatternSettings {
102 pub fn resolve(&self) -> PatternFilters {
103 PatternFilters::new().add_filters(
104 self.filters
105 .iter()
106 .map(PositionFilterSettings::resolve)
107 .collect(),
108 )
109 }
110}
111
112#[derive(Debug, Clone, Default, Serialize, Deserialize)]
113pub struct ValidationSettings {
114 pub required: bool,
115 pub character_limits: Option<CharacterLimits>,
116 pub pattern: Option<PatternSettings>,
117 pub allowed_values: Option<AllowedValues>,
118 pub display_mask: Option<DisplayMask>,
119 pub external_validation_enabled: bool,
120}
121
122impl ValidationSettings {
123 pub fn resolve(&self) -> ValidationConfig {
124 ValidationConfig {
125 required: self.required,
126 character_limits: self.character_limits.clone(),
127 pattern_filters: self.pattern.as_ref().map(PatternSettings::resolve),
128 allowed_values: self.allowed_values.clone(),
129 display_mask: self.display_mask.clone(),
130 external_validation_enabled: self.external_validation_enabled,
131 }
132 }
133
134 pub fn merge_rules<'a>(
135 rules: impl IntoIterator<Item = &'a ValidationSettings>,
136 ) -> Result<Self, ValidationMergeError> {
137 let mut merged = ValidationSettings::default();
138
139 for rule in rules {
140 merged.merge_rule(rule)?;
141 }
142
143 Ok(merged)
144 }
145
146 pub fn merge_rule(&mut self, rule: &ValidationSettings) -> Result<(), ValidationMergeError> {
147 self.required |= rule.required;
148 self.external_validation_enabled |= rule.external_validation_enabled;
149
150 merge_singleton(
151 "character_limits",
152 &mut self.character_limits,
153 &rule.character_limits,
154 )?;
155 merge_singleton(
156 "allowed_values",
157 &mut self.allowed_values,
158 &rule.allowed_values,
159 )?;
160 merge_singleton("display_mask", &mut self.display_mask, &rule.display_mask)?;
161
162 if let Some(pattern) = &rule.pattern {
163 match &mut self.pattern {
164 Some(existing) => {
165 existing.filters.extend(pattern.filters.clone());
166 if existing.description.is_none() {
167 existing.description = pattern.description.clone();
168 }
169 }
170 None => self.pattern = Some(pattern.clone()),
171 }
172 }
173
174 Ok(())
175 }
176}
177
178fn merge_singleton<T: Clone>(
179 field_name: &'static str,
180 target: &mut Option<T>,
181 source: &Option<T>,
182) -> Result<(), ValidationMergeError> {
183 if let Some(source) = source {
184 if target.is_some() {
185 return Err(ValidationMergeError::DuplicateSingleton { field_name });
186 }
187
188 *target = Some(source.clone());
189 }
190
191 Ok(())
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Error)]
195pub enum ValidationMergeError {
196 #[error("validation set contains more than one rule configuring {field_name}")]
197 DuplicateSingleton { field_name: &'static str },
198}
199
200#[derive(Debug, Clone, Default)]
201pub struct ValidationConfig {
202 pub required: bool,
203 pub character_limits: Option<CharacterLimits>,
204 pub pattern_filters: Option<PatternFilters>,
205 pub allowed_values: Option<AllowedValues>,
206 pub display_mask: Option<DisplayMask>,
207 pub external_validation_enabled: bool,
208}
209
210impl ValidationConfig {
211 pub fn validate_content(&self, text: &str) -> ValidationResult {
212 if text.is_empty() {
213 if self.required {
214 return ValidationResult::error("Value required");
215 }
216
217 if let Some(allowed_values) = &self.allowed_values {
218 if !allowed_values.allow_empty {
219 return ValidationResult::error("Empty value is not allowed");
220 }
221 }
222
223 return ValidationResult::Valid;
224 }
225
226 if let Some(limits) = &self.character_limits {
227 if let Some(result) = limits.validate_content(text) {
228 if !result.is_acceptable() {
229 return result;
230 }
231 }
232 }
233
234 if let Some(pattern_filters) = &self.pattern_filters {
235 if let Err(message) = pattern_filters.validate_text(text) {
236 return ValidationResult::error(message);
237 }
238 }
239
240 if let Some(allowed_values) = &self.allowed_values {
241 if !allowed_values.matches(text) {
242 return ValidationResult::error("Value must be one of the allowed options");
243 }
244 }
245
246 ValidationResult::Valid
247 }
248
249 pub fn has_validation(&self) -> bool {
250 self.required
251 || self.character_limits.is_some()
252 || self.pattern_filters.is_some()
253 || self.allowed_values.is_some()
254 || self.display_mask.is_some()
255 || self.external_validation_enabled
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, Eq)]
260pub enum ValidationResult {
261 Valid,
262 Warning { message: String },
263 Error { message: String },
264}
265
266impl ValidationResult {
267 pub fn is_acceptable(&self) -> bool {
268 matches!(self, Self::Valid | Self::Warning { .. })
269 }
270
271 pub fn is_error(&self) -> bool {
272 matches!(self, Self::Error { .. })
273 }
274
275 pub fn message(&self) -> Option<&str> {
276 match self {
277 Self::Valid => None,
278 Self::Warning { message } | Self::Error { message } => Some(message),
279 }
280 }
281
282 pub fn warning(message: impl Into<String>) -> Self {
283 Self::Warning {
284 message: message.into(),
285 }
286 }
287
288 pub fn error(message: impl Into<String>) -> Self {
289 Self::Error {
290 message: message.into(),
291 }
292 }
293}