use crate::error::{AxCode, AxError};
use crate::locator::Locator;
fn exceeds(limit: u64, observed: usize) -> bool {
match u64::try_from(observed) {
Ok(count) => count > limit,
Err(_) => true,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ImagesPerTurn(u64);
impl ImagesPerTurn {
pub(crate) const fn new(max: u64) -> ImagesPerTurn {
ImagesPerTurn(max)
}
pub fn admit(self, found: usize) -> Result<(), AxError> {
let max = self.0;
if exceeds(max, found) {
return Err(AxError::failure(
AxCode::InvalidArgs,
"put pictures on a provider request",
format!("this turn carries {found} pictures"),
)
.with_recovery(format!(
"one turn carries at most {max} pictures; send the rest in a later turn"
)));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ImageMaxBytes(u64);
impl ImageMaxBytes {
pub(crate) const fn new(max: u64) -> ImageMaxBytes {
ImageMaxBytes(max)
}
pub fn admit(self, at: &Locator, size: usize) -> Result<(), AxError> {
let max = self.0;
if exceeds(max, size) {
return Err(AxError::failure(
AxCode::InvalidArgs,
"put a picture on a provider request",
format!("{at} is {size} bytes"),
)
.with_recovery(format!(
"one picture is at most {max} bytes; shrink it before attaching it"
)));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ImageQuality(u64);
impl ImageQuality {
pub(crate) const fn new(max: u64) -> ImageQuality {
ImageQuality(max)
}
pub fn admit(self, asked: u32) -> Result<(), AxError> {
if u64::from(asked) <= self.0 {
return Ok(());
}
Err(AxError::failure(
AxCode::InvalidArgs,
"encode a picture",
format!("quality {asked} is outside 0..={}", self.0),
)
.with_recovery(format!(
"pass a quality between 0 and {}; an encoder has no others",
self.0
)))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClockZonesMax(u64);
impl ClockZonesMax {
pub(crate) const fn new(max: u64) -> ClockZonesMax {
ClockZonesMax(max)
}
pub fn admit(self, configured: usize) -> Result<(), AxError> {
let max = self.0;
if exceeds(max, configured) {
return Err(AxError::failure(
AxCode::InvalidArgs,
"format clock stamp",
format!("{configured} zones exceed CLOCK_ZONES_MAX={max}"),
)
.with_recovery(format!(
"keep at most {max} rows in `[clock] zones`; the UTC row is added on top \
of them and is never one of them"
)));
}
Ok(())
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
reason = "test code"
)]
mod tests {
use super::*;
use crate::consts_policy::{CLOCK_ZONES_MAX, IMAGE_MAX_BYTES, IMAGE_QUALITY, IMAGES_PER_TURN};
fn a_picture() -> Locator {
Locator::parse(&format!("cas:b3-{}", "0".repeat(64))).unwrap()
}
#[test]
fn a_turn_carries_the_limit_and_one_picture_past_it_is_refused() {
assert!(IMAGES_PER_TURN.admit(4).is_ok());
let expected = AxError::failure(
AxCode::InvalidArgs,
"put pictures on a provider request",
"this turn carries 5 pictures",
)
.with_recovery("one turn carries at most 4 pictures; send the rest in a later turn");
assert_eq!(IMAGES_PER_TURN.admit(5).unwrap_err(), expected);
}
#[test]
fn the_quality_domain_admits_its_limit_and_refuses_one_past_it() {
assert!(IMAGE_QUALITY.admit(100).is_ok());
assert!(IMAGE_QUALITY.admit(0).is_ok());
let expected = AxError::failure(
AxCode::InvalidArgs,
"encode a picture",
"quality 101 is outside 0..=100",
)
.with_recovery("pass a quality between 0 and 100; an encoder has no others");
assert_eq!(IMAGE_QUALITY.admit(101).unwrap_err(), expected);
}
#[test]
fn a_picture_fits_at_the_ceiling_and_one_past_it_is_refused() {
let at = a_picture();
assert!(IMAGE_MAX_BYTES.admit(&at, 2_097_152).is_ok());
let expected = AxError::failure(
AxCode::InvalidArgs,
"put a picture on a provider request",
format!("{at} is 2097153 bytes"),
)
.with_recovery("one picture is at most 2097152 bytes; shrink it before attaching it");
assert_eq!(IMAGE_MAX_BYTES.admit(&at, 2_097_153).unwrap_err(), expected);
}
#[test]
fn the_zone_count_admits_the_limit_and_one_past_it_is_refused() {
assert!(CLOCK_ZONES_MAX.admit(4).is_ok());
let expected = AxError::failure(
AxCode::InvalidArgs,
"format clock stamp",
"5 zones exceed CLOCK_ZONES_MAX=4",
)
.with_recovery(
"keep at most 4 rows in `[clock] zones`; the UTC row is added on top of them and is \
never one of them",
);
assert_eq!(CLOCK_ZONES_MAX.admit(5).unwrap_err(), expected);
}
}