ocpi-tariffs 0.20.0

OCPI tariff calculations
Documentation
use std::{borrow::Cow, fmt};

use rust_decimal::Decimal;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::{
    into_caveat, json,
    warning::{self, IntoCaveat},
};

/// <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/types.asciidoc#14-number-type>
const DECIMAL_SCALE: u32 = 4;

#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum WarningKind {
    /// Neither the timezone or country field require char escape codes.
    ContainsEscapeCodes,

    /// The value provided exceeds `Decimal::MAX`.
    ExceedsMaximumPossibleValue,

    /// This error should not happen.
    Internal(String),

    /// The JSON value given is not a number.
    InvalidType,

    /// The value provided is less than `Decimal::MIN`.
    LessThanMinimumPossibleValue,

    /// An underflow is when there are more fractional digits than can be represented within `Decimal`.
    Underflow,
}

impl fmt::Display for WarningKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            WarningKind::ContainsEscapeCodes => write!(
                f,
                "The value contains escape codes but it does not need them"
            ),
            WarningKind::InvalidType => write!(f, "The value should be a number."),
            WarningKind::ExceedsMaximumPossibleValue => {
                write!(
                    f,
                    "The value provided exceeds `79,228,162,514,264,337,593,543,950,335`."
                )
            }
            WarningKind::LessThanMinimumPossibleValue => write!(
                f,
                "The value provided is less than `-79,228,162,514,264,337,593,543,950,335`."
            ),
            WarningKind::Underflow => write!(
                f,
                "An underflow is when there are more than 28 fractional digits"
            ),
            WarningKind::Internal(msg) => write!(f, "{msg}"),
        }
    }
}

impl warning::Kind for WarningKind {
    fn id(&self) -> Cow<'static, str> {
        match self {
            WarningKind::ContainsEscapeCodes => "contains_escape_codes".into(),
            WarningKind::InvalidType => "invalid_type".into(),
            WarningKind::ExceedsMaximumPossibleValue => "exceeds_maximum_possible_value".into(),
            WarningKind::LessThanMinimumPossibleValue => "less_than_minimum_possible_value".into(),
            WarningKind::Underflow => "underflow".into(),
            WarningKind::Internal(_) => "<internal_error>".into(),
        }
    }
}
/// A decimal number that will serialize to the precision defined in the OCPI spec.
///  
/// <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/types.asciidoc#14-number-type>
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Number(Decimal);

impl json::FromJson<'_, '_> for Number {
    type WarningKind = WarningKind;

    fn from_json(elem: &json::Element<'_>) -> crate::Verdict<Self, Self::WarningKind> {
        let mut warnings = warning::Set::new();
        let value = elem.as_value();

        let Some(s) = value.as_number() else {
            warnings.with_elem(WarningKind::InvalidType, elem);
            return Err(warnings);
        };

        let mut decimal = match Decimal::from_str_exact(s) {
            Ok(v) => v,
            Err(err) => {
                let kind = match err {
                    rust_decimal::Error::ConversionTo(msg)
                    | rust_decimal::Error::ErrorString(msg) => WarningKind::Internal(msg),
                    rust_decimal::Error::ExceedsMaximumPossibleValue => {
                        WarningKind::ExceedsMaximumPossibleValue
                    }
                    rust_decimal::Error::LessThanMinimumPossibleValue => {
                        WarningKind::LessThanMinimumPossibleValue
                    }
                    rust_decimal::Error::Underflow => WarningKind::Underflow,
                    rust_decimal::Error::ScaleExceedsMaximumPrecision(_) => {
                        WarningKind::Internal(err.to_string())
                    }
                };

                warnings.with_elem(kind, elem);
                return Err(warnings);
            }
        };

        decimal.rescale(DECIMAL_SCALE);
        Ok(Self(decimal).into_caveat(warnings))
    }
}

into_caveat!(Number);

impl Number {
    pub(crate) fn from_decimal(d: Decimal) -> Self {
        Self(d)
    }

    pub fn is_zero(&self) -> bool {
        self.0.is_zero()
    }

    #[must_use]
    pub fn ceil(self) -> Self {
        Self(self.0.ceil())
    }

    /// Round this number to the OCPI specified amount of decimals.
    #[must_use]
    pub fn rescale(mut self) -> Self {
        self.0.rescale(DECIMAL_SCALE);
        self
    }

    pub fn checked_div(self, other: Self) -> Option<Self> {
        self.0.checked_div(other.0).map(Self)
    }

    #[must_use]
    pub fn saturating_sub(self, other: Self) -> Self {
        Self(self.0.saturating_sub(other.0))
    }

    #[must_use]
    pub fn saturating_add(self, other: Self) -> Self {
        Self(self.0.saturating_add(other.0))
    }

    #[must_use]
    pub fn saturating_mul(self, other: Self) -> Self {
        Self(self.0.saturating_mul(other.0))
    }

    #[must_use]
    pub fn round_dp(self, digits: u32) -> Self {
        Self(self.0.round_dp(digits))
    }
}

impl From<Number> for Decimal {
    fn from(value: Number) -> Self {
        value.0
    }
}

impl<'de> Deserialize<'de> for Number {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let mut decimal = <Decimal as Deserialize>::deserialize(deserializer)?;
        decimal.rescale(DECIMAL_SCALE);
        Ok(Self(decimal))
    }
}

impl Serialize for Number {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut decimal = self.0;

        decimal.rescale(DECIMAL_SCALE);
        decimal.normalize_assign();

        Serialize::serialize(&decimal, serializer)
    }
}

impl From<Decimal> for Number {
    fn from(value: Decimal) -> Self {
        Self(value)
    }
}

impl From<i64> for Number {
    fn from(value: i64) -> Self {
        Self(value.into())
    }
}

impl From<u64> for Number {
    fn from(value: u64) -> Self {
        Self(value.into())
    }
}

impl From<i32> for Number {
    fn from(value: i32) -> Self {
        Self(value.into())
    }
}

impl TryFrom<Number> for i64 {
    type Error = rust_decimal::Error;

    fn try_from(value: Number) -> Result<Self, Self::Error> {
        value.0.try_into()
    }
}

impl fmt::Display for Number {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

#[cfg(test)]
mod test {
    use rust_decimal::Decimal;

    use crate::test::AsDecimal;

    use super::Number;

    impl AsDecimal for Number {
        fn as_dec(&self) -> &Decimal {
            &self.0
        }
    }
}