ocpi-tariffs 0.42.0

OCPI tariff calculations
Documentation
//! The Warning infrastructure for the OCPI `DayOfWeek` type.
//!
//! We rename the type of `Weekday` as this is the common way of expressing the concept in English.
//!
//! * 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>)
//! * 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>)

#[cfg(test)]
mod test_day_of_week;

use std::fmt;

use crate::{
    into_caveat, json,
    warning::{self, GatherWarnings as _, IntoCaveat as _},
    OutOfRange, Verdict,
};

#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Warning {
    /// Neither the day of the week does not need to have escape codes.
    ContainsEscapeCodes,

    /// The field at the path could not be decoded.
    Decode(json::decode::Warning),

    /// Each day of the week should be uppercase.
    PreferUpperCase,

    /// The value is not a valid day.
    InvalidDay,

    /// The JSON value given is not a string.
    InvalidType { type_found: json::ValueKind },
}

impl Warning {
    fn invalid_type(elem: &json::Element<'_>) -> Self {
        Self::InvalidType {
            type_found: elem.value().kind(),
        }
    }
}

impl fmt::Display for Warning {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::ContainsEscapeCodes => write!(
                f,
                "The value contains escape codes but it does not need them."
            ),
            Self::Decode(warning) => fmt::Display::fmt(warning, f),
            Self::PreferUpperCase => write!(f, "The day should be uppercase."),
            Self::InvalidDay => {
                write!(f, "The value is not a valid day.")
            }
            Self::InvalidType { type_found } => {
                write!(f, "The value should be a string but is `{type_found}`")
            }
        }
    }
}

impl crate::Warning for Warning {
    fn id(&self) -> warning::Id {
        match self {
            Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
            Self::Decode(warning) => warning.id(),
            Self::PreferUpperCase => warning::Id::from_static("prefer_upper_case"),
            Self::InvalidDay => warning::Id::from_static("invalid_day"),
            Self::InvalidType { .. } => warning::Id::from_static("invalid_type"),
        }
    }
}

impl From<json::decode::Warning> for Warning {
    fn from(warning: json::decode::Warning) -> Self {
        Self::Decode(warning)
    }
}

/// A single day of the week.
#[derive(Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Hash)]
pub(crate) enum Weekday {
    /// Monday
    Monday,
    /// Tuesday
    Tuesday,
    /// Wednesday
    Wednesday,
    /// Thursday
    Thursday,
    /// Friday
    Friday,
    /// Saturday
    Saturday,
    /// Sunday
    Sunday,
}

into_caveat!(Weekday);

/// Convert a `chrono::Weekday` into an OCPI `Weekday`.
impl From<chrono::Weekday> for Weekday {
    fn from(value: chrono::Weekday) -> Self {
        match value {
            chrono::Weekday::Mon => Weekday::Monday,
            chrono::Weekday::Tue => Weekday::Tuesday,
            chrono::Weekday::Wed => Weekday::Wednesday,
            chrono::Weekday::Thu => Weekday::Thursday,
            chrono::Weekday::Fri => Weekday::Friday,
            chrono::Weekday::Sat => Weekday::Saturday,
            chrono::Weekday::Sun => Weekday::Sunday,
        }
    }
}

/// Convert a `Weekday` into an index.
impl From<Weekday> for usize {
    fn from(value: Weekday) -> Self {
        match value {
            Weekday::Monday => 0,
            Weekday::Tuesday => 1,
            Weekday::Wednesday => 2,
            Weekday::Thursday => 3,
            Weekday::Friday => 4,
            Weekday::Saturday => 5,
            Weekday::Sunday => 6,
        }
    }
}

/// Convert an index into a `Weekday`.
impl TryFrom<usize> for Weekday {
    type Error = OutOfRange;

    fn try_from(value: usize) -> Result<Self, Self::Error> {
        let day = match value {
            0 => Weekday::Monday,
            1 => Weekday::Tuesday,
            2 => Weekday::Wednesday,
            3 => Weekday::Thursday,
            4 => Weekday::Friday,
            5 => Weekday::Saturday,
            6 => Weekday::Sunday,
            _ => return Err(OutOfRange::new()),
        };

        Ok(day)
    }
}

impl json::FromJson<'_, '_> for Weekday {
    type Warning = Warning;

    fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
        const NUM_DAYS: usize = 7;
        const DAYS: [&str; NUM_DAYS] = [
            "MONDAY",
            "TUESDAY",
            "WEDNESDAY",
            "THURSDAY",
            "FRIDAY",
            "SATURDAY",
            "SUNDAY",
        ];

        let mut warnings = warning::Set::new();
        let value = elem.as_value();

        let Some(s) = value.as_raw_str() else {
            return warnings.bail(Warning::invalid_type(elem), elem);
        };

        let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);

        let s = match pending_str {
            json::decode::PendingStr::NoEscapes(s) => s,
            json::decode::PendingStr::HasEscapes(_) => {
                return warnings.bail(Warning::ContainsEscapeCodes, elem);
            }
        };

        if !s.chars().all(char::is_uppercase) {
            warnings.insert(Warning::PreferUpperCase, elem);
        }

        let Some(index) = DAYS.iter().position(|day| day.eq_ignore_ascii_case(s)) else {
            return warnings.bail(Warning::InvalidDay, elem);
        };

        let Ok(day) = Weekday::try_from(index) else {
            return warnings.bail(Warning::InvalidDay, elem);
        };

        Ok(day.into_caveat(warnings))
    }
}

impl From<Weekday> for chrono::Weekday {
    fn from(day: Weekday) -> Self {
        match day {
            Weekday::Monday => Self::Mon,
            Weekday::Tuesday => Self::Tue,
            Weekday::Wednesday => Self::Wed,
            Weekday::Thursday => Self::Thu,
            Weekday::Friday => Self::Fri,
            Weekday::Saturday => Self::Sat,
            Weekday::Sunday => Self::Sun,
        }
    }
}