1use bytes::BytesMut;
55use core::marker::PhantomData;
56use tokio::io::{AsyncRead, AsyncReadExt as _};
57use tokio_util::codec::{Decoder, FramedRead};
58
59use crate::de::{ParserConfig, from_slice_with_config};
60use crate::error::{Error, Result};
61
62pub async fn from_async_reader<R, T>(reader: &mut R) -> Result<T>
74where
75 R: AsyncRead + Unpin,
76 T: serde_core::de::DeserializeOwned + 'static,
81{
82 from_async_reader_with_config(reader, &ParserConfig::default()).await
83}
84
85pub async fn from_async_reader_with_config<R, T>(reader: &mut R, config: &ParserConfig) -> Result<T>
99where
100 R: AsyncRead + Unpin,
101 T: serde_core::de::DeserializeOwned + 'static,
106{
107 let buf = drain_bounded(reader, config.max_document_length, "max_document_length").await?;
108 let buf = strip_bom_owned(buf);
109 from_slice_with_config(&buf, config)
110}
111
112pub async fn from_async_reader_multi<R, T>(reader: &mut R) -> Result<Vec<T>>
124where
125 R: AsyncRead + Unpin,
126 T: serde_core::de::DeserializeOwned + 'static,
131{
132 from_async_reader_multi_with_config(reader, &ParserConfig::default()).await
133}
134
135pub async fn from_async_reader_multi_with_config<R, T>(
152 reader: &mut R,
153 config: &ParserConfig,
154) -> Result<Vec<T>>
155where
156 R: AsyncRead + Unpin,
157 T: serde_core::de::DeserializeOwned + 'static,
162{
163 let buf = drain_bounded(reader, config.max_stream_bytes, "max_stream_bytes").await?;
164 let buf = strip_bom_owned(buf);
165 let text = core::str::from_utf8(&buf)
169 .map_err(|e| Error::from(std::io::Error::new(std::io::ErrorKind::InvalidData, e)))?;
170 let docs = crate::doc_boundary::split_documents_checked(text, config.max_documents)?;
174 let config = &crate::parser::meter::StreamTally::share(config);
177 let mut results = Vec::with_capacity(docs.len());
178 for doc in docs {
179 results.push(crate::from_str_with_config::<T>(doc, config)?);
180 }
181 Ok(results)
182}
183
184async fn drain_bounded<R>(reader: &mut R, max_bytes: usize, limit_name: &str) -> Result<Vec<u8>>
190where
191 R: AsyncRead + Unpin,
192{
193 let probe_bytes = max_bytes.saturating_add(1);
194 let initial_capacity = max_bytes.min(16 * 1024);
195 let mut buf = Vec::with_capacity(initial_capacity);
196 let take = u64::try_from(probe_bytes).unwrap_or(u64::MAX);
197 let mut limited = reader.take(take);
198 let _ = limited.read_to_end(&mut buf).await.map_err(Error::from)?;
199 if buf.len() > max_bytes {
200 return Err(Error::from(std::io::Error::new(
201 std::io::ErrorKind::InvalidData,
202 format!(
203 "noyalib async reader: input {} > {limit_name} {}",
204 buf.len(),
205 max_bytes
206 ),
207 )));
208 }
209 Ok(buf)
210}
211
212fn strip_bom_owned(mut buf: Vec<u8>) -> Vec<u8> {
216 if crate::doc_boundary::strip_bom(&buf) == 3 {
217 let _ = buf.drain(..3);
218 }
219 buf
220}
221
222#[derive(Debug, Clone)]
238pub struct YamlDecoder<T> {
239 config: ParserConfig,
240 max_frame_size: Option<usize>,
246 scanned: usize,
250 _marker: PhantomData<fn() -> T>,
251}
252
253pub type AsyncYamlStream<R, T> = FramedRead<R, YamlDecoder<T>>;
266
267#[must_use]
272pub fn async_yaml_stream<R, T>(reader: R) -> AsyncYamlStream<R, T> {
273 FramedRead::new(reader, YamlDecoder::new())
274}
275
276#[must_use]
279pub fn async_yaml_stream_with_config<R, T>(
280 reader: R,
281 config: ParserConfig,
282) -> AsyncYamlStream<R, T> {
283 FramedRead::new(reader, YamlDecoder::with_config(config))
284}
285
286impl<T> Default for YamlDecoder<T> {
287 fn default() -> Self {
288 Self::new()
289 }
290}
291
292impl<T> YamlDecoder<T> {
293 #[must_use]
296 pub fn new() -> Self {
297 Self::with_config(ParserConfig::default())
298 }
299
300 #[must_use]
306 pub fn with_config(config: ParserConfig) -> Self {
307 let config = crate::parser::meter::StreamTally::share(&config);
308 let max_frame_size = Some(config.max_document_length);
309 Self {
310 config,
311 max_frame_size,
312 scanned: 0,
313 _marker: PhantomData,
314 }
315 }
316
317 #[must_use]
326 pub fn max_frame_size(mut self, max: usize) -> Self {
327 self.max_frame_size = Some(max);
328 self
329 }
330
331 fn check_frame_len(&self, frame_len: usize) -> core::result::Result<(), Error> {
334 let doc_max = self.config.max_document_length;
335 let (max, name) = match self.max_frame_size {
336 Some(frame_max) if frame_max <= doc_max => (frame_max, "max_frame_size"),
337 _ => (doc_max, "max_document_length"),
338 };
339 if frame_len > max {
340 return Err(Error::from(std::io::Error::new(
341 std::io::ErrorKind::InvalidData,
342 format!("noyalib YamlDecoder: frame {frame_len} > {name} {max}"),
343 )));
344 }
345 Ok(())
346 }
347
348 fn next_boundary(&mut self, bytes: &[u8], at_eof: bool) -> Option<usize> {
358 if self.scanned > bytes.len() {
359 self.scanned = 0;
360 }
361 let found = crate::doc_boundary::next_marker_after_streaming(bytes, self.scanned, at_eof);
362 if found.is_none() {
363 let tail = if at_eof { 2 } else { 3 };
364 self.scanned = self.scanned.max(bytes.len().saturating_sub(tail));
365 }
366 found
367 }
368
369 fn decode_step(
370 &mut self,
371 src: &mut BytesMut,
372 at_eof: bool,
373 ) -> core::result::Result<Option<T>, Error>
374 where
375 T: serde_core::de::DeserializeOwned + 'static,
376 {
377 loop {
381 let boundary = self.next_boundary(src.as_ref(), at_eof);
382
383 self.check_frame_len(boundary.unwrap_or(src.len()))?;
388
389 let Some(end) = boundary else {
390 return Ok(None);
391 };
392
393 let doc = src.split_to(end);
398 self.scanned = 0;
399 if doc.iter().all(u8::is_ascii_whitespace) {
400 continue;
403 }
404 let parsed = from_slice_with_config::<T>(&doc, &self.config)?;
405 return Ok(Some(parsed));
406 }
407 }
408}
409
410impl<T> Decoder for YamlDecoder<T>
411where
412 T: serde_core::de::DeserializeOwned + 'static,
417{
418 type Item = T;
419 type Error = Error;
420
421 fn decode(&mut self, src: &mut BytesMut) -> core::result::Result<Option<T>, Error> {
422 self.decode_step(src, false)
423 }
424
425 fn decode_eof(&mut self, src: &mut BytesMut) -> core::result::Result<Option<T>, Error> {
426 if src.is_empty() {
427 return Ok(None);
428 }
429 if let Some(v) = self.decode_step(src, true)? {
433 return Ok(Some(v));
434 }
435 if src.iter().all(u8::is_ascii_whitespace) {
436 src.clear();
437 self.scanned = 0;
438 return Ok(None);
439 }
440 let doc = src.split();
441 self.scanned = 0;
442 let parsed = from_slice_with_config::<T>(&doc, &self.config)?;
443 Ok(Some(parsed))
444 }
445}
446
447#[cfg(test)]
458fn find_doc_boundary(bytes: &[u8]) -> Option<usize> {
459 crate::doc_boundary::next_marker_after(bytes, 0)
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465 use bytes::BytesMut;
466
467 use tokio::io::BufReader;
468
469 #[derive(Debug, serde::Deserialize, PartialEq)]
470 struct Pkg {
471 name: String,
472 version: String,
473 }
474
475 #[tokio::test]
476 async fn reader_parses_single_document() {
477 let mut r = BufReader::new(&b"name: noyalib\nversion: 0.0.6\n"[..]);
478 let p: Pkg = from_async_reader(&mut r).await.unwrap();
479 assert_eq!(
480 p,
481 Pkg {
482 name: "noyalib".into(),
483 version: "0.0.6".into(),
484 }
485 );
486 }
487
488 #[tokio::test]
489 async fn reader_multi_parses_each_document() {
490 let yaml = b"---\nname: a\nversion: '1'\n---\nname: b\nversion: '2'\n";
491 let mut r = BufReader::new(&yaml[..]);
492 let docs: Vec<Pkg> = from_async_reader_multi(&mut r).await.unwrap();
493 assert_eq!(docs.len(), 2);
494 assert_eq!(docs[0].name, "a");
495 assert_eq!(docs[1].name, "b");
496 }
497
498 #[test]
499 fn decoder_emits_first_complete_document() {
500 let mut decoder: YamlDecoder<Pkg> = YamlDecoder::new();
501 let mut buf = BytesMut::from(&b"name: a\nversion: '1'\n---\nname: b\nversion: '2'\n"[..]);
502 let first = decoder.decode(&mut buf).unwrap().unwrap();
503 assert_eq!(first.name, "a");
504 let second = decoder.decode_eof(&mut buf).unwrap().unwrap();
507 assert_eq!(second.name, "b");
508 }
509
510 #[test]
511 fn decoder_returns_none_on_incomplete_buffer() {
512 let mut decoder: YamlDecoder<Pkg> = YamlDecoder::new();
513 let mut buf = BytesMut::from(&b"name: a\n"[..]);
515 assert!(decoder.decode(&mut buf).unwrap().is_none());
516 }
517
518 #[tokio::test]
519 async fn reader_with_config_respects_overrides() {
520 let mut r = BufReader::new(&b"name: x\nversion: '1'\n"[..]);
521 let cfg = ParserConfig::default();
522 let p: Pkg = from_async_reader_with_config(&mut r, &cfg).await.unwrap();
523 assert_eq!(p.name, "x");
524 }
525
526 #[test]
527 fn decoder_with_config_constructor() {
528 let cfg = ParserConfig::default();
529 let _d: YamlDecoder<Pkg> = YamlDecoder::with_config(cfg);
530 let _d2: YamlDecoder<Pkg> = YamlDecoder::default();
531 let _printed = format!("{:?}", YamlDecoder::<Pkg>::new());
532 }
533
534 #[test]
535 fn decoder_eof_on_empty_buffer_returns_none() {
536 let mut decoder: YamlDecoder<Pkg> = YamlDecoder::new();
537 let mut buf = BytesMut::new();
538 assert!(decoder.decode_eof(&mut buf).unwrap().is_none());
539 }
540
541 #[test]
542 fn decoder_skips_whitespace_only_preamble() {
543 let mut decoder: YamlDecoder<Pkg> = YamlDecoder::new();
544 let mut buf = BytesMut::from(&b"\n\n---\nname: q\nversion: '2'\n"[..]);
547 let p = decoder.decode_eof(&mut buf).unwrap().unwrap();
548 assert_eq!(p.name, "q");
549 }
550
551 #[test]
552 fn decoder_eof_drains_trailing_whitespace() {
553 let mut decoder: YamlDecoder<Pkg> = YamlDecoder::new();
554 let mut buf = BytesMut::from(&b"name: r\nversion: '3'\n\n\n"[..]);
557 let p = decoder.decode_eof(&mut buf).unwrap().unwrap();
558 assert_eq!(p.name, "r");
559 }
560
561 #[tokio::test]
562 async fn reader_multi_handles_invalid_utf8() {
563 let mut r = BufReader::new(&[0xFFu8, 0xFE, 0xFD][..]);
566 let res: Result<Vec<Pkg>> = from_async_reader_multi(&mut r).await;
567 assert!(res.is_err());
568 }
569
570 #[test]
571 fn find_doc_boundary_handles_short_input() {
572 assert!(find_doc_boundary(b"").is_none());
573 assert!(find_doc_boundary(b"abc").is_none());
574 }
575
576 #[tokio::test]
577 async fn reader_strips_leading_bom() {
578 let mut r = BufReader::new(&b"\xEF\xBB\xBFname: x\nversion: '1'\n"[..]);
581 let p: Pkg = from_async_reader(&mut r).await.unwrap();
582 assert_eq!(p.name, "x");
583 }
584
585 #[tokio::test]
586 async fn reader_multi_accepts_crlf() {
587 let yaml = b"---\r\nname: a\r\nversion: '1'\r\n---\r\nname: b\r\nversion: '2'\r\n";
589 let mut r = BufReader::new(&yaml[..]);
590 let docs: Vec<Pkg> = from_async_reader_multi(&mut r).await.unwrap();
591 assert_eq!(docs.len(), 2);
592 assert_eq!(docs[0].name, "a");
593 assert_eq!(docs[1].name, "b");
594 }
595
596 #[tokio::test]
597 async fn reader_caps_at_max_document_length() {
598 let valid_prefix = "name: x\nversion: '1'\n";
601 let yaml = format!("{valid_prefix}ignored: true\n");
602 let cfg = ParserConfig {
603 max_document_length: valid_prefix.len(),
604 ..ParserConfig::default()
605 };
606 let mut r = BufReader::new(yaml.as_bytes());
607 let err = from_async_reader_with_config::<_, Pkg>(&mut r, &cfg)
608 .await
609 .expect_err("over-limit input must be rejected, not truncated");
610 assert!(err.to_string().contains("max_document_length"));
611 }
612
613 #[tokio::test]
614 async fn reader_accepts_input_exactly_at_limit() {
615 let yaml = "name: x\nversion: '1'\n";
616 let cfg = ParserConfig {
617 max_document_length: yaml.len(),
618 ..ParserConfig::default()
619 };
620 let mut r = BufReader::new(yaml.as_bytes());
621 let pkg = from_async_reader_with_config::<_, Pkg>(&mut r, &cfg)
622 .await
623 .unwrap();
624 assert_eq!(pkg.name, "x");
625 }
626
627 #[tokio::test]
628 async fn reader_zero_limit_rejects_nonempty_input() {
629 let cfg = ParserConfig {
630 max_document_length: 0,
631 ..ParserConfig::default()
632 };
633 let mut r = BufReader::new(&b"null\n"[..]);
634 let err = from_async_reader_with_config::<_, crate::Value>(&mut r, &cfg)
635 .await
636 .expect_err("a zero limit must reject nonempty input");
637 assert!(err.to_string().contains("max_document_length 0"));
638 }
639
640 #[test]
641 fn decoder_rejects_oversize_frame() {
642 let mut decoder: YamlDecoder<Pkg> = YamlDecoder::new().max_frame_size(16);
644 let mut buf = BytesMut::from(&b"name: long-name-no-marker-yet-need-more-bytes"[..]);
645 let err = decoder.decode(&mut buf).err().unwrap();
646 assert!(err.to_string().contains("max_frame_size"));
647 }
648
649 #[test]
650 fn decoder_accepts_multiple_buffered_documents_beyond_frame_cap() {
651 let cfg = ParserConfig {
652 max_document_length: 28,
653 ..ParserConfig::default()
654 };
655 let mut decoder: YamlDecoder<Pkg> = YamlDecoder::with_config(cfg);
656 let mut buf = BytesMut::from(&b"name: a\nversion: '1'\n---\nname: b\nversion: '2'\n"[..]);
657 assert!(buf.len() > 28);
658
659 let first = decoder.decode(&mut buf).unwrap().unwrap();
660 assert_eq!(first.name, "a");
661 let second = decoder.decode_eof(&mut buf).unwrap().unwrap();
662 assert_eq!(second.name, "b");
663 }
664
665 #[test]
666 fn stream_constructors_preserve_decoder_configuration() {
667 let default_stream = async_yaml_stream::<_, Pkg>(&b""[..]);
668 assert_eq!(
669 default_stream.decoder().max_frame_size,
670 Some(ParserConfig::default().max_document_length)
671 );
672
673 let cfg = ParserConfig {
674 max_document_length: 17,
675 ..ParserConfig::default()
676 };
677 let configured_stream = async_yaml_stream_with_config::<_, Pkg>(&b""[..], cfg);
678 assert_eq!(configured_stream.decoder().max_frame_size, Some(17));
679 }
680
681 #[test]
682 fn decoder_derives_frame_cap_from_parser_config() {
683 let cfg = ParserConfig {
684 max_document_length: 16,
685 ..ParserConfig::default()
686 };
687 let mut decoder: YamlDecoder<Pkg> = YamlDecoder::with_config(cfg);
688 let mut buf = BytesMut::from(&b"name: long-name-without-boundary"[..]);
689 let err = decoder.decode(&mut buf).unwrap_err();
690 assert!(err.to_string().contains("max_frame_size 16"));
691 }
692
693 #[test]
694 fn decoder_eof_preserves_parser_config() {
695 let cfg = ParserConfig::strict();
696 let mut decoder: YamlDecoder<crate::Value> = YamlDecoder::with_config(cfg);
697 let mut buf = BytesMut::from(&b"a: 1\na: 2\n"[..]);
698 let err = decoder
699 .decode_eof(&mut buf)
700 .expect_err("strict duplicate-key policy must apply at EOF");
701 assert!(matches!(
702 err.kind(),
703 crate::error::ErrorKind::DuplicateKey | crate::error::ErrorKind::KeyCollision
704 ));
705 }
706
707 #[test]
708 fn decoder_frame_cap_never_exceeds_max_document_length() {
709 let cfg = ParserConfig {
712 max_document_length: 16,
713 ..ParserConfig::default()
714 };
715 let mut decoder: YamlDecoder<Pkg> = YamlDecoder::with_config(cfg).max_frame_size(1 << 20);
716 let mut buf = BytesMut::from(&b"name: long-name-without-boundary-at-all"[..]);
717 let err = decoder.decode(&mut buf).unwrap_err();
718 assert!(err.to_string().contains("max_document_length 16"), "{err}");
719 }
720
721 #[test]
722 fn decoder_finds_boundaries_split_across_reads() {
723 let input = b"name: a\nversion: '1'\n---\nname: b\nversion: '2'\n";
724 for split in 0..=input.len() {
725 let mut decoder: YamlDecoder<Pkg> = YamlDecoder::new();
726 let mut buf = BytesMut::from(&input[..split]);
727 let mut out = Vec::new();
728 if let Some(doc) = decoder.decode(&mut buf).unwrap() {
729 out.push(doc);
730 }
731 buf.extend_from_slice(&input[split..]);
732 while let Some(doc) = decoder.decode(&mut buf).unwrap() {
733 out.push(doc);
734 }
735 if let Some(doc) = decoder.decode_eof(&mut buf).unwrap() {
736 out.push(doc);
737 }
738 let names: Vec<_> = out.iter().map(|p| p.name.as_str()).collect();
739 assert_eq!(names, ["a", "b"], "split at {split}");
740 }
741 }
742
743 #[test]
744 fn decoder_scan_resumes_instead_of_rescanning() {
745 let cfg = ParserConfig {
748 max_document_length: 16 << 20,
749 ..ParserConfig::default()
750 };
751 let mut decoder: YamlDecoder<crate::Value> = YamlDecoder::with_config(cfg);
752 let mut chunk = Vec::new();
753 while chunk.len() < 8 << 10 {
754 chunk.extend_from_slice(b"# padding line\n");
755 }
756 let mut buf = BytesMut::new();
757 let start = std::time::Instant::now();
758 for _ in 0..1024 {
759 buf.extend_from_slice(&chunk);
760 assert!(decoder.decode(&mut buf).unwrap().is_none());
761 }
762 buf.extend_from_slice(b"k: v\n---\n");
763 let doc = decoder.decode(&mut buf).unwrap().unwrap();
764 let elapsed = start.elapsed();
765 assert_eq!(doc["k"].as_str(), Some("v"));
766 assert!(
767 elapsed < std::time::Duration::from_secs(10),
768 "8 MiB in 8 KiB reads took {elapsed:?}"
769 );
770 }
771
772 #[test]
773 fn decoder_accepts_crlf_boundary() {
774 let mut decoder: YamlDecoder<Pkg> = YamlDecoder::new();
776 let mut buf =
777 BytesMut::from(&b"name: a\r\nversion: '1'\r\n---\r\nname: b\r\nversion: '2'\r\n"[..]);
778 let first = decoder.decode(&mut buf).unwrap().unwrap();
779 assert_eq!(first.name, "a");
780 }
781
782 #[tokio::test]
783 async fn reader_multi_with_config_routes_through() {
784 let yaml = b"---\nname: a\nversion: '1'\n---\nname: b\nversion: '2'\n";
786 let mut r = BufReader::new(&yaml[..]);
787 let cfg = ParserConfig::default();
788 let docs: Vec<Pkg> = from_async_reader_multi_with_config(&mut r, &cfg)
789 .await
790 .unwrap();
791 assert_eq!(docs.len(), 2);
792 }
793
794 #[tokio::test]
795 async fn multi_reader_separates_stream_and_document_byte_limits() {
796 let yaml = b"---\nname: a\nversion: '1'\n---\nname: b\nversion: '2'\n";
797 let cfg = ParserConfig::default()
798 .max_document_length(32)
799 .max_stream_bytes(yaml.len());
800 let mut reader = BufReader::new(&yaml[..]);
801
802 let docs: Vec<Pkg> = from_async_reader_multi_with_config(&mut reader, &cfg)
803 .await
804 .unwrap();
805 assert_eq!(docs.len(), 2);
806 }
807
808 #[tokio::test]
809 async fn multi_reader_rejects_stream_byte_overflow() {
810 let yaml = b"---\nname: a\nversion: '1'\n---\nname: b\nversion: '2'\n";
811 let cfg = ParserConfig::default().max_stream_bytes(yaml.len() - 1);
812 let mut reader = BufReader::new(&yaml[..]);
813
814 let error = from_async_reader_multi_with_config::<_, Pkg>(&mut reader, &cfg)
815 .await
816 .unwrap_err();
817 assert!(error.to_string().contains("max_stream_bytes"));
818 }
819
820 #[test]
821 fn find_doc_boundary_skips_leading_marker() {
822 assert!(find_doc_boundary(b"---\na: 1\n").is_none());
825 let bs = b"---\na: 1\n---\nb: 2\n";
827 let at = find_doc_boundary(bs).unwrap();
828 assert_eq!(at, 9);
830 }
831}