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
860#[cfg(test)]
861mod tests {
862 use super::*;
863 use crate::yaml::Document;
864 use std::str::FromStr;
865
866 #[test]
867 fn test_yaml_eq_different_quoting_styles() {
868 let yaml = r#"
869plain: value
870single: 'value'
871double: "value"
872"#;
873
874 let doc = Document::from_str(yaml).unwrap();
875 let mapping = doc.as_mapping().unwrap();
876
877 let plain = mapping.get("plain").unwrap();
878 let single = mapping.get("single").unwrap();
879 let double = mapping.get("double").unwrap();
880
881 assert!(yaml_eq(&plain, &single));
883 assert!(yaml_eq(&single, &double));
884 assert!(yaml_eq(&plain, &double));
885
886 assert!(yaml_eq(&plain, &"value"));
888 assert!(yaml_eq(&single, &"value"));
889 assert!(yaml_eq(&double, &"value"));
890 }
891
892 #[test]
893 fn test_yaml_eq_escape_sequences() {
894 let yaml = r#"
895newline1: "line1\nline2"
896newline2: "line1
897line2"
898tab1: "a\tb"
899tab2: "a b"
900backslash1: "path\\file"
901backslash2: 'path\file'
902quote1: "say \"hi\""
903quote2: 'say "hi"'
904"#;
905
906 let doc = Document::from_str(yaml).unwrap();
907 let mapping = doc.as_mapping().unwrap();
908
909 let newline1 = mapping.get("newline1").unwrap();
910 let newline2 = mapping.get("newline2").unwrap();
911 let tab1 = mapping.get("tab1").unwrap();
912 let tab2 = mapping.get("tab2").unwrap();
913 let backslash1 = mapping.get("backslash1").unwrap();
914 let backslash2 = mapping.get("backslash2").unwrap();
915 let quote1 = mapping.get("quote1").unwrap();
916 let quote2 = mapping.get("quote2").unwrap();
917
918 assert!(yaml_eq(&newline1, &newline2));
920
921 assert!(yaml_eq(&tab1, &tab2));
923
924 assert!(yaml_eq(&backslash1, &backslash2));
929 assert!(yaml_eq(&backslash1, &"path\\file"));
930 assert!(yaml_eq(&backslash2, &"path\\file"));
931
932 assert!(yaml_eq("e1, "e2));
934 assert!(yaml_eq("e1, &r#"say "hi""#));
935 }
936
937 #[test]
938 fn test_yaml_eq_single_quote_escaping() {
939 let yaml = r#"
940single1: 'can''t'
941single2: "can't"
942"#;
943
944 let doc = Document::from_str(yaml).unwrap();
945 let mapping = doc.as_mapping().unwrap();
946
947 let single1 = mapping.get("single1").unwrap();
948 let single2 = mapping.get("single2").unwrap();
949
950 assert!(yaml_eq(&single1, &single2));
952 assert!(yaml_eq(&single1, &"can't"));
953 }
954
955 #[test]
956 fn test_yaml_eq_unicode_escapes() {
957 let yaml = r#"
958unicode1: "hello\x20world"
959unicode2: "hello world"
960unicode3: "smiley\u0020face"
961unicode4: "smiley face"
962"#;
963
964 let doc = Document::from_str(yaml).unwrap();
965 let mapping = doc.as_mapping().unwrap();
966
967 let unicode1 = mapping.get("unicode1").unwrap();
968 let unicode2 = mapping.get("unicode2").unwrap();
969 let unicode3 = mapping.get("unicode3").unwrap();
970 let unicode4 = mapping.get("unicode4").unwrap();
971
972 assert!(yaml_eq(&unicode1, &unicode2));
974 assert!(yaml_eq(&unicode1, &"hello world"));
975
976 assert!(yaml_eq(&unicode3, &unicode4));
978 assert!(yaml_eq(&unicode3, &"smiley face"));
979 }
980
981 #[test]
982 fn test_yaml_eq_with_comments() {
983 let yaml1 = r#"
984# This is a comment
985key: value # inline comment
986"#;
987
988 let yaml2 = r#"
989key: value
990"#;
991
992 let doc1 = Document::from_str(yaml1).unwrap();
993 let doc2 = Document::from_str(yaml2).unwrap();
994
995 let mapping1 = doc1.as_mapping().unwrap();
996 let mapping2 = doc2.as_mapping().unwrap();
997
998 assert!(yaml_eq(&mapping1, &mapping2));
1000
1001 let value1 = mapping1.get("key").unwrap();
1002 let value2 = mapping2.get("key").unwrap();
1003
1004 assert!(yaml_eq(&value1, &value2));
1005 assert!(yaml_eq(&value1, &"value"));
1006 }
1007
1008 #[test]
1009 fn test_yaml_eq_mappings() {
1010 let yaml1 = r#"
1011a: 1
1012b: 2
1013c: 3
1014"#;
1015
1016 let yaml2 = r#"
1017a: 1
1018b: 2
1019c: 3
1020"#;
1021
1022 let yaml3 = r#"
1023a: 1
1024c: 3
1025b: 2
1026"#;
1027
1028 let doc1 = Document::from_str(yaml1).unwrap();
1029 let doc2 = Document::from_str(yaml2).unwrap();
1030 let doc3 = Document::from_str(yaml3).unwrap();
1031
1032 let mapping1 = doc1.as_mapping().unwrap();
1033 let mapping2 = doc2.as_mapping().unwrap();
1034 let mapping3 = doc3.as_mapping().unwrap();
1035
1036 assert!(yaml_eq(&mapping1, &mapping2));
1038
1039 assert!(!yaml_eq(&mapping1, &mapping3));
1041 }
1042
1043 #[test]
1044 fn test_yaml_eq_sequences() {
1045 let yaml1 = r#"
1046- one
1047- two
1048- three
1049"#;
1050
1051 let yaml2 = r#"
1052- one
1053- two
1054- three
1055"#;
1056
1057 let yaml3 = r#"
1058- one
1059- three
1060- two
1061"#;
1062
1063 let doc1 = Document::from_str(yaml1).unwrap();
1064 let doc2 = Document::from_str(yaml2).unwrap();
1065 let doc3 = Document::from_str(yaml3).unwrap();
1066
1067 let seq1 = doc1.as_sequence().unwrap();
1068 let seq2 = doc2.as_sequence().unwrap();
1069 let seq3 = doc3.as_sequence().unwrap();
1070
1071 assert!(yaml_eq(&seq1, &seq2));
1073
1074 assert!(!yaml_eq(&seq1, &seq3));
1076 }
1077
1078 #[test]
1079 fn test_yaml_eq_nested_structures() {
1080 let yaml1 = r#"
1081outer:
1082 inner:
1083 key: "value"
1084"#;
1085
1086 let yaml2 = r#"
1087outer:
1088 inner:
1089 key: 'value'
1090"#;
1091
1092 let doc1 = Document::from_str(yaml1).unwrap();
1093 let doc2 = Document::from_str(yaml2).unwrap();
1094
1095 let mapping1 = doc1.as_mapping().unwrap();
1096 let mapping2 = doc2.as_mapping().unwrap();
1097
1098 assert!(yaml_eq(&mapping1, &mapping2));
1100 }
1101
1102 #[test]
1103 fn test_yaml_eq_special_characters() {
1104 let yaml = r#"
1105bell: "\a"
1106escape: "\e"
1107"null": "\0"
1108backspace: "\b"
1109formfeed: "\f"
1110carriagereturn: "\r"
1111verticaltab: "\v"
1112"#;
1113
1114 let doc = Document::from_str(yaml).unwrap();
1115 let mapping = doc.as_mapping().unwrap();
1116
1117 assert!(yaml_eq(&mapping.get("bell").unwrap(), &"\x07"));
1120 assert!(yaml_eq(&mapping.get("escape").unwrap(), &"\x1B"));
1121 assert!(yaml_eq(&mapping.get("null").unwrap(), &"\0"));
1122 assert!(yaml_eq(&mapping.get("backspace").unwrap(), &"\x08"));
1123 assert!(yaml_eq(&mapping.get("formfeed").unwrap(), &"\x0C"));
1124 assert!(yaml_eq(&mapping.get("carriagereturn").unwrap(), &"\r"));
1125 assert!(yaml_eq(&mapping.get("verticaltab").unwrap(), &"\x0B"));
1126 }
1127
1128 #[test]
1129 fn test_yaml_eq_empty_strings() {
1130 let yaml = r#"
1131empty1: ""
1132empty2: ''
1133"#;
1134
1135 let doc = Document::from_str(yaml).unwrap();
1136 let mapping = doc.as_mapping().unwrap();
1137
1138 let empty1 = mapping.get("empty1").unwrap();
1139 let empty2 = mapping.get("empty2").unwrap();
1140
1141 assert!(yaml_eq(&empty1, &empty2));
1142 assert!(yaml_eq(&empty1, &""));
1143 }
1144
1145 #[test]
1146 fn test_yaml_eq_whitespace_handling() {
1147 let yaml = r#"
1148spaces1: " leading"
1149spaces2: "trailing "
1150spaces3: " both "
1151plain: value
1152"#;
1153
1154 let doc = Document::from_str(yaml).unwrap();
1155 let mapping = doc.as_mapping().unwrap();
1156
1157 let spaces1 = mapping.get("spaces1").unwrap();
1158 let spaces2 = mapping.get("spaces2").unwrap();
1159 let spaces3 = mapping.get("spaces3").unwrap();
1160 let plain = mapping.get("plain").unwrap();
1161
1162 assert!(yaml_eq(&spaces1, &" leading"));
1164 assert!(yaml_eq(&spaces2, &"trailing "));
1165 assert!(yaml_eq(&spaces3, &" both "));
1166 assert!(yaml_eq(&plain, &"value"));
1167
1168 assert!(!yaml_eq(&spaces1, &"leading"));
1170 assert!(!yaml_eq(&spaces2, &"trailing"));
1171 }
1172
1173 #[test]
1174 fn test_yaml_eq_different_types() {
1175 let yaml = r#"
1176string: "123"
1177number: 123
1178sequence:
1179 - item
1180mapping:
1181 key: value
1182"#;
1183
1184 let doc = Document::from_str(yaml).unwrap();
1185 let mapping = doc.as_mapping().unwrap();
1186
1187 let string_val = mapping.get("string").unwrap();
1188 let number_val = mapping.get("number").unwrap();
1189 let sequence_val = mapping.get("sequence").unwrap();
1190 let mapping_val = mapping.get("mapping").unwrap();
1191
1192 assert!(!yaml_eq(&string_val, &number_val));
1195 assert!(!yaml_eq(&string_val, &sequence_val));
1196 assert!(!yaml_eq(&string_val, &mapping_val));
1197 assert!(!yaml_eq(&number_val, &sequence_val));
1198 assert!(!yaml_eq(&sequence_val, &mapping_val));
1199 }
1200
1201 #[test]
1202 fn test_yaml_eq_line_folding_escapes() {
1203 let yaml = r#"
1204folded1: "line1\
1205 line2"
1206folded2: "line1 line2"
1207"#;
1208
1209 let doc = Document::from_str(yaml).unwrap();
1210 let mapping = doc.as_mapping().unwrap();
1211
1212 let folded1 = mapping.get("folded1").unwrap();
1213 let folded2 = mapping.get("folded2").unwrap();
1214
1215 assert!(yaml_eq(&folded1, &folded2));
1217 assert!(yaml_eq(&folded1, &"line1 line2"));
1218 }
1219
1220 #[test]
1221 fn test_yaml_eq_complex_unicode() {
1222 let yaml = r#"
1223emoji1: "😀"
1224emoji2: "\U0001F600"
1225"#;
1226
1227 let doc = Document::from_str(yaml).unwrap();
1228 let mapping = doc.as_mapping().unwrap();
1229
1230 let emoji1 = mapping.get("emoji1").unwrap();
1231 let emoji2 = mapping.get("emoji2").unwrap();
1232
1233 assert!(yaml_eq(&emoji1, &emoji2));
1235 assert!(yaml_eq(&emoji1, &"😀"));
1236 }
1237
1238 #[test]
1239 fn test_yaml_eq_block_scalars() {
1240 let yaml1 = "literal1: |\n line1\n line2\n";
1241 let yaml2 = "literal2: |\n line1\n line2\n";
1242
1243 let doc1 = Document::from_str(yaml1).unwrap();
1244 let doc2 = Document::from_str(yaml2).unwrap();
1245
1246 let mapping1 = doc1.as_mapping().unwrap();
1247 let mapping2 = doc2.as_mapping().unwrap();
1248
1249 let literal1 = mapping1.get("literal1").unwrap();
1250 let literal2 = mapping2.get("literal2").unwrap();
1251
1252 assert!(yaml_eq(&literal1, &literal2));
1254 }
1255
1256 #[test]
1257 fn test_yaml_eq_null_and_special_values() {
1258 let yaml = r#"
1259null1: null
1260null2: null
1261null3:
1262empty: ""
1263"#;
1264
1265 let doc = Document::from_str(yaml).unwrap();
1266 let mapping = doc.as_mapping().unwrap();
1267
1268 let null1 = mapping.get("null1").unwrap();
1269 let null2 = mapping.get("null2").unwrap();
1270 let null3 = mapping.get("null3").unwrap();
1271 let empty = mapping.get("empty").unwrap();
1272
1273 assert!(yaml_eq(&null1, &null2));
1275
1276 assert!(yaml_eq(&null3, &null1));
1279 assert!(yaml_eq(&null3, &null2));
1280
1281 assert!(!yaml_eq(&null1, &empty));
1283
1284 assert!(!yaml_eq(&null1, &"null"));
1286 }
1287
1288 #[test]
1289 fn test_yaml_eq_boolean_representations() {
1290 let yaml = r#"
1291true1: true
1292true2: True
1293true3: TRUE
1294false1: false
1295false2: False
1296"#;
1297
1298 let doc = Document::from_str(yaml).unwrap();
1299 let mapping = doc.as_mapping().unwrap();
1300
1301 let true1 = mapping.get("true1").unwrap();
1302 let true2 = mapping.get("true2").unwrap();
1303 let true3 = mapping.get("true3").unwrap();
1304 let false1 = mapping.get("false1").unwrap();
1305 let false2 = mapping.get("false2").unwrap();
1306
1307 assert!(yaml_eq(&true1, &true2));
1309 assert!(yaml_eq(&true1, &true3));
1310 assert!(yaml_eq(&false1, &false2));
1311
1312 assert!(!yaml_eq(&true1, &"true"));
1314 assert!(!yaml_eq(&false1, &"false"));
1315 }
1316
1317 #[test]
1318 fn test_yaml_eq_numeric_formats() {
1319 let yaml = r#"
1320decimal: 123
1321octal: 0o173
1322hex: 0x7B
1323"#;
1324
1325 let doc = Document::from_str(yaml).unwrap();
1326 let mapping = doc.as_mapping().unwrap();
1327
1328 let decimal = mapping.get("decimal").unwrap();
1329 let octal = mapping.get("octal").unwrap();
1330 let hex = mapping.get("hex").unwrap();
1331
1332 assert!(yaml_eq(&decimal, &octal));
1334 assert!(yaml_eq(&decimal, &hex));
1335 assert!(yaml_eq(&octal, &hex));
1336
1337 assert!(!yaml_eq(&decimal, &"123"));
1339
1340 assert!(yaml_eq(&decimal, &123));
1342 assert!(yaml_eq(&octal, &123));
1343 assert!(yaml_eq(&hex, &123));
1344 }
1345
1346 #[test]
1347 fn test_yaml_eq_with_anchors() {
1348 let yaml = r#"
1349original: &anchor value
1350duplicate: value
1351"#;
1352
1353 let doc = Document::from_str(yaml).unwrap();
1354 let mapping = doc.as_mapping().unwrap();
1355
1356 let original = mapping.get("original").unwrap();
1357 let duplicate = mapping.get("duplicate").unwrap();
1358
1359 assert!(yaml_eq(&original, &duplicate));
1361 assert!(yaml_eq(&original, &"value"));
1362 }
1363
1364 #[test]
1365 fn test_yaml_eq_flow_vs_block_collections() {
1366 let yaml = r#"
1367flow_seq: [1, 2, 3]
1368block_seq:
1369 - 1
1370 - 2
1371 - 3
1372flow_map: {a: 1, b: 2}
1373block_map:
1374 a: 1
1375 b: 2
1376"#;
1377
1378 let doc = Document::from_str(yaml).unwrap();
1379 let mapping = doc.as_mapping().unwrap();
1380
1381 let flow_seq = mapping.get("flow_seq").unwrap();
1382 let block_seq = mapping.get("block_seq").unwrap();
1383 let flow_map = mapping.get("flow_map").unwrap();
1384 let block_map = mapping.get("block_map").unwrap();
1385
1386 assert!(yaml_eq(&flow_seq, &block_seq));
1388 assert!(yaml_eq(&flow_map, &block_map));
1389 }
1390}