1#![deny(unsafe_code)]
25#![warn(missing_docs)]
26#![warn(clippy::all)]
27#![warn(clippy::pedantic)]
28#![allow(clippy::module_name_repetitions)]
29#![allow(clippy::result_large_err)]
30#![allow(clippy::uninlined_format_args)]
31#![allow(clippy::approx_constant)]
32#![allow(clippy::too_many_lines)]
33#![allow(clippy::unnecessary_wraps)]
34#![allow(clippy::missing_errors_doc)]
35#![allow(clippy::must_use_candidate)]
36#![allow(clippy::return_self_not_must_use)]
37#![allow(clippy::unused_self)]
38#![allow(clippy::only_used_in_recursion)]
39#![allow(clippy::manual_let_else)]
40#![allow(clippy::needless_pass_by_value)]
41#![allow(clippy::map_unwrap_or)]
42#![allow(clippy::redundant_closure_for_method_calls)]
43#![allow(clippy::inefficient_to_string)]
44#![allow(clippy::doc_markdown)]
45#![allow(clippy::match_same_arms)]
46#![allow(clippy::unnecessary_map_or)]
47#![allow(clippy::len_zero)]
48#![allow(clippy::field_reassign_with_default)]
49#![allow(clippy::single_match_else)]
50#![allow(clippy::clone_on_copy)]
51#![allow(clippy::unwrap_or_default)]
52#![allow(clippy::cast_possible_truncation)]
53#![allow(clippy::format_push_string)]
54#![allow(clippy::missing_panics_doc)]
55#![allow(clippy::struct_excessive_bools)]
56#![allow(clippy::cast_sign_loss)]
57#![allow(clippy::explicit_iter_loop)]
58#![allow(clippy::ignored_unit_patterns)]
59#![allow(clippy::no_effect_underscore_binding)]
60#![allow(clippy::collapsible_if)]
61#![allow(clippy::comparison_chain)]
62#![allow(clippy::collapsible_else_if)]
63#![allow(clippy::redundant_pattern_matching)]
64#![allow(clippy::cast_precision_loss)]
65#![allow(dead_code)]
66#![allow(clippy::needless_pass_by_ref_mut)]
67#![allow(clippy::missing_const_for_fn)]
68#![allow(clippy::manual_contains)]
69#![allow(clippy::option_if_let_else)]
70#![allow(clippy::elidable_lifetime_names)]
71#![allow(clippy::derive_partial_eq_without_eq)]
72#![allow(clippy::needless_borrow)]
73#![allow(clippy::cast_possible_wrap)]
74#![allow(clippy::wildcard_imports)]
75#![allow(clippy::or_fun_call)]
76#![allow(clippy::if_not_else)]
77#![allow(clippy::manual_strip)]
78#![allow(clippy::range_plus_one)]
79#![allow(clippy::get_first)]
80#![allow(clippy::use_self)]
81#![allow(clippy::needless_raw_string_hashes)]
82#![allow(unused_mut)]
83#![allow(clippy::single_match)]
84#![allow(clippy::manual_flatten)]
85#![allow(unused_variables)]
86#![allow(clippy::while_let_on_iterator)]
87
88pub mod composer;
89pub mod composer_borrowed;
90pub mod composer_comments;
91pub mod composer_optimized;
92pub mod constructor;
93pub mod emitter;
94pub mod error;
95pub mod limits;
96pub mod parser;
97pub mod position;
98pub mod profiling;
99pub mod representer;
100pub mod resolver;
101pub mod scanner;
102pub mod schema;
103pub mod serializer;
104#[cfg(feature = "async")]
105pub mod streaming_async;
106pub mod streaming_enhanced;
107pub mod tag;
108pub mod value;
109pub mod value_borrowed;
110pub mod version;
111pub mod yaml;
112pub mod zero_copy_value;
113pub mod zerocopy;
114
115pub use error::{Error, Result};
117pub use limits::{Limits, ResourceStats, ResourceTracker};
118pub use position::Position;
119pub use scanner::QuoteStyle;
120pub use schema::{
121 Schema, SchemaRule, SchemaValidator, ValidationError, ValidationResult, ValueType,
122};
123pub use value::{CommentedValue, Comments, IndentStyle, Style, Value};
124pub use value_borrowed::BorrowedValue;
125pub use yaml::{IndentConfig, LoaderType, Yaml, YamlConfig};
126pub use zero_copy_value::OptimizedValue;
127
128pub use composer::{BasicComposer, Composer};
130pub use composer_borrowed::{BorrowedComposer, ZeroCopyComposer};
131pub use composer_comments::CommentPreservingComposer;
132pub use composer_optimized::{OptimizedComposer, ReducedAllocComposer};
133pub use constructor::{
134 CommentPreservingConstructor, Constructor, RoundTripConstructor, SafeConstructor,
135};
136pub use emitter::{BasicEmitter, Emitter};
137pub use parser::{
138 BasicParser, Event, EventType, Parser, StreamingConfig, StreamingParser, StreamingStats,
139};
140pub use representer::{Representer, SafeRepresenter};
141pub use resolver::{BasicResolver, PlainScalarType, Resolver, resolve_plain_scalar};
142pub use scanner::{BasicScanner, Scanner, Token, TokenType};
143pub use serializer::{BasicSerializer, Serializer};
144pub use streaming_enhanced::{
145 StreamConfig, StreamingYamlParser, stream_from_file, stream_from_string,
146};
147pub use version::YamlVersion;
148pub use zerocopy::{ScannerStats, TokenPool, ZeroScanner, ZeroString, ZeroToken, ZeroTokenType};
149#[cfg(feature = "serde")]
152pub mod serde_integration;
153#[cfg(feature = "serde")]
154pub use serde_integration::{from_reader, from_slice, from_str, to_string, to_writer};
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use crate::parser::ScalarStyle;
160
161 #[test]
162 fn test_basic_functionality() {
163 let yaml = Yaml::new();
164 let value = yaml.load_str("42").unwrap();
165 assert_eq!(value, Value::Int(42));
166 }
167
168 #[test]
169 fn test_error_creation() {
170 let pos = Position::new();
171 let error = Error::parse(pos, "test error");
172 assert!(error.to_string().contains("test error"));
173 }
174
175 #[test]
176 fn test_value_types() {
177 assert_eq!(Value::Null, Value::Null);
178 assert_eq!(Value::Bool(true), Value::Bool(true));
179 assert_eq!(Value::Int(42), Value::Int(42));
180 assert_eq!(Value::Float(3.14), Value::Float(3.14));
181 assert_eq!(
182 Value::String("test".to_string()),
183 Value::String("test".to_string())
184 );
185 }
186
187 #[test]
188 fn test_anchor_alias_parsing() {
189 let yaml_with_anchors = r"
190base: &base
191 name: test
192 value: 42
193
194prod: *base
195";
196
197 let mut parser = BasicParser::new_eager(yaml_with_anchors.to_string());
198
199 let mut events = Vec::new();
200 while parser.check_event() {
201 if let Ok(Some(event)) = parser.get_event() {
202 events.push(event);
203 } else {
204 break;
205 }
206 }
207
208 let base_mapping = events.iter().find(|e| {
210 if let EventType::MappingStart { anchor, .. } = &e.event_type {
211 anchor.as_ref().map_or(false, |a| a == "base")
212 } else {
213 false
214 }
215 });
216
217 assert!(
218 base_mapping.is_some(),
219 "Should find mapping with 'base' anchor"
220 );
221
222 let alias_event = events
224 .iter()
225 .find(|e| matches!(e.event_type, EventType::Alias { .. }));
226
227 assert!(alias_event.is_some(), "Should find alias event");
228
229 if let EventType::Alias { anchor } = &alias_event.unwrap().event_type {
230 assert_eq!(anchor, "base", "Alias should reference 'base'");
231 }
232 }
233
234 #[test]
235 fn test_literal_block_scalar() {
236 let yaml_literal = r"literal: |
237 This text contains
238 multiple lines
239 with preserved newlines
240";
241
242 let mut parser = BasicParser::new_eager(yaml_literal.to_string());
243
244 let mut events = Vec::new();
245 while parser.check_event() {
246 if let Ok(Some(event)) = parser.get_event() {
247 events.push(event);
248 } else {
249 break;
250 }
251 }
252
253 let literal_scalar = events.iter().find(|e| {
255 if let EventType::Scalar { value, style, .. } = &e.event_type {
256 *style == ScalarStyle::Literal && value.contains("This text contains")
257 } else {
258 false
259 }
260 });
261
262 assert!(literal_scalar.is_some(), "Should find literal scalar");
263
264 if let EventType::Scalar { value, .. } = &literal_scalar.unwrap().event_type {
265 assert!(
266 value.contains('\n'),
267 "Literal scalar should preserve newlines"
268 );
269 assert!(
270 value.contains("This text contains"),
271 "Should contain the literal text"
272 );
273 }
274 }
275
276 #[test]
277 fn test_folded_block_scalar() {
278 let yaml_folded = r"folded: >
279 This text will be
280 folded into a
281 single line
282";
283
284 let mut parser = BasicParser::new_eager(yaml_folded.to_string());
285
286 let mut events = Vec::new();
287 while parser.check_event() {
288 if let Ok(Some(event)) = parser.get_event() {
289 events.push(event);
290 } else {
291 break;
292 }
293 }
294
295 let folded_scalar = events.iter().find(|e| {
297 if let EventType::Scalar { value, style, .. } = &e.event_type {
298 *style == ScalarStyle::Folded && value.contains("This text will be")
299 } else {
300 false
301 }
302 });
303
304 assert!(folded_scalar.is_some(), "Should find folded scalar");
305
306 if let EventType::Scalar { value, .. } = &folded_scalar.unwrap().event_type {
307 assert!(
311 value.contains("This text will be folded into a single line"),
312 "Should fold the text"
313 );
314 assert!(
315 value.ends_with('\n'),
316 "Clip-mode folded scalar keeps one trailing newline"
317 );
318 assert_eq!(
319 value.matches('\n').count(),
320 1,
321 "Internal line breaks must be folded, only one trailing newline allowed"
322 );
323 }
324 }
325
326 fn parse_scalar_value(yaml: &str) -> String {
327 let mut parser = BasicParser::new_eager(yaml.to_string());
328 let mut events = Vec::new();
329 while parser.check_event() {
330 if let Ok(Some(event)) = parser.get_event() {
331 events.push(event);
332 } else {
333 break;
334 }
335 }
336 for ev in &events {
337 if let EventType::Scalar { value, style, .. } = &ev.event_type {
338 if matches!(style, ScalarStyle::Literal | ScalarStyle::Folded) {
339 return value.clone();
340 }
341 }
342 }
343 panic!("No block scalar found in events");
344 }
345
346 #[test]
347 fn test_literal_clip_default_keeps_single_trailing_newline() {
348 let yaml = "|\n ab\n";
349 assert_eq!(parse_scalar_value(yaml), "ab\n");
350 }
351
352 #[test]
353 fn test_literal_strip_removes_trailing_newlines() {
354 let yaml = "|-\n ab\n";
355 assert_eq!(parse_scalar_value(yaml), "ab");
356 }
357
358 #[test]
359 fn test_literal_keep_preserves_trailing_blank_lines() {
360 let yaml = "|+\n ab\n\n\n";
361 assert_eq!(parse_scalar_value(yaml), "ab\n\n\n");
362 }
363
364 #[test]
365 fn test_literal_strip_removes_multiple_trailing_newlines() {
366 let yaml = "|-\n ab\n\n\n";
367 assert_eq!(parse_scalar_value(yaml), "ab");
368 }
369
370 #[test]
371 fn test_folded_clip_default_keeps_single_trailing_newline() {
372 let yaml = ">\n ab\n cd\n";
373 assert_eq!(parse_scalar_value(yaml), "ab cd\n");
374 }
375
376 #[test]
380 fn test_literal_blank_line_preserves_extra_indent_as_content() {
381 let yaml = "|+\n ab\n\n \n";
384 assert_eq!(parse_scalar_value(yaml), "ab\n\n \n");
385 }
386
387 #[test]
388 fn test_folded_strip_removes_trailing_newlines() {
389 let yaml = ">-\n ab\n";
390 assert_eq!(parse_scalar_value(yaml), "ab");
391 }
392
393 #[test]
394 fn test_folded_preserves_breaks_around_more_indented() {
395 let yaml = ">\n a b\n\n c d\n";
398 assert_eq!(parse_scalar_value(yaml), "a b\n\n c d\n");
401 }
402
403 #[test]
404 fn test_folded_collapses_breaks_between_normal_only() {
405 let yaml = ">\n a\n\n b\n";
407 assert_eq!(parse_scalar_value(yaml), "a\nb\n");
408 }
409
410 #[test]
416 fn test_same_line_scalar_in_value_position_is_value_not_key() {
417 let yaml = "? &a a\n: &b b\n: *a\n";
418 let mut parser = BasicParser::new_eager(yaml.to_string());
419 let mut events = Vec::new();
420 while parser.check_event() {
421 if let Ok(Some(event)) = parser.get_event() {
422 events.push(event);
423 } else {
424 break;
425 }
426 }
427 let entries: Vec<String> = events
428 .iter()
429 .filter_map(|e| match &e.event_type {
430 EventType::Scalar { value, anchor, .. } => {
431 Some(format!("V:{}:{}", anchor.as_deref().unwrap_or(""), value))
432 }
433 EventType::Alias { anchor } => Some(format!("A:{}", anchor)),
434 _ => None,
435 })
436 .collect();
437 assert_eq!(
438 entries,
439 vec![
440 "V:a:a".to_string(),
441 "V:b:b".to_string(),
442 "V::".to_string(),
443 "A:a".to_string(),
444 ],
445 "expected key(a&a)/val(b&b)/empty-key/alias(*a); got {entries:?}"
446 );
447 }
448
449 #[test]
453 fn test_anchor_before_implicit_key_attaches_to_key() {
454 let yaml = "&a a: b\n";
455 let mut parser = BasicParser::new_eager(yaml.to_string());
456 let mut events = Vec::new();
457 while parser.check_event() {
458 if let Ok(Some(event)) = parser.get_event() {
459 events.push(event);
460 } else {
461 break;
462 }
463 }
464 let mapping_anchor = events.iter().find_map(|e| match &e.event_type {
465 EventType::MappingStart { anchor, .. } => Some(anchor.clone()),
466 _ => None,
467 });
468 assert_eq!(
469 mapping_anchor,
470 Some(None),
471 "MappingStart should have no anchor; got {mapping_anchor:?}"
472 );
473 let first_scalar = events.iter().find_map(|e| match &e.event_type {
474 EventType::Scalar { value, anchor, .. } => Some((value.clone(), anchor.clone())),
475 _ => None,
476 });
477 assert_eq!(
478 first_scalar,
479 Some(("a".to_string(), Some("a".to_string()))),
480 "Anchor should be on the key scalar; got {first_scalar:?}"
481 );
482 }
483
484 #[test]
489 fn test_anchor_on_value_mapping_then_anchor_on_inner_key() {
490 let yaml = "top1: &node1\n &k1 key1: one\n";
491 let mut parser = BasicParser::new_eager(yaml.to_string());
492 let mut events = Vec::new();
493 while parser.check_event() {
494 match parser.get_event() {
495 Ok(Some(event)) => events.push(event),
496 Ok(None) => break,
497 Err(e) => panic!("parser error: {e:?}"),
498 }
499 }
500 let mapping_anchors: Vec<Option<String>> = events
502 .iter()
503 .filter_map(|e| match &e.event_type {
504 EventType::MappingStart { anchor, .. } => Some(anchor.clone()),
505 _ => None,
506 })
507 .collect();
508 assert_eq!(
509 mapping_anchors,
510 vec![None, Some("node1".to_string())],
511 "Expected [outer=None, inner=node1]; got {mapping_anchors:?}"
512 );
513 let key1 = events.iter().find_map(|e| match &e.event_type {
515 EventType::Scalar { value, anchor, .. } if value == "key1" => Some(anchor.clone()),
516 _ => None,
517 });
518 assert_eq!(
519 key1,
520 Some(Some("k1".to_string())),
521 "Expected &k1 on key1; got {key1:?}"
522 );
523 }
524
525 #[test]
530 fn test_flow_seq_as_implicit_key_opens_block_mapping() {
531 let yaml = "[flow]: block\n";
532 let mut parser = BasicParser::new_eager(yaml.to_string());
533 let mut events = Vec::new();
534 while parser.check_event() {
535 match parser.get_event() {
536 Ok(Some(event)) => events.push(event),
537 Ok(None) => break,
538 Err(e) => panic!("parser error: {e:?}"),
539 }
540 }
541 let event_kinds: Vec<&str> = events
542 .iter()
543 .map(|e| match &e.event_type {
544 EventType::StreamStart => "+STR",
545 EventType::StreamEnd => "-STR",
546 EventType::DocumentStart { .. } => "+DOC",
547 EventType::DocumentEnd { .. } => "-DOC",
548 EventType::MappingStart {
549 flow_style: false, ..
550 } => "+MAP",
551 EventType::MappingStart {
552 flow_style: true, ..
553 } => "+MAP{}",
554 EventType::MappingEnd => "-MAP",
555 EventType::SequenceStart {
556 flow_style: false, ..
557 } => "+SEQ",
558 EventType::SequenceStart {
559 flow_style: true, ..
560 } => "+SEQ[]",
561 EventType::SequenceEnd => "-SEQ",
562 EventType::Scalar { .. } => "=VAL",
563 EventType::Alias { .. } => "*ALIAS",
564 })
565 .collect();
566 assert_eq!(
567 event_kinds,
568 vec![
569 "+STR", "+DOC", "+MAP", "+SEQ[]", "=VAL", "-SEQ", "=VAL", "-MAP", "-DOC", "-STR"
570 ],
571 "Got: {event_kinds:?}"
572 );
573 }
574
575 #[test]
579 fn test_nested_block_sequence_spans_lines() {
580 let yaml = "- - s1_i1\n - s1_i2\n- s2\n";
581 let mut parser = BasicParser::new_eager(yaml.to_string());
582 let mut events = Vec::new();
583 while parser.check_event() {
584 match parser.get_event() {
585 Ok(Some(event)) => events.push(event),
586 Ok(None) => break,
587 Err(e) => panic!("parser error: {e:?}"),
588 }
589 }
590 let summary: Vec<String> = events
591 .iter()
592 .map(|e| match &e.event_type {
593 EventType::SequenceStart { .. } => "+SEQ".to_string(),
594 EventType::SequenceEnd => "-SEQ".to_string(),
595 EventType::Scalar { value, .. } => format!("=VAL :{value}"),
596 _ => String::new(),
597 })
598 .filter(|s| !s.is_empty())
599 .collect();
600 assert_eq!(
601 summary,
602 vec![
603 "+SEQ",
604 "+SEQ",
605 "=VAL :s1_i1",
606 "=VAL :s1_i2",
607 "-SEQ",
608 "=VAL :s2",
609 "-SEQ",
610 ],
611 "Got: {summary:?}"
612 );
613 }
614
615 #[test]
620 fn test_freestanding_anchor_attaches_to_collection() {
621 let yaml = "---\n&m\n&k a: b\n";
622 let mut parser = BasicParser::new_eager(yaml.to_string());
623 let mut events = Vec::new();
624 while parser.check_event() {
625 match parser.get_event() {
626 Ok(Some(event)) => events.push(event),
627 Ok(None) => break,
628 Err(e) => panic!("parser error: {e:?}"),
629 }
630 }
631 let map_anchor = events.iter().find_map(|e| match &e.event_type {
632 EventType::MappingStart { anchor, .. } => Some(anchor.clone()),
633 _ => None,
634 });
635 assert_eq!(
636 map_anchor,
637 Some(Some("m".to_string())),
638 "expected outer map anchor=Some(m); got {map_anchor:?}"
639 );
640 let key_a = events.iter().find_map(|e| match &e.event_type {
641 EventType::Scalar { value, anchor, .. } if value == "a" => Some(anchor.clone()),
642 _ => None,
643 });
644 assert_eq!(
645 key_a,
646 Some(Some("k".to_string())),
647 "expected key 'a' anchor=Some(k); got {key_a:?}"
648 );
649 }
650
651 #[test]
655 fn test_double_comma_in_flow_seq_errors() {
656 let yaml = "---\n[ a, b, c, , ]\n";
657 let mut parser = BasicParser::new_eager(yaml.to_string());
658 let mut saw_error = false;
659 while parser.check_event() {
660 match parser.get_event() {
661 Ok(Some(_)) => {}
662 Ok(None) => break,
663 Err(_) => {
664 saw_error = true;
665 break;
666 }
667 }
668 }
669 assert!(saw_error, "Expected error on [a, b, c, , ]");
670 }
671
672 #[test]
677 fn test_block_entry_no_item_synthesises_empty_scalar() {
678 let yaml = "-\n";
679 let mut parser = BasicParser::new_eager(yaml.to_string());
680 let mut events = Vec::new();
681 while parser.check_event() {
682 match parser.get_event() {
683 Ok(Some(event)) => events.push(event),
684 Ok(None) => break,
685 Err(e) => panic!("parser error: {e:?}"),
686 }
687 }
688 let scalars: Vec<(String, bool)> = events
689 .iter()
690 .filter_map(|e| match &e.event_type {
691 EventType::Scalar {
692 value,
693 plain_implicit,
694 ..
695 } => Some((value.clone(), *plain_implicit)),
696 _ => None,
697 })
698 .collect();
699 assert_eq!(
700 scalars,
701 vec![(String::new(), true)],
702 "Expected one implicit empty scalar item; got {scalars:?}"
703 );
704 }
705
706 #[test]
710 fn test_mixed_indent_widths_are_legal() {
711 let yaml = "a:\n b: 1\nx:\n y: 2\n";
712 let mut parser = BasicParser::new_eager(yaml.to_string());
713 let mut events = Vec::new();
714 while parser.check_event() {
715 match parser.get_event() {
716 Ok(Some(event)) => events.push(event),
717 Ok(None) => break,
718 Err(e) => panic!("parser error: {e:?}"),
719 }
720 }
721 let scalars: Vec<String> = events
722 .iter()
723 .filter_map(|e| match &e.event_type {
724 EventType::Scalar { value, .. } => Some(value.clone()),
725 _ => None,
726 })
727 .collect();
728 assert_eq!(
729 scalars,
730 vec!["a", "b", "1", "x", "y", "2"],
731 "Got scalars: {scalars:?}"
732 );
733 }
734
735 #[test]
740 fn test_alias_key_inside_anchored_mapping_value() {
741 let yaml = "anc: &a v\nouter: &node3\n *a : scalar3\n";
742 let mut parser = BasicParser::new_eager(yaml.to_string());
743 let mut events = Vec::new();
744 while parser.check_event() {
745 match parser.get_event() {
746 Ok(Some(event)) => events.push(event),
747 Ok(None) => break,
748 Err(e) => panic!("parser error: {e:?}"),
749 }
750 }
751 let mapping_anchors: Vec<Option<String>> = events
752 .iter()
753 .filter_map(|e| match &e.event_type {
754 EventType::MappingStart { anchor, .. } => Some(anchor.clone()),
755 _ => None,
756 })
757 .collect();
758 assert_eq!(
760 mapping_anchors,
761 vec![None, Some("node3".to_string())],
762 "expected [None, Some(node3)]; got {mapping_anchors:?}"
763 );
764 let alias_present = events
766 .iter()
767 .any(|e| matches!(&e.event_type, EventType::Alias { anchor } if anchor == "a"));
768 assert!(alias_present, "Expected Alias *a; events: {events:?}");
769 }
770
771 #[test]
775 fn test_leading_colon_implies_empty_key() {
776 let yaml = ": a\n: b\n";
777 let mut parser = BasicParser::new_eager(yaml.to_string());
778 let mut events = Vec::new();
779 while parser.check_event() {
780 if let Ok(Some(event)) = parser.get_event() {
781 events.push(event);
782 } else {
783 break;
784 }
785 }
786 assert!(
787 events
788 .iter()
789 .any(|e| matches!(e.event_type, EventType::MappingStart { .. })),
790 "Expected MappingStart for leading-colon mapping; events: {events:?}"
791 );
792 let scalars: Vec<String> = events
793 .iter()
794 .filter_map(|e| match &e.event_type {
795 EventType::Scalar { value, .. } => Some(value.clone()),
796 _ => None,
797 })
798 .collect();
799 assert_eq!(
800 scalars,
801 vec![
802 String::new(),
803 "a".to_string(),
804 String::new(),
805 "b".to_string(),
806 ],
807 "Expected empty-key/value pairs; got {scalars:?}"
808 );
809 }
810
811 #[test]
816 fn test_consecutive_complex_keys_emit_empty_values() {
817 let yaml = "? a\n? b\n";
818 let mut parser = BasicParser::new_eager(yaml.to_string());
819 let mut scalars = Vec::new();
820 while parser.check_event() {
821 if let Ok(Some(event)) = parser.get_event() {
822 if let EventType::Scalar { value, .. } = event.event_type {
823 scalars.push(value);
824 }
825 } else {
826 break;
827 }
828 }
829 assert_eq!(
830 scalars,
831 vec![
832 "a".to_string(),
833 String::new(),
834 "b".to_string(),
835 String::new()
836 ],
837 "Expected key/empty/key/empty pattern; got {scalars:?}"
838 );
839 }
840
841 #[test]
846 fn test_plain_scalar_key_may_contain_inner_colons() {
847 let yaml = "ab::cd: value\n";
848 let mut parser = BasicParser::new_eager(yaml.to_string());
849 let mut events = Vec::new();
850 while parser.check_event() {
851 if let Ok(Some(event)) = parser.get_event() {
852 events.push(event);
853 } else {
854 break;
855 }
856 }
857 let has_mapping = events
858 .iter()
859 .any(|e| matches!(e.event_type, EventType::MappingStart { .. }));
860 assert!(
861 has_mapping,
862 "Plain scalar `ab::cd: value` should open a mapping; events were {events:?}"
863 );
864 let scalars: Vec<&str> = events
865 .iter()
866 .filter_map(|e| match &e.event_type {
867 EventType::Scalar { value, .. } => Some(value.as_str()),
868 _ => None,
869 })
870 .collect();
871 assert_eq!(
872 scalars,
873 vec!["ab::cd", "value"],
874 "key/value split incorrect"
875 );
876 }
877
878 #[test]
879 fn test_explicit_type_tags() {
880 let yaml_with_tags = r"
881string_value: !!str 42
882int_value: !!int '123'
883float_value: !!float '3.14'
884bool_value: !!bool 'yes'
885null_value: !!null 'something'
886";
887
888 let mut parser = BasicParser::new_eager(yaml_with_tags.to_string());
889
890 let mut events = Vec::new();
891 while parser.check_event() {
892 if let Ok(Some(event)) = parser.get_event() {
893 events.push(event);
894 } else {
895 break;
896 }
897 }
898
899 let tagged_scalars: Vec<_> = events
901 .iter()
902 .filter_map(|e| {
903 if let EventType::Scalar { value, tag, .. } = &e.event_type {
904 Some((value.as_str(), tag.as_ref()?))
905 } else {
906 None
907 }
908 })
909 .collect();
910
911 assert!(!tagged_scalars.is_empty(), "Should find tagged scalars");
912
913 let str_scalar = tagged_scalars.iter().find(|(value, _)| *value == "42");
915 if let Some((_, tag)) = str_scalar {
916 assert_eq!(
917 *tag, "tag:yaml.org,2002:str",
918 "String tag should be normalized"
919 );
920 }
921
922 let int_scalar = tagged_scalars.iter().find(|(value, _)| *value == "123");
923 if let Some((_, tag)) = int_scalar {
924 assert_eq!(
925 *tag, "tag:yaml.org,2002:int",
926 "Int tag should be normalized"
927 );
928 }
929 }
930
931 #[test]
932 fn test_collection_type_tags() {
933 let yaml_with_collection_tags = r"
934explicit_sequence: !!seq [a, b, c]
935explicit_mapping: !!map {key: value}
936";
937
938 let mut parser = BasicParser::new_eager(yaml_with_collection_tags.to_string());
939
940 let mut events = Vec::new();
941 while parser.check_event() {
942 if let Ok(Some(event)) = parser.get_event() {
943 events.push(event);
944 } else {
945 break;
946 }
947 }
948
949 let tagged_seq = events.iter().find(|e| {
951 if let EventType::SequenceStart { tag, .. } = &e.event_type {
952 tag.as_ref().map_or(false, |t| t == "tag:yaml.org,2002:seq")
953 } else {
954 false
955 }
956 });
957
958 let tagged_map = events.iter().find(|e| {
959 if let EventType::MappingStart { tag, .. } = &e.event_type {
960 tag.as_ref().map_or(false, |t| t == "tag:yaml.org,2002:map")
961 } else {
962 false
963 }
964 });
965
966 assert!(tagged_seq.is_some(), "Should find tagged sequence");
967 assert!(tagged_map.is_some(), "Should find tagged mapping");
968 }
969
970 #[test]
971 fn test_tag_scanner() {
972 let yaml_with_various_tags = "value: !!str hello\nother: !custom tag\nshort: !int 42";
973
974 let mut scanner = BasicScanner::new_eager(yaml_with_various_tags.to_string());
975
976 let mut tag_tokens = Vec::new();
977 while scanner.check_token() {
978 if let Ok(Some(token)) = scanner.get_token() {
979 if let TokenType::Tag(tag) = &token.token_type {
980 tag_tokens.push(tag.clone());
981 }
982 } else {
983 break;
984 }
985 }
986
987 assert!(!tag_tokens.is_empty(), "Should find tag tokens");
988 assert!(
989 tag_tokens.iter().any(|t| t == "!!str"),
990 "Should find !!str tag"
991 );
992 assert!(
993 tag_tokens.iter().any(|t| t == "!custom"),
994 "Should preserve custom tags"
995 );
996 assert!(
997 tag_tokens.iter().any(|t| t == "!int"),
998 "Should find !int tag"
999 );
1000 }
1001
1002 #[test]
1003 fn test_streaming_parser() {
1004 let yaml = r"
1005items:
1006 - name: first
1007 value: 1
1008 - name: second
1009 value: 2
1010";
1011
1012 let mut streaming_parser = BasicParser::new(yaml.to_string());
1014 let mut stream_events = Vec::new();
1015
1016 while streaming_parser.check_event() {
1018 if let Ok(Some(event)) = streaming_parser.get_event() {
1019 stream_events.push(event);
1020 } else {
1021 break;
1022 }
1023 }
1024
1025 let mut eager_parser = BasicParser::new_eager(yaml.to_string());
1027 let mut eager_events = Vec::new();
1028
1029 while eager_parser.check_event() {
1030 if let Ok(Some(event)) = eager_parser.get_event() {
1031 eager_events.push(event);
1032 } else {
1033 break;
1034 }
1035 }
1036
1037 let has_mapping_start = stream_events
1040 .iter()
1041 .any(|e| matches!(e.event_type, EventType::MappingStart { .. }));
1042 let has_scalars = stream_events
1043 .iter()
1044 .any(|e| matches!(e.event_type, EventType::Scalar { .. }));
1045
1046 assert!(
1047 stream_events.len() > 0,
1048 "Streaming parser should generate events"
1049 );
1050 assert!(has_mapping_start, "Should have mapping start events");
1051 assert!(has_scalars, "Should have scalar events");
1052
1053 let eager_has_sequence = eager_events
1055 .iter()
1056 .any(|e| matches!(e.event_type, EventType::SequenceStart { .. }));
1057 assert!(
1058 eager_has_sequence,
1059 "Eager parser should have sequence start events"
1060 );
1061 }
1062
1063 #[test]
1064 fn test_complex_yaml_document() {
1065 let complex_yaml = r"
1066# Configuration for a web service
1067service:
1068 name: my-web-service
1069 version: &version '2.1.0'
1070
1071 # Server configuration
1072 server:
1073 host: localhost
1074 port: 8080
1075 ssl: true
1076
1077 # Database connections
1078 databases:
1079 primary: &primary_db
1080 driver: postgresql
1081 host: db.example.com
1082 port: 5432
1083 name: myapp_prod
1084
1085 cache:
1086 driver: redis
1087 host: cache.example.com
1088 port: 6379
1089
1090 # Feature flags with explicit types
1091 features:
1092 new_ui: !!bool true
1093 max_connections: !!int 100
1094 timeout: !!float 30.5
1095
1096 # Deployment environments
1097 environments:
1098 - name: development
1099 database: *primary_db
1100 debug: true
1101
1102 - name: staging
1103 database: *primary_db
1104 debug: false
1105
1106 - name: production
1107 database: *primary_db
1108 debug: false
1109
1110 # Multi-line configurations
1111 nginx_config: |
1112 server {
1113 listen 80;
1114 server_name example.com;
1115 location / {
1116 proxy_pass http://localhost:8080;
1117 }
1118 }
1119
1120 description: >
1121 This is a long description that will be
1122 folded into a single line when parsed,
1123 making it easier to read in the YAML file.
1124";
1125
1126 let mut parser = BasicParser::new_eager(complex_yaml.to_string());
1127 let mut events = Vec::new();
1128
1129 while parser.check_event() {
1130 if let Ok(Some(event)) = parser.get_event() {
1131 events.push(event);
1132 } else {
1133 break;
1134 }
1135 }
1136
1137 assert!(
1139 events.len() > 20,
1140 "Complex YAML should generate many events"
1141 );
1142
1143 let has_mapping_starts = events
1145 .iter()
1146 .filter(|e| matches!(e.event_type, EventType::MappingStart { .. }))
1147 .count();
1148 let has_sequence_starts = events
1149 .iter()
1150 .filter(|e| matches!(e.event_type, EventType::SequenceStart { .. }))
1151 .count();
1152 let has_scalars = events
1153 .iter()
1154 .filter(|e| matches!(e.event_type, EventType::Scalar { .. }))
1155 .count();
1156 let has_aliases = events
1157 .iter()
1158 .filter(|e| matches!(e.event_type, EventType::Alias { .. }))
1159 .count();
1160
1161 assert!(
1162 has_mapping_starts > 0,
1163 "Should have mapping starts (found: {})",
1164 has_mapping_starts
1165 );
1166 assert!(has_sequence_starts > 0, "Should have sequence starts");
1167 assert!(has_scalars > 10, "Should have many scalars");
1168 assert!(has_aliases > 0, "Should have aliases");
1169
1170 let anchored_scalars = events
1172 .iter()
1173 .filter(|e| {
1174 if let EventType::Scalar { anchor, .. } = &e.event_type {
1175 anchor.is_some()
1176 } else {
1177 false
1178 }
1179 })
1180 .count();
1181 assert!(anchored_scalars > 0, "Should have anchored scalars");
1182
1183 let tagged_scalars = events
1185 .iter()
1186 .filter(|e| {
1187 if let EventType::Scalar { tag, .. } = &e.event_type {
1188 tag.is_some()
1189 } else {
1190 false
1191 }
1192 })
1193 .count();
1194 assert!(tagged_scalars > 0, "Should have tagged scalars");
1195
1196 let literal_scalars = events
1198 .iter()
1199 .filter(|e| {
1200 if let EventType::Scalar { style, .. } = &e.event_type {
1201 matches!(style, parser::ScalarStyle::Literal)
1202 } else {
1203 false
1204 }
1205 })
1206 .count();
1207
1208 let folded_scalars = events
1209 .iter()
1210 .filter(|e| {
1211 if let EventType::Scalar { style, .. } = &e.event_type {
1212 matches!(style, parser::ScalarStyle::Folded)
1213 } else {
1214 false
1215 }
1216 })
1217 .count();
1218
1219 assert!(literal_scalars > 0, "Should have literal block scalars");
1220 assert!(folded_scalars > 0, "Should have folded block scalars");
1221 }
1222
1223 #[test]
1224 fn test_yaml_edge_cases() {
1225 let edge_cases = vec![
1227 ("", "empty document"),
1229 ("# Just a comment\n# Another comment", "comment only"),
1231 ("key: ~\nother: null\nthird:", "null values"),
1233 ("yes: true\nno: false\nmaybe: !!bool yes", "boolean values"),
1235 (
1237 "decimal: 123\noctal: 0o123\nhex: 0x123\nfloat: 1.23e4",
1238 "number formats",
1239 ),
1240 ("empty_list: []\nempty_dict: {}", "empty collections"),
1242 ("a: {b: {c: {d: value}}}", "deep nesting"),
1244 ];
1245
1246 for (yaml_content, description) in edge_cases {
1247 let mut parser = BasicParser::new_eager(yaml_content.to_string());
1248 let mut events = Vec::new();
1249
1250 while parser.check_event() {
1251 if let Ok(Some(event)) = parser.get_event() {
1252 events.push(event);
1253 } else {
1254 break;
1255 }
1256 }
1257
1258 assert!(
1260 events.len() >= 2,
1261 "Failed parsing {}: should have at least stream events",
1262 description
1263 );
1264
1265 let first_event = &events[0];
1266 let last_event = &events[events.len() - 1];
1267
1268 assert!(
1269 matches!(first_event.event_type, EventType::StreamStart),
1270 "Failed {}: should start with StreamStart",
1271 description
1272 );
1273 assert!(
1274 matches!(last_event.event_type, EventType::StreamEnd),
1275 "Failed {}: should end with StreamEnd",
1276 description
1277 );
1278 }
1279 }
1280
1281 #[test]
1282 fn test_round_trip_scalars() {
1283 let yaml = Yaml::new();
1284
1285 let test_values = vec![
1287 Value::Null,
1288 Value::Bool(true),
1289 Value::Bool(false),
1290 Value::Int(42),
1291 Value::String("hello".to_string()),
1292 ];
1293
1294 for original in test_values {
1295 if let Ok(yaml_str) = yaml.dump_str(&original) {
1297 if let Ok(round_trip) = yaml.load_str(&yaml_str) {
1298 assert_eq!(
1299 original, round_trip,
1300 "Round-trip failed for {:?}. YAML: {}",
1301 original, yaml_str
1302 );
1303 }
1304 }
1306 }
1308 }
1309
1310 #[test]
1311 fn test_round_trip_collections() {
1312 let yaml = Yaml::new();
1313
1314 let seq = Value::Sequence(vec![
1316 Value::Int(1),
1317 Value::String("hello".to_string()),
1318 Value::Bool(true),
1319 ]);
1320
1321 let yaml_str = yaml.dump_str(&seq).expect("Failed to serialize sequence");
1322 let round_trip = yaml.load_str(&yaml_str).expect("Failed to parse sequence");
1323 assert_eq!(
1324 seq, round_trip,
1325 "Sequence round-trip failed. YAML: {}",
1326 yaml_str
1327 );
1328
1329 let mut map = indexmap::IndexMap::new();
1331 map.insert(
1332 Value::String("name".to_string()),
1333 Value::String("Alice".to_string()),
1334 );
1335 map.insert(Value::String("age".to_string()), Value::Int(30));
1336 map.insert(Value::String("active".to_string()), Value::Bool(true));
1337 let mapping = Value::Mapping(map);
1338
1339 let yaml_str = yaml
1340 .dump_str(&mapping)
1341 .expect("Failed to serialize mapping");
1342 let round_trip = yaml.load_str(&yaml_str).expect("Failed to parse mapping");
1343 assert_eq!(
1344 mapping, round_trip,
1345 "Mapping round-trip failed. YAML: {}",
1346 yaml_str
1347 );
1348 }
1349
1350 #[test]
1351 fn test_round_trip_nested_structure() {
1352 let yaml = Yaml::new();
1353
1354 let mut inner_map = indexmap::IndexMap::new();
1356 inner_map.insert(Value::String("x".to_string()), Value::Int(10));
1357 inner_map.insert(Value::String("y".to_string()), Value::Int(20));
1358
1359 let seq = Value::Sequence(vec![
1360 Value::String("first".to_string()),
1361 Value::String("second".to_string()),
1362 Value::Mapping(inner_map),
1363 ]);
1364
1365 let mut outer_map = indexmap::IndexMap::new();
1366 outer_map.insert(Value::String("items".to_string()), seq);
1367 outer_map.insert(Value::String("count".to_string()), Value::Int(3));
1368
1369 let original = Value::Mapping(outer_map);
1370
1371 let yaml_str = yaml
1372 .dump_str(&original)
1373 .expect("Failed to serialize nested structure");
1374 let round_trip = yaml
1375 .load_str(&yaml_str)
1376 .expect("Failed to parse nested structure");
1377
1378 assert_eq!(
1379 original, round_trip,
1380 "Nested structure round-trip failed. YAML: {}",
1381 yaml_str
1382 );
1383 }
1384
1385 #[test]
1386 fn test_round_trip_with_special_strings() {
1387 let yaml = Yaml::new();
1388
1389 let special_strings = vec![
1390 "null", "true", "false", "123", "3.14", "yes", "no", "on", "off", "", " spaced ", ];
1402
1403 for s in special_strings {
1404 let original = Value::String(s.to_string());
1405 let yaml_str = yaml
1406 .dump_str(&original)
1407 .expect("Failed to serialize special string");
1408 let round_trip = yaml
1409 .load_str(&yaml_str)
1410 .expect("Failed to parse special string");
1411
1412 assert_eq!(
1413 original, round_trip,
1414 "Special string round-trip failed for '{}'. YAML: {}",
1415 s, yaml_str
1416 );
1417 }
1418 }
1419
1420 #[test]
1421 fn test_round_trip_complex_yaml() {
1422 let yaml = Yaml::new();
1423
1424 let complex_yaml = r"
1426service:
1427 name: my-web-service
1428 version: '2.1.0'
1429 server:
1430 host: localhost
1431 port: 8080
1432 ssl: true
1433 features:
1434 new_ui: true
1435 max_connections: 100
1436 timeout: 30.5
1437";
1438
1439 let parsed = yaml
1441 .load_str(complex_yaml)
1442 .expect("Failed to parse complex YAML");
1443
1444 let serialized = yaml
1446 .dump_str(&parsed)
1447 .expect("Failed to serialize complex structure");
1448
1449 let round_trip = yaml
1451 .load_str(&serialized)
1452 .expect("Failed to parse round-trip");
1453
1454 assert_eq!(parsed, round_trip, "Complex YAML round-trip failed");
1456 }
1457
1458 #[test]
1459 fn test_anchor_alias_serialization() {
1460 let yaml = Yaml::new();
1461
1462 let shared_mapping = {
1464 let mut map = indexmap::IndexMap::new();
1465 map.insert(
1466 Value::String("name".to_string()),
1467 Value::String("shared".to_string()),
1468 );
1469 map.insert(Value::String("value".to_string()), Value::Int(42));
1470 Value::Mapping(map)
1471 };
1472
1473 let mut root_map = indexmap::IndexMap::new();
1475 root_map.insert(Value::String("first".to_string()), shared_mapping.clone());
1476 root_map.insert(Value::String("second".to_string()), shared_mapping.clone());
1477 root_map.insert(Value::String("third".to_string()), shared_mapping);
1478
1479 let root = Value::Mapping(root_map);
1480
1481 let serialized = yaml
1483 .dump_str(&root)
1484 .expect("Failed to serialize shared structure");
1485
1486 println!("Serialized with anchors/aliases:");
1487 println!("{}", serialized);
1488
1489 assert!(
1491 serialized.contains("&anchor"),
1492 "Should contain anchor definition"
1493 );
1494 assert!(
1495 serialized.contains("*anchor"),
1496 "Should contain alias reference"
1497 );
1498
1499 assert!(
1501 serialized.contains("first:") && serialized.contains("&anchor0"),
1502 "Should have anchored first mapping"
1503 );
1504 assert!(
1505 serialized.contains("second:") && serialized.contains("*anchor0"),
1506 "Should have aliased second mapping"
1507 );
1508 assert!(
1509 serialized.contains("third:") && serialized.contains("*anchor0"),
1510 "Should have aliased third mapping"
1511 );
1512 assert!(
1513 serialized.contains("name: shared"),
1514 "Should contain shared content"
1515 );
1516 assert!(
1517 serialized.contains("value: 42"),
1518 "Should contain shared value"
1519 );
1520 }
1521
1522 #[test]
1523 fn test_anchor_alias_with_sequences() {
1524 let yaml = Yaml::new();
1525
1526 let shared_sequence = Value::Sequence(vec![
1528 Value::String("item1".to_string()),
1529 Value::String("item2".to_string()),
1530 Value::Int(123),
1531 ]);
1532
1533 let mut root_map = indexmap::IndexMap::new();
1535 root_map.insert(Value::String("list1".to_string()), shared_sequence.clone());
1536 root_map.insert(Value::String("list2".to_string()), shared_sequence);
1537
1538 let root = Value::Mapping(root_map);
1539
1540 let serialized = yaml
1542 .dump_str(&root)
1543 .expect("Failed to serialize shared sequences");
1544
1545 println!("Serialized sequences with anchors/aliases:");
1546 println!("{}", serialized);
1547
1548 assert!(
1550 serialized.contains("&anchor"),
1551 "Should contain anchor for shared sequence"
1552 );
1553 assert!(
1554 serialized.contains("*anchor"),
1555 "Should contain alias for shared sequence"
1556 );
1557
1558 assert!(
1560 serialized.contains("list1:") && serialized.contains("&anchor0"),
1561 "Should have anchored sequence"
1562 );
1563 assert!(
1564 serialized.contains("list2:") && serialized.contains("*anchor0"),
1565 "Should have aliased sequence"
1566 );
1567 assert!(
1568 serialized.contains("- item1"),
1569 "Should contain sequence items"
1570 );
1571 assert!(
1572 serialized.contains("- 123"),
1573 "Should contain sequence values"
1574 );
1575 }
1576}