1use std::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 Warning {
18 ContainsEscapeCodes,
20
21 Decode(json::decode::Warning),
23
24 PreferUpperCase,
26
27 InvalidDay,
29
30 InvalidType,
32}
33
34impl fmt::Display for Warning {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 Warning::ContainsEscapeCodes => write!(
38 f,
39 "The value contains escape codes but it does not need them."
40 ),
41 Warning::Decode(warning) => fmt::Display::fmt(warning, f),
42 Warning::PreferUpperCase => write!(f, "The day should be uppercase."),
43 Warning::InvalidDay => {
44 write!(f, "The value is not a valid day.")
45 }
46 Warning::InvalidType => write!(f, "The value should be a string."),
47 }
48 }
49}
50
51impl crate::Warning for Warning {
52 fn id(&self) -> crate::SmartString {
53 match self {
54 Warning::ContainsEscapeCodes => "contains_escape_codes".into(),
55 Warning::Decode(warning) => warning.id(),
56 Warning::PreferUpperCase => "prefer_upper_case".into(),
57 Warning::InvalidDay => "invalid_day".into(),
58 Warning::InvalidType => "invalid_type".into(),
59 }
60 }
61}
62
63impl From<json::decode::Warning> for Warning {
64 fn from(warning: json::decode::Warning) -> Self {
65 Self::Decode(warning)
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 Warning = Warning;
142
143 fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
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 return warnings.bail(Warning::InvalidType, elem);
160 };
161
162 let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
163
164 let s = match pending_str {
165 json::decode::PendingStr::NoEscapes(s) => s,
166 json::decode::PendingStr::HasEscapes(_) => {
167 return warnings.bail(Warning::ContainsEscapeCodes, elem);
168 }
169 };
170
171 if !s.chars().all(char::is_uppercase) {
172 warnings.with_elem(Warning::PreferUpperCase, elem);
173 }
174
175 let Some(index) = DAYS.iter().position(|day| day.eq_ignore_ascii_case(s)) else {
176 return warnings.bail(Warning::InvalidDay, elem);
177 };
178
179 let Ok(day) = Weekday::try_from(index) else {
180 return warnings.bail(Warning::InvalidDay, elem);
181 };
182
183 Ok(day.into_caveat(warnings))
184 }
185}
186
187impl From<Weekday> for chrono::Weekday {
188 fn from(day: Weekday) -> Self {
189 match day {
190 Weekday::Monday => Self::Mon,
191 Weekday::Tuesday => Self::Tue,
192 Weekday::Wednesday => Self::Wed,
193 Weekday::Thursday => Self::Thu,
194 Weekday::Friday => Self::Fri,
195 Weekday::Saturday => Self::Sat,
196 Weekday::Sunday => Self::Sun,
197 }
198 }
199}
200
201#[cfg(test)]
202mod test_day_of_week {
203 #![allow(
204 clippy::indexing_slicing,
205 reason = "unwraps are allowed anywhere in tests"
206 )]
207
208 use assert_matches::assert_matches;
209
210 use crate::{
211 json::{self, FromJson as _},
212 test,
213 warning::test::VerdictTestExt,
214 };
215
216 use super::{Warning, Weekday};
217
218 #[test]
219 fn should_create_from_json() {
220 const JSON: &str = r#""MONDAY""#;
221
222 test::setup();
223
224 let elem = json::parse(JSON).unwrap();
225 let day = Weekday::from_json(&elem).unwrap().unwrap();
226 assert_matches!(day, Weekday::Monday);
227 }
228
229 #[test]
230 fn should_fail_on_type_from_json() {
231 const JSON: &str = "[]";
232
233 test::setup();
234
235 let elem = json::parse(JSON).unwrap();
236 let error = Weekday::from_json(&elem).unwrap_only_error();
237 assert_matches!(error.into_warning(), Warning::InvalidType);
238 }
239
240 #[test]
241 fn should_fail_on_value_from_json() {
242 const JSON: &str = r#""MOONDAY""#;
243
244 test::setup();
245
246 let elem = json::parse(JSON).unwrap();
247 let error = Weekday::from_json(&elem).unwrap_only_error();
248 assert_matches!(error.into_warning(), Warning::InvalidDay);
249 }
250
251 #[test]
252 fn should_warn_about_case_from_json() {
253 const JSON: &str = r#""sunday""#;
254
255 test::setup();
256
257 let elem = json::parse(JSON).unwrap();
258 let (day, warnings) = Weekday::from_json(&elem).unwrap().into_parts();
259 let warnings = warnings.into_path_map();
260 let warnings = &*warnings["$"];
261
262 assert_matches!(day, Weekday::Sunday);
263 assert_matches!(warnings, [Warning::PreferUpperCase]);
264 }
265}