use std::{borrow::Cow, fmt};
use rust_decimal::Decimal;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::{
into_caveat, json,
warning::{self, IntoCaveat},
};
const DECIMAL_SCALE: u32 = 4;
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum WarningKind {
ContainsEscapeCodes,
ExceedsMaximumPossibleValue,
Internal(String),
InvalidType,
LessThanMinimumPossibleValue,
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(),
}
}
}
#[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())
}
#[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
}
}
}