1use crate::{lex::SyntaxKind, parse::Parse, PositionedParseError};
4use rowan::ast::AstNode;
5use rowan::GreenNodeBuilder;
6use std::path::Path;
7use std::str::FromStr;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub(crate) struct ParsedYaml {
16 pub(crate) green_node: rowan::GreenNode,
18 pub(crate) errors: Vec<String>,
20 pub(crate) positioned_errors: Vec<PositionedParseError>,
22}
23
24use crate::nodes::{ast_node, has_child_token};
26pub use crate::nodes::{Lang, SyntaxNode};
27
28pub use crate::nodes::{
30 Alias, Comment, Directive, Document, Entry, Mapping, MappingEntry, OccupiedEntry, Scalar,
31 ScalarConversionError, Sequence, TaggedNode, VacantEntry,
32};
33
34ast_node!(
35 YamlFile,
36 ROOT,
37 "A YAML file containing one or more documents"
38);
39
40impl YamlFile {
41 pub fn snapshot(&self) -> Self {
48 YamlFile(SyntaxNode::new_root_mut(self.0.green().into_owned()))
49 }
50
51 pub fn tree_eq(&self, other: &Self) -> bool {
55 let a = self.0.green();
56 let b = other.0.green();
57 let a_ref: &rowan::GreenNodeData = &a;
58 let b_ref: &rowan::GreenNodeData = &b;
59 std::ptr::eq(a_ref as *const _, b_ref as *const _) || a_ref == b_ref
60 }
61}
62
63pub trait ValueNode: rowan::ast::AstNode<Language = Lang> {
65 fn is_inline(&self) -> bool;
67}
68
69impl ValueNode for Mapping {
70 fn is_inline(&self) -> bool {
71 has_child_token(&self.0, |k| {
73 matches!(k, SyntaxKind::LEFT_BRACE | SyntaxKind::RIGHT_BRACE)
74 })
75 }
76}
77
78impl ValueNode for Sequence {
79 fn is_inline(&self) -> bool {
80 has_child_token(&self.0, |k| {
82 matches!(k, SyntaxKind::LEFT_BRACKET | SyntaxKind::RIGHT_BRACKET)
83 })
84 }
85}
86
87impl ValueNode for Scalar {
88 fn is_inline(&self) -> bool {
89 !self
94 .0
95 .children_with_tokens()
96 .any(|c| matches!(c.kind(), SyntaxKind::PIPE | SyntaxKind::GREATER))
97 }
98}
99
100impl ValueNode for TaggedNode {
101 fn is_inline(&self) -> bool {
102 let mut seen_newline = false;
106 for child in self.0.children_with_tokens() {
107 match child {
108 rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::NEWLINE => {
109 seen_newline = true;
110 }
111 rowan::NodeOrToken::Node(n) => {
112 if seen_newline {
113 return false;
114 }
115 return match n.kind() {
116 SyntaxKind::MAPPING => Mapping::cast(n).is_some_and(|m| m.is_inline()),
117 SyntaxKind::SEQUENCE => Sequence::cast(n).is_some_and(|s| s.is_inline()),
118 SyntaxKind::SCALAR => Scalar::cast(n).is_some_and(|s| s.is_inline()),
119 _ => true,
120 };
121 }
122 _ => {}
123 }
124 }
125 true
126 }
127}
128
129pub(crate) fn ends_with_newline(node: &SyntaxNode) -> bool {
133 node.last_token()
134 .is_some_and(|t| t.kind() == SyntaxKind::NEWLINE)
135}
136
137pub(crate) fn detach_empty_collection_placeholder_newline(
144 empty_collection: &SyntaxNode,
145 new_entry: &SyntaxNode,
146) {
147 if !ends_with_newline(new_entry) {
148 return;
149 }
150 let Some(parent_value) = empty_collection.parent() else {
151 return;
152 };
153 if parent_value.kind() != SyntaxKind::VALUE {
154 return;
155 }
156 let Some(parent_entry) = parent_value.parent() else {
157 return;
158 };
159 if parent_entry.kind() != SyntaxKind::MAPPING_ENTRY {
160 return;
161 }
162 let Some(last) = parent_entry.last_child_or_token() else {
163 return;
164 };
165 if last
166 .as_token()
167 .is_some_and(|t| t.kind() == SyntaxKind::NEWLINE)
168 {
169 last.detach();
170 }
171}
172
173pub(crate) fn convert_placeholder_to_block(collection: &SyntaxNode) {
180 let children: Vec<_> = collection.children_with_tokens().collect();
181 for child in children {
182 child.detach();
183 }
184
185 let Some(parent) = collection
186 .parent()
187 .filter(|p| p.kind() == SyntaxKind::VALUE)
188 else {
189 return;
190 };
191 let entry = parent
192 .parent()
193 .filter(|e| e.kind() == SyntaxKind::MAPPING_ENTRY);
194 let indent_width = entry
195 .as_ref()
196 .and_then(|e| e.parent())
197 .filter(|m| m.kind() == SyntaxKind::MAPPING)
198 .and_then(crate::nodes::Mapping::cast)
199 .map_or(2, |m| m.detect_indentation_level() + 2);
200
201 if let Some(entry) = entry {
205 let sep = entry
206 .children_with_tokens()
207 .position(|c| c.as_node() == Some(&parent))
208 .filter(|&i| i >= 1)
209 .and_then(|i| entry.children_with_tokens().nth(i - 1))
210 .filter(|c| {
211 c.as_token()
212 .is_some_and(|t| t.kind() == SyntaxKind::WHITESPACE)
213 });
214 if let Some(sep) = sep {
215 sep.detach();
216 }
217 }
218
219 let pos = parent
220 .children_with_tokens()
221 .position(|c| c.as_node() == Some(collection))
222 .unwrap_or(0);
223 let already_scaffolded = pos >= 1
224 && parent
225 .children_with_tokens()
226 .nth(pos - 1)
227 .and_then(|c| c.into_token())
228 .is_some_and(|t| matches!(t.kind(), SyntaxKind::INDENT | SyntaxKind::NEWLINE));
229 if already_scaffolded {
230 return;
231 }
232 let nl = crate::nodes::fresh_token(SyntaxKind::NEWLINE, "\n");
233 let indent = crate::nodes::fresh_token(SyntaxKind::INDENT, &" ".repeat(indent_width));
234 parent.splice_children(pos..pos, vec![nl.into(), indent.into()]);
235}
236
237pub(crate) fn collapse_empty_child_collection_in_parent(collection: &SyntaxNode) {
247 let (open_kind, close_kind, open_txt, close_txt) = match collection.kind() {
248 SyntaxKind::MAPPING => (SyntaxKind::LEFT_BRACE, SyntaxKind::RIGHT_BRACE, "{", "}"),
249 SyntaxKind::SEQUENCE => (
250 SyntaxKind::LEFT_BRACKET,
251 SyntaxKind::RIGHT_BRACKET,
252 "[",
253 "]",
254 ),
255 _ => return,
256 };
257
258 let is_flow = has_child_token(collection, |k| k == open_kind);
260 if is_flow {
261 return;
262 }
263
264 let Some(host) = collection
267 .parent()
268 .filter(|p| matches!(p.kind(), SyntaxKind::VALUE | SyntaxKind::TAGGED_NODE))
269 else {
270 return;
271 };
272 let value_node = if host.kind() == SyntaxKind::TAGGED_NODE {
273 host.parent()
274 } else {
275 Some(host.clone())
276 };
277 let Some(value_node) = value_node.filter(|v| v.kind() == SyntaxKind::VALUE) else {
278 return;
279 };
280 let Some(entry_node) = value_node
281 .parent()
282 .filter(|p| p.kind() == SyntaxKind::MAPPING_ENTRY)
283 else {
284 return;
285 };
286
287 let has_other = host.children_with_tokens().any(|el| match el {
290 rowan::NodeOrToken::Node(n) => &n != collection,
291 rowan::NodeOrToken::Token(t) => !matches!(
292 t.kind(),
293 SyntaxKind::NEWLINE | SyntaxKind::INDENT | SyntaxKind::WHITESPACE | SyntaxKind::TAG
294 ),
295 });
296 if has_other {
297 return;
298 }
299
300 let tag_token = host
307 .children_with_tokens()
308 .find_map(|el| el.into_token().filter(|t| t.kind() == SyntaxKind::TAG));
309 let host_children: Vec<_> = host.children_with_tokens().collect();
310 for c in &host_children {
311 c.detach();
312 }
313 let ws = crate::nodes::fresh_token(SyntaxKind::WHITESPACE, " ");
314 let mut inner_builder = rowan::GreenNodeBuilder::new();
315 inner_builder.start_node(collection.kind().into());
316 inner_builder.token(open_kind.into(), open_txt);
317 inner_builder.token(close_kind.into(), close_txt);
318 inner_builder.finish_node();
319 let new_collection = SyntaxNode::new_root_mut(inner_builder.finish());
320 let mut replacement: Vec<rowan::NodeOrToken<SyntaxNode, rowan::SyntaxToken<Lang>>> = Vec::new();
321 let tagged = tag_token.is_some();
322 if let Some(tag) = tag_token {
323 replacement.push(crate::nodes::fresh_token(SyntaxKind::TAG, tag.text()).into());
324 replacement.push(ws.clone().into());
325 }
326 replacement.push(new_collection.into());
327 host.splice_children(0..0, replacement);
328
329 if !tagged {
335 let value_index = entry_node
336 .children_with_tokens()
337 .position(|el| el.as_node() == Some(&value_node));
338 if let Some(index) = value_index {
339 let separated = index
340 .checked_sub(1)
341 .and_then(|p| entry_node.children_with_tokens().nth(p))
342 .is_some_and(|el| {
343 el.as_token()
344 .is_some_and(|t| t.kind() == SyntaxKind::WHITESPACE)
345 });
346 if !separated {
347 entry_node.splice_children(index..index, vec![ws.into()]);
348 }
349 }
350 }
351
352 crate::nodes::ensure_trailing_newline(&entry_node);
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, Hash)]
360pub struct Set(SyntaxNode);
361
362impl Set {
363 pub fn cast(node: SyntaxNode) -> Option<Self> {
365 if node.kind() == SyntaxKind::TAGGED_NODE {
366 if let Some(tagged_node) = TaggedNode::cast(node.clone()) {
367 if tagged_node.tag().as_deref() == Some("!!set") {
368 return Some(Set(node));
369 }
370 }
371 }
372 None
373 }
374
375 fn inner_mapping(&self) -> Option<Mapping> {
377 self.0.children().find_map(Mapping::cast)
378 }
379
380 pub fn members(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
393 self.inner_mapping().into_iter().flat_map(|m| {
407 let mapping_node = m.syntax().clone();
408 let has_entries = mapping_node
409 .children()
410 .any(|n| n.kind() == SyntaxKind::MAPPING_ENTRY);
411
412 if has_entries {
413 mapping_node
415 .children()
416 .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
417 .filter_map(|entry| {
418 entry
419 .children()
420 .find(|n| n.kind() == SyntaxKind::KEY)
421 .and_then(|key| key.children().next())
422 .and_then(crate::as_yaml::YamlNode::from_syntax)
423 })
424 .collect::<Vec<_>>()
425 } else {
426 mapping_node
428 .children()
429 .filter(|n| n.kind() == SyntaxKind::KEY)
430 .filter_map(|key| {
431 key.children()
432 .next()
433 .and_then(crate::as_yaml::YamlNode::from_syntax)
434 })
435 .collect::<Vec<_>>()
436 }
437 })
438 }
439
440 pub fn len(&self) -> usize {
442 self.members().count()
443 }
444
445 pub fn is_empty(&self) -> bool {
447 self.members().next().is_none()
448 }
449
450 pub fn contains(&self, value: impl crate::AsYaml) -> bool {
452 self.members()
453 .any(|member| crate::as_yaml::yaml_eq(&member, &value))
454 }
455}
456
457fn extract_content_node(wrapper: &SyntaxNode) -> Option<SyntaxNode> {
461 use crate::lex::SyntaxKind;
462 match wrapper.kind() {
463 SyntaxKind::VALUE | SyntaxKind::KEY => wrapper.children().next(),
464 _ => Some(wrapper.clone()),
465 }
466}
467
468fn smart_cast<T: AstNode<Language = Lang>>(node: SyntaxNode) -> Option<T> {
470 extract_content_node(&node).and_then(T::cast)
471}
472
473pub(crate) fn extract_scalar(node: &SyntaxNode) -> Option<Scalar> {
475 smart_cast(node.clone())
476}
477
478pub(crate) fn extract_mapping(node: &SyntaxNode) -> Option<Mapping> {
480 smart_cast(node.clone())
481}
482
483pub(crate) fn extract_sequence(node: &SyntaxNode) -> Option<Sequence> {
485 smart_cast(node.clone())
486}
487
488pub(crate) fn extract_tagged_node(node: &SyntaxNode) -> Option<TaggedNode> {
490 smart_cast(node.clone())
491}
492
493pub(crate) fn copy_node_to_builder(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
495 builder.start_node(node.kind().into());
496 add_node_children_to(builder, node);
497 builder.finish_node();
498}
499
500pub(crate) fn add_node_children_to(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
502 for child in node.children_with_tokens() {
503 match child {
504 rowan::NodeOrToken::Node(child_node) => {
505 builder.start_node(child_node.kind().into());
506 add_node_children_to(builder, &child_node);
507 builder.finish_node();
508 }
509 rowan::NodeOrToken::Token(token) => {
510 builder.token(token.kind().into(), token.text());
511 }
512 }
513 }
514}
515
516pub(crate) fn dump_cst_to_string(node: &SyntaxNode, indent: usize) -> String {
518 let mut result = String::new();
519 let indent_str = " ".repeat(indent);
520
521 for child in node.children_with_tokens() {
522 match child {
523 rowan::NodeOrToken::Node(n) => {
524 result.push_str(&format!("{}{:?}\n", indent_str, n.kind()));
525 result.push_str(&dump_cst_to_string(&n, indent + 1));
526 }
527 rowan::NodeOrToken::Token(t) => {
528 result.push_str(&format!("{} {:?} {:?}\n", indent_str, t.kind(), t.text()));
529 }
530 }
531 }
532 result
533}
534
535impl Default for YamlFile {
536 fn default() -> Self {
537 Self::new()
538 }
539}
540
541impl YamlFile {
542 pub fn new() -> YamlFile {
544 let mut builder = GreenNodeBuilder::new();
545 builder.start_node(SyntaxKind::ROOT.into());
546 builder.finish_node();
547 YamlFile(SyntaxNode::new_root_mut(builder.finish()))
548 }
549
550 pub fn parse(text: &str) -> Parse<YamlFile> {
552 Parse::parse_yaml(text)
553 }
554
555 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<YamlFile, crate::YamlError> {
557 let contents = std::fs::read_to_string(path)?;
558 Self::from_str(&contents)
559 }
560
561 pub fn from_str_relaxed(s: &str) -> (YamlFile, Vec<String>) {
577 let parsed = YamlFile::parse(s);
578 let errors = parsed.errors();
579 (parsed.tree(), errors)
580 }
581
582 pub fn from_path_relaxed<P: AsRef<Path>>(
587 path: P,
588 ) -> Result<(YamlFile, Vec<String>), std::io::Error> {
589 let contents = std::fs::read_to_string(path)?;
590 Ok(Self::from_str_relaxed(&contents))
591 }
592
593 pub fn read_relaxed<R: std::io::Read>(
598 mut r: R,
599 ) -> Result<(YamlFile, Vec<String>), std::io::Error> {
600 let mut buf = String::new();
601 r.read_to_string(&mut buf)?;
602 Ok(Self::from_str_relaxed(&buf))
603 }
604
605 pub fn documents(&self) -> impl Iterator<Item = Document> {
607 self.0.children().filter_map(Document::cast)
608 }
609
610 pub fn comments(&self) -> impl Iterator<Item = Comment> {
625 crate::nodes::comment::comments(&self.0)
626 }
627
628 pub fn document(&self) -> Option<Document> {
633 self.documents().next()
634 }
635
636 pub fn ensure_document(&self) -> Document {
640 if self.documents().next().is_none() {
641 let doc = Document::new_mapping();
643 self.push_document(doc);
644 }
645 self.documents()
646 .next()
647 .expect("Document should exist after ensuring")
648 }
649
650 pub fn directives(&self) -> impl Iterator<Item = Directive> {
652 self.0.children().filter_map(Directive::cast)
653 }
654
655 pub fn add_directive(&self, directive_text: &str) {
664 let mut builder = GreenNodeBuilder::new();
666 builder.start_node(SyntaxKind::DIRECTIVE.into());
667 builder.token(SyntaxKind::DIRECTIVE.into(), directive_text);
668 builder.finish_node();
669 let directive_node = SyntaxNode::new_root_mut(builder.finish());
670
671 self.0.splice_children(0..0, vec![directive_node.into()]);
673 }
674
675 pub fn push_document(&self, document: Document) {
677 crate::nodes::append_children(&self.0, vec![document.0.into()]);
678 }
679
680 pub fn set(&self, key: impl crate::AsYaml, value: impl crate::AsYaml) {
689 if let Some(doc) = self.document() {
690 doc.set(key, value);
691 }
692 }
693
694 pub fn insert_after(
703 &self,
704 after_key: impl crate::AsYaml,
705 key: impl crate::AsYaml,
706 value: impl crate::AsYaml,
707 ) -> bool {
708 self.document()
709 .is_some_and(|doc| doc.insert_after(after_key, key, value))
710 }
711
712 pub fn insert_before(
721 &self,
722 before_key: impl crate::AsYaml,
723 key: impl crate::AsYaml,
724 value: impl crate::AsYaml,
725 ) -> bool {
726 self.document()
727 .is_some_and(|doc| doc.insert_before(before_key, key, value))
728 }
729
730 pub fn move_after(
741 &self,
742 after_key: impl crate::AsYaml,
743 key: impl crate::AsYaml,
744 value: impl crate::AsYaml,
745 ) -> bool {
746 self.document()
747 .is_some_and(|doc| doc.move_after(after_key, key, value))
748 }
749
750 pub fn move_before(
761 &self,
762 before_key: impl crate::AsYaml,
763 key: impl crate::AsYaml,
764 value: impl crate::AsYaml,
765 ) -> bool {
766 self.document()
767 .is_some_and(|doc| doc.move_before(before_key, key, value))
768 }
769
770 pub fn insert_at_index(
780 &self,
781 index: usize,
782 key: impl crate::AsYaml,
783 value: impl crate::AsYaml,
784 ) {
785 if let Some(doc) = self.document() {
786 doc.insert_at_index(index, key, value);
787 } else {
789 let mut builder = GreenNodeBuilder::new();
792 builder.start_node(SyntaxKind::DOCUMENT.into());
793 builder.start_node(SyntaxKind::MAPPING.into());
794 builder.finish_node(); builder.finish_node(); let doc = Document(SyntaxNode::new_root_mut(builder.finish()));
797
798 self.0.splice_children(0..0, vec![doc.0.into()]);
800
801 if let Some(doc) = self.document() {
803 doc.insert_at_index(index, key, value);
804 }
805 }
806 }
807}
808
809impl FromStr for YamlFile {
810 type Err = crate::YamlError;
811
812 fn from_str(s: &str) -> Result<Self, Self::Err> {
813 let parsed = YamlFile::parse(s);
814 if !parsed.positioned_errors().is_empty() {
815 let first = &parsed.positioned_errors()[0];
816 let lc = crate::byte_offset_to_line_column(s, first.range.start as usize);
817 return Err(crate::YamlError::Parse {
818 message: first.message.clone(),
819 line: Some(lc.line),
820 column: Some(lc.column),
821 });
822 }
823 Ok(parsed.tree())
824 }
825}
826
827pub(crate) use crate::parser::parse;
828
829#[cfg(test)]
832mod tests {
833 use super::*;
834 use crate::builder::{MappingBuilder, SequenceBuilder};
835 use crate::scalar::ScalarValue;
836 use crate::value::YamlValue; #[test]
839 fn test_simple_mapping() {
840 let yaml = "key: value";
841 let parsed = YamlFile::from_str(yaml).unwrap();
842 let doc = parsed.document().unwrap();
843 let mapping = doc.as_mapping().unwrap();
844
845 assert_eq!(parsed.to_string().trim(), "key: value");
847
848 let value = mapping.get("key");
850 assert!(value.is_some());
851 }
852
853 #[test]
854 fn test_simple_sequence() {
855 let yaml = "- item1\n- item2";
856 let parsed = YamlFile::from_str(yaml);
857 assert!(parsed.is_ok());
858 }
859
860 #[test]
861 fn test_complex_yaml() {
862 let yaml = r#"
863name: my-app
864version: 1.0.0
865dependencies:
866 - serde
867 - tokio
868config:
869 port: 8080
870 enabled: true
871"#;
872 let parsed = YamlFile::from_str(yaml).unwrap();
873 assert_eq!(parsed.documents().count(), 1);
874
875 let doc = parsed.document().unwrap();
876 assert!(doc.as_mapping().is_some());
877 }
878
879 #[test]
880 fn test_multiple_documents() {
881 let yaml = r#"---
882doc: first
883---
884doc: second
885...
886"#;
887 let parsed = YamlFile::from_str(yaml).unwrap();
888 assert_eq!(parsed.documents().count(), 2);
889 }
890
891 #[test]
892 fn test_flow_styles() {
893 let yaml = r#"
894array: [1, 2, 3]
895object: {key: value, another: 42}
896"#;
897 let parsed = YamlFile::from_str(yaml).unwrap();
898 assert!(parsed.document().is_some());
899 }
900
901 #[test]
902 fn test_scalar_types_parsing() {
903 let yaml = r#"
904string: hello
905integer: 42
906float: 3.14
907bool_true: true
908bool_false: false
909null_value: null
910tilde: ~
911"#;
912 let parsed = YamlFile::from_str(yaml).unwrap();
913 let doc = parsed.document().unwrap();
914 let mapping = doc.as_mapping().unwrap();
915
916 assert!(mapping.get("string").is_some());
918 assert!(mapping.get("integer").is_some());
919 assert!(mapping.get("float").is_some());
920 assert!(mapping.get("bool_true").is_some());
921 assert!(mapping.get("bool_false").is_some());
922 assert!(mapping.get("null_value").is_some());
923 assert!(mapping.get("tilde").is_some());
924 }
925
926 #[test]
927 fn test_preserve_formatting() {
928 let yaml = r#"# Comment at start
929key: value # inline comment
930
931# Another comment
932list:
933 - item1
934 - item2
935"#;
936 let parsed = YamlFile::from_str(yaml).unwrap();
937
938 let doc = parsed.document().unwrap();
939 let mapping = doc.as_mapping().unwrap();
940 assert_eq!(
941 mapping.get("key").unwrap().as_scalar().unwrap().as_string(),
942 "value"
943 );
944 let list = mapping.get_sequence("list").unwrap();
945 assert_eq!(list.len(), 2);
946 let items: Vec<String> = list
948 .values()
949 .map(|v| v.to_string().trim().to_string())
950 .collect();
951 assert_eq!(items, vec!["item1", "item2"]);
952
953 let output = parsed.to_string();
955 assert_eq!(output, yaml);
956 }
957
958 #[test]
959 fn test_quoted_strings() {
960 let yaml = r#"
961single: 'single quoted'
962double: "double quoted"
963plain: unquoted
964"#;
965 let parsed = YamlFile::from_str(yaml).unwrap();
966 let doc = parsed.document().unwrap();
967 let mapping = doc.as_mapping().unwrap();
968
969 assert!(mapping.get("single").is_some());
970 assert!(mapping.get("double").is_some());
971 assert!(mapping.get("plain").is_some());
972 }
973
974 #[test]
975 fn test_nested_structures() {
976 let yaml = r#"
977root:
978 nested:
979 deeply:
980 value: 42
981 list:
982 - item1
983 - item2
984"#;
985 let parsed = YamlFile::from_str(yaml).unwrap();
986 assert!(parsed.document().is_some());
987 }
988
989 #[test]
990 fn test_empty_values() {
991 let yaml = r#"
992empty_string: ""
993empty_after_colon:
994another_key: value
995"#;
996 let parsed = YamlFile::from_str(yaml).unwrap();
997 let doc = parsed.document().unwrap();
998 let mapping = doc.as_mapping().unwrap();
999
1000 assert!(mapping.get("empty_string").is_some());
1001 assert!(mapping.get("another_key").is_some());
1002 }
1003
1004 #[test]
1005 fn test_special_characters() {
1006 let yaml = r#"
1007special: "line1\nline2"
1008unicode: "emoji 😀"
1009escaped: 'it\'s escaped'
1010"#;
1011 let result = YamlFile::from_str(yaml);
1012 assert!(result.is_ok());
1014 }
1015
1016 #[test]
1019 fn test_error_handling() {
1020 let yaml = "key: value\n invalid indentation for key";
1022 let result = YamlFile::from_str(yaml);
1023 let _ = result;
1025 }
1026
1027 #[test]
1030 fn test_anchor_exact_output() {
1031 let yaml = "key: &anchor value\nref: *anchor";
1032 let parsed = YamlFile::from_str(yaml).unwrap();
1033 let output = parsed.to_string();
1034
1035 assert_eq!(output, "key: &anchor value\nref: *anchor");
1037 }
1038
1039 #[test]
1040 fn test_anchor_with_different_value_types() {
1041 let yaml = r#"string_anchor: &str_val "hello"
1042int_anchor: &int_val 42
1043bool_anchor: &bool_val true
1044null_anchor: &null_val null
1045str_ref: *str_val
1046int_ref: *int_val
1047bool_ref: *bool_val
1048null_ref: *null_val"#;
1049
1050 let parsed = YamlFile::from_str(yaml);
1051 assert!(
1052 parsed.is_ok(),
1053 "Should parse anchors with different value types"
1054 );
1055
1056 let yaml_doc = parsed.unwrap();
1057
1058 let doc = yaml_doc.document().unwrap();
1059 let mapping = doc.as_mapping().unwrap();
1060
1061 assert_eq!(
1063 mapping
1064 .get("string_anchor")
1065 .unwrap()
1066 .as_scalar()
1067 .unwrap()
1068 .as_string(),
1069 "hello"
1070 );
1071 assert_eq!(mapping.get("int_anchor").unwrap().to_i64(), Some(42));
1072 assert_eq!(mapping.get("bool_anchor").unwrap().to_bool(), Some(true));
1073 assert!(mapping.get("null_anchor").unwrap().as_scalar().is_some());
1074
1075 let str_ref = mapping.get("str_ref").unwrap();
1077 assert!(str_ref.is_alias());
1078 assert_eq!(str_ref.as_alias().unwrap().name(), "str_val");
1079
1080 let int_ref = mapping.get("int_ref").unwrap();
1081 assert!(int_ref.is_alias());
1082 assert_eq!(int_ref.as_alias().unwrap().name(), "int_val");
1083
1084 let bool_ref = mapping.get("bool_ref").unwrap();
1085 assert!(bool_ref.is_alias());
1086 assert_eq!(bool_ref.as_alias().unwrap().name(), "bool_val");
1087
1088 let null_ref = mapping.get("null_ref").unwrap();
1089 assert!(null_ref.is_alias());
1090 assert_eq!(null_ref.as_alias().unwrap().name(), "null_val");
1091
1092 let output = yaml_doc.to_string();
1094 assert_eq!(output, yaml);
1095 }
1096
1097 #[test]
1098 fn test_undefined_alias_parses_successfully() {
1099 let yaml = "key: *undefined";
1100 let parse_result = Parse::parse_yaml(yaml);
1101
1102 assert!(
1105 !parse_result.has_errors(),
1106 "Parser should not validate undefined aliases"
1107 );
1108
1109 let output = parse_result.tree().to_string();
1111 assert_eq!(output.trim(), "key: *undefined");
1112 }
1113
1114 #[test]
1115 fn test_anchor_names_with_alphanumeric_chars() {
1116 let yaml1 = "key1: &anchor_123 val1\nref1: *anchor_123";
1118 let parsed1 = YamlFile::from_str(yaml1);
1119 assert!(
1120 parsed1.is_ok(),
1121 "Should parse anchors with underscores and numbers"
1122 );
1123
1124 let file1 = parsed1.unwrap();
1125 let doc1 = file1.document().unwrap();
1126 let map1 = doc1.as_mapping().unwrap();
1127 assert_eq!(
1128 map1.get("key1").unwrap().as_scalar().unwrap().as_string(),
1129 "val1"
1130 );
1131 assert!(map1.get("ref1").unwrap().is_alias());
1132 assert_eq!(
1133 map1.get("ref1").unwrap().as_alias().unwrap().name(),
1134 "anchor_123"
1135 );
1136 assert_eq!(file1.to_string(), yaml1);
1137
1138 let yaml2 = "key2: &AnchorName val2\nref2: *AnchorName";
1139 let parsed2 = YamlFile::from_str(yaml2);
1140 assert!(parsed2.is_ok(), "Should parse anchors with mixed case");
1141
1142 let file2 = parsed2.unwrap();
1143 let doc2 = file2.document().unwrap();
1144 let map2 = doc2.as_mapping().unwrap();
1145 assert_eq!(
1146 map2.get("key2").unwrap().as_scalar().unwrap().as_string(),
1147 "val2"
1148 );
1149 assert!(map2.get("ref2").unwrap().is_alias());
1150 assert_eq!(
1151 map2.get("ref2").unwrap().as_alias().unwrap().name(),
1152 "AnchorName"
1153 );
1154 assert_eq!(file2.to_string(), yaml2);
1155
1156 let yaml3 = "key3: &anchor123abc val3\nref3: *anchor123abc";
1157 let parsed3 = YamlFile::from_str(yaml3);
1158 assert!(
1159 parsed3.is_ok(),
1160 "Should parse anchors with letters and numbers"
1161 );
1162
1163 let file3 = parsed3.unwrap();
1164 let doc3 = file3.document().unwrap();
1165 let map3 = doc3.as_mapping().unwrap();
1166 assert_eq!(
1167 map3.get("key3").unwrap().as_scalar().unwrap().as_string(),
1168 "val3"
1169 );
1170 assert!(map3.get("ref3").unwrap().is_alias());
1171 assert_eq!(
1172 map3.get("ref3").unwrap().as_alias().unwrap().name(),
1173 "anchor123abc"
1174 );
1175 assert_eq!(file3.to_string(), yaml3);
1176 }
1177
1178 #[test]
1179 fn test_anchor_in_sequence_detailed() {
1180 let yaml = r#"items:
1181 - &first_item value1
1182 - second_item
1183 - *first_item"#;
1184
1185 let parsed = YamlFile::from_str(yaml);
1186 assert!(parsed.is_ok(), "Should parse anchors in sequences");
1187
1188 let yaml_doc = parsed.unwrap();
1189
1190 let doc = yaml_doc.document().unwrap();
1191 let mapping = doc.as_mapping().unwrap();
1192 let seq = mapping.get_sequence("items").unwrap();
1193 assert_eq!(seq.len(), 3);
1194
1195 let item0 = seq.get(0).unwrap();
1197 let item1 = seq.get(1).unwrap();
1198 let item2 = seq.get(2).unwrap();
1199
1200 assert_eq!(item0.as_scalar().unwrap().as_string(), "value1");
1202
1203 assert_eq!(item1.as_scalar().unwrap().as_string(), "second_item");
1205
1206 assert!(item2.is_alias(), "Third item should be an alias");
1208 assert_eq!(item2.as_alias().unwrap().name(), "first_item");
1209
1210 let output = yaml_doc.to_string();
1211 assert_eq!(output, yaml);
1212 }
1213
1214 #[test]
1215 fn test_preserve_whitespace_around_anchors() {
1216 let yaml = "key: &anchor value \nref: *anchor ";
1217 let parsed = YamlFile::from_str(yaml).unwrap();
1218
1219 let doc = parsed.document().unwrap();
1220 let mapping = doc.as_mapping().unwrap();
1221 assert_eq!(
1222 mapping
1223 .get("key")
1224 .unwrap()
1225 .as_scalar()
1226 .unwrap()
1227 .as_string()
1228 .trim(),
1229 "value"
1230 );
1231 let ref_node = mapping.get("ref").unwrap();
1232 assert!(ref_node.is_alias());
1233 assert_eq!(ref_node.as_alias().unwrap().name(), "anchor");
1234
1235 let output = parsed.to_string();
1237 assert_eq!(output, yaml);
1238 }
1239
1240 #[test]
1241 fn test_literal_block_scalar_basic() {
1242 let yaml = r#"literal: |
1243 Line 1
1244 Line 2
1245 Line 3
1246"#;
1247 let parsed = YamlFile::from_str(yaml);
1248 assert!(parsed.is_ok(), "Should parse basic literal block scalar");
1249
1250 let yaml_doc = parsed.unwrap();
1251 let output = yaml_doc.to_string();
1252
1253 assert_eq!(output, yaml);
1255 }
1256
1257 #[test]
1258 fn test_folded_block_scalar_basic() {
1259 let yaml = r#"folded: >
1260 This is a very long line that will be folded
1261 into a single line in the output
1262 but preserves paragraph breaks.
1263
1264 This is a new paragraph.
1265"#;
1266 let parsed = YamlFile::from_str(yaml);
1267 assert!(parsed.is_ok(), "Should parse basic folded block scalar");
1268
1269 let yaml_doc = parsed.unwrap();
1270 let output = yaml_doc.to_string();
1271
1272 assert_eq!(output, yaml);
1274 }
1275
1276 #[test]
1277 fn test_literal_block_scalar_with_chomping_indicators() {
1278 let yaml1 = r#"strip: |-
1280 Line 1
1281 Line 2
1282
1283"#;
1284 let parsed1 = YamlFile::from_str(yaml1);
1285 assert!(
1286 parsed1.is_ok(),
1287 "Should parse literal block scalar with strip indicator"
1288 );
1289
1290 let file1 = parsed1.unwrap();
1291 let doc1 = file1.document().unwrap();
1292 let mapping1 = doc1.as_mapping().unwrap();
1293 let value1 = mapping1
1294 .get("strip")
1295 .unwrap()
1296 .as_scalar()
1297 .unwrap()
1298 .as_string();
1299 assert_eq!(value1, "Line 1\nLine 2");
1300
1301 let output1 = file1.to_string();
1302 assert_eq!(output1, yaml1);
1303
1304 let yaml2 = r#"keep: |+
1306 Line 1
1307 Line 2
1308
1309"#;
1310 let parsed2 = YamlFile::from_str(yaml2);
1311 assert!(
1312 parsed2.is_ok(),
1313 "Should parse literal block scalar with keep indicator"
1314 );
1315
1316 let file2 = parsed2.unwrap();
1317 let doc2 = file2.document().unwrap();
1318 let mapping2 = doc2.as_mapping().unwrap();
1319 let value2 = mapping2
1320 .get("keep")
1321 .unwrap()
1322 .as_scalar()
1323 .unwrap()
1324 .as_string();
1325 assert_eq!(value2, "Line 1\nLine 2\n\n");
1326
1327 let output2 = file2.to_string();
1328 assert_eq!(output2, yaml2);
1329 }
1330
1331 #[test]
1332 fn test_folded_block_scalar_with_chomping_indicators() {
1333 let yaml1 = r#"strip: >-
1335 Folded content that should
1336 be stripped of final newlines
1337"#;
1338 let parsed1 = YamlFile::from_str(yaml1);
1339 assert!(
1340 parsed1.is_ok(),
1341 "Should parse folded block scalar with strip indicator"
1342 );
1343
1344 let file1 = parsed1.unwrap();
1345 let doc1 = file1.document().unwrap();
1346 let mapping1 = doc1.as_mapping().unwrap();
1347 let value1 = mapping1
1348 .get("strip")
1349 .unwrap()
1350 .as_scalar()
1351 .unwrap()
1352 .as_string();
1353 assert_eq!(
1354 value1,
1355 "Folded content that should be stripped of final newlines"
1356 );
1357
1358 let output1 = file1.to_string();
1359 assert_eq!(output1, yaml1);
1360
1361 let yaml2 = r#"keep: >+
1363 Folded content that should
1364 keep all final newlines
1365
1366"#;
1367 let parsed2 = YamlFile::from_str(yaml2);
1368 assert!(
1369 parsed2.is_ok(),
1370 "Should parse folded block scalar with keep indicator"
1371 );
1372
1373 let file2 = parsed2.unwrap();
1374 let doc2 = file2.document().unwrap();
1375 let mapping2 = doc2.as_mapping().unwrap();
1376 let value2 = mapping2
1377 .get("keep")
1378 .unwrap()
1379 .as_scalar()
1380 .unwrap()
1381 .as_string();
1382 assert_eq!(
1383 value2,
1384 "Folded content that should keep all final newlines\n\n"
1385 );
1386
1387 let output2 = file2.to_string();
1388 assert_eq!(output2, yaml2);
1389 }
1390
1391 #[test]
1392 fn test_block_scalar_with_explicit_indentation() {
1393 let yaml1 = r#"explicit: |2
1394 Two space indent
1395 Another line
1396"#;
1397 let parsed1 = YamlFile::from_str(yaml1)
1398 .expect("Should parse literal block scalar with explicit indentation");
1399
1400 let doc1 = parsed1.document().expect("Should have document");
1401 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
1402 let scalar1 = mapping1
1403 .get("explicit")
1404 .expect("Should have 'explicit' key");
1405 assert_eq!(
1408 scalar1.as_scalar().unwrap().as_string(),
1409 " Two space indent\n Another line\n"
1410 );
1411
1412 let output1 = parsed1.to_string();
1413 assert_eq!(output1, yaml1);
1414
1415 let yaml2 = r#"folded_explicit: >3
1416 Three space indent
1417 Another folded line
1418"#;
1419 let parsed2 = YamlFile::from_str(yaml2)
1420 .expect("Should parse folded block scalar with explicit indentation");
1421
1422 let doc2 = parsed2.document().expect("Should have document");
1423 let mapping2 = doc2.as_mapping().expect("Should be a mapping");
1424 let scalar2 = mapping2
1425 .get("folded_explicit")
1426 .expect("Should have 'folded_explicit' key");
1427 assert_eq!(
1431 scalar2.as_scalar().unwrap().as_string(),
1432 " Three space indent\n Another folded line\n"
1433 );
1434
1435 let output2 = parsed2.to_string();
1436 assert_eq!(output2, yaml2);
1437 }
1438
1439 #[test]
1440 fn test_block_scalar_in_mapping() {
1441 let yaml = r#"description: |
1442 This is a multi-line
1443 description that should
1444 preserve line breaks.
1445
1446 It can have multiple paragraphs too.
1447
1448summary: >
1449 This is a summary that
1450 should be folded into
1451 a single line.
1452
1453version: "1.0"
1454"#;
1455 let parsed =
1456 YamlFile::from_str(yaml).expect("Should parse block scalars in mapping context");
1457
1458 let doc = parsed.document().expect("Should have document");
1459 let mapping = doc.as_mapping().expect("Should be a mapping");
1460
1461 let description = mapping
1462 .get("description")
1463 .expect("Should have 'description' key");
1464 assert_eq!(
1465 description.as_scalar().unwrap().as_string(),
1466 "This is a multi-line\ndescription that should\npreserve line breaks.\n\nIt can have multiple paragraphs too.\n"
1467 );
1468
1469 let summary = mapping.get("summary").expect("Should have 'summary' key");
1470 assert_eq!(
1471 summary.as_scalar().unwrap().as_string(),
1472 "This is a summary that should be folded into a single line.\n"
1473 );
1474
1475 let version = mapping.get("version").expect("Should have 'version' key");
1476 assert_eq!(version.as_scalar().unwrap().as_string(), "1.0");
1477
1478 let output = parsed.to_string();
1479 assert_eq!(output, yaml);
1480 }
1481
1482 #[test]
1483 fn test_mixed_block_and_regular_scalars() {
1484 let yaml = r#"config:
1485 name: "My App"
1486 description: |
1487 This application does many things:
1488 - Feature 1
1489 - Feature 2
1490 - Feature 3
1491 summary: >
1492 A brief summary that spans
1493 multiple lines but should
1494 be folded together.
1495 version: 1.0
1496 enabled: true
1497"#;
1498 let parsed =
1499 YamlFile::from_str(yaml).expect("Should parse mixed block and regular scalars");
1500
1501 let doc = parsed.document().expect("Should have document");
1502 let mapping = doc.as_mapping().expect("Should be a mapping");
1503 let config_node = mapping.get("config").expect("Should have 'config' key");
1504 let config = config_node
1505 .as_mapping()
1506 .expect("Should be a nested mapping");
1507
1508 assert_eq!(
1509 config.get("name").unwrap().as_scalar().unwrap().as_string(),
1510 "My App"
1511 );
1512 assert_eq!(
1513 config
1514 .get("description")
1515 .unwrap()
1516 .as_scalar()
1517 .unwrap()
1518 .as_string(),
1519 "This application does many things:\n- Feature 1\n- Feature 2\n- Feature 3\n"
1520 );
1521 assert_eq!(
1522 config
1523 .get("summary")
1524 .unwrap()
1525 .as_scalar()
1526 .unwrap()
1527 .as_string(),
1528 "A brief summary that spans multiple lines but should be folded together.\n"
1529 );
1530 assert_eq!(
1531 config
1532 .get("version")
1533 .unwrap()
1534 .as_scalar()
1535 .unwrap()
1536 .as_string(),
1537 "1.0"
1538 );
1539 assert_eq!(config.get("enabled").unwrap().to_bool(), Some(true));
1540
1541 let output = parsed.to_string();
1542 assert_eq!(output, yaml);
1543 }
1544
1545 #[test]
1546 fn test_block_scalar_edge_cases() {
1547 let yaml1 = r#"empty_literal: |
1551empty_folded: >
1552"#;
1553 let parsed1 = YamlFile::from_str(yaml1).expect("Should parse this edge case");
1554
1555 let doc1 = parsed1.document().expect("Should have document");
1556 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
1557 assert_eq!(mapping1.len(), 2, "Both keys are entries of their own");
1558 assert_eq!(
1559 mapping1
1560 .get("empty_literal")
1561 .unwrap()
1562 .as_scalar()
1563 .unwrap()
1564 .as_string(),
1565 ""
1566 );
1567 assert!(mapping1.get("empty_folded").is_some());
1568
1569 assert_eq!(parsed1.to_string(), yaml1);
1570
1571 let yaml2 = r#"whitespace: |
1573
1574
1575"#;
1576 let parsed2 =
1577 YamlFile::from_str(yaml2).expect("Should parse block scalar with only whitespace");
1578
1579 assert_eq!(parsed2.to_string(), yaml2);
1580
1581 let yaml3 = r#"first: |
1583 Content
1584second: value
1585"#;
1586 let parsed3 =
1587 YamlFile::from_str(yaml3).expect("Should parse block scalar followed by other keys");
1588
1589 let doc3 = parsed3.document().expect("Should have document");
1590 let mapping3 = doc3.as_mapping().expect("Should be a mapping");
1591 assert_eq!(
1592 mapping3
1593 .get("first")
1594 .unwrap()
1595 .as_scalar()
1596 .unwrap()
1597 .as_string(),
1598 "Content\n"
1599 );
1600 assert_eq!(
1601 mapping3
1602 .get("second")
1603 .unwrap()
1604 .as_scalar()
1605 .unwrap()
1606 .as_string(),
1607 "value"
1608 );
1609
1610 let output3 = parsed3.to_string();
1611 assert_eq!(output3, yaml3);
1612 }
1613
1614 #[test]
1615 fn test_literal_block_scalar_advanced_formatting() {
1616 let yaml = r#"poem: |
1617 Roses are red,
1618 Violets are blue,
1619 YAML is great,
1620 And so are you!
1621
1622 This is another stanza
1623 with different content.
1624 And this line has extra indentation.
1625 Back to normal indentation.
1626
1627 Final stanza.
1628"#;
1629 let parsed = YamlFile::from_str(yaml).expect("Should parse complex literal block scalar");
1630
1631 let doc = parsed.document().expect("Should have document");
1632 let mapping = doc.as_mapping().expect("Should be a mapping");
1633 let poem = mapping.get("poem").expect("Should have 'poem' key");
1634 let expected_content = "Roses are red,\nViolets are blue,\nYAML is great,\nAnd so are you!\n\nThis is another stanza\nwith different content.\n And this line has extra indentation.\nBack to normal indentation.\n\nFinal stanza.\n";
1635 assert_eq!(poem.as_scalar().unwrap().as_string(), expected_content);
1636
1637 let output = parsed.to_string();
1638 assert_eq!(output, yaml);
1639 }
1640
1641 #[test]
1642 fn test_folded_block_scalar_paragraph_handling() {
1643 let yaml = r#"description: >
1644 This is the first paragraph that should
1645 be folded into a single line when processed
1646 by a YAML parser.
1647
1648 This is a second paragraph that should
1649 also be folded but kept separate from
1650 the first paragraph.
1651
1652
1653 This is a third paragraph after
1654 multiple blank lines.
1655
1656 Final paragraph.
1657"#;
1658 let parsed =
1659 YamlFile::from_str(yaml).expect("Should parse folded block scalar with paragraphs");
1660
1661 let doc = parsed.document().expect("Should have document");
1662 let mapping = doc.as_mapping().expect("Should be a mapping");
1663 let description = mapping
1664 .get("description")
1665 .expect("Should have 'description' key");
1666 let expected_content = "This is the first paragraph that should be folded into a single line when processed by a YAML parser.\nThis is a second paragraph that should also be folded but kept separate from the first paragraph.\nThis is a third paragraph after multiple blank lines.\nFinal paragraph.\n";
1667 assert_eq!(
1668 description.as_scalar().unwrap().as_string(),
1669 expected_content
1670 );
1671
1672 let output = parsed.to_string();
1673 assert_eq!(output, yaml);
1674 }
1675
1676 #[test]
1677 fn test_block_scalars_with_special_characters() {
1678 let yaml = r#"special_chars: |
1679 Line with colons: key: value
1680 Line with dashes - and more - dashes
1681 Line with quotes "double" and 'single'
1682 Line with brackets [array] and braces {object}
1683 Line with pipes | and greater than >
1684 Line with at @ and hash # symbols
1685 Line with percent % and exclamation !
1686
1687backslash_test: >
1688 This line has a backslash \ in it
1689 And this line has multiple \\ backslashes
1690
1691unicode_test: |
1692 This line has unicode: 你好世界
1693 And emojis: 🚀 🎉 ✨
1694"#;
1695 let parsed =
1696 YamlFile::from_str(yaml).expect("Should parse block scalars with special characters");
1697
1698 let doc = parsed.document().expect("Should have document");
1699 let mapping = doc.as_mapping().expect("Should be a mapping");
1700
1701 let special_chars = mapping
1702 .get("special_chars")
1703 .expect("Should have 'special_chars' key");
1704 assert_eq!(
1705 special_chars.as_scalar().unwrap().as_string(),
1706 "Line with colons: key: value\nLine with dashes - and more - dashes\nLine with quotes \"double\" and 'single'\nLine with brackets [array] and braces {object}\nLine with pipes | and greater than >\nLine with at @ and hash # symbols\nLine with percent % and exclamation !\n"
1707 );
1708
1709 let backslash_test = mapping
1710 .get("backslash_test")
1711 .expect("Should have 'backslash_test' key");
1712 assert_eq!(
1713 backslash_test.as_scalar().unwrap().as_string(),
1714 "This line has a backslash \\ in it And this line has multiple \\\\ backslashes\n"
1715 );
1716
1717 let unicode_test = mapping
1718 .get("unicode_test")
1719 .expect("Should have 'unicode_test' key");
1720 assert_eq!(
1721 unicode_test.as_scalar().unwrap().as_string(),
1722 "This line has unicode: 你好世界\nAnd emojis: 🚀 🎉 ✨\n"
1723 );
1724
1725 let output = parsed.to_string();
1726 assert_eq!(output, yaml);
1727 }
1728
1729 #[test]
1730 fn test_block_scalar_chomping_detailed() {
1731 let yaml_clip = r#"clip: |
1733 Line 1
1734 Line 2
1735
1736"#;
1737 let parsed_clip =
1738 YamlFile::from_str(yaml_clip).expect("Should parse block scalar with default clipping");
1739
1740 let doc_clip = parsed_clip.document().expect("Should have document");
1742 let mapping_clip = doc_clip.as_mapping().expect("Should be a mapping");
1743 assert_eq!(
1744 mapping_clip
1745 .get("clip")
1746 .unwrap()
1747 .as_scalar()
1748 .unwrap()
1749 .as_string(),
1750 "Line 1\nLine 2\n"
1751 );
1752
1753 assert_eq!(parsed_clip.to_string(), yaml_clip);
1754
1755 let yaml_strip = r#"strip: |-
1757 Line 1
1758 Line 2
1759
1760
1761
1762"#;
1763 let parsed_strip =
1764 YamlFile::from_str(yaml_strip).expect("Should parse block scalar with strip indicator");
1765
1766 let doc_strip = parsed_strip.document().expect("Should have document");
1768 let mapping_strip = doc_strip.as_mapping().expect("Should be a mapping");
1769 assert_eq!(
1770 mapping_strip
1771 .get("strip")
1772 .unwrap()
1773 .as_scalar()
1774 .unwrap()
1775 .as_string(),
1776 "Line 1\nLine 2"
1777 );
1778
1779 assert_eq!(parsed_strip.to_string(), yaml_strip);
1780
1781 let yaml_keep = r#"keep: |+
1783 Line 1
1784 Line 2
1785
1786
1787
1788"#;
1789 let parsed_keep =
1790 YamlFile::from_str(yaml_keep).expect("Should parse block scalar with keep indicator");
1791
1792 let doc_keep = parsed_keep.document().expect("Should have document");
1794 let mapping_keep = doc_keep.as_mapping().expect("Should be a mapping");
1795 assert_eq!(
1796 mapping_keep
1797 .get("keep")
1798 .unwrap()
1799 .as_scalar()
1800 .unwrap()
1801 .as_string(),
1802 "Line 1\nLine 2\n\n\n\n"
1803 );
1804
1805 assert_eq!(parsed_keep.to_string(), yaml_keep);
1806 }
1807
1808 #[test]
1809 fn test_block_scalar_explicit_indentation_detailed() {
1810 let yaml1 = r#"indent1: |1
1812 Single space indent
1813"#;
1814 let parsed1 = YamlFile::from_str(yaml1);
1815 assert!(parsed1.is_ok(), "Should parse |1 block scalar");
1816 let output1 = parsed1.unwrap().to_string();
1817 assert_eq!(output1, yaml1);
1818
1819 let yaml2 = r#"indent2: |2
1820 Two space indent
1821"#;
1822 let parsed2 = YamlFile::from_str(yaml2);
1823 assert!(parsed2.is_ok(), "Should parse |2 block scalar");
1824 let output2 = parsed2.unwrap().to_string();
1825 assert_eq!(output2, yaml2);
1826
1827 let yaml3 = r#"folded_indent: >2
1828 Two space folded
1829 content spans lines
1830"#;
1831 let parsed3 = YamlFile::from_str(yaml3);
1832 assert!(parsed3.is_ok(), "Should parse >2 folded block scalar");
1833 let output3 = parsed3.unwrap().to_string();
1834 assert_eq!(output3, yaml3);
1835 }
1836
1837 #[test]
1838 fn test_block_scalar_combined_indicators() {
1839 let yaml = r#"strip_with_indent: |2-
1840 Content with explicit indent
1841 and strip chomping
1842
1843
1844keep_with_indent: >3+
1845 Content with explicit indent
1846 and keep chomping
1847
1848
1849
1850folded_strip: >-
1851 Folded content
1852 with strip indicator
1853
1854literal_keep: |+
1855 Literal content
1856 with keep indicator
1857
1858
1859"#;
1860 let parsed =
1861 YamlFile::from_str(yaml).expect("Should parse block scalars with combined indicators");
1862
1863 let doc = parsed.document().expect("Should have document");
1864 let mapping = doc.as_mapping().expect("Should be a mapping");
1865
1866 assert_eq!(
1867 mapping
1868 .get("strip_with_indent")
1869 .unwrap()
1870 .as_scalar()
1871 .unwrap()
1872 .as_string(),
1873 "Content with explicit indent\nand strip chomping"
1874 );
1875 assert_eq!(
1876 mapping
1877 .get("keep_with_indent")
1878 .unwrap()
1879 .as_scalar()
1880 .unwrap()
1881 .as_string(),
1882 "Content with explicit indent and keep chomping\n\n\n\n"
1883 );
1884 assert_eq!(
1885 mapping
1886 .get("folded_strip")
1887 .unwrap()
1888 .as_scalar()
1889 .unwrap()
1890 .as_string(),
1891 "Folded content with strip indicator"
1892 );
1893 assert_eq!(
1894 mapping
1895 .get("literal_keep")
1896 .unwrap()
1897 .as_scalar()
1898 .unwrap()
1899 .as_string(),
1900 "Literal content\nwith keep indicator\n\n\n"
1901 );
1902
1903 let output = parsed.to_string();
1904 assert_eq!(output, yaml);
1905 }
1906
1907 #[test]
1908 fn test_block_scalar_whitespace_and_empty() {
1909 let yaml1 = r#"whitespace_only: |
1911
1912
1913
1914"#;
1915 let parsed1 =
1916 YamlFile::from_str(yaml1).expect("Should handle block scalar with only whitespace");
1917
1918 let doc1 = parsed1.document().expect("Should have document");
1919 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
1920 assert_eq!(
1921 mapping1
1922 .get("whitespace_only")
1923 .unwrap()
1924 .as_scalar()
1925 .unwrap()
1926 .as_string(),
1927 "\n"
1928 );
1929
1930 assert_eq!(parsed1.to_string(), yaml1);
1931
1932 let yaml2 = r#"mixed_indent: |
1934 Normal line
1935 Indented line
1936 Back to normal
1937 More indented
1938 Normal again
1939"#;
1940 let parsed2 = YamlFile::from_str(yaml2).expect("Should handle mixed indentation levels");
1941
1942 let doc2 = parsed2.document().expect("Should have document");
1943 let mapping2 = doc2.as_mapping().expect("Should be a mapping");
1944 assert_eq!(
1945 mapping2
1946 .get("mixed_indent")
1947 .unwrap()
1948 .as_scalar()
1949 .unwrap()
1950 .as_string(),
1951 "Normal line\n Indented line\nBack to normal\n More indented\nNormal again\n"
1952 );
1953
1954 assert_eq!(parsed2.to_string(), yaml2);
1955
1956 let yaml3 = r#"first: |
1958 Content
1959immediate: value
1960another: |
1961 More content
1962final: end
1963"#;
1964 let parsed3 =
1965 YamlFile::from_str(yaml3).expect("Should handle multiple block scalars in mapping");
1966
1967 let doc3 = parsed3.document().expect("Should have document");
1968 let mapping3 = doc3.as_mapping().expect("Should be a mapping");
1969 assert_eq!(
1970 mapping3
1971 .get("first")
1972 .unwrap()
1973 .as_scalar()
1974 .unwrap()
1975 .as_string(),
1976 "Content\n"
1977 );
1978 assert_eq!(
1979 mapping3
1980 .get("immediate")
1981 .unwrap()
1982 .as_scalar()
1983 .unwrap()
1984 .as_string(),
1985 "value"
1986 );
1987 assert_eq!(
1988 mapping3
1989 .get("another")
1990 .unwrap()
1991 .as_scalar()
1992 .unwrap()
1993 .as_string(),
1994 "More content\n"
1995 );
1996 assert_eq!(
1997 mapping3
1998 .get("final")
1999 .unwrap()
2000 .as_scalar()
2001 .unwrap()
2002 .as_string(),
2003 "end"
2004 );
2005
2006 let output3 = parsed3.to_string();
2007 assert_eq!(output3, yaml3);
2008 }
2009
2010 #[test]
2011 fn test_block_scalar_with_comments() {
2012 let yaml = r#"# Main configuration
2013config: | # This is a literal block
2014 # This comment is inside the block
2015 line1: value1
2016 # Another internal comment
2017 line2: value2
2018
2019# Outside comment
2020other: > # Folded block comment
2021 This content spans
2022 # This hash is part of the content, not a comment
2023 multiple lines
2024"#;
2025 let parsed = YamlFile::from_str(yaml).expect("Should parse block scalars with comments");
2026
2027 let doc = parsed.document().expect("Should have document");
2028 let mapping = doc.as_mapping().expect("Should be a mapping");
2029
2030 assert_eq!(
2037 mapping.get("config").unwrap().as_scalar().unwrap().as_string(),
2038 "# This comment is inside the block\nline1: value1\n# Another internal comment\nline2: value2\n\n# Outside comment\n"
2039 );
2040
2041 assert_eq!(
2042 mapping
2043 .get("other")
2044 .unwrap()
2045 .as_scalar()
2046 .unwrap()
2047 .as_string(),
2048 "This content spans # This hash is part of the content, not a comment multiple lines\n"
2049 );
2050
2051 let output = parsed.to_string();
2052 assert_eq!(output, yaml);
2053 }
2054
2055 #[test]
2056 fn test_block_scalar_empty_and_minimal() {
2057 let yaml = r#"empty_literal: |
2058
2059empty_folded: >
2060
2061minimal_literal: |
2062 x
2063
2064minimal_folded: >
2065 y
2066
2067just_newlines: |
2068
2069
2070
2071just_spaces: |
2072
2073
2074
2075"#;
2076 let parsed =
2077 YamlFile::from_str(yaml).expect("Should handle empty and minimal block scalars");
2078
2079 let doc = parsed.document().expect("Should have document");
2080 let mapping = doc.as_mapping().expect("Should be a mapping");
2081
2082 assert_eq!(
2083 mapping
2084 .get("empty_literal")
2085 .unwrap()
2086 .as_scalar()
2087 .unwrap()
2088 .as_string(),
2089 "\n"
2090 );
2091 assert_eq!(
2092 mapping
2093 .get("empty_folded")
2094 .unwrap()
2095 .as_scalar()
2096 .unwrap()
2097 .as_string(),
2098 "\n"
2099 );
2100 assert_eq!(
2101 mapping
2102 .get("minimal_literal")
2103 .unwrap()
2104 .as_scalar()
2105 .unwrap()
2106 .as_string(),
2107 "x\n"
2108 );
2109 assert_eq!(
2110 mapping
2111 .get("minimal_folded")
2112 .unwrap()
2113 .as_scalar()
2114 .unwrap()
2115 .as_string(),
2116 "y\n"
2117 );
2118 assert_eq!(
2119 mapping
2120 .get("just_newlines")
2121 .unwrap()
2122 .as_scalar()
2123 .unwrap()
2124 .as_string(),
2125 "\n"
2126 );
2127 assert_eq!(
2128 mapping
2129 .get("just_spaces")
2130 .unwrap()
2131 .as_scalar()
2132 .unwrap()
2133 .as_string(),
2134 "\n"
2135 );
2136
2137 let output = parsed.to_string();
2138 assert_eq!(output, yaml);
2139 }
2140
2141 #[test]
2142 fn test_block_scalar_with_document_markers() {
2143 let yaml = r#"---
2144doc1: |
2145 This is the first document
2146 with a literal block scalar.
2147
2148next_key: value
2149---
2150doc2: >
2151 This is the second document
2152 with a folded block scalar.
2153
2154another_key: another_value
2155...
2156"#;
2157 let parsed =
2158 YamlFile::from_str(yaml).expect("Should parse block scalars with document markers");
2159
2160 let doc = parsed.document().expect("Should have first document");
2162 let mapping = doc.as_mapping().expect("Should be a mapping");
2163
2164 assert_eq!(
2165 mapping
2166 .get("doc1")
2167 .unwrap()
2168 .as_scalar()
2169 .unwrap()
2170 .as_string(),
2171 "This is the first document\nwith a literal block scalar.\n"
2172 );
2173 assert_eq!(
2174 mapping
2175 .get("next_key")
2176 .unwrap()
2177 .as_scalar()
2178 .unwrap()
2179 .as_string(),
2180 "value"
2181 );
2182
2183 let output = parsed.to_string();
2185 assert_eq!(output, yaml);
2186 }
2187
2188 #[test]
2189 fn test_block_scalar_formatting_preservation() {
2190 let original = r#"preserve_me: |
2191 Line with multiple spaces
2192 Line with tabs here
2193 Line with trailing spaces
2194
2195 Empty line above and below
2196
2197 Final line
2198"#;
2199 let parsed = YamlFile::from_str(original).expect("Should preserve exact formatting");
2200
2201 let doc = parsed.document().expect("Should have document");
2202 let mapping = doc.as_mapping().expect("Should be a mapping");
2203
2204 let preserve_me = mapping
2205 .get("preserve_me")
2206 .expect("Should have 'preserve_me' key");
2207 let expected = "Line with multiple spaces\nLine with\ttabs\there\nLine with trailing spaces\n\nEmpty line above and below\n\nFinal line\n";
2208 assert_eq!(preserve_me.as_scalar().unwrap().as_string(), expected);
2209
2210 let output = parsed.to_string();
2212 assert_eq!(output, original);
2213 }
2214
2215 #[test]
2216 fn test_block_scalar_complex_yaml_content() {
2217 let yaml = r#"yaml_content: |
2218 # This block contains YAML-like content
2219 nested:
2220 - item: value
2221 - item: another
2222
2223 mapping:
2224 key1: |
2225 Even more nested literal content
2226 key2: value
2227
2228 anchors: &anchor
2229 anchor_content: data
2230
2231 reference: *anchor
2232
2233quoted_yaml: >
2234 This folded block contains
2235 YAML structures: {key: value, array: [1, 2, 3]}
2236 that should be treated as plain text.
2237"#;
2238 let parsed = YamlFile::from_str(yaml)
2239 .expect("Should parse block scalars containing YAML-like structures");
2240
2241 let doc = parsed.document().expect("Should have document");
2242 let mapping = doc.as_mapping().expect("Should be a mapping");
2243
2244 let expected_yaml_content = "# This block contains YAML-like content\nnested:\n - item: value\n - item: another\n\nmapping:\n key1: |\n Even more nested literal content\n key2: value\n\nanchors: &anchor\n anchor_content: data\n\nreference: *anchor\n";
2245 assert_eq!(
2246 mapping
2247 .get("yaml_content")
2248 .unwrap()
2249 .as_scalar()
2250 .unwrap()
2251 .as_string(),
2252 expected_yaml_content
2253 );
2254
2255 let expected_quoted_yaml = "This folded block contains YAML structures: {key: value, array: [1, 2, 3]} that should be treated as plain text.\n";
2256 assert_eq!(
2257 mapping
2258 .get("quoted_yaml")
2259 .unwrap()
2260 .as_scalar()
2261 .unwrap()
2262 .as_string(),
2263 expected_quoted_yaml
2264 );
2265
2266 let output = parsed.to_string();
2267 assert_eq!(output, yaml);
2268 }
2269
2270 #[test]
2271 fn test_block_scalar_performance_large_content() {
2272 let mut large_content = String::new();
2274 for i in 1..=100 {
2275 large_content.push_str(&format!(
2276 " Line number {} with some content that makes it longer\n",
2277 i
2278 ));
2279 }
2280
2281 let yaml = format!(
2282 "large_literal: |\n{}\nlarge_folded: >\n{}\n",
2283 large_content, large_content
2284 );
2285
2286 let parsed =
2287 YamlFile::from_str(&yaml).expect("Should parse large block scalars without errors");
2288
2289 let doc = parsed.document().expect("Should have document");
2290 let mapping = doc.as_mapping().expect("Should be a mapping");
2291
2292 let literal_value = mapping
2293 .get("large_literal")
2294 .expect("Should have large_literal key")
2295 .as_scalar()
2296 .expect("Should be scalar")
2297 .as_string();
2298
2299 let mut expected_literal = String::new();
2301 for i in 1..=100 {
2302 expected_literal.push_str(&format!(
2303 "Line number {} with some content that makes it longer\n",
2304 i
2305 ));
2306 }
2307 assert_eq!(literal_value, expected_literal);
2308
2309 let folded_value = mapping
2310 .get("large_folded")
2311 .expect("Should have large_folded key")
2312 .as_scalar()
2313 .expect("Should be scalar")
2314 .as_string();
2315
2316 let mut expected_folded = String::new();
2318 for i in 1..=100 {
2319 if i > 1 {
2320 expected_folded.push(' ');
2321 }
2322 expected_folded.push_str(&format!(
2323 "Line number {} with some content that makes it longer",
2324 i
2325 ));
2326 }
2327 expected_folded.push('\n');
2328 assert_eq!(folded_value, expected_folded);
2329
2330 let output = parsed.to_string();
2331 assert_eq!(output, yaml);
2332 }
2333
2334 #[test]
2335 fn test_block_scalar_error_recovery() {
2336 let yaml = r#"good_key: value
2341bad_block: |
2342incomplete_key
2343another_good: works
2344"#;
2345 let err = YamlFile::from_str(yaml).unwrap_err();
2346 assert!(err.to_string().contains("could not be parsed"), "{err}");
2347 }
2348
2349 #[test]
2350 fn test_block_scalar_with_flow_structures() {
2351 let yaml = r#"mixed_styles: |
2352 This literal block contains:
2353 - A flow sequence: [1, 2, 3]
2354 - A flow mapping: {key: value, other: data}
2355 - Mixed content: [a, {nested: true}, c]
2356
2357flow_then_block:
2358 flow_seq: [item1, item2]
2359 block_literal: |
2360 This comes after flow style
2361 and should work fine.
2362 flow_map: {after: block}
2363"#;
2364 let parsed = YamlFile::from_str(yaml).expect("Should parse mixed flow and block styles");
2365
2366 let doc = parsed.document().expect("Should have document");
2367 let mapping = doc.as_mapping().expect("Should be a mapping");
2368
2369 let expected_mixed = "This literal block contains:\n- A flow sequence: [1, 2, 3]\n- A flow mapping: {key: value, other: data}\n- Mixed content: [a, {nested: true}, c]\n";
2371 assert_eq!(
2372 mapping
2373 .get("mixed_styles")
2374 .unwrap()
2375 .as_scalar()
2376 .unwrap()
2377 .as_string(),
2378 expected_mixed
2379 );
2380
2381 let flow_then_block_value = mapping.get("flow_then_block").unwrap();
2383 let flow_then_block = flow_then_block_value.as_mapping().unwrap();
2384
2385 let flow_seq_value = flow_then_block.get("flow_seq").unwrap();
2387 let flow_seq = flow_seq_value.as_sequence().unwrap();
2388 assert_eq!(flow_seq.len(), 2);
2389 assert_eq!(
2390 flow_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
2391 "item1"
2392 );
2393 assert_eq!(
2394 flow_seq.get(1).unwrap().as_scalar().unwrap().as_string(),
2395 "item2"
2396 );
2397
2398 assert_eq!(
2400 flow_then_block
2401 .get("block_literal")
2402 .unwrap()
2403 .as_scalar()
2404 .unwrap()
2405 .as_string(),
2406 "This comes after flow style\nand should work fine.\n"
2407 );
2408
2409 let flow_map_value = flow_then_block.get("flow_map").unwrap();
2411 let flow_map = flow_map_value.as_mapping().unwrap();
2412 assert_eq!(
2413 flow_map
2414 .get("after")
2415 .unwrap()
2416 .as_scalar()
2417 .unwrap()
2418 .as_string(),
2419 "block"
2420 );
2421
2422 let output = parsed.to_string();
2423 assert_eq!(output, yaml);
2424 }
2425
2426 #[test]
2427 fn test_block_scalar_indentation_edge_cases() {
2428 let yaml1 = r#"empty: |
2430next: value"#;
2431 let parsed1 = YamlFile::from_str(yaml1);
2432 assert!(parsed1.is_ok(), "Should handle empty block followed by key");
2433
2434 let yaml2 = r#"inconsistent: |
2436 normal indent
2437 more indent
2438 back to normal
2439 even more
2440 normal
2441"#;
2442 let parsed2 = YamlFile::from_str(yaml2);
2443 assert!(
2444 parsed2.is_ok(),
2445 "Should handle inconsistent but valid indentation"
2446 );
2447
2448 let yaml3 = "tabs: |\n\tTab indented line\n\tAnother tab line\n";
2450 let parsed3 = YamlFile::from_str(yaml3);
2451 assert!(
2452 parsed3.is_ok(),
2453 "Should handle tab characters in block scalars"
2454 );
2455 }
2456
2457 #[test]
2458 fn test_block_scalar_with_anchors_and_aliases() {
2459 let yaml = r#"template: &template |
2460 This is a template
2461 with multiple lines
2462 that can be referenced.
2463
2464instance1: *template
2465
2466instance2:
2467 content: *template
2468 other: value
2469
2470modified: |
2471 <<: *template
2472 Additional content here
2473"#;
2474 let parsed =
2475 YamlFile::from_str(yaml).expect("Should parse block scalars with anchors and aliases");
2476
2477 let doc = parsed.document().expect("Should have document");
2478 let mapping = doc.as_mapping().expect("Should be a mapping");
2479
2480 let expected_template =
2482 "This is a template\nwith multiple lines\nthat can be referenced.\n";
2483 let template_value = mapping.get("template").unwrap();
2484 assert_eq!(
2485 template_value.as_scalar().unwrap().as_string(),
2486 expected_template
2487 );
2488
2489 let instance1 = mapping.get("instance1").unwrap();
2491 assert!(
2492 instance1.is_alias(),
2493 "instance1 should be an alias, not a scalar"
2494 );
2495 assert_eq!(instance1.as_alias().unwrap().name(), "template");
2496
2497 let instance2_value = mapping.get("instance2").unwrap();
2499 let instance2 = instance2_value.as_mapping().unwrap();
2500
2501 let content = instance2.get("content").unwrap();
2503 assert!(
2504 content.is_alias(),
2505 "content should be an alias, not a scalar"
2506 );
2507 assert_eq!(content.as_alias().unwrap().name(), "template");
2508
2509 assert_eq!(
2511 instance2
2512 .get("other")
2513 .unwrap()
2514 .as_scalar()
2515 .unwrap()
2516 .as_string(),
2517 "value"
2518 );
2519
2520 let modified = mapping.get("modified").unwrap();
2523 assert!(
2524 modified.is_scalar(),
2525 "modified should be a scalar, not an alias"
2526 );
2527 assert_eq!(
2528 modified.as_scalar().unwrap().as_string(),
2529 "<<: *template\nAdditional content here\n"
2530 );
2531
2532 let output = parsed.to_string();
2533 assert_eq!(output, yaml);
2534 }
2535
2536 #[test]
2537 fn test_block_scalar_newline_variations() {
2538 let yaml_unix = "unix: |\n Line 1\n Line 2\n";
2540 let parsed_unix = YamlFile::from_str(yaml_unix).expect("Should handle Unix newlines");
2541
2542 let yaml_windows = "windows: |\r\n Line 1\r\n Line 2\r\n";
2543 let parsed_windows =
2544 YamlFile::from_str(yaml_windows).expect("Should handle Windows newlines");
2545
2546 let doc_unix = parsed_unix.document().expect("Should have document");
2548 let mapping_unix = doc_unix.as_mapping().expect("Should be a mapping");
2549 assert_eq!(
2550 mapping_unix
2551 .get("unix")
2552 .unwrap()
2553 .as_scalar()
2554 .unwrap()
2555 .as_string(),
2556 "Line 1\nLine 2\n"
2557 );
2558
2559 let doc_windows = parsed_windows.document().expect("Should have document");
2561 let mapping_windows = doc_windows.as_mapping().expect("Should be a mapping");
2562 assert_eq!(
2563 mapping_windows
2564 .get("windows")
2565 .unwrap()
2566 .as_scalar()
2567 .unwrap()
2568 .as_string(),
2569 "Line 1\nLine 2\n"
2570 );
2571
2572 assert_eq!(parsed_unix.to_string(), yaml_unix);
2573 assert_eq!(parsed_windows.to_string(), yaml_windows);
2574 }
2575
2576 #[test]
2577 fn test_block_scalar_boundary_detection() {
2578 let yaml = r#"config:
2580 description: |
2581 This is a configuration
2582 with multiple lines.
2583
2584 name: "MyApp"
2585 version: 1.0
2586
2587 settings: >
2588 These are settings that
2589 span multiple lines too.
2590
2591 debug: true
2592"#;
2593 let parsed =
2594 YamlFile::from_str(yaml).expect("Should properly detect block scalar boundaries");
2595
2596 let doc = parsed.document().expect("Should have document");
2597 let mapping = doc.as_mapping().expect("Should be a mapping");
2598 let config_value = mapping.get("config").unwrap();
2599 let config = config_value.as_mapping().unwrap();
2600
2601 assert_eq!(
2603 config
2604 .get("description")
2605 .unwrap()
2606 .as_scalar()
2607 .unwrap()
2608 .as_string(),
2609 "This is a configuration\nwith multiple lines.\n"
2610 );
2611
2612 assert_eq!(
2614 config.get("name").unwrap().as_scalar().unwrap().as_string(),
2615 "MyApp"
2616 );
2617
2618 assert_eq!(
2620 config
2621 .get("version")
2622 .unwrap()
2623 .as_scalar()
2624 .unwrap()
2625 .as_string(),
2626 "1.0"
2627 );
2628
2629 assert_eq!(
2631 config
2632 .get("settings")
2633 .unwrap()
2634 .as_scalar()
2635 .unwrap()
2636 .as_string(),
2637 "These are settings that span multiple lines too.\n"
2638 );
2639
2640 assert_eq!(
2642 config
2643 .get("debug")
2644 .unwrap()
2645 .as_scalar()
2646 .unwrap()
2647 .as_string(),
2648 "true"
2649 );
2650
2651 let output = parsed.to_string();
2652 assert_eq!(output, yaml);
2653 }
2654
2655 #[test]
2656 fn test_block_scalar_with_numeric_content() {
2657 let yaml = r#"numbers_as_text: |
2658 123
2659 45.67
2660 -89
2661 +100
2662 0xFF
2663 1e5
2664 true
2665 false
2666 null
2667
2668calculations: >
2669 The result is: 2 + 2 = 4
2670 And 10 * 5 = 50
2671 Also: 100% complete
2672"#;
2673 let parsed = YamlFile::from_str(yaml)
2674 .expect("Should parse numeric content as text in block scalars");
2675
2676 let doc = parsed.document().expect("Should have document");
2677 let mapping = doc.as_mapping().expect("Should be a mapping");
2678
2679 let expected_numbers = "123\n45.67\n-89\n+100\n0xFF\n1e5\ntrue\nfalse\nnull\n";
2681 assert_eq!(
2682 mapping
2683 .get("numbers_as_text")
2684 .unwrap()
2685 .as_scalar()
2686 .unwrap()
2687 .as_string(),
2688 expected_numbers
2689 );
2690
2691 let expected_calculations =
2693 "The result is: 2 + 2 = 4 And 10 * 5 = 50 Also: 100% complete\n";
2694 assert_eq!(
2695 mapping
2696 .get("calculations")
2697 .unwrap()
2698 .as_scalar()
2699 .unwrap()
2700 .as_string(),
2701 expected_calculations
2702 );
2703
2704 let output = parsed.to_string();
2705 assert_eq!(output, yaml);
2706 }
2707
2708 #[test]
2709 fn test_block_scalar_exact_preservation() {
2710 let test_cases = [
2712 r#"simple: |
2714 Hello World
2715"#,
2716 r#"folded: >
2718 Hello World
2719"#,
2720 r#"strip: |-
2722 Content
2723
2724keep: |+
2725 Content
2726
2727"#,
2728 r#"explicit: |2
2730 Two space indent
2731"#,
2732 r#"config:
2734 script: |
2735 #!/bin/bash
2736 echo "Starting deployment"
2737
2738 for service in api web worker; do
2739 echo "Deploying $service"
2740 kubectl apply -f $service.yaml
2741 done
2742
2743 description: >
2744 This configuration defines a deployment
2745 script that will be executed during
2746 the CI/CD pipeline.
2747"#,
2748 ];
2749
2750 for (i, yaml) in test_cases.iter().enumerate() {
2751 let parsed = YamlFile::from_str(yaml);
2752 assert!(parsed.is_ok(), "Test case {} should parse successfully", i);
2753
2754 let output = parsed.unwrap().to_string();
2755 assert_eq!(
2756 output, *yaml,
2757 "Test case {} should preserve exact formatting",
2758 i
2759 );
2760 }
2761 }
2762
2763 #[test]
2764 fn test_block_scalar_chomping_exact() {
2765 let yaml_strip = r#"strip: |-
2766 Content
2767"#;
2768 let parsed_strip = YamlFile::from_str(yaml_strip).unwrap();
2769 assert_eq!(parsed_strip.to_string(), yaml_strip);
2770
2771 let yaml_keep = r#"keep: |+
2772 Content
2773
2774"#;
2775 let parsed_keep = YamlFile::from_str(yaml_keep).unwrap();
2776 assert_eq!(parsed_keep.to_string(), yaml_keep);
2777
2778 let yaml_folded_strip = r#"folded: >-
2779 Content
2780"#;
2781 let parsed_folded_strip = YamlFile::from_str(yaml_folded_strip).unwrap();
2782 assert_eq!(parsed_folded_strip.to_string(), yaml_folded_strip);
2783 }
2784
2785 #[test]
2786 fn test_block_scalar_indentation_exact() {
2787 let yaml1 = r#"indent1: |1
2788 Single space
2789"#;
2790 let parsed1 = YamlFile::from_str(yaml1).unwrap();
2791 assert_eq!(parsed1.to_string(), yaml1);
2792
2793 let yaml2 = r#"indent2: |2
2794 Two spaces
2795"#;
2796 let parsed2 = YamlFile::from_str(yaml2).unwrap();
2797 assert_eq!(parsed2.to_string(), yaml2);
2798
2799 let yaml3 = r#"combined: |3+
2800 Content with keep
2801
2802"#;
2803 let parsed3 = YamlFile::from_str(yaml3).unwrap();
2804 assert_eq!(parsed3.to_string(), yaml3);
2805 }
2806
2807 #[test]
2808 fn test_block_scalar_mapping_exact() {
2809 let yaml = r#"description: |
2810 Line 1
2811 Line 2
2812
2813summary: >
2814 Folded content
2815
2816version: "1.0"
2817"#;
2818 let parsed = YamlFile::from_str(yaml).unwrap();
2819 assert_eq!(parsed.to_string(), yaml);
2820 }
2821
2822 #[test]
2823 fn test_block_scalar_sequence_exact() {
2824 let yaml = r#"items:
2825 - |
2826 First item content
2827 with multiple lines
2828
2829 - >
2830 Second item folded
2831 content
2832
2833 - regular_item
2834"#;
2835 let parsed = YamlFile::from_str(yaml).unwrap();
2836 assert_eq!(parsed.to_string(), yaml);
2837 }
2838
2839 #[test]
2840 fn test_block_scalar_empty_exact() {
2841 let yaml1 = r#"empty: |
2842
2843"#;
2844 let parsed1 = YamlFile::from_str(yaml1).unwrap();
2845 assert_eq!(parsed1.to_string(), yaml1);
2846
2847 let yaml2 = r#"empty_folded: >
2848
2849"#;
2850 let parsed2 = YamlFile::from_str(yaml2).unwrap();
2851 assert_eq!(parsed2.to_string(), yaml2);
2852 }
2853
2854 #[test]
2855 fn test_empty_documents_in_stream() {
2856 let yaml = "---\n---\nkey: value\n---\n...\n";
2858 let parsed = YamlFile::from_str(yaml).unwrap();
2859 assert_eq!(parsed.documents().count(), 3);
2860 assert_eq!(parsed.to_string(), yaml);
2861 }
2862
2863 #[test]
2864 fn test_mixed_document_end_markers() {
2865 let yaml = "---\nfirst: doc\n...\n---\nsecond: doc\n---\nthird: doc\n...\n";
2867 let parsed = YamlFile::from_str(yaml).unwrap();
2868 assert_eq!(parsed.documents().count(), 3);
2869 assert_eq!(parsed.to_string(), yaml);
2870 }
2871
2872 #[test]
2873 fn test_complex_document_stream() {
2874 let yaml = r#"%YAML 1.2
2875%TAG ! tag:example.com,2000:app/
2876---
2877template: &anchor
2878 key: !custom value
2879instance:
2880 <<: *anchor
2881 extra: data
2882...
2883%YAML 1.2
2884---
2885- item1
2886- item2: nested
2887...
2888---
2889literal: |
2890 Block content
2891 Multiple lines
2892folded: >
2893 Folded content
2894 on multiple lines
2895...
2896"#;
2897 let parsed = YamlFile::from_str(yaml).unwrap();
2898 assert_eq!(parsed.documents().count(), 3);
2899 assert_eq!(parsed.to_string(), yaml);
2900 }
2901
2902 #[test]
2903 fn test_number_format_parsing() {
2904 let yaml = YamlFile::from_str("value: 0b1010").unwrap();
2906 assert_eq!(yaml.to_string().trim(), "value: 0b1010");
2907
2908 let yaml = YamlFile::from_str("value: 0B1111").unwrap();
2909 assert_eq!(yaml.to_string().trim(), "value: 0B1111");
2910
2911 let yaml = YamlFile::from_str("value: 0o755").unwrap();
2913 assert_eq!(yaml.to_string().trim(), "value: 0o755");
2914
2915 let yaml = YamlFile::from_str("value: 0O644").unwrap();
2916 assert_eq!(yaml.to_string().trim(), "value: 0O644");
2917
2918 let yaml = YamlFile::from_str("value: -0b1010").unwrap();
2920 assert_eq!(yaml.to_string().trim(), "value: -0b1010");
2921
2922 let yaml = YamlFile::from_str("value: +0o755").unwrap();
2923 assert_eq!(yaml.to_string().trim(), "value: +0o755");
2924
2925 let yaml = YamlFile::from_str("value: 0755").unwrap();
2927 assert_eq!(yaml.to_string().trim(), "value: 0755");
2928
2929 let yaml = YamlFile::from_str("value: 0xFF").unwrap();
2930 assert_eq!(yaml.to_string().trim(), "value: 0xFF");
2931 }
2932
2933 #[test]
2934 fn test_invalid_number_formats_as_strings() {
2935 let yaml = YamlFile::from_str("value: 0b2").unwrap();
2937 assert_eq!(yaml.to_string().trim(), "value: 0b2");
2938
2939 let yaml = YamlFile::from_str("value: 0o9").unwrap();
2940 assert_eq!(yaml.to_string().trim(), "value: 0o9");
2941
2942 let yaml = YamlFile::from_str("value: 0xGH").unwrap();
2943 assert_eq!(yaml.to_string().trim(), "value: 0xGH");
2944 }
2945
2946 #[test]
2947 fn test_number_formats_in_complex_structures() {
2948 let input = r#"
2949config:
2950 permissions: 0o755
2951 flags: 0b11010
2952 color: 0xFF00FF
2953 count: 42"#;
2954
2955 let yaml = YamlFile::from_str(input).unwrap();
2956
2957 let doc = yaml.document().expect("Should have document");
2958 let mapping = doc.as_mapping().expect("Should be a mapping");
2959 let config_value = mapping.get("config").unwrap();
2960 let config = config_value.as_mapping().unwrap();
2961
2962 assert_eq!(
2963 config
2964 .get("permissions")
2965 .unwrap()
2966 .as_scalar()
2967 .unwrap()
2968 .as_string(),
2969 "0o755"
2970 );
2971 assert_eq!(
2972 config
2973 .get("flags")
2974 .unwrap()
2975 .as_scalar()
2976 .unwrap()
2977 .as_string(),
2978 "0b11010"
2979 );
2980 assert_eq!(
2981 config
2982 .get("color")
2983 .unwrap()
2984 .as_scalar()
2985 .unwrap()
2986 .as_string(),
2987 "0xFF00FF"
2988 );
2989 assert_eq!(
2990 config
2991 .get("count")
2992 .unwrap()
2993 .as_scalar()
2994 .unwrap()
2995 .as_string(),
2996 "42"
2997 );
2998
2999 let output = yaml.to_string();
3000 assert_eq!(output, input);
3001 }
3002
3003 #[test]
3004 fn test_editing_operations() {
3005 let yaml = YamlFile::from_str("name: old-name\nversion: 1.0.0").unwrap();
3007 if let Some(doc) = yaml.document() {
3008 doc.set("name", "new-name");
3009 doc.set("version", "2.0.0");
3010
3011 assert_eq!(doc.get_string("name"), Some("new-name".to_string()));
3013 assert_eq!(doc.get_string("version"), Some("2.0.0".to_string()));
3014
3015 let output = doc.to_string();
3017 assert_eq!(output, "name: new-name\nversion: 2.0.0");
3018 }
3019 }
3020
3021 #[test]
3022 fn test_timestamp_parsing_and_validation() {
3023 use crate::scalar::{ScalarType, ScalarValue};
3024
3025 let test_cases = vec![
3027 ("2001-12-14 21:59:43.10 -5", true), ("2001-12-15T02:59:43.1Z", true), ("2002-12-14", true), ("2001-12-14t21:59:43.10-05:00", true), ("2001-12-14 21:59:43.10", true), ("2001-12-14T21:59:43", true), ("not-a-timestamp", false), ("2001-13-14", false), ("2001-12-32", false), ];
3037
3038 for (timestamp_str, should_be_valid) in test_cases {
3039 let scalar = ScalarValue::parse(timestamp_str);
3040
3041 if should_be_valid {
3042 assert_eq!(
3043 scalar.scalar_type(),
3044 ScalarType::Timestamp,
3045 "Failed to recognize '{}' as timestamp",
3046 timestamp_str
3047 );
3048 assert!(scalar.is_timestamp());
3049
3050 assert_eq!(scalar.value(), timestamp_str);
3052
3053 let yaml = format!("timestamp: {}", timestamp_str);
3055 let parsed = YamlFile::from_str(&yaml).unwrap();
3056
3057 let doc = parsed.document().expect("Should have document");
3058 let mapping = doc.as_mapping().expect("Should be a mapping");
3059 assert_eq!(
3060 mapping
3061 .get("timestamp")
3062 .unwrap()
3063 .as_scalar()
3064 .unwrap()
3065 .as_string(),
3066 timestamp_str,
3067 "Timestamp '{}' not preserved",
3068 timestamp_str
3069 );
3070
3071 let output = parsed.to_string();
3072 assert_eq!(output, yaml);
3073 } else {
3074 assert_ne!(
3075 scalar.scalar_type(),
3076 ScalarType::Timestamp,
3077 "'{}' should not be recognized as timestamp",
3078 timestamp_str
3079 );
3080 }
3081 }
3082
3083 let yaml_with_timestamps = r#"
3085created_at: 2001-12-14 21:59:43.10 -5
3086updated_at: 2001-12-15T02:59:43.1Z
3087date_only: 2002-12-14
3088timestamps_in_array:
3089 - 2001-12-14 21:59:43.10 -5
3090 - 2001-12-15T02:59:43.1Z
3091 - 2002-12-14"#;
3092
3093 let parsed = YamlFile::from_str(yaml_with_timestamps).unwrap();
3094
3095 let doc = parsed.document().expect("Should have document");
3096 let mapping = doc.as_mapping().expect("Should be a mapping");
3097 assert_eq!(
3098 mapping
3099 .get("created_at")
3100 .unwrap()
3101 .as_scalar()
3102 .unwrap()
3103 .as_string(),
3104 "2001-12-14 21:59:43.10 -5"
3105 );
3106 assert_eq!(
3107 mapping
3108 .get("updated_at")
3109 .unwrap()
3110 .as_scalar()
3111 .unwrap()
3112 .as_string(),
3113 "2001-12-15T02:59:43.1Z"
3114 );
3115 assert_eq!(
3116 mapping
3117 .get("date_only")
3118 .unwrap()
3119 .as_scalar()
3120 .unwrap()
3121 .as_string(),
3122 "2002-12-14"
3123 );
3124
3125 let array_value = mapping.get("timestamps_in_array").unwrap();
3126 let array = array_value.as_sequence().unwrap();
3127 assert_eq!(
3128 array.get(0).unwrap().as_scalar().unwrap().as_string(),
3129 "2001-12-14 21:59:43.10 -5"
3130 );
3131 assert_eq!(
3132 array.get(1).unwrap().as_scalar().unwrap().as_string(),
3133 "2001-12-15T02:59:43.1Z"
3134 );
3135 assert_eq!(
3136 array.get(2).unwrap().as_scalar().unwrap().as_string(),
3137 "2002-12-14"
3138 );
3139
3140 let output = parsed.to_string();
3141 assert_eq!(output, yaml_with_timestamps);
3142 }
3143
3144 #[test]
3145 fn test_regex_support_in_yaml() {
3146 use crate::scalar::{ScalarType, ScalarValue};
3147
3148 let yaml_with_regex = r#"
3150patterns:
3151 digits: !!regex '\d+'
3152 word: !!regex '\w+'
3153 simple: !!regex 'test'"#;
3154
3155 let parsed = YamlFile::from_str(yaml_with_regex).unwrap();
3156
3157 let output = parsed.to_string();
3159 assert_eq!(output, yaml_with_regex);
3160
3161 let regex_scalar = ScalarValue::regex(r"^\d{4}-\d{2}-\d{2}$");
3163 assert_eq!(regex_scalar.scalar_type(), ScalarType::Regex);
3164 assert!(regex_scalar.is_regex());
3165 assert_eq!(regex_scalar.value(), r"^\d{4}-\d{2}-\d{2}$");
3166 assert_eq!(
3167 regex_scalar.to_yaml_string(),
3168 r"!!regex ^\d{4}-\d{2}-\d{2}$"
3169 );
3170
3171 let yaml_simple = "pattern: !!regex '\\d+'";
3173 let parsed_simple = YamlFile::from_str(yaml_simple).unwrap();
3174
3175 let doc_simple = parsed_simple.document().expect("Should have document");
3176 let mapping_simple = doc_simple.as_mapping().expect("Should be a mapping");
3177 let pattern_value = mapping_simple
3178 .get("pattern")
3179 .expect("Should have pattern key");
3180
3181 assert!(pattern_value.is_tagged(), "pattern should be a tagged node");
3183 let tagged = pattern_value.as_tagged().expect("Should be tagged");
3184 assert_eq!(tagged.tag(), Some("!!regex".to_string()));
3185 assert_eq!(tagged.as_string(), Some("\\d+".to_string()));
3186
3187 let output_simple = parsed_simple.to_string();
3188 assert_eq!(output_simple, yaml_simple);
3189
3190 let complex_regex = r#"validation: !!regex '^https?://(?:[-\w.])+(?:\:[0-9]+)?'"#;
3192 let parsed_complex = YamlFile::from_str(complex_regex).unwrap();
3193
3194 let doc_complex = parsed_complex.document().expect("Should have document");
3195 let mapping_complex = doc_complex.as_mapping().expect("Should be a mapping");
3196 let validation_value = mapping_complex
3197 .get("validation")
3198 .expect("Should have validation key");
3199
3200 assert!(
3201 validation_value.is_tagged(),
3202 "validation should be a tagged node"
3203 );
3204 let tagged_complex = validation_value.as_tagged().expect("Should be tagged");
3205 assert_eq!(tagged_complex.tag(), Some("!!regex".to_string()));
3206 assert_eq!(
3207 tagged_complex.as_string(),
3208 Some("^https?://(?:[-\\w.])+(?:\\:[0-9]+)?".to_string())
3209 );
3210
3211 let output_complex = parsed_complex.to_string();
3212 assert_eq!(output_complex, complex_regex);
3213 }
3214
3215 #[test]
3216 fn test_regex_in_different_contexts() {
3217 let yaml_sequence = r#"
3219patterns:
3220 - !!regex '\d+'
3221 - !!regex '[a-z]+'
3222 - normal_string
3223 - !!regex '.*@.*\..*'
3224"#;
3225
3226 let parsed_seq = YamlFile::from_str(yaml_sequence).unwrap();
3227
3228 let doc_seq = parsed_seq.document().expect("Should have document");
3229 let mapping_seq = doc_seq.as_mapping().expect("Should be a mapping");
3230 let patterns_value = mapping_seq
3231 .get("patterns")
3232 .expect("Should have patterns key");
3233 let patterns = patterns_value.as_sequence().expect("Should be a sequence");
3234
3235 assert!(patterns.get(0).unwrap().is_tagged());
3237 assert_eq!(
3238 patterns.get(0).unwrap().as_tagged().unwrap().tag(),
3239 Some("!!regex".to_string())
3240 );
3241
3242 assert!(patterns.get(1).unwrap().is_tagged());
3244 assert_eq!(
3245 patterns.get(1).unwrap().as_tagged().unwrap().tag(),
3246 Some("!!regex".to_string())
3247 );
3248
3249 assert!(!patterns.get(2).unwrap().is_tagged());
3251 assert_eq!(
3252 patterns.get(2).unwrap().as_scalar().unwrap().as_string(),
3253 "normal_string"
3254 );
3255
3256 assert!(patterns.get(3).unwrap().is_tagged());
3258 assert_eq!(
3259 patterns.get(3).unwrap().as_tagged().unwrap().tag(),
3260 Some("!!regex".to_string())
3261 );
3262
3263 let output_seq = parsed_seq.to_string();
3264 assert_eq!(output_seq, yaml_sequence);
3265
3266 let yaml_nested = r#"
3268validation:
3269 email: !!regex '[^@]+@[^@]+\.[a-z]+'
3270 phone: !!regex '\d{3}-\d{3}-\d{4}'
3271 config:
3272 debug_pattern: !!regex 'DEBUG:.*'
3273 nested:
3274 deep_pattern: !!regex 'ERROR'
3275"#;
3276
3277 let parsed_nested = YamlFile::from_str(yaml_nested).unwrap();
3278 let output_nested = parsed_nested.to_string();
3280 assert_eq!(output_nested, yaml_nested);
3281
3282 let yaml_mixed = r#"
3284mixed_collection:
3285 - name: "test"
3286 patterns: [!!regex '\d+', !!regex '\w+']
3287 - patterns:
3288 simple: !!regex 'test'
3289 complex: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
3290"#;
3291
3292 let parsed_mixed = YamlFile::from_str(yaml_mixed).unwrap();
3293 let output_mixed = parsed_mixed.to_string();
3295 assert_eq!(output_mixed, yaml_mixed);
3296
3297 let yaml_flow =
3299 r#"inline_patterns: {email: !!regex '[^@]+@[^@]+', phone: !!regex '\d{3}-\d{4}'}"#;
3300
3301 let parsed_flow = YamlFile::from_str(yaml_flow).unwrap();
3302 let output_flow = parsed_flow.to_string();
3304 assert_eq!(output_flow, yaml_flow);
3305 }
3306
3307 #[test]
3308 fn test_regex_parsing_edge_cases() {
3309 let yaml_quotes = r#"
3312patterns:
3313 single_quoted: !!regex 'pattern with spaces'
3314 double_quoted: !!regex "pattern_without_escapes"
3315 unquoted: !!regex simple_pattern
3316"#;
3317
3318 let parsed_quotes = YamlFile::from_str(yaml_quotes).unwrap();
3319
3320 let output_quotes = parsed_quotes.to_string();
3321 assert_eq!(output_quotes, yaml_quotes);
3322
3323 let yaml_empty = r#"
3325empty: !!regex ''
3326whitespace: !!regex ' '
3327tabs: !!regex ' '
3328"#;
3329
3330 let parsed_empty =
3331 YamlFile::from_str(yaml_empty).expect("Should parse empty/whitespace regex patterns");
3332
3333 let output_empty = parsed_empty.to_string();
3334 assert_eq!(output_empty, yaml_empty);
3335
3336 let yaml_special = r#"special: !!regex 'pattern_with_underscores_and_123'"#;
3338
3339 let parsed_special = YamlFile::from_str(yaml_special)
3340 .expect("Should parse regex with safe special characters");
3341
3342 let output_special = parsed_special.to_string();
3343 assert_eq!(output_special, yaml_special);
3344
3345 let yaml_verify = r#"test_pattern: !!regex '\d{4}-\d{2}-\d{2}'"#;
3347 let parsed_verify = YamlFile::from_str(yaml_verify).unwrap();
3348
3349 let output_verify = parsed_verify.to_string();
3350 assert_eq!(output_verify, yaml_verify);
3351
3352 let yaml_multiple = r#"
3354patterns:
3355 email: !!regex '^[^\s@]+@[^\s@]+\.[^\s@]+$'
3356 phone: !!regex '^\+?[\d\s\-\(\)]{10,}$'
3357 url: !!regex '^https?://[^\s]+$'
3358 ipv4: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
3359 uuid: !!regex '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
3360"#;
3361
3362 let parsed_multiple =
3363 YamlFile::from_str(yaml_multiple).expect("Should parse multiple regex patterns");
3364
3365 let output_multiple = parsed_multiple.to_string();
3367 assert_eq!(output_multiple, yaml_multiple);
3368 }
3369
3370 #[test]
3371 fn test_enhanced_comment_support() {
3372 let yaml1 = r#"flow_seq: [
3377 item1, # comment after item1
3378 item2, # comment after item2
3379 item3 # comment after item3
3380]"#;
3381 let parsed1 = YamlFile::from_str(yaml1).unwrap();
3382 let output1 = parsed1.to_string();
3383
3384 assert_eq!(output1, yaml1);
3385
3386 let yaml2 = r#"flow_map: {
3388 key1: val1, # comment after first pair
3389 key2: val2, # comment after second pair
3390 key3: val3 # comment after third pair
3391}"#;
3392 let parsed2 = YamlFile::from_str(yaml2).unwrap();
3393 let output2 = parsed2.to_string();
3394
3395 assert_eq!(output2, yaml2);
3396
3397 let yaml3 = r#"config:
3399 servers: [
3400 {name: web1, port: 80}, # Web server 1
3401 {name: web2, port: 80}, # Web server 2
3402 {name: db1, port: 5432} # Database server
3403 ] # End servers array"#;
3404 let parsed3 = YamlFile::from_str(yaml3).unwrap();
3405 let output3 = parsed3.to_string();
3406
3407 assert_eq!(output3, yaml3);
3408
3409 let yaml4 = r#"items:
3411 - first # First item comment
3412 - second # Second item comment
3413 # Comment between items
3414 - third # Third item comment"#;
3415 let parsed4 = YamlFile::from_str(yaml4).unwrap();
3416 let output4 = parsed4.to_string();
3417
3418 assert_eq!(output4, yaml4);
3419
3420 for yaml in [yaml1, yaml2, yaml3, yaml4] {
3422 let parsed = YamlFile::from_str(yaml).unwrap();
3423 let output = parsed.to_string();
3424 let reparsed = YamlFile::from_str(&output);
3425 assert!(reparsed.is_ok(), "Round-trip parsing should succeed");
3426 }
3427 }
3428
3429 #[test]
3430 fn test_insert_after_with_sequence() {
3431 let yaml = "name: project\nversion: 1.0.0";
3432 let parsed = YamlFile::from_str(yaml).unwrap();
3433 let doc = parsed.document().expect("Should have a document");
3434
3435 let features = SequenceBuilder::new()
3437 .item("feature1")
3438 .item("feature2")
3439 .item("feature3")
3440 .build_document()
3441 .as_sequence()
3442 .unwrap();
3443 let success = doc.insert_after("name", "features", features);
3444 assert!(success, "insert_after should succeed");
3445
3446 let output = doc.to_string();
3447
3448 let expected = r#"name: project
3450features:
3451 - feature1
3452 - feature2
3453 - feature3
3454version: 1.0.0"#;
3455 assert_eq!(output.trim(), expected);
3456
3457 let reparsed = YamlFile::from_str(&output);
3458 assert!(reparsed.is_ok(), "Output should be valid YAML");
3459 }
3460
3461 #[test]
3462 fn test_insert_before_with_mapping() {
3463 let yaml = "name: project\nversion: 1.0.0";
3464 let parsed = YamlFile::from_str(yaml).unwrap();
3465 let doc = parsed.document().expect("Should have a document");
3466
3467 let database = MappingBuilder::new()
3469 .pair("host", "localhost")
3470 .pair("port", 5432)
3471 .pair("database", "mydb")
3472 .build_document()
3473 .as_mapping()
3474 .unwrap();
3475 let success = doc.insert_before("version", "database", database);
3476 assert!(success, "insert_before should succeed");
3477
3478 let output = doc.to_string();
3479
3480 let expected = r#"name: project
3482database:
3483 host: localhost
3484 port: 5432
3485 database: mydb
3486version: 1.0.0"#;
3487 assert_eq!(output.trim(), expected);
3488
3489 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
3491 let reparsed_doc = reparsed.document().expect("Should have document");
3492 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
3493
3494 let db_value = reparsed_mapping
3495 .get("database")
3496 .expect("Should have database key");
3497 let db_mapping = db_value.as_mapping().expect("database should be mapping");
3498 assert_eq!(
3499 db_mapping
3500 .get("host")
3501 .unwrap()
3502 .as_scalar()
3503 .unwrap()
3504 .as_string(),
3505 "localhost"
3506 );
3507 assert_eq!(
3508 db_mapping
3509 .get("port")
3510 .unwrap()
3511 .as_scalar()
3512 .unwrap()
3513 .as_string(),
3514 "5432"
3515 );
3516 }
3517
3518 #[test]
3519 fn test_insert_at_index_with_mixed_types() {
3520 let yaml = "name: project";
3521 let parsed = YamlFile::from_str(yaml).unwrap();
3522 let doc = parsed.document().expect("Should have a document");
3523
3524 doc.insert_at_index(1, "version", "1.0.0");
3526 doc.insert_at_index(2, "active", true);
3527 doc.insert_at_index(3, "count", 42);
3528
3529 let features = SequenceBuilder::new()
3530 .item("auth")
3531 .item("logging")
3532 .build_document()
3533 .as_sequence()
3534 .unwrap();
3535 doc.insert_at_index(4, "features", features);
3536
3537 let output = doc.to_string();
3538
3539 let expected = r#"name: project
3540version: 1.0.0
3541active: true
3542count: 42
3543features:
3544- auth
3545- logging"#;
3546 assert_eq!(output.trim(), expected);
3547
3548 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
3549 let reparsed_doc = reparsed.document().expect("Should have document");
3550 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
3551
3552 assert_eq!(
3553 reparsed_mapping
3554 .get("version")
3555 .unwrap()
3556 .as_scalar()
3557 .unwrap()
3558 .as_string(),
3559 "1.0.0"
3560 );
3561 assert_eq!(
3562 reparsed_mapping.get("active").unwrap().to_bool(),
3563 Some(true)
3564 );
3565 assert_eq!(reparsed_mapping.get("count").unwrap().to_i64(), Some(42));
3566
3567 let features_value = reparsed_mapping.get("features").unwrap();
3568 let features = features_value.as_sequence().unwrap();
3569 assert_eq!(
3570 features.get(0).unwrap().as_scalar().unwrap().as_string(),
3571 "auth"
3572 );
3573 assert_eq!(
3574 features.get(1).unwrap().as_scalar().unwrap().as_string(),
3575 "logging"
3576 );
3577 }
3578
3579 #[test]
3580 fn test_insert_with_null_and_special_scalars() {
3581 let yaml = "name: project";
3582 let parsed = YamlFile::from_str(yaml).unwrap();
3583 let doc = parsed.document().expect("Should have a document");
3584
3585 doc.insert_after("name", "null_value", ScalarValue::null());
3587 doc.insert_after("null_value", "empty_string", "");
3588 doc.insert_after("empty_string", "number", 1.234);
3589 doc.insert_after("number", "boolean", false);
3590
3591 let output = doc.to_string();
3592
3593 let expected = r#"name: project
3594null_value: null
3595empty_string: ''
3596number: 1.234
3597boolean: false"#;
3598 assert_eq!(output.trim(), expected);
3599
3600 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
3601 let reparsed_doc = reparsed.document().expect("Should have document");
3602 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
3603
3604 assert_eq!(
3605 reparsed_mapping
3606 .get("name")
3607 .unwrap()
3608 .as_scalar()
3609 .unwrap()
3610 .as_string(),
3611 "project"
3612 );
3613 assert!(reparsed_mapping
3614 .get("null_value")
3615 .unwrap()
3616 .as_scalar()
3617 .unwrap()
3618 .is_null());
3619 assert_eq!(
3620 reparsed_mapping
3621 .get("empty_string")
3622 .unwrap()
3623 .as_scalar()
3624 .unwrap()
3625 .as_string(),
3626 ""
3627 );
3628 assert_eq!(
3629 reparsed_mapping.get("number").unwrap().to_f64(),
3630 Some(1.234)
3631 );
3632 assert_eq!(
3633 reparsed_mapping.get("boolean").unwrap().to_bool(),
3634 Some(false)
3635 );
3636 }
3637
3638 #[test]
3639 fn test_insert_ordering_preservation() {
3640 let yaml = "first: 1\nthird: 3\nfifth: 5";
3641 let parsed = YamlFile::from_str(yaml).unwrap();
3642 let doc = parsed.document().expect("Should have a document");
3643
3644 doc.insert_after("first", "second", 2);
3646 doc.insert_before("fifth", "fourth", 4);
3647
3648 let output = doc.to_string();
3649
3650 let expected = r#"first: 1
3652second: 2
3653third: 3
3654fourth: 4
3655fifth: 5"#;
3656 assert_eq!(output.trim(), expected);
3657
3658 let reparsed = YamlFile::from_str(&output);
3659 assert!(reparsed.is_ok(), "Output should be valid YAML");
3660 }
3661
3662 #[test]
3663 fn test_insert_with_yamlvalue_positioning() {
3664 let yaml = "name: project\nversion: 1.0\nactive: true";
3665 let parsed = YamlFile::from_str(yaml).unwrap();
3666 let doc = parsed.document().expect("Should have a document");
3667
3668 let success = doc.insert_after("name", "description", "A sample project");
3672 assert!(success, "Should find string key");
3673
3674 let success = doc.insert_after(1.0, "build", "gradle");
3676 assert!(
3677 !success,
3678 "Should not find numeric key (1.0) when actual key is string 'version'"
3679 );
3680
3681 let success = doc.insert_after(true, "test", "enabled");
3683 assert!(
3684 !success,
3685 "Should not find boolean key (true) when actual key is string 'active'"
3686 );
3687
3688 let bool_string_key = "true";
3690 let success = doc.insert_after(bool_string_key, "test_mode", "development");
3691 assert!(!success, "Should not find 'true' key when value is true");
3692
3693 let output = doc.to_string();
3694
3695 let expected = "name: project\ndescription: A sample project\nversion: 1.0\nactive: true";
3697 assert_eq!(output, expected);
3698 }
3699
3700 #[test]
3701 fn test_insert_complex_nested_structure() {
3702 let yaml = "name: project";
3703 let parsed = YamlFile::from_str(yaml).unwrap();
3704 let doc = parsed.document().expect("Should have a document");
3705
3706 let config = MappingBuilder::new()
3708 .mapping("server", |m| m.pair("host", "0.0.0.0").pair("port", 8080))
3709 .pair("debug", true)
3710 .sequence("features", |s| s.item("api").item("web").item("cli"))
3711 .build_document()
3712 .as_mapping()
3713 .unwrap();
3714
3715 doc.insert_after("name", "config", config);
3716
3717 let output = doc.to_string();
3718
3719 let expected = "name: project\nconfig:\n server: \n host: 0.0.0.0\n port: 8080\n debug: true\n features: \n - api\n - web\n - cli\n";
3721 assert_eq!(output, expected);
3722
3723 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
3724 let reparsed_doc = reparsed.document().expect("Should have document");
3725 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
3726
3727 let config_value = reparsed_mapping
3728 .get("config")
3729 .expect("Should have config key");
3730 let config_mapping = config_value.as_mapping().expect("config should be mapping");
3731
3732 let server_value = config_mapping
3733 .get("server")
3734 .expect("Should have server key");
3735 let server = server_value.as_mapping().expect("server should be mapping");
3736 assert_eq!(
3737 server.get("host").unwrap().as_scalar().unwrap().as_string(),
3738 "0.0.0.0"
3739 );
3740 assert_eq!(server.get("port").unwrap().to_i64(), Some(8080));
3741
3742 assert_eq!(config_mapping.get("debug").unwrap().to_bool(), Some(true));
3743
3744 let features_value = config_mapping
3745 .get("features")
3746 .expect("Should have features key");
3747 let features = features_value
3748 .as_sequence()
3749 .expect("features should be sequence");
3750 assert_eq!(features.len(), 3);
3751 assert_eq!(
3752 features.get(0).unwrap().as_scalar().unwrap().as_string(),
3753 "api"
3754 );
3755 assert_eq!(
3756 features.get(1).unwrap().as_scalar().unwrap().as_string(),
3757 "web"
3758 );
3759 assert_eq!(
3760 features.get(2).unwrap().as_scalar().unwrap().as_string(),
3761 "cli"
3762 );
3763 }
3764
3765 #[test]
3766 fn test_insert_with_yaml_sets() {
3767 let yaml = "name: project";
3768 let parsed = YamlFile::from_str(yaml).unwrap();
3769 let doc = parsed.document().expect("Should have a document");
3770
3771 let mut tags = std::collections::BTreeSet::new();
3773 tags.insert("production".to_string());
3774 tags.insert("database".to_string());
3775 tags.insert("web".to_string());
3776
3777 let yaml_set = YamlValue::from_set(tags);
3778 doc.insert_after("name", "tags", yaml_set);
3779
3780 let output = doc.to_string();
3781
3782 let expected =
3784 "name: project\ntags: !!set\n database: null\n production: null\n web: null\n";
3785 assert_eq!(output, expected);
3786
3787 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
3788 let reparsed_doc = reparsed.document().expect("Should have document");
3789 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
3790
3791 let tags_value = reparsed_mapping.get("tags").expect("Should have tags key");
3792 assert!(tags_value.is_tagged(), "tags should be tagged");
3793 let tagged = tags_value.as_tagged().expect("Should be tagged node");
3794 assert_eq!(tagged.tag(), Some("!!set".to_string()));
3795
3796 let tags_syntax = tagged.syntax();
3799 let tags_mapping = tags_syntax
3800 .children()
3801 .find_map(Mapping::cast)
3802 .expect("Set should have mapping child");
3803
3804 assert!(tags_mapping
3805 .get("database")
3806 .unwrap()
3807 .as_scalar()
3808 .unwrap()
3809 .is_null());
3810 assert!(tags_mapping
3811 .get("production")
3812 .unwrap()
3813 .as_scalar()
3814 .unwrap()
3815 .is_null());
3816 assert!(tags_mapping
3817 .get("web")
3818 .unwrap()
3819 .as_scalar()
3820 .unwrap()
3821 .is_null());
3822 }
3823
3824 #[test]
3825 fn test_insert_with_ordered_mappings() {
3826 let yaml = "name: project";
3827 let parsed = YamlFile::from_str(yaml).unwrap();
3828 let doc = parsed.document().expect("Should have a document");
3829
3830 let ordered_steps = vec![
3832 ("compile".to_string(), YamlValue::from("gcc main.c")),
3833 ("test".to_string(), YamlValue::from("./a.out test")),
3834 (
3835 "package".to_string(),
3836 YamlValue::from("tar -czf app.tar.gz ."),
3837 ),
3838 ];
3839
3840 let yaml_omap = YamlValue::from_ordered_mapping(ordered_steps);
3841 doc.insert_after("name", "build_steps", yaml_omap);
3842
3843 let output = doc.to_string();
3844
3845 let expected = "name: project\nbuild_steps: !!omap\n - compile: gcc main.c\n - test: ./a.out test\n - package: tar -czf app.tar.gz .\n";
3847 assert_eq!(output, expected);
3848
3849 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
3850 let reparsed_doc = reparsed.document().expect("Should have document");
3851 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
3852
3853 let steps_value = reparsed_mapping
3854 .get("build_steps")
3855 .expect("Should have build_steps key");
3856 assert!(steps_value.is_tagged(), "build_steps should be tagged");
3857 let tagged = steps_value.as_tagged().expect("Should be tagged node");
3858 assert_eq!(tagged.tag(), Some("!!omap".to_string()));
3859
3860 let steps_syntax = tagged.syntax();
3862 let steps_seq = steps_syntax
3863 .children()
3864 .find_map(Sequence::cast)
3865 .expect("Omap should have sequence child");
3866
3867 assert_eq!(steps_seq.len(), 3);
3868 let compile_value = steps_seq.get(0).unwrap();
3870 let compile_item = compile_value.as_mapping().expect("Should be mapping");
3871 assert_eq!(
3872 compile_item
3873 .get("compile")
3874 .unwrap()
3875 .as_scalar()
3876 .unwrap()
3877 .as_string(),
3878 "gcc main.c"
3879 );
3880
3881 let test_value = steps_seq.get(1).unwrap();
3882 let test_item = test_value.as_mapping().expect("Should be mapping");
3883 assert_eq!(
3884 test_item
3885 .get("test")
3886 .unwrap()
3887 .as_scalar()
3888 .unwrap()
3889 .as_string(),
3890 "./a.out test"
3891 );
3892
3893 let package_value = steps_seq.get(2).unwrap();
3894 let package_item = package_value.as_mapping().expect("Should be mapping");
3895 assert_eq!(
3896 package_item
3897 .get("package")
3898 .unwrap()
3899 .as_scalar()
3900 .unwrap()
3901 .as_string(),
3902 "tar -czf app.tar.gz ."
3903 );
3904 }
3905
3906 #[test]
3907 fn test_insert_with_pairs() {
3908 let yaml = "name: project";
3909 let parsed = YamlFile::from_str(yaml).unwrap();
3910 let doc = parsed.document().expect("Should have a document");
3911
3912 let connection_attempts = vec![
3914 ("server".to_string(), YamlValue::from("primary.db")),
3915 ("server".to_string(), YamlValue::from("secondary.db")), ("server".to_string(), YamlValue::from("tertiary.db")), ("timeout".to_string(), YamlValue::from(30)),
3918 ];
3919
3920 let yaml_pairs = YamlValue::from_pairs(connection_attempts);
3921 doc.insert_after("name", "connections", yaml_pairs);
3922
3923 let output = doc.to_string();
3924
3925 let expected = "name: project\nconnections: !!pairs\n - server: primary.db\n - server: secondary.db\n - server: tertiary.db\n - timeout: 30\n";
3927 assert_eq!(output, expected);
3928
3929 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
3930 let reparsed_doc = reparsed.document().expect("Should have document");
3931 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
3932
3933 let connections_value = reparsed_mapping
3934 .get("connections")
3935 .expect("Should have connections key");
3936 assert!(
3937 connections_value.is_tagged(),
3938 "connections should be tagged"
3939 );
3940 let tagged = connections_value
3941 .as_tagged()
3942 .expect("Should be tagged node");
3943 assert_eq!(tagged.tag(), Some("!!pairs".to_string()));
3944
3945 let connections_syntax = tagged.syntax();
3947 let connections_seq = connections_syntax
3948 .children()
3949 .find_map(Sequence::cast)
3950 .expect("Pairs should have sequence child");
3951
3952 assert_eq!(connections_seq.len(), 4);
3953
3954 let server1_val = connections_seq.get(0).unwrap();
3955 let server1 = server1_val.as_mapping().expect("Should be mapping");
3956 assert_eq!(
3957 server1
3958 .get("server")
3959 .unwrap()
3960 .as_scalar()
3961 .unwrap()
3962 .as_string(),
3963 "primary.db"
3964 );
3965
3966 let server2_val = connections_seq.get(1).unwrap();
3967 let server2 = server2_val.as_mapping().expect("Should be mapping");
3968 assert_eq!(
3969 server2
3970 .get("server")
3971 .unwrap()
3972 .as_scalar()
3973 .unwrap()
3974 .as_string(),
3975 "secondary.db"
3976 );
3977
3978 let server3_val = connections_seq.get(2).unwrap();
3979 let server3 = server3_val.as_mapping().expect("Should be mapping");
3980 assert_eq!(
3981 server3
3982 .get("server")
3983 .unwrap()
3984 .as_scalar()
3985 .unwrap()
3986 .as_string(),
3987 "tertiary.db"
3988 );
3989
3990 let timeout_val = connections_seq.get(3).unwrap();
3991 let timeout = timeout_val.as_mapping().expect("Should be mapping");
3992 assert_eq!(timeout.get("timeout").unwrap().to_i64(), Some(30));
3993 }
3994
3995 #[test]
3996 fn test_insert_with_empty_collections() {
3997 let yaml1 = "name: project";
4001 let parsed1 = YamlFile::from_str(yaml1).unwrap();
4002 let doc1 = parsed1.document().expect("Should have a document");
4003
4004 let empty_seq_yaml = YamlFile::from_str("[]").unwrap();
4006 let empty_list = empty_seq_yaml.document().unwrap().as_sequence().unwrap();
4007
4008 doc1.insert_after("name", "empty_list", empty_list);
4009 let output1 = doc1.to_string();
4010 assert_eq!(output1, "name: project\nempty_list: []\n");
4011
4012 let yaml2 = "name: project";
4014 let parsed2 = YamlFile::from_str(yaml2).unwrap();
4015 let doc2 = parsed2.document().expect("Should have a document");
4016
4017 let empty_map_yaml = YamlFile::from_str("{}").unwrap();
4019 let empty_map = empty_map_yaml.document().unwrap().as_mapping().unwrap();
4020
4021 doc2.insert_after("name", "empty_map", empty_map);
4022 let output2 = doc2.to_string();
4023 assert_eq!(output2, "name: project\nempty_map: {}\n");
4024
4025 let yaml3 = "name: project";
4027 let parsed3 = YamlFile::from_str(yaml3).unwrap();
4028 let doc3 = parsed3.document().expect("Should have a document");
4029 doc3.insert_after("name", "empty_set", YamlValue::set());
4030 let output3 = doc3.to_string();
4031 assert_eq!(output3, "name: project\nempty_set: !!set {}\n");
4032
4033 assert!(
4035 YamlFile::from_str(&output1).is_ok(),
4036 "Empty sequence output should be valid YAML"
4037 );
4038 assert!(
4039 YamlFile::from_str(&output2).is_ok(),
4040 "Empty mapping output should be valid YAML"
4041 );
4042 assert!(
4043 YamlFile::from_str(&output3).is_ok(),
4044 "Empty set output should be valid YAML"
4045 );
4046 }
4047
4048 #[test]
4049 fn test_insert_with_deeply_nested_sequences() {
4050 let yaml = "name: project";
4051 let parsed = YamlFile::from_str(yaml).unwrap();
4052 let doc = parsed.document().expect("Should have a document");
4053
4054 let nested_data = SequenceBuilder::new()
4056 .item("item1")
4057 .mapping(|m| {
4058 m.sequence("config", |s| s.item("debug").item(true).item(8080))
4059 .pair("name", "service")
4060 })
4061 .item(42)
4062 .build_document()
4063 .as_sequence()
4064 .unwrap();
4065
4066 doc.insert_after("name", "nested_data", nested_data);
4067
4068 let output = doc.to_string();
4069
4070 let expected = "name: project\nnested_data:\n - item1\n - \n config: \n - debug\n - true\n - 8080\n name: service- 42\n";
4071 assert_eq!(output, expected);
4072
4073 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
4074 let reparsed_doc = reparsed.document().expect("Should have document");
4075 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
4076
4077 let nested_value = reparsed_mapping
4078 .get("nested_data")
4079 .expect("Should have nested_data key");
4080 let nested_seq = nested_value.as_sequence().expect("Should be sequence");
4081 assert_eq!(nested_seq.len(), 2);
4084
4085 assert_eq!(
4087 nested_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
4088 "item1"
4089 );
4090
4091 let second_item = nested_seq.get(1).unwrap();
4093 let second_mapping = second_item.as_mapping().expect("Should be mapping");
4094
4095 let config_value = second_mapping.get("config").expect("Should have config");
4096 let config_seq = config_value
4097 .as_sequence()
4098 .expect("config should be sequence");
4099 assert_eq!(config_seq.len(), 3);
4100 assert_eq!(
4101 config_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
4102 "debug"
4103 );
4104 assert_eq!(config_seq.get(1).unwrap().to_bool(), Some(true));
4105 assert_eq!(config_seq.get(2).unwrap().to_i64(), Some(8080));
4106
4107 assert_eq!(
4109 second_mapping
4110 .get("name")
4111 .unwrap()
4112 .as_scalar()
4113 .unwrap()
4114 .as_string(),
4115 "service- 42"
4116 );
4117 }
4118
4119 #[test]
4120 fn test_ast_preservation_comments_in_mapping() {
4121 let yaml = r#"---
4123# Header comment
4124key1: value1 # Inline comment 1
4125# Middle comment
4126key2: value2 # Inline comment 2
4127# Footer comment
4128"#;
4129 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
4130
4131 if let Some(mapping) = doc.as_mapping() {
4133 mapping.move_after("key1", "new_key", "new_value");
4134 }
4135
4136 let result = doc.to_string();
4137
4138 let expected = r#"---
4139# Header comment
4140key1: value1 # Inline comment 1
4141new_key: new_value
4142# Middle comment
4143key2: value2 # Inline comment 2
4144# Footer comment
4145"#;
4146 assert_eq!(result, expected);
4147 }
4148
4149 #[test]
4150 fn test_ast_preservation_whitespace_in_mapping() {
4151 let yaml = r#"---
4153key1: value1
4154
4155
4156key2: value2
4157"#;
4158 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
4159
4160 if let Some(mapping) = doc.as_mapping() {
4162 mapping.move_after("key1", "new_key", "new_value");
4163 }
4164
4165 let result = doc.to_string();
4166
4167 let expected = r#"---
4168key1: value1
4169new_key: new_value
4170
4171
4172key2: value2
4173"#;
4174 assert_eq!(result, expected);
4175 }
4176
4177 #[test]
4178 fn test_ast_preservation_complex_structure() {
4179 let yaml = r#"---
4181# Configuration file
4182database: # Database settings
4183 host: localhost # Default host
4184 port: 5432 # Default port
4185 # Connection pool settings
4186 pool:
4187 min: 1 # Minimum connections
4188 max: 10 # Maximum connections
4189
4190# Server configuration
4191server:
4192 port: 8080 # HTTP port
4193"#;
4194 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
4195
4196 eprintln!("===========\n");
4197
4198 if let Some(mapping) = doc.as_mapping() {
4200 mapping.move_after("database", "logging", "debug");
4201 }
4202
4203 let result = doc.to_string();
4204
4205 let expected = r#"---
4207# Configuration file
4208database: # Database settings
4209 host: localhost # Default host
4210 port: 5432 # Default port
4211 # Connection pool settings
4212 pool:
4213 min: 1 # Minimum connections
4214 max: 10 # Maximum connections
4215
4216logging: debug
4217# Server configuration
4218server:
4219 port: 8080 # HTTP port
4220"#;
4221 assert_eq!(result, expected);
4222 }
4223
4224 mod lookahead_tests {
4226 use crate::lex::lex;
4227 use crate::parser::has_implicit_mapping_pattern;
4228
4229 fn check_implicit_mapping(yaml: &str) -> bool {
4231 let flow = format!("[{yaml}]");
4232 let tokens = lex(&flow);
4233 has_implicit_mapping_pattern(tokens[1..tokens.len() - 1].iter().map(|(kind, _)| *kind))
4234 }
4235
4236 #[test]
4237 fn test_simple_implicit_mapping() {
4238 assert!(check_implicit_mapping("'key' : value"));
4241 }
4242
4243 #[test]
4244 fn test_simple_value_no_mapping() {
4245 assert!(!check_implicit_mapping("value ,"));
4247 }
4248
4249 #[test]
4250 fn test_value_with_comma() {
4251 assert!(!check_implicit_mapping("value , more"));
4253 }
4254
4255 #[test]
4256 fn test_nested_sequence_as_key() {
4257 assert!(check_implicit_mapping("[a, b]: value"));
4259 }
4260
4261 #[test]
4262 fn test_nested_mapping_not_implicit() {
4263 assert!(!check_implicit_mapping("{a: 1}, b"));
4265 }
4266
4267 #[test]
4268 fn test_deeply_nested_key() {
4269 assert!(check_implicit_mapping("[[a]]: value"));
4271 }
4272
4273 #[test]
4274 fn test_with_whitespace() {
4275 assert!(check_implicit_mapping("'key' : value"));
4277 }
4278
4279 #[test]
4280 fn test_mapping_value_in_sequence() {
4281 assert!(!check_implicit_mapping("a, {key: value}"));
4284 }
4285
4286 #[test]
4287 fn test_multiple_colons() {
4288 assert!(check_implicit_mapping("key1: value1, key2: value2"));
4290 }
4291
4292 #[test]
4293 fn test_url_not_mapping() {
4294 assert!(!check_implicit_mapping("http://example.com"));
4297 }
4298 }
4299
4300 #[test]
4301 fn test_from_str_relaxed_valid() {
4302 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: value\n");
4303 assert!(errors.is_empty());
4304 let doc = yaml_file.document().unwrap();
4305 let mapping = doc.as_mapping().unwrap();
4306 assert_eq!(
4307 mapping.get("key").unwrap().as_scalar().unwrap().to_string(),
4308 "value"
4309 );
4310 }
4311
4312 #[test]
4313 fn test_from_str_relaxed_unclosed_flow_sequence() {
4314 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: [a, b");
4315 assert!(!errors.is_empty());
4316 let doc = yaml_file.document().unwrap();
4318 assert!(doc.as_mapping().is_some());
4319 }
4320
4321 #[test]
4322 fn test_from_str_relaxed_unclosed_flow_mapping() {
4323 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: {a: 1");
4324 assert!(!errors.is_empty());
4325 let doc = yaml_file.document().unwrap();
4326 assert!(doc.as_mapping().is_some());
4327 }
4328
4329 #[test]
4330 fn test_from_str_relaxed_preserves_valid_content() {
4331 let input = "good: value\nbad: [unclosed\nalso_good: 42\n";
4332 let (yaml_file, errors) = YamlFile::from_str_relaxed(input);
4333 assert!(!errors.is_empty());
4334 let doc = yaml_file.document().unwrap();
4335 let mapping = doc.as_mapping().unwrap();
4336 assert_eq!(
4337 mapping
4338 .get("good")
4339 .unwrap()
4340 .as_scalar()
4341 .unwrap()
4342 .to_string(),
4343 "value"
4344 );
4345 }
4346
4347 #[test]
4348 fn test_from_str_relaxed_empty_input() {
4349 let (yaml_file, errors) = YamlFile::from_str_relaxed("");
4350 assert!(errors.is_empty());
4351 assert!(yaml_file.document().is_none());
4352 }
4353
4354 #[test]
4355 fn test_parse_flow_sequence_with_stray_close_brace_does_not_hang() {
4356 let parse = crate::Parse::parse_yaml("[}9[Z");
4359 let _ = parse.tree();
4360 assert!(parse.has_errors());
4361 }
4362
4363 #[test]
4364 fn test_parse_flow_mapping_with_stray_close_bracket_does_not_hang() {
4365 let parse = crate::Parse::parse_yaml("{]");
4367 let _ = parse.tree();
4368 assert!(parse.has_errors());
4369 }
4370
4371 #[test]
4372 fn test_parse_explicit_key_multiline_with_comment_does_not_hang() {
4373 let input = "?\u{18}\0\0\0\0\0\0\n\t#\t\t?\t";
4377 let parse = crate::Parse::parse_yaml(input);
4378 let _ = parse.tree();
4379 }
4380
4381 #[test]
4382 fn test_parse_deeply_nested_flow_mapping_does_not_blow_up() {
4383 let input = "{".repeat(2000);
4386 let parse = crate::Parse::parse_yaml(&input);
4387 let _ = parse.tree();
4388 assert!(parse.has_errors());
4389 }
4390
4391 #[test]
4392 fn test_parse_deeply_nested_flow_sequence_does_not_blow_up() {
4393 let input = "[".repeat(2000);
4394 let parse = crate::Parse::parse_yaml(&input);
4395 let _ = parse.tree();
4396 assert!(parse.has_errors());
4397 }
4398
4399 #[test]
4400 fn test_parse_deeply_nested_block_mapping_does_not_blow_up() {
4401 let mut input = String::new();
4402 for i in 0..2000 {
4403 input.push_str(&" ".repeat(i));
4404 input.push_str("k:\n");
4405 }
4406 input.push_str(&" ".repeat(2000));
4407 input.push_str("v\n");
4408 let parse = crate::Parse::parse_yaml(&input);
4409 let _ = parse.tree();
4410 assert!(parse.has_errors());
4411 }
4412
4413 #[test]
4414 fn test_parse_stacked_anchors_does_not_blow_up() {
4415 let input = format!("{}v", "&x ".repeat(2000));
4416 let parse = crate::Parse::parse_yaml(&input);
4417 let _ = parse.tree();
4418 assert!(parse.has_errors());
4419 }
4420
4421 #[test]
4422 fn test_parse_explicit_key_followed_by_question_in_value_does_not_hang() {
4423 let input = "<<?: ?: \n<<\n ?: : 0\n:@";
4427 let parse = crate::Parse::parse_yaml(input);
4428 let _ = parse.tree();
4429 }
4430
4431 #[test]
4432 fn test_parse_complex_key_mapping_with_stray_close_bracket_does_not_hang() {
4433 let input = "[%:%:]: \n]: \n\n";
4436 let parse = crate::Parse::parse_yaml(input);
4437 let _ = parse.tree();
4438 }
4439
4440 #[test]
4441 fn test_parse_mapping_with_stray_close_brace_does_not_hang() {
4442 let input = "\0:\n\n}:\n\n{";
4447 let parse = crate::Parse::parse_yaml(input);
4448 let _ = parse.tree();
4449 assert!(parse.has_errors());
4450 }
4451
4452 #[test]
4455 fn test_tag_only_key() {
4456 let yaml = YamlFile::from_str("!!str a: b\n").unwrap();
4459 let doc = yaml.document().unwrap();
4460 let mapping = doc.as_mapping().expect("root should be a mapping");
4461 assert_eq!(mapping.len(), 1);
4462 assert_eq!(
4463 mapping.get("a").unwrap().to_string().trim(),
4464 "b",
4465 "value for 'a' should be 'b'"
4466 );
4467 }
4468
4469 #[test]
4470 fn test_tag_and_anchor_key() {
4471 let yaml = YamlFile::from_str("!!str &a1 \"foo\": bar\n").unwrap();
4475 let doc = yaml.document().unwrap();
4476 let mapping = doc.as_mapping().expect("root should be a mapping");
4477 assert_eq!(mapping.len(), 1);
4478 assert_eq!(mapping.get("foo").unwrap().to_string().trim(), "bar");
4479 }
4480
4481 #[test]
4482 fn test_anchor_only_key() {
4483 let yaml = YamlFile::from_str("&anchor a: b\n").unwrap();
4485 let doc = yaml.document().unwrap();
4486 let mapping = doc.as_mapping().expect("root should be a mapping");
4487 assert_eq!(mapping.len(), 1);
4488 assert_eq!(mapping.get("a").unwrap().to_string().trim(), "b");
4489 }
4490
4491 #[test]
4492 fn test_tag_annotated_value_still_wraps() {
4493 let yaml = YamlFile::from_str("c: !!int 42\n").unwrap();
4496 let doc = yaml.document().unwrap();
4497 let mapping = doc.as_mapping().unwrap();
4498 let value = mapping.get("c").unwrap();
4502 assert!(
4503 value.to_string().contains("42"),
4504 "value for 'c' should contain the scalar 42: {:?}",
4505 value.to_string()
4506 );
4507 }
4508
4509 #[test]
4510 fn test_explicit_key_produces_zero_width_null_value() {
4511 use crate::lex::SyntaxKind;
4518 let yaml = YamlFile::from_str("? a\n").unwrap();
4519 let doc = yaml.document().unwrap();
4520 let mapping = doc.as_mapping().expect("should parse as a mapping");
4521 assert_eq!(mapping.len(), 1);
4522
4523 let entry = mapping.entries().next().expect("one entry");
4525 let syntax = entry.syntax();
4526 let terminal = syntax
4527 .last_token()
4528 .expect("MAPPING_ENTRY has at least one token");
4529 assert!(
4530 terminal.kind() == SyntaxKind::NULL && terminal.text().is_empty(),
4531 "expected zero-width NULL terminal, got {:?} = {:?}",
4532 terminal.kind(),
4533 terminal.text()
4534 );
4535 }
4536
4537 #[test]
4538 fn test_block_scalar_under_tagged_node_validates() {
4539 let yaml_text = "k: !!str >\n wrapped\n paragraph\n";
4544 let yaml = YamlFile::from_str(yaml_text).unwrap();
4545 let doc = yaml.document().unwrap();
4546 let mapping = doc.as_mapping().unwrap();
4547 let value = mapping.get("k").expect("k should exist");
4548 assert!(
4549 value.to_string().contains("wrapped"),
4550 "tagged block scalar value should include 'wrapped': {:?}",
4551 value.to_string()
4552 );
4553 }
4554}