use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use chrono::{DateTime, NaiveDate, Utc};
pub const MAX_PRECISION: u8 = 38;
pub const MAX_SCALE: u8 = 37;
#[must_use]
pub const fn valid_decimal(precision: u8, scale: u8) -> bool {
precision >= 1 && precision <= MAX_PRECISION && scale <= MAX_SCALE && scale <= precision
}
const MAX_UNSCALED: u128 = 99_999_999_999_999_999_999_999_999_999_999_999_999;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecimalError {
TooManyDigits { precision: u8 },
ScaleTooLarge,
}
impl fmt::Display for DecimalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooManyDigits { precision } => {
write!(f, "decimal has more than {precision} digits")
}
Self::ScaleTooLarge => write!(f, "decimal scale is over {MAX_SCALE}"),
}
}
}
impl core::error::Error for DecimalError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Decimal {
unscaled: i128,
scale: u8,
}
impl Decimal {
pub const fn new(unscaled: i128, scale: u8) -> Result<Self, DecimalError> {
if unscaled.unsigned_abs() > MAX_UNSCALED {
Err(DecimalError::TooManyDigits {
precision: MAX_PRECISION,
})
} else if scale > MAX_SCALE {
Err(DecimalError::ScaleTooLarge)
} else {
Ok(Self { unscaled, scale })
}
}
#[must_use]
pub const fn unscaled(&self) -> i128 {
self.unscaled
}
#[must_use]
pub const fn scale(&self) -> u8 {
self.scale
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum RawValue {
Null,
Text(String),
Integer(i64),
Decimal(Decimal),
Float(f64),
Boolean(bool),
Date(NaiveDate),
Timestamp(DateTime<Utc>),
Array(Vec<Self>),
Json(serde_json::Value),
}
#[cfg(test)]
mod tests {
use super::{Decimal, DecimalError};
const MAX: i128 = 99_999_999_999_999_999_999_999_999_999_999_999_999;
#[test]
fn decimal_keeps_38_digits_either_sign() {
assert_eq!(Decimal::new(MAX, 10).map(|d| d.unscaled()), Ok(MAX));
assert_eq!(Decimal::new(-MAX, 0).map(|d| d.unscaled()), Ok(-MAX));
}
#[test]
fn decimal_rejects_39_digits() {
assert_eq!(
Decimal::new(100_000_000_000_000_000_000_000_000_000_000_000_000, 0),
Err(DecimalError::TooManyDigits { precision: 38 })
);
assert_eq!(
Decimal::new(-100_000_000_000_000_000_000_000_000_000_000_000_000, 0),
Err(DecimalError::TooManyDigits { precision: 38 })
);
assert_eq!(
Decimal::new(i128::MIN, 0),
Err(DecimalError::TooManyDigits { precision: 38 })
);
}
#[test]
fn decimal_rejects_scale_over_37() {
assert_eq!(Decimal::new(1, 37).map(|d| d.scale()), Ok(37));
assert_eq!(Decimal::new(1, 38), Err(DecimalError::ScaleTooLarge));
}
}