Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/src/validation/profile.rs

11.7 KiB, 69 runs

created by r1870400018:8626, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Named bundles of validation rules.
2//!
3//! A profile carries a set of rules, a strictness flag and some metadata, and
4//! turns into a validator on demand. A registry holds several by name, and a
5//! set of standard profiles covers the usual cases.
6//!
7//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
8//! Anthropic Claude
9
10use crate::{
11 validation::{
12 CalClockValidator, ValidationRule, ValidationRules, ConditionalRule,
13 },
14 constant::{DayOfWeek, MonthOfYear},
15};
16
17use oxedyne_fe2o3_core::prelude::*;
18
19use std::collections::{HashMap, HashSet};
20
21/// # Examples
22///
23/// ```ignore
24/// use oxedyne_fe2o3_datime::validation::{ValidationProfile, ProfileBuilder};
25///
26/// // Create a business scheduling profile
27/// let profile = ProfileBuilder::new("business_scheduling")
28/// .description("Standard business scheduling rules")
29/// .business_hours_only()
30/// .no_weekends()
31/// .future_dates_only()
32/// .build();
33///
34/// let validator = profile.create_validator();
35/// ```
36#[derive(Debug)]
37pub struct ValidationProfile {
38 name: String,
39 description: Option<String>,
40 version: String,
41 tags: Vec<String>,
42 rules: Vec<ValidationRule>,
43 metadata: HashMap<String, String>,
44 strict: bool, // builds a strict validator
45}
46
47impl ValidationProfile {
48 pub fn new<S: Into<String>>(name: S) -> Self {
49 Self {
50 name: name.into(),
51 description: None,
52 version: "1.0.0".to_string(),
53 tags: Vec::new(),
54 rules: Vec::new(),
55 metadata: HashMap::new(),
56 strict: true,
57 }
58 }
59
60 pub fn description<S: Into<String>>(mut self, description: S) -> Self {
61 self.description = Some(description.into());
62 self
63 }
64
65 pub fn version<S: Into<String>>(mut self, version: S) -> Self {
66 self.version = version.into();
67 self
68 }
69
70 pub fn tag<S: Into<String>>(mut self, tag: S) -> Self {
71 self.tags.push(tag.into());
72 self
73 }
74
75 pub fn tags<S: Into<String>>(mut self, tags: Vec<S>) -> Self {
76 for tag in tags {
77 self.tags.push(tag.into());
78 }
79 self
80 }
81
82 pub fn rule(mut self, rule: ValidationRule) -> Self {
83 self.rules.push(rule);
84 self
85 }
86
87 pub fn rules(mut self, mut rules: Vec<ValidationRule>) -> Self {
88 self.rules.append(&mut rules);
89 self
90 }
91
92 pub fn metadata<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
93 self.metadata.insert(key.into(), value.into());
94 self
95 }
96
97 pub fn strict(mut self, strict: bool) -> Self {
98 self.strict = strict;
99 self
100 }
101
102 pub fn create_validator(self) -> CalClockValidator {
103 let mut validator = if self.strict {
104 CalClockValidator::strict()
105 } else {
106 CalClockValidator::new()
107 };
108
109 // Move rules into the validator since ValidationRule doesn't implement Clone
110 for rule in self.rules {
111 validator.add_rule(rule);
112 }
113
114 validator
115 }
116
117 pub fn name(&self) -> &str {
118 &self.name
119 }
120
121 pub fn get_description(&self) -> Option<&str> {
122 self.description.as_deref()
123 }
124
125 pub fn get_version(&self) -> &str {
126 &self.version
127 }
128
129 pub fn get_tags(&self) -> &[String] {
130 &self.tags
131 }
132
133 pub fn get_rules(&self) -> &[ValidationRule] {
134 &self.rules
135 }
136
137 pub fn get_metadata(&self) -> &HashMap<String, String> {
138 &self.metadata
139 }
140
141 pub fn is_strict(&self) -> bool {
142 self.strict
143 }
144
145 /// Rules and tags accumulate; where a metadata key appears in both,
146 /// the other profile's value wins.
147 pub fn merge(mut self, other: ValidationProfile) -> Self {
148 self.rules.extend(other.rules);
149 self.tags.extend(other.tags);
150 self.metadata.extend(other.metadata);
151 self
152 }
153}
154
155#[derive(Debug)]
156pub struct ProfileBuilder {
157 profile: ValidationProfile,
158}
159
160impl ProfileBuilder {
161 pub fn new<S: Into<String>>(name: S) -> Self {
162 Self {
163 profile: ValidationProfile::new(name),
164 }
165 }
166
167 pub fn description<S: Into<String>>(mut self, description: S) -> Self {
168 self.profile = self.profile.description(description);
169 self
170 }
171
172 pub fn version<S: Into<String>>(mut self, version: S) -> Self {
173 self.profile = self.profile.version(version);
174 self
175 }
176
177 pub fn tag<S: Into<String>>(mut self, tag: S) -> Self {
178 self.profile = self.profile.tag(tag);
179 self
180 }
181
182 pub fn strict(mut self, strict: bool) -> Self {
183 self.profile = self.profile.strict(strict);
184 self
185 }
186
187 pub fn rule(mut self, rule: ValidationRule) -> Self {
188 self.profile = self.profile.rule(rule);
189 self
190 }
191
192 pub fn business_hours_only(mut self) -> Self {
193 self.profile = self.profile.rule(ValidationRules::business_hours());
194 self
195 }
196
197 pub fn no_weekends(mut self) -> Self {
198 let mut weekdays = HashSet::new();
199 weekdays.insert(DayOfWeek::Monday);
200 weekdays.insert(DayOfWeek::Tuesday);
201 weekdays.insert(DayOfWeek::Wednesday);
202 weekdays.insert(DayOfWeek::Thursday);
203 weekdays.insert(DayOfWeek::Friday);
204
205 self.profile = self.profile.rule(ValidationRules::allowed_weekdays(weekdays));
206 self
207 }
208
209 pub fn weekends_only(mut self) -> Self {
210 self.profile = self.profile.rule(ValidationRules::weekends_only());
211 self
212 }
213
214 pub fn no_holidays(mut self) -> Self {
215 self.profile = self.profile.rule(ValidationRules::no_holidays());
216 self
217 }
218
219 pub fn future_dates_only(mut self) -> Self {
220 self.profile = self.profile.rule(ValidationRules::not_too_old(0));
221 self
222 }
223
224 pub fn past_dates_only(mut self) -> Self {
225 self.profile = self.profile.rule(ValidationRules::not_too_future(0));
226 self
227 }
228
229 pub fn whole_minutes_only(mut self) -> Self {
230 self.profile = self.profile.rule(ValidationRules::whole_minutes_only());
231 self
232 }
233
234 pub fn whole_seconds_only(mut self) -> Self {
235 self.profile = self.profile.rule(ValidationRules::whole_seconds_only());
236 self
237 }
238
239 pub fn months_only(mut self, months: Vec<MonthOfYear>) -> Self {
240 let month_set: HashSet<MonthOfYear> = months.into_iter().collect();
241 self.profile = self.profile.rule(ValidationRules::allowed_months(month_set));
242 self
243 }
244
245 pub fn hour_range(mut self, min_hour: u8, max_hour: u8) -> Self {
246 self.profile = self.profile.rule(ValidationRules::hour_range(min_hour, max_hour));
247 self
248 }
249
250 pub fn year_range(mut self, min_year: i32, max_year: i32) -> Self {
251 self.profile = self.profile.rule(ValidationRules::year_range(min_year, max_year));
252 self
253 }
254
255 pub fn conditional_business_hours(mut self) -> Self {
256 self.profile = self.profile.rule(ConditionalRule::business_hours_by_day().into_rule());
257 self
258 }
259
260 pub fn seasonal_hours(mut self) -> Self {
261 self.profile = self.profile.rule(ConditionalRule::seasonal_hours().into_rule());
262 self
263 }
264
265 pub fn holiday_scheduling(mut self) -> Self {
266 self.profile = self.profile.rule(ConditionalRule::holiday_scheduling().into_rule());
267 self
268 }
269
270 pub fn build(self) -> ValidationProfile {
271 self.profile
272 }
273}
274
275#[derive(Debug, Default)]
276pub struct ProfileRegistry {
277 profiles: HashMap<String, ValidationProfile>,
278}
279
280impl ProfileRegistry {
281 pub fn new() -> Self {
282 Self {
283 profiles: HashMap::new(),
284 }
285 }
286
287 pub fn register(&mut self, profile: ValidationProfile) -> Outcome<()> {
288 if self.profiles.contains_key(profile.name()) {
289 return Err(err!("Profile '{}' already exists", profile.name(); Duplicate));
290 }
291
292 self.profiles.insert(profile.name().to_string(), profile);
293 Ok(())
294 }
295
296 pub fn get(&self, name: &str) -> Option<&ValidationProfile> {
297 self.profiles.get(name)
298 }
299
300 pub fn get_mut(&mut self, name: &str) -> Option<&mut ValidationProfile> {
301 self.profiles.get_mut(name)
302 }
303
304 pub fn remove(&mut self, name: &str) -> Option<ValidationProfile> {
305 self.profiles.remove(name)
306 }
307
308 pub fn list_names(&self) -> Vec<String> {
309 self.profiles.keys().cloned().collect()
310 }
311
312 pub fn list_profiles(&self) -> Vec<&ValidationProfile> {
313 self.profiles.values().collect()
314 }
315
316 pub fn find_by_tag(&self, tag: &str) -> Vec<&ValidationProfile> {
317 self.profiles
318 .values()
319 .filter(|profile| profile.get_tags().contains(&tag.to_string()))
320 .collect()
321 }
322
323 pub fn clear(&mut self) {
324 self.profiles.clear();
325 }
326
327 pub fn len(&self) -> usize {
328 self.profiles.len()
329 }
330
331 pub fn is_empty(&self) -> bool {
332 self.profiles.is_empty()
333 }
334}
335
336pub struct StandardProfiles;
337
338impl StandardProfiles {
339 pub fn business_scheduling() -> ValidationProfile {
340 ProfileBuilder::new("business_scheduling")
341 .description("Standard business scheduling validation")
342 .tag("business")
343 .tag("scheduling")
344 .version("1.0.0")
345 .business_hours_only()
346 .no_weekends()
347 .no_holidays()
348 .whole_minutes_only()
349 .build()
350 }
351
352 pub fn appointment_booking() -> ValidationProfile {
353 ProfileBuilder::new("appointment_booking")
354 .description("Flexible appointment booking validation")
355 .tag("appointment")
356 .tag("booking")
357 .version("1.0.0")
358 .conditional_business_hours()
359 .future_dates_only()
360 .whole_minutes_only()
361 .build()
362 }
363
364 pub fn historical_data() -> ValidationProfile {
365 ProfileBuilder::new("historical_data")
366 .description("Historical data entry validation")
367 .tag("historical")
368 .tag("data")
369 .version("1.0.0")
370 .strict(true)
371 .past_dates_only()
372 .year_range(1900, 2024)
373 .build()
374 }
375
376 pub fn event_scheduling() -> ValidationProfile {
377 ProfileBuilder::new("event_scheduling")
378 .description("Event scheduling with seasonal hours")
379 .tag("event")
380 .tag("scheduling")
381 .version("1.0.0")
382 .seasonal_hours()
383 .holiday_scheduling()
384 .whole_minutes_only()
385 .build()
386 }
387
388 pub fn maintenance_window() -> ValidationProfile {
389 ProfileBuilder::new("maintenance_window")
390 .description("System maintenance window scheduling")
391 .tag("maintenance")
392 .tag("system")
393 .version("1.0.0")
394 .strict(true)
395 .hour_range(2, 6) // 2 AM - 6 AM
396 .weekends_only()
397 .future_dates_only()
398 .build()
399 }
400
401 pub fn testing() -> ValidationProfile {
402 ProfileBuilder::new("testing")
403 .description("Relaxed validation for testing purposes")
404 .tag("testing")
405 .tag("development")
406 .version("1.0.0")
407 .strict(false)
408 .year_range(1970, 2100)
409 .build()
410 }
411
412 pub fn create_registry() -> ProfileRegistry {
413 let mut registry = ProfileRegistry::new();
414
415 let _ = registry.register(Self::business_scheduling());
416 let _ = registry.register(Self::appointment_booking());
417 let _ = registry.register(Self::historical_data());
418 let _ = registry.register(Self::event_scheduling());
419 let _ = registry.register(Self::maintenance_window());
420 let _ = registry.register(Self::testing());
421
422 registry
423 }
424}