Skip to main content

ocpi_tariffs/
weekday.rs

1//! The Warning infrastructure for the OCPI `DayOfWeek` type.
2//!
3//! We rename the type of `Weekday` as this is the common way of expressing the concept in English.
4//!
5//! * See: [OCPI spec 2.2.1: Tariff DayOfWeek](<https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/mod_tariffs.asciidoc#mod_tariffs_dayofweek_enum>)
6//! * See: [OCPI spec 2.1.1: Tariff DayOfWeek](<https://github.com/ocpi/ocpi/blob/release-2.1.1-bugfixes/mod_tariffs.md#41-dayofweek-enum>)
7
8#[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    /// Neither the day of the week does not need to have escape codes.
22    ContainsEscapeCodes,
23
24    /// The field at the path could not be decoded.
25    Decode(json::decode::Warning),
26
27    /// Each day of the week should be uppercase.
28    PreferUpperCase,
29
30    /// The value is not a valid day.
31    InvalidDay,
32
33    /// The JSON value given is not a string.
34    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/// A single day of the week.
83#[derive(Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Hash)]
84pub(crate) enum Weekday {
85    /// Monday
86    Monday,
87    /// Tuesday
88    Tuesday,
89    /// Wednesday
90    Wednesday,
91    /// Thursday
92    Thursday,
93    /// Friday
94    Friday,
95    /// Saturday
96    Saturday,
97    /// Sunday
98    Sunday,
99}
100
101into_caveat!(Weekday);
102
103/// Convert a `chrono::Weekday` into an OCPI `Weekday`.
104impl 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
118/// Convert a `Weekday` into an index.
119impl 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
133/// Convert an index into a `Weekday`.
134impl 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}