1use crate::error::{HedlError, HedlResult};
21
22#[cfg(not(target_arch = "wasm32"))]
23use std::time::{Duration, Instant};
24
25#[cfg(target_arch = "wasm32")]
26use std::time::Duration;
27
28#[derive(Debug, Clone)]
33pub struct Limits {
34 pub max_file_size: usize,
36 pub max_line_length: usize,
38 pub max_indent_depth: usize,
40 pub max_nodes: usize,
42 pub max_aliases: usize,
44 pub max_columns: usize,
46 pub max_nest_depth: usize,
48 pub max_block_string_size: usize,
50 pub max_object_keys: usize,
52 pub max_total_keys: usize,
62 pub max_total_ids: usize,
72 pub timeout: Option<Duration>,
80}
81
82impl Default for Limits {
83 fn default() -> Self {
84 Self {
85 max_file_size: 1024 * 1024 * 1024, max_line_length: 1024 * 1024, max_indent_depth: 50,
88 max_nodes: 10_000_000,
89 max_aliases: 10_000,
90 max_columns: 100,
91 max_nest_depth: 100,
92 max_block_string_size: 10 * 1024 * 1024, max_object_keys: 10_000,
94 max_total_keys: 10_000_000, max_total_ids: 10_000_000, timeout: Some(Duration::from_secs(30)), }
98 }
99}
100
101impl Limits {
102 pub fn unlimited() -> Self {
104 Self {
105 max_file_size: usize::MAX,
106 max_line_length: usize::MAX,
107 max_indent_depth: usize::MAX,
108 max_nodes: usize::MAX,
109 max_aliases: usize::MAX,
110 max_columns: usize::MAX,
111 max_nest_depth: usize::MAX,
112 max_block_string_size: usize::MAX,
113 max_object_keys: usize::MAX,
114 max_total_keys: usize::MAX,
115 max_total_ids: usize::MAX,
116 timeout: None,
117 }
118 }
119}
120
121#[derive(Debug, Clone, Copy)]
129pub struct TimeoutContext {
130 #[cfg(not(target_arch = "wasm32"))]
131 start: Instant,
132 #[cfg(not(target_arch = "wasm32"))]
133 timeout: Option<Duration>,
134}
135
136impl TimeoutContext {
137 #[cfg(not(target_arch = "wasm32"))]
139 pub fn new(timeout: Option<Duration>) -> Self {
140 Self {
141 start: Instant::now(),
142 timeout,
143 }
144 }
145
146 #[cfg(target_arch = "wasm32")]
148 pub fn new(_timeout: Option<Duration>) -> Self {
149 Self {}
150 }
151
152 #[cfg(not(target_arch = "wasm32"))]
163 pub fn check_timeout(&self, line_num: usize) -> HedlResult<()> {
164 if let Some(timeout) = self.timeout {
165 let elapsed = self.start.elapsed();
166 if elapsed > timeout {
167 return Err(HedlError::security(
168 format!(
169 "parsing timeout exceeded: {}ms > {}ms",
170 elapsed.as_millis(),
171 timeout.as_millis()
172 ),
173 line_num,
174 ));
175 }
176 }
177 Ok(())
178 }
179
180 #[cfg(target_arch = "wasm32")]
182 #[inline(always)]
183 pub fn check_timeout(&self, _line_num: usize) -> HedlResult<()> {
184 Ok(())
185 }
186}
187
188pub const DEFAULT_TIMEOUT_CHECK_INTERVAL: usize = 10_000;
195
196pub struct TimeoutCheckIterator<'a, I>
226where
227 I: Iterator,
228{
229 inner: I,
230 timeout_ctx: &'a TimeoutContext,
231 check_interval: usize,
232 iteration_count: usize,
233}
234
235impl<'a, I> TimeoutCheckIterator<'a, I>
236where
237 I: Iterator,
238{
239 pub fn new(inner: I, timeout_ctx: &'a TimeoutContext) -> Self {
241 Self::with_interval(inner, timeout_ctx, DEFAULT_TIMEOUT_CHECK_INTERVAL)
242 }
243
244 pub fn with_interval(inner: I, timeout_ctx: &'a TimeoutContext, check_interval: usize) -> Self {
252 Self {
253 inner,
254 timeout_ctx,
255 check_interval,
256 iteration_count: 0,
257 }
258 }
259}
260
261impl<'a, I> Iterator for TimeoutCheckIterator<'a, I>
262where
263 I: Iterator<Item = (usize, &'a str)>,
264{
265 type Item = Result<(usize, &'a str), HedlError>;
266
267 fn next(&mut self) -> Option<Self::Item> {
268 let item = self.inner.next()?;
270 let (line_num, _line) = item;
271
272 self.iteration_count += 1;
274 if self.iteration_count % self.check_interval == 0 {
275 if let Err(e) = self.timeout_ctx.check_timeout(line_num) {
276 return Some(Err(e));
277 }
278 }
279
280 Some(Ok(item))
281 }
282
283 fn size_hint(&self) -> (usize, Option<usize>) {
284 self.inner.size_hint()
285 }
286}
287
288pub trait TimeoutCheckExt<'a>: Iterator<Item = (usize, &'a str)> + Sized {
293 fn with_timeout_check(self, timeout_ctx: &'a TimeoutContext) -> TimeoutCheckIterator<'a, Self> {
313 TimeoutCheckIterator::new(self, timeout_ctx)
314 }
315}
316
317impl<'a, I> TimeoutCheckExt<'a> for I where I: Iterator<Item = (usize, &'a str)> {}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
327 fn test_default_max_file_size() {
328 let limits = Limits::default();
329 assert_eq!(limits.max_file_size, 1024 * 1024 * 1024); }
331
332 #[test]
333 fn test_default_max_line_length() {
334 let limits = Limits::default();
335 assert_eq!(limits.max_line_length, 1024 * 1024); }
337
338 #[test]
339 fn test_default_max_indent_depth() {
340 let limits = Limits::default();
341 assert_eq!(limits.max_indent_depth, 50);
342 }
343
344 #[test]
345 fn test_default_max_nodes() {
346 let limits = Limits::default();
347 assert_eq!(limits.max_nodes, 10_000_000); }
349
350 #[test]
351 fn test_default_max_aliases() {
352 let limits = Limits::default();
353 assert_eq!(limits.max_aliases, 10_000); }
355
356 #[test]
357 fn test_default_max_columns() {
358 let limits = Limits::default();
359 assert_eq!(limits.max_columns, 100);
360 }
361
362 #[test]
365 fn test_unlimited_max_file_size() {
366 let limits = Limits::unlimited();
367 assert_eq!(limits.max_file_size, usize::MAX);
368 }
369
370 #[test]
371 fn test_unlimited_max_line_length() {
372 let limits = Limits::unlimited();
373 assert_eq!(limits.max_line_length, usize::MAX);
374 }
375
376 #[test]
377 fn test_unlimited_max_indent_depth() {
378 let limits = Limits::unlimited();
379 assert_eq!(limits.max_indent_depth, usize::MAX);
380 }
381
382 #[test]
383 fn test_unlimited_max_nodes() {
384 let limits = Limits::unlimited();
385 assert_eq!(limits.max_nodes, usize::MAX);
386 }
387
388 #[test]
389 fn test_unlimited_max_aliases() {
390 let limits = Limits::unlimited();
391 assert_eq!(limits.max_aliases, usize::MAX);
392 }
393
394 #[test]
395 fn test_unlimited_max_columns() {
396 let limits = Limits::unlimited();
397 assert_eq!(limits.max_columns, usize::MAX);
398 }
399
400 #[test]
403 fn test_limits_clone() {
404 let original = Limits::default();
405 let cloned = original.clone();
406 assert_eq!(original.max_file_size, cloned.max_file_size);
407 assert_eq!(original.max_line_length, cloned.max_line_length);
408 assert_eq!(original.max_indent_depth, cloned.max_indent_depth);
409 assert_eq!(original.max_nodes, cloned.max_nodes);
410 assert_eq!(original.max_aliases, cloned.max_aliases);
411 assert_eq!(original.max_columns, cloned.max_columns);
412 }
413
414 #[test]
415 fn test_limits_debug() {
416 let limits = Limits::default();
417 let debug = format!("{:?}", limits);
418 assert!(debug.contains("max_file_size"));
419 assert!(debug.contains("max_line_length"));
420 assert!(debug.contains("max_indent_depth"));
421 assert!(debug.contains("max_nodes"));
422 assert!(debug.contains("max_aliases"));
423 assert!(debug.contains("max_columns"));
424 }
425
426 #[test]
429 fn test_custom_limits() {
430 let limits = Limits {
431 max_file_size: 100,
432 max_line_length: 200,
433 max_indent_depth: 5,
434 max_nodes: 1000,
435 max_aliases: 50,
436 max_columns: 10,
437 max_nest_depth: 20,
438 max_block_string_size: 5000,
439 max_object_keys: 100,
440 max_total_keys: 500,
441 max_total_ids: 1000,
442 timeout: Some(Duration::from_secs(5)),
443 };
444 assert_eq!(limits.max_file_size, 100);
445 assert_eq!(limits.max_line_length, 200);
446 assert_eq!(limits.max_indent_depth, 5);
447 assert_eq!(limits.max_nodes, 1000);
448 assert_eq!(limits.max_aliases, 50);
449 assert_eq!(limits.max_columns, 10);
450 assert_eq!(limits.max_nest_depth, 20);
451 assert_eq!(limits.max_block_string_size, 5000);
452 assert_eq!(limits.max_object_keys, 100);
453 assert_eq!(limits.max_total_keys, 500);
454 assert_eq!(limits.max_total_ids, 1000);
455 assert_eq!(limits.timeout, Some(Duration::from_secs(5)));
456 }
457
458 #[test]
459 fn test_limits_zero_values() {
460 let limits = Limits {
461 max_file_size: 0,
462 max_line_length: 0,
463 max_indent_depth: 0,
464 max_nodes: 0,
465 max_aliases: 0,
466 max_columns: 0,
467 max_nest_depth: 0,
468 max_block_string_size: 0,
469 max_object_keys: 0,
470 max_total_keys: 0,
471 max_total_ids: 0,
472 timeout: Some(Duration::from_secs(0)),
473 };
474 assert_eq!(limits.max_file_size, 0);
475 assert_eq!(limits.max_columns, 0);
476 assert_eq!(limits.max_nest_depth, 0);
477 assert_eq!(limits.max_block_string_size, 0);
478 assert_eq!(limits.max_object_keys, 0);
479 assert_eq!(limits.max_total_keys, 0);
480 }
481
482 #[test]
485 fn test_default_max_nest_depth() {
486 let limits = Limits::default();
487 assert_eq!(limits.max_nest_depth, 100);
488 }
489
490 #[test]
491 fn test_default_max_block_string_size() {
492 let limits = Limits::default();
493 assert_eq!(limits.max_block_string_size, 10 * 1024 * 1024); }
495
496 #[test]
497 fn test_unlimited_max_nest_depth() {
498 let limits = Limits::unlimited();
499 assert_eq!(limits.max_nest_depth, usize::MAX);
500 }
501
502 #[test]
503 fn test_unlimited_max_block_string_size() {
504 let limits = Limits::unlimited();
505 assert_eq!(limits.max_block_string_size, usize::MAX);
506 }
507
508 #[test]
509 fn test_default_max_total_keys() {
510 let limits = Limits::default();
511 assert_eq!(limits.max_total_keys, 10_000_000);
512 }
513
514 #[test]
515 fn test_unlimited_max_total_keys() {
516 let limits = Limits::unlimited();
517 assert_eq!(limits.max_total_keys, usize::MAX);
518 }
519
520 #[test]
521 fn test_max_total_keys_greater_than_max_object_keys() {
522 let limits = Limits::default();
523 assert!(
524 limits.max_total_keys > limits.max_object_keys,
525 "max_total_keys ({}) should be greater than max_object_keys ({})",
526 limits.max_total_keys,
527 limits.max_object_keys
528 );
529 }
530
531 #[test]
534 fn test_default_max_total_ids() {
535 let limits = Limits::default();
536 assert_eq!(limits.max_total_ids, 10_000_000);
537 }
538
539 #[test]
540 fn test_unlimited_max_total_ids() {
541 let limits = Limits::unlimited();
542 assert_eq!(limits.max_total_ids, usize::MAX);
543 }
544
545 #[test]
546 fn test_max_total_ids_matches_max_total_keys() {
547 let limits = Limits::default();
548 assert_eq!(
549 limits.max_total_ids, limits.max_total_keys,
550 "max_total_ids ({}) should match max_total_keys ({}) for consistency",
551 limits.max_total_ids, limits.max_total_keys
552 );
553 }
554
555 #[test]
558 fn test_default_timeout() {
559 let limits = Limits::default();
560 assert_eq!(limits.timeout, Some(Duration::from_secs(30)));
561 }
562
563 #[test]
564 fn test_unlimited_no_timeout() {
565 let limits = Limits::unlimited();
566 assert_eq!(limits.timeout, None);
567 }
568
569 #[test]
570 fn test_custom_timeout() {
571 let limits = Limits {
572 timeout: Some(Duration::from_secs(60)),
573 ..Limits::default()
574 };
575 assert_eq!(limits.timeout, Some(Duration::from_secs(60)));
576 }
577
578 #[test]
579 fn test_disabled_timeout() {
580 let limits = Limits {
581 timeout: None,
582 ..Limits::default()
583 };
584 assert_eq!(limits.timeout, None);
585 }
586
587 #[test]
590 fn test_timeout_context_no_timeout() {
591 let ctx = TimeoutContext::new(None);
592 assert!(ctx.check_timeout(1).is_ok());
594 assert!(ctx.check_timeout(1000).is_ok());
595 }
596
597 #[test]
598 fn test_timeout_context_with_generous_timeout() {
599 let ctx = TimeoutContext::new(Some(Duration::from_secs(10)));
600 assert!(ctx.check_timeout(1).is_ok());
602 }
603
604 #[test]
605 fn test_timeout_context_with_zero_timeout() {
606 let ctx = TimeoutContext::new(Some(Duration::from_micros(1)));
608 std::thread::sleep(Duration::from_micros(10));
610 let result = ctx.check_timeout(42);
612 assert!(result.is_err());
613 if let Err(e) = result {
614 let msg = e.to_string();
615 assert!(msg.contains("timeout exceeded") || msg.contains("Timeout"));
616 }
617 }
618
619 #[test]
620 fn test_timeout_context_error_message() {
621 let ctx = TimeoutContext::new(Some(Duration::from_nanos(1)));
622 std::thread::sleep(Duration::from_millis(1));
623 let result = ctx.check_timeout(123);
624 assert!(result.is_err());
625 if let Err(e) = result {
626 let msg = e.to_string();
627 assert!(msg.contains("123")); }
629 }
630
631 #[test]
634 fn test_timeout_iterator_basic() {
635 let lines = [(1, "line1"), (2, "line2"), (3, "line3")];
636 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
637
638 let mut count = 0;
639 for result in lines.iter().copied().with_timeout_check(&timeout_ctx) {
640 let (_line_num, _line) = result.unwrap();
641 count += 1;
642 }
643 assert_eq!(count, 3);
644 }
645
646 #[test]
647 fn test_timeout_iterator_no_timeout() {
648 let lines = vec![(1, "a"); 1000];
649 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
650
651 let count = lines
652 .iter()
653 .copied()
654 .with_timeout_check(&timeout_ctx)
655 .filter_map(Result::ok)
656 .count();
657 assert_eq!(count, 1000);
658 }
659
660 #[test]
661 fn test_timeout_iterator_triggers_timeout() {
662 let lines: Vec<(usize, &str)> = (1..=100_000).map(|i| (i, "line")).collect();
664
665 let timeout_ctx = TimeoutContext::new(Some(Duration::from_micros(1)));
667
668 let mut hit_timeout = false;
670 for result in lines.iter().copied().with_timeout_check(&timeout_ctx) {
671 if result.is_err() {
672 hit_timeout = true;
673 break;
674 }
675 }
676
677 let _ = hit_timeout; }
682
683 #[test]
684 fn test_timeout_iterator_custom_interval() {
685 let lines = vec![(1, "a"); 100];
686 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
687
688 let count = TimeoutCheckIterator::with_interval(lines.iter().copied(), &timeout_ctx, 1)
690 .filter_map(Result::ok)
691 .count();
692 assert_eq!(count, 100);
693 }
694
695 #[test]
696 fn test_timeout_iterator_size_hint() {
697 let lines = [(1, "a"), (2, "b"), (3, "c")];
698 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
699
700 let iter = lines.iter().copied().with_timeout_check(&timeout_ctx);
701 let (lower, upper) = iter.size_hint();
702 assert_eq!(lower, 3);
703 assert_eq!(upper, Some(3));
704 }
705
706 #[test]
707 fn test_timeout_iterator_empty() {
708 let lines: Vec<(usize, &str)> = vec![];
709 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
710
711 let count = lines
712 .iter()
713 .copied()
714 .with_timeout_check(&timeout_ctx)
715 .filter_map(Result::ok)
716 .count();
717 assert_eq!(count, 0);
718 }
719
720 #[test]
721 fn test_timeout_iterator_single_item() {
722 let lines = [(1, "line")];
723 let timeout_ctx = TimeoutContext::new(Some(Duration::from_secs(60)));
724
725 let items: Vec<_> = lines
726 .iter()
727 .copied()
728 .with_timeout_check(&timeout_ctx)
729 .collect();
730 assert_eq!(items.len(), 1);
731 assert!(items[0].is_ok());
732 }
733
734 #[test]
735 fn test_timeout_iterator_no_timeout_configured() {
736 let lines = vec![(1, "a"); 1000];
737 let timeout_ctx = TimeoutContext::new(None);
738
739 let count = lines
740 .iter()
741 .copied()
742 .with_timeout_check(&timeout_ctx)
743 .filter_map(Result::ok)
744 .count();
745 assert_eq!(count, 1000);
746 }
747
748 #[test]
749 fn test_default_timeout_check_interval() {
750 assert_eq!(DEFAULT_TIMEOUT_CHECK_INTERVAL, 10_000);
751 }
752
753 #[test]
756 fn test_timeout_check_interval_performance_characteristic() {
757 let interval = DEFAULT_TIMEOUT_CHECK_INTERVAL;
760
761 assert!(
763 interval >= 1000,
764 "Check interval too small, may impact performance"
765 );
766
767 assert!(
769 interval <= 100_000,
770 "Check interval too large, slow timeout detection"
771 );
772 }
773}