fluidattacks-database-core-domain 0.1.0

Database core functional core: database-agnostic table identifiers and types (no I/O)
Documentation
use super::cell_type::CellType;
use super::field_type::{FieldType, IntoValue};
use super::value::{valid_decimal, Decimal, DecimalError, RawValue};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Numeric<const P: u8, const S: u8> {
    value: Decimal,
}

impl<const P: u8, const S: u8> Numeric<P, S> {
    const LIMITS: () = assert!(
        valid_decimal(P, S),
        "Numeric needs 1 <= P <= 38, S <= 37 and S <= P"
    );

    pub fn new(unscaled: i128) -> Result<Self, DecimalError> {
        let () = Self::LIMITS;
        let fits = 10_u128
            .checked_pow(u32::from(P))
            .is_some_and(|limit| unscaled.unsigned_abs() < limit);
        if fits {
            Decimal::new(unscaled, S).map(|value| Self { value })
        } else {
            Err(DecimalError::TooManyDigits { precision: P })
        }
    }

    #[must_use]
    pub const fn unscaled(&self) -> i128 {
        self.value.unscaled()
    }
}

impl<const P: u8, const S: u8> FieldType for Numeric<P, S> {
    fn cell_type() -> CellType {
        let () = Self::LIMITS;
        CellType::Decimal {
            precision: P,
            scale: S,
        }
    }
}

impl<const P: u8, const S: u8> IntoValue for Numeric<P, S> {
    fn into_value(self) -> RawValue {
        RawValue::Decimal(self.value)
    }
}

#[cfg(test)]
mod tests {
    use super::Numeric;
    use crate::cell::cell_type::CellType;
    use crate::cell::field_type::{FieldType, IntoValue};
    use crate::cell::value::{Decimal, DecimalError, RawValue};

    #[test]
    fn numeric_is_a_decimal_column_of_its_precision_and_scale() {
        assert_eq!(
            Numeric::<12, 2>::cell_type(),
            CellType::Decimal {
                precision: 12,
                scale: 2
            }
        );
        assert_eq!(
            Numeric::<12, 2>::new(12_345).map(IntoValue::into_value),
            Decimal::new(12_345, 2).map(RawValue::Decimal)
        );
    }

    #[test]
    fn numeric_rejects_values_over_its_precision() {
        assert_eq!(
            Numeric::<4, 2>::new(-9_999).map(|n| n.unscaled()),
            Ok(-9_999)
        );
        assert_eq!(
            Numeric::<4, 2>::new(10_000),
            Err(DecimalError::TooManyDigits { precision: 4 })
        );
        assert_eq!(
            Numeric::<38, 0>::new(i128::MIN),
            Err(DecimalError::TooManyDigits { precision: 38 })
        );
    }
}