1use crate::event::ScalarStyle;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Schema {
27 Failsafe,
30 Json,
33 Core,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum ResolvedTag {
43 Str,
45 Int,
47 Float,
49 Bool,
51 Null,
53 Seq,
55 Map,
57}
58
59impl ResolvedTag {
60 #[must_use]
62 pub const fn as_str(self) -> &'static str {
63 match self {
64 Self::Str => "tag:yaml.org,2002:str",
65 Self::Int => "tag:yaml.org,2002:int",
66 Self::Float => "tag:yaml.org,2002:float",
67 Self::Bool => "tag:yaml.org,2002:bool",
68 Self::Null => "tag:yaml.org,2002:null",
69 Self::Seq => "tag:yaml.org,2002:seq",
70 Self::Map => "tag:yaml.org,2002:map",
71 }
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
82#[error("unresolved scalar: no JSON schema pattern matched the plain scalar value")]
83pub struct UnresolvedScalar;
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum CollectionKind {
88 Sequence,
90 Mapping,
92}
93
94pub fn resolve_scalar(
119 schema: Schema,
120 style: ScalarStyle,
121 value: &str,
122 source_tag: Option<&str>,
123) -> Result<Option<ResolvedTag>, UnresolvedScalar> {
124 if source_tag.is_some() {
126 return Ok(None);
127 }
128
129 match schema {
130 Schema::Failsafe => Ok(Some(ResolvedTag::Str)),
131
132 Schema::Core => {
133 let tag = match style {
134 ScalarStyle::Plain => resolve_core_plain(value),
135 ScalarStyle::SingleQuoted
137 | ScalarStyle::DoubleQuoted
138 | ScalarStyle::Literal(_)
139 | ScalarStyle::Folded(_) => ResolvedTag::Str,
140 };
141 Ok(Some(tag))
142 }
143
144 Schema::Json => {
145 let tag = match style {
146 ScalarStyle::Plain => resolve_json_plain(value)?,
147 ScalarStyle::SingleQuoted
149 | ScalarStyle::DoubleQuoted
150 | ScalarStyle::Literal(_)
151 | ScalarStyle::Folded(_) => ResolvedTag::Str,
152 };
153 Ok(Some(tag))
154 }
155 }
156}
157
158#[must_use]
167pub const fn resolve_collection(
168 schema: Schema,
169 kind: CollectionKind,
170 source_tag: Option<&str>,
171) -> Option<ResolvedTag> {
172 if source_tag.is_some() {
174 return None;
175 }
176 let _ = schema;
178 Some(match kind {
179 CollectionKind::Sequence => ResolvedTag::Seq,
180 CollectionKind::Mapping => ResolvedTag::Map,
181 })
182}
183
184fn resolve_core_plain(value: &str) -> ResolvedTag {
192 if is_core_null(value) {
193 ResolvedTag::Null
194 } else if is_core_bool(value) {
195 ResolvedTag::Bool
196 } else if is_core_int(value) {
197 ResolvedTag::Int
198 } else if is_core_float(value) {
199 ResolvedTag::Float
200 } else {
201 ResolvedTag::Str
202 }
203}
204
205fn resolve_json_plain(value: &str) -> Result<ResolvedTag, UnresolvedScalar> {
220 if is_json_null(value) {
221 Ok(ResolvedTag::Null)
222 } else if is_json_bool(value) {
223 Ok(ResolvedTag::Bool)
224 } else if is_json_int(value) {
225 Ok(ResolvedTag::Int)
226 } else if is_json_float(value) {
227 Ok(ResolvedTag::Float)
228 } else {
229 Err(UnresolvedScalar)
230 }
231}
232
233#[must_use]
239pub fn is_core_null(value: &str) -> bool {
240 matches!(value, "null" | "Null" | "NULL" | "~" | "")
241}
242
243#[must_use]
245pub fn is_core_bool(value: &str) -> bool {
246 matches!(
247 value,
248 "true" | "True" | "TRUE" | "false" | "False" | "FALSE"
249 )
250}
251
252#[must_use]
255pub fn is_core_int(value: &str) -> bool {
256 let rest = value
258 .strip_prefix('-')
259 .or_else(|| value.strip_prefix('+'))
260 .unwrap_or(value);
261
262 if rest.is_empty() {
263 return false;
264 }
265
266 if let Some(oct) = rest.strip_prefix("0o") {
267 !oct.is_empty() && oct.bytes().all(|b| matches!(b, b'0'..=b'7'))
269 } else if let Some(hex) = rest.strip_prefix("0x") {
270 !hex.is_empty() && hex.bytes().all(|b| b.is_ascii_hexdigit())
272 } else {
273 if rest.len() > 1 && rest.starts_with('0') {
275 return false;
276 }
277 rest.bytes().all(|b| b.is_ascii_digit())
278 }
279}
280
281#[must_use]
285pub fn is_core_float(value: &str) -> bool {
286 if matches!(value, ".nan" | ".NaN" | ".NAN") {
288 return true;
289 }
290
291 let unsigned = value
293 .strip_prefix('-')
294 .or_else(|| value.strip_prefix('+'))
295 .unwrap_or(value);
296
297 if matches!(unsigned, ".inf" | ".Inf" | ".INF") {
299 return true;
300 }
301
302 is_core_decimal_float(unsigned)
304}
305
306fn is_core_decimal_float(s: &str) -> bool {
309 let (mantissa, exp_part) = split_exponent(s);
311
312 if exp_part.is_some_and(|exp| !is_valid_exponent_digits(exp)) {
314 return false;
315 }
316
317 if let Some(after_dot) = mantissa.strip_prefix('.') {
321 !after_dot.is_empty() && after_dot.bytes().all(|b| b.is_ascii_digit())
323 } else {
324 let (int_part, frac) = mantissa.find('.').map_or((mantissa, None), |pos| {
326 (&mantissa[..pos], Some(&mantissa[pos + 1..]))
327 });
328 if int_part.is_empty() || !int_part.bytes().all(|b| b.is_ascii_digit()) {
329 return false;
330 }
331 if let Some(frac_digits) = frac {
333 if !frac_digits.bytes().all(|b| b.is_ascii_digit()) {
334 return false;
335 }
336 } else {
337 if exp_part.is_none() {
340 return false;
341 }
342 }
343 true
344 }
345}
346
347fn split_exponent(s: &str) -> (&str, Option<&str>) {
350 s.find(['e', 'E'])
351 .map_or((s, None), |pos| (&s[..pos], Some(&s[pos + 1..])))
352}
353
354fn is_valid_exponent_digits(exp: &str) -> bool {
356 let digits = exp.strip_prefix(['-', '+']).unwrap_or(exp);
357 !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit())
358}
359
360#[must_use]
366pub fn is_json_null(value: &str) -> bool {
367 value == "null"
368}
369
370#[must_use]
372pub fn is_json_bool(value: &str) -> bool {
373 matches!(value, "true" | "false")
374}
375
376#[must_use]
380pub fn is_json_int(value: &str) -> bool {
381 if value == "0" {
382 return true;
383 }
384 let rest = value.strip_prefix('-').unwrap_or(value);
386 let mut bytes = rest.bytes();
387 match bytes.next() {
388 Some(b'1'..=b'9') => {}
390 _ => return false,
391 }
392 bytes.all(|b| b.is_ascii_digit())
393}
394
395#[must_use]
399pub fn is_json_float(value: &str) -> bool {
400 let unsigned = value.strip_prefix('-').unwrap_or(value);
402
403 let after_int = if let Some(rest) = unsigned.strip_prefix('0') {
405 rest
406 } else {
407 let mut bytes = unsigned.bytes();
408 match bytes.next() {
409 Some(b'1'..=b'9') => {}
410 _ => return false,
411 }
412 let consumed = 1 + bytes.take_while(u8::is_ascii_digit).count();
413 &unsigned[consumed..]
414 };
415
416 let after_frac = after_int.strip_prefix('.').map_or(after_int, |rest| {
418 let digits = rest.bytes().take_while(u8::is_ascii_digit).count();
419 &rest[digits..]
420 });
421
422 let after_exp = if let Some(exp_rest) = after_frac
424 .strip_prefix('e')
425 .or_else(|| after_frac.strip_prefix('E'))
426 {
427 let digits_start = exp_rest.strip_prefix(['-', '+']).unwrap_or(exp_rest);
428 if digits_start.is_empty() || !digits_start.bytes().all(|b| b.is_ascii_digit()) {
429 return false;
430 }
431 ""
432 } else {
433 after_frac
434 };
435
436 after_exp.is_empty()
438}
439
440#[cfg(test)]
445mod tests {
446 use super::*;
447 use crate::event::Chomp;
448 use rstest::rstest;
449
450 #[rstest]
453 #[case::str_tag(ResolvedTag::Str, "tag:yaml.org,2002:str")]
454 #[case::int_tag(ResolvedTag::Int, "tag:yaml.org,2002:int")]
455 #[case::float_tag(ResolvedTag::Float, "tag:yaml.org,2002:float")]
456 #[case::bool_tag(ResolvedTag::Bool, "tag:yaml.org,2002:bool")]
457 #[case::null_tag(ResolvedTag::Null, "tag:yaml.org,2002:null")]
458 #[case::seq_tag(ResolvedTag::Seq, "tag:yaml.org,2002:seq")]
459 #[case::map_tag(ResolvedTag::Map, "tag:yaml.org,2002:map")]
460 fn resolved_tag_as_str_returns_uri(#[case] tag: ResolvedTag, #[case] expected: &str) {
461 assert_eq!(tag.as_str(), expected);
462 }
463
464 #[rstest]
469 #[case::null_lowercase("null")]
470 #[case::null_titlecase("Null")]
471 #[case::null_uppercase("NULL")]
472 #[case::tilde("~")]
473 #[case::empty("")]
474 fn is_core_null_returns_true(#[case] input: &str) {
475 assert!(is_core_null(input));
476 }
477
478 #[rstest]
481 #[case::none_string("none")]
482 #[case::nil_string("nil")]
483 #[case::mixed_case_null("nUll")]
484 #[case::single_space(" ")]
485 #[case::json_null_inside_word("nullX")]
486 fn is_core_null_returns_false(#[case] input: &str) {
487 assert!(!is_core_null(input));
488 }
489
490 #[rstest]
493 #[case::true_lowercase("true")]
494 #[case::true_titlecase("True")]
495 #[case::true_uppercase("TRUE")]
496 #[case::false_lowercase("false")]
497 #[case::false_titlecase("False")]
498 #[case::false_uppercase("FALSE")]
499 fn is_core_bool_returns_true(#[case] input: &str) {
500 assert!(is_core_bool(input));
501 }
502
503 #[rstest]
506 #[case::yaml11_yes("yes")]
507 #[case::yaml11_no("no")]
508 #[case::yaml11_on("on")]
509 #[case::yaml11_off("off")]
510 #[case::mixed_case_true("tRue")]
511 #[case::integer_one("1")]
512 #[case::integer_zero("0")]
513 fn is_core_bool_returns_false(#[case] input: &str) {
514 assert!(!is_core_bool(input));
515 }
516
517 #[rstest]
520 #[case::decimal_zero("0")]
521 #[case::decimal_positive("42")]
522 #[case::decimal_negative("-1")]
523 #[case::decimal_plus_prefix("+100")]
524 #[case::octal("0o17")]
525 #[case::octal_negative("-0o10")]
526 #[case::hex_lower("0xff")]
527 #[case::hex_upper("0xFF")]
528 #[case::hex_negative("-0x1A")]
529 fn is_core_int_returns_true(#[case] input: &str) {
530 assert!(is_core_int(input));
531 }
532
533 #[rstest]
536 #[case::leading_zeros("007")]
537 #[case::empty("")]
538 #[case::sign_only_plus("+")]
539 #[case::sign_only_minus("-")]
540 #[case::float_with_dot("3.14")]
541 #[case::float_exp("1e5")]
542 #[case::octal_prefix_only("0o")]
543 #[case::hex_prefix_only("0x")]
544 #[case::alpha_string("abc")]
545 fn is_core_int_returns_false(#[case] input: &str) {
546 assert!(!is_core_int(input));
547 }
548
549 #[rstest]
552 #[case::decimal_dot("3.14")]
553 #[case::decimal_no_integer_part(".5")]
554 #[case::exponent_only("1e10")]
555 #[case::exponent_negative("1.5E-3")]
556 #[case::positive_signed_float("+1.0")]
557 #[case::negative_float("-0.5")]
558 #[case::inf_lowercase(".inf")]
559 #[case::inf_titlecase(".Inf")]
560 #[case::inf_uppercase(".INF")]
561 #[case::neg_inf_lowercase("-.inf")]
562 #[case::neg_inf_titlecase("-.Inf")]
563 #[case::neg_inf_uppercase("-.INF")]
564 #[case::pos_inf("+.inf")]
565 #[case::nan_lowercase(".nan")]
566 #[case::nan_titlecase(".NaN")]
567 #[case::nan_uppercase(".NAN")]
568 fn is_core_float_returns_true(#[case] input: &str) {
569 assert!(is_core_float(input));
570 }
571
572 #[rstest]
575 #[case::bare_integer("42")]
576 #[case::empty("")]
577 #[case::bare_inf_no_dot("inf")]
578 #[case::bare_nan_no_dot("nan")]
579 #[case::sign_only("+")]
580 #[case::dot_only(".")]
581 fn is_core_float_returns_false(#[case] input: &str) {
582 assert!(!is_core_float(input));
583 }
584
585 #[test]
590 fn is_json_null_returns_true() {
591 assert!(is_json_null("null"));
592 }
593
594 #[rstest]
595 #[case::null_titlecase("Null")]
596 #[case::null_uppercase("NULL")]
597 #[case::tilde("~")]
598 #[case::empty("")]
599 fn is_json_null_returns_false(#[case] input: &str) {
600 assert!(!is_json_null(input));
601 }
602
603 #[rstest]
606 #[case::true_lowercase("true")]
607 #[case::false_lowercase("false")]
608 fn is_json_bool_returns_true(#[case] input: &str) {
609 assert!(is_json_bool(input));
610 }
611
612 #[rstest]
613 #[case::true_titlecase("True")]
614 #[case::true_uppercase("TRUE")]
615 #[case::false_titlecase("False")]
616 #[case::false_uppercase("FALSE")]
617 fn is_json_bool_returns_false(#[case] input: &str) {
618 assert!(!is_json_bool(input));
619 }
620
621 #[rstest]
624 #[case::zero("0")]
625 #[case::positive_decimal("42")]
626 #[case::negative_decimal("-1")]
627 #[case::negative_multi("-100")]
628 #[case::large_negative("-9999")]
629 fn is_json_int_returns_true(#[case] input: &str) {
630 assert!(is_json_int(input));
631 }
632
633 #[rstest]
634 #[case::plus_prefix("+42")]
635 #[case::plus_zero("+0")]
636 #[case::minus_zero("-0")]
637 #[case::leading_zeros("007")]
638 #[case::octal("0o17")]
639 #[case::hex("0xFF")]
640 #[case::empty("")]
641 #[case::sign_only_plus("+")]
642 #[case::sign_only_minus("-")]
643 fn is_json_int_returns_false(#[case] input: &str) {
644 assert!(!is_json_int(input));
645 }
646
647 #[rstest]
650 #[case::zero_float_simple("0.5")]
651 #[case::negative_with_decimal("-1.5")]
652 #[case::with_exponent("1e10")]
653 #[case::with_negative_exponent("-1.5e-3")]
654 #[case::minus_zero("-0")]
656 #[case::zero_alone("0")]
658 fn is_json_float_returns_true(#[case] input: &str) {
659 assert!(is_json_float(input));
660 }
661
662 #[rstest]
663 #[case::plus_prefix("+1.5")]
664 #[case::inf_dot(".inf")]
665 #[case::nan_dot(".nan")]
666 #[case::leading_dot(".5")]
667 #[case::empty("")]
668 #[case::sign_only("-")]
669 fn is_json_float_returns_false(#[case] input: &str) {
670 assert!(!is_json_float(input));
671 }
672
673 #[rstest]
678 #[case::plain_null(ScalarStyle::Plain, "null", None)]
679 #[case::single_quoted_true(ScalarStyle::SingleQuoted, "true", None)]
680 #[case::double_quoted_int(ScalarStyle::DoubleQuoted, "42", None)]
681 #[case::literal_block(ScalarStyle::Literal(Chomp::Clip), "hello", None)]
682 #[case::folded_block(ScalarStyle::Folded(Chomp::Strip), "world", None)]
683 fn resolve_scalar_failsafe_always_str(
684 #[case] style: ScalarStyle,
685 #[case] value: &str,
686 #[case] source_tag: Option<&str>,
687 ) {
688 assert_eq!(
689 resolve_scalar(Schema::Failsafe, style, value, source_tag),
690 Ok(Some(ResolvedTag::Str))
691 );
692 }
693
694 #[test]
695 fn resolve_scalar_failsafe_explicit_tag_passthrough() {
696 let result = resolve_scalar(
697 Schema::Failsafe,
698 ScalarStyle::Plain,
699 "null",
700 Some("tag:yaml.org,2002:str"),
701 );
702 assert_eq!(result, Ok(None));
703 }
704
705 #[rstest]
708 #[case::plain_null_lowercase(ScalarStyle::Plain, "null", None, ResolvedTag::Null)]
709 #[case::plain_null_tilde(ScalarStyle::Plain, "~", None, ResolvedTag::Null)]
710 #[case::plain_null_empty(ScalarStyle::Plain, "", None, ResolvedTag::Null)]
711 #[case::plain_bool_true_lower(ScalarStyle::Plain, "true", None, ResolvedTag::Bool)]
712 #[case::plain_bool_false_upper(ScalarStyle::Plain, "FALSE", None, ResolvedTag::Bool)]
713 #[case::plain_int_decimal(ScalarStyle::Plain, "42", None, ResolvedTag::Int)]
714 #[case::plain_int_octal(ScalarStyle::Plain, "0o17", None, ResolvedTag::Int)]
715 #[case::plain_int_hex(ScalarStyle::Plain, "0xFF", None, ResolvedTag::Int)]
716 #[case::plain_float_decimal(ScalarStyle::Plain, "3.14", None, ResolvedTag::Float)]
717 #[case::plain_float_inf(ScalarStyle::Plain, ".inf", None, ResolvedTag::Float)]
718 #[case::plain_float_nan(ScalarStyle::Plain, ".nan", None, ResolvedTag::Float)]
719 #[case::plain_unmatched_str(ScalarStyle::Plain, "hello", None, ResolvedTag::Str)]
720 #[case::plain_leading_zeros(ScalarStyle::Plain, "007", None, ResolvedTag::Str)]
721 #[case::single_quoted_null(ScalarStyle::SingleQuoted, "null", None, ResolvedTag::Str)]
722 #[case::double_quoted_true(ScalarStyle::DoubleQuoted, "true", None, ResolvedTag::Str)]
723 #[case::literal_any(ScalarStyle::Literal(Chomp::Clip), "42", None, ResolvedTag::Str)]
724 #[case::folded_any(ScalarStyle::Folded(Chomp::Keep), "null", None, ResolvedTag::Str)]
725 fn resolve_scalar_core(
726 #[case] style: ScalarStyle,
727 #[case] value: &str,
728 #[case] source_tag: Option<&str>,
729 #[case] expected: ResolvedTag,
730 ) {
731 assert_eq!(
732 resolve_scalar(Schema::Core, style, value, source_tag),
733 Ok(Some(expected))
734 );
735 }
736
737 #[test]
738 fn resolve_scalar_core_explicit_tag_passthrough() {
739 let result = resolve_scalar(
740 Schema::Core,
741 ScalarStyle::Plain,
742 "null",
743 Some("tag:yaml.org,2002:int"),
744 );
745 assert_eq!(result, Ok(None));
746 }
747
748 #[rstest]
751 #[case::plain_null_lowercase(ScalarStyle::Plain, "null", None, Ok(Some(ResolvedTag::Null)))]
753 #[case::plain_null_tilde_rejected(ScalarStyle::Plain, "~", None, Err(UnresolvedScalar))]
755 #[case::plain_empty_rejected(ScalarStyle::Plain, "", None, Err(UnresolvedScalar))]
756 #[case::plain_bool_true_lower(ScalarStyle::Plain, "true", None, Ok(Some(ResolvedTag::Bool)))]
758 #[case::plain_bool_true_upper_rejected(ScalarStyle::Plain, "TRUE", None, Err(UnresolvedScalar))]
759 #[case::plain_int_decimal(ScalarStyle::Plain, "42", None, Ok(Some(ResolvedTag::Int)))]
761 #[case::plain_int_zero(ScalarStyle::Plain, "0", None, Ok(Some(ResolvedTag::Int)))]
762 #[case::plain_int_negative(ScalarStyle::Plain, "-1", None, Ok(Some(ResolvedTag::Int)))]
763 #[case::plain_int_plus_rejected(ScalarStyle::Plain, "+42", None, Err(UnresolvedScalar))]
764 #[case::plain_minus_zero_is_float(ScalarStyle::Plain, "-0", None, Ok(Some(ResolvedTag::Float)))]
766 #[case::plain_octal_rejected(ScalarStyle::Plain, "0o17", None, Err(UnresolvedScalar))]
767 #[case::plain_hex_rejected(ScalarStyle::Plain, "0xFF", None, Err(UnresolvedScalar))]
768 #[case::plain_float_decimal(ScalarStyle::Plain, "1.5", None, Ok(Some(ResolvedTag::Float)))]
770 #[case::plain_float_inf_rejected(ScalarStyle::Plain, ".inf", None, Err(UnresolvedScalar))]
771 #[case::plain_float_nan_rejected(ScalarStyle::Plain, ".nan", None, Err(UnresolvedScalar))]
772 #[case::plain_float_plus_rejected(ScalarStyle::Plain, "+1.5", None, Err(UnresolvedScalar))]
773 #[case::plain_unmatched_rejected(ScalarStyle::Plain, "hello", None, Err(UnresolvedScalar))]
775 #[case::single_quoted_becomes_str(
777 ScalarStyle::SingleQuoted,
778 "null",
779 None,
780 Ok(Some(ResolvedTag::Str))
781 )]
782 #[case::double_quoted_becomes_str(
783 ScalarStyle::DoubleQuoted,
784 "true",
785 None,
786 Ok(Some(ResolvedTag::Str))
787 )]
788 #[case::literal_becomes_str(
789 ScalarStyle::Literal(Chomp::Clip),
790 "42",
791 None,
792 Ok(Some(ResolvedTag::Str))
793 )]
794 #[case::folded_becomes_str(
795 ScalarStyle::Folded(Chomp::Strip),
796 "null",
797 None,
798 Ok(Some(ResolvedTag::Str))
799 )]
800 fn resolve_scalar_json(
801 #[case] style: ScalarStyle,
802 #[case] value: &str,
803 #[case] source_tag: Option<&str>,
804 #[case] expected: Result<Option<ResolvedTag>, UnresolvedScalar>,
805 ) {
806 assert_eq!(
807 resolve_scalar(Schema::Json, style, value, source_tag),
808 expected
809 );
810 }
811
812 #[test]
813 fn resolve_scalar_json_explicit_tag_passthrough() {
814 let result = resolve_scalar(Schema::Json, ScalarStyle::Plain, "null", Some("!custom"));
815 assert_eq!(result, Ok(None));
816 }
817
818 #[test]
821 fn resolve_scalar_explicit_tag_returns_none_failsafe() {
822 assert_eq!(
823 resolve_scalar(
824 Schema::Failsafe,
825 ScalarStyle::Plain,
826 "null",
827 Some("anything")
828 ),
829 Ok(None)
830 );
831 }
832
833 #[test]
834 fn resolve_scalar_explicit_tag_returns_none_json() {
835 assert_eq!(
836 resolve_scalar(Schema::Json, ScalarStyle::Plain, "null", Some("anything")),
837 Ok(None)
838 );
839 }
840
841 #[test]
842 fn resolve_scalar_explicit_tag_returns_none_core() {
843 assert_eq!(
844 resolve_scalar(Schema::Core, ScalarStyle::Plain, "null", Some("anything")),
845 Ok(None)
846 );
847 }
848
849 #[rstest]
852 #[case::failsafe_sequence_no_tag(
853 Schema::Failsafe,
854 CollectionKind::Sequence,
855 None,
856 Some(ResolvedTag::Seq)
857 )]
858 #[case::failsafe_mapping_no_tag(
859 Schema::Failsafe,
860 CollectionKind::Mapping,
861 None,
862 Some(ResolvedTag::Map)
863 )]
864 #[case::json_sequence_no_tag(
865 Schema::Json,
866 CollectionKind::Sequence,
867 None,
868 Some(ResolvedTag::Seq)
869 )]
870 #[case::json_mapping_no_tag(
871 Schema::Json,
872 CollectionKind::Mapping,
873 None,
874 Some(ResolvedTag::Map)
875 )]
876 #[case::core_sequence_no_tag(
877 Schema::Core,
878 CollectionKind::Sequence,
879 None,
880 Some(ResolvedTag::Seq)
881 )]
882 #[case::core_mapping_no_tag(
883 Schema::Core,
884 CollectionKind::Mapping,
885 None,
886 Some(ResolvedTag::Map)
887 )]
888 #[case::failsafe_sequence_explicit_tag(
889 Schema::Failsafe,
890 CollectionKind::Sequence,
891 Some("!custom"),
892 None
893 )]
894 #[case::failsafe_mapping_explicit_tag(
895 Schema::Failsafe,
896 CollectionKind::Mapping,
897 Some("tag:yaml.org,2002:map"),
898 None
899 )]
900 #[case::core_sequence_explicit_tag(Schema::Core, CollectionKind::Sequence, Some("!seq"), None)]
901 #[case::json_mapping_explicit_tag(Schema::Json, CollectionKind::Mapping, Some("!map"), None)]
902 fn resolve_collection_dispatch(
903 #[case] schema: Schema,
904 #[case] kind: CollectionKind,
905 #[case] source_tag: Option<&str>,
906 #[case] expected: Option<ResolvedTag>,
907 ) {
908 assert_eq!(resolve_collection(schema, kind, source_tag), expected);
909 }
910}