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