1use crate::error::{HedlError, HedlResult};
21use std::time::{Duration, Instant};
22
23#[derive(Debug, Clone)]
28pub struct Limits {
29 pub max_file_size: usize,
31 pub max_line_length: usize,
33 pub max_indent_depth: usize,
35 pub max_nodes: usize,
37 pub max_aliases: usize,
39 pub max_columns: usize,
41 pub max_nest_depth: usize,
43 pub max_block_string_size: usize,
45 pub max_object_keys: usize,
47 pub max_total_keys: usize,
57 pub max_total_ids: usize,
67 pub timeout: Option<Duration>,
75}
76
77impl Default for Limits {
78 fn default() -> Self {
79 Self {
80 max_file_size: 1024 * 1024 * 1024, max_line_length: 1024 * 1024, max_indent_depth: 50,
83 max_nodes: 10_000_000,
84 max_aliases: 10_000,
85 max_columns: 100,
86 max_nest_depth: 100,
87 max_block_string_size: 10 * 1024 * 1024, max_object_keys: 10_000,
89 max_total_keys: 10_000_000, max_total_ids: 10_000_000, timeout: Some(Duration::from_secs(30)), }
93 }
94}
95
96impl Limits {
97 pub fn unlimited() -> Self {
99 Self {
100 max_file_size: usize::MAX,
101 max_line_length: usize::MAX,
102 max_indent_depth: usize::MAX,
103 max_nodes: usize::MAX,
104 max_aliases: usize::MAX,
105 max_columns: usize::MAX,
106 max_nest_depth: usize::MAX,
107 max_block_string_size: usize::MAX,
108 max_object_keys: usize::MAX,
109 max_total_keys: usize::MAX,
110 max_total_ids: usize::MAX,
111 timeout: None,
112 }
113 }
114}
115
116#[derive(Debug, Clone, Copy)]
121pub struct TimeoutContext {
122 start: Instant,
123 timeout: Option<Duration>,
124}
125
126impl TimeoutContext {
127 pub fn new(timeout: Option<Duration>) -> Self {
129 Self {
130 start: Instant::now(),
131 timeout,
132 }
133 }
134
135 pub fn check_timeout(&self, line_num: usize) -> HedlResult<()> {
145 if let Some(timeout) = self.timeout {
146 let elapsed = self.start.elapsed();
147 if elapsed > timeout {
148 return Err(HedlError::security(
149 format!(
150 "parsing timeout exceeded: {}ms > {}ms",
151 elapsed.as_millis(),
152 timeout.as_millis()
153 ),
154 line_num,
155 ));
156 }
157 }
158 Ok(())
159 }
160}
161
162pub const DEFAULT_TIMEOUT_CHECK_INTERVAL: usize = 10_000;
169
170pub struct TimeoutCheckIterator<'a, I>
200where
201 I: Iterator,
202{
203 inner: I,
204 timeout_ctx: &'a TimeoutContext,
205 check_interval: usize,
206 iteration_count: usize,
207}
208
209impl<'a, I> TimeoutCheckIterator<'a, I>
210where
211 I: Iterator,
212{
213 pub fn new(inner: I, timeout_ctx: &'a TimeoutContext) -> Self {
215 Self::with_interval(inner, timeout_ctx, DEFAULT_TIMEOUT_CHECK_INTERVAL)
216 }
217
218 pub fn with_interval(inner: I, timeout_ctx: &'a TimeoutContext, check_interval: usize) -> Self {
226 Self {
227 inner,
228 timeout_ctx,
229 check_interval,
230 iteration_count: 0,
231 }
232 }
233}
234
235impl<'a, I> Iterator for TimeoutCheckIterator<'a, I>
236where
237 I: Iterator<Item = (usize, &'a str)>,
238{
239 type Item = Result<(usize, &'a str), HedlError>;
240
241 fn next(&mut self) -> Option<Self::Item> {
242 let item = self.inner.next()?;
244 let (line_num, _line) = item;
245
246 self.iteration_count += 1;
248 if self.iteration_count % self.check_interval == 0 {
249 if let Err(e) = self.timeout_ctx.check_timeout(line_num) {
250 return Some(Err(e));
251 }
252 }
253
254 Some(Ok(item))
255 }
256
257 fn size_hint(&self) -> (usize, Option<usize>) {
258 self.inner.size_hint()
259 }
260}
261
262pub trait TimeoutCheckExt<'a>: Iterator<Item = (usize, &'a str)> + Sized {
267 fn with_timeout_check(self, timeout_ctx: &'a TimeoutContext) -> TimeoutCheckIterator<'a, Self> {
287 TimeoutCheckIterator::new(self, timeout_ctx)
288 }
289
290 #[allow(dead_code)]
299 fn with_timeout_check_interval(
300 self,
301 timeout_ctx: &'a TimeoutContext,
302 check_interval: usize,
303 ) -> TimeoutCheckIterator<'a, Self> {
304 TimeoutCheckIterator::with_interval(self, timeout_ctx, check_interval)
305 }
306}
307
308impl<'a, I> TimeoutCheckExt<'a> for I where I: Iterator<Item = (usize, &'a str)> {}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
318 fn test_default_max_file_size() {
319 let limits = Limits::default();
320 assert_eq!(limits.max_file_size, 1024 * 1024 * 1024); }
322
323 #[test]
324 fn test_default_max_line_length() {
325 let limits = Limits::default();
326 assert_eq!(limits.max_line_length, 1024 * 1024); }
328
329 #[test]
330 fn test_default_max_indent_depth() {
331 let limits = Limits::default();
332 assert_eq!(limits.max_indent_depth, 50);
333 }
334
335 #[test]
336 fn test_default_max_nodes() {
337 let limits = Limits::default();
338 assert_eq!(limits.max_nodes, 10_000_000); }
340
341 #[test]
342 fn test_default_max_aliases() {
343 let limits = Limits::default();
344 assert_eq!(limits.max_aliases, 10_000); }
346
347 #[test]
348 fn test_default_max_columns() {
349 let limits = Limits::default();
350 assert_eq!(limits.max_columns, 100);
351 }
352
353 #[test]
356 fn test_unlimited_max_file_size() {
357 let limits = Limits::unlimited();
358 assert_eq!(limits.max_file_size, usize::MAX);
359 }
360
361 #[test]
362 fn test_unlimited_max_line_length() {
363 let limits = Limits::unlimited();
364 assert_eq!(limits.max_line_length, usize::MAX);
365 }
366
367 #[test]
368 fn test_unlimited_max_indent_depth() {
369 let limits = Limits::unlimited();
370 assert_eq!(limits.max_indent_depth, usize::MAX);
371 }
372
373 #[test]
374 fn test_unlimited_max_nodes() {
375 let limits = Limits::unlimited();
376 assert_eq!(limits.max_nodes, usize::MAX);
377 }
378
379 #[test]
380 fn test_unlimited_max_aliases() {
381 let limits = Limits::unlimited();
382 assert_eq!(limits.max_aliases, usize::MAX);
383 }
384
385 #[test]
386 fn test_unlimited_max_columns() {
387 let limits = Limits::unlimited();
388 assert_eq!(limits.max_columns, usize::MAX);
389 }
390
391 #[test]
394 fn test_limits_clone() {
395 let original = Limits::default();
396 let cloned = original.clone();
397 assert_eq!(original.max_file_size, cloned.max_file_size);
398 assert_eq!(original.max_line_length, cloned.max_line_length);
399 assert_eq!(original.max_indent_depth, cloned.max_indent_depth);
400 assert_eq!(original.max_nodes, cloned.max_nodes);
401 assert_eq!(original.max_aliases, cloned.max_aliases);
402 assert_eq!(original.max_columns, cloned.max_columns);
403 }
404
405 #[test]
406 fn test_limits_debug() {
407 let limits = Limits::default();
408 let debug = format!("{:?}", limits);
409 assert!(debug.contains("max_file_size"));
410 assert!(debug.contains("max_line_length"));
411 assert!(debug.contains("max_indent_depth"));
412 assert!(debug.contains("max_nodes"));
413 assert!(debug.contains("max_aliases"));
414 assert!(debug.contains("max_columns"));
415 }
416
417 #[test]
420 fn test_custom_limits() {
421 let limits = Limits {
422 max_file_size: 100,
423 max_line_length: 200,
424 max_indent_depth: 5,
425 max_nodes: 1000,
426 max_aliases: 50,
427 max_columns: 10,
428 max_nest_depth: 20,
429 max_block_string_size: 5000,
430 max_object_keys: 100,
431 max_total_keys: 500,
432 max_total_ids: 1000,
433 timeout: Some(Duration::from_secs(5)),
434 };
435 assert_eq!(limits.max_file_size, 100);
436 assert_eq!(limits.max_line_length, 200);
437 assert_eq!(limits.max_indent_depth, 5);
438 assert_eq!(limits.max_nodes, 1000);
439 assert_eq!(limits.max_aliases, 50);
440 assert_eq!(limits.max_columns, 10);
441 assert_eq!(limits.max_nest_depth, 20);
442 assert_eq!(limits.max_block_string_size, 5000);
443 assert_eq!(limits.max_object_keys, 100);
444 assert_eq!(limits.max_total_keys, 500);
445 assert_eq!(limits.max_total_ids, 1000);
446 assert_eq!(limits.timeout, Some(Duration::from_secs(5)));
447 }
448
449 #[test]
450 fn test_limits_zero_values() {
451 let limits = Limits {
452 max_file_size: 0,
453 max_line_length: 0,
454 max_indent_depth: 0,
455 max_nodes: 0,
456 max_aliases: 0,
457 max_columns: 0,
458 max_nest_depth: 0,
459 max_block_string_size: 0,
460 max_object_keys: 0,
461 max_total_keys: 0,
462 max_total_ids: 0,
463 timeout: Some(Duration::from_secs(0)),
464 };
465 assert_eq!(limits.max_file_size, 0);
466 assert_eq!(limits.max_columns, 0);
467 assert_eq!(limits.max_nest_depth, 0);
468 assert_eq!(limits.max_block_string_size, 0);
469 assert_eq!(limits.max_object_keys, 0);
470 assert_eq!(limits.max_total_keys, 0);
471 }
472
473 #[test]
476 fn test_default_max_nest_depth() {
477 let limits = Limits::default();
478 assert_eq!(limits.max_nest_depth, 100);
479 }
480
481 #[test]
482 fn test_default_max_block_string_size() {
483 let limits = Limits::default();
484 assert_eq!(limits.max_block_string_size, 10 * 1024 * 1024); }
486
487 #[test]
488 fn test_unlimited_max_nest_depth() {
489 let limits = Limits::unlimited();
490 assert_eq!(limits.max_nest_depth, usize::MAX);
491 }
492
493 #[test]
494 fn test_unlimited_max_block_string_size() {
495 let limits = Limits::unlimited();
496 assert_eq!(limits.max_block_string_size, usize::MAX);
497 }
498
499 #[test]
500 fn test_default_max_total_keys() {
501 let limits = Limits::default();
502 assert_eq!(limits.max_total_keys, 10_000_000);
503 }
504
505 #[test]
506 fn test_unlimited_max_total_keys() {
507 let limits = Limits::unlimited();
508 assert_eq!(limits.max_total_keys, usize::MAX);
509 }
510
511 #[test]
512 fn test_max_total_keys_greater_than_max_object_keys() {
513 let limits = Limits::default();
514 assert!(
515 limits.max_total_keys > limits.max_object_keys,
516 "max_total_keys ({}) should be greater than max_object_keys ({})",
517 limits.max_total_keys,
518 limits.max_object_keys
519 );
520 }
521
522 #[test]
525 fn test_default_max_total_ids() {
526 let limits = Limits::default();
527 assert_eq!(limits.max_total_ids, 10_000_000);
528 }
529
530 #[test]
531 fn test_unlimited_max_total_ids() {
532 let limits = Limits::unlimited();
533 assert_eq!(limits.max_total_ids, usize::MAX);
534 }
535
536 #[test]
537 fn test_max_total_ids_matches_max_total_keys() {
538 let limits = Limits::default();
539 assert_eq!(
540 limits.max_total_ids, limits.max_total_keys,
541 "max_total_ids ({}) should match max_total_keys ({}) for consistency",
542 limits.max_total_ids, limits.max_total_keys
543 );
544 }
545
546 #[test]
549 fn test_default_timeout() {
550 let limits = Limits::default();
551 assert_eq!(limits.timeout, Some(Duration::from_secs(30)));
552 }
553
554 #[test]
555 fn test_unlimited_no_timeout() {
556 let limits = Limits::unlimited();
557 assert_eq!(limits.timeout, None);
558 }
559
560 #[test]
561 fn test_custom_timeout() {
562 let limits = Limits {
563 timeout: Some(Duration::from_secs(60)),
564 ..Limits::default()
565 };
566 assert_eq!(limits.timeout, Some(Duration::from_secs(60)));
567 }
568
569 #[test]
570 fn test_disabled_timeout() {
571 let limits = Limits {
572 timeout: None,
573 ..Limits::default()
574 };
575 assert_eq!(limits.timeout, None);
576 }
577
578 #[test]
581 fn test_timeout_context_no_timeout() {
582 let ctx = TimeoutContext::new(None);
583 assert!(ctx.check_timeout(1).is_ok());
585 assert!(ctx.check_timeout(1000).is_ok());
586 }
587
588 #[test]
589 fn test_timeout_context_with_generous_timeout() {
590 let ctx = TimeoutContext::new(Some(Duration::from_secs(10)));
591 assert!(ctx.check_timeout(1).is_ok());
593 }
594
595 #[test]
596 fn test_timeout_context_with_zero_timeout() {
597 let ctx = TimeoutContext::new(Some(Duration::from_micros(1)));
599 std::thread::sleep(Duration::from_micros(10));
601 let result = ctx.check_timeout(42);
603 assert!(result.is_err());
604 if let Err(e) = result {
605 let msg = e.to_string();
606 assert!(msg.contains("timeout exceeded") || msg.contains("Timeout"));
607 }
608 }
609
610 #[test]
611 fn test_timeout_context_error_message() {
612 let ctx = TimeoutContext::new(Some(Duration::from_nanos(1)));
613 std::thread::sleep(Duration::from_millis(1));
614 let result = ctx.check_timeout(123);
615 assert!(result.is_err());
616 if let Err(e) = result {
617 let msg = e.to_string();
618 assert!(msg.contains("123")); }
620 }
621
622 #[test]
625 fn test_timeout_iterator_basic() {
626 let lines = [(1, "line1"), (2, "line2"), (3, "line3")];
627 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
628
629 let mut count = 0;
630 for result in lines.iter().copied().with_timeout_check(&timeout_ctx) {
631 let (_line_num, _line) = result.unwrap();
632 count += 1;
633 }
634 assert_eq!(count, 3);
635 }
636
637 #[test]
638 fn test_timeout_iterator_no_timeout() {
639 let lines = vec![(1, "a"); 1000];
640 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
641
642 let count = lines
643 .iter()
644 .copied()
645 .with_timeout_check(&timeout_ctx)
646 .filter_map(Result::ok)
647 .count();
648 assert_eq!(count, 1000);
649 }
650
651 #[test]
652 fn test_timeout_iterator_triggers_timeout() {
653 let lines: Vec<(usize, &str)> = (1..=100_000).map(|i| (i, "line")).collect();
655
656 let timeout_ctx = TimeoutContext::new(Some(Duration::from_micros(1)));
658
659 let mut hit_timeout = false;
661 for result in lines.iter().copied().with_timeout_check(&timeout_ctx) {
662 if result.is_err() {
663 hit_timeout = true;
664 break;
665 }
666 }
667
668 let _ = hit_timeout; }
673
674 #[test]
675 fn test_timeout_iterator_custom_interval() {
676 let lines = vec![(1, "a"); 100];
677 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
678
679 let count = lines
681 .iter()
682 .copied()
683 .with_timeout_check_interval(&timeout_ctx, 1)
684 .filter_map(Result::ok)
685 .count();
686 assert_eq!(count, 100);
687 }
688
689 #[test]
690 fn test_timeout_iterator_size_hint() {
691 let lines = [(1, "a"), (2, "b"), (3, "c")];
692 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
693
694 let iter = lines.iter().copied().with_timeout_check(&timeout_ctx);
695 let (lower, upper) = iter.size_hint();
696 assert_eq!(lower, 3);
697 assert_eq!(upper, Some(3));
698 }
699
700 #[test]
701 fn test_timeout_iterator_empty() {
702 let lines: Vec<(usize, &str)> = vec![];
703 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
704
705 let count = lines
706 .iter()
707 .copied()
708 .with_timeout_check(&timeout_ctx)
709 .filter_map(Result::ok)
710 .count();
711 assert_eq!(count, 0);
712 }
713
714 #[test]
715 fn test_timeout_iterator_single_item() {
716 let lines = [(1, "line")];
717 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
718
719 let items: Vec<_> = lines
720 .iter()
721 .copied()
722 .with_timeout_check(&timeout_ctx)
723 .collect();
724 assert_eq!(items.len(), 1);
725 assert!(items[0].is_ok());
726 }
727
728 #[test]
729 fn test_timeout_iterator_no_timeout_configured() {
730 let lines = vec![(1, "a"); 1000];
731 let timeout_ctx = TimeoutContext::new(None);
732
733 let count = lines
734 .iter()
735 .copied()
736 .with_timeout_check(&timeout_ctx)
737 .filter_map(Result::ok)
738 .count();
739 assert_eq!(count, 1000);
740 }
741
742 #[test]
743 fn test_default_timeout_check_interval() {
744 assert_eq!(DEFAULT_TIMEOUT_CHECK_INTERVAL, 10_000);
745 }
746
747 #[test]
750 fn test_timeout_check_interval_performance_characteristic() {
751 let interval = DEFAULT_TIMEOUT_CHECK_INTERVAL;
754
755 assert!(
757 interval >= 1000,
758 "Check interval too small, may impact performance"
759 );
760
761 assert!(
763 interval <= 100_000,
764 "Check interval too large, slow timeout detection"
765 );
766 }
767}