1use std::{borrow::Cow, fmt};
9
10use crate::{
11 into_caveat, json,
12 warning::{self, GatherWarnings as _, IntoCaveat as _},
13 OutOfRange, Verdict,
14};
15
16#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
17pub enum WarningKind {
18 ContainsEscapeCodes,
20
21 Decode(json::decode::WarningKind),
23
24 PreferUpperCase,
26
27 InvalidDay,
29
30 InvalidType,
32}
33
34impl fmt::Display for WarningKind {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 WarningKind::ContainsEscapeCodes => write!(
38 f,
39 "The value contains escape codes but it does not need them."
40 ),
41 WarningKind::Decode(warning) => fmt::Display::fmt(warning, f),
42 WarningKind::PreferUpperCase => write!(f, "The day should be uppercase."),
43 WarningKind::InvalidDay => {
44 write!(f, "The value is not a valid day.")
45 }
46 WarningKind::InvalidType => write!(f, "The value should be a string."),
47 }
48 }
49}
50
51impl warning::Kind for WarningKind {
52 fn id(&self) -> Cow<'static, str> {
53 match self {
54 WarningKind::ContainsEscapeCodes => "contains_escape_codes".into(),
55 WarningKind::Decode(kind) => kind.id(),
56 WarningKind::PreferUpperCase => "prefer_upper_case".into(),
57 WarningKind::InvalidDay => "invalid_day".into(),
58 WarningKind::InvalidType => "invalid_type".into(),
59 }
60 }
61}
62
63impl From<json::decode::WarningKind> for WarningKind {
64 fn from(warn_kind: json::decode::WarningKind) -> Self {
65 Self::Decode(warn_kind)
66 }
67}
68
69#[derive(Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Hash)]
71pub(crate) enum Weekday {
72 Monday,
74 Tuesday,
76 Wednesday,
78 Thursday,
80 Friday,
82 Saturday,
84 Sunday,
86}
87
88into_caveat!(Weekday);
89
90impl From<chrono::Weekday> for Weekday {
92 fn from(value: chrono::Weekday) -> Self {
93 match value {
94 chrono::Weekday::Mon => Weekday::Monday,
95 chrono::Weekday::Tue => Weekday::Tuesday,
96 chrono::Weekday::Wed => Weekday::Wednesday,
97 chrono::Weekday::Thu => Weekday::Thursday,
98 chrono::Weekday::Fri => Weekday::Friday,
99 chrono::Weekday::Sat => Weekday::Saturday,
100 chrono::Weekday::Sun => Weekday::Sunday,
101 }
102 }
103}
104
105impl From<Weekday> for usize {
107 fn from(value: Weekday) -> Self {
108 match value {
109 Weekday::Monday => 0,
110 Weekday::Tuesday => 1,
111 Weekday::Wednesday => 2,
112 Weekday::Thursday => 3,
113 Weekday::Friday => 4,
114 Weekday::Saturday => 5,
115 Weekday::Sunday => 6,
116 }
117 }
118}
119
120impl TryFrom<usize> for Weekday {
122 type Error = OutOfRange;
123
124 fn try_from(value: usize) -> Result<Self, Self::Error> {
125 let day = match value {
126 0 => Weekday::Monday,
127 1 => Weekday::Tuesday,
128 2 => Weekday::Wednesday,
129 3 => Weekday::Thursday,
130 4 => Weekday::Friday,
131 5 => Weekday::Saturday,
132 6 => Weekday::Sunday,
133 _ => return Err(OutOfRange::new()),
134 };
135
136 Ok(day)
137 }
138}
139
140impl json::FromJson<'_, '_> for Weekday {
141 type WarningKind = WarningKind;
142
143 fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::WarningKind> {
144 const NUM_DAYS: usize = 7;
145 const DAYS: [&str; NUM_DAYS] = [
146 "MONDAY",
147 "TUESDAY",
148 "WEDNESDAY",
149 "THURSDAY",
150 "FRIDAY",
151 "SATURDAY",
152 "SUNDAY",
153 ];
154
155 let mut warnings = warning::Set::new();
156 let value = elem.as_value();
157
158 let Some(s) = value.as_raw_str() else {
159 warnings.with_elem(WarningKind::InvalidType, elem);
160 return Err(warnings);
161 };
162
163 let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
164
165 let s = match pending_str {
166 json::decode::PendingStr::NoEscapes(s) => s,
167 json::decode::PendingStr::HasEscapes(_) => {
168 warnings.with_elem(WarningKind::ContainsEscapeCodes, elem);
169 return Err(warnings);
170 }
171 };
172
173 if !s.chars().all(char::is_uppercase) {
174 warnings.with_elem(WarningKind::PreferUpperCase, elem);
175 }
176
177 let Some(index) = DAYS.iter().position(|day| day.eq_ignore_ascii_case(s)) else {
178 warnings.with_elem(WarningKind::InvalidDay, elem);
179 return Err(warnings);
180 };
181
182 let Ok(day) = Weekday::try_from(index) else {
183 warnings.with_elem(WarningKind::InvalidDay, elem);
184 return Err(warnings);
185 };
186
187 Ok(day.into_caveat(warnings))
188 }
189}
190
191impl From<Weekday> for chrono::Weekday {
192 fn from(day: Weekday) -> Self {
193 match day {
194 Weekday::Monday => Self::Mon,
195 Weekday::Tuesday => Self::Tue,
196 Weekday::Wednesday => Self::Wed,
197 Weekday::Thursday => Self::Thu,
198 Weekday::Friday => Self::Fri,
199 Weekday::Saturday => Self::Sat,
200 Weekday::Sunday => Self::Sun,
201 }
202 }
203}
204
205#[cfg(test)]
206mod test_day_of_week {
207 use assert_matches::assert_matches;
208
209 use crate::{
210 json::{self, FromJson as _},
211 test,
212 };
213
214 use super::{WarningKind, Weekday};
215
216 #[test]
217 fn should_create_from_json() {
218 const JSON: &str = r#""MONDAY""#;
219
220 test::setup();
221
222 let elem = json::parse(JSON).unwrap();
223 let day = Weekday::from_json(&elem).unwrap().unwrap();
224 assert_matches!(day, Weekday::Monday);
225 }
226
227 #[test]
228 fn should_fail_on_type_from_json() {
229 const JSON: &str = "[]";
230
231 test::setup();
232
233 let elem = json::parse(JSON).unwrap();
234 let warnings = Weekday::from_json(&elem).unwrap_err().into_kind_vec();
235 assert_matches!(*warnings, [WarningKind::InvalidType]);
236 }
237
238 #[test]
239 fn should_fail_on_value_from_json() {
240 const JSON: &str = r#""MOONDAY""#;
241
242 test::setup();
243
244 let elem = json::parse(JSON).unwrap();
245 let warnings = Weekday::from_json(&elem).unwrap_err().into_kind_vec();
246 assert_matches!(*warnings, [WarningKind::InvalidDay]);
247 }
248
249 #[test]
250 fn should_warn_about_case_from_json() {
251 const JSON: &str = r#""sunday""#;
252
253 test::setup();
254
255 let elem = json::parse(JSON).unwrap();
256 let (day, warnings) = Weekday::from_json(&elem).unwrap().into_parts();
257 let warnings = warnings.into_kind_vec();
258
259 assert_matches!(day, Weekday::Sunday);
260 assert_matches!(*warnings, [WarningKind::PreferUpperCase]);
261 }
262}