1pub const DEFAULT_MAX_RENDER_PIXELS: u64 = 512 * 1024 * 1024;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ResourceLimitAxis {
9 FileBytes,
11 PageCount,
13 ComponentCount,
15 PagePixels,
17 TotalPixels,
19 DecodedBytes,
21 RenderOutputPixels,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct ResourceLimitExceeded {
28 pub operation: &'static str,
31 pub axis: ResourceLimitAxis,
33 pub found: u64,
35 pub limit: u64,
37 pub page_number: Option<usize>,
39 pub width: Option<u32>,
41 pub height: Option<u32>,
43}
44
45impl core::fmt::Display for ResourceLimitExceeded {
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 match self.axis {
48 ResourceLimitAxis::PagePixels => write!(
49 f,
50 "{}: page {} is {}x{} = {} pixels, exceeding limit {}",
51 self.operation,
52 self.page_number.unwrap_or(0),
53 self.width.unwrap_or(0),
54 self.height.unwrap_or(0),
55 self.found,
56 self.limit
57 ),
58 ResourceLimitAxis::RenderOutputPixels => write!(
59 f,
60 "{}: render output {}x{} = {} pixels exceeds limit {}",
61 self.operation,
62 self.width.unwrap_or(0),
63 self.height.unwrap_or(0),
64 self.found,
65 self.limit
66 ),
67 ResourceLimitAxis::FileBytes => write!(
68 f,
69 "{}: file is {} bytes, exceeding limit {}",
70 self.operation, self.found, self.limit
71 ),
72 ResourceLimitAxis::PageCount => write!(
73 f,
74 "{}: document has {} pages, exceeding limit {}",
75 self.operation, self.found, self.limit
76 ),
77 ResourceLimitAxis::ComponentCount => write!(
78 f,
79 "{}: document has {} components, exceeding limit {}",
80 self.operation, self.found, self.limit
81 ),
82 ResourceLimitAxis::TotalPixels => write!(
83 f,
84 "{}: document totals {} pixels, exceeding limit {}",
85 self.operation, self.found, self.limit
86 ),
87 ResourceLimitAxis::DecodedBytes => write!(
88 f,
89 "{}: peak decoded page memory is an estimated {} bytes, exceeding limit {}",
90 self.operation, self.found, self.limit
91 ),
92 }
93 }
94}
95
96impl core::error::Error for ResourceLimitExceeded {}
97
98#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
100pub struct ResourceLimits {
101 pub max_file_bytes: Option<u64>,
103 pub max_pages: Option<u64>,
105 pub max_components: Option<u64>,
107 pub max_page_pixels: Option<u64>,
109 pub max_total_pixels: Option<u64>,
111 pub max_decoded_bytes: Option<u64>,
113 pub max_render_pixels: Option<u64>,
115}
116
117impl ResourceLimits {
118 pub const fn is_empty(&self) -> bool {
120 self.max_file_bytes.is_none()
121 && self.max_pages.is_none()
122 && self.max_components.is_none()
123 && self.max_page_pixels.is_none()
124 && self.max_total_pixels.is_none()
125 && self.max_decoded_bytes.is_none()
126 && self.max_render_pixels.is_none()
127 }
128
129 pub const fn inherited() -> Self {
131 Self {
132 max_render_pixels: Some(DEFAULT_MAX_RENDER_PIXELS),
133 max_file_bytes: None,
134 max_pages: None,
135 max_components: None,
136 max_page_pixels: None,
137 max_total_pixels: None,
138 max_decoded_bytes: None,
139 }
140 }
141}
142
143#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
145pub struct ParseOptions {
146 pub limits: Option<ResourceLimits>,
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 fn exceeded(axis: ResourceLimitAxis) -> ResourceLimitExceeded {
155 ResourceLimitExceeded {
156 operation: "document.parse",
157 axis,
158 found: 100,
159 limit: 10,
160 page_number: Some(3),
161 width: Some(20),
162 height: Some(5),
163 }
164 }
165
166 #[test]
170 fn display_file_bytes() {
171 let msg = exceeded(ResourceLimitAxis::FileBytes).to_string();
172 assert_eq!(msg, "document.parse: file is 100 bytes, exceeding limit 10");
173 }
174
175 #[test]
176 fn display_page_count() {
177 let msg = exceeded(ResourceLimitAxis::PageCount).to_string();
178 assert_eq!(
179 msg,
180 "document.parse: document has 100 pages, exceeding limit 10"
181 );
182 }
183
184 #[test]
185 fn display_component_count() {
186 let msg = exceeded(ResourceLimitAxis::ComponentCount).to_string();
187 assert_eq!(
188 msg,
189 "document.parse: document has 100 components, exceeding limit 10"
190 );
191 }
192
193 #[test]
194 fn display_page_pixels_includes_page_number_and_dimensions() {
195 let msg = exceeded(ResourceLimitAxis::PagePixels).to_string();
196 assert_eq!(
197 msg,
198 "document.parse: page 3 is 20x5 = 100 pixels, exceeding limit 10"
199 );
200 }
201
202 #[test]
203 fn display_total_pixels() {
204 let msg = exceeded(ResourceLimitAxis::TotalPixels).to_string();
205 assert_eq!(
206 msg,
207 "document.parse: document totals 100 pixels, exceeding limit 10"
208 );
209 }
210
211 #[test]
212 fn display_decoded_bytes() {
213 let msg = exceeded(ResourceLimitAxis::DecodedBytes).to_string();
214 assert_eq!(
215 msg,
216 "document.parse: peak decoded page memory is an estimated 100 bytes, exceeding limit 10"
217 );
218 }
219
220 #[test]
221 fn display_render_output_pixels_includes_dimensions() {
222 let msg = exceeded(ResourceLimitAxis::RenderOutputPixels).to_string();
223 assert_eq!(
224 msg,
225 "document.parse: render output 20x5 = 100 pixels exceeds limit 10"
226 );
227 }
228
229 #[test]
233 fn display_page_pixels_defaults_missing_fields_to_zero() {
234 let err = ResourceLimitExceeded {
235 operation: "op",
236 axis: ResourceLimitAxis::PagePixels,
237 found: 5,
238 limit: 1,
239 page_number: None,
240 width: None,
241 height: None,
242 };
243 assert_eq!(
244 err.to_string(),
245 "op: page 0 is 0x0 = 5 pixels, exceeding limit 1"
246 );
247 }
248
249 #[test]
250 fn is_empty_true_for_default() {
251 assert!(ResourceLimits::default().is_empty());
252 }
253
254 #[test]
255 fn is_empty_false_when_any_field_set() {
256 let limits = ResourceLimits {
257 max_pages: Some(5),
258 ..ResourceLimits::default()
259 };
260 assert!(!limits.is_empty());
261 }
262
263 #[test]
264 fn inherited_sets_only_render_pixel_ceiling() {
265 let inherited = ResourceLimits::inherited();
266 assert_eq!(inherited.max_render_pixels, Some(DEFAULT_MAX_RENDER_PIXELS));
267 assert!(inherited.max_file_bytes.is_none());
268 assert!(inherited.max_pages.is_none());
269 assert!(inherited.max_components.is_none());
270 assert!(inherited.max_page_pixels.is_none());
271 assert!(inherited.max_total_pixels.is_none());
272 assert!(inherited.max_decoded_bytes.is_none());
273 }
274}