1#[cfg(test)]
9mod test_day_of_week;
10
11use std::fmt;
12
13use crate::{
14 into_caveat, json,
15 warning::{self, GatherWarnings as _, IntoCaveat as _},
16 OutOfRange, Verdict,
17};
18
19#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
20pub enum Warning {
21 ContainsEscapeCodes,
23
24 Decode(json::decode::Warning),
26
27 PreferUpperCase,
29
30 InvalidDay,
32
33 InvalidType { type_found: json::ValueKind },
35}
36
37impl Warning {
38 fn invalid_type(elem: &json::Element<'_>) -> Self {
39 Self::InvalidType {
40 type_found: elem.value().kind(),
41 }
42 }
43}
44
45impl fmt::Display for Warning {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 Self::ContainsEscapeCodes => write!(
49 f,
50 "The value contains escape codes but it does not need them."
51 ),
52 Self::Decode(warning) => fmt::Display::fmt(warning, f),
53 Self::PreferUpperCase => write!(f, "The day should be uppercase."),
54 Self::InvalidDay => {
55 write!(f, "The value is not a valid day.")
56 }
57 Self::InvalidType { type_found } => {
58 write!(f, "The value should be a string but is `{type_found}`")
59 }
60 }
61 }
62}
63
64impl crate::Warning for Warning {
65 fn id(&self) -> warning::Id {
66 match self {
67 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
68 Self::Decode(warning) => warning.id(),
69 Self::PreferUpperCase => warning::Id::from_static("prefer_upper_case"),
70 Self::InvalidDay => warning::Id::from_static("invalid_day"),
71 Self::InvalidType { .. } => warning::Id::from_static("invalid_type"),
72 }
73 }
74}
75
76impl From<json::decode::Warning> for Warning {
77 fn from(warning: json::decode::Warning) -> Self {
78 Self::Decode(warning)
79 }
80}
81
82#[derive(Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Hash)]
84pub(crate) enum Weekday {
85 Monday,
87 Tuesday,
89 Wednesday,
91 Thursday,
93 Friday,
95 Saturday,
97 Sunday,
99}
100
101into_caveat!(Weekday);
102
103impl From<chrono::Weekday> for Weekday {
105 fn from(value: chrono::Weekday) -> Self {
106 match value {
107 chrono::Weekday::Mon => Weekday::Monday,
108 chrono::Weekday::Tue => Weekday::Tuesday,
109 chrono::Weekday::Wed => Weekday::Wednesday,
110 chrono::Weekday::Thu => Weekday::Thursday,
111 chrono::Weekday::Fri => Weekday::Friday,
112 chrono::Weekday::Sat => Weekday::Saturday,
113 chrono::Weekday::Sun => Weekday::Sunday,
114 }
115 }
116}
117
118impl From<Weekday> for usize {
120 fn from(value: Weekday) -> Self {
121 match value {
122 Weekday::Monday => 0,
123 Weekday::Tuesday => 1,
124 Weekday::Wednesday => 2,
125 Weekday::Thursday => 3,
126 Weekday::Friday => 4,
127 Weekday::Saturday => 5,
128 Weekday::Sunday => 6,
129 }
130 }
131}
132
133impl TryFrom<usize> for Weekday {
135 type Error = OutOfRange;
136
137 fn try_from(value: usize) -> Result<Self, Self::Error> {
138 let day = match value {
139 0 => Weekday::Monday,
140 1 => Weekday::Tuesday,
141 2 => Weekday::Wednesday,
142 3 => Weekday::Thursday,
143 4 => Weekday::Friday,
144 5 => Weekday::Saturday,
145 6 => Weekday::Sunday,
146 _ => return Err(OutOfRange::new()),
147 };
148
149 Ok(day)
150 }
151}
152
153impl json::FromJson<'_, '_> for Weekday {
154 type Warning = Warning;
155
156 fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
157 const NUM_DAYS: usize = 7;
158 const DAYS: [&str; NUM_DAYS] = [
159 "MONDAY",
160 "TUESDAY",
161 "WEDNESDAY",
162 "THURSDAY",
163 "FRIDAY",
164 "SATURDAY",
165 "SUNDAY",
166 ];
167
168 let mut warnings = warning::Set::new();
169 let value = elem.as_value();
170
171 let Some(s) = value.as_raw_str() else {
172 return warnings.bail(Warning::invalid_type(elem), elem);
173 };
174
175 let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
176
177 let s = match pending_str {
178 json::decode::PendingStr::NoEscapes(s) => s,
179 json::decode::PendingStr::HasEscapes(_) => {
180 return warnings.bail(Warning::ContainsEscapeCodes, elem);
181 }
182 };
183
184 if !s.chars().all(char::is_uppercase) {
185 warnings.insert(Warning::PreferUpperCase, elem);
186 }
187
188 let Some(index) = DAYS.iter().position(|day| day.eq_ignore_ascii_case(s)) else {
189 return warnings.bail(Warning::InvalidDay, elem);
190 };
191
192 let Ok(day) = Weekday::try_from(index) else {
193 return warnings.bail(Warning::InvalidDay, elem);
194 };
195
196 Ok(day.into_caveat(warnings))
197 }
198}
199
200impl From<Weekday> for chrono::Weekday {
201 fn from(day: Weekday) -> Self {
202 match day {
203 Weekday::Monday => Self::Mon,
204 Weekday::Tuesday => Self::Tue,
205 Weekday::Wednesday => Self::Wed,
206 Weekday::Thursday => Self::Thu,
207 Weekday::Friday => Self::Fri,
208 Weekday::Saturday => Self::Sat,
209 Weekday::Sunday => Self::Sun,
210 }
211 }
212}