use num_bigint::BigInt;
use super::BigIntegerLimitsBuilder;
use crate::resource::LimitExceededError;
use crate::resource::MeasuredBudgetError;
use crate::resource::Observation;
use crate::resource::ResourceLimit;
use crate::resource::ResourceQuantity;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BigIntegerLimits<R, Q = u64>
where
Q: ResourceQuantity,
{
max_magnitude_bits: Option<ResourceLimit<R, Q>>,
max_significant_decimal_digits: Option<ResourceLimit<R, Q>>,
}
impl<R, Q> BigIntegerLimits<R, Q>
where
Q: ResourceQuantity,
{
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
max_magnitude_bits: None,
max_significant_decimal_digits: None,
}
}
#[inline]
#[must_use]
pub const fn builder() -> BigIntegerLimitsBuilder<R, Q> {
BigIntegerLimitsBuilder::new()
}
#[inline]
#[must_use]
pub const fn into_builder(self) -> BigIntegerLimitsBuilder<R, Q> {
BigIntegerLimitsBuilder::from_limits(self)
}
#[must_use]
#[inline(always)]
pub const fn magnitude_bits_limit(&self) -> Option<&ResourceLimit<R, Q>> {
self.max_magnitude_bits.as_ref()
}
#[must_use]
#[inline(always)]
pub const fn significant_decimal_digits_limit(&self) -> Option<&ResourceLimit<R, Q>> {
self.max_significant_decimal_digits.as_ref()
}
#[inline]
pub fn check(&self, value: &BigInt) -> Result<(), MeasuredBudgetError<R, Q>>
where
R: Clone,
{
if let Some(limit) = self.max_magnitude_bits.as_ref() {
let bits = Q::try_from_u64(value.bits())
.map_err(|source| MeasuredBudgetError::quantity(limit.resource().clone(), source))?;
limit.check(bits).map_err(MeasuredBudgetError::from)?;
}
if let Some(limit) = self.max_significant_decimal_digits.as_ref() {
check_decimal_digits(limit, value)?;
}
Ok(())
}
#[inline(always)]
pub(super) fn set_magnitude_bits_limit(&mut self, limit: ResourceLimit<R, Q>) {
self.max_magnitude_bits = Some(limit);
}
#[inline(always)]
pub(super) fn set_significant_decimal_digits_limit(&mut self, limit: ResourceLimit<R, Q>) {
self.max_significant_decimal_digits = Some(limit);
}
}
impl<R, Q> Default for BigIntegerLimits<R, Q>
where
Q: ResourceQuantity,
{
#[inline]
fn default() -> Self {
Self::new()
}
}
fn check_decimal_digits<R, Q>(limit: &ResourceLimit<R, Q>, value: &BigInt) -> Result<(), MeasuredBudgetError<R, Q>>
where
R: Clone,
Q: ResourceQuantity,
{
let bits = value.bits();
if bits == 0 {
return Ok(());
}
let maximum = limit.maximum();
let bits =
Q::try_from_u64(bits).map_err(|source| MeasuredBudgetError::quantity(limit.resource().clone(), source))?;
let low_bits = maximum
.checked_add(maximum)
.and_then(|value| value.checked_add(maximum));
if low_bits.is_some_and(|low_bits| bits <= low_bits) {
return Ok(());
}
let high_bits = low_bits.and_then(|value| value.checked_add(maximum));
if high_bits.is_some_and(|high_bits| bits > high_bits) {
let Some(observed) = maximum.checked_add(Q::ONE) else {
return Ok(());
};
return Err(LimitExceededError {
resource: limit.resource().clone(),
observed: Observation::AtLeast(observed),
maximum,
}
.into());
}
let text = value.to_str_radix(10);
let digits = text.strip_prefix('-').unwrap_or(&text).len();
let digits =
Q::try_from_usize(digits).map_err(|source| MeasuredBudgetError::quantity(limit.resource().clone(), source))?;
if digits > maximum {
Err(LimitExceededError {
resource: limit.resource().clone(),
observed: Observation::Exact(digits),
maximum,
}
.into())
} else {
Ok(())
}
}