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> AsYaml for &T {
586 fn as_node(&self) -> Option<&SyntaxNode> {
587 (*self).as_node()
588 }
589
590 fn kind(&self) -> YamlKind {
591 (*self).kind()
592 }
593
594 fn build_content(
595 &self,
596 builder: &mut rowan::GreenNodeBuilder,
597 indent: usize,
598 flow_context: bool,
599 ) -> bool {
600 (*self).build_content(builder, indent, flow_context)
601 }
602
603 fn is_inline(&self) -> bool {
604 (*self).is_inline()
605 }
606}
607
608pub(crate) fn copy_node_content(builder: &mut rowan::GreenNodeBuilder, node: &SyntaxNode) {
610 for child in node.children_with_tokens() {
611 match child {
612 rowan::NodeOrToken::Node(n) => {
613 builder.start_node(n.kind().into());
614 copy_node_content(builder, &n);
615 builder.finish_node();
616 }
617 rowan::NodeOrToken::Token(t) => {
618 builder.token(t.kind().into(), t.text());
619 }
620 }
621 }
622}
623
624pub(crate) fn copy_node_content_with_indent(
630 builder: &mut rowan::GreenNodeBuilder,
631 node: &SyntaxNode,
632 indent: usize,
633) {
634 use crate::lex::SyntaxKind;
635 let mut after_newline = false;
636
637 for child in node.children_with_tokens() {
638 match child {
639 rowan::NodeOrToken::Node(n) => {
640 if after_newline && indent > 0 {
642 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
643 }
644 builder.start_node(n.kind().into());
645 copy_node_content_with_indent(builder, &n, indent);
646 builder.finish_node();
647 after_newline = false;
648 }
649 rowan::NodeOrToken::Token(t) => {
650 match t.kind() {
651 SyntaxKind::NEWLINE => {
652 builder.token(t.kind().into(), t.text());
653 after_newline = true;
654 }
655 SyntaxKind::WHITESPACE | SyntaxKind::INDENT => {
656 if after_newline && indent > 0 {
658 let existing_indent = t.text().len();
659 let total_indent = indent + existing_indent;
660 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(total_indent));
661 } else if after_newline {
662 } else {
664 builder.token(t.kind().into(), t.text());
666 }
667 after_newline = false;
668 }
669 SyntaxKind::DASH => {
670 if after_newline && indent > 0 {
672 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
673 }
674 builder.token(t.kind().into(), t.text());
675 after_newline = false;
676 }
677 _ => {
678 if after_newline && indent > 0 {
680 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
681 }
682 builder.token(t.kind().into(), t.text());
683 after_newline = false;
684 }
685 }
686 }
687 }
688 }
689}
690
691macro_rules! impl_as_yaml_int {
697 ($($ty:ty),+ $(,)?) => {
698 $(impl AsYaml for $ty {
699 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
700 None
701 }
702
703 fn kind(&self) -> YamlKind {
704 YamlKind::Scalar
705 }
706
707 fn build_content(&self, builder: &mut rowan::GreenNodeBuilder, _indent: usize, _flow_context: bool) -> bool {
708 use crate::lex::SyntaxKind;
709 builder.start_node(SyntaxKind::SCALAR.into());
710 builder.token(SyntaxKind::INT.into(), &self.to_string());
711 builder.finish_node();
712 false
713 }
714
715 fn is_inline(&self) -> bool {
716 true
717 }
718 })+
719 };
720}
721
722macro_rules! impl_as_yaml_float {
724 ($($ty:ty),+ $(,)?) => {
725 $(impl AsYaml for $ty {
726 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
727 None
728 }
729
730 fn kind(&self) -> YamlKind {
731 YamlKind::Scalar
732 }
733
734 fn build_content(&self, builder: &mut rowan::GreenNodeBuilder, _indent: usize, _flow_context: bool) -> bool {
735 use crate::lex::SyntaxKind;
736 builder.start_node(SyntaxKind::SCALAR.into());
737 let s = self.to_string();
738 let float_str = if s.contains('.') || s.contains('e') || s.contains('E') {
740 s
741 } else {
742 format!("{}.0", s)
743 };
744 builder.token(SyntaxKind::FLOAT.into(), &float_str);
745 builder.finish_node();
746 false
747 }
748
749 fn is_inline(&self) -> bool {
750 true
751 }
752 })+
753 };
754}
755
756impl_as_yaml_int!(i64, i32, i16, i8, isize, u64, u32, u16, u8, usize);
758
759impl_as_yaml_float!(f64, f32);
761
762impl AsYaml for bool {
763 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
764 None
765 }
766
767 fn kind(&self) -> YamlKind {
768 YamlKind::Scalar
769 }
770
771 fn build_content(
772 &self,
773 builder: &mut rowan::GreenNodeBuilder,
774 _indent: usize,
775 _flow_context: bool,
776 ) -> bool {
777 use crate::lex::SyntaxKind;
778 builder.start_node(SyntaxKind::SCALAR.into());
779 builder.token(
780 SyntaxKind::BOOL.into(),
781 if *self { "true" } else { "false" },
782 );
783 builder.finish_node();
784 false
785 }
786
787 fn is_inline(&self) -> bool {
788 true
789 }
790}
791
792impl AsYaml for String {
793 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
794 None
795 }
796
797 fn kind(&self) -> YamlKind {
798 YamlKind::Scalar
799 }
800
801 fn build_content(
802 &self,
803 builder: &mut rowan::GreenNodeBuilder,
804 _indent: usize,
805 flow_context: bool,
806 ) -> bool {
807 self.as_str().build_content(builder, _indent, flow_context)
808 }
809
810 fn is_inline(&self) -> bool {
811 true
812 }
813}
814
815impl AsYaml for &str {
816 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
817 None
818 }
819
820 fn kind(&self) -> YamlKind {
821 YamlKind::Scalar
822 }
823
824 fn build_content(
825 &self,
826 builder: &mut rowan::GreenNodeBuilder,
827 _indent: usize,
828 flow_context: bool,
829 ) -> bool {
830 use crate::lex::SyntaxKind;
831 use crate::scalar::ScalarValue;
832
833 let scalar = if flow_context {
836 ScalarValue::double_quoted(*self)
837 } else {
838 ScalarValue::string(*self)
839 };
840
841 let yaml_text = scalar.to_yaml_string();
842 builder.start_node(SyntaxKind::SCALAR.into());
845 builder.token(SyntaxKind::STRING.into(), &yaml_text);
846 builder.finish_node();
847 false
848 }
849
850 fn is_inline(&self) -> bool {
851 true
852 }
853}
854
855#[cfg(test)]
856mod tests {
857 use super::*;
858 use crate::yaml::Document;
859 use std::str::FromStr;
860
861 #[test]
862 fn test_yaml_eq_different_quoting_styles() {
863 let yaml = r#"
864plain: value
865single: 'value'
866double: "value"
867"#;
868
869 let doc = Document::from_str(yaml).unwrap();
870 let mapping = doc.as_mapping().unwrap();
871
872 let plain = mapping.get("plain").unwrap();
873 let single = mapping.get("single").unwrap();
874 let double = mapping.get("double").unwrap();
875
876 assert!(yaml_eq(&plain, &single));
878 assert!(yaml_eq(&single, &double));
879 assert!(yaml_eq(&plain, &double));
880
881 assert!(yaml_eq(&plain, &"value"));
883 assert!(yaml_eq(&single, &"value"));
884 assert!(yaml_eq(&double, &"value"));
885 }
886
887 #[test]
888 fn test_yaml_eq_escape_sequences() {
889 let yaml = r#"
890newline1: "line1\nline2"
891newline2: "line1
892line2"
893tab1: "a\tb"
894tab2: "a b"
895backslash1: "path\\file"
896backslash2: 'path\file'
897quote1: "say \"hi\""
898quote2: 'say "hi"'
899"#;
900
901 let doc = Document::from_str(yaml).unwrap();
902 let mapping = doc.as_mapping().unwrap();
903
904 let newline1 = mapping.get("newline1").unwrap();
905 let newline2 = mapping.get("newline2").unwrap();
906 let tab1 = mapping.get("tab1").unwrap();
907 let tab2 = mapping.get("tab2").unwrap();
908 let backslash1 = mapping.get("backslash1").unwrap();
909 let backslash2 = mapping.get("backslash2").unwrap();
910 let quote1 = mapping.get("quote1").unwrap();
911 let quote2 = mapping.get("quote2").unwrap();
912
913 assert!(yaml_eq(&newline1, &newline2));
915
916 assert!(yaml_eq(&tab1, &tab2));
918
919 assert!(yaml_eq(&backslash1, &backslash2));
924 assert!(yaml_eq(&backslash1, &"path\\file"));
925 assert!(yaml_eq(&backslash2, &"path\\file"));
926
927 assert!(yaml_eq("e1, "e2));
929 assert!(yaml_eq("e1, &r#"say "hi""#));
930 }
931
932 #[test]
933 fn test_yaml_eq_single_quote_escaping() {
934 let yaml = r#"
935single1: 'can''t'
936single2: "can't"
937"#;
938
939 let doc = Document::from_str(yaml).unwrap();
940 let mapping = doc.as_mapping().unwrap();
941
942 let single1 = mapping.get("single1").unwrap();
943 let single2 = mapping.get("single2").unwrap();
944
945 assert!(yaml_eq(&single1, &single2));
947 assert!(yaml_eq(&single1, &"can't"));
948 }
949
950 #[test]
951 fn test_yaml_eq_unicode_escapes() {
952 let yaml = r#"
953unicode1: "hello\x20world"
954unicode2: "hello world"
955unicode3: "smiley\u0020face"
956unicode4: "smiley face"
957"#;
958
959 let doc = Document::from_str(yaml).unwrap();
960 let mapping = doc.as_mapping().unwrap();
961
962 let unicode1 = mapping.get("unicode1").unwrap();
963 let unicode2 = mapping.get("unicode2").unwrap();
964 let unicode3 = mapping.get("unicode3").unwrap();
965 let unicode4 = mapping.get("unicode4").unwrap();
966
967 assert!(yaml_eq(&unicode1, &unicode2));
969 assert!(yaml_eq(&unicode1, &"hello world"));
970
971 assert!(yaml_eq(&unicode3, &unicode4));
973 assert!(yaml_eq(&unicode3, &"smiley face"));
974 }
975
976 #[test]
977 fn test_yaml_eq_with_comments() {
978 let yaml1 = r#"
979# This is a comment
980key: value # inline comment
981"#;
982
983 let yaml2 = r#"
984key: value
985"#;
986
987 let doc1 = Document::from_str(yaml1).unwrap();
988 let doc2 = Document::from_str(yaml2).unwrap();
989
990 let mapping1 = doc1.as_mapping().unwrap();
991 let mapping2 = doc2.as_mapping().unwrap();
992
993 assert!(yaml_eq(&mapping1, &mapping2));
995
996 let value1 = mapping1.get("key").unwrap();
997 let value2 = mapping2.get("key").unwrap();
998
999 assert!(yaml_eq(&value1, &value2));
1000 assert!(yaml_eq(&value1, &"value"));
1001 }
1002
1003 #[test]
1004 fn test_yaml_eq_mappings() {
1005 let yaml1 = r#"
1006a: 1
1007b: 2
1008c: 3
1009"#;
1010
1011 let yaml2 = r#"
1012a: 1
1013b: 2
1014c: 3
1015"#;
1016
1017 let yaml3 = r#"
1018a: 1
1019c: 3
1020b: 2
1021"#;
1022
1023 let doc1 = Document::from_str(yaml1).unwrap();
1024 let doc2 = Document::from_str(yaml2).unwrap();
1025 let doc3 = Document::from_str(yaml3).unwrap();
1026
1027 let mapping1 = doc1.as_mapping().unwrap();
1028 let mapping2 = doc2.as_mapping().unwrap();
1029 let mapping3 = doc3.as_mapping().unwrap();
1030
1031 assert!(yaml_eq(&mapping1, &mapping2));
1033
1034 assert!(!yaml_eq(&mapping1, &mapping3));
1036 }
1037
1038 #[test]
1039 fn test_yaml_eq_sequences() {
1040 let yaml1 = r#"
1041- one
1042- two
1043- three
1044"#;
1045
1046 let yaml2 = r#"
1047- one
1048- two
1049- three
1050"#;
1051
1052 let yaml3 = r#"
1053- one
1054- three
1055- two
1056"#;
1057
1058 let doc1 = Document::from_str(yaml1).unwrap();
1059 let doc2 = Document::from_str(yaml2).unwrap();
1060 let doc3 = Document::from_str(yaml3).unwrap();
1061
1062 let seq1 = doc1.as_sequence().unwrap();
1063 let seq2 = doc2.as_sequence().unwrap();
1064 let seq3 = doc3.as_sequence().unwrap();
1065
1066 assert!(yaml_eq(&seq1, &seq2));
1068
1069 assert!(!yaml_eq(&seq1, &seq3));
1071 }
1072
1073 #[test]
1074 fn test_yaml_eq_nested_structures() {
1075 let yaml1 = r#"
1076outer:
1077 inner:
1078 key: "value"
1079"#;
1080
1081 let yaml2 = r#"
1082outer:
1083 inner:
1084 key: 'value'
1085"#;
1086
1087 let doc1 = Document::from_str(yaml1).unwrap();
1088 let doc2 = Document::from_str(yaml2).unwrap();
1089
1090 let mapping1 = doc1.as_mapping().unwrap();
1091 let mapping2 = doc2.as_mapping().unwrap();
1092
1093 assert!(yaml_eq(&mapping1, &mapping2));
1095 }
1096
1097 #[test]
1098 fn test_yaml_eq_special_characters() {
1099 let yaml = r#"
1100bell: "\a"
1101escape: "\e"
1102"null": "\0"
1103backspace: "\b"
1104formfeed: "\f"
1105carriagereturn: "\r"
1106verticaltab: "\v"
1107"#;
1108
1109 let doc = Document::from_str(yaml).unwrap();
1110 let mapping = doc.as_mapping().unwrap();
1111
1112 assert!(yaml_eq(&mapping.get("bell").unwrap(), &"\x07"));
1115 assert!(yaml_eq(&mapping.get("escape").unwrap(), &"\x1B"));
1116 assert!(yaml_eq(&mapping.get("null").unwrap(), &"\0"));
1117 assert!(yaml_eq(&mapping.get("backspace").unwrap(), &"\x08"));
1118 assert!(yaml_eq(&mapping.get("formfeed").unwrap(), &"\x0C"));
1119 assert!(yaml_eq(&mapping.get("carriagereturn").unwrap(), &"\r"));
1120 assert!(yaml_eq(&mapping.get("verticaltab").unwrap(), &"\x0B"));
1121 }
1122
1123 #[test]
1124 fn test_yaml_eq_empty_strings() {
1125 let yaml = r#"
1126empty1: ""
1127empty2: ''
1128"#;
1129
1130 let doc = Document::from_str(yaml).unwrap();
1131 let mapping = doc.as_mapping().unwrap();
1132
1133 let empty1 = mapping.get("empty1").unwrap();
1134 let empty2 = mapping.get("empty2").unwrap();
1135
1136 assert!(yaml_eq(&empty1, &empty2));
1137 assert!(yaml_eq(&empty1, &""));
1138 }
1139
1140 #[test]
1141 fn test_yaml_eq_whitespace_handling() {
1142 let yaml = r#"
1143spaces1: " leading"
1144spaces2: "trailing "
1145spaces3: " both "
1146plain: value
1147"#;
1148
1149 let doc = Document::from_str(yaml).unwrap();
1150 let mapping = doc.as_mapping().unwrap();
1151
1152 let spaces1 = mapping.get("spaces1").unwrap();
1153 let spaces2 = mapping.get("spaces2").unwrap();
1154 let spaces3 = mapping.get("spaces3").unwrap();
1155 let plain = mapping.get("plain").unwrap();
1156
1157 assert!(yaml_eq(&spaces1, &" leading"));
1159 assert!(yaml_eq(&spaces2, &"trailing "));
1160 assert!(yaml_eq(&spaces3, &" both "));
1161 assert!(yaml_eq(&plain, &"value"));
1162
1163 assert!(!yaml_eq(&spaces1, &"leading"));
1165 assert!(!yaml_eq(&spaces2, &"trailing"));
1166 }
1167
1168 #[test]
1169 fn test_yaml_eq_different_types() {
1170 let yaml = r#"
1171string: "123"
1172number: 123
1173sequence:
1174 - item
1175mapping:
1176 key: value
1177"#;
1178
1179 let doc = Document::from_str(yaml).unwrap();
1180 let mapping = doc.as_mapping().unwrap();
1181
1182 let string_val = mapping.get("string").unwrap();
1183 let number_val = mapping.get("number").unwrap();
1184 let sequence_val = mapping.get("sequence").unwrap();
1185 let mapping_val = mapping.get("mapping").unwrap();
1186
1187 assert!(!yaml_eq(&string_val, &number_val));
1190 assert!(!yaml_eq(&string_val, &sequence_val));
1191 assert!(!yaml_eq(&string_val, &mapping_val));
1192 assert!(!yaml_eq(&number_val, &sequence_val));
1193 assert!(!yaml_eq(&sequence_val, &mapping_val));
1194 }
1195
1196 #[test]
1197 fn test_yaml_eq_line_folding_escapes() {
1198 let yaml = r#"
1199folded1: "line1\
1200 line2"
1201folded2: "line1 line2"
1202"#;
1203
1204 let doc = Document::from_str(yaml).unwrap();
1205 let mapping = doc.as_mapping().unwrap();
1206
1207 let folded1 = mapping.get("folded1").unwrap();
1208 let folded2 = mapping.get("folded2").unwrap();
1209
1210 assert!(yaml_eq(&folded1, &folded2));
1212 assert!(yaml_eq(&folded1, &"line1 line2"));
1213 }
1214
1215 #[test]
1216 fn test_yaml_eq_complex_unicode() {
1217 let yaml = r#"
1218emoji1: "😀"
1219emoji2: "\U0001F600"
1220"#;
1221
1222 let doc = Document::from_str(yaml).unwrap();
1223 let mapping = doc.as_mapping().unwrap();
1224
1225 let emoji1 = mapping.get("emoji1").unwrap();
1226 let emoji2 = mapping.get("emoji2").unwrap();
1227
1228 assert!(yaml_eq(&emoji1, &emoji2));
1230 assert!(yaml_eq(&emoji1, &"😀"));
1231 }
1232
1233 #[test]
1234 fn test_yaml_eq_block_scalars() {
1235 let yaml1 = "literal1: |\n line1\n line2\n";
1236 let yaml2 = "literal2: |\n line1\n line2\n";
1237
1238 let doc1 = Document::from_str(yaml1).unwrap();
1239 let doc2 = Document::from_str(yaml2).unwrap();
1240
1241 let mapping1 = doc1.as_mapping().unwrap();
1242 let mapping2 = doc2.as_mapping().unwrap();
1243
1244 let literal1 = mapping1.get("literal1").unwrap();
1245 let literal2 = mapping2.get("literal2").unwrap();
1246
1247 assert!(yaml_eq(&literal1, &literal2));
1249 }
1250
1251 #[test]
1252 fn test_yaml_eq_null_and_special_values() {
1253 let yaml = r#"
1254null1: null
1255null2: null
1256null3:
1257empty: ""
1258"#;
1259
1260 let doc = Document::from_str(yaml).unwrap();
1261 let mapping = doc.as_mapping().unwrap();
1262
1263 let null1 = mapping.get("null1").unwrap();
1264 let null2 = mapping.get("null2").unwrap();
1265 let null3 = mapping.get("null3").unwrap();
1266 let empty = mapping.get("empty").unwrap();
1267
1268 assert!(yaml_eq(&null1, &null2));
1270
1271 assert!(yaml_eq(&null3, &null1));
1274 assert!(yaml_eq(&null3, &null2));
1275
1276 assert!(!yaml_eq(&null1, &empty));
1278
1279 assert!(!yaml_eq(&null1, &"null"));
1281 }
1282
1283 #[test]
1284 fn test_yaml_eq_boolean_representations() {
1285 let yaml = r#"
1286true1: true
1287true2: True
1288true3: TRUE
1289false1: false
1290false2: False
1291"#;
1292
1293 let doc = Document::from_str(yaml).unwrap();
1294 let mapping = doc.as_mapping().unwrap();
1295
1296 let true1 = mapping.get("true1").unwrap();
1297 let true2 = mapping.get("true2").unwrap();
1298 let true3 = mapping.get("true3").unwrap();
1299 let false1 = mapping.get("false1").unwrap();
1300 let false2 = mapping.get("false2").unwrap();
1301
1302 assert!(yaml_eq(&true1, &true2));
1304 assert!(yaml_eq(&true1, &true3));
1305 assert!(yaml_eq(&false1, &false2));
1306
1307 assert!(!yaml_eq(&true1, &"true"));
1309 assert!(!yaml_eq(&false1, &"false"));
1310 }
1311
1312 #[test]
1313 fn test_yaml_eq_numeric_formats() {
1314 let yaml = r#"
1315decimal: 123
1316octal: 0o173
1317hex: 0x7B
1318"#;
1319
1320 let doc = Document::from_str(yaml).unwrap();
1321 let mapping = doc.as_mapping().unwrap();
1322
1323 let decimal = mapping.get("decimal").unwrap();
1324 let octal = mapping.get("octal").unwrap();
1325 let hex = mapping.get("hex").unwrap();
1326
1327 assert!(yaml_eq(&decimal, &octal));
1329 assert!(yaml_eq(&decimal, &hex));
1330 assert!(yaml_eq(&octal, &hex));
1331
1332 assert!(!yaml_eq(&decimal, &"123"));
1334
1335 assert!(yaml_eq(&decimal, &123));
1337 assert!(yaml_eq(&octal, &123));
1338 assert!(yaml_eq(&hex, &123));
1339 }
1340
1341 #[test]
1342 fn test_yaml_eq_with_anchors() {
1343 let yaml = r#"
1344original: &anchor value
1345duplicate: value
1346"#;
1347
1348 let doc = Document::from_str(yaml).unwrap();
1349 let mapping = doc.as_mapping().unwrap();
1350
1351 let original = mapping.get("original").unwrap();
1352 let duplicate = mapping.get("duplicate").unwrap();
1353
1354 assert!(yaml_eq(&original, &duplicate));
1356 assert!(yaml_eq(&original, &"value"));
1357 }
1358
1359 #[test]
1360 fn test_yaml_eq_flow_vs_block_collections() {
1361 let yaml = r#"
1362flow_seq: [1, 2, 3]
1363block_seq:
1364 - 1
1365 - 2
1366 - 3
1367flow_map: {a: 1, b: 2}
1368block_map:
1369 a: 1
1370 b: 2
1371"#;
1372
1373 let doc = Document::from_str(yaml).unwrap();
1374 let mapping = doc.as_mapping().unwrap();
1375
1376 let flow_seq = mapping.get("flow_seq").unwrap();
1377 let block_seq = mapping.get("block_seq").unwrap();
1378 let flow_map = mapping.get("flow_map").unwrap();
1379 let block_map = mapping.get("block_map").unwrap();
1380
1381 assert!(yaml_eq(&flow_seq, &block_seq));
1383 assert!(yaml_eq(&flow_map, &block_map));
1384 }
1385}