pub const DEFAULT_MAX_RENDER_PIXELS: u64 = 512 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResourceLimitAxis {
FileBytes,
PageCount,
ComponentCount,
PagePixels,
TotalPixels,
DecodedBytes,
RenderOutputPixels,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResourceLimitExceeded {
pub operation: &'static str,
pub axis: ResourceLimitAxis,
pub found: u64,
pub limit: u64,
pub page_number: Option<usize>,
pub width: Option<u32>,
pub height: Option<u32>,
}
impl core::fmt::Display for ResourceLimitExceeded {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.axis {
ResourceLimitAxis::PagePixels => write!(
f,
"{}: page {} is {}x{} = {} pixels, exceeding limit {}",
self.operation,
self.page_number.unwrap_or(0),
self.width.unwrap_or(0),
self.height.unwrap_or(0),
self.found,
self.limit
),
ResourceLimitAxis::RenderOutputPixels => write!(
f,
"{}: render output {}x{} = {} pixels exceeds limit {}",
self.operation,
self.width.unwrap_or(0),
self.height.unwrap_or(0),
self.found,
self.limit
),
ResourceLimitAxis::FileBytes => write!(
f,
"{}: file is {} bytes, exceeding limit {}",
self.operation, self.found, self.limit
),
ResourceLimitAxis::PageCount => write!(
f,
"{}: document has {} pages, exceeding limit {}",
self.operation, self.found, self.limit
),
ResourceLimitAxis::ComponentCount => write!(
f,
"{}: document has {} components, exceeding limit {}",
self.operation, self.found, self.limit
),
ResourceLimitAxis::TotalPixels => write!(
f,
"{}: document totals {} pixels, exceeding limit {}",
self.operation, self.found, self.limit
),
ResourceLimitAxis::DecodedBytes => write!(
f,
"{}: peak decoded page memory is an estimated {} bytes, exceeding limit {}",
self.operation, self.found, self.limit
),
}
}
}
impl core::error::Error for ResourceLimitExceeded {}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ResourceLimits {
pub max_file_bytes: Option<u64>,
pub max_pages: Option<u64>,
pub max_components: Option<u64>,
pub max_page_pixels: Option<u64>,
pub max_total_pixels: Option<u64>,
pub max_decoded_bytes: Option<u64>,
pub max_render_pixels: Option<u64>,
}
impl ResourceLimits {
pub const fn is_empty(&self) -> bool {
self.max_file_bytes.is_none()
&& self.max_pages.is_none()
&& self.max_components.is_none()
&& self.max_page_pixels.is_none()
&& self.max_total_pixels.is_none()
&& self.max_decoded_bytes.is_none()
&& self.max_render_pixels.is_none()
}
pub const fn inherited() -> Self {
Self {
max_render_pixels: Some(DEFAULT_MAX_RENDER_PIXELS),
max_file_bytes: None,
max_pages: None,
max_components: None,
max_page_pixels: None,
max_total_pixels: None,
max_decoded_bytes: None,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ParseOptions {
pub limits: Option<ResourceLimits>,
}
#[cfg(test)]
mod tests {
use super::*;
fn exceeded(axis: ResourceLimitAxis) -> ResourceLimitExceeded {
ResourceLimitExceeded {
operation: "document.parse",
axis,
found: 100,
limit: 10,
page_number: Some(3),
width: Some(20),
height: Some(5),
}
}
#[test]
fn display_file_bytes() {
let msg = exceeded(ResourceLimitAxis::FileBytes).to_string();
assert_eq!(msg, "document.parse: file is 100 bytes, exceeding limit 10");
}
#[test]
fn display_page_count() {
let msg = exceeded(ResourceLimitAxis::PageCount).to_string();
assert_eq!(
msg,
"document.parse: document has 100 pages, exceeding limit 10"
);
}
#[test]
fn display_component_count() {
let msg = exceeded(ResourceLimitAxis::ComponentCount).to_string();
assert_eq!(
msg,
"document.parse: document has 100 components, exceeding limit 10"
);
}
#[test]
fn display_page_pixels_includes_page_number_and_dimensions() {
let msg = exceeded(ResourceLimitAxis::PagePixels).to_string();
assert_eq!(
msg,
"document.parse: page 3 is 20x5 = 100 pixels, exceeding limit 10"
);
}
#[test]
fn display_total_pixels() {
let msg = exceeded(ResourceLimitAxis::TotalPixels).to_string();
assert_eq!(
msg,
"document.parse: document totals 100 pixels, exceeding limit 10"
);
}
#[test]
fn display_decoded_bytes() {
let msg = exceeded(ResourceLimitAxis::DecodedBytes).to_string();
assert_eq!(
msg,
"document.parse: peak decoded page memory is an estimated 100 bytes, exceeding limit 10"
);
}
#[test]
fn display_render_output_pixels_includes_dimensions() {
let msg = exceeded(ResourceLimitAxis::RenderOutputPixels).to_string();
assert_eq!(
msg,
"document.parse: render output 20x5 = 100 pixels exceeds limit 10"
);
}
#[test]
fn display_page_pixels_defaults_missing_fields_to_zero() {
let err = ResourceLimitExceeded {
operation: "op",
axis: ResourceLimitAxis::PagePixels,
found: 5,
limit: 1,
page_number: None,
width: None,
height: None,
};
assert_eq!(
err.to_string(),
"op: page 0 is 0x0 = 5 pixels, exceeding limit 1"
);
}
#[test]
fn is_empty_true_for_default() {
assert!(ResourceLimits::default().is_empty());
}
#[test]
fn is_empty_false_when_any_field_set() {
let limits = ResourceLimits {
max_pages: Some(5),
..ResourceLimits::default()
};
assert!(!limits.is_empty());
}
#[test]
fn inherited_sets_only_render_pixel_ceiling() {
let inherited = ResourceLimits::inherited();
assert_eq!(inherited.max_render_pixels, Some(DEFAULT_MAX_RENDER_PIXELS));
assert!(inherited.max_file_bytes.is_none());
assert!(inherited.max_pages.is_none());
assert!(inherited.max_components.is_none());
assert!(inherited.max_page_pixels.is_none());
assert!(inherited.max_total_pixels.is_none());
assert!(inherited.max_decoded_bytes.is_none());
}
}