1use crate::{
4 error_recovery::{ErrorBuilder, ErrorRecoveryContext, ParseContext, RecoveryStrategy},
5 lex::{lex, SyntaxKind},
6 parse::Parse,
7 ParseErrorKind, PositionedParseError,
8};
9use rowan::ast::AstNode;
10use rowan::GreenNodeBuilder;
11use std::path::Path;
12use std::str::FromStr;
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
20pub(crate) struct ParsedYaml {
21 pub(crate) green_node: rowan::GreenNode,
23 pub(crate) errors: Vec<String>,
25 pub(crate) positioned_errors: Vec<PositionedParseError>,
27}
28
29use crate::nodes::ast_node;
31pub use crate::nodes::{Lang, SyntaxNode};
32
33pub use crate::nodes::{
35 Alias, Directive, Document, Mapping, MappingEntry, Scalar, ScalarConversionError, Sequence,
36 TaggedNode,
37};
38
39ast_node!(
40 YamlFile,
41 ROOT,
42 "A YAML file containing one or more documents"
43);
44
45impl YamlFile {
46 pub fn snapshot(&self) -> Self {
53 YamlFile(SyntaxNode::new_root_mut(self.0.green().into_owned()))
54 }
55
56 pub fn tree_eq(&self, other: &Self) -> bool {
60 let a = self.0.green();
61 let b = other.0.green();
62 let a_ref: &rowan::GreenNodeData = &a;
63 let b_ref: &rowan::GreenNodeData = &b;
64 std::ptr::eq(a_ref as *const _, b_ref as *const _) || a_ref == b_ref
65 }
66}
67
68pub trait ValueNode: rowan::ast::AstNode<Language = Lang> {
70 fn is_inline(&self) -> bool;
72}
73
74impl ValueNode for Mapping {
75 fn is_inline(&self) -> bool {
76 if self.0.children_with_tokens().any(|c| {
78 c.as_token()
79 .map(|t| t.kind() == SyntaxKind::LEFT_BRACE || t.kind() == SyntaxKind::RIGHT_BRACE)
80 .unwrap_or(false)
81 }) {
82 return true;
83 }
84 false
85 }
86}
87
88impl ValueNode for Sequence {
89 fn is_inline(&self) -> bool {
90 if self.0.children_with_tokens().any(|c| {
92 c.as_token()
93 .map(|t| {
94 t.kind() == SyntaxKind::LEFT_BRACKET || t.kind() == SyntaxKind::RIGHT_BRACKET
95 })
96 .unwrap_or(false)
97 }) {
98 return true;
99 }
100 false
101 }
102}
103
104impl ValueNode for Scalar {
105 fn is_inline(&self) -> bool {
106 true
108 }
109}
110
111pub(crate) fn ends_with_newline(node: &SyntaxNode) -> bool {
115 node.last_token()
116 .map(|t| t.kind() == SyntaxKind::NEWLINE)
117 .unwrap_or(false)
118}
119
120pub(crate) fn add_newline_token(
122 elements: &mut Vec<rowan::NodeOrToken<rowan::SyntaxNode<Lang>, rowan::SyntaxToken<Lang>>>,
123) {
124 let mut nl_builder = rowan::GreenNodeBuilder::new();
125 nl_builder.start_node(SyntaxKind::ROOT.into());
126 nl_builder.token(SyntaxKind::NEWLINE.into(), "\n");
127 nl_builder.finish_node();
128 let nl_node = SyntaxNode::new_root_mut(nl_builder.finish());
129 if let Some(token) = nl_node.first_token() {
130 elements.push(token.into());
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Hash)]
136pub struct Set(SyntaxNode);
137
138impl Set {
139 pub fn cast(node: SyntaxNode) -> Option<Self> {
141 if node.kind() == SyntaxKind::TAGGED_NODE {
142 if let Some(tagged_node) = TaggedNode::cast(node.clone()) {
143 if tagged_node.tag().as_deref() == Some("!!set") {
144 return Some(Set(node));
145 }
146 }
147 }
148 None
149 }
150
151 fn inner_mapping(&self) -> Option<Mapping> {
153 self.0.children().find_map(Mapping::cast)
154 }
155
156 pub fn members(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
169 self.inner_mapping().into_iter().flat_map(|m| {
183 let mapping_node = m.syntax().clone();
184 let has_entries = mapping_node
185 .children()
186 .any(|n| n.kind() == SyntaxKind::MAPPING_ENTRY);
187
188 if has_entries {
189 mapping_node
191 .children()
192 .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
193 .filter_map(|entry| {
194 entry
195 .children()
196 .find(|n| n.kind() == SyntaxKind::KEY)
197 .and_then(|key| key.children().next())
198 .and_then(crate::as_yaml::YamlNode::from_syntax)
199 })
200 .collect::<Vec<_>>()
201 } else {
202 mapping_node
204 .children()
205 .filter(|n| n.kind() == SyntaxKind::KEY)
206 .filter_map(|key| {
207 key.children()
208 .next()
209 .and_then(crate::as_yaml::YamlNode::from_syntax)
210 })
211 .collect::<Vec<_>>()
212 }
213 })
214 }
215
216 pub fn len(&self) -> usize {
218 self.members().count()
219 }
220
221 pub fn is_empty(&self) -> bool {
223 self.members().next().is_none()
224 }
225
226 pub fn contains(&self, value: impl crate::AsYaml) -> bool {
228 self.members()
229 .any(|member| crate::as_yaml::yaml_eq(&member, &value))
230 }
231}
232
233fn extract_content_node(wrapper: &SyntaxNode) -> Option<SyntaxNode> {
237 use crate::lex::SyntaxKind;
238 match wrapper.kind() {
239 SyntaxKind::VALUE | SyntaxKind::KEY => wrapper.children().next(),
240 _ => Some(wrapper.clone()),
241 }
242}
243
244fn smart_cast<T: AstNode<Language = Lang>>(node: SyntaxNode) -> Option<T> {
246 if let Some(content) = extract_content_node(&node) {
247 T::cast(content)
248 } else {
249 None
250 }
251}
252
253pub(crate) fn extract_scalar(node: &SyntaxNode) -> Option<Scalar> {
255 smart_cast(node.clone())
256}
257
258pub(crate) fn extract_mapping(node: &SyntaxNode) -> Option<Mapping> {
260 smart_cast(node.clone())
261}
262
263pub(crate) fn extract_sequence(node: &SyntaxNode) -> Option<Sequence> {
265 smart_cast(node.clone())
266}
267
268pub(crate) fn extract_tagged_node(node: &SyntaxNode) -> Option<TaggedNode> {
270 smart_cast(node.clone())
271}
272
273pub(crate) fn copy_node_to_builder(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
275 builder.start_node(node.kind().into());
276 add_node_children_to(builder, node);
277 builder.finish_node();
278}
279
280pub(crate) fn add_node_children_to(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
282 for child in node.children_with_tokens() {
283 match child {
284 rowan::NodeOrToken::Node(child_node) => {
285 builder.start_node(child_node.kind().into());
286 add_node_children_to(builder, &child_node);
287 builder.finish_node();
288 }
289 rowan::NodeOrToken::Token(token) => {
290 builder.token(token.kind().into(), token.text());
291 }
292 }
293 }
294}
295
296pub(crate) fn dump_cst_to_string(node: &SyntaxNode, indent: usize) -> String {
298 let mut result = String::new();
299 let indent_str = " ".repeat(indent);
300
301 for child in node.children_with_tokens() {
302 match child {
303 rowan::NodeOrToken::Node(n) => {
304 result.push_str(&format!("{}{:?}\n", indent_str, n.kind()));
305 result.push_str(&dump_cst_to_string(&n, indent + 1));
306 }
307 rowan::NodeOrToken::Token(t) => {
308 result.push_str(&format!("{} {:?} {:?}\n", indent_str, t.kind(), t.text()));
309 }
310 }
311 }
312 result
313}
314
315impl Default for YamlFile {
316 fn default() -> Self {
317 Self::new()
318 }
319}
320
321impl YamlFile {
322 pub fn new() -> YamlFile {
324 let mut builder = GreenNodeBuilder::new();
325 builder.start_node(SyntaxKind::ROOT.into());
326 builder.finish_node();
327 YamlFile(SyntaxNode::new_root_mut(builder.finish()))
328 }
329
330 pub fn parse(text: &str) -> Parse<YamlFile> {
332 Parse::parse_yaml(text)
333 }
334
335 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<YamlFile, crate::YamlError> {
337 let contents = std::fs::read_to_string(path)?;
338 Self::from_str(&contents)
339 }
340
341 pub fn from_str_relaxed(s: &str) -> (YamlFile, Vec<String>) {
357 let parsed = YamlFile::parse(s);
358 let errors = parsed.errors();
359 (parsed.tree(), errors)
360 }
361
362 pub fn from_path_relaxed<P: AsRef<Path>>(
367 path: P,
368 ) -> Result<(YamlFile, Vec<String>), std::io::Error> {
369 let contents = std::fs::read_to_string(path)?;
370 Ok(Self::from_str_relaxed(&contents))
371 }
372
373 pub fn read_relaxed<R: std::io::Read>(
378 mut r: R,
379 ) -> Result<(YamlFile, Vec<String>), std::io::Error> {
380 let mut buf = String::new();
381 r.read_to_string(&mut buf)?;
382 Ok(Self::from_str_relaxed(&buf))
383 }
384
385 pub fn documents(&self) -> impl Iterator<Item = Document> {
387 self.0.children().filter_map(Document::cast)
388 }
389
390 pub fn document(&self) -> Option<Document> {
395 self.documents().next()
396 }
397
398 pub fn ensure_document(&self) -> Document {
402 if self.documents().next().is_none() {
403 let doc = Document::new_mapping();
405 self.push_document(doc);
406 }
407 self.documents()
408 .next()
409 .expect("Document should exist after ensuring")
410 }
411
412 pub fn directives(&self) -> impl Iterator<Item = Directive> {
414 self.0.children().filter_map(Directive::cast)
415 }
416
417 pub fn add_directive(&self, directive_text: &str) {
426 let mut builder = GreenNodeBuilder::new();
428 builder.start_node(SyntaxKind::DIRECTIVE.into());
429 builder.token(SyntaxKind::DIRECTIVE.into(), directive_text);
430 builder.finish_node();
431 let directive_node = SyntaxNode::new_root_mut(builder.finish());
432
433 self.0.splice_children(0..0, vec![directive_node.into()]);
435 }
436
437 pub fn push_document(&self, document: Document) {
439 let children_count = self.0.children_with_tokens().count();
440
441 self.0
443 .splice_children(children_count..children_count, vec![document.0.into()]);
444 }
445
446 pub fn set(&self, key: impl crate::AsYaml, value: impl crate::AsYaml) {
455 if let Some(doc) = self.document() {
456 doc.set(key, value);
457 }
458 }
459
460 pub fn insert_after(
469 &self,
470 after_key: impl crate::AsYaml,
471 key: impl crate::AsYaml,
472 value: impl crate::AsYaml,
473 ) -> bool {
474 if let Some(doc) = self.document() {
475 doc.insert_after(after_key, key, value)
476 } else {
477 false
478 }
479 }
480
481 pub fn insert_before(
490 &self,
491 before_key: impl crate::AsYaml,
492 key: impl crate::AsYaml,
493 value: impl crate::AsYaml,
494 ) -> bool {
495 if let Some(doc) = self.document() {
496 doc.insert_before(before_key, key, value)
497 } else {
498 false
499 }
500 }
501
502 pub fn move_after(
513 &self,
514 after_key: impl crate::AsYaml,
515 key: impl crate::AsYaml,
516 value: impl crate::AsYaml,
517 ) -> bool {
518 if let Some(doc) = self.document() {
519 doc.move_after(after_key, key, value)
520 } else {
521 false
522 }
523 }
524
525 pub fn move_before(
536 &self,
537 before_key: impl crate::AsYaml,
538 key: impl crate::AsYaml,
539 value: impl crate::AsYaml,
540 ) -> bool {
541 if let Some(doc) = self.document() {
542 doc.move_before(before_key, key, value)
543 } else {
544 false
545 }
546 }
547
548 pub fn insert_at_index(
557 &self,
558 index: usize,
559 key: impl crate::AsYaml,
560 value: impl crate::AsYaml,
561 ) {
562 if let Some(doc) = self.document() {
563 doc.insert_at_index(index, key, value);
564 } else {
566 let mut builder = GreenNodeBuilder::new();
569 builder.start_node(SyntaxKind::DOCUMENT.into());
570 builder.start_node(SyntaxKind::MAPPING.into());
571 builder.finish_node(); builder.finish_node(); let doc = Document(SyntaxNode::new_root_mut(builder.finish()));
574
575 self.0.splice_children(0..0, vec![doc.0.into()]);
577
578 if let Some(doc) = self.document() {
580 doc.insert_at_index(index, key, value);
581 }
582 }
583 }
584}
585
586impl FromStr for YamlFile {
587 type Err = crate::YamlError;
588
589 fn from_str(s: &str) -> Result<Self, Self::Err> {
590 let parsed = YamlFile::parse(s);
591 if !parsed.positioned_errors().is_empty() {
592 let first = &parsed.positioned_errors()[0];
593 let lc = crate::byte_offset_to_line_column(s, first.range.start as usize);
594 return Err(crate::YamlError::Parse {
595 message: first.message.clone(),
596 line: Some(lc.line),
597 column: Some(lc.column),
598 });
599 }
600 Ok(parsed.tree())
601 }
602}
603
604const MAX_FLOW_DEPTH: usize = 256;
608
609struct Parser {
611 tokens: Vec<(SyntaxKind, String)>,
612 current_token_index: usize,
613 builder: GreenNodeBuilder<'static>,
614 errors: Vec<String>,
615 positioned_errors: Vec<PositionedParseError>,
616 in_flow_context: bool,
617 error_context: ErrorRecoveryContext,
619 in_value_context: bool,
621 current_line_indent: usize,
623 flow_depth: usize,
625}
626
627impl Parser {
628 fn new(text: &str) -> Self {
629 let lexed = lex(text);
630 let mut tokens = Vec::new();
631
632 for (kind, token_text) in lexed {
633 tokens.push((kind, token_text.to_string()));
634 }
635
636 let token_count = tokens.len();
638 tokens.reverse();
639
640 Self {
641 tokens,
642 current_token_index: token_count,
643 builder: GreenNodeBuilder::new(),
644 errors: Vec::new(),
645 positioned_errors: Vec::new(),
646 in_flow_context: false,
647 error_context: ErrorRecoveryContext::new(text.to_string()),
648 in_value_context: false,
649 current_line_indent: 0,
650 flow_depth: 0,
651 }
652 }
653
654 fn parse(mut self) -> ParsedYaml {
655 self.builder.start_node(SyntaxKind::ROOT.into());
656
657 if self.current() == Some(SyntaxKind::BOM) {
660 self.bump(); }
662
663 self.skip_ws_and_newlines();
664
665 while self.current() == Some(SyntaxKind::DIRECTIVE) {
667 self.parse_directive();
668 self.skip_ws_and_newlines();
669 }
670
671 if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
674 self.parse_document();
675 self.skip_ws_and_newlines();
676
677 while self.current() == Some(SyntaxKind::DOC_START)
679 || self.current() == Some(SyntaxKind::DIRECTIVE)
680 {
681 while self.current() == Some(SyntaxKind::DIRECTIVE) {
683 self.parse_directive();
684 self.skip_ws_and_newlines();
685 }
686
687 if self.current() == Some(SyntaxKind::DOC_START)
689 || (self.current().is_some() && self.current() != Some(SyntaxKind::EOF))
690 {
691 self.parse_document();
692 self.skip_ws_and_newlines();
693 } else {
694 break;
695 }
696 }
697 }
698
699 while self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
702 self.builder.start_node(SyntaxKind::ERROR.into());
703
704 while self.current().is_some()
706 && self.current() != Some(SyntaxKind::EOF)
707 && self.current() != Some(SyntaxKind::DOC_START)
708 && self.current() != Some(SyntaxKind::DIRECTIVE)
709 {
710 self.bump();
711 }
712
713 self.builder.finish_node();
714
715 if self.current() == Some(SyntaxKind::DOC_START)
717 || self.current() == Some(SyntaxKind::DIRECTIVE)
718 {
719 while self.current() == Some(SyntaxKind::DIRECTIVE) {
721 self.parse_directive();
722 self.skip_ws_and_newlines();
723 }
724
725 if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
727 self.parse_document();
728 self.skip_ws_and_newlines();
729 }
730 }
731 }
732
733 self.builder.finish_node();
734
735 ParsedYaml {
736 green_node: self.builder.finish(),
737 errors: self.errors,
738 positioned_errors: self.positioned_errors,
739 }
740 }
741
742 fn parse_document(&mut self) {
743 self.builder.start_node(SyntaxKind::DOCUMENT.into());
744
745 if self.current() == Some(SyntaxKind::DOC_START) {
747 self.bump();
748 self.skip_ws_and_newlines();
749 }
750
751 if self.current().is_some()
753 && self.current() != Some(SyntaxKind::DOC_END)
754 && self.current() != Some(SyntaxKind::DOC_START)
755 {
756 self.parse_value();
757 }
758
759 if self.current() == Some(SyntaxKind::DOC_END) {
761 self.bump();
762
763 self.skip_whitespace();
765 if self.current().is_some()
766 && self.current() != Some(SyntaxKind::NEWLINE)
767 && self.current() != Some(SyntaxKind::EOF)
768 && self.current() != Some(SyntaxKind::DOC_START)
769 && self.current() != Some(SyntaxKind::DIRECTIVE)
770 {
771 self.builder.start_node(SyntaxKind::ERROR.into());
773 while self.current().is_some()
774 && self.current() != Some(SyntaxKind::NEWLINE)
775 && self.current() != Some(SyntaxKind::EOF)
776 && self.current() != Some(SyntaxKind::DOC_START)
777 && self.current() != Some(SyntaxKind::DIRECTIVE)
778 {
779 self.bump();
780 }
781 self.builder.finish_node();
782 }
783 }
784
785 self.builder.finish_node();
786 }
787
788 fn parse_value(&mut self) {
789 self.parse_value_with_base_indent(0);
790 }
791
792 fn parse_value_with_base_indent(&mut self, base_indent: usize) {
793 match self.current() {
794 Some(SyntaxKind::COMMENT) => {
795 self.bump(); self.skip_ws_and_newlines(); self.parse_value_with_base_indent(base_indent);
800 }
801 Some(SyntaxKind::DASH) if !self.in_flow_context => {
802 self.parse_sequence_with_base_indent(base_indent)
803 }
804 Some(SyntaxKind::ANCHOR) => {
805 self.bump(); self.skip_whitespace();
807 self.parse_value_with_base_indent(base_indent);
808 }
809 Some(SyntaxKind::REFERENCE) => self.parse_alias(),
810 Some(SyntaxKind::TAG) => self.parse_tagged_value(),
811 Some(SyntaxKind::MERGE_KEY) => {
812 self.parse_mapping_with_base_indent(base_indent);
814 }
815 Some(SyntaxKind::QUESTION) => {
816 self.parse_explicit_key_mapping();
818 }
819 Some(SyntaxKind::PIPE) => self.parse_literal_block_scalar(),
820 Some(SyntaxKind::GREATER) => self.parse_folded_block_scalar(),
821 Some(
822 SyntaxKind::STRING
823 | SyntaxKind::INT
824 | SyntaxKind::FLOAT
825 | SyntaxKind::BOOL
826 | SyntaxKind::NULL,
827 ) => {
828 if !self.in_flow_context && !self.in_value_context && self.is_mapping_key() {
832 self.parse_mapping_with_base_indent(base_indent);
833 } else {
834 self.parse_scalar();
835 }
836 }
837 Some(SyntaxKind::LEFT_BRACKET) => {
838 if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
841 {
842 self.parse_complex_key_mapping();
843 } else {
844 self.parse_flow_sequence();
845 }
846 }
847 Some(SyntaxKind::LEFT_BRACE) => {
848 if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
851 {
852 self.parse_complex_key_mapping();
853 } else {
854 self.parse_flow_mapping();
855 }
856 }
857 Some(SyntaxKind::INDENT) => {
858 self.bump(); self.parse_value(); }
862 Some(SyntaxKind::NEWLINE) => {
863 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
866 let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
867 self.bump(); self.parse_value_with_base_indent(indent_level);
869 } else {
870 self.builder.start_node(SyntaxKind::SCALAR.into());
872 self.builder.finish_node();
873 }
874 }
875 _ => self.parse_scalar(),
876 }
877 }
878
879 fn parse_alias(&mut self) {
880 self.builder.start_node(SyntaxKind::ALIAS.into());
883 if self.current() == Some(SyntaxKind::REFERENCE) {
884 self.bump(); }
886 self.builder.finish_node();
887 }
888
889 fn parse_scalar(&mut self) {
890 self.builder.start_node(SyntaxKind::SCALAR.into());
891
892 if matches!(
894 self.current(),
895 Some(SyntaxKind::QUOTE | SyntaxKind::SINGLE_QUOTE)
896 ) {
897 let quote_type = self
898 .current()
899 .expect("current token is Some: checked by matches! guard above");
900 self.bump(); while self.current().is_some() && self.current() != Some(quote_type) {
904 self.bump();
905 }
906
907 if self.current() == Some(quote_type) {
908 self.bump(); } else {
910 let expected_quote = if quote_type == SyntaxKind::QUOTE {
911 "\""
912 } else {
913 "'"
914 };
915 let error_msg = self.create_detailed_error(
916 "Unterminated quoted string",
917 &format!("closing quote {}", expected_quote),
918 self.current_text(),
919 );
920 self.add_error_and_recover(
921 error_msg,
922 quote_type,
923 ParseErrorKind::UnterminatedString,
924 );
925 }
926 } else {
927 if matches!(
929 self.current(),
930 Some(
931 SyntaxKind::STRING
932 | SyntaxKind::UNTERMINATED_STRING
933 | SyntaxKind::INT
934 | SyntaxKind::FLOAT
935 | SyntaxKind::BOOL
936 | SyntaxKind::NULL
937 )
938 ) {
939 if self.current() == Some(SyntaxKind::UNTERMINATED_STRING) {
941 self.add_error(
942 "Unterminated quoted string".to_string(),
943 ParseErrorKind::UnterminatedString,
944 );
945 }
946 if !self.in_flow_context {
947 let scalar_indent = self.current_line_indent;
955
956 while let Some(kind) = self.current() {
957 if kind == SyntaxKind::COMMENT {
958 break;
960 }
961
962 if kind == SyntaxKind::NEWLINE {
963 if self.is_plain_scalar_continuation(scalar_indent) {
965 self.bump(); while matches!(
970 self.current(),
971 Some(SyntaxKind::INDENT | SyntaxKind::WHITESPACE)
972 ) {
973 self.bump();
974 }
975
976 continue;
978 } else {
979 break;
981 }
982 }
983
984 if matches!(
986 kind,
987 SyntaxKind::LEFT_BRACKET
988 | SyntaxKind::LEFT_BRACE
989 | SyntaxKind::RIGHT_BRACKET
990 | SyntaxKind::RIGHT_BRACE
991 | SyntaxKind::COMMA
992 ) {
993 break;
994 }
995
996 if kind == SyntaxKind::WHITESPACE {
998 if self.tokens.len() >= 2 {
1000 let next_kind = self.tokens[self.tokens.len() - 2].0;
1001 if next_kind == SyntaxKind::COMMENT {
1002 break;
1004 }
1005 }
1006 }
1007
1008 self.bump();
1009 }
1010 } else {
1011 let is_quoted_string = if let Some(SyntaxKind::STRING) = self.current() {
1019 self.current_text()
1020 .map(|text| text.starts_with('"') || text.starts_with('\''))
1021 .unwrap_or(false)
1022 } else {
1023 false
1024 };
1025
1026 self.bump(); if !is_quoted_string {
1031 while let Some(kind) = self.current() {
1032 if matches!(
1034 kind,
1035 SyntaxKind::COMMA
1036 | SyntaxKind::RIGHT_BRACE
1037 | SyntaxKind::RIGHT_BRACKET
1038 | SyntaxKind::COMMENT
1039 ) {
1040 break;
1041 }
1042
1043 if kind == SyntaxKind::NEWLINE {
1046 self.bump(); while matches!(
1049 self.current(),
1050 Some(SyntaxKind::WHITESPACE | SyntaxKind::INDENT)
1051 ) {
1052 self.bump();
1053 }
1054 continue;
1056 }
1057
1058 if kind == SyntaxKind::WHITESPACE {
1062 if self.tokens.len() >= 2 {
1065 let after_whitespace = self.tokens[self.tokens.len() - 2].0;
1067 if matches!(
1068 after_whitespace,
1069 SyntaxKind::COMMA
1070 | SyntaxKind::RIGHT_BRACE
1071 | SyntaxKind::RIGHT_BRACKET
1072 | SyntaxKind::NEWLINE
1073 | SyntaxKind::COMMENT
1074 ) {
1075 break;
1077 }
1078 }
1080 }
1081
1082 if kind == SyntaxKind::COLON && self.tokens.len() >= 2 {
1084 let next_kind = self.tokens[self.tokens.len() - 2].0;
1085 if matches!(
1086 next_kind,
1087 SyntaxKind::COMMA
1088 | SyntaxKind::RIGHT_BRACE
1089 | SyntaxKind::RIGHT_BRACKET
1090 | SyntaxKind::WHITESPACE
1091 | SyntaxKind::NEWLINE
1092 ) {
1093 break;
1095 }
1096 }
1097
1098 self.bump();
1099 }
1100 }
1101 }
1102 } else {
1103 while let Some(kind) = self.current() {
1105 if matches!(
1106 kind,
1107 SyntaxKind::NEWLINE
1108 | SyntaxKind::DASH
1109 | SyntaxKind::COMMENT
1110 | SyntaxKind::DOC_START
1111 | SyntaxKind::DOC_END
1112 ) {
1113 break;
1114 }
1115
1116 if kind == SyntaxKind::COLON {
1119 if self.in_flow_context {
1120 if self.tokens.len() >= 2 {
1123 let next_kind = self.tokens[self.tokens.len() - 2].0;
1124 if matches!(
1125 next_kind,
1126 SyntaxKind::COMMA
1127 | SyntaxKind::RIGHT_BRACE
1128 | SyntaxKind::RIGHT_BRACKET
1129 | SyntaxKind::WHITESPACE
1130 | SyntaxKind::NEWLINE
1131 ) {
1132 break;
1134 }
1135 }
1136 } else {
1138 break;
1140 }
1141 }
1142
1143 if self.in_flow_context
1145 && matches!(
1146 kind,
1147 SyntaxKind::LEFT_BRACKET
1148 | SyntaxKind::RIGHT_BRACKET
1149 | SyntaxKind::LEFT_BRACE
1150 | SyntaxKind::RIGHT_BRACE
1151 | SyntaxKind::COMMA
1152 )
1153 {
1154 break;
1155 }
1156 self.bump();
1157 }
1158 }
1159 }
1160
1161 self.builder.finish_node();
1162 }
1163
1164 fn parse_tagged_value(&mut self) {
1165 let tag_text = self.peek_tag_text();
1167
1168 match tag_text {
1169 Some("!!set") => self.parse_tagged_set(),
1170 Some("!!omap") => self.parse_tagged_omap(),
1171 Some("!!pairs") => self.parse_tagged_pairs(),
1172 _ => {
1173 self.builder.start_node(SyntaxKind::TAGGED_NODE.into());
1175 self.bump(); while matches!(self.current(), Some(SyntaxKind::WHITESPACE)) {
1179 self.bump();
1180 }
1181
1182 self.parse_value();
1184
1185 self.builder.finish_node();
1186 }
1187 }
1188 }
1189
1190 fn peek_tag_text(&self) -> Option<&str> {
1191 self.tokens
1192 .last()
1193 .filter(|(kind, _)| *kind == SyntaxKind::TAG)
1194 .map(|(_, text)| text.as_str())
1195 }
1196
1197 fn parse_tagged_set(&mut self) {
1198 self.parse_tagged_collection(true); }
1200
1201 fn parse_tagged_omap(&mut self) {
1202 self.parse_tagged_collection(false); }
1204
1205 fn parse_tagged_pairs(&mut self) {
1206 self.parse_tagged_collection(false); }
1208
1209 fn parse_tagged_collection(&mut self, is_mapping: bool) {
1210 self.builder.start_node(SyntaxKind::TAGGED_NODE.into());
1211
1212 self.bump(); while matches!(self.current(), Some(SyntaxKind::WHITESPACE)) {
1217 self.bump();
1218 }
1219
1220 match self.current() {
1222 Some(SyntaxKind::LEFT_BRACE) if is_mapping => self.parse_flow_mapping(),
1223 Some(SyntaxKind::LEFT_BRACKET) if !is_mapping => self.parse_flow_sequence(),
1224 Some(SyntaxKind::NEWLINE) => {
1225 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1228 self.bump(); }
1230 if is_mapping {
1231 self.parse_mapping();
1232 } else {
1233 self.parse_sequence();
1234 }
1235 }
1236 _ => {
1237 if is_mapping {
1238 self.parse_mapping();
1239 } else {
1240 self.parse_sequence();
1241 }
1242 }
1243 }
1244
1245 self.builder.finish_node();
1246 }
1247
1248 fn parse_literal_block_scalar(&mut self) {
1249 self.builder.start_node(SyntaxKind::SCALAR.into());
1250 self.bump(); self.parse_block_scalar_header();
1252 self.parse_block_scalar_content();
1253 self.builder.finish_node();
1254 }
1255
1256 fn parse_folded_block_scalar(&mut self) {
1257 self.builder.start_node(SyntaxKind::SCALAR.into());
1258 self.bump(); self.parse_block_scalar_header();
1260 self.parse_block_scalar_content();
1261 self.builder.finish_node();
1262 }
1263
1264 fn parse_block_scalar_header(&mut self) {
1265 while let Some(kind) = self.current() {
1270 match kind {
1271 SyntaxKind::NEWLINE | SyntaxKind::COMMENT => break,
1272 SyntaxKind::INT => {
1273 if let Some(text) = self.current_text() {
1275 if text.len() == 1
1276 && text
1277 .chars()
1278 .next()
1279 .expect("text is non-empty: len == 1 checked above")
1280 .is_ascii_digit()
1281 {
1282 self.bump(); } else {
1284 break;
1286 }
1287 } else {
1288 break;
1289 }
1290 }
1291 SyntaxKind::STRING => {
1292 if let Some(text) = self.current_text() {
1294 if text == "+" || text == "-" {
1295 self.bump(); } else {
1297 break;
1299 }
1300 } else {
1301 break;
1302 }
1303 }
1304 SyntaxKind::WHITESPACE => {
1305 self.bump();
1307 }
1308 _ => {
1309 break;
1311 }
1312 }
1313 }
1314
1315 if self.current() == Some(SyntaxKind::COMMENT) {
1317 self.bump();
1318 }
1319
1320 if self.current() == Some(SyntaxKind::NEWLINE) {
1322 self.bump();
1323 }
1324 }
1325
1326 fn parse_block_scalar_content(&mut self) {
1327 let mut last_was_newline = false;
1329 let mut base_indent: Option<usize> = None;
1330 let mut first_content_indent: Option<usize> = None;
1331
1332 while let Some(kind) = self.current() {
1333 if kind == SyntaxKind::INDENT && first_content_indent.is_none() {
1335 first_content_indent = self.current_text().map(|t| t.len());
1336 }
1337
1338 if base_indent.is_none() && first_content_indent.is_some() {
1340 base_indent = first_content_indent;
1341 }
1342
1343 if self.is_at_unindented_content_for_block_scalar(last_was_newline, base_indent) {
1345 break;
1346 }
1347
1348 match kind {
1349 SyntaxKind::DOC_START | SyntaxKind::DOC_END => break,
1351 SyntaxKind::NEWLINE => {
1353 self.bump();
1354 last_was_newline = true;
1355 continue;
1356 }
1357 _ => {
1359 self.bump();
1360 last_was_newline = false;
1361 }
1362 }
1363 }
1364 }
1365
1366 fn is_at_unindented_content_for_block_scalar(
1367 &self,
1368 after_newline: bool,
1369 base_indent: Option<usize>,
1370 ) -> bool {
1371 if after_newline {
1374 let current = self.current();
1376
1377 if matches!(
1379 current,
1380 Some(SyntaxKind::COLON) | Some(SyntaxKind::QUESTION)
1381 ) {
1382 return true;
1383 }
1384
1385 if let Some(base) = base_indent {
1387 if current == Some(SyntaxKind::INDENT) {
1388 if let Some(text) = self.current_text() {
1389 if text.len() < base {
1390 return true;
1392 }
1393 }
1394 }
1395 }
1396
1397 if current != Some(SyntaxKind::INDENT)
1399 && current != Some(SyntaxKind::WHITESPACE)
1400 && current != Some(SyntaxKind::NEWLINE)
1401 && current != Some(SyntaxKind::COMMENT)
1402 {
1403 return true;
1405 }
1406 }
1407 false
1408 }
1409
1410 fn parse_mapping(&mut self) {
1411 self.parse_mapping_with_base_indent(0);
1412 }
1413
1414 fn parse_mapping_with_base_indent(&mut self, base_indent: usize) {
1415 self.builder.start_node(SyntaxKind::MAPPING.into());
1416 self.error_context.push_context(ParseContext::Mapping);
1417
1418 while self.current().is_some() {
1419 let tokens_before_iter = self.tokens.len();
1420 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1422 break;
1423 }
1424
1425 loop {
1427 if self.current() == Some(SyntaxKind::COMMENT) {
1428 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1431 break;
1432 }
1433 self.bump();
1434 if self.current() == Some(SyntaxKind::NEWLINE) {
1435 self.bump();
1436 }
1437 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1438 break;
1439 }
1440 } else {
1441 break;
1442 }
1443 }
1444
1445 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1449 break;
1450 }
1451
1452 if !self.is_mapping_key() && !self.is_complex_mapping_key() {
1454 break;
1455 }
1456
1457 if self.current() == Some(SyntaxKind::LEFT_BRACKET)
1459 || self.current() == Some(SyntaxKind::LEFT_BRACE)
1460 {
1461 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1463
1464 self.builder.start_node(SyntaxKind::KEY.into());
1465 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
1466 self.parse_flow_sequence();
1467 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
1468 self.parse_flow_mapping();
1469 }
1470 self.builder.finish_node();
1471
1472 self.skip_ws_and_newlines();
1473
1474 if self.current() == Some(SyntaxKind::COLON) {
1475 self.bump();
1476 self.skip_whitespace();
1477
1478 self.builder.start_node(SyntaxKind::VALUE.into());
1479 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1480 self.parse_value();
1481 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1482 self.bump();
1483 if self.current() == Some(SyntaxKind::INDENT) {
1484 self.bump();
1485 self.parse_value();
1486 }
1487 }
1488 self.builder.finish_node();
1489 } else {
1490 let error_msg = self.create_detailed_error(
1491 "Missing colon in mapping",
1492 "':' after key",
1493 self.current_text(),
1494 );
1495 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
1496 }
1497
1498 self.builder.finish_node();
1500 }
1501 else if self.current() == Some(SyntaxKind::QUESTION) {
1503 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1505
1506 self.bump(); self.skip_whitespace();
1509
1510 self.builder.start_node(SyntaxKind::KEY.into());
1511 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1512 self.parse_value();
1513 }
1514 self.builder.finish_node();
1515
1516 self.skip_ws_and_newlines();
1517
1518 if self.current() == Some(SyntaxKind::COLON) {
1520 self.bump(); self.skip_whitespace();
1522
1523 self.builder.start_node(SyntaxKind::VALUE.into());
1524 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1525 self.parse_value();
1526 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1527 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1529 self.bump(); self.parse_value();
1531 }
1532 }
1533 self.builder.finish_node();
1534 } else {
1535 self.builder.start_node(SyntaxKind::VALUE.into());
1537 self.builder.start_node(SyntaxKind::SCALAR.into());
1538 self.builder.token(SyntaxKind::NULL.into(), "");
1539 self.builder.finish_node();
1540 self.builder.finish_node();
1541 }
1542
1543 self.builder.finish_node();
1545 } else {
1546 self.parse_mapping_key_value_pair();
1547 }
1548
1549 if self.tokens.len() == tokens_before_iter {
1554 let unexpected = self.current_text().unwrap_or("").to_string();
1555 self.add_error(
1556 format!("Unexpected token in mapping: {:?}", unexpected),
1557 ParseErrorKind::Other,
1558 );
1559 self.bump();
1560 }
1561 }
1562
1563 self.builder.finish_node();
1564 self.error_context.pop_context();
1565 }
1566
1567 fn parse_sequence(&mut self) {
1568 self.parse_sequence_with_base_indent(0);
1569 }
1570
1571 fn parse_sequence_with_base_indent(&mut self, base_indent: usize) {
1572 self.builder.start_node(SyntaxKind::SEQUENCE.into());
1573 self.error_context.push_context(ParseContext::Sequence);
1574
1575 while self.current().is_some() {
1576 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1578 break;
1579 }
1580
1581 loop {
1583 if self.current() == Some(SyntaxKind::COMMENT) {
1584 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1587 break;
1588 }
1589 self.bump();
1590 if self.current() == Some(SyntaxKind::NEWLINE) {
1591 self.bump();
1592 }
1593 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1594 break;
1595 }
1596 } else {
1597 break;
1598 }
1599 }
1600
1601 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1605 break;
1606 }
1607
1608 if self.current() != Some(SyntaxKind::DASH) {
1610 break;
1611 }
1612 self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
1614
1615 let item_indent = self.current_line_indent;
1617
1618 self.bump(); self.skip_whitespace();
1620
1621 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1622 self.parse_value_with_base_indent(item_indent);
1624 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1625 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1628 let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
1629 self.bump(); self.parse_value_with_base_indent(indent_level);
1632 }
1633 }
1634
1635 if self.current() == Some(SyntaxKind::NEWLINE) {
1637 self.bump();
1638 }
1639
1640 self.builder.finish_node();
1642 }
1643
1644 self.builder.finish_node();
1645 self.error_context.pop_context();
1646 }
1647
1648 fn next_flow_element_is_implicit_mapping(&self) -> bool {
1661 let tokens = std::iter::once(self.current().unwrap_or(SyntaxKind::EOF))
1663 .chain(self.upcoming_tokens());
1664 has_implicit_mapping_pattern(tokens)
1665 }
1666
1667 fn parse_implicit_flow_mapping(&mut self) {
1670 self.builder.start_node(SyntaxKind::MAPPING.into());
1671 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1672
1673 self.builder.start_node(SyntaxKind::KEY.into());
1675 self.parse_value();
1676 self.builder.finish_node();
1677
1678 self.skip_ws_and_newlines();
1679
1680 if self.current() == Some(SyntaxKind::COLON) {
1682 self.bump();
1683 self.skip_ws_and_newlines();
1684 }
1685
1686 self.builder.start_node(SyntaxKind::VALUE.into());
1688 if matches!(
1690 self.current(),
1691 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACKET)
1692 ) {
1693 } else {
1695 self.parse_value();
1696 }
1697 self.builder.finish_node();
1698
1699 self.builder.finish_node(); self.builder.finish_node(); }
1702}
1703
1704fn has_implicit_mapping_pattern(tokens: impl Iterator<Item = SyntaxKind>) -> bool {
1708 let mut depth = 0;
1709
1710 for kind in tokens {
1711 match kind {
1712 SyntaxKind::LEFT_BRACE | SyntaxKind::LEFT_BRACKET => {
1714 depth += 1;
1715 }
1716 SyntaxKind::RIGHT_BRACE | SyntaxKind::RIGHT_BRACKET => {
1718 if depth == 0 {
1719 return false;
1721 }
1722 depth -= 1;
1723 }
1724 SyntaxKind::COLON if depth == 0 => {
1726 return true;
1728 }
1729 SyntaxKind::COMMA if depth == 0 => {
1730 return false;
1732 }
1733 _ => {}
1735 }
1736 }
1737
1738 false
1740}
1741
1742impl Parser {
1743 fn parse_flow_sequence(&mut self) {
1744 self.builder.start_node(SyntaxKind::SEQUENCE.into());
1745 self.error_context.push_context(ParseContext::FlowSequence);
1746
1747 if self.flow_depth >= MAX_FLOW_DEPTH {
1748 self.add_error(
1749 format!(
1750 "Flow collection nested too deeply (limit {})",
1751 MAX_FLOW_DEPTH
1752 ),
1753 ParseErrorKind::Other,
1754 );
1755 while let Some(kind) = self.current() {
1758 if matches!(
1759 kind,
1760 SyntaxKind::RIGHT_BRACKET | SyntaxKind::DOC_START | SyntaxKind::DOC_END
1761 ) {
1762 break;
1763 }
1764 self.bump();
1765 }
1766 if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
1767 self.bump();
1768 }
1769 self.builder.finish_node();
1770 self.error_context.pop_context();
1771 return;
1772 }
1773 self.flow_depth += 1;
1774
1775 self.bump(); self.skip_ws_and_newlines(); let prev_flow = self.in_flow_context;
1779 self.in_flow_context = true;
1780
1781 while self.current() != Some(SyntaxKind::RIGHT_BRACKET) && self.current().is_some() {
1782 let tokens_before = self.tokens.len();
1783
1784 self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
1786
1787 if self.next_flow_element_is_implicit_mapping() {
1790 self.parse_implicit_flow_mapping();
1792 } else {
1793 self.parse_value();
1795 }
1796
1797 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COMMA) {
1801 self.bump();
1802 self.skip_ws_and_newlines(); }
1804
1805 self.builder.finish_node(); if self.current() != Some(SyntaxKind::RIGHT_BRACKET) && self.current().is_some() {
1808 if matches!(
1811 self.current(),
1812 Some(SyntaxKind::DASH | SyntaxKind::DOC_START | SyntaxKind::DOC_END)
1813 ) {
1814 break;
1816 }
1817 }
1818
1819 if self.tokens.len() == tokens_before {
1823 let unexpected = self.current_text().unwrap_or("").to_string();
1824 self.add_error(
1825 format!("Unexpected token in flow sequence: {:?}", unexpected),
1826 ParseErrorKind::Other,
1827 );
1828 self.bump();
1829 }
1830 }
1831
1832 self.in_flow_context = prev_flow;
1833
1834 if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
1835 self.bump();
1836 } else {
1837 let error_msg = self.create_detailed_error(
1838 "Unclosed flow sequence",
1839 "']' to close sequence",
1840 self.current_text(),
1841 );
1842 self.add_error_and_recover(
1843 error_msg,
1844 SyntaxKind::RIGHT_BRACKET,
1845 ParseErrorKind::UnclosedFlowSequence,
1846 );
1847 }
1848
1849 self.flow_depth -= 1;
1850 self.builder.finish_node();
1851 self.error_context.pop_context();
1852 }
1853
1854 fn parse_flow_mapping(&mut self) {
1855 self.builder.start_node(SyntaxKind::MAPPING.into());
1856 self.error_context.push_context(ParseContext::FlowMapping);
1857
1858 if self.flow_depth >= MAX_FLOW_DEPTH {
1859 self.add_error(
1860 format!(
1861 "Flow collection nested too deeply (limit {})",
1862 MAX_FLOW_DEPTH
1863 ),
1864 ParseErrorKind::Other,
1865 );
1866 while let Some(kind) = self.current() {
1867 if matches!(
1868 kind,
1869 SyntaxKind::RIGHT_BRACE | SyntaxKind::DOC_START | SyntaxKind::DOC_END
1870 ) {
1871 break;
1872 }
1873 self.bump();
1874 }
1875 if self.current() == Some(SyntaxKind::RIGHT_BRACE) {
1876 self.bump();
1877 }
1878 self.builder.finish_node();
1879 self.error_context.pop_context();
1880 return;
1881 }
1882 self.flow_depth += 1;
1883
1884 self.bump(); self.skip_ws_and_newlines(); let prev_flow = self.in_flow_context;
1888 self.in_flow_context = true;
1889
1890 while self.current() != Some(SyntaxKind::RIGHT_BRACE) && self.current().is_some() {
1891 if matches!(
1893 self.current(),
1894 Some(SyntaxKind::DASH | SyntaxKind::DOC_START | SyntaxKind::DOC_END)
1895 ) {
1896 break;
1898 }
1899
1900 let tokens_before = self.tokens.len();
1901
1902 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1904
1905 self.builder.start_node(SyntaxKind::KEY.into());
1907
1908 if self.current() == Some(SyntaxKind::QUESTION) {
1910 self.bump(); self.skip_whitespace();
1912 }
1913
1914 self.parse_value();
1915 self.builder.finish_node();
1916
1917 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COLON) {
1920 self.bump();
1921 self.skip_ws_and_newlines(); if matches!(
1926 self.current(),
1927 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACE)
1928 ) {
1929 self.builder.start_node(SyntaxKind::VALUE.into());
1931 self.builder.start_node(SyntaxKind::SCALAR.into());
1932 self.builder.token(SyntaxKind::NULL.into(), "");
1933 self.builder.finish_node(); self.builder.finish_node(); } else {
1936 self.builder.start_node(SyntaxKind::VALUE.into());
1938 self.parse_value();
1939 self.builder.finish_node();
1940 }
1941 } else if matches!(
1942 self.current(),
1943 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACE)
1944 ) {
1945 self.builder.start_node(SyntaxKind::VALUE.into());
1949 self.builder.start_node(SyntaxKind::SCALAR.into());
1950 self.builder.token(SyntaxKind::NULL.into(), "");
1951 self.builder.finish_node(); self.builder.finish_node(); } else {
1954 let error_msg = self.create_detailed_error(
1955 "Missing colon in flow mapping",
1956 "':' after key",
1957 self.current_text(),
1958 );
1959 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
1960 }
1961
1962 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COMMA) {
1966 self.bump();
1967 self.skip_ws_and_newlines(); }
1969
1970 self.builder.finish_node();
1972
1973 if self.tokens.len() == tokens_before {
1977 let unexpected = self.current_text().unwrap_or("").to_string();
1978 self.add_error(
1979 format!("Unexpected token in flow mapping: {:?}", unexpected),
1980 ParseErrorKind::Other,
1981 );
1982 self.bump();
1983 }
1984 }
1985
1986 self.in_flow_context = prev_flow;
1987
1988 if self.current() == Some(SyntaxKind::RIGHT_BRACE) {
1989 self.bump();
1990 } else {
1991 let error_msg = self.create_detailed_error(
1992 "Unclosed flow mapping",
1993 "'}' to close mapping",
1994 self.current_text(),
1995 );
1996 self.add_error_and_recover(
1997 error_msg,
1998 SyntaxKind::RIGHT_BRACE,
1999 ParseErrorKind::UnclosedFlowMapping,
2000 );
2001 }
2002
2003 self.flow_depth -= 1;
2004 self.builder.finish_node();
2005 self.error_context.pop_context();
2006 }
2007
2008 fn parse_directive(&mut self) {
2009 self.builder.start_node(SyntaxKind::DIRECTIVE.into());
2010
2011 if self.current() == Some(SyntaxKind::DIRECTIVE) {
2012 self.bump(); } else {
2014 self.add_error("Expected directive".to_string(), ParseErrorKind::Other);
2015 }
2016
2017 self.builder.finish_node();
2018 }
2019
2020 fn parse_explicit_key_mapping(&mut self) {
2021 self.builder.start_node(SyntaxKind::MAPPING.into());
2023
2024 while self.current() == Some(SyntaxKind::QUESTION) {
2025 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2027
2028 self.bump(); self.skip_whitespace();
2031
2032 self.builder.start_node(SyntaxKind::KEY.into());
2034
2035 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2037 self.parse_value();
2038 }
2039
2040 if self.current() == Some(SyntaxKind::NEWLINE) {
2043 if self.tokens.len() >= 2 {
2046 let (next_kind, _) = &self.tokens[self.tokens.len() - 2];
2047 if *next_kind == SyntaxKind::INDENT {
2048 if self.tokens.len() >= 3 {
2050 let (token_after_indent, _) = &self.tokens[self.tokens.len() - 3];
2051 if *token_after_indent != SyntaxKind::DASH {
2054 self.bump(); self.bump(); while self.current().is_some()
2060 && self.current() != Some(SyntaxKind::NEWLINE)
2061 && self.current() != Some(SyntaxKind::COLON)
2062 {
2063 let before = self.tokens.len();
2064 self.parse_scalar();
2065 if self.current() == Some(SyntaxKind::WHITESPACE) {
2066 self.bump(); }
2068 if self.tokens.len() == before {
2072 break;
2073 }
2074 }
2075 }
2076 }
2077 }
2078 }
2079 }
2080
2081 self.builder.finish_node();
2082
2083 self.skip_ws_and_newlines();
2084
2085 if self.current() == Some(SyntaxKind::COLON) {
2087 self.bump(); self.skip_whitespace();
2089
2090 self.builder.start_node(SyntaxKind::VALUE.into());
2091 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2092 self.parse_value();
2093 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2094 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2097 self.bump(); self.parse_value();
2099 }
2100 }
2101 self.builder.finish_node();
2102 } else {
2103 self.builder.start_node(SyntaxKind::VALUE.into());
2105 self.builder.start_node(SyntaxKind::SCALAR.into());
2106 self.builder.token(SyntaxKind::NULL.into(), "");
2107 self.builder.finish_node();
2108 self.builder.finish_node();
2109 }
2110
2111 self.builder.finish_node();
2113
2114 self.skip_ws_and_newlines();
2115
2116 if self.current() != Some(SyntaxKind::QUESTION) && !self.is_mapping_key() {
2118 break;
2119 }
2120 }
2121
2122 while self.current().is_some() && self.is_mapping_key() {
2124 let tokens_before_iter = self.tokens.len();
2125 if self.current() == Some(SyntaxKind::QUESTION) {
2129 self.parse_explicit_key_entries();
2130 break;
2131 }
2132 self.parse_mapping_key_value_pair();
2133 self.skip_ws_and_newlines();
2134 if self.tokens.len() == tokens_before_iter {
2137 let unexpected = self.current_text().unwrap_or("").to_string();
2138 self.add_error(
2139 format!("Unexpected token in explicit-key mapping: {:?}", unexpected),
2140 ParseErrorKind::Other,
2141 );
2142 self.bump();
2143 }
2144 }
2145
2146 self.builder.finish_node();
2147 }
2148
2149 fn parse_complex_key_mapping(&mut self) {
2150 self.builder.start_node(SyntaxKind::MAPPING.into());
2152
2153 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2155
2156 self.builder.start_node(SyntaxKind::KEY.into());
2158 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
2159 self.parse_flow_sequence();
2160 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
2161 self.parse_flow_mapping();
2162 }
2163 self.builder.finish_node();
2164
2165 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COLON) {
2169 self.bump();
2170 self.skip_whitespace();
2171
2172 self.builder.start_node(SyntaxKind::VALUE.into());
2174 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2175 self.parse_value();
2176 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2177 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2179 self.bump(); self.parse_value();
2181 }
2182 }
2183 self.builder.finish_node();
2184 } else {
2185 let error_msg = self.create_detailed_error(
2186 "Missing colon in complex mapping",
2187 "':' after complex key",
2188 self.current_text(),
2189 );
2190 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
2191 }
2192
2193 self.builder.finish_node();
2195
2196 self.skip_ws_and_newlines();
2197
2198 while self.current().is_some() {
2200 let tokens_before_iter = self.tokens.len();
2201 if self.current() == Some(SyntaxKind::QUESTION) {
2202 self.parse_explicit_key_entries();
2204 break;
2205 } else if self.is_complex_mapping_key()
2206 || (self.is_mapping_key() && self.current() != Some(SyntaxKind::QUESTION))
2207 {
2208 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2210
2211 self.builder.start_node(SyntaxKind::KEY.into());
2213
2214 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
2215 self.parse_flow_sequence();
2216 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
2217 self.parse_flow_mapping();
2218 } else if matches!(
2219 self.current(),
2220 Some(
2221 SyntaxKind::STRING
2222 | SyntaxKind::INT
2223 | SyntaxKind::FLOAT
2224 | SyntaxKind::BOOL
2225 | SyntaxKind::NULL
2226 | SyntaxKind::MERGE_KEY
2227 )
2228 ) {
2229 self.bump();
2230 }
2231 self.builder.finish_node();
2232
2233 self.skip_whitespace();
2234
2235 if self.current() == Some(SyntaxKind::COLON) {
2236 self.bump();
2237 self.skip_whitespace();
2238
2239 self.builder.start_node(SyntaxKind::VALUE.into());
2240 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2241 self.parse_value();
2242 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2243 self.bump();
2244 if self.current() == Some(SyntaxKind::INDENT) {
2245 self.bump();
2246 self.parse_value();
2247 }
2248 }
2249 self.builder.finish_node();
2250 }
2251
2252 self.builder.finish_node();
2254
2255 self.skip_ws_and_newlines();
2256 } else {
2257 break;
2258 }
2259
2260 if self.tokens.len() == tokens_before_iter {
2264 let unexpected = self.current_text().unwrap_or("").to_string();
2265 self.add_error(
2266 format!("Unexpected token in complex mapping: {:?}", unexpected),
2267 ParseErrorKind::Other,
2268 );
2269 self.bump();
2270 }
2271 }
2272
2273 self.builder.finish_node();
2274 }
2275
2276 fn parse_explicit_key_entries(&mut self) {
2277 while self.current() == Some(SyntaxKind::QUESTION) {
2279 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2281
2282 self.bump(); self.skip_whitespace();
2284
2285 self.builder.start_node(SyntaxKind::KEY.into());
2286 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2287 self.parse_value();
2288 }
2289 self.builder.finish_node();
2290
2291 self.skip_ws_and_newlines();
2292
2293 if self.current() == Some(SyntaxKind::COLON) {
2294 self.bump();
2295 self.skip_whitespace();
2296
2297 self.builder.start_node(SyntaxKind::VALUE.into());
2298 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2299 self.parse_value();
2300 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2301 self.bump();
2302 if self.current() == Some(SyntaxKind::INDENT) {
2303 self.bump();
2304 self.parse_value();
2305 }
2306 }
2307 self.builder.finish_node();
2308 } else {
2309 self.builder.start_node(SyntaxKind::VALUE.into());
2311 self.builder.start_node(SyntaxKind::SCALAR.into());
2312 self.builder.token(SyntaxKind::NULL.into(), "");
2313 self.builder.finish_node();
2314 self.builder.finish_node();
2315 }
2316
2317 self.builder.finish_node();
2319
2320 self.skip_ws_and_newlines();
2321 }
2322 }
2323
2324 fn is_complex_mapping_key(&self) -> bool {
2325 if !matches!(
2327 self.current(),
2328 Some(SyntaxKind::LEFT_BRACKET) | Some(SyntaxKind::LEFT_BRACE)
2329 ) {
2330 return false;
2331 }
2332
2333 let mut depth = 0;
2335 let start_kind = self.current();
2336 let close_kind = match start_kind {
2337 Some(SyntaxKind::LEFT_BRACKET) => SyntaxKind::RIGHT_BRACKET,
2338 Some(SyntaxKind::LEFT_BRACE) => SyntaxKind::RIGHT_BRACE,
2339 _ => return false,
2340 };
2341
2342 let mut found_close = false;
2343 for kind in self.upcoming_tokens() {
2344 if !found_close {
2345 if Some(kind) == start_kind {
2346 depth += 1;
2347 } else if kind == close_kind {
2348 if depth == 0 {
2349 found_close = true;
2351 } else {
2352 depth -= 1;
2353 }
2354 }
2355 } else {
2356 match kind {
2358 SyntaxKind::WHITESPACE | SyntaxKind::INDENT => continue,
2359 SyntaxKind::COLON => return true,
2360 _ => return false,
2361 }
2362 }
2363 }
2364 false
2365 }
2366
2367 fn parse_mapping_value(&mut self) {
2368 match self.current() {
2372 Some(SyntaxKind::DASH) if !self.in_flow_context => self.parse_sequence(),
2373 Some(SyntaxKind::ANCHOR) => {
2374 self.bump(); self.skip_whitespace();
2376 self.parse_value_with_base_indent(0);
2377 }
2378 Some(SyntaxKind::REFERENCE) => self.parse_alias(),
2379 Some(SyntaxKind::TAG) => self.parse_tagged_value(),
2380 Some(SyntaxKind::QUESTION) => {
2381 self.parse_explicit_key_mapping();
2383 }
2384 Some(SyntaxKind::PIPE) => self.parse_literal_block_scalar(),
2385 Some(SyntaxKind::GREATER) => self.parse_folded_block_scalar(),
2386 Some(SyntaxKind::LEFT_BRACKET) => {
2387 if !self.in_flow_context && self.is_complex_mapping_key() {
2389 self.parse_complex_key_mapping();
2390 } else {
2391 self.parse_flow_sequence();
2392 }
2393 }
2394 Some(SyntaxKind::LEFT_BRACE) => {
2395 if !self.in_flow_context && self.is_complex_mapping_key() {
2397 self.parse_complex_key_mapping();
2398 } else {
2399 self.parse_flow_mapping();
2400 }
2401 }
2402 _ => {
2403 self.parse_scalar();
2406 }
2407 }
2408 }
2409
2410 fn is_mapping_key(&self) -> bool {
2411 if self.current() == Some(SyntaxKind::QUESTION) {
2413 return true;
2414 }
2415
2416 if self.current() == Some(SyntaxKind::MERGE_KEY) {
2418 return true;
2419 }
2420
2421 if self.current() == Some(SyntaxKind::DASH) {
2423 return false;
2424 }
2425
2426 let upcoming = self.upcoming_tokens();
2429 for kind in upcoming {
2430 match kind {
2431 SyntaxKind::COLON => {
2432 return true;
2433 }
2434 SyntaxKind::WHITESPACE => continue,
2435 _ => {
2437 return false;
2438 }
2439 }
2440 }
2441 false
2442 }
2443
2444 fn skip_whitespace(&mut self) {
2445 self.skip_tokens(&[SyntaxKind::WHITESPACE]);
2446 }
2447
2448 fn skip_tokens(&mut self, kinds: &[SyntaxKind]) {
2449 while let Some(current) = self.current() {
2450 if kinds.contains(¤t) {
2451 self.bump();
2452 } else {
2453 break;
2454 }
2455 }
2456 }
2457
2458 fn is_plain_scalar_continuation(&self, scalar_indent: usize) -> bool {
2461 let current_idx = self.tokens.len().saturating_sub(1);
2464
2465 if current_idx == 0 {
2466 return false; }
2468
2469 let mut peek_idx = current_idx.saturating_sub(1);
2472
2473 let next_line_indent = self
2475 .tokens
2476 .get(peek_idx)
2477 .and_then(|(kind, text)| {
2478 if *kind == SyntaxKind::INDENT {
2479 peek_idx = peek_idx.saturating_sub(1);
2480 Some(text.len())
2481 } else {
2482 None
2483 }
2484 })
2485 .unwrap_or(0);
2486
2487 while self
2489 .tokens
2490 .get(peek_idx)
2491 .is_some_and(|(kind, _)| *kind == SyntaxKind::WHITESPACE)
2492 {
2493 peek_idx = peek_idx.saturating_sub(1);
2494 }
2495
2496 let has_content = self.tokens.get(peek_idx).is_some_and(|(kind, _)| {
2498 matches!(
2499 kind,
2500 SyntaxKind::STRING
2501 | SyntaxKind::INT
2502 | SyntaxKind::FLOAT
2503 | SyntaxKind::BOOL
2504 | SyntaxKind::NULL
2505 | SyntaxKind::UNTERMINATED_STRING
2506 )
2507 });
2508
2509 if !has_content || next_line_indent <= scalar_indent {
2510 return false;
2511 }
2512
2513 if peek_idx > 0 {
2516 let mut check_idx = peek_idx.saturating_sub(1);
2517
2518 while self
2520 .tokens
2521 .get(check_idx)
2522 .is_some_and(|(kind, _)| *kind == SyntaxKind::WHITESPACE)
2523 {
2524 if check_idx == 0 {
2525 break;
2526 }
2527 check_idx = check_idx.saturating_sub(1);
2528 }
2529
2530 if self
2532 .tokens
2533 .get(check_idx)
2534 .is_some_and(|(kind, _)| *kind == SyntaxKind::COLON)
2535 {
2536 return false;
2537 }
2538 }
2539
2540 true
2541 }
2542
2543 fn is_at_dedented_position(&self, base_indent: usize) -> bool {
2547 if base_indent == 0 {
2553 self.current_line_indent > 0
2555 } else {
2556 self.current_line_indent < base_indent
2558 }
2559 }
2560
2561 fn skip_whitespace_only_with_dedent_check(&mut self, base_indent: usize) -> bool {
2564 while self.current().is_some() {
2565 match self.current() {
2566 Some(SyntaxKind::WHITESPACE) => {
2567 self.bump();
2568 }
2569 Some(SyntaxKind::NEWLINE) => {
2570 self.bump();
2571 match self.current() {
2573 Some(SyntaxKind::INDENT) => {
2574 if let Some((_, text)) = self.tokens.last() {
2575 if text.len() < base_indent {
2576 return true;
2578 }
2579 if base_indent == 0 && !text.is_empty() {
2580 return true;
2582 }
2583 }
2584 self.bump(); }
2586 Some(SyntaxKind::COMMENT) => {
2587 if base_indent > 0 {
2589 return true;
2591 }
2592 return false;
2594 }
2595 Some(SyntaxKind::WHITESPACE) | Some(SyntaxKind::NEWLINE) => {
2596 }
2598 None => {
2599 return false;
2601 }
2602 _ => {
2603 if base_indent > 0 {
2605 return true; }
2607 return false;
2609 }
2610 }
2611 }
2612 Some(SyntaxKind::INDENT) => {
2613 if let Some((_, text)) = self.tokens.last() {
2615 if text.len() < base_indent {
2616 return true; }
2618 }
2619 self.bump();
2620 }
2621 _ => {
2622 return false;
2624 }
2625 }
2626 }
2627 false
2628 }
2629
2630 fn skip_ws_and_newlines(&mut self) {
2631 self.skip_tokens(&[
2632 SyntaxKind::WHITESPACE,
2633 SyntaxKind::NEWLINE,
2634 SyntaxKind::INDENT,
2635 SyntaxKind::COMMENT,
2636 ]);
2637 }
2638
2639 fn parse_mapping_key_value_pair(&mut self) {
2640 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2642
2643 self.builder.start_node(SyntaxKind::KEY.into());
2645
2646 if self.current() == Some(SyntaxKind::ANCHOR) {
2648 self.bump(); self.skip_whitespace();
2650 }
2651
2652 if self.current() == Some(SyntaxKind::MERGE_KEY) {
2653 self.builder.start_node(SyntaxKind::SCALAR.into());
2654 self.bump(); self.builder.finish_node(); } else if self.current() == Some(SyntaxKind::REFERENCE) {
2657 self.parse_alias();
2659 } else if matches!(
2660 self.current(),
2661 Some(
2662 SyntaxKind::STRING
2663 | SyntaxKind::INT
2664 | SyntaxKind::FLOAT
2665 | SyntaxKind::BOOL
2666 | SyntaxKind::NULL
2667 )
2668 ) {
2669 self.builder.start_node(SyntaxKind::SCALAR.into());
2670 self.bump(); self.builder.finish_node(); }
2673 self.builder.finish_node(); self.skip_whitespace();
2676
2677 if self.current() == Some(SyntaxKind::COLON) {
2679 self.bump();
2680 self.skip_whitespace();
2681
2682 self.builder.start_node(SyntaxKind::VALUE.into());
2684 let mut has_value = false;
2685 if self.current().is_some()
2686 && self.current() != Some(SyntaxKind::NEWLINE)
2687 && self.current() != Some(SyntaxKind::COMMENT)
2688 {
2689 self.parse_mapping_value();
2691 has_value = true;
2692
2693 if self.current() == Some(SyntaxKind::WHITESPACE) {
2696 self.bump(); }
2698 if self.current() == Some(SyntaxKind::COMMENT) {
2699 self.bump(); }
2701 } else if self.current() == Some(SyntaxKind::COMMENT) {
2702 self.bump(); if self.current() == Some(SyntaxKind::NEWLINE) {
2708 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2711 let indent_level =
2712 self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
2713 self.bump(); self.parse_value_with_base_indent(indent_level);
2716 has_value = true;
2717 }
2718 }
2719 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2721 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2724 let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
2725 self.bump(); self.parse_value_with_base_indent(indent_level);
2728 has_value = true;
2729 } else if self.current() == Some(SyntaxKind::DASH) {
2730 self.parse_sequence();
2733 has_value = true;
2734 }
2735 }
2736
2737 if !has_value {
2739 self.builder.start_node(SyntaxKind::SCALAR.into());
2740 self.builder.token(SyntaxKind::NULL.into(), "");
2741 self.builder.finish_node();
2742 }
2743
2744 self.builder.finish_node(); } else {
2746 let error_msg = self.create_detailed_error(
2747 "Missing colon in mapping",
2748 "':' after key",
2749 self.current_text(),
2750 );
2751 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
2752 }
2753
2754 while self.current() == Some(SyntaxKind::WHITESPACE) {
2759 self.bump();
2760 }
2761
2762 if self.current() == Some(SyntaxKind::NEWLINE) {
2764 self.bump();
2765 }
2766
2767 self.builder.finish_node();
2769 }
2770
2771 fn bump(&mut self) {
2772 if let Some((kind, text)) = self.tokens.pop() {
2773 match kind {
2775 SyntaxKind::INDENT => {
2776 self.current_line_indent = text.len();
2777 }
2778 SyntaxKind::NEWLINE => {
2779 self.current_line_indent = 0;
2781 }
2782 _ => {}
2783 }
2784
2785 self.builder.token(kind.into(), &text);
2786 if self.current_token_index > 0 {
2787 self.current_token_index -= 1;
2788 }
2789 self.error_context.advance(text.len());
2791 }
2792 }
2793
2794 fn current(&self) -> Option<SyntaxKind> {
2795 self.tokens.last().map(|(kind, _)| *kind)
2796 }
2797
2798 fn current_text(&self) -> Option<&str> {
2799 self.tokens.last().map(|(_, text)| text.as_str())
2800 }
2801
2802 fn upcoming_tokens(&self) -> impl Iterator<Item = SyntaxKind> + '_ {
2804 let len = self.tokens.len();
2807 (0..len.saturating_sub(1))
2808 .rev()
2809 .map(move |i| self.tokens[i].0)
2810 }
2811
2812 fn add_error(&mut self, message: String, kind: ParseErrorKind) {
2813 let token_len = self.current_text().map(|s| s.len()).unwrap_or(1);
2815 let positioned_error = self.error_context.create_error(message, token_len, kind);
2816
2817 self.errors.push(positioned_error.message.clone());
2818 self.positioned_errors.push(positioned_error);
2819 }
2820
2821 fn add_error_and_recover(
2823 &mut self,
2824 message: String,
2825 expected: SyntaxKind,
2826 kind: ParseErrorKind,
2827 ) {
2828 self.add_error(message, kind);
2829
2830 let found = self.current();
2832 let strategy = self.error_context.suggest_recovery(expected, found);
2833
2834 match strategy {
2835 RecoveryStrategy::SkipToken => {
2836 if self.current().is_some() {
2838 self.bump();
2839 }
2840 }
2841 RecoveryStrategy::SkipToEndOfLine => {
2842 while self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2844 self.bump();
2845 }
2846 }
2847 RecoveryStrategy::InsertToken(kind) => {
2848 self.builder.token(kind.into(), "");
2850 }
2851 RecoveryStrategy::SyncToSafePoint => {
2852 let sync_point = self
2854 .error_context
2855 .find_sync_point(&self.tokens, self.tokens.len() - self.current_token_index);
2856 let tokens_to_skip = sync_point - (self.tokens.len() - self.current_token_index);
2857 for _ in 0..tokens_to_skip {
2858 if self.current().is_some() {
2859 self.bump();
2860 }
2861 }
2862 }
2863 }
2864 }
2865
2866 fn create_detailed_error(
2868 &self,
2869 base_message: &str,
2870 expected: &str,
2871 found: Option<&str>,
2872 ) -> String {
2873 let mut builder = ErrorBuilder::new(base_message);
2874 builder = builder.expected(expected);
2875
2876 if let Some(found_str) = found {
2877 builder = builder.found(found_str);
2878 } else if let Some(token) = self.current_text() {
2879 builder = builder.found(format!("'{}'", token));
2880 } else {
2881 builder = builder.found("end of input");
2882 }
2883
2884 let context = match self.error_context.current_context() {
2886 ParseContext::Mapping => "in mapping",
2887 ParseContext::Sequence => "in sequence",
2888 ParseContext::FlowMapping => "in flow mapping",
2889 ParseContext::FlowSequence => "in flow sequence",
2890 ParseContext::BlockScalar => "in block scalar",
2891 ParseContext::QuotedString => "in quoted string",
2892 _ => "at document level",
2893 };
2894 builder = builder.context(context);
2895
2896 let suggestion = self.get_error_suggestion(base_message, expected, found);
2898 if let Some(suggestion_text) = suggestion {
2899 builder = builder.suggestion(suggestion_text);
2900 }
2901
2902 builder.build()
2903 }
2904
2905 fn get_error_suggestion(
2907 &self,
2908 base_message: &str,
2909 expected: &str,
2910 found: Option<&str>,
2911 ) -> Option<String> {
2912 if base_message.contains("Unterminated quoted string") {
2913 return Some(
2914 "Add closing quote or check for unescaped quotes within the string".to_string(),
2915 );
2916 }
2917
2918 if base_message.contains("Missing colon") || expected.contains("':'") {
2919 return Some("Add ':' after the key, or check for proper indentation".to_string());
2920 }
2921
2922 if base_message.contains("Unclosed flow sequence") {
2923 return Some(
2924 "Add ']' to close the array, or check for missing commas between elements"
2925 .to_string(),
2926 );
2927 }
2928
2929 if base_message.contains("Unclosed flow mapping") {
2930 return Some(
2931 "Add '}' to close the object, or check for missing commas between key-value pairs"
2932 .to_string(),
2933 );
2934 }
2935
2936 if let Some(found_text) = found {
2937 if found_text.contains('\n') {
2938 return Some(
2939 "Unexpected newline - check indentation and YAML structure".to_string(),
2940 );
2941 }
2942
2943 if found_text.contains('\t') {
2944 return Some(
2945 "Tabs are not allowed in YAML - use spaces for indentation".to_string(),
2946 );
2947 }
2948 }
2949
2950 None
2951 }
2952}
2953
2954pub(crate) fn parse(text: &str) -> ParsedYaml {
2956 let parser = Parser::new(text);
2957 parser.parse()
2958}
2959
2960#[cfg(test)]
2963mod tests {
2964 use super::*;
2965 use crate::builder::{MappingBuilder, SequenceBuilder};
2966 use crate::scalar::ScalarValue;
2967 use crate::value::YamlValue; #[test]
2970 fn test_simple_mapping() {
2971 let yaml = "key: value";
2972 let parsed = YamlFile::from_str(yaml).unwrap();
2973 let doc = parsed.document().unwrap();
2974 let mapping = doc.as_mapping().unwrap();
2975
2976 assert_eq!(parsed.to_string().trim(), "key: value");
2978
2979 let value = mapping.get("key");
2981 assert!(value.is_some());
2982 }
2983
2984 #[test]
2985 fn test_simple_sequence() {
2986 let yaml = "- item1\n- item2";
2987 let parsed = YamlFile::from_str(yaml);
2988 assert!(parsed.is_ok());
2989 }
2990
2991 #[test]
2992 fn test_complex_yaml() {
2993 let yaml = r#"
2994name: my-app
2995version: 1.0.0
2996dependencies:
2997 - serde
2998 - tokio
2999config:
3000 port: 8080
3001 enabled: true
3002"#;
3003 let parsed = YamlFile::from_str(yaml).unwrap();
3004 assert_eq!(parsed.documents().count(), 1);
3005
3006 let doc = parsed.document().unwrap();
3007 assert!(doc.as_mapping().is_some());
3008 }
3009
3010 #[test]
3011 fn test_multiple_documents() {
3012 let yaml = r#"---
3013doc: first
3014---
3015doc: second
3016...
3017"#;
3018 let parsed = YamlFile::from_str(yaml).unwrap();
3019 assert_eq!(parsed.documents().count(), 2);
3020 }
3021
3022 #[test]
3023 fn test_flow_styles() {
3024 let yaml = r#"
3025array: [1, 2, 3]
3026object: {key: value, another: 42}
3027"#;
3028 let parsed = YamlFile::from_str(yaml).unwrap();
3029 assert!(parsed.document().is_some());
3030 }
3031
3032 #[test]
3033 fn test_scalar_types_parsing() {
3034 let yaml = r#"
3035string: hello
3036integer: 42
3037float: 3.14
3038bool_true: true
3039bool_false: false
3040null_value: null
3041tilde: ~
3042"#;
3043 let parsed = YamlFile::from_str(yaml).unwrap();
3044 let doc = parsed.document().unwrap();
3045 let mapping = doc.as_mapping().unwrap();
3046
3047 assert!(mapping.get("string").is_some());
3049 assert!(mapping.get("integer").is_some());
3050 assert!(mapping.get("float").is_some());
3051 assert!(mapping.get("bool_true").is_some());
3052 assert!(mapping.get("bool_false").is_some());
3053 assert!(mapping.get("null_value").is_some());
3054 assert!(mapping.get("tilde").is_some());
3055 }
3056
3057 #[test]
3058 fn test_preserve_formatting() {
3059 let yaml = r#"# Comment at start
3060key: value # inline comment
3061
3062# Another comment
3063list:
3064 - item1
3065 - item2
3066"#;
3067 let parsed = YamlFile::from_str(yaml).unwrap();
3068
3069 let doc = parsed.document().unwrap();
3070 let mapping = doc.as_mapping().unwrap();
3071 assert_eq!(
3072 mapping.get("key").unwrap().as_scalar().unwrap().as_string(),
3073 "value"
3074 );
3075 let list = mapping.get_sequence("list").unwrap();
3076 assert_eq!(list.len(), 2);
3077 let items: Vec<String> = list
3079 .values()
3080 .map(|v| v.to_string().trim().to_string())
3081 .collect();
3082 assert_eq!(items, vec!["item1", "item2"]);
3083
3084 let output = parsed.to_string();
3086 assert_eq!(output, yaml);
3087 }
3088
3089 #[test]
3090 fn test_quoted_strings() {
3091 let yaml = r#"
3092single: 'single quoted'
3093double: "double quoted"
3094plain: unquoted
3095"#;
3096 let parsed = YamlFile::from_str(yaml).unwrap();
3097 let doc = parsed.document().unwrap();
3098 let mapping = doc.as_mapping().unwrap();
3099
3100 assert!(mapping.get("single").is_some());
3101 assert!(mapping.get("double").is_some());
3102 assert!(mapping.get("plain").is_some());
3103 }
3104
3105 #[test]
3106 fn test_nested_structures() {
3107 let yaml = r#"
3108root:
3109 nested:
3110 deeply:
3111 value: 42
3112 list:
3113 - item1
3114 - item2
3115"#;
3116 let parsed = YamlFile::from_str(yaml).unwrap();
3117 assert!(parsed.document().is_some());
3118 }
3119
3120 #[test]
3121 fn test_empty_values() {
3122 let yaml = r#"
3123empty_string: ""
3124empty_after_colon:
3125another_key: value
3126"#;
3127 let parsed = YamlFile::from_str(yaml).unwrap();
3128 let doc = parsed.document().unwrap();
3129 let mapping = doc.as_mapping().unwrap();
3130
3131 assert!(mapping.get("empty_string").is_some());
3132 assert!(mapping.get("another_key").is_some());
3133 }
3134
3135 #[test]
3136 fn test_special_characters() {
3137 let yaml = r#"
3138special: "line1\nline2"
3139unicode: "emoji 😀"
3140escaped: 'it\'s escaped'
3141"#;
3142 let result = YamlFile::from_str(yaml);
3143 assert!(result.is_ok());
3145 }
3146
3147 #[test]
3150 fn test_error_handling() {
3151 let yaml = "key: value\n invalid indentation for key";
3153 let result = YamlFile::from_str(yaml);
3154 let _ = result;
3156 }
3157
3158 #[test]
3161 fn test_anchor_exact_output() {
3162 let yaml = "key: &anchor value\nref: *anchor";
3163 let parsed = YamlFile::from_str(yaml).unwrap();
3164 let output = parsed.to_string();
3165
3166 assert_eq!(output, "key: &anchor value\nref: *anchor");
3168 }
3169
3170 #[test]
3171 fn test_anchor_with_different_value_types() {
3172 let yaml = r#"string_anchor: &str_val "hello"
3173int_anchor: &int_val 42
3174bool_anchor: &bool_val true
3175null_anchor: &null_val null
3176str_ref: *str_val
3177int_ref: *int_val
3178bool_ref: *bool_val
3179null_ref: *null_val"#;
3180
3181 let parsed = YamlFile::from_str(yaml);
3182 assert!(
3183 parsed.is_ok(),
3184 "Should parse anchors with different value types"
3185 );
3186
3187 let yaml_doc = parsed.unwrap();
3188
3189 let doc = yaml_doc.document().unwrap();
3190 let mapping = doc.as_mapping().unwrap();
3191
3192 assert_eq!(
3194 mapping
3195 .get("string_anchor")
3196 .unwrap()
3197 .as_scalar()
3198 .unwrap()
3199 .as_string(),
3200 "hello"
3201 );
3202 assert_eq!(mapping.get("int_anchor").unwrap().to_i64(), Some(42));
3203 assert_eq!(mapping.get("bool_anchor").unwrap().to_bool(), Some(true));
3204 assert!(mapping.get("null_anchor").unwrap().as_scalar().is_some());
3205
3206 let str_ref = mapping.get("str_ref").unwrap();
3208 assert!(str_ref.is_alias());
3209 assert_eq!(str_ref.as_alias().unwrap().name(), "str_val");
3210
3211 let int_ref = mapping.get("int_ref").unwrap();
3212 assert!(int_ref.is_alias());
3213 assert_eq!(int_ref.as_alias().unwrap().name(), "int_val");
3214
3215 let bool_ref = mapping.get("bool_ref").unwrap();
3216 assert!(bool_ref.is_alias());
3217 assert_eq!(bool_ref.as_alias().unwrap().name(), "bool_val");
3218
3219 let null_ref = mapping.get("null_ref").unwrap();
3220 assert!(null_ref.is_alias());
3221 assert_eq!(null_ref.as_alias().unwrap().name(), "null_val");
3222
3223 let output = yaml_doc.to_string();
3225 assert_eq!(output, yaml);
3226 }
3227
3228 #[test]
3229 fn test_undefined_alias_parses_successfully() {
3230 let yaml = "key: *undefined";
3231 let parse_result = Parse::parse_yaml(yaml);
3232
3233 assert!(
3236 !parse_result.has_errors(),
3237 "Parser should not validate undefined aliases"
3238 );
3239
3240 let output = parse_result.tree().to_string();
3242 assert_eq!(output.trim(), "key: *undefined");
3243 }
3244
3245 #[test]
3246 fn test_anchor_names_with_alphanumeric_chars() {
3247 let yaml1 = "key1: &anchor_123 val1\nref1: *anchor_123";
3249 let parsed1 = YamlFile::from_str(yaml1);
3250 assert!(
3251 parsed1.is_ok(),
3252 "Should parse anchors with underscores and numbers"
3253 );
3254
3255 let file1 = parsed1.unwrap();
3256 let doc1 = file1.document().unwrap();
3257 let map1 = doc1.as_mapping().unwrap();
3258 assert_eq!(
3259 map1.get("key1").unwrap().as_scalar().unwrap().as_string(),
3260 "val1"
3261 );
3262 assert!(map1.get("ref1").unwrap().is_alias());
3263 assert_eq!(
3264 map1.get("ref1").unwrap().as_alias().unwrap().name(),
3265 "anchor_123"
3266 );
3267 assert_eq!(file1.to_string(), yaml1);
3268
3269 let yaml2 = "key2: &AnchorName val2\nref2: *AnchorName";
3270 let parsed2 = YamlFile::from_str(yaml2);
3271 assert!(parsed2.is_ok(), "Should parse anchors with mixed case");
3272
3273 let file2 = parsed2.unwrap();
3274 let doc2 = file2.document().unwrap();
3275 let map2 = doc2.as_mapping().unwrap();
3276 assert_eq!(
3277 map2.get("key2").unwrap().as_scalar().unwrap().as_string(),
3278 "val2"
3279 );
3280 assert!(map2.get("ref2").unwrap().is_alias());
3281 assert_eq!(
3282 map2.get("ref2").unwrap().as_alias().unwrap().name(),
3283 "AnchorName"
3284 );
3285 assert_eq!(file2.to_string(), yaml2);
3286
3287 let yaml3 = "key3: &anchor123abc val3\nref3: *anchor123abc";
3288 let parsed3 = YamlFile::from_str(yaml3);
3289 assert!(
3290 parsed3.is_ok(),
3291 "Should parse anchors with letters and numbers"
3292 );
3293
3294 let file3 = parsed3.unwrap();
3295 let doc3 = file3.document().unwrap();
3296 let map3 = doc3.as_mapping().unwrap();
3297 assert_eq!(
3298 map3.get("key3").unwrap().as_scalar().unwrap().as_string(),
3299 "val3"
3300 );
3301 assert!(map3.get("ref3").unwrap().is_alias());
3302 assert_eq!(
3303 map3.get("ref3").unwrap().as_alias().unwrap().name(),
3304 "anchor123abc"
3305 );
3306 assert_eq!(file3.to_string(), yaml3);
3307 }
3308
3309 #[test]
3310 fn test_anchor_in_sequence_detailed() {
3311 let yaml = r#"items:
3312 - &first_item value1
3313 - second_item
3314 - *first_item"#;
3315
3316 let parsed = YamlFile::from_str(yaml);
3317 assert!(parsed.is_ok(), "Should parse anchors in sequences");
3318
3319 let yaml_doc = parsed.unwrap();
3320
3321 let doc = yaml_doc.document().unwrap();
3322 let mapping = doc.as_mapping().unwrap();
3323 let seq = mapping.get_sequence("items").unwrap();
3324 assert_eq!(seq.len(), 3);
3325
3326 let item0 = seq.get(0).unwrap();
3328 let item1 = seq.get(1).unwrap();
3329 let item2 = seq.get(2).unwrap();
3330
3331 assert_eq!(item0.as_scalar().unwrap().as_string(), "value1");
3333
3334 assert_eq!(item1.as_scalar().unwrap().as_string(), "second_item");
3336
3337 assert!(item2.is_alias(), "Third item should be an alias");
3339 assert_eq!(item2.as_alias().unwrap().name(), "first_item");
3340
3341 let output = yaml_doc.to_string();
3342 assert_eq!(output, yaml);
3343 }
3344
3345 #[test]
3346 fn test_preserve_whitespace_around_anchors() {
3347 let yaml = "key: &anchor value \nref: *anchor ";
3348 let parsed = YamlFile::from_str(yaml).unwrap();
3349
3350 let doc = parsed.document().unwrap();
3351 let mapping = doc.as_mapping().unwrap();
3352 assert_eq!(
3353 mapping
3354 .get("key")
3355 .unwrap()
3356 .as_scalar()
3357 .unwrap()
3358 .as_string()
3359 .trim(),
3360 "value"
3361 );
3362 let ref_node = mapping.get("ref").unwrap();
3363 assert!(ref_node.is_alias());
3364 assert_eq!(ref_node.as_alias().unwrap().name(), "anchor");
3365
3366 let output = parsed.to_string();
3368 assert_eq!(output, yaml);
3369 }
3370
3371 #[test]
3372 fn test_literal_block_scalar_basic() {
3373 let yaml = r#"literal: |
3374 Line 1
3375 Line 2
3376 Line 3
3377"#;
3378 let parsed = YamlFile::from_str(yaml);
3379 assert!(parsed.is_ok(), "Should parse basic literal block scalar");
3380
3381 let yaml_doc = parsed.unwrap();
3382 let output = yaml_doc.to_string();
3383
3384 assert_eq!(output, yaml);
3386 }
3387
3388 #[test]
3389 fn test_folded_block_scalar_basic() {
3390 let yaml = r#"folded: >
3391 This is a very long line that will be folded
3392 into a single line in the output
3393 but preserves paragraph breaks.
3394
3395 This is a new paragraph.
3396"#;
3397 let parsed = YamlFile::from_str(yaml);
3398 assert!(parsed.is_ok(), "Should parse basic folded block scalar");
3399
3400 let yaml_doc = parsed.unwrap();
3401 let output = yaml_doc.to_string();
3402
3403 assert_eq!(output, yaml);
3405 }
3406
3407 #[test]
3408 fn test_literal_block_scalar_with_chomping_indicators() {
3409 let yaml1 = r#"strip: |-
3411 Line 1
3412 Line 2
3413
3414"#;
3415 let parsed1 = YamlFile::from_str(yaml1);
3416 assert!(
3417 parsed1.is_ok(),
3418 "Should parse literal block scalar with strip indicator"
3419 );
3420
3421 let file1 = parsed1.unwrap();
3422 let doc1 = file1.document().unwrap();
3423 let mapping1 = doc1.as_mapping().unwrap();
3424 let value1 = mapping1
3425 .get("strip")
3426 .unwrap()
3427 .as_scalar()
3428 .unwrap()
3429 .as_string();
3430 assert_eq!(value1, "Line 1\nLine 2");
3431
3432 let output1 = file1.to_string();
3433 assert_eq!(output1, yaml1);
3434
3435 let yaml2 = r#"keep: |+
3437 Line 1
3438 Line 2
3439
3440"#;
3441 let parsed2 = YamlFile::from_str(yaml2);
3442 assert!(
3443 parsed2.is_ok(),
3444 "Should parse literal block scalar with keep indicator"
3445 );
3446
3447 let file2 = parsed2.unwrap();
3448 let doc2 = file2.document().unwrap();
3449 let mapping2 = doc2.as_mapping().unwrap();
3450 let value2 = mapping2
3451 .get("keep")
3452 .unwrap()
3453 .as_scalar()
3454 .unwrap()
3455 .as_string();
3456 assert_eq!(value2, "Line 1\nLine 2\n\n");
3457
3458 let output2 = file2.to_string();
3459 assert_eq!(output2, yaml2);
3460 }
3461
3462 #[test]
3463 fn test_folded_block_scalar_with_chomping_indicators() {
3464 let yaml1 = r#"strip: >-
3466 Folded content that should
3467 be stripped of final newlines
3468"#;
3469 let parsed1 = YamlFile::from_str(yaml1);
3470 assert!(
3471 parsed1.is_ok(),
3472 "Should parse folded block scalar with strip indicator"
3473 );
3474
3475 let file1 = parsed1.unwrap();
3476 let doc1 = file1.document().unwrap();
3477 let mapping1 = doc1.as_mapping().unwrap();
3478 let value1 = mapping1
3479 .get("strip")
3480 .unwrap()
3481 .as_scalar()
3482 .unwrap()
3483 .as_string();
3484 assert_eq!(
3485 value1,
3486 "Folded content that should be stripped of final newlines"
3487 );
3488
3489 let output1 = file1.to_string();
3490 assert_eq!(output1, yaml1);
3491
3492 let yaml2 = r#"keep: >+
3494 Folded content that should
3495 keep all final newlines
3496
3497"#;
3498 let parsed2 = YamlFile::from_str(yaml2);
3499 assert!(
3500 parsed2.is_ok(),
3501 "Should parse folded block scalar with keep indicator"
3502 );
3503
3504 let file2 = parsed2.unwrap();
3505 let doc2 = file2.document().unwrap();
3506 let mapping2 = doc2.as_mapping().unwrap();
3507 let value2 = mapping2
3508 .get("keep")
3509 .unwrap()
3510 .as_scalar()
3511 .unwrap()
3512 .as_string();
3513 assert_eq!(
3514 value2,
3515 "Folded content that should keep all final newlines\n\n"
3516 );
3517
3518 let output2 = file2.to_string();
3519 assert_eq!(output2, yaml2);
3520 }
3521
3522 #[test]
3523 fn test_block_scalar_with_explicit_indentation() {
3524 let yaml1 = r#"explicit: |2
3525 Two space indent
3526 Another line
3527"#;
3528 let parsed1 = YamlFile::from_str(yaml1)
3529 .expect("Should parse literal block scalar with explicit indentation");
3530
3531 let doc1 = parsed1.document().expect("Should have document");
3532 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
3533 let scalar1 = mapping1
3534 .get("explicit")
3535 .expect("Should have 'explicit' key");
3536 assert_eq!(
3537 scalar1.as_scalar().unwrap().as_string(),
3538 "Two space indent\nAnother line\n"
3539 );
3540
3541 let output1 = parsed1.to_string();
3542 assert_eq!(output1, yaml1);
3543
3544 let yaml2 = r#"folded_explicit: >3
3545 Three space indent
3546 Another folded line
3547"#;
3548 let parsed2 = YamlFile::from_str(yaml2)
3549 .expect("Should parse folded block scalar with explicit indentation");
3550
3551 let doc2 = parsed2.document().expect("Should have document");
3552 let mapping2 = doc2.as_mapping().expect("Should be a mapping");
3553 let scalar2 = mapping2
3554 .get("folded_explicit")
3555 .expect("Should have 'folded_explicit' key");
3556 assert_eq!(
3557 scalar2.as_scalar().unwrap().as_string(),
3558 "Three space indent Another folded line\n"
3559 );
3560
3561 let output2 = parsed2.to_string();
3562 assert_eq!(output2, yaml2);
3563 }
3564
3565 #[test]
3566 fn test_block_scalar_in_mapping() {
3567 let yaml = r#"description: |
3568 This is a multi-line
3569 description that should
3570 preserve line breaks.
3571
3572 It can have multiple paragraphs too.
3573
3574summary: >
3575 This is a summary that
3576 should be folded into
3577 a single line.
3578
3579version: "1.0"
3580"#;
3581 let parsed =
3582 YamlFile::from_str(yaml).expect("Should parse block scalars in mapping context");
3583
3584 let doc = parsed.document().expect("Should have document");
3585 let mapping = doc.as_mapping().expect("Should be a mapping");
3586
3587 let description = mapping
3588 .get("description")
3589 .expect("Should have 'description' key");
3590 assert_eq!(
3591 description.as_scalar().unwrap().as_string(),
3592 "This is a multi-line\ndescription that should\npreserve line breaks.\n\nIt can have multiple paragraphs too.\n"
3593 );
3594
3595 let summary = mapping.get("summary").expect("Should have 'summary' key");
3596 assert_eq!(
3597 summary.as_scalar().unwrap().as_string(),
3598 "This is a summary that should be folded into a single line.\n"
3599 );
3600
3601 let version = mapping.get("version").expect("Should have 'version' key");
3602 assert_eq!(version.as_scalar().unwrap().as_string(), "1.0");
3603
3604 let output = parsed.to_string();
3605 assert_eq!(output, yaml);
3606 }
3607
3608 #[test]
3609 fn test_mixed_block_and_regular_scalars() {
3610 let yaml = r#"config:
3611 name: "My App"
3612 description: |
3613 This application does many things:
3614 - Feature 1
3615 - Feature 2
3616 - Feature 3
3617 summary: >
3618 A brief summary that spans
3619 multiple lines but should
3620 be folded together.
3621 version: 1.0
3622 enabled: true
3623"#;
3624 let parsed =
3625 YamlFile::from_str(yaml).expect("Should parse mixed block and regular scalars");
3626
3627 let doc = parsed.document().expect("Should have document");
3628 let mapping = doc.as_mapping().expect("Should be a mapping");
3629 let config_node = mapping.get("config").expect("Should have 'config' key");
3630 let config = config_node
3631 .as_mapping()
3632 .expect("Should be a nested mapping");
3633
3634 assert_eq!(
3635 config.get("name").unwrap().as_scalar().unwrap().as_string(),
3636 "My App"
3637 );
3638 assert_eq!(
3639 config
3640 .get("description")
3641 .unwrap()
3642 .as_scalar()
3643 .unwrap()
3644 .as_string(),
3645 "This application does many things:\n- Feature 1\n- Feature 2\n- Feature 3\n"
3646 );
3647 assert_eq!(
3648 config
3649 .get("summary")
3650 .unwrap()
3651 .as_scalar()
3652 .unwrap()
3653 .as_string(),
3654 "A brief summary that spans multiple lines but should be folded together.\n"
3655 );
3656 assert_eq!(
3657 config
3658 .get("version")
3659 .unwrap()
3660 .as_scalar()
3661 .unwrap()
3662 .as_string(),
3663 "1.0"
3664 );
3665 assert_eq!(config.get("enabled").unwrap().to_bool(), Some(true));
3666
3667 let output = parsed.to_string();
3668 assert_eq!(output, yaml);
3669 }
3670
3671 #[test]
3672 fn test_block_scalar_edge_cases() {
3673 let yaml1 = r#"empty_literal: |
3677empty_folded: >
3678"#;
3679 let parsed1 = YamlFile::from_str(yaml1).expect("Should parse this edge case");
3680
3681 let doc1 = parsed1.document().expect("Should have document");
3683 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
3684 assert_eq!(mapping1.len(), 1, "Should have only one key");
3685 assert_eq!(
3686 mapping1
3687 .get("empty_literal")
3688 .unwrap()
3689 .as_scalar()
3690 .unwrap()
3691 .as_string(),
3692 "empty_folded: >\n"
3693 );
3694
3695 assert_eq!(parsed1.to_string(), yaml1);
3696
3697 let yaml2 = r#"whitespace: |
3699
3700
3701"#;
3702 let parsed2 =
3703 YamlFile::from_str(yaml2).expect("Should parse block scalar with only whitespace");
3704
3705 assert_eq!(parsed2.to_string(), yaml2);
3706
3707 let yaml3 = r#"first: |
3709 Content
3710second: value
3711"#;
3712 let parsed3 =
3713 YamlFile::from_str(yaml3).expect("Should parse block scalar followed by other keys");
3714
3715 let doc3 = parsed3.document().expect("Should have document");
3716 let mapping3 = doc3.as_mapping().expect("Should be a mapping");
3717 assert_eq!(
3718 mapping3
3719 .get("first")
3720 .unwrap()
3721 .as_scalar()
3722 .unwrap()
3723 .as_string(),
3724 "Content\n"
3725 );
3726 assert_eq!(
3727 mapping3
3728 .get("second")
3729 .unwrap()
3730 .as_scalar()
3731 .unwrap()
3732 .as_string(),
3733 "value"
3734 );
3735
3736 let output3 = parsed3.to_string();
3737 assert_eq!(output3, yaml3);
3738 }
3739
3740 #[test]
3741 fn test_literal_block_scalar_advanced_formatting() {
3742 let yaml = r#"poem: |
3743 Roses are red,
3744 Violets are blue,
3745 YAML is great,
3746 And so are you!
3747
3748 This is another stanza
3749 with different content.
3750 And this line has extra indentation.
3751 Back to normal indentation.
3752
3753 Final stanza.
3754"#;
3755 let parsed = YamlFile::from_str(yaml).expect("Should parse complex literal block scalar");
3756
3757 let doc = parsed.document().expect("Should have document");
3758 let mapping = doc.as_mapping().expect("Should be a mapping");
3759 let poem = mapping.get("poem").expect("Should have 'poem' key");
3760 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";
3761 assert_eq!(poem.as_scalar().unwrap().as_string(), expected_content);
3762
3763 let output = parsed.to_string();
3764 assert_eq!(output, yaml);
3765 }
3766
3767 #[test]
3768 fn test_folded_block_scalar_paragraph_handling() {
3769 let yaml = r#"description: >
3770 This is the first paragraph that should
3771 be folded into a single line when processed
3772 by a YAML parser.
3773
3774 This is a second paragraph that should
3775 also be folded but kept separate from
3776 the first paragraph.
3777
3778
3779 This is a third paragraph after
3780 multiple blank lines.
3781
3782 Final paragraph.
3783"#;
3784 let parsed =
3785 YamlFile::from_str(yaml).expect("Should parse folded block scalar with paragraphs");
3786
3787 let doc = parsed.document().expect("Should have document");
3788 let mapping = doc.as_mapping().expect("Should be a mapping");
3789 let description = mapping
3790 .get("description")
3791 .expect("Should have 'description' key");
3792 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";
3793 assert_eq!(
3794 description.as_scalar().unwrap().as_string(),
3795 expected_content
3796 );
3797
3798 let output = parsed.to_string();
3799 assert_eq!(output, yaml);
3800 }
3801
3802 #[test]
3803 fn test_block_scalars_with_special_characters() {
3804 let yaml = r#"special_chars: |
3805 Line with colons: key: value
3806 Line with dashes - and more - dashes
3807 Line with quotes "double" and 'single'
3808 Line with brackets [array] and braces {object}
3809 Line with pipes | and greater than >
3810 Line with at @ and hash # symbols
3811 Line with percent % and exclamation !
3812
3813backslash_test: >
3814 This line has a backslash \ in it
3815 And this line has multiple \\ backslashes
3816
3817unicode_test: |
3818 This line has unicode: 你好世界
3819 And emojis: 🚀 🎉 ✨
3820"#;
3821 let parsed =
3822 YamlFile::from_str(yaml).expect("Should parse block scalars with special characters");
3823
3824 let doc = parsed.document().expect("Should have document");
3825 let mapping = doc.as_mapping().expect("Should be a mapping");
3826
3827 let special_chars = mapping
3828 .get("special_chars")
3829 .expect("Should have 'special_chars' key");
3830 assert_eq!(
3831 special_chars.as_scalar().unwrap().as_string(),
3832 "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"
3833 );
3834
3835 let backslash_test = mapping
3836 .get("backslash_test")
3837 .expect("Should have 'backslash_test' key");
3838 assert_eq!(
3839 backslash_test.as_scalar().unwrap().as_string(),
3840 "This line has a backslash \\ in it And this line has multiple \\\\ backslashes\n"
3841 );
3842
3843 let unicode_test = mapping
3844 .get("unicode_test")
3845 .expect("Should have 'unicode_test' key");
3846 assert_eq!(
3847 unicode_test.as_scalar().unwrap().as_string(),
3848 "This line has unicode: 你好世界\nAnd emojis: 🚀 🎉 ✨\n"
3849 );
3850
3851 let output = parsed.to_string();
3852 assert_eq!(output, yaml);
3853 }
3854
3855 #[test]
3856 fn test_block_scalar_chomping_detailed() {
3857 let yaml_clip = r#"clip: |
3859 Line 1
3860 Line 2
3861
3862"#;
3863 let parsed_clip =
3864 YamlFile::from_str(yaml_clip).expect("Should parse block scalar with default clipping");
3865
3866 let doc_clip = parsed_clip.document().expect("Should have document");
3868 let mapping_clip = doc_clip.as_mapping().expect("Should be a mapping");
3869 assert_eq!(
3870 mapping_clip
3871 .get("clip")
3872 .unwrap()
3873 .as_scalar()
3874 .unwrap()
3875 .as_string(),
3876 "Line 1\nLine 2\n"
3877 );
3878
3879 assert_eq!(parsed_clip.to_string(), yaml_clip);
3880
3881 let yaml_strip = r#"strip: |-
3883 Line 1
3884 Line 2
3885
3886
3887
3888"#;
3889 let parsed_strip =
3890 YamlFile::from_str(yaml_strip).expect("Should parse block scalar with strip indicator");
3891
3892 let doc_strip = parsed_strip.document().expect("Should have document");
3894 let mapping_strip = doc_strip.as_mapping().expect("Should be a mapping");
3895 assert_eq!(
3896 mapping_strip
3897 .get("strip")
3898 .unwrap()
3899 .as_scalar()
3900 .unwrap()
3901 .as_string(),
3902 "Line 1\nLine 2"
3903 );
3904
3905 assert_eq!(parsed_strip.to_string(), yaml_strip);
3906
3907 let yaml_keep = r#"keep: |+
3909 Line 1
3910 Line 2
3911
3912
3913
3914"#;
3915 let parsed_keep =
3916 YamlFile::from_str(yaml_keep).expect("Should parse block scalar with keep indicator");
3917
3918 let doc_keep = parsed_keep.document().expect("Should have document");
3920 let mapping_keep = doc_keep.as_mapping().expect("Should be a mapping");
3921 assert_eq!(
3922 mapping_keep
3923 .get("keep")
3924 .unwrap()
3925 .as_scalar()
3926 .unwrap()
3927 .as_string(),
3928 "Line 1\nLine 2\n\n\n\n"
3929 );
3930
3931 assert_eq!(parsed_keep.to_string(), yaml_keep);
3932 }
3933
3934 #[test]
3935 fn test_block_scalar_explicit_indentation_detailed() {
3936 let yaml1 = r#"indent1: |1
3938 Single space indent
3939"#;
3940 let parsed1 = YamlFile::from_str(yaml1);
3941 assert!(parsed1.is_ok(), "Should parse |1 block scalar");
3942 let output1 = parsed1.unwrap().to_string();
3943 assert_eq!(output1, yaml1);
3944
3945 let yaml2 = r#"indent2: |2
3946 Two space indent
3947"#;
3948 let parsed2 = YamlFile::from_str(yaml2);
3949 assert!(parsed2.is_ok(), "Should parse |2 block scalar");
3950 let output2 = parsed2.unwrap().to_string();
3951 assert_eq!(output2, yaml2);
3952
3953 let yaml3 = r#"folded_indent: >2
3954 Two space folded
3955 content spans lines
3956"#;
3957 let parsed3 = YamlFile::from_str(yaml3);
3958 assert!(parsed3.is_ok(), "Should parse >2 folded block scalar");
3959 let output3 = parsed3.unwrap().to_string();
3960 assert_eq!(output3, yaml3);
3961 }
3962
3963 #[test]
3964 fn test_block_scalar_combined_indicators() {
3965 let yaml = r#"strip_with_indent: |2-
3966 Content with explicit indent
3967 and strip chomping
3968
3969
3970keep_with_indent: >3+
3971 Content with explicit indent
3972 and keep chomping
3973
3974
3975
3976folded_strip: >-
3977 Folded content
3978 with strip indicator
3979
3980literal_keep: |+
3981 Literal content
3982 with keep indicator
3983
3984
3985"#;
3986 let parsed =
3987 YamlFile::from_str(yaml).expect("Should parse block scalars with combined indicators");
3988
3989 let doc = parsed.document().expect("Should have document");
3990 let mapping = doc.as_mapping().expect("Should be a mapping");
3991
3992 assert_eq!(
3993 mapping
3994 .get("strip_with_indent")
3995 .unwrap()
3996 .as_scalar()
3997 .unwrap()
3998 .as_string(),
3999 "Content with explicit indent\nand strip chomping"
4000 );
4001 assert_eq!(
4002 mapping
4003 .get("keep_with_indent")
4004 .unwrap()
4005 .as_scalar()
4006 .unwrap()
4007 .as_string(),
4008 "Content with explicit indent and keep chomping\n\n\n\n"
4009 );
4010 assert_eq!(
4011 mapping
4012 .get("folded_strip")
4013 .unwrap()
4014 .as_scalar()
4015 .unwrap()
4016 .as_string(),
4017 "Folded content with strip indicator"
4018 );
4019 assert_eq!(
4020 mapping
4021 .get("literal_keep")
4022 .unwrap()
4023 .as_scalar()
4024 .unwrap()
4025 .as_string(),
4026 "Literal content\nwith keep indicator\n\n\n"
4027 );
4028
4029 let output = parsed.to_string();
4030 assert_eq!(output, yaml);
4031 }
4032
4033 #[test]
4034 fn test_block_scalar_whitespace_and_empty() {
4035 let yaml1 = r#"whitespace_only: |
4037
4038
4039
4040"#;
4041 let parsed1 =
4042 YamlFile::from_str(yaml1).expect("Should handle block scalar with only whitespace");
4043
4044 let doc1 = parsed1.document().expect("Should have document");
4045 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
4046 assert_eq!(
4047 mapping1
4048 .get("whitespace_only")
4049 .unwrap()
4050 .as_scalar()
4051 .unwrap()
4052 .as_string(),
4053 "\n"
4054 );
4055
4056 assert_eq!(parsed1.to_string(), yaml1);
4057
4058 let yaml2 = r#"mixed_indent: |
4060 Normal line
4061 Indented line
4062 Back to normal
4063 More indented
4064 Normal again
4065"#;
4066 let parsed2 = YamlFile::from_str(yaml2).expect("Should handle mixed indentation levels");
4067
4068 let doc2 = parsed2.document().expect("Should have document");
4069 let mapping2 = doc2.as_mapping().expect("Should be a mapping");
4070 assert_eq!(
4071 mapping2
4072 .get("mixed_indent")
4073 .unwrap()
4074 .as_scalar()
4075 .unwrap()
4076 .as_string(),
4077 "Normal line\n Indented line\nBack to normal\n More indented\nNormal again\n"
4078 );
4079
4080 assert_eq!(parsed2.to_string(), yaml2);
4081
4082 let yaml3 = r#"first: |
4084 Content
4085immediate: value
4086another: |
4087 More content
4088final: end
4089"#;
4090 let parsed3 =
4091 YamlFile::from_str(yaml3).expect("Should handle multiple block scalars in mapping");
4092
4093 let doc3 = parsed3.document().expect("Should have document");
4094 let mapping3 = doc3.as_mapping().expect("Should be a mapping");
4095 assert_eq!(
4096 mapping3
4097 .get("first")
4098 .unwrap()
4099 .as_scalar()
4100 .unwrap()
4101 .as_string(),
4102 "Content\n"
4103 );
4104 assert_eq!(
4105 mapping3
4106 .get("immediate")
4107 .unwrap()
4108 .as_scalar()
4109 .unwrap()
4110 .as_string(),
4111 "value"
4112 );
4113 assert_eq!(
4114 mapping3
4115 .get("another")
4116 .unwrap()
4117 .as_scalar()
4118 .unwrap()
4119 .as_string(),
4120 "More content\n"
4121 );
4122 assert_eq!(
4123 mapping3
4124 .get("final")
4125 .unwrap()
4126 .as_scalar()
4127 .unwrap()
4128 .as_string(),
4129 "end"
4130 );
4131
4132 let output3 = parsed3.to_string();
4133 assert_eq!(output3, yaml3);
4134 }
4135
4136 #[test]
4137 fn test_block_scalar_with_comments() {
4138 let yaml = r#"# Main configuration
4139config: | # This is a literal block
4140 # This comment is inside the block
4141 line1: value1
4142 # Another internal comment
4143 line2: value2
4144
4145# Outside comment
4146other: > # Folded block comment
4147 This content spans
4148 # This hash is part of the content, not a comment
4149 multiple lines
4150"#;
4151 let parsed = YamlFile::from_str(yaml).expect("Should parse block scalars with comments");
4152
4153 let doc = parsed.document().expect("Should have document");
4154 let mapping = doc.as_mapping().expect("Should be a mapping");
4155
4156 assert_eq!(
4163 mapping.get("config").unwrap().as_scalar().unwrap().as_string(),
4164 "# This comment is inside the block\nline1: value1\n# Another internal comment\nline2: value2\n\n# Outside comment\n"
4165 );
4166
4167 assert_eq!(
4168 mapping
4169 .get("other")
4170 .unwrap()
4171 .as_scalar()
4172 .unwrap()
4173 .as_string(),
4174 "This content spans # This hash is part of the content, not a comment multiple lines\n"
4175 );
4176
4177 let output = parsed.to_string();
4178 assert_eq!(output, yaml);
4179 }
4180
4181 #[test]
4182 fn test_block_scalar_empty_and_minimal() {
4183 let yaml = r#"empty_literal: |
4184
4185empty_folded: >
4186
4187minimal_literal: |
4188 x
4189
4190minimal_folded: >
4191 y
4192
4193just_newlines: |
4194
4195
4196
4197just_spaces: |
4198
4199
4200
4201"#;
4202 let parsed =
4203 YamlFile::from_str(yaml).expect("Should handle empty and minimal block scalars");
4204
4205 let doc = parsed.document().expect("Should have document");
4206 let mapping = doc.as_mapping().expect("Should be a mapping");
4207
4208 assert_eq!(
4209 mapping
4210 .get("empty_literal")
4211 .unwrap()
4212 .as_scalar()
4213 .unwrap()
4214 .as_string(),
4215 "\n"
4216 );
4217 assert_eq!(
4218 mapping
4219 .get("empty_folded")
4220 .unwrap()
4221 .as_scalar()
4222 .unwrap()
4223 .as_string(),
4224 "\n"
4225 );
4226 assert_eq!(
4227 mapping
4228 .get("minimal_literal")
4229 .unwrap()
4230 .as_scalar()
4231 .unwrap()
4232 .as_string(),
4233 "x\n"
4234 );
4235 assert_eq!(
4236 mapping
4237 .get("minimal_folded")
4238 .unwrap()
4239 .as_scalar()
4240 .unwrap()
4241 .as_string(),
4242 "y\n"
4243 );
4244 assert_eq!(
4245 mapping
4246 .get("just_newlines")
4247 .unwrap()
4248 .as_scalar()
4249 .unwrap()
4250 .as_string(),
4251 "\n"
4252 );
4253 assert_eq!(
4254 mapping
4255 .get("just_spaces")
4256 .unwrap()
4257 .as_scalar()
4258 .unwrap()
4259 .as_string(),
4260 "\n"
4261 );
4262
4263 let output = parsed.to_string();
4264 assert_eq!(output, yaml);
4265 }
4266
4267 #[test]
4268 fn test_block_scalar_with_document_markers() {
4269 let yaml = r#"---
4270doc1: |
4271 This is the first document
4272 with a literal block scalar.
4273
4274next_key: value
4275---
4276doc2: >
4277 This is the second document
4278 with a folded block scalar.
4279
4280another_key: another_value
4281...
4282"#;
4283 let parsed =
4284 YamlFile::from_str(yaml).expect("Should parse block scalars with document markers");
4285
4286 let doc = parsed.document().expect("Should have first document");
4288 let mapping = doc.as_mapping().expect("Should be a mapping");
4289
4290 assert_eq!(
4291 mapping
4292 .get("doc1")
4293 .unwrap()
4294 .as_scalar()
4295 .unwrap()
4296 .as_string(),
4297 "This is the first document\nwith a literal block scalar.\n"
4298 );
4299 assert_eq!(
4300 mapping
4301 .get("next_key")
4302 .unwrap()
4303 .as_scalar()
4304 .unwrap()
4305 .as_string(),
4306 "value"
4307 );
4308
4309 let output = parsed.to_string();
4311 assert_eq!(output, yaml);
4312 }
4313
4314 #[test]
4315 fn test_block_scalar_formatting_preservation() {
4316 let original = r#"preserve_me: |
4317 Line with multiple spaces
4318 Line with tabs here
4319 Line with trailing spaces
4320
4321 Empty line above and below
4322
4323 Final line
4324"#;
4325 let parsed = YamlFile::from_str(original).expect("Should preserve exact formatting");
4326
4327 let doc = parsed.document().expect("Should have document");
4328 let mapping = doc.as_mapping().expect("Should be a mapping");
4329
4330 let preserve_me = mapping
4331 .get("preserve_me")
4332 .expect("Should have 'preserve_me' key");
4333 let expected = "Line with multiple spaces\nLine with\ttabs\there\nLine with trailing spaces\n\nEmpty line above and below\n\nFinal line\n";
4334 assert_eq!(preserve_me.as_scalar().unwrap().as_string(), expected);
4335
4336 let output = parsed.to_string();
4338 assert_eq!(output, original);
4339 }
4340
4341 #[test]
4342 fn test_block_scalar_complex_yaml_content() {
4343 let yaml = r#"yaml_content: |
4344 # This block contains YAML-like content
4345 nested:
4346 - item: value
4347 - item: another
4348
4349 mapping:
4350 key1: |
4351 Even more nested literal content
4352 key2: value
4353
4354 anchors: &anchor
4355 anchor_content: data
4356
4357 reference: *anchor
4358
4359quoted_yaml: >
4360 This folded block contains
4361 YAML structures: {key: value, array: [1, 2, 3]}
4362 that should be treated as plain text.
4363"#;
4364 let parsed = YamlFile::from_str(yaml)
4365 .expect("Should parse block scalars containing YAML-like structures");
4366
4367 let doc = parsed.document().expect("Should have document");
4368 let mapping = doc.as_mapping().expect("Should be a mapping");
4369
4370 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";
4371 assert_eq!(
4372 mapping
4373 .get("yaml_content")
4374 .unwrap()
4375 .as_scalar()
4376 .unwrap()
4377 .as_string(),
4378 expected_yaml_content
4379 );
4380
4381 let expected_quoted_yaml = "This folded block contains YAML structures: {key: value, array: [1, 2, 3]} that should be treated as plain text.\n";
4382 assert_eq!(
4383 mapping
4384 .get("quoted_yaml")
4385 .unwrap()
4386 .as_scalar()
4387 .unwrap()
4388 .as_string(),
4389 expected_quoted_yaml
4390 );
4391
4392 let output = parsed.to_string();
4393 assert_eq!(output, yaml);
4394 }
4395
4396 #[test]
4397 fn test_block_scalar_performance_large_content() {
4398 let mut large_content = String::new();
4400 for i in 1..=100 {
4401 large_content.push_str(&format!(
4402 " Line number {} with some content that makes it longer\n",
4403 i
4404 ));
4405 }
4406
4407 let yaml = format!(
4408 "large_literal: |\n{}\nlarge_folded: >\n{}\n",
4409 large_content, large_content
4410 );
4411
4412 let parsed =
4413 YamlFile::from_str(&yaml).expect("Should parse large block scalars without errors");
4414
4415 let doc = parsed.document().expect("Should have document");
4416 let mapping = doc.as_mapping().expect("Should be a mapping");
4417
4418 let literal_value = mapping
4419 .get("large_literal")
4420 .expect("Should have large_literal key")
4421 .as_scalar()
4422 .expect("Should be scalar")
4423 .as_string();
4424
4425 let mut expected_literal = String::new();
4427 for i in 1..=100 {
4428 expected_literal.push_str(&format!(
4429 "Line number {} with some content that makes it longer\n",
4430 i
4431 ));
4432 }
4433 assert_eq!(literal_value, expected_literal);
4434
4435 let folded_value = mapping
4436 .get("large_folded")
4437 .expect("Should have large_folded key")
4438 .as_scalar()
4439 .expect("Should be scalar")
4440 .as_string();
4441
4442 let mut expected_folded = String::new();
4444 for i in 1..=100 {
4445 if i > 1 {
4446 expected_folded.push(' ');
4447 }
4448 expected_folded.push_str(&format!(
4449 "Line number {} with some content that makes it longer",
4450 i
4451 ));
4452 }
4453 expected_folded.push('\n');
4454 assert_eq!(folded_value, expected_folded);
4455
4456 let output = parsed.to_string();
4457 assert_eq!(output, yaml);
4458 }
4459
4460 #[test]
4461 fn test_block_scalar_error_recovery() {
4462 let yaml = r#"good_key: value
4464bad_block: |
4465incomplete_key
4466another_good: works
4467"#;
4468 let parsed = YamlFile::from_str(yaml).expect("Should parse");
4469
4470 let doc = parsed.document().expect("Should have document");
4471 let mapping = doc.as_mapping().expect("Should be a mapping");
4472
4473 assert_eq!(
4475 mapping
4476 .get("good_key")
4477 .unwrap()
4478 .as_scalar()
4479 .unwrap()
4480 .as_string(),
4481 "value"
4482 );
4483
4484 assert_eq!(
4486 mapping
4487 .get("bad_block")
4488 .unwrap()
4489 .as_scalar()
4490 .unwrap()
4491 .as_string(),
4492 "incomplete_key\n"
4493 );
4494
4495 assert_eq!(
4497 mapping
4498 .get("another_good")
4499 .unwrap()
4500 .as_scalar()
4501 .unwrap()
4502 .as_string(),
4503 "works"
4504 );
4505
4506 let output = parsed.to_string();
4507 assert_eq!(output, yaml);
4508 }
4509
4510 #[test]
4511 fn test_block_scalar_with_flow_structures() {
4512 let yaml = r#"mixed_styles: |
4513 This literal block contains:
4514 - A flow sequence: [1, 2, 3]
4515 - A flow mapping: {key: value, other: data}
4516 - Mixed content: [a, {nested: true}, c]
4517
4518flow_then_block:
4519 flow_seq: [item1, item2]
4520 block_literal: |
4521 This comes after flow style
4522 and should work fine.
4523 flow_map: {after: block}
4524"#;
4525 let parsed = YamlFile::from_str(yaml).expect("Should parse mixed flow and block styles");
4526
4527 let doc = parsed.document().expect("Should have document");
4528 let mapping = doc.as_mapping().expect("Should be a mapping");
4529
4530 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";
4532 assert_eq!(
4533 mapping
4534 .get("mixed_styles")
4535 .unwrap()
4536 .as_scalar()
4537 .unwrap()
4538 .as_string(),
4539 expected_mixed
4540 );
4541
4542 let flow_then_block_value = mapping.get("flow_then_block").unwrap();
4544 let flow_then_block = flow_then_block_value.as_mapping().unwrap();
4545
4546 let flow_seq_value = flow_then_block.get("flow_seq").unwrap();
4548 let flow_seq = flow_seq_value.as_sequence().unwrap();
4549 assert_eq!(flow_seq.len(), 2);
4550 assert_eq!(
4551 flow_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
4552 "item1"
4553 );
4554 assert_eq!(
4555 flow_seq.get(1).unwrap().as_scalar().unwrap().as_string(),
4556 "item2"
4557 );
4558
4559 assert_eq!(
4561 flow_then_block
4562 .get("block_literal")
4563 .unwrap()
4564 .as_scalar()
4565 .unwrap()
4566 .as_string(),
4567 "This comes after flow style\nand should work fine.\n"
4568 );
4569
4570 let flow_map_value = flow_then_block.get("flow_map").unwrap();
4572 let flow_map = flow_map_value.as_mapping().unwrap();
4573 assert_eq!(
4574 flow_map
4575 .get("after")
4576 .unwrap()
4577 .as_scalar()
4578 .unwrap()
4579 .as_string(),
4580 "block"
4581 );
4582
4583 let output = parsed.to_string();
4584 assert_eq!(output, yaml);
4585 }
4586
4587 #[test]
4588 fn test_block_scalar_indentation_edge_cases() {
4589 let yaml1 = r#"empty: |
4591next: value"#;
4592 let parsed1 = YamlFile::from_str(yaml1);
4593 assert!(parsed1.is_ok(), "Should handle empty block followed by key");
4594
4595 let yaml2 = r#"inconsistent: |
4597 normal indent
4598 more indent
4599 back to normal
4600 even more
4601 normal
4602"#;
4603 let parsed2 = YamlFile::from_str(yaml2);
4604 assert!(
4605 parsed2.is_ok(),
4606 "Should handle inconsistent but valid indentation"
4607 );
4608
4609 let yaml3 = "tabs: |\n\tTab indented line\n\tAnother tab line\n";
4611 let parsed3 = YamlFile::from_str(yaml3);
4612 assert!(
4613 parsed3.is_ok(),
4614 "Should handle tab characters in block scalars"
4615 );
4616 }
4617
4618 #[test]
4619 fn test_block_scalar_with_anchors_and_aliases() {
4620 let yaml = r#"template: &template |
4621 This is a template
4622 with multiple lines
4623 that can be referenced.
4624
4625instance1: *template
4626
4627instance2:
4628 content: *template
4629 other: value
4630
4631modified: |
4632 <<: *template
4633 Additional content here
4634"#;
4635 let parsed =
4636 YamlFile::from_str(yaml).expect("Should parse block scalars with anchors and aliases");
4637
4638 let doc = parsed.document().expect("Should have document");
4639 let mapping = doc.as_mapping().expect("Should be a mapping");
4640
4641 let expected_template =
4643 "This is a template\nwith multiple lines\nthat can be referenced.\n";
4644 let template_value = mapping.get("template").unwrap();
4645 assert_eq!(
4646 template_value.as_scalar().unwrap().as_string(),
4647 expected_template
4648 );
4649
4650 let instance1 = mapping.get("instance1").unwrap();
4652 assert!(
4653 instance1.is_alias(),
4654 "instance1 should be an alias, not a scalar"
4655 );
4656 assert_eq!(instance1.as_alias().unwrap().name(), "template");
4657
4658 let instance2_value = mapping.get("instance2").unwrap();
4660 let instance2 = instance2_value.as_mapping().unwrap();
4661
4662 let content = instance2.get("content").unwrap();
4664 assert!(
4665 content.is_alias(),
4666 "content should be an alias, not a scalar"
4667 );
4668 assert_eq!(content.as_alias().unwrap().name(), "template");
4669
4670 assert_eq!(
4672 instance2
4673 .get("other")
4674 .unwrap()
4675 .as_scalar()
4676 .unwrap()
4677 .as_string(),
4678 "value"
4679 );
4680
4681 let modified = mapping.get("modified").unwrap();
4684 assert!(
4685 modified.is_scalar(),
4686 "modified should be a scalar, not an alias"
4687 );
4688 assert_eq!(
4689 modified.as_scalar().unwrap().as_string(),
4690 "<<: *template\nAdditional content here\n"
4691 );
4692
4693 let output = parsed.to_string();
4694 assert_eq!(output, yaml);
4695 }
4696
4697 #[test]
4698 fn test_block_scalar_newline_variations() {
4699 let yaml_unix = "unix: |\n Line 1\n Line 2\n";
4701 let parsed_unix = YamlFile::from_str(yaml_unix).expect("Should handle Unix newlines");
4702
4703 let yaml_windows = "windows: |\r\n Line 1\r\n Line 2\r\n";
4704 let parsed_windows =
4705 YamlFile::from_str(yaml_windows).expect("Should handle Windows newlines");
4706
4707 let doc_unix = parsed_unix.document().expect("Should have document");
4709 let mapping_unix = doc_unix.as_mapping().expect("Should be a mapping");
4710 assert_eq!(
4711 mapping_unix
4712 .get("unix")
4713 .unwrap()
4714 .as_scalar()
4715 .unwrap()
4716 .as_string(),
4717 "Line 1\nLine 2\n"
4718 );
4719
4720 let doc_windows = parsed_windows.document().expect("Should have document");
4722 let mapping_windows = doc_windows.as_mapping().expect("Should be a mapping");
4723 assert_eq!(
4724 mapping_windows
4725 .get("windows")
4726 .unwrap()
4727 .as_scalar()
4728 .unwrap()
4729 .as_string(),
4730 "Line 1\nLine 2\n"
4731 );
4732
4733 assert_eq!(parsed_unix.to_string(), yaml_unix);
4734 assert_eq!(parsed_windows.to_string(), yaml_windows);
4735 }
4736
4737 #[test]
4738 fn test_block_scalar_boundary_detection() {
4739 let yaml = r#"config:
4741 description: |
4742 This is a configuration
4743 with multiple lines.
4744
4745 name: "MyApp"
4746 version: 1.0
4747
4748 settings: >
4749 These are settings that
4750 span multiple lines too.
4751
4752 debug: true
4753"#;
4754 let parsed =
4755 YamlFile::from_str(yaml).expect("Should properly detect block scalar boundaries");
4756
4757 let doc = parsed.document().expect("Should have document");
4758 let mapping = doc.as_mapping().expect("Should be a mapping");
4759 let config_value = mapping.get("config").unwrap();
4760 let config = config_value.as_mapping().unwrap();
4761
4762 assert_eq!(
4764 config
4765 .get("description")
4766 .unwrap()
4767 .as_scalar()
4768 .unwrap()
4769 .as_string(),
4770 "This is a configuration\nwith multiple lines.\n"
4771 );
4772
4773 assert_eq!(
4775 config.get("name").unwrap().as_scalar().unwrap().as_string(),
4776 "MyApp"
4777 );
4778
4779 assert_eq!(
4781 config
4782 .get("version")
4783 .unwrap()
4784 .as_scalar()
4785 .unwrap()
4786 .as_string(),
4787 "1.0"
4788 );
4789
4790 assert_eq!(
4792 config
4793 .get("settings")
4794 .unwrap()
4795 .as_scalar()
4796 .unwrap()
4797 .as_string(),
4798 "These are settings that span multiple lines too.\n"
4799 );
4800
4801 assert_eq!(
4803 config
4804 .get("debug")
4805 .unwrap()
4806 .as_scalar()
4807 .unwrap()
4808 .as_string(),
4809 "true"
4810 );
4811
4812 let output = parsed.to_string();
4813 assert_eq!(output, yaml);
4814 }
4815
4816 #[test]
4817 fn test_block_scalar_with_numeric_content() {
4818 let yaml = r#"numbers_as_text: |
4819 123
4820 45.67
4821 -89
4822 +100
4823 0xFF
4824 1e5
4825 true
4826 false
4827 null
4828
4829calculations: >
4830 The result is: 2 + 2 = 4
4831 And 10 * 5 = 50
4832 Also: 100% complete
4833"#;
4834 let parsed = YamlFile::from_str(yaml)
4835 .expect("Should parse numeric content as text in block scalars");
4836
4837 let doc = parsed.document().expect("Should have document");
4838 let mapping = doc.as_mapping().expect("Should be a mapping");
4839
4840 let expected_numbers = "123\n45.67\n-89\n+100\n0xFF\n1e5\ntrue\nfalse\nnull\n";
4842 assert_eq!(
4843 mapping
4844 .get("numbers_as_text")
4845 .unwrap()
4846 .as_scalar()
4847 .unwrap()
4848 .as_string(),
4849 expected_numbers
4850 );
4851
4852 let expected_calculations =
4854 "The result is: 2 + 2 = 4 And 10 * 5 = 50 Also: 100% complete\n";
4855 assert_eq!(
4856 mapping
4857 .get("calculations")
4858 .unwrap()
4859 .as_scalar()
4860 .unwrap()
4861 .as_string(),
4862 expected_calculations
4863 );
4864
4865 let output = parsed.to_string();
4866 assert_eq!(output, yaml);
4867 }
4868
4869 #[test]
4870 fn test_block_scalar_exact_preservation() {
4871 let test_cases = [
4873 r#"simple: |
4875 Hello World
4876"#,
4877 r#"folded: >
4879 Hello World
4880"#,
4881 r#"strip: |-
4883 Content
4884
4885keep: |+
4886 Content
4887
4888"#,
4889 r#"explicit: |2
4891 Two space indent
4892"#,
4893 r#"config:
4895 script: |
4896 #!/bin/bash
4897 echo "Starting deployment"
4898
4899 for service in api web worker; do
4900 echo "Deploying $service"
4901 kubectl apply -f $service.yaml
4902 done
4903
4904 description: >
4905 This configuration defines a deployment
4906 script that will be executed during
4907 the CI/CD pipeline.
4908"#,
4909 ];
4910
4911 for (i, yaml) in test_cases.iter().enumerate() {
4912 let parsed = YamlFile::from_str(yaml);
4913 assert!(parsed.is_ok(), "Test case {} should parse successfully", i);
4914
4915 let output = parsed.unwrap().to_string();
4916 assert_eq!(
4917 output, *yaml,
4918 "Test case {} should preserve exact formatting",
4919 i
4920 );
4921 }
4922 }
4923
4924 #[test]
4925 fn test_block_scalar_chomping_exact() {
4926 let yaml_strip = r#"strip: |-
4927 Content
4928"#;
4929 let parsed_strip = YamlFile::from_str(yaml_strip).unwrap();
4930 assert_eq!(parsed_strip.to_string(), yaml_strip);
4931
4932 let yaml_keep = r#"keep: |+
4933 Content
4934
4935"#;
4936 let parsed_keep = YamlFile::from_str(yaml_keep).unwrap();
4937 assert_eq!(parsed_keep.to_string(), yaml_keep);
4938
4939 let yaml_folded_strip = r#"folded: >-
4940 Content
4941"#;
4942 let parsed_folded_strip = YamlFile::from_str(yaml_folded_strip).unwrap();
4943 assert_eq!(parsed_folded_strip.to_string(), yaml_folded_strip);
4944 }
4945
4946 #[test]
4947 fn test_block_scalar_indentation_exact() {
4948 let yaml1 = r#"indent1: |1
4949 Single space
4950"#;
4951 let parsed1 = YamlFile::from_str(yaml1).unwrap();
4952 assert_eq!(parsed1.to_string(), yaml1);
4953
4954 let yaml2 = r#"indent2: |2
4955 Two spaces
4956"#;
4957 let parsed2 = YamlFile::from_str(yaml2).unwrap();
4958 assert_eq!(parsed2.to_string(), yaml2);
4959
4960 let yaml3 = r#"combined: |3+
4961 Content with keep
4962
4963"#;
4964 let parsed3 = YamlFile::from_str(yaml3).unwrap();
4965 assert_eq!(parsed3.to_string(), yaml3);
4966 }
4967
4968 #[test]
4969 fn test_block_scalar_mapping_exact() {
4970 let yaml = r#"description: |
4971 Line 1
4972 Line 2
4973
4974summary: >
4975 Folded content
4976
4977version: "1.0"
4978"#;
4979 let parsed = YamlFile::from_str(yaml).unwrap();
4980 assert_eq!(parsed.to_string(), yaml);
4981 }
4982
4983 #[test]
4984 fn test_block_scalar_sequence_exact() {
4985 let yaml = r#"items:
4986 - |
4987 First item content
4988 with multiple lines
4989
4990 - >
4991 Second item folded
4992 content
4993
4994 - regular_item
4995"#;
4996 let parsed = YamlFile::from_str(yaml).unwrap();
4997 assert_eq!(parsed.to_string(), yaml);
4998 }
4999
5000 #[test]
5001 fn test_block_scalar_empty_exact() {
5002 let yaml1 = r#"empty: |
5003
5004"#;
5005 let parsed1 = YamlFile::from_str(yaml1).unwrap();
5006 assert_eq!(parsed1.to_string(), yaml1);
5007
5008 let yaml2 = r#"empty_folded: >
5009
5010"#;
5011 let parsed2 = YamlFile::from_str(yaml2).unwrap();
5012 assert_eq!(parsed2.to_string(), yaml2);
5013 }
5014
5015 #[test]
5016 fn test_empty_documents_in_stream() {
5017 let yaml = "---\n---\nkey: value\n---\n...\n";
5019 let parsed = YamlFile::from_str(yaml).unwrap();
5020 assert_eq!(parsed.documents().count(), 3);
5021 assert_eq!(parsed.to_string(), yaml);
5022 }
5023
5024 #[test]
5025 fn test_mixed_document_end_markers() {
5026 let yaml = "---\nfirst: doc\n...\n---\nsecond: doc\n---\nthird: doc\n...\n";
5028 let parsed = YamlFile::from_str(yaml).unwrap();
5029 assert_eq!(parsed.documents().count(), 3);
5030 assert_eq!(parsed.to_string(), yaml);
5031 }
5032
5033 #[test]
5034 fn test_complex_document_stream() {
5035 let yaml = r#"%YAML 1.2
5036%TAG ! tag:example.com,2000:app/
5037---
5038template: &anchor
5039 key: !custom value
5040instance:
5041 <<: *anchor
5042 extra: data
5043...
5044%YAML 1.2
5045---
5046- item1
5047- item2: nested
5048...
5049---
5050literal: |
5051 Block content
5052 Multiple lines
5053folded: >
5054 Folded content
5055 on multiple lines
5056...
5057"#;
5058 let parsed = YamlFile::from_str(yaml).unwrap();
5059 assert_eq!(parsed.documents().count(), 3);
5060 assert_eq!(parsed.to_string(), yaml);
5061 }
5062
5063 #[test]
5064 fn test_number_format_parsing() {
5065 let yaml = YamlFile::from_str("value: 0b1010").unwrap();
5067 assert_eq!(yaml.to_string().trim(), "value: 0b1010");
5068
5069 let yaml = YamlFile::from_str("value: 0B1111").unwrap();
5070 assert_eq!(yaml.to_string().trim(), "value: 0B1111");
5071
5072 let yaml = YamlFile::from_str("value: 0o755").unwrap();
5074 assert_eq!(yaml.to_string().trim(), "value: 0o755");
5075
5076 let yaml = YamlFile::from_str("value: 0O644").unwrap();
5077 assert_eq!(yaml.to_string().trim(), "value: 0O644");
5078
5079 let yaml = YamlFile::from_str("value: -0b1010").unwrap();
5081 assert_eq!(yaml.to_string().trim(), "value: -0b1010");
5082
5083 let yaml = YamlFile::from_str("value: +0o755").unwrap();
5084 assert_eq!(yaml.to_string().trim(), "value: +0o755");
5085
5086 let yaml = YamlFile::from_str("value: 0755").unwrap();
5088 assert_eq!(yaml.to_string().trim(), "value: 0755");
5089
5090 let yaml = YamlFile::from_str("value: 0xFF").unwrap();
5091 assert_eq!(yaml.to_string().trim(), "value: 0xFF");
5092 }
5093
5094 #[test]
5095 fn test_invalid_number_formats_as_strings() {
5096 let yaml = YamlFile::from_str("value: 0b2").unwrap();
5098 assert_eq!(yaml.to_string().trim(), "value: 0b2");
5099
5100 let yaml = YamlFile::from_str("value: 0o9").unwrap();
5101 assert_eq!(yaml.to_string().trim(), "value: 0o9");
5102
5103 let yaml = YamlFile::from_str("value: 0xGH").unwrap();
5104 assert_eq!(yaml.to_string().trim(), "value: 0xGH");
5105 }
5106
5107 #[test]
5108 fn test_number_formats_in_complex_structures() {
5109 let input = r#"
5110config:
5111 permissions: 0o755
5112 flags: 0b11010
5113 color: 0xFF00FF
5114 count: 42"#;
5115
5116 let yaml = YamlFile::from_str(input).unwrap();
5117
5118 let doc = yaml.document().expect("Should have document");
5119 let mapping = doc.as_mapping().expect("Should be a mapping");
5120 let config_value = mapping.get("config").unwrap();
5121 let config = config_value.as_mapping().unwrap();
5122
5123 assert_eq!(
5124 config
5125 .get("permissions")
5126 .unwrap()
5127 .as_scalar()
5128 .unwrap()
5129 .as_string(),
5130 "0o755"
5131 );
5132 assert_eq!(
5133 config
5134 .get("flags")
5135 .unwrap()
5136 .as_scalar()
5137 .unwrap()
5138 .as_string(),
5139 "0b11010"
5140 );
5141 assert_eq!(
5142 config
5143 .get("color")
5144 .unwrap()
5145 .as_scalar()
5146 .unwrap()
5147 .as_string(),
5148 "0xFF00FF"
5149 );
5150 assert_eq!(
5151 config
5152 .get("count")
5153 .unwrap()
5154 .as_scalar()
5155 .unwrap()
5156 .as_string(),
5157 "42"
5158 );
5159
5160 let output = yaml.to_string();
5161 assert_eq!(output, input);
5162 }
5163
5164 #[test]
5165 fn test_editing_operations() {
5166 let yaml = YamlFile::from_str("name: old-name\nversion: 1.0.0").unwrap();
5168 if let Some(doc) = yaml.document() {
5169 doc.set("name", "new-name");
5170 doc.set("version", "2.0.0");
5171
5172 assert_eq!(doc.get_string("name"), Some("new-name".to_string()));
5174 assert_eq!(doc.get_string("version"), Some("2.0.0".to_string()));
5175
5176 let output = doc.to_string();
5178 assert_eq!(output, "name: new-name\nversion: 2.0.0");
5179 }
5180 }
5181
5182 #[test]
5183 fn test_timestamp_parsing_and_validation() {
5184 use crate::scalar::{ScalarType, ScalarValue};
5185
5186 let test_cases = vec![
5188 ("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), ];
5198
5199 for (timestamp_str, should_be_valid) in test_cases {
5200 let scalar = ScalarValue::parse(timestamp_str);
5201
5202 if should_be_valid {
5203 assert_eq!(
5204 scalar.scalar_type(),
5205 ScalarType::Timestamp,
5206 "Failed to recognize '{}' as timestamp",
5207 timestamp_str
5208 );
5209 assert!(scalar.is_timestamp());
5210
5211 assert_eq!(scalar.value(), timestamp_str);
5213
5214 let yaml = format!("timestamp: {}", timestamp_str);
5216 let parsed = YamlFile::from_str(&yaml).unwrap();
5217
5218 let doc = parsed.document().expect("Should have document");
5219 let mapping = doc.as_mapping().expect("Should be a mapping");
5220 assert_eq!(
5221 mapping
5222 .get("timestamp")
5223 .unwrap()
5224 .as_scalar()
5225 .unwrap()
5226 .as_string(),
5227 timestamp_str,
5228 "Timestamp '{}' not preserved",
5229 timestamp_str
5230 );
5231
5232 let output = parsed.to_string();
5233 assert_eq!(output, yaml);
5234 } else {
5235 assert_ne!(
5236 scalar.scalar_type(),
5237 ScalarType::Timestamp,
5238 "'{}' should not be recognized as timestamp",
5239 timestamp_str
5240 );
5241 }
5242 }
5243
5244 let yaml_with_timestamps = r#"
5246created_at: 2001-12-14 21:59:43.10 -5
5247updated_at: 2001-12-15T02:59:43.1Z
5248date_only: 2002-12-14
5249timestamps_in_array:
5250 - 2001-12-14 21:59:43.10 -5
5251 - 2001-12-15T02:59:43.1Z
5252 - 2002-12-14"#;
5253
5254 let parsed = YamlFile::from_str(yaml_with_timestamps).unwrap();
5255
5256 let doc = parsed.document().expect("Should have document");
5257 let mapping = doc.as_mapping().expect("Should be a mapping");
5258 assert_eq!(
5259 mapping
5260 .get("created_at")
5261 .unwrap()
5262 .as_scalar()
5263 .unwrap()
5264 .as_string(),
5265 "2001-12-14 21:59:43.10 -5"
5266 );
5267 assert_eq!(
5268 mapping
5269 .get("updated_at")
5270 .unwrap()
5271 .as_scalar()
5272 .unwrap()
5273 .as_string(),
5274 "2001-12-15T02:59:43.1Z"
5275 );
5276 assert_eq!(
5277 mapping
5278 .get("date_only")
5279 .unwrap()
5280 .as_scalar()
5281 .unwrap()
5282 .as_string(),
5283 "2002-12-14"
5284 );
5285
5286 let array_value = mapping.get("timestamps_in_array").unwrap();
5287 let array = array_value.as_sequence().unwrap();
5288 assert_eq!(
5289 array.get(0).unwrap().as_scalar().unwrap().as_string(),
5290 "2001-12-14 21:59:43.10 -5"
5291 );
5292 assert_eq!(
5293 array.get(1).unwrap().as_scalar().unwrap().as_string(),
5294 "2001-12-15T02:59:43.1Z"
5295 );
5296 assert_eq!(
5297 array.get(2).unwrap().as_scalar().unwrap().as_string(),
5298 "2002-12-14"
5299 );
5300
5301 let output = parsed.to_string();
5302 assert_eq!(output, yaml_with_timestamps);
5303 }
5304
5305 #[test]
5306 fn test_regex_support_in_yaml() {
5307 use crate::scalar::{ScalarType, ScalarValue};
5308
5309 let yaml_with_regex = r#"
5311patterns:
5312 digits: !!regex '\d+'
5313 word: !!regex '\w+'
5314 simple: !!regex 'test'"#;
5315
5316 let parsed = YamlFile::from_str(yaml_with_regex).unwrap();
5317
5318 let output = parsed.to_string();
5320 assert_eq!(output, yaml_with_regex);
5321
5322 let regex_scalar = ScalarValue::regex(r"^\d{4}-\d{2}-\d{2}$");
5324 assert_eq!(regex_scalar.scalar_type(), ScalarType::Regex);
5325 assert!(regex_scalar.is_regex());
5326 assert_eq!(regex_scalar.value(), r"^\d{4}-\d{2}-\d{2}$");
5327 assert_eq!(
5328 regex_scalar.to_yaml_string(),
5329 r"!!regex ^\d{4}-\d{2}-\d{2}$"
5330 );
5331
5332 let yaml_simple = "pattern: !!regex '\\d+'";
5334 let parsed_simple = YamlFile::from_str(yaml_simple).unwrap();
5335
5336 let doc_simple = parsed_simple.document().expect("Should have document");
5337 let mapping_simple = doc_simple.as_mapping().expect("Should be a mapping");
5338 let pattern_value = mapping_simple
5339 .get("pattern")
5340 .expect("Should have pattern key");
5341
5342 assert!(pattern_value.is_tagged(), "pattern should be a tagged node");
5344 let tagged = pattern_value.as_tagged().expect("Should be tagged");
5345 assert_eq!(tagged.tag(), Some("!!regex".to_string()));
5346 assert_eq!(tagged.as_string(), Some("\\d+".to_string()));
5347
5348 let output_simple = parsed_simple.to_string();
5349 assert_eq!(output_simple, yaml_simple);
5350
5351 let complex_regex = r#"validation: !!regex '^https?://(?:[-\w.])+(?:\:[0-9]+)?'"#;
5353 let parsed_complex = YamlFile::from_str(complex_regex).unwrap();
5354
5355 let doc_complex = parsed_complex.document().expect("Should have document");
5356 let mapping_complex = doc_complex.as_mapping().expect("Should be a mapping");
5357 let validation_value = mapping_complex
5358 .get("validation")
5359 .expect("Should have validation key");
5360
5361 assert!(
5362 validation_value.is_tagged(),
5363 "validation should be a tagged node"
5364 );
5365 let tagged_complex = validation_value.as_tagged().expect("Should be tagged");
5366 assert_eq!(tagged_complex.tag(), Some("!!regex".to_string()));
5367 assert_eq!(
5368 tagged_complex.as_string(),
5369 Some("^https?://(?:[-\\w.])+(?:\\:[0-9]+)?".to_string())
5370 );
5371
5372 let output_complex = parsed_complex.to_string();
5373 assert_eq!(output_complex, complex_regex);
5374 }
5375
5376 #[test]
5377 fn test_regex_in_different_contexts() {
5378 let yaml_sequence = r#"
5380patterns:
5381 - !!regex '\d+'
5382 - !!regex '[a-z]+'
5383 - normal_string
5384 - !!regex '.*@.*\..*'
5385"#;
5386
5387 let parsed_seq = YamlFile::from_str(yaml_sequence).unwrap();
5388
5389 let doc_seq = parsed_seq.document().expect("Should have document");
5390 let mapping_seq = doc_seq.as_mapping().expect("Should be a mapping");
5391 let patterns_value = mapping_seq
5392 .get("patterns")
5393 .expect("Should have patterns key");
5394 let patterns = patterns_value.as_sequence().expect("Should be a sequence");
5395
5396 assert!(patterns.get(0).unwrap().is_tagged());
5398 assert_eq!(
5399 patterns.get(0).unwrap().as_tagged().unwrap().tag(),
5400 Some("!!regex".to_string())
5401 );
5402
5403 assert!(patterns.get(1).unwrap().is_tagged());
5405 assert_eq!(
5406 patterns.get(1).unwrap().as_tagged().unwrap().tag(),
5407 Some("!!regex".to_string())
5408 );
5409
5410 assert!(!patterns.get(2).unwrap().is_tagged());
5412 assert_eq!(
5413 patterns.get(2).unwrap().as_scalar().unwrap().as_string(),
5414 "normal_string"
5415 );
5416
5417 assert!(patterns.get(3).unwrap().is_tagged());
5419 assert_eq!(
5420 patterns.get(3).unwrap().as_tagged().unwrap().tag(),
5421 Some("!!regex".to_string())
5422 );
5423
5424 let output_seq = parsed_seq.to_string();
5425 assert_eq!(output_seq, yaml_sequence);
5426
5427 let yaml_nested = r#"
5429validation:
5430 email: !!regex '[^@]+@[^@]+\.[a-z]+'
5431 phone: !!regex '\d{3}-\d{3}-\d{4}'
5432 config:
5433 debug_pattern: !!regex 'DEBUG:.*'
5434 nested:
5435 deep_pattern: !!regex 'ERROR'
5436"#;
5437
5438 let parsed_nested = YamlFile::from_str(yaml_nested).unwrap();
5439 let output_nested = parsed_nested.to_string();
5441 assert_eq!(output_nested, yaml_nested);
5442
5443 let yaml_mixed = r#"
5445mixed_collection:
5446 - name: "test"
5447 patterns: [!!regex '\d+', !!regex '\w+']
5448 - patterns:
5449 simple: !!regex 'test'
5450 complex: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
5451"#;
5452
5453 let parsed_mixed = YamlFile::from_str(yaml_mixed).unwrap();
5454 let output_mixed = parsed_mixed.to_string();
5456 assert_eq!(output_mixed, yaml_mixed);
5457
5458 let yaml_flow =
5460 r#"inline_patterns: {email: !!regex '[^@]+@[^@]+', phone: !!regex '\d{3}-\d{4}'}"#;
5461
5462 let parsed_flow = YamlFile::from_str(yaml_flow).unwrap();
5463 let output_flow = parsed_flow.to_string();
5465 assert_eq!(output_flow, yaml_flow);
5466 }
5467
5468 #[test]
5469 fn test_regex_parsing_edge_cases() {
5470 let yaml_quotes = r#"
5473patterns:
5474 single_quoted: !!regex 'pattern with spaces'
5475 double_quoted: !!regex "pattern_without_escapes"
5476 unquoted: !!regex simple_pattern
5477"#;
5478
5479 let parsed_quotes = YamlFile::from_str(yaml_quotes).unwrap();
5480
5481 let output_quotes = parsed_quotes.to_string();
5482 assert_eq!(output_quotes, yaml_quotes);
5483
5484 let yaml_empty = r#"
5486empty: !!regex ''
5487whitespace: !!regex ' '
5488tabs: !!regex ' '
5489"#;
5490
5491 let parsed_empty =
5492 YamlFile::from_str(yaml_empty).expect("Should parse empty/whitespace regex patterns");
5493
5494 let output_empty = parsed_empty.to_string();
5495 assert_eq!(output_empty, yaml_empty);
5496
5497 let yaml_special = r#"special: !!regex 'pattern_with_underscores_and_123'"#;
5499
5500 let parsed_special = YamlFile::from_str(yaml_special)
5501 .expect("Should parse regex with safe special characters");
5502
5503 let output_special = parsed_special.to_string();
5504 assert_eq!(output_special, yaml_special);
5505
5506 let yaml_verify = r#"test_pattern: !!regex '\d{4}-\d{2}-\d{2}'"#;
5508 let parsed_verify = YamlFile::from_str(yaml_verify).unwrap();
5509
5510 let output_verify = parsed_verify.to_string();
5511 assert_eq!(output_verify, yaml_verify);
5512
5513 let yaml_multiple = r#"
5515patterns:
5516 email: !!regex '^[^\s@]+@[^\s@]+\.[^\s@]+$'
5517 phone: !!regex '^\+?[\d\s\-\(\)]{10,}$'
5518 url: !!regex '^https?://[^\s]+$'
5519 ipv4: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
5520 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}$'
5521"#;
5522
5523 let parsed_multiple =
5524 YamlFile::from_str(yaml_multiple).expect("Should parse multiple regex patterns");
5525
5526 let output_multiple = parsed_multiple.to_string();
5528 assert_eq!(output_multiple, yaml_multiple);
5529 }
5530
5531 #[test]
5532 fn test_enhanced_comment_support() {
5533 let yaml1 = r#"flow_seq: [
5538 item1, # comment after item1
5539 item2, # comment after item2
5540 item3 # comment after item3
5541]"#;
5542 let parsed1 = YamlFile::from_str(yaml1).unwrap();
5543 let output1 = parsed1.to_string();
5544
5545 assert_eq!(output1, yaml1);
5546
5547 let yaml2 = r#"flow_map: {
5549 key1: val1, # comment after first pair
5550 key2: val2, # comment after second pair
5551 key3: val3 # comment after third pair
5552}"#;
5553 let parsed2 = YamlFile::from_str(yaml2).unwrap();
5554 let output2 = parsed2.to_string();
5555
5556 assert_eq!(output2, yaml2);
5557
5558 let yaml3 = r#"config:
5560 servers: [
5561 {name: web1, port: 80}, # Web server 1
5562 {name: web2, port: 80}, # Web server 2
5563 {name: db1, port: 5432} # Database server
5564 ] # End servers array"#;
5565 let parsed3 = YamlFile::from_str(yaml3).unwrap();
5566 let output3 = parsed3.to_string();
5567
5568 assert_eq!(output3, yaml3);
5569
5570 let yaml4 = r#"items:
5572 - first # First item comment
5573 - second # Second item comment
5574 # Comment between items
5575 - third # Third item comment"#;
5576 let parsed4 = YamlFile::from_str(yaml4).unwrap();
5577 let output4 = parsed4.to_string();
5578
5579 assert_eq!(output4, yaml4);
5580
5581 for yaml in [yaml1, yaml2, yaml3, yaml4] {
5583 let parsed = YamlFile::from_str(yaml).unwrap();
5584 let output = parsed.to_string();
5585 let reparsed = YamlFile::from_str(&output);
5586 assert!(reparsed.is_ok(), "Round-trip parsing should succeed");
5587 }
5588 }
5589
5590 #[test]
5591 fn test_insert_after_with_sequence() {
5592 let yaml = "name: project\nversion: 1.0.0";
5593 let parsed = YamlFile::from_str(yaml).unwrap();
5594 let doc = parsed.document().expect("Should have a document");
5595
5596 let features = SequenceBuilder::new()
5598 .item("feature1")
5599 .item("feature2")
5600 .item("feature3")
5601 .build_document()
5602 .as_sequence()
5603 .unwrap();
5604 let success = doc.insert_after("name", "features", features);
5605 assert!(success, "insert_after should succeed");
5606
5607 let output = doc.to_string();
5608
5609 let expected = r#"name: project
5611features:
5612 - feature1
5613 - feature2
5614 - feature3
5615version: 1.0.0"#;
5616 assert_eq!(output.trim(), expected);
5617
5618 let reparsed = YamlFile::from_str(&output);
5619 assert!(reparsed.is_ok(), "Output should be valid YAML");
5620 }
5621
5622 #[test]
5623 fn test_insert_before_with_mapping() {
5624 let yaml = "name: project\nversion: 1.0.0";
5625 let parsed = YamlFile::from_str(yaml).unwrap();
5626 let doc = parsed.document().expect("Should have a document");
5627
5628 let database = MappingBuilder::new()
5630 .pair("host", "localhost")
5631 .pair("port", 5432)
5632 .pair("database", "mydb")
5633 .build_document()
5634 .as_mapping()
5635 .unwrap();
5636 let success = doc.insert_before("version", "database", database);
5637 assert!(success, "insert_before should succeed");
5638
5639 let output = doc.to_string();
5640
5641 let expected = r#"name: project
5643database:
5644 host: localhost
5645 port: 5432
5646 database: mydb
5647version: 1.0.0"#;
5648 assert_eq!(output.trim(), expected);
5649
5650 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5652 let reparsed_doc = reparsed.document().expect("Should have document");
5653 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5654
5655 let db_value = reparsed_mapping
5656 .get("database")
5657 .expect("Should have database key");
5658 let db_mapping = db_value.as_mapping().expect("database should be mapping");
5659 assert_eq!(
5660 db_mapping
5661 .get("host")
5662 .unwrap()
5663 .as_scalar()
5664 .unwrap()
5665 .as_string(),
5666 "localhost"
5667 );
5668 assert_eq!(
5669 db_mapping
5670 .get("port")
5671 .unwrap()
5672 .as_scalar()
5673 .unwrap()
5674 .as_string(),
5675 "5432"
5676 );
5677 }
5678
5679 #[test]
5680 fn test_insert_at_index_with_mixed_types() {
5681 let yaml = "name: project";
5682 let parsed = YamlFile::from_str(yaml).unwrap();
5683 let doc = parsed.document().expect("Should have a document");
5684
5685 doc.insert_at_index(1, "version", "1.0.0");
5687 doc.insert_at_index(2, "active", true);
5688 doc.insert_at_index(3, "count", 42);
5689
5690 let features = SequenceBuilder::new()
5691 .item("auth")
5692 .item("logging")
5693 .build_document()
5694 .as_sequence()
5695 .unwrap();
5696 doc.insert_at_index(4, "features", features);
5697
5698 let output = doc.to_string();
5699
5700 let expected = r#"name: project
5701version: 1.0.0
5702active: true
5703count: 42
5704features:
5705- auth
5706- logging"#;
5707 assert_eq!(output.trim(), expected);
5708
5709 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5710 let reparsed_doc = reparsed.document().expect("Should have document");
5711 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5712
5713 assert_eq!(
5714 reparsed_mapping
5715 .get("version")
5716 .unwrap()
5717 .as_scalar()
5718 .unwrap()
5719 .as_string(),
5720 "1.0.0"
5721 );
5722 assert_eq!(
5723 reparsed_mapping.get("active").unwrap().to_bool(),
5724 Some(true)
5725 );
5726 assert_eq!(reparsed_mapping.get("count").unwrap().to_i64(), Some(42));
5727
5728 let features_value = reparsed_mapping.get("features").unwrap();
5729 let features = features_value.as_sequence().unwrap();
5730 assert_eq!(
5731 features.get(0).unwrap().as_scalar().unwrap().as_string(),
5732 "auth"
5733 );
5734 assert_eq!(
5735 features.get(1).unwrap().as_scalar().unwrap().as_string(),
5736 "logging"
5737 );
5738 }
5739
5740 #[test]
5741 fn test_insert_with_null_and_special_scalars() {
5742 let yaml = "name: project";
5743 let parsed = YamlFile::from_str(yaml).unwrap();
5744 let doc = parsed.document().expect("Should have a document");
5745
5746 doc.insert_after("name", "null_value", ScalarValue::null());
5748 doc.insert_after("null_value", "empty_string", "");
5749 doc.insert_after("empty_string", "number", 1.234);
5750 doc.insert_after("number", "boolean", false);
5751
5752 let output = doc.to_string();
5753
5754 let expected = r#"name: project
5755null_value: null
5756empty_string: ''
5757number: 1.234
5758boolean: false"#;
5759 assert_eq!(output.trim(), expected);
5760
5761 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5762 let reparsed_doc = reparsed.document().expect("Should have document");
5763 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5764
5765 assert_eq!(
5766 reparsed_mapping
5767 .get("name")
5768 .unwrap()
5769 .as_scalar()
5770 .unwrap()
5771 .as_string(),
5772 "project"
5773 );
5774 assert!(reparsed_mapping
5775 .get("null_value")
5776 .unwrap()
5777 .as_scalar()
5778 .unwrap()
5779 .is_null());
5780 assert_eq!(
5781 reparsed_mapping
5782 .get("empty_string")
5783 .unwrap()
5784 .as_scalar()
5785 .unwrap()
5786 .as_string(),
5787 ""
5788 );
5789 assert_eq!(
5790 reparsed_mapping.get("number").unwrap().to_f64(),
5791 Some(1.234)
5792 );
5793 assert_eq!(
5794 reparsed_mapping.get("boolean").unwrap().to_bool(),
5795 Some(false)
5796 );
5797 }
5798
5799 #[test]
5800 fn test_insert_ordering_preservation() {
5801 let yaml = "first: 1\nthird: 3\nfifth: 5";
5802 let parsed = YamlFile::from_str(yaml).unwrap();
5803 let doc = parsed.document().expect("Should have a document");
5804
5805 doc.insert_after("first", "second", 2);
5807 doc.insert_before("fifth", "fourth", 4);
5808
5809 let output = doc.to_string();
5810
5811 let expected = r#"first: 1
5813second: 2
5814third: 3
5815fourth: 4
5816fifth: 5"#;
5817 assert_eq!(output.trim(), expected);
5818
5819 let reparsed = YamlFile::from_str(&output);
5820 assert!(reparsed.is_ok(), "Output should be valid YAML");
5821 }
5822
5823 #[test]
5824 fn test_insert_with_yamlvalue_positioning() {
5825 let yaml = "name: project\nversion: 1.0\nactive: true";
5826 let parsed = YamlFile::from_str(yaml).unwrap();
5827 let doc = parsed.document().expect("Should have a document");
5828
5829 let success = doc.insert_after("name", "description", "A sample project");
5833 assert!(success, "Should find string key");
5834
5835 let success = doc.insert_after(1.0, "build", "gradle");
5837 assert!(
5838 !success,
5839 "Should not find numeric key (1.0) when actual key is string 'version'"
5840 );
5841
5842 let success = doc.insert_after(true, "test", "enabled");
5844 assert!(
5845 !success,
5846 "Should not find boolean key (true) when actual key is string 'active'"
5847 );
5848
5849 let bool_string_key = "true";
5851 let success = doc.insert_after(bool_string_key, "test_mode", "development");
5852 assert!(!success, "Should not find 'true' key when value is true");
5853
5854 let output = doc.to_string();
5855
5856 let expected = "name: project\ndescription: A sample project\nversion: 1.0\nactive: true";
5858 assert_eq!(output, expected);
5859 }
5860
5861 #[test]
5862 fn test_insert_complex_nested_structure() {
5863 let yaml = "name: project";
5864 let parsed = YamlFile::from_str(yaml).unwrap();
5865 let doc = parsed.document().expect("Should have a document");
5866
5867 let config = MappingBuilder::new()
5869 .mapping("server", |m| m.pair("host", "0.0.0.0").pair("port", 8080))
5870 .pair("debug", true)
5871 .sequence("features", |s| s.item("api").item("web").item("cli"))
5872 .build_document()
5873 .as_mapping()
5874 .unwrap();
5875
5876 doc.insert_after("name", "config", config);
5877
5878 let output = doc.to_string();
5879
5880 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";
5882 assert_eq!(output, expected);
5883
5884 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5885 let reparsed_doc = reparsed.document().expect("Should have document");
5886 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5887
5888 let config_value = reparsed_mapping
5889 .get("config")
5890 .expect("Should have config key");
5891 let config_mapping = config_value.as_mapping().expect("config should be mapping");
5892
5893 let server_value = config_mapping
5894 .get("server")
5895 .expect("Should have server key");
5896 let server = server_value.as_mapping().expect("server should be mapping");
5897 assert_eq!(
5898 server.get("host").unwrap().as_scalar().unwrap().as_string(),
5899 "0.0.0.0"
5900 );
5901 assert_eq!(server.get("port").unwrap().to_i64(), Some(8080));
5902
5903 assert_eq!(config_mapping.get("debug").unwrap().to_bool(), Some(true));
5904
5905 let features_value = config_mapping
5906 .get("features")
5907 .expect("Should have features key");
5908 let features = features_value
5909 .as_sequence()
5910 .expect("features should be sequence");
5911 assert_eq!(features.len(), 3);
5912 assert_eq!(
5913 features.get(0).unwrap().as_scalar().unwrap().as_string(),
5914 "api"
5915 );
5916 assert_eq!(
5917 features.get(1).unwrap().as_scalar().unwrap().as_string(),
5918 "web"
5919 );
5920 assert_eq!(
5921 features.get(2).unwrap().as_scalar().unwrap().as_string(),
5922 "cli"
5923 );
5924 }
5925
5926 #[test]
5927 fn test_insert_with_yaml_sets() {
5928 let yaml = "name: project";
5929 let parsed = YamlFile::from_str(yaml).unwrap();
5930 let doc = parsed.document().expect("Should have a document");
5931
5932 let mut tags = std::collections::BTreeSet::new();
5934 tags.insert("production".to_string());
5935 tags.insert("database".to_string());
5936 tags.insert("web".to_string());
5937
5938 let yaml_set = YamlValue::from_set(tags);
5939 doc.insert_after("name", "tags", yaml_set);
5940
5941 let output = doc.to_string();
5942
5943 let expected =
5945 "name: project\ntags: !!set\n database: null\n production: null\n web: null\n";
5946 assert_eq!(output, expected);
5947
5948 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5949 let reparsed_doc = reparsed.document().expect("Should have document");
5950 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5951
5952 let tags_value = reparsed_mapping.get("tags").expect("Should have tags key");
5953 assert!(tags_value.is_tagged(), "tags should be tagged");
5954 let tagged = tags_value.as_tagged().expect("Should be tagged node");
5955 assert_eq!(tagged.tag(), Some("!!set".to_string()));
5956
5957 let tags_syntax = tagged.syntax();
5960 let tags_mapping = tags_syntax
5961 .children()
5962 .find_map(Mapping::cast)
5963 .expect("Set should have mapping child");
5964
5965 assert!(tags_mapping
5966 .get("database")
5967 .unwrap()
5968 .as_scalar()
5969 .unwrap()
5970 .is_null());
5971 assert!(tags_mapping
5972 .get("production")
5973 .unwrap()
5974 .as_scalar()
5975 .unwrap()
5976 .is_null());
5977 assert!(tags_mapping
5978 .get("web")
5979 .unwrap()
5980 .as_scalar()
5981 .unwrap()
5982 .is_null());
5983 }
5984
5985 #[test]
5986 fn test_insert_with_ordered_mappings() {
5987 let yaml = "name: project";
5988 let parsed = YamlFile::from_str(yaml).unwrap();
5989 let doc = parsed.document().expect("Should have a document");
5990
5991 let ordered_steps = vec![
5993 ("compile".to_string(), YamlValue::from("gcc main.c")),
5994 ("test".to_string(), YamlValue::from("./a.out test")),
5995 (
5996 "package".to_string(),
5997 YamlValue::from("tar -czf app.tar.gz ."),
5998 ),
5999 ];
6000
6001 let yaml_omap = YamlValue::from_ordered_mapping(ordered_steps);
6002 doc.insert_after("name", "build_steps", yaml_omap);
6003
6004 let output = doc.to_string();
6005
6006 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";
6008 assert_eq!(output, expected);
6009
6010 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6011 let reparsed_doc = reparsed.document().expect("Should have document");
6012 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6013
6014 let steps_value = reparsed_mapping
6015 .get("build_steps")
6016 .expect("Should have build_steps key");
6017 assert!(steps_value.is_tagged(), "build_steps should be tagged");
6018 let tagged = steps_value.as_tagged().expect("Should be tagged node");
6019 assert_eq!(tagged.tag(), Some("!!omap".to_string()));
6020
6021 let steps_syntax = tagged.syntax();
6023 let steps_seq = steps_syntax
6024 .children()
6025 .find_map(Sequence::cast)
6026 .expect("Omap should have sequence child");
6027
6028 assert_eq!(steps_seq.len(), 3);
6029 let compile_value = steps_seq.get(0).unwrap();
6031 let compile_item = compile_value.as_mapping().expect("Should be mapping");
6032 assert_eq!(
6033 compile_item
6034 .get("compile")
6035 .unwrap()
6036 .as_scalar()
6037 .unwrap()
6038 .as_string(),
6039 "gcc main.c"
6040 );
6041
6042 let test_value = steps_seq.get(1).unwrap();
6043 let test_item = test_value.as_mapping().expect("Should be mapping");
6044 assert_eq!(
6045 test_item
6046 .get("test")
6047 .unwrap()
6048 .as_scalar()
6049 .unwrap()
6050 .as_string(),
6051 "./a.out test"
6052 );
6053
6054 let package_value = steps_seq.get(2).unwrap();
6055 let package_item = package_value.as_mapping().expect("Should be mapping");
6056 assert_eq!(
6057 package_item
6058 .get("package")
6059 .unwrap()
6060 .as_scalar()
6061 .unwrap()
6062 .as_string(),
6063 "tar -czf app.tar.gz ."
6064 );
6065 }
6066
6067 #[test]
6068 fn test_insert_with_pairs() {
6069 let yaml = "name: project";
6070 let parsed = YamlFile::from_str(yaml).unwrap();
6071 let doc = parsed.document().expect("Should have a document");
6072
6073 let connection_attempts = vec![
6075 ("server".to_string(), YamlValue::from("primary.db")),
6076 ("server".to_string(), YamlValue::from("secondary.db")), ("server".to_string(), YamlValue::from("tertiary.db")), ("timeout".to_string(), YamlValue::from(30)),
6079 ];
6080
6081 let yaml_pairs = YamlValue::from_pairs(connection_attempts);
6082 doc.insert_after("name", "connections", yaml_pairs);
6083
6084 let output = doc.to_string();
6085
6086 let expected = "name: project\nconnections: !!pairs\n - server: primary.db\n - server: secondary.db\n - server: tertiary.db\n - timeout: 30\n";
6088 assert_eq!(output, expected);
6089
6090 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6091 let reparsed_doc = reparsed.document().expect("Should have document");
6092 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6093
6094 let connections_value = reparsed_mapping
6095 .get("connections")
6096 .expect("Should have connections key");
6097 assert!(
6098 connections_value.is_tagged(),
6099 "connections should be tagged"
6100 );
6101 let tagged = connections_value
6102 .as_tagged()
6103 .expect("Should be tagged node");
6104 assert_eq!(tagged.tag(), Some("!!pairs".to_string()));
6105
6106 let connections_syntax = tagged.syntax();
6108 let connections_seq = connections_syntax
6109 .children()
6110 .find_map(Sequence::cast)
6111 .expect("Pairs should have sequence child");
6112
6113 assert_eq!(connections_seq.len(), 4);
6114
6115 let server1_val = connections_seq.get(0).unwrap();
6116 let server1 = server1_val.as_mapping().expect("Should be mapping");
6117 assert_eq!(
6118 server1
6119 .get("server")
6120 .unwrap()
6121 .as_scalar()
6122 .unwrap()
6123 .as_string(),
6124 "primary.db"
6125 );
6126
6127 let server2_val = connections_seq.get(1).unwrap();
6128 let server2 = server2_val.as_mapping().expect("Should be mapping");
6129 assert_eq!(
6130 server2
6131 .get("server")
6132 .unwrap()
6133 .as_scalar()
6134 .unwrap()
6135 .as_string(),
6136 "secondary.db"
6137 );
6138
6139 let server3_val = connections_seq.get(2).unwrap();
6140 let server3 = server3_val.as_mapping().expect("Should be mapping");
6141 assert_eq!(
6142 server3
6143 .get("server")
6144 .unwrap()
6145 .as_scalar()
6146 .unwrap()
6147 .as_string(),
6148 "tertiary.db"
6149 );
6150
6151 let timeout_val = connections_seq.get(3).unwrap();
6152 let timeout = timeout_val.as_mapping().expect("Should be mapping");
6153 assert_eq!(timeout.get("timeout").unwrap().to_i64(), Some(30));
6154 }
6155
6156 #[test]
6157 fn test_insert_with_empty_collections() {
6158 let yaml1 = "name: project";
6162 let parsed1 = YamlFile::from_str(yaml1).unwrap();
6163 let doc1 = parsed1.document().expect("Should have a document");
6164
6165 let empty_seq_yaml = YamlFile::from_str("[]").unwrap();
6167 let empty_list = empty_seq_yaml.document().unwrap().as_sequence().unwrap();
6168
6169 doc1.insert_after("name", "empty_list", empty_list);
6170 let output1 = doc1.to_string();
6171 assert_eq!(output1, "name: project\nempty_list: []\n");
6172
6173 let yaml2 = "name: project";
6175 let parsed2 = YamlFile::from_str(yaml2).unwrap();
6176 let doc2 = parsed2.document().expect("Should have a document");
6177
6178 let empty_map_yaml = YamlFile::from_str("{}").unwrap();
6180 let empty_map = empty_map_yaml.document().unwrap().as_mapping().unwrap();
6181
6182 doc2.insert_after("name", "empty_map", empty_map);
6183 let output2 = doc2.to_string();
6184 assert_eq!(output2, "name: project\nempty_map: {}\n");
6185
6186 let yaml3 = "name: project";
6188 let parsed3 = YamlFile::from_str(yaml3).unwrap();
6189 let doc3 = parsed3.document().expect("Should have a document");
6190 doc3.insert_after("name", "empty_set", YamlValue::set());
6191 let output3 = doc3.to_string();
6192 assert_eq!(output3, "name: project\nempty_set: !!set {}\n");
6193
6194 assert!(
6196 YamlFile::from_str(&output1).is_ok(),
6197 "Empty sequence output should be valid YAML"
6198 );
6199 assert!(
6200 YamlFile::from_str(&output2).is_ok(),
6201 "Empty mapping output should be valid YAML"
6202 );
6203 assert!(
6204 YamlFile::from_str(&output3).is_ok(),
6205 "Empty set output should be valid YAML"
6206 );
6207 }
6208
6209 #[test]
6210 fn test_insert_with_deeply_nested_sequences() {
6211 let yaml = "name: project";
6212 let parsed = YamlFile::from_str(yaml).unwrap();
6213 let doc = parsed.document().expect("Should have a document");
6214
6215 let nested_data = SequenceBuilder::new()
6217 .item("item1")
6218 .mapping(|m| {
6219 m.sequence("config", |s| s.item("debug").item(true).item(8080))
6220 .pair("name", "service")
6221 })
6222 .item(42)
6223 .build_document()
6224 .as_sequence()
6225 .unwrap();
6226
6227 doc.insert_after("name", "nested_data", nested_data);
6228
6229 let output = doc.to_string();
6230
6231 let expected = "name: project\nnested_data:\n - item1\n - \n config: \n - debug\n - true\n - 8080\n name: service- 42\n";
6232 assert_eq!(output, expected);
6233
6234 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6235 let reparsed_doc = reparsed.document().expect("Should have document");
6236 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6237
6238 let nested_value = reparsed_mapping
6239 .get("nested_data")
6240 .expect("Should have nested_data key");
6241 let nested_seq = nested_value.as_sequence().expect("Should be sequence");
6242 assert_eq!(nested_seq.len(), 2);
6245
6246 assert_eq!(
6248 nested_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
6249 "item1"
6250 );
6251
6252 let second_item = nested_seq.get(1).unwrap();
6254 let second_mapping = second_item.as_mapping().expect("Should be mapping");
6255
6256 let config_value = second_mapping.get("config").expect("Should have config");
6257 let config_seq = config_value
6258 .as_sequence()
6259 .expect("config should be sequence");
6260 assert_eq!(config_seq.len(), 3);
6261 assert_eq!(
6262 config_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
6263 "debug"
6264 );
6265 assert_eq!(config_seq.get(1).unwrap().to_bool(), Some(true));
6266 assert_eq!(config_seq.get(2).unwrap().to_i64(), Some(8080));
6267
6268 assert_eq!(
6270 second_mapping
6271 .get("name")
6272 .unwrap()
6273 .as_scalar()
6274 .unwrap()
6275 .as_string(),
6276 "service- 42"
6277 );
6278 }
6279
6280 #[test]
6281 fn test_ast_preservation_comments_in_mapping() {
6282 let yaml = r#"---
6284# Header comment
6285key1: value1 # Inline comment 1
6286# Middle comment
6287key2: value2 # Inline comment 2
6288# Footer comment
6289"#;
6290 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6291
6292 if let Some(mapping) = doc.as_mapping() {
6294 mapping.move_after("key1", "new_key", "new_value");
6295 }
6296
6297 let result = doc.to_string();
6298
6299 let expected = r#"---
6300# Header comment
6301key1: value1 # Inline comment 1
6302new_key: new_value
6303# Middle comment
6304key2: value2 # Inline comment 2
6305# Footer comment
6306"#;
6307 assert_eq!(result, expected);
6308 }
6309
6310 #[test]
6311 fn test_ast_preservation_whitespace_in_mapping() {
6312 let yaml = r#"---
6314key1: value1
6315
6316
6317key2: value2
6318"#;
6319 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6320
6321 if let Some(mapping) = doc.as_mapping() {
6323 mapping.move_after("key1", "new_key", "new_value");
6324 }
6325
6326 let result = doc.to_string();
6327
6328 let expected = r#"---
6329key1: value1
6330new_key: new_value
6331
6332
6333key2: value2
6334"#;
6335 assert_eq!(result, expected);
6336 }
6337
6338 #[test]
6339 fn test_ast_preservation_complex_structure() {
6340 let yaml = r#"---
6342# Configuration file
6343database: # Database settings
6344 host: localhost # Default host
6345 port: 5432 # Default port
6346 # Connection pool settings
6347 pool:
6348 min: 1 # Minimum connections
6349 max: 10 # Maximum connections
6350
6351# Server configuration
6352server:
6353 port: 8080 # HTTP port
6354"#;
6355 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6356
6357 eprintln!("===========\n");
6358
6359 if let Some(mapping) = doc.as_mapping() {
6361 mapping.move_after("database", "logging", "debug");
6362 }
6363
6364 let result = doc.to_string();
6365
6366 let expected = r#"---
6368# Configuration file
6369database: # Database settings
6370 host: localhost # Default host
6371 port: 5432 # Default port
6372 # Connection pool settings
6373 pool:
6374 min: 1 # Minimum connections
6375 max: 10 # Maximum connections
6376
6377logging: debug
6378# Server configuration
6379server:
6380 port: 8080 # HTTP port
6381"#;
6382 assert_eq!(result, expected);
6383 }
6384
6385 mod lookahead_tests {
6387 use super::*;
6388 use crate::lex::lex;
6389
6390 fn check_implicit_mapping(yaml: &str) -> bool {
6392 let tokens: Vec<(SyntaxKind, &str)> = lex(yaml);
6393 let kinds: Vec<SyntaxKind> = tokens.iter().rev().map(|(kind, _)| *kind).collect();
6395 has_implicit_mapping_pattern(kinds.into_iter())
6396 }
6397
6398 #[test]
6399 fn test_simple_implicit_mapping() {
6400 assert!(check_implicit_mapping("'key' : value"));
6403 }
6404
6405 #[test]
6406 fn test_simple_value_no_mapping() {
6407 assert!(!check_implicit_mapping("value ,"));
6409 }
6410
6411 #[test]
6412 fn test_value_with_comma() {
6413 assert!(!check_implicit_mapping("value , more"));
6415 }
6416
6417 #[test]
6418 fn test_nested_sequence_as_key() {
6419 assert!(check_implicit_mapping("[a, b]: value"));
6421 }
6422
6423 #[test]
6424 fn test_nested_mapping_not_implicit() {
6425 assert!(!check_implicit_mapping("{a: 1}, b"));
6427 }
6428
6429 #[test]
6430 fn test_deeply_nested_key() {
6431 assert!(check_implicit_mapping("[[a]]: value"));
6433 }
6434
6435 #[test]
6436 fn test_with_whitespace() {
6437 assert!(check_implicit_mapping("'key' : value"));
6439 }
6440
6441 #[test]
6442 fn test_mapping_value_in_sequence() {
6443 assert!(!check_implicit_mapping("a, {key: value}"));
6446 }
6447
6448 #[test]
6449 fn test_multiple_colons() {
6450 assert!(check_implicit_mapping("key1: value1, key2: value2"));
6452 }
6453
6454 #[test]
6455 fn test_url_not_mapping() {
6456 assert!(!check_implicit_mapping("http://example.com"));
6459 }
6460 }
6461
6462 #[test]
6463 fn test_from_str_relaxed_valid() {
6464 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: value\n");
6465 assert!(errors.is_empty());
6466 let doc = yaml_file.document().unwrap();
6467 let mapping = doc.as_mapping().unwrap();
6468 assert_eq!(
6469 mapping.get("key").unwrap().as_scalar().unwrap().to_string(),
6470 "value"
6471 );
6472 }
6473
6474 #[test]
6475 fn test_from_str_relaxed_unclosed_flow_sequence() {
6476 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: [a, b");
6477 assert!(!errors.is_empty());
6478 let doc = yaml_file.document().unwrap();
6480 assert!(doc.as_mapping().is_some());
6481 }
6482
6483 #[test]
6484 fn test_from_str_relaxed_unclosed_flow_mapping() {
6485 let (yaml_file, errors) = YamlFile::from_str_relaxed("key: {a: 1");
6486 assert!(!errors.is_empty());
6487 let doc = yaml_file.document().unwrap();
6488 assert!(doc.as_mapping().is_some());
6489 }
6490
6491 #[test]
6492 fn test_from_str_relaxed_preserves_valid_content() {
6493 let input = "good: value\nbad: [unclosed\nalso_good: 42\n";
6494 let (yaml_file, errors) = YamlFile::from_str_relaxed(input);
6495 assert!(!errors.is_empty());
6496 let doc = yaml_file.document().unwrap();
6497 let mapping = doc.as_mapping().unwrap();
6498 assert_eq!(
6499 mapping
6500 .get("good")
6501 .unwrap()
6502 .as_scalar()
6503 .unwrap()
6504 .to_string(),
6505 "value"
6506 );
6507 }
6508
6509 #[test]
6510 fn test_from_str_relaxed_empty_input() {
6511 let (yaml_file, errors) = YamlFile::from_str_relaxed("");
6512 assert!(errors.is_empty());
6513 assert!(yaml_file.document().is_none());
6514 }
6515
6516 #[test]
6517 fn test_parse_flow_sequence_with_stray_close_brace_does_not_hang() {
6518 let parse = crate::Parse::parse_yaml("[}9[Z");
6521 let _ = parse.tree();
6522 assert!(parse.has_errors());
6523 }
6524
6525 #[test]
6526 fn test_parse_flow_mapping_with_stray_close_bracket_does_not_hang() {
6527 let parse = crate::Parse::parse_yaml("{]");
6529 let _ = parse.tree();
6530 assert!(parse.has_errors());
6531 }
6532
6533 #[test]
6534 fn test_parse_explicit_key_multiline_with_comment_does_not_hang() {
6535 let input = "?\u{18}\0\0\0\0\0\0\n\t#\t\t?\t";
6539 let parse = crate::Parse::parse_yaml(input);
6540 let _ = parse.tree();
6541 }
6542
6543 #[test]
6544 fn test_parse_deeply_nested_flow_mapping_does_not_blow_up() {
6545 let input = "{".repeat(2000);
6548 let parse = crate::Parse::parse_yaml(&input);
6549 let _ = parse.tree();
6550 assert!(parse.has_errors());
6551 }
6552
6553 #[test]
6554 fn test_parse_deeply_nested_flow_sequence_does_not_blow_up() {
6555 let input = "[".repeat(2000);
6556 let parse = crate::Parse::parse_yaml(&input);
6557 let _ = parse.tree();
6558 assert!(parse.has_errors());
6559 }
6560
6561 #[test]
6562 fn test_parse_explicit_key_followed_by_question_in_value_does_not_hang() {
6563 let input = "<<?: ?: \n<<\n ?: : 0\n:@";
6567 let parse = crate::Parse::parse_yaml(input);
6568 let _ = parse.tree();
6569 }
6570
6571 #[test]
6572 fn test_parse_complex_key_mapping_with_stray_close_bracket_does_not_hang() {
6573 let input = "[%:%:]: \n]: \n\n";
6576 let parse = crate::Parse::parse_yaml(input);
6577 let _ = parse.tree();
6578 }
6579
6580 #[test]
6581 fn test_parse_mapping_with_stray_close_brace_does_not_hang() {
6582 let input = "\0:\n\n}:\n\n{";
6587 let parse = crate::Parse::parse_yaml(input);
6588 let _ = parse.tree();
6589 assert!(parse.has_errors());
6590 }
6591}