use num_bigint::BigInt;
use super::BigIntegerLimitsBuilder;
use crate::resource::LimitExceededError;
use crate::resource::MeasuredBudgetError;
use crate::resource::Observation;
use crate::resource::QuantityConversionError;
use crate::resource::QuantityMeasurement;
use crate::resource::ResourceLimit;
use crate::resource::ResourceQuantity;
use crate::resource::quantity_as_u128;
#[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 = "the integer limits should be used"]
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)
}
#[inline]
#[must_use]
pub const fn magnitude_bits_limit(&self) -> Option<&ResourceLimit<R, Q>> {
self.max_magnitude_bits.as_ref()
}
#[inline]
#[must_use]
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,
{
self.check_measurements(value.bits(), || {
let text = value.to_str_radix(10);
text.strip_prefix('-').unwrap_or(&text).len()
})
}
#[inline]
pub(super) fn check_measurements<F>(
&self,
magnitude_bits: u64,
decimal_digits: F,
) -> Result<(), MeasuredBudgetError<R, Q>>
where
R: Clone,
F: FnOnce() -> usize,
{
if let Some(limit) = self.max_magnitude_bits.as_ref() {
let bits = Q::try_from_u64(magnitude_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, magnitude_bits, decimal_digits)?;
}
Ok(())
}
pub(super) fn set_magnitude_bits_limit(&mut self, limit: ResourceLimit<R, Q>) {
self.max_magnitude_bits = Some(limit);
}
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, F>(
limit: &ResourceLimit<R, Q>,
magnitude_bits: u64,
decimal_digits: F,
) -> Result<(), MeasuredBudgetError<R, Q>>
where
R: Clone,
Q: ResourceQuantity,
F: FnOnce() -> usize,
{
if magnitude_bits == 0 {
return Ok(());
}
let maximum = limit.maximum();
let wide_maximum = quantity_as_u128(maximum);
let upper = magnitude_bits.div_ceil(3);
if u128::from(upper) <= wide_maximum {
return Ok(());
}
let lower = (magnitude_bits - 1) / 4 + 1;
if u128::from(lower) > wide_maximum {
return match Q::try_from_u64(lower) {
Ok(observed) => Err(LimitExceededError::at_least(limit.resource().clone(), observed, maximum).into()),
Err(source) => Err(MeasuredBudgetError::quantity(
limit.resource().clone(),
QuantityConversionError::new(QuantityMeasurement::AtLeastU64(lower), source.target()),
)),
};
}
let digits = Q::try_from_usize(decimal_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(())
}
}
#[cfg(test)]
mod tests {
use super::check_decimal_digits;
use crate::resource::ResourceLimit;
#[test]
fn test_digit_estimates_skip_formatting_when_decisive() {
let fits = ResourceLimit::new("digits", 100_u8);
check_decimal_digits(&fits, 257, || panic!("must not format")).expect("upper estimate is within the maximum");
let exceeds = ResourceLimit::new("digits", 255_u8);
let error = check_decimal_digits(&exceeds, 16_385, || panic!("must not format"))
.expect_err("lower estimate exceeds the maximum");
assert!(error.quantity_error().is_some());
}
}