1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct ParserLimits {
25 pub max_entries: usize,
32
33 pub max_links_per_feed: usize,
39
40 pub max_links_per_entry: usize,
46
47 pub max_authors: usize,
51
52 pub max_contributors: usize,
56
57 pub max_tags: usize,
61
62 pub max_content_blocks: usize,
68
69 pub max_enclosures: usize,
75
76 pub max_namespaces: usize,
82
83 pub max_nesting_depth: usize,
89
90 pub max_html_nesting_depth: usize,
110
111 pub max_text_length: usize,
117
118 pub max_feed_size_bytes: usize,
124
125 pub max_attribute_length: usize,
131
132 pub max_podcast_soundbites: usize,
138
139 pub max_podcast_transcripts: usize,
145
146 pub max_podcast_funding: usize,
152
153 pub max_podcast_persons: usize,
159
160 pub max_value_recipients: usize,
167
168 pub max_podcast_alternate_enclosures: usize,
172
173 pub max_podcast_alternate_enclosure_sources: usize,
177
178 pub max_podcast_podroll: usize,
182
183 pub max_podcast_social_interact: usize,
187
188 pub max_podcast_txt: usize,
192
193 pub max_podcast_follow: usize,
197
198 pub max_podcast_chat: usize,
204
205 pub max_podcast_value_time_splits: usize,
212}
213
214impl Default for ParserLimits {
215 fn default() -> Self {
220 Self {
221 max_entries: 10_000,
222 max_links_per_feed: 100,
223 max_links_per_entry: 50,
224 max_authors: 20,
225 max_contributors: 20,
226 max_tags: 100,
227 max_content_blocks: 10,
228 max_enclosures: 20,
229 max_namespaces: 100,
230 max_nesting_depth: 100,
231 max_html_nesting_depth: 100,
232 max_text_length: 10 * 1024 * 1024, max_feed_size_bytes: 100 * 1024 * 1024, max_attribute_length: 64 * 1024, max_podcast_soundbites: 10,
236 max_podcast_transcripts: 20,
237 max_podcast_funding: 20,
238 max_podcast_persons: 50,
239 max_value_recipients: 20,
240 max_podcast_alternate_enclosures: 20,
241 max_podcast_alternate_enclosure_sources: 10,
242 max_podcast_podroll: 50,
243 max_podcast_social_interact: 20,
244 max_podcast_txt: 20,
245 max_podcast_follow: 20,
246 max_podcast_chat: 20,
247 max_podcast_value_time_splits: 20,
248 }
249 }
250}
251
252impl ParserLimits {
253 #[must_use]
267 pub const fn strict() -> Self {
268 Self {
269 max_entries: 1_000,
270 max_links_per_feed: 20,
271 max_links_per_entry: 10,
272 max_authors: 5,
273 max_contributors: 5,
274 max_tags: 20,
275 max_content_blocks: 3,
276 max_enclosures: 5,
277 max_namespaces: 20,
278 max_nesting_depth: 50,
279 max_html_nesting_depth: 50,
280 max_text_length: 1024 * 1024, max_feed_size_bytes: 10 * 1024 * 1024, max_attribute_length: 8 * 1024, max_podcast_soundbites: 5,
284 max_podcast_transcripts: 5,
285 max_podcast_funding: 5,
286 max_podcast_persons: 10,
287 max_value_recipients: 5,
288 max_podcast_alternate_enclosures: 5,
289 max_podcast_alternate_enclosure_sources: 3,
290 max_podcast_podroll: 10,
291 max_podcast_social_interact: 5,
292 max_podcast_txt: 5,
293 max_podcast_follow: 5,
294 max_podcast_chat: 5,
295 max_podcast_value_time_splits: 5,
296 }
297 }
298
299 #[must_use]
313 pub const fn permissive() -> Self {
314 Self {
315 max_entries: 100_000,
316 max_links_per_feed: 500,
317 max_links_per_entry: 200,
318 max_authors: 100,
319 max_contributors: 100,
320 max_tags: 500,
321 max_content_blocks: 50,
322 max_enclosures: 100,
323 max_namespaces: 500,
324 max_nesting_depth: 200,
325 max_html_nesting_depth: 200,
326 max_text_length: 50 * 1024 * 1024, max_feed_size_bytes: 500 * 1024 * 1024, max_attribute_length: 256 * 1024, max_podcast_soundbites: 50,
330 max_podcast_transcripts: 100,
331 max_podcast_funding: 50,
332 max_podcast_persons: 200,
333 max_value_recipients: 50,
334 max_podcast_alternate_enclosures: 100,
335 max_podcast_alternate_enclosure_sources: 50,
336 max_podcast_podroll: 200,
337 max_podcast_social_interact: 100,
338 max_podcast_txt: 100,
339 max_podcast_follow: 100,
340 max_podcast_chat: 100,
341 max_podcast_value_time_splits: 50,
342 }
343 }
344
345 pub const fn check_feed_size(&self, size: usize) -> Result<(), LimitError> {
353 if size > self.max_feed_size_bytes {
354 Err(LimitError::FeedTooLarge {
355 size,
356 max: self.max_feed_size_bytes,
357 })
358 } else {
359 Ok(())
360 }
361 }
362
363 pub const fn check_collection_size(
371 &self,
372 current: usize,
373 limit: usize,
374 name: &'static str,
375 ) -> Result<(), LimitError> {
376 if current >= limit {
377 Err(LimitError::CollectionTooLarge {
378 name,
379 size: current,
380 max: limit,
381 })
382 } else {
383 Ok(())
384 }
385 }
386
387 pub const fn check_nesting_depth(&self, depth: usize) -> Result<(), LimitError> {
393 if depth > self.max_nesting_depth {
394 Err(LimitError::NestingTooDeep {
395 depth,
396 max: self.max_nesting_depth,
397 })
398 } else {
399 Ok(())
400 }
401 }
402
403 pub const fn check_text_length(&self, length: usize) -> Result<(), LimitError> {
409 if length > self.max_text_length {
410 Err(LimitError::TextTooLong {
411 length,
412 max: self.max_text_length,
413 })
414 } else {
415 Ok(())
416 }
417 }
418}
419
420#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
422#[allow(missing_docs)] pub enum LimitError {
424 #[error("Feed size ({size} bytes) exceeds maximum ({max} bytes)")]
426 FeedTooLarge { size: usize, max: usize },
427
428 #[error("Collection '{name}' has {size} items, exceeds maximum ({max})")]
430 CollectionTooLarge {
431 name: &'static str,
432 size: usize,
433 max: usize,
434 },
435
436 #[error("XML nesting depth ({depth}) exceeds maximum ({max})")]
438 NestingTooDeep { depth: usize, max: usize },
439
440 #[error("Text field length ({length} bytes) exceeds maximum ({max} bytes)")]
442 TextTooLong { length: usize, max: usize },
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448
449 #[test]
450 fn test_default_limits() {
451 let limits = ParserLimits::default();
452 assert_eq!(limits.max_entries, 10_000);
453 assert_eq!(limits.max_feed_size_bytes, 100 * 1024 * 1024);
454 }
455
456 #[test]
457 fn test_strict_limits() {
458 let limits = ParserLimits::strict();
459 assert_eq!(limits.max_entries, 1_000);
460 assert!(limits.max_entries < ParserLimits::default().max_entries);
461 }
462
463 #[test]
464 fn test_permissive_limits() {
465 let limits = ParserLimits::permissive();
466 assert_eq!(limits.max_entries, 100_000);
467 assert!(limits.max_entries > ParserLimits::default().max_entries);
468 }
469
470 #[test]
471 fn test_check_feed_size_ok() {
472 let limits = ParserLimits::default();
473 assert!(limits.check_feed_size(1024).is_ok());
474 }
475
476 #[test]
477 fn test_check_feed_size_too_large() {
478 let limits = ParserLimits::default();
479 let result = limits.check_feed_size(200 * 1024 * 1024);
480 assert!(result.is_err());
481 assert!(matches!(result, Err(LimitError::FeedTooLarge { .. })));
482 }
483
484 #[test]
485 fn test_check_collection_size_ok() {
486 let limits = ParserLimits::default();
487 assert!(
488 limits
489 .check_collection_size(50, limits.max_entries, "entries")
490 .is_ok()
491 );
492 }
493
494 #[test]
495 fn test_check_collection_size_too_large() {
496 let limits = ParserLimits::default();
497 let result = limits.check_collection_size(10_001, limits.max_entries, "entries");
498 assert!(result.is_err());
499 assert!(matches!(result, Err(LimitError::CollectionTooLarge { .. })));
500 }
501
502 #[test]
503 fn test_check_nesting_depth_ok() {
504 let limits = ParserLimits::default();
505 assert!(limits.check_nesting_depth(50).is_ok());
506 }
507
508 #[test]
509 fn test_check_nesting_depth_too_deep() {
510 let limits = ParserLimits::default();
511 let result = limits.check_nesting_depth(101);
512 assert!(result.is_err());
513 assert!(matches!(result, Err(LimitError::NestingTooDeep { .. })));
514 }
515
516 #[test]
517 fn test_check_text_length_ok() {
518 let limits = ParserLimits::default();
519 assert!(limits.check_text_length(1024).is_ok());
520 }
521
522 #[test]
523 fn test_check_text_length_too_long() {
524 let limits = ParserLimits::default();
525 let result = limits.check_text_length(20 * 1024 * 1024);
526 assert!(result.is_err());
527 assert!(matches!(result, Err(LimitError::TextTooLong { .. })));
528 }
529
530 #[test]
531 fn test_limit_error_display() {
532 let err = LimitError::FeedTooLarge {
533 size: 200_000_000,
534 max: 100_000_000,
535 };
536 let msg = err.to_string();
537 assert!(msg.contains("200000000"));
538 assert!(msg.contains("100000000"));
539 }
540
541 #[test]
542 fn test_max_value_recipients_default() {
543 let limits = ParserLimits::default();
544 assert_eq!(limits.max_value_recipients, 20);
545 }
546
547 #[test]
548 fn test_max_value_recipients_strict() {
549 let limits = ParserLimits::strict();
550 assert_eq!(limits.max_value_recipients, 5);
551 assert!(limits.max_value_recipients < ParserLimits::default().max_value_recipients);
552 }
553
554 #[test]
555 fn test_max_value_recipients_permissive() {
556 let limits = ParserLimits::permissive();
557 assert_eq!(limits.max_value_recipients, 50);
558 assert!(limits.max_value_recipients > ParserLimits::default().max_value_recipients);
559 }
560
561 #[test]
562 fn test_value_recipients_limit_enforcement() {
563 let limits = ParserLimits::default();
564
565 assert!(
567 limits
568 .check_collection_size(19, limits.max_value_recipients, "value_recipients")
569 .is_ok()
570 );
571
572 assert!(
574 limits
575 .check_collection_size(20, limits.max_value_recipients, "value_recipients")
576 .is_err()
577 );
578
579 let result =
581 limits.check_collection_size(21, limits.max_value_recipients, "value_recipients");
582 assert!(result.is_err());
583 assert!(matches!(result, Err(LimitError::CollectionTooLarge { .. })));
584 }
585
586 #[test]
587 fn test_max_podcast_chat_tiers() {
588 assert_eq!(ParserLimits::default().max_podcast_chat, 20);
589 assert_eq!(ParserLimits::strict().max_podcast_chat, 5);
590 assert_eq!(ParserLimits::permissive().max_podcast_chat, 100);
591 assert!(ParserLimits::strict().max_podcast_chat < ParserLimits::default().max_podcast_chat);
592 assert!(
593 ParserLimits::permissive().max_podcast_chat > ParserLimits::default().max_podcast_chat
594 );
595 }
596
597 #[test]
598 fn test_podcast_chat_limit_enforcement() {
599 let limits = ParserLimits::default();
600 assert!(
601 limits
602 .check_collection_size(19, limits.max_podcast_chat, "chat")
603 .is_ok()
604 );
605 let result = limits.check_collection_size(20, limits.max_podcast_chat, "chat");
606 assert!(result.is_err());
607 assert!(matches!(result, Err(LimitError::CollectionTooLarge { .. })));
608 }
609
610 #[test]
611 fn test_max_podcast_value_time_splits_tiers() {
612 assert_eq!(ParserLimits::default().max_podcast_value_time_splits, 20);
613 assert_eq!(ParserLimits::strict().max_podcast_value_time_splits, 5);
614 assert_eq!(ParserLimits::permissive().max_podcast_value_time_splits, 50);
615 assert!(
616 ParserLimits::strict().max_podcast_value_time_splits
617 < ParserLimits::default().max_podcast_value_time_splits
618 );
619 assert!(
620 ParserLimits::permissive().max_podcast_value_time_splits
621 > ParserLimits::default().max_podcast_value_time_splits
622 );
623 }
624
625 #[test]
626 fn test_podcast_value_time_splits_limit_enforcement() {
627 let limits = ParserLimits::default();
628 assert!(
629 limits
630 .check_collection_size(19, limits.max_podcast_value_time_splits, "time_splits")
631 .is_ok()
632 );
633 let result =
634 limits.check_collection_size(20, limits.max_podcast_value_time_splits, "time_splits");
635 assert!(result.is_err());
636 assert!(matches!(result, Err(LimitError::CollectionTooLarge { .. })));
637 }
638}