karbon_framework/validation/constraints/date/
date_range.rs1use crate::validation::constraints::{Constraint, ConstraintResult, ConstraintViolation};
2use chrono::NaiveDate;
3
4pub struct DateRange {
6 pub min: Option<NaiveDate>,
7 pub max: Option<NaiveDate>,
8 pub format: String,
9 pub min_message: String,
10 pub max_message: String,
11 pub invalid_message: String,
12}
13
14impl Default for DateRange {
15 fn default() -> Self {
16 Self {
17 min: None,
18 max: None,
19 format: "%Y-%m-%d".to_string(),
20 min_message: "This date should be {{ limit }} or later.".to_string(),
21 max_message: "This date should be {{ limit }} or earlier.".to_string(),
22 invalid_message: "This value is not a valid date.".to_string(),
23 }
24 }
25}
26
27impl DateRange {
28 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn min(mut self, date: NaiveDate) -> Self {
33 self.min = Some(date);
34 self
35 }
36
37 pub fn max(mut self, date: NaiveDate) -> Self {
38 self.max = Some(date);
39 self
40 }
41
42 pub fn between(min: NaiveDate, max: NaiveDate) -> Self {
43 Self {
44 min: Some(min),
45 max: Some(max),
46 ..Self::default()
47 }
48 }
49
50 pub fn with_format(mut self, format: impl Into<String>) -> Self {
51 self.format = format.into();
52 self
53 }
54
55 pub fn future_only() -> Self {
56 Self {
57 min: Some(chrono::Utc::now().date_naive()),
58 min_message: "This date should be in the future.".to_string(),
59 ..Self::default()
60 }
61 }
62
63 pub fn past_only() -> Self {
64 Self {
65 max: Some(chrono::Utc::now().date_naive()),
66 max_message: "This date should be in the past.".to_string(),
67 ..Self::default()
68 }
69 }
70}
71
72impl Constraint for DateRange {
73 fn validate(&self, value: &str) -> ConstraintResult {
74 let date = NaiveDate::parse_from_str(value, &self.format).map_err(|_| {
75 ConstraintViolation::new(self.name(), &self.invalid_message, value)
76 })?;
77
78 if let Some(min) = self.min {
79 if date < min {
80 return Err(ConstraintViolation::new(
81 self.name(),
82 self.min_message.replace("{{ limit }}", &min.to_string()),
83 value,
84 ));
85 }
86 }
87
88 if let Some(max) = self.max {
89 if date > max {
90 return Err(ConstraintViolation::new(
91 self.name(),
92 self.max_message.replace("{{ limit }}", &max.to_string()),
93 value,
94 ));
95 }
96 }
97
98 Ok(())
99 }
100
101 fn name(&self) -> &'static str {
102 "DateRange"
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_within_range() {
112 let constraint = DateRange::between(
113 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
114 NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
115 );
116 assert!(constraint.validate("2024-06-15").is_ok());
117 assert!(constraint.validate("2024-01-01").is_ok());
118 assert!(constraint.validate("2024-12-31").is_ok());
119 }
120
121 #[test]
122 fn test_out_of_range() {
123 let constraint = DateRange::between(
124 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
125 NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
126 );
127 assert!(constraint.validate("2023-12-31").is_err());
128 assert!(constraint.validate("2025-01-01").is_err());
129 }
130
131 #[test]
132 fn test_min_only() {
133 let constraint = DateRange::new().min(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
134 assert!(constraint.validate("2024-06-15").is_ok());
135 assert!(constraint.validate("2023-12-31").is_err());
136 }
137
138 #[test]
139 fn test_max_only() {
140 let constraint = DateRange::new().max(NaiveDate::from_ymd_opt(2024, 12, 31).unwrap());
141 assert!(constraint.validate("2024-06-15").is_ok());
142 assert!(constraint.validate("2025-01-01").is_err());
143 }
144
145 #[test]
146 fn test_invalid_date_format() {
147 let constraint = DateRange::new();
148 assert!(constraint.validate("not-a-date").is_err());
149 }
150
151 #[test]
152 fn test_past_only() {
153 let constraint = DateRange::past_only();
154 assert!(constraint.validate("2020-01-01").is_ok());
155 assert!(constraint.validate("2099-01-01").is_err());
156 }
157
158 #[test]
159 fn test_future_only() {
160 let constraint = DateRange::future_only();
161 assert!(constraint.validate("2099-01-01").is_ok());
162 assert!(constraint.validate("2020-01-01").is_err());
163 }
164}