1use crate::yaml::{Alias, Mapping, Scalar, Sequence, SyntaxNode, TaggedNode};
23use rowan::ast::AstNode;
24use std::borrow::Cow;
25use std::fmt;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum YamlKind {
30 Mapping,
32 Sequence,
34 Scalar,
36 Alias,
38 Document,
40 Tagged(Cow<'static, str>),
44}
45
46pub trait AsYaml {
62 fn as_node(&self) -> Option<&SyntaxNode>;
67
68 fn kind(&self) -> YamlKind;
70
71 fn build_content(
79 &self,
80 builder: &mut rowan::GreenNodeBuilder,
81 indent: usize,
82 flow_context: bool,
83 ) -> bool;
84
85 fn is_inline(&self) -> bool;
90}
91
92pub fn yaml_eq<A, B>(a: &A, b: &B) -> bool
98where
99 A: AsYaml + ?Sized,
100 B: AsYaml + ?Sized,
101{
102 if let Some(node) = a.as_node() {
104 use crate::lex::SyntaxKind;
105 return match node.kind() {
106 SyntaxKind::SCALAR => Scalar::cast(node.clone()).is_some_and(|s| scalar_eq_rhs(&s, b)),
107 SyntaxKind::MAPPING => {
108 Mapping::cast(node.clone()).is_some_and(|m| mapping_eq_rhs(&m, b))
109 }
110 SyntaxKind::SEQUENCE => {
111 Sequence::cast(node.clone()).is_some_and(|s| sequence_eq_rhs(&s, b))
112 }
113 SyntaxKind::TAGGED_NODE => {
114 TaggedNode::cast(node.clone()).is_some_and(|t| tagged_eq_rhs(&t, b))
115 }
116 _ => false,
117 };
118 }
119
120 if let Some(node) = b.as_node() {
122 use crate::lex::SyntaxKind;
123 return match node.kind() {
124 SyntaxKind::SCALAR => Scalar::cast(node.clone()).is_some_and(|s| scalar_eq_rhs(&s, a)),
125 SyntaxKind::MAPPING => {
126 Mapping::cast(node.clone()).is_some_and(|m| mapping_eq_rhs(&m, a))
127 }
128 SyntaxKind::SEQUENCE => {
129 Sequence::cast(node.clone()).is_some_and(|s| sequence_eq_rhs(&s, a))
130 }
131 SyntaxKind::TAGGED_NODE => {
132 TaggedNode::cast(node.clone()).is_some_and(|t| tagged_eq_rhs(&t, a))
133 }
134 _ => false,
135 };
136 }
137
138 if a.kind() != b.kind() {
140 return false;
141 }
142 match (raw_scalar_str(a), raw_scalar_str(b)) {
143 (Some(sa), Some(sb)) => sa == sb,
144 _ => false,
145 }
146}
147
148fn raw_scalar_str<T: AsYaml + ?Sized>(v: &T) -> Option<String> {
150 if v.as_node().is_some() || v.kind() != YamlKind::Scalar {
151 return None;
152 }
153 let mut builder = rowan::GreenNodeBuilder::new();
154 v.build_content(&mut builder, 0, false);
155 let green = builder.finish();
156 let node = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
157 Scalar::cast(node.clone())
158 .map(|s| s.as_string())
159 .or_else(|| Some(node.text().to_string()))
160}
161
162fn scalar_semantic_value(scalar: &Scalar) -> Option<(crate::lex::SyntaxKind, String)> {
169 use crate::lex::SyntaxKind;
170 use crate::scalar::ScalarValue;
171
172 let token = scalar.0.first_token()?;
174 let kind = token.kind();
175 let text = token.text();
176
177 let normalized = match kind {
178 SyntaxKind::INT => {
179 ScalarValue::parse_integer(text)
181 .map(|v| v.to_string())
182 .unwrap_or_else(|| text.to_string())
183 }
184 SyntaxKind::FLOAT => {
185 text.parse::<f64>()
187 .map(|v| v.to_string())
188 .unwrap_or_else(|_| text.to_string())
189 }
190 SyntaxKind::BOOL => {
191 text.to_lowercase()
193 }
194 SyntaxKind::NULL => {
195 "null".to_string()
197 }
198 SyntaxKind::STRING => {
199 scalar.as_string()
201 }
202 _ => {
203 scalar.as_string()
205 }
206 };
207
208 Some((kind, normalized))
209}
210
211fn scalar_eq_rhs<B: AsYaml + ?Sized>(lhs: &Scalar, rhs: &B) -> bool {
212 let rhs_scalar = if let Some(node) = rhs.as_node() {
214 let Some(scalar) = Scalar::cast(node.clone()) else {
215 return false;
216 };
217 scalar
218 } else {
219 if rhs.kind() != YamlKind::Scalar {
221 return false;
222 }
223 let mut builder = rowan::GreenNodeBuilder::new();
224 rhs.build_content(&mut builder, 0, false);
225 let green = builder.finish();
226 let node = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
227 let Some(scalar) = Scalar::cast(node) else {
228 return false;
229 };
230 scalar
231 };
232
233 let Some((lhs_kind, lhs_value)) = scalar_semantic_value(lhs) else {
235 return false;
236 };
237 let Some((rhs_kind, rhs_value)) = scalar_semantic_value(&rhs_scalar) else {
238 return false;
239 };
240
241 lhs_kind == rhs_kind && lhs_value == rhs_value
245}
246
247fn mapping_eq_rhs<B: AsYaml + ?Sized>(lhs: &Mapping, rhs: &B) -> bool {
248 let Some(node) = rhs.as_node() else {
249 return false;
250 };
251 let Some(r) = Mapping::cast(node.clone()) else {
252 return false;
253 };
254 let lhs_pairs: Vec<_> = lhs.pairs().collect();
255 let rhs_pairs: Vec<_> = r.pairs().collect();
256 if lhs_pairs.len() != rhs_pairs.len() {
257 return false;
258 }
259 lhs_pairs
261 .iter()
262 .zip(rhs_pairs.iter())
263 .all(|((lk, lv), (rk, rv))| {
264 let Some(lk) = YamlNode::from_syntax_peeled(lk.clone()) else {
265 return false;
266 };
267 let Some(rk) = YamlNode::from_syntax_peeled(rk.clone()) else {
268 return false;
269 };
270 let Some(lv) = YamlNode::from_syntax_peeled(lv.clone()) else {
271 return false;
272 };
273 let Some(rv) = YamlNode::from_syntax_peeled(rv.clone()) else {
274 return false;
275 };
276 yaml_eq(&lk, &rk) && yaml_eq(&lv, &rv)
277 })
278}
279
280fn sequence_eq_rhs<B: AsYaml + ?Sized>(lhs: &Sequence, rhs: &B) -> bool {
281 let Some(node) = rhs.as_node() else {
282 return false;
283 };
284 let Some(r) = Sequence::cast(node.clone()) else {
285 return false;
286 };
287 let lhs_items: Vec<_> = lhs.items().collect();
288 let rhs_items: Vec<_> = r.items().collect();
289 if lhs_items.len() != rhs_items.len() {
290 return false;
291 }
292 lhs_items.iter().zip(rhs_items.iter()).all(|(l, r)| {
294 match (
295 YamlNode::from_syntax(l.clone()),
296 YamlNode::from_syntax(r.clone()),
297 ) {
298 (Some(l), Some(r)) => yaml_eq(&l, &r),
299 _ => false,
300 }
301 })
302}
303
304fn tagged_eq_rhs<B: AsYaml + ?Sized>(lhs: &TaggedNode, rhs: &B) -> bool {
305 let Some(node) = rhs.as_node() else {
306 return false;
307 };
308 TaggedNode::cast(node.clone())
309 .is_some_and(|r| lhs.tag() == r.tag() && lhs.as_string() == r.as_string())
310}
311
312#[derive(Debug, Clone, PartialEq)]
339pub enum YamlNode {
340 Scalar(Scalar),
342 Mapping(Mapping),
344 Sequence(Sequence),
346 Alias(Alias),
348 TaggedNode(TaggedNode),
350}
351
352impl YamlNode {
353 pub fn from_syntax(node: SyntaxNode) -> Option<Self> {
358 use crate::lex::SyntaxKind;
359 match node.kind() {
360 SyntaxKind::SCALAR => Scalar::cast(node).map(YamlNode::Scalar),
361 SyntaxKind::MAPPING => Mapping::cast(node).map(YamlNode::Mapping),
362 SyntaxKind::SEQUENCE => Sequence::cast(node).map(YamlNode::Sequence),
363 SyntaxKind::ALIAS => Alias::cast(node).map(YamlNode::Alias),
364 SyntaxKind::TAGGED_NODE => TaggedNode::cast(node).map(YamlNode::TaggedNode),
365 _ => None,
366 }
367 }
368
369 pub(crate) fn from_syntax_peeled(node: SyntaxNode) -> Option<Self> {
375 use crate::lex::SyntaxKind;
376 let inner = if matches!(node.kind(), SyntaxKind::KEY | SyntaxKind::VALUE) {
377 node.children().next()?
378 } else {
379 node
380 };
381 Self::from_syntax(inner)
382 }
383
384 pub fn kind(&self) -> YamlKind {
386 match self {
387 YamlNode::Scalar(_) => YamlKind::Scalar,
388 YamlNode::Mapping(_) => YamlKind::Mapping,
389 YamlNode::Sequence(_) => YamlKind::Sequence,
390 YamlNode::Alias(_) => YamlKind::Alias,
391 YamlNode::TaggedNode(t) => t
392 .tag()
393 .map(|tag| YamlKind::Tagged(Cow::Owned(tag)))
394 .unwrap_or(YamlKind::Scalar),
395 }
396 }
397
398 pub(crate) fn syntax(&self) -> &SyntaxNode {
400 match self {
401 YamlNode::Scalar(s) => s.syntax(),
402 YamlNode::Mapping(m) => m.syntax(),
403 YamlNode::Sequence(s) => s.syntax(),
404 YamlNode::Alias(a) => a.syntax(),
405 YamlNode::TaggedNode(t) => t.syntax(),
406 }
407 }
408
409 pub fn yaml_eq<O: AsYaml>(&self, other: &O) -> bool {
411 yaml_eq(self, other)
412 }
413
414 pub fn as_scalar(&self) -> Option<&Scalar> {
416 if let YamlNode::Scalar(s) = self {
417 Some(s)
418 } else {
419 None
420 }
421 }
422
423 pub fn as_mapping(&self) -> Option<&Mapping> {
425 if let YamlNode::Mapping(m) = self {
426 Some(m)
427 } else {
428 None
429 }
430 }
431
432 pub fn as_sequence(&self) -> Option<&Sequence> {
434 if let YamlNode::Sequence(s) = self {
435 Some(s)
436 } else {
437 None
438 }
439 }
440
441 pub fn as_tagged(&self) -> Option<&TaggedNode> {
443 if let YamlNode::TaggedNode(t) = self {
444 Some(t)
445 } else {
446 None
447 }
448 }
449
450 pub fn as_alias(&self) -> Option<&Alias> {
452 if let YamlNode::Alias(a) = self {
453 Some(a)
454 } else {
455 None
456 }
457 }
458
459 pub fn is_scalar(&self) -> bool {
461 matches!(self, YamlNode::Scalar(_))
462 }
463
464 pub fn is_mapping(&self) -> bool {
466 matches!(self, YamlNode::Mapping(_))
467 }
468
469 pub fn is_sequence(&self) -> bool {
471 matches!(self, YamlNode::Sequence(_))
472 }
473
474 pub fn is_tagged(&self) -> bool {
476 matches!(self, YamlNode::TaggedNode(_))
477 }
478
479 pub fn is_alias(&self) -> bool {
481 matches!(self, YamlNode::Alias(_))
482 }
483
484 pub fn to_i64(&self) -> Option<i64> {
488 crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_i64()
489 }
490
491 pub fn to_f64(&self) -> Option<f64> {
495 crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_f64()
496 }
497
498 pub fn to_bool(&self) -> Option<bool> {
502 crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_bool()
503 }
504
505 pub fn get(&self, key: impl crate::AsYaml) -> Option<YamlNode> {
509 self.as_mapping()?
510 .get_node(key)
511 .and_then(YamlNode::from_syntax)
512 }
513
514 pub fn get_item(&self, index: usize) -> Option<YamlNode> {
518 self.as_sequence()?
519 .items()
520 .nth(index)
521 .and_then(YamlNode::from_syntax)
522 }
523}
524
525impl AsYaml for YamlNode {
526 fn as_node(&self) -> Option<&SyntaxNode> {
527 Some(self.syntax())
528 }
529
530 fn kind(&self) -> YamlKind {
531 YamlNode::kind(self)
532 }
533
534 fn build_content(
535 &self,
536 builder: &mut rowan::GreenNodeBuilder,
537 _indent: usize,
538 _flow_context: bool,
539 ) -> bool {
540 let node = self.syntax();
541 copy_node_content(builder, node);
542 node.last_token()
543 .map(|t| t.kind() == crate::lex::SyntaxKind::NEWLINE)
544 .unwrap_or(false)
545 }
546
547 fn is_inline(&self) -> bool {
548 use crate::yaml::ValueNode;
549 match self {
550 YamlNode::Scalar(_) => true,
551 YamlNode::Mapping(m) => ValueNode::is_inline(m),
552 YamlNode::Sequence(s) => ValueNode::is_inline(s),
553 YamlNode::Alias(_) => true,
554 YamlNode::TaggedNode(_) => true,
555 }
556 }
557}
558
559impl PartialEq<str> for YamlNode {
561 fn eq(&self, other: &str) -> bool {
562 yaml_eq(self, &other)
563 }
564}
565
566impl PartialEq<&str> for YamlNode {
567 fn eq(&self, other: &&str) -> bool {
568 yaml_eq(self, other)
569 }
570}
571
572impl PartialEq<String> for YamlNode {
573 fn eq(&self, other: &String) -> bool {
574 yaml_eq(self, &other.as_str())
575 }
576}
577
578impl fmt::Display for YamlNode {
579 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580 write!(f, "{}", self.syntax().text())
581 }
582}
583
584impl<T: AsYaml + ?Sized> AsYaml for &T {
591 fn as_node(&self) -> Option<&SyntaxNode> {
592 (*self).as_node()
593 }
594
595 fn kind(&self) -> YamlKind {
596 (*self).kind()
597 }
598
599 fn build_content(
600 &self,
601 builder: &mut rowan::GreenNodeBuilder,
602 indent: usize,
603 flow_context: bool,
604 ) -> bool {
605 (*self).build_content(builder, indent, flow_context)
606 }
607
608 fn is_inline(&self) -> bool {
609 (*self).is_inline()
610 }
611}
612
613pub(crate) fn copy_node_content(builder: &mut rowan::GreenNodeBuilder, node: &SyntaxNode) {
615 for child in node.children_with_tokens() {
616 match child {
617 rowan::NodeOrToken::Node(n) => {
618 builder.start_node(n.kind().into());
619 copy_node_content(builder, &n);
620 builder.finish_node();
621 }
622 rowan::NodeOrToken::Token(t) => {
623 builder.token(t.kind().into(), t.text());
624 }
625 }
626 }
627}
628
629pub(crate) fn copy_node_content_with_indent(
635 builder: &mut rowan::GreenNodeBuilder,
636 node: &SyntaxNode,
637 indent: usize,
638) {
639 use crate::lex::SyntaxKind;
640 let mut after_newline = false;
641
642 for child in node.children_with_tokens() {
643 match child {
644 rowan::NodeOrToken::Node(n) => {
645 if after_newline && indent > 0 {
647 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
648 }
649 builder.start_node(n.kind().into());
650 copy_node_content_with_indent(builder, &n, indent);
651 builder.finish_node();
652 after_newline = false;
653 }
654 rowan::NodeOrToken::Token(t) => {
655 match t.kind() {
656 SyntaxKind::NEWLINE => {
657 builder.token(t.kind().into(), t.text());
658 after_newline = true;
659 }
660 SyntaxKind::WHITESPACE | SyntaxKind::INDENT => {
661 if after_newline && indent > 0 {
663 let existing_indent = t.text().len();
664 let total_indent = indent + existing_indent;
665 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(total_indent));
666 } else if after_newline {
667 } else {
669 builder.token(t.kind().into(), t.text());
671 }
672 after_newline = false;
673 }
674 SyntaxKind::DASH => {
675 if after_newline && indent > 0 {
677 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
678 }
679 builder.token(t.kind().into(), t.text());
680 after_newline = false;
681 }
682 _ => {
683 if after_newline && indent > 0 {
685 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
686 }
687 builder.token(t.kind().into(), t.text());
688 after_newline = false;
689 }
690 }
691 }
692 }
693 }
694}
695
696macro_rules! impl_as_yaml_int {
702 ($($ty:ty),+ $(,)?) => {
703 $(impl AsYaml for $ty {
704 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
705 None
706 }
707
708 fn kind(&self) -> YamlKind {
709 YamlKind::Scalar
710 }
711
712 fn build_content(&self, builder: &mut rowan::GreenNodeBuilder, _indent: usize, _flow_context: bool) -> bool {
713 use crate::lex::SyntaxKind;
714 builder.start_node(SyntaxKind::SCALAR.into());
715 builder.token(SyntaxKind::INT.into(), &self.to_string());
716 builder.finish_node();
717 false
718 }
719
720 fn is_inline(&self) -> bool {
721 true
722 }
723 })+
724 };
725}
726
727macro_rules! impl_as_yaml_float {
729 ($($ty:ty),+ $(,)?) => {
730 $(impl AsYaml for $ty {
731 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
732 None
733 }
734
735 fn kind(&self) -> YamlKind {
736 YamlKind::Scalar
737 }
738
739 fn build_content(&self, builder: &mut rowan::GreenNodeBuilder, _indent: usize, _flow_context: bool) -> bool {
740 use crate::lex::SyntaxKind;
741 builder.start_node(SyntaxKind::SCALAR.into());
742 let s = self.to_string();
743 let float_str = if s.contains('.') || s.contains('e') || s.contains('E') {
745 s
746 } else {
747 format!("{}.0", s)
748 };
749 builder.token(SyntaxKind::FLOAT.into(), &float_str);
750 builder.finish_node();
751 false
752 }
753
754 fn is_inline(&self) -> bool {
755 true
756 }
757 })+
758 };
759}
760
761impl_as_yaml_int!(i64, i32, i16, i8, isize, u64, u32, u16, u8, usize);
763
764impl_as_yaml_float!(f64, f32);
766
767impl AsYaml for bool {
768 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
769 None
770 }
771
772 fn kind(&self) -> YamlKind {
773 YamlKind::Scalar
774 }
775
776 fn build_content(
777 &self,
778 builder: &mut rowan::GreenNodeBuilder,
779 _indent: usize,
780 _flow_context: bool,
781 ) -> bool {
782 use crate::lex::SyntaxKind;
783 builder.start_node(SyntaxKind::SCALAR.into());
784 builder.token(
785 SyntaxKind::BOOL.into(),
786 if *self { "true" } else { "false" },
787 );
788 builder.finish_node();
789 false
790 }
791
792 fn is_inline(&self) -> bool {
793 true
794 }
795}
796
797impl AsYaml for String {
798 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
799 None
800 }
801
802 fn kind(&self) -> YamlKind {
803 YamlKind::Scalar
804 }
805
806 fn build_content(
807 &self,
808 builder: &mut rowan::GreenNodeBuilder,
809 _indent: usize,
810 flow_context: bool,
811 ) -> bool {
812 self.as_str().build_content(builder, _indent, flow_context)
813 }
814
815 fn is_inline(&self) -> bool {
816 true
817 }
818}
819
820impl AsYaml for &str {
821 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
822 None
823 }
824
825 fn kind(&self) -> YamlKind {
826 YamlKind::Scalar
827 }
828
829 fn build_content(
830 &self,
831 builder: &mut rowan::GreenNodeBuilder,
832 _indent: usize,
833 flow_context: bool,
834 ) -> bool {
835 use crate::lex::SyntaxKind;
836 use crate::scalar::ScalarValue;
837
838 let scalar = if flow_context {
841 ScalarValue::double_quoted(*self)
842 } else {
843 ScalarValue::string(*self)
844 };
845
846 let yaml_text = scalar.to_yaml_string();
847 builder.start_node(SyntaxKind::SCALAR.into());
850 builder.token(SyntaxKind::STRING.into(), &yaml_text);
851 builder.finish_node();
852 false
853 }
854
855 fn is_inline(&self) -> bool {
856 true
857 }
858}
859
860impl<T: AsYaml> AsYaml for Option<T> {
862 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
863 self.as_ref().and_then(AsYaml::as_node)
864 }
865
866 fn kind(&self) -> YamlKind {
867 match self {
868 Some(v) => v.kind(),
869 None => YamlKind::Scalar,
870 }
871 }
872
873 fn build_content(
874 &self,
875 builder: &mut rowan::GreenNodeBuilder,
876 indent: usize,
877 flow_context: bool,
878 ) -> bool {
879 match self {
880 Some(v) => v.build_content(builder, indent, flow_context),
881 None => crate::scalar::ScalarValue::null().build_content(builder, indent, flow_context),
882 }
883 }
884
885 fn is_inline(&self) -> bool {
886 match self {
887 Some(v) => v.is_inline(),
888 None => true,
889 }
890 }
891}
892
893#[cfg(test)]
894mod tests {
895 use super::*;
896 use crate::yaml::Document;
897 use std::str::FromStr;
898
899 #[test]
900 fn test_yaml_eq_different_quoting_styles() {
901 let yaml = r#"
902plain: value
903single: 'value'
904double: "value"
905"#;
906
907 let doc = Document::from_str(yaml).unwrap();
908 let mapping = doc.as_mapping().unwrap();
909
910 let plain = mapping.get("plain").unwrap();
911 let single = mapping.get("single").unwrap();
912 let double = mapping.get("double").unwrap();
913
914 assert!(yaml_eq(&plain, &single));
916 assert!(yaml_eq(&single, &double));
917 assert!(yaml_eq(&plain, &double));
918
919 assert!(yaml_eq(&plain, &"value"));
921 assert!(yaml_eq(&single, &"value"));
922 assert!(yaml_eq(&double, &"value"));
923 }
924
925 #[test]
926 fn test_yaml_eq_escape_sequences() {
927 let yaml = r#"
928newline1: "line1\nline2"
929newline2: "line1
930line2"
931tab1: "a\tb"
932tab2: "a b"
933backslash1: "path\\file"
934backslash2: 'path\file'
935quote1: "say \"hi\""
936quote2: 'say "hi"'
937"#;
938
939 let doc = Document::from_str(yaml).unwrap();
940 let mapping = doc.as_mapping().unwrap();
941
942 let newline1 = mapping.get("newline1").unwrap();
943 let newline2 = mapping.get("newline2").unwrap();
944 let tab1 = mapping.get("tab1").unwrap();
945 let tab2 = mapping.get("tab2").unwrap();
946 let backslash1 = mapping.get("backslash1").unwrap();
947 let backslash2 = mapping.get("backslash2").unwrap();
948 let quote1 = mapping.get("quote1").unwrap();
949 let quote2 = mapping.get("quote2").unwrap();
950
951 assert!(yaml_eq(&newline1, &newline2));
953
954 assert!(yaml_eq(&tab1, &tab2));
956
957 assert!(yaml_eq(&backslash1, &backslash2));
962 assert!(yaml_eq(&backslash1, &"path\\file"));
963 assert!(yaml_eq(&backslash2, &"path\\file"));
964
965 assert!(yaml_eq("e1, "e2));
967 assert!(yaml_eq("e1, &r#"say "hi""#));
968 }
969
970 #[test]
971 fn test_yaml_eq_single_quote_escaping() {
972 let yaml = r#"
973single1: 'can''t'
974single2: "can't"
975"#;
976
977 let doc = Document::from_str(yaml).unwrap();
978 let mapping = doc.as_mapping().unwrap();
979
980 let single1 = mapping.get("single1").unwrap();
981 let single2 = mapping.get("single2").unwrap();
982
983 assert!(yaml_eq(&single1, &single2));
985 assert!(yaml_eq(&single1, &"can't"));
986 }
987
988 #[test]
989 fn test_yaml_eq_unicode_escapes() {
990 let yaml = r#"
991unicode1: "hello\x20world"
992unicode2: "hello world"
993unicode3: "smiley\u0020face"
994unicode4: "smiley face"
995"#;
996
997 let doc = Document::from_str(yaml).unwrap();
998 let mapping = doc.as_mapping().unwrap();
999
1000 let unicode1 = mapping.get("unicode1").unwrap();
1001 let unicode2 = mapping.get("unicode2").unwrap();
1002 let unicode3 = mapping.get("unicode3").unwrap();
1003 let unicode4 = mapping.get("unicode4").unwrap();
1004
1005 assert!(yaml_eq(&unicode1, &unicode2));
1007 assert!(yaml_eq(&unicode1, &"hello world"));
1008
1009 assert!(yaml_eq(&unicode3, &unicode4));
1011 assert!(yaml_eq(&unicode3, &"smiley face"));
1012 }
1013
1014 #[test]
1015 fn test_yaml_eq_with_comments() {
1016 let yaml1 = r#"
1017# This is a comment
1018key: value # inline comment
1019"#;
1020
1021 let yaml2 = r#"
1022key: value
1023"#;
1024
1025 let doc1 = Document::from_str(yaml1).unwrap();
1026 let doc2 = Document::from_str(yaml2).unwrap();
1027
1028 let mapping1 = doc1.as_mapping().unwrap();
1029 let mapping2 = doc2.as_mapping().unwrap();
1030
1031 assert!(yaml_eq(&mapping1, &mapping2));
1033
1034 let value1 = mapping1.get("key").unwrap();
1035 let value2 = mapping2.get("key").unwrap();
1036
1037 assert!(yaml_eq(&value1, &value2));
1038 assert!(yaml_eq(&value1, &"value"));
1039 }
1040
1041 #[test]
1042 fn test_yaml_eq_mappings() {
1043 let yaml1 = r#"
1044a: 1
1045b: 2
1046c: 3
1047"#;
1048
1049 let yaml2 = r#"
1050a: 1
1051b: 2
1052c: 3
1053"#;
1054
1055 let yaml3 = r#"
1056a: 1
1057c: 3
1058b: 2
1059"#;
1060
1061 let doc1 = Document::from_str(yaml1).unwrap();
1062 let doc2 = Document::from_str(yaml2).unwrap();
1063 let doc3 = Document::from_str(yaml3).unwrap();
1064
1065 let mapping1 = doc1.as_mapping().unwrap();
1066 let mapping2 = doc2.as_mapping().unwrap();
1067 let mapping3 = doc3.as_mapping().unwrap();
1068
1069 assert!(yaml_eq(&mapping1, &mapping2));
1071
1072 assert!(!yaml_eq(&mapping1, &mapping3));
1074 }
1075
1076 #[test]
1077 fn test_yaml_eq_sequences() {
1078 let yaml1 = r#"
1079- one
1080- two
1081- three
1082"#;
1083
1084 let yaml2 = r#"
1085- one
1086- two
1087- three
1088"#;
1089
1090 let yaml3 = r#"
1091- one
1092- three
1093- two
1094"#;
1095
1096 let doc1 = Document::from_str(yaml1).unwrap();
1097 let doc2 = Document::from_str(yaml2).unwrap();
1098 let doc3 = Document::from_str(yaml3).unwrap();
1099
1100 let seq1 = doc1.as_sequence().unwrap();
1101 let seq2 = doc2.as_sequence().unwrap();
1102 let seq3 = doc3.as_sequence().unwrap();
1103
1104 assert!(yaml_eq(&seq1, &seq2));
1106
1107 assert!(!yaml_eq(&seq1, &seq3));
1109 }
1110
1111 #[test]
1112 fn test_yaml_eq_nested_structures() {
1113 let yaml1 = r#"
1114outer:
1115 inner:
1116 key: "value"
1117"#;
1118
1119 let yaml2 = r#"
1120outer:
1121 inner:
1122 key: 'value'
1123"#;
1124
1125 let doc1 = Document::from_str(yaml1).unwrap();
1126 let doc2 = Document::from_str(yaml2).unwrap();
1127
1128 let mapping1 = doc1.as_mapping().unwrap();
1129 let mapping2 = doc2.as_mapping().unwrap();
1130
1131 assert!(yaml_eq(&mapping1, &mapping2));
1133 }
1134
1135 #[test]
1136 fn test_yaml_eq_special_characters() {
1137 let yaml = r#"
1138bell: "\a"
1139escape: "\e"
1140"null": "\0"
1141backspace: "\b"
1142formfeed: "\f"
1143carriagereturn: "\r"
1144verticaltab: "\v"
1145"#;
1146
1147 let doc = Document::from_str(yaml).unwrap();
1148 let mapping = doc.as_mapping().unwrap();
1149
1150 assert!(yaml_eq(&mapping.get("bell").unwrap(), &"\x07"));
1153 assert!(yaml_eq(&mapping.get("escape").unwrap(), &"\x1B"));
1154 assert!(yaml_eq(&mapping.get("null").unwrap(), &"\0"));
1155 assert!(yaml_eq(&mapping.get("backspace").unwrap(), &"\x08"));
1156 assert!(yaml_eq(&mapping.get("formfeed").unwrap(), &"\x0C"));
1157 assert!(yaml_eq(&mapping.get("carriagereturn").unwrap(), &"\r"));
1158 assert!(yaml_eq(&mapping.get("verticaltab").unwrap(), &"\x0B"));
1159 }
1160
1161 #[test]
1162 fn test_yaml_eq_empty_strings() {
1163 let yaml = r#"
1164empty1: ""
1165empty2: ''
1166"#;
1167
1168 let doc = Document::from_str(yaml).unwrap();
1169 let mapping = doc.as_mapping().unwrap();
1170
1171 let empty1 = mapping.get("empty1").unwrap();
1172 let empty2 = mapping.get("empty2").unwrap();
1173
1174 assert!(yaml_eq(&empty1, &empty2));
1175 assert!(yaml_eq(&empty1, &""));
1176 }
1177
1178 #[test]
1179 fn test_yaml_eq_whitespace_handling() {
1180 let yaml = r#"
1181spaces1: " leading"
1182spaces2: "trailing "
1183spaces3: " both "
1184plain: value
1185"#;
1186
1187 let doc = Document::from_str(yaml).unwrap();
1188 let mapping = doc.as_mapping().unwrap();
1189
1190 let spaces1 = mapping.get("spaces1").unwrap();
1191 let spaces2 = mapping.get("spaces2").unwrap();
1192 let spaces3 = mapping.get("spaces3").unwrap();
1193 let plain = mapping.get("plain").unwrap();
1194
1195 assert!(yaml_eq(&spaces1, &" leading"));
1197 assert!(yaml_eq(&spaces2, &"trailing "));
1198 assert!(yaml_eq(&spaces3, &" both "));
1199 assert!(yaml_eq(&plain, &"value"));
1200
1201 assert!(!yaml_eq(&spaces1, &"leading"));
1203 assert!(!yaml_eq(&spaces2, &"trailing"));
1204 }
1205
1206 #[test]
1207 fn test_yaml_eq_different_types() {
1208 let yaml = r#"
1209string: "123"
1210number: 123
1211sequence:
1212 - item
1213mapping:
1214 key: value
1215"#;
1216
1217 let doc = Document::from_str(yaml).unwrap();
1218 let mapping = doc.as_mapping().unwrap();
1219
1220 let string_val = mapping.get("string").unwrap();
1221 let number_val = mapping.get("number").unwrap();
1222 let sequence_val = mapping.get("sequence").unwrap();
1223 let mapping_val = mapping.get("mapping").unwrap();
1224
1225 assert!(!yaml_eq(&string_val, &number_val));
1228 assert!(!yaml_eq(&string_val, &sequence_val));
1229 assert!(!yaml_eq(&string_val, &mapping_val));
1230 assert!(!yaml_eq(&number_val, &sequence_val));
1231 assert!(!yaml_eq(&sequence_val, &mapping_val));
1232 }
1233
1234 #[test]
1235 fn test_yaml_eq_line_folding_escapes() {
1236 let yaml = r#"
1237folded1: "line1\
1238 line2"
1239folded2: "line1 line2"
1240"#;
1241
1242 let doc = Document::from_str(yaml).unwrap();
1243 let mapping = doc.as_mapping().unwrap();
1244
1245 let folded1 = mapping.get("folded1").unwrap();
1246 let folded2 = mapping.get("folded2").unwrap();
1247
1248 assert!(yaml_eq(&folded1, &folded2));
1250 assert!(yaml_eq(&folded1, &"line1 line2"));
1251 }
1252
1253 #[test]
1254 fn test_yaml_eq_complex_unicode() {
1255 let yaml = r#"
1256emoji1: "😀"
1257emoji2: "\U0001F600"
1258"#;
1259
1260 let doc = Document::from_str(yaml).unwrap();
1261 let mapping = doc.as_mapping().unwrap();
1262
1263 let emoji1 = mapping.get("emoji1").unwrap();
1264 let emoji2 = mapping.get("emoji2").unwrap();
1265
1266 assert!(yaml_eq(&emoji1, &emoji2));
1268 assert!(yaml_eq(&emoji1, &"😀"));
1269 }
1270
1271 #[test]
1272 fn test_yaml_eq_block_scalars() {
1273 let yaml1 = "literal1: |\n line1\n line2\n";
1274 let yaml2 = "literal2: |\n line1\n line2\n";
1275
1276 let doc1 = Document::from_str(yaml1).unwrap();
1277 let doc2 = Document::from_str(yaml2).unwrap();
1278
1279 let mapping1 = doc1.as_mapping().unwrap();
1280 let mapping2 = doc2.as_mapping().unwrap();
1281
1282 let literal1 = mapping1.get("literal1").unwrap();
1283 let literal2 = mapping2.get("literal2").unwrap();
1284
1285 assert!(yaml_eq(&literal1, &literal2));
1287 }
1288
1289 #[test]
1290 fn test_yaml_eq_null_and_special_values() {
1291 let yaml = r#"
1292null1: null
1293null2: null
1294null3:
1295empty: ""
1296"#;
1297
1298 let doc = Document::from_str(yaml).unwrap();
1299 let mapping = doc.as_mapping().unwrap();
1300
1301 let null1 = mapping.get("null1").unwrap();
1302 let null2 = mapping.get("null2").unwrap();
1303 let null3 = mapping.get("null3").unwrap();
1304 let empty = mapping.get("empty").unwrap();
1305
1306 assert!(yaml_eq(&null1, &null2));
1308
1309 assert!(yaml_eq(&null3, &null1));
1312 assert!(yaml_eq(&null3, &null2));
1313
1314 assert!(!yaml_eq(&null1, &empty));
1316
1317 assert!(!yaml_eq(&null1, &"null"));
1319 }
1320
1321 #[test]
1322 fn test_yaml_eq_boolean_representations() {
1323 let yaml = r#"
1324true1: true
1325true2: True
1326true3: TRUE
1327false1: false
1328false2: False
1329"#;
1330
1331 let doc = Document::from_str(yaml).unwrap();
1332 let mapping = doc.as_mapping().unwrap();
1333
1334 let true1 = mapping.get("true1").unwrap();
1335 let true2 = mapping.get("true2").unwrap();
1336 let true3 = mapping.get("true3").unwrap();
1337 let false1 = mapping.get("false1").unwrap();
1338 let false2 = mapping.get("false2").unwrap();
1339
1340 assert!(yaml_eq(&true1, &true2));
1342 assert!(yaml_eq(&true1, &true3));
1343 assert!(yaml_eq(&false1, &false2));
1344
1345 assert!(!yaml_eq(&true1, &"true"));
1347 assert!(!yaml_eq(&false1, &"false"));
1348 }
1349
1350 #[test]
1351 fn test_yaml_eq_numeric_formats() {
1352 let yaml = r#"
1353decimal: 123
1354octal: 0o173
1355hex: 0x7B
1356"#;
1357
1358 let doc = Document::from_str(yaml).unwrap();
1359 let mapping = doc.as_mapping().unwrap();
1360
1361 let decimal = mapping.get("decimal").unwrap();
1362 let octal = mapping.get("octal").unwrap();
1363 let hex = mapping.get("hex").unwrap();
1364
1365 assert!(yaml_eq(&decimal, &octal));
1367 assert!(yaml_eq(&decimal, &hex));
1368 assert!(yaml_eq(&octal, &hex));
1369
1370 assert!(!yaml_eq(&decimal, &"123"));
1372
1373 assert!(yaml_eq(&decimal, &123));
1375 assert!(yaml_eq(&octal, &123));
1376 assert!(yaml_eq(&hex, &123));
1377 }
1378
1379 #[test]
1380 fn test_yaml_eq_with_anchors() {
1381 let yaml = r#"
1382original: &anchor value
1383duplicate: value
1384"#;
1385
1386 let doc = Document::from_str(yaml).unwrap();
1387 let mapping = doc.as_mapping().unwrap();
1388
1389 let original = mapping.get("original").unwrap();
1390 let duplicate = mapping.get("duplicate").unwrap();
1391
1392 assert!(yaml_eq(&original, &duplicate));
1394 assert!(yaml_eq(&original, &"value"));
1395 }
1396
1397 #[test]
1398 fn test_yaml_eq_flow_vs_block_collections() {
1399 let yaml = r#"
1400flow_seq: [1, 2, 3]
1401block_seq:
1402 - 1
1403 - 2
1404 - 3
1405flow_map: {a: 1, b: 2}
1406block_map:
1407 a: 1
1408 b: 2
1409"#;
1410
1411 let doc = Document::from_str(yaml).unwrap();
1412 let mapping = doc.as_mapping().unwrap();
1413
1414 let flow_seq = mapping.get("flow_seq").unwrap();
1415 let block_seq = mapping.get("block_seq").unwrap();
1416 let flow_map = mapping.get("flow_map").unwrap();
1417 let block_map = mapping.get("block_map").unwrap();
1418
1419 assert!(yaml_eq(&flow_seq, &block_seq));
1421 assert!(yaml_eq(&flow_map, &block_map));
1422 }
1423
1424 #[test]
1425 fn test_option_none_emits_null() {
1426 let doc = Document::from_str("key: value\n").unwrap();
1427 doc.as_mapping().unwrap().set("key", None::<&str>);
1428 assert_eq!(doc.to_string(), "key: null\n");
1429 }
1430
1431 #[test]
1432 fn test_option_some_delegates() {
1433 let doc = Document::from_str("key: value\n").unwrap();
1434 doc.as_mapping().unwrap().set("key", Some("hello"));
1435 assert_eq!(doc.to_string(), "key: hello\n");
1436
1437 let doc = Document::from_str("key: value\n").unwrap();
1438 doc.as_mapping().unwrap().set("key", Some(42i64));
1439 assert_eq!(doc.to_string(), "key: 42\n");
1440 }
1441}