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
94#[inline]
119pub fn resolve_scalar(
120 schema: Schema,
121 style: ScalarStyle,
122 value: &str,
123 source_tag: Option<&str>,
124) -> Result<Option<ResolvedTag>, UnresolvedScalar> {
125 if source_tag.is_some() {
127 return Ok(None);
128 }
129
130 match schema {
131 Schema::Failsafe => Ok(Some(ResolvedTag::Str)),
132
133 Schema::Core => {
134 let tag = match style {
135 ScalarStyle::Plain => resolve_core_plain(value),
136 ScalarStyle::SingleQuoted
138 | ScalarStyle::DoubleQuoted
139 | ScalarStyle::Literal(_)
140 | ScalarStyle::Folded(_) => ResolvedTag::Str,
141 };
142 Ok(Some(tag))
143 }
144
145 Schema::Json => {
146 let tag = match style {
147 ScalarStyle::Plain => resolve_json_plain(value)?,
148 ScalarStyle::SingleQuoted
150 | ScalarStyle::DoubleQuoted
151 | ScalarStyle::Literal(_)
152 | ScalarStyle::Folded(_) => ResolvedTag::Str,
153 };
154 Ok(Some(tag))
155 }
156 }
157}
158
159#[must_use]
168pub const fn resolve_collection(
169 schema: Schema,
170 kind: CollectionKind,
171 source_tag: Option<&str>,
172) -> Option<ResolvedTag> {
173 if source_tag.is_some() {
175 return None;
176 }
177 let _ = schema;
179 Some(match kind {
180 CollectionKind::Sequence => ResolvedTag::Seq,
181 CollectionKind::Mapping => ResolvedTag::Map,
182 })
183}
184
185#[inline]
195fn resolve_core_plain(value: &str) -> ResolvedTag {
196 match value.as_bytes().first().copied() {
197 None | Some(b'~') => ResolvedTag::Null,
199 Some(b'n' | b'N') => {
201 if is_core_null(value) {
202 ResolvedTag::Null
203 } else {
204 ResolvedTag::Str
205 }
206 }
207 Some(b't' | b'T' | b'f' | b'F') => {
209 if is_core_bool(value) {
210 ResolvedTag::Bool
211 } else {
212 ResolvedTag::Str
213 }
214 }
215 Some(b'-' | b'+' | b'0'..=b'9') => {
217 if is_core_int(value) {
218 ResolvedTag::Int
219 } else if is_core_float(value) {
220 ResolvedTag::Float
221 } else {
222 ResolvedTag::Str
223 }
224 }
225 Some(b'.') => {
227 if is_core_float(value) {
228 ResolvedTag::Float
229 } else {
230 ResolvedTag::Str
231 }
232 }
233 Some(_) => ResolvedTag::Str,
235 }
236}
237
238fn resolve_json_plain(value: &str) -> Result<ResolvedTag, UnresolvedScalar> {
253 if is_json_null(value) {
254 Ok(ResolvedTag::Null)
255 } else if is_json_bool(value) {
256 Ok(ResolvedTag::Bool)
257 } else if is_json_int(value) {
258 Ok(ResolvedTag::Int)
259 } else if is_json_float(value) {
260 Ok(ResolvedTag::Float)
261 } else {
262 Err(UnresolvedScalar)
263 }
264}
265
266#[must_use]
272pub fn is_core_null(value: &str) -> bool {
273 matches!(value, "null" | "Null" | "NULL" | "~" | "")
274}
275
276#[must_use]
278pub fn is_core_bool(value: &str) -> bool {
279 matches!(
280 value,
281 "true" | "True" | "TRUE" | "false" | "False" | "FALSE"
282 )
283}
284
285#[must_use]
288pub fn is_core_int(value: &str) -> bool {
289 let rest = value
291 .strip_prefix('-')
292 .or_else(|| value.strip_prefix('+'))
293 .unwrap_or(value);
294
295 if rest.is_empty() {
296 return false;
297 }
298
299 if let Some(oct) = rest.strip_prefix("0o") {
300 !oct.is_empty() && oct.bytes().all(|b| matches!(b, b'0'..=b'7'))
302 } else if let Some(hex) = rest.strip_prefix("0x") {
303 !hex.is_empty() && hex.bytes().all(|b| b.is_ascii_hexdigit())
305 } else {
306 if rest.len() > 1 && rest.starts_with('0') {
308 return false;
309 }
310 rest.bytes().all(|b| b.is_ascii_digit())
311 }
312}
313
314#[must_use]
318pub fn is_core_float(value: &str) -> bool {
319 if matches!(value, ".nan" | ".NaN" | ".NAN") {
321 return true;
322 }
323
324 let unsigned = value
326 .strip_prefix('-')
327 .or_else(|| value.strip_prefix('+'))
328 .unwrap_or(value);
329
330 if matches!(unsigned, ".inf" | ".Inf" | ".INF") {
332 return true;
333 }
334
335 is_core_decimal_float(unsigned)
337}
338
339fn is_core_decimal_float(s: &str) -> bool {
342 let (mantissa, exp_part) = split_exponent(s);
344
345 if exp_part.is_some_and(|exp| !is_valid_exponent_digits(exp)) {
347 return false;
348 }
349
350 if let Some(after_dot) = mantissa.strip_prefix('.') {
354 !after_dot.is_empty() && after_dot.bytes().all(|b| b.is_ascii_digit())
356 } else {
357 let (int_part, frac) = mantissa.find('.').map_or((mantissa, None), |pos| {
359 (&mantissa[..pos], Some(&mantissa[pos + 1..]))
360 });
361 if int_part.is_empty() || !int_part.bytes().all(|b| b.is_ascii_digit()) {
362 return false;
363 }
364 if let Some(frac_digits) = frac {
366 if !frac_digits.bytes().all(|b| b.is_ascii_digit()) {
367 return false;
368 }
369 } else {
370 if exp_part.is_none() {
373 return false;
374 }
375 }
376 true
377 }
378}
379
380fn split_exponent(s: &str) -> (&str, Option<&str>) {
383 s.find(['e', 'E'])
384 .map_or((s, None), |pos| (&s[..pos], Some(&s[pos + 1..])))
385}
386
387fn is_valid_exponent_digits(exp: &str) -> bool {
389 let digits = exp.strip_prefix(['-', '+']).unwrap_or(exp);
390 !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit())
391}
392
393#[must_use]
399pub fn is_json_null(value: &str) -> bool {
400 value == "null"
401}
402
403#[must_use]
405pub fn is_json_bool(value: &str) -> bool {
406 matches!(value, "true" | "false")
407}
408
409#[must_use]
413pub fn is_json_int(value: &str) -> bool {
414 if value == "0" {
415 return true;
416 }
417 let rest = value.strip_prefix('-').unwrap_or(value);
419 let mut bytes = rest.bytes();
420 match bytes.next() {
421 Some(b'1'..=b'9') => {}
423 _ => return false,
424 }
425 bytes.all(|b| b.is_ascii_digit())
426}
427
428#[must_use]
432pub fn is_json_float(value: &str) -> bool {
433 let unsigned = value.strip_prefix('-').unwrap_or(value);
435
436 let after_int = if let Some(rest) = unsigned.strip_prefix('0') {
438 rest
439 } else {
440 let mut bytes = unsigned.bytes();
441 match bytes.next() {
442 Some(b'1'..=b'9') => {}
443 _ => return false,
444 }
445 let consumed = 1 + bytes.take_while(u8::is_ascii_digit).count();
446 &unsigned[consumed..]
447 };
448
449 let after_frac = after_int.strip_prefix('.').map_or(after_int, |rest| {
451 let digits = rest.bytes().take_while(u8::is_ascii_digit).count();
452 &rest[digits..]
453 });
454
455 let after_exp = if let Some(exp_rest) = after_frac
457 .strip_prefix('e')
458 .or_else(|| after_frac.strip_prefix('E'))
459 {
460 let digits_start = exp_rest.strip_prefix(['-', '+']).unwrap_or(exp_rest);
461 if digits_start.is_empty() || !digits_start.bytes().all(|b| b.is_ascii_digit()) {
462 return false;
463 }
464 ""
465 } else {
466 after_frac
467 };
468
469 after_exp.is_empty()
471}
472
473#[cfg(test)]
478mod tests {
479 use super::*;
480 use crate::event::Chomp;
481 use rstest::rstest;
482
483 #[rstest]
486 #[case::str_tag(ResolvedTag::Str, "tag:yaml.org,2002:str")]
487 #[case::int_tag(ResolvedTag::Int, "tag:yaml.org,2002:int")]
488 #[case::float_tag(ResolvedTag::Float, "tag:yaml.org,2002:float")]
489 #[case::bool_tag(ResolvedTag::Bool, "tag:yaml.org,2002:bool")]
490 #[case::null_tag(ResolvedTag::Null, "tag:yaml.org,2002:null")]
491 #[case::seq_tag(ResolvedTag::Seq, "tag:yaml.org,2002:seq")]
492 #[case::map_tag(ResolvedTag::Map, "tag:yaml.org,2002:map")]
493 fn resolved_tag_as_str_returns_uri(#[case] tag: ResolvedTag, #[case] expected: &str) {
494 assert_eq!(tag.as_str(), expected);
495 }
496
497 #[rstest]
502 #[case::null_lowercase("null")]
503 #[case::null_titlecase("Null")]
504 #[case::null_uppercase("NULL")]
505 #[case::tilde("~")]
506 #[case::empty("")]
507 fn is_core_null_returns_true(#[case] input: &str) {
508 assert!(is_core_null(input));
509 }
510
511 #[rstest]
514 #[case::none_string("none")]
515 #[case::nil_string("nil")]
516 #[case::mixed_case_null("nUll")]
517 #[case::single_space(" ")]
518 #[case::json_null_inside_word("nullX")]
519 fn is_core_null_returns_false(#[case] input: &str) {
520 assert!(!is_core_null(input));
521 }
522
523 #[rstest]
526 #[case::true_lowercase("true")]
527 #[case::true_titlecase("True")]
528 #[case::true_uppercase("TRUE")]
529 #[case::false_lowercase("false")]
530 #[case::false_titlecase("False")]
531 #[case::false_uppercase("FALSE")]
532 fn is_core_bool_returns_true(#[case] input: &str) {
533 assert!(is_core_bool(input));
534 }
535
536 #[rstest]
539 #[case::yaml11_yes("yes")]
540 #[case::yaml11_no("no")]
541 #[case::yaml11_on("on")]
542 #[case::yaml11_off("off")]
543 #[case::mixed_case_true("tRue")]
544 #[case::integer_one("1")]
545 #[case::integer_zero("0")]
546 fn is_core_bool_returns_false(#[case] input: &str) {
547 assert!(!is_core_bool(input));
548 }
549
550 #[rstest]
553 #[case::decimal_zero("0")]
554 #[case::decimal_positive("42")]
555 #[case::decimal_negative("-1")]
556 #[case::decimal_plus_prefix("+100")]
557 #[case::octal("0o17")]
558 #[case::octal_negative("-0o10")]
559 #[case::hex_lower("0xff")]
560 #[case::hex_upper("0xFF")]
561 #[case::hex_negative("-0x1A")]
562 fn is_core_int_returns_true(#[case] input: &str) {
563 assert!(is_core_int(input));
564 }
565
566 #[rstest]
569 #[case::leading_zeros("007")]
570 #[case::empty("")]
571 #[case::sign_only_plus("+")]
572 #[case::sign_only_minus("-")]
573 #[case::float_with_dot("3.14")]
574 #[case::float_exp("1e5")]
575 #[case::octal_prefix_only("0o")]
576 #[case::hex_prefix_only("0x")]
577 #[case::alpha_string("abc")]
578 fn is_core_int_returns_false(#[case] input: &str) {
579 assert!(!is_core_int(input));
580 }
581
582 #[rstest]
585 #[case::decimal_dot("3.14")]
586 #[case::decimal_no_integer_part(".5")]
587 #[case::exponent_only("1e10")]
588 #[case::exponent_negative("1.5E-3")]
589 #[case::positive_signed_float("+1.0")]
590 #[case::negative_float("-0.5")]
591 #[case::inf_lowercase(".inf")]
592 #[case::inf_titlecase(".Inf")]
593 #[case::inf_uppercase(".INF")]
594 #[case::neg_inf_lowercase("-.inf")]
595 #[case::neg_inf_titlecase("-.Inf")]
596 #[case::neg_inf_uppercase("-.INF")]
597 #[case::pos_inf("+.inf")]
598 #[case::nan_lowercase(".nan")]
599 #[case::nan_titlecase(".NaN")]
600 #[case::nan_uppercase(".NAN")]
601 fn is_core_float_returns_true(#[case] input: &str) {
602 assert!(is_core_float(input));
603 }
604
605 #[rstest]
608 #[case::bare_integer("42")]
609 #[case::empty("")]
610 #[case::bare_inf_no_dot("inf")]
611 #[case::bare_nan_no_dot("nan")]
612 #[case::sign_only("+")]
613 #[case::dot_only(".")]
614 fn is_core_float_returns_false(#[case] input: &str) {
615 assert!(!is_core_float(input));
616 }
617
618 #[test]
623 fn is_json_null_returns_true() {
624 assert!(is_json_null("null"));
625 }
626
627 #[rstest]
628 #[case::null_titlecase("Null")]
629 #[case::null_uppercase("NULL")]
630 #[case::tilde("~")]
631 #[case::empty("")]
632 fn is_json_null_returns_false(#[case] input: &str) {
633 assert!(!is_json_null(input));
634 }
635
636 #[rstest]
639 #[case::true_lowercase("true")]
640 #[case::false_lowercase("false")]
641 fn is_json_bool_returns_true(#[case] input: &str) {
642 assert!(is_json_bool(input));
643 }
644
645 #[rstest]
646 #[case::true_titlecase("True")]
647 #[case::true_uppercase("TRUE")]
648 #[case::false_titlecase("False")]
649 #[case::false_uppercase("FALSE")]
650 fn is_json_bool_returns_false(#[case] input: &str) {
651 assert!(!is_json_bool(input));
652 }
653
654 #[rstest]
657 #[case::zero("0")]
658 #[case::positive_decimal("42")]
659 #[case::negative_decimal("-1")]
660 #[case::negative_multi("-100")]
661 #[case::large_negative("-9999")]
662 fn is_json_int_returns_true(#[case] input: &str) {
663 assert!(is_json_int(input));
664 }
665
666 #[rstest]
667 #[case::plus_prefix("+42")]
668 #[case::plus_zero("+0")]
669 #[case::minus_zero("-0")]
670 #[case::leading_zeros("007")]
671 #[case::octal("0o17")]
672 #[case::hex("0xFF")]
673 #[case::empty("")]
674 #[case::sign_only_plus("+")]
675 #[case::sign_only_minus("-")]
676 fn is_json_int_returns_false(#[case] input: &str) {
677 assert!(!is_json_int(input));
678 }
679
680 #[rstest]
683 #[case::zero_float_simple("0.5")]
684 #[case::negative_with_decimal("-1.5")]
685 #[case::with_exponent("1e10")]
686 #[case::with_negative_exponent("-1.5e-3")]
687 #[case::minus_zero("-0")]
689 #[case::zero_alone("0")]
691 fn is_json_float_returns_true(#[case] input: &str) {
692 assert!(is_json_float(input));
693 }
694
695 #[rstest]
696 #[case::plus_prefix("+1.5")]
697 #[case::inf_dot(".inf")]
698 #[case::nan_dot(".nan")]
699 #[case::leading_dot(".5")]
700 #[case::empty("")]
701 #[case::sign_only("-")]
702 fn is_json_float_returns_false(#[case] input: &str) {
703 assert!(!is_json_float(input));
704 }
705
706 #[rstest]
711 #[case::plain_null(ScalarStyle::Plain, "null", None)]
712 #[case::single_quoted_true(ScalarStyle::SingleQuoted, "true", None)]
713 #[case::double_quoted_int(ScalarStyle::DoubleQuoted, "42", None)]
714 #[case::literal_block(ScalarStyle::Literal(Chomp::Clip), "hello", None)]
715 #[case::folded_block(ScalarStyle::Folded(Chomp::Strip), "world", None)]
716 fn resolve_scalar_failsafe_always_str(
717 #[case] style: ScalarStyle,
718 #[case] value: &str,
719 #[case] source_tag: Option<&str>,
720 ) {
721 assert_eq!(
722 resolve_scalar(Schema::Failsafe, style, value, source_tag),
723 Ok(Some(ResolvedTag::Str))
724 );
725 }
726
727 #[test]
728 fn resolve_scalar_failsafe_explicit_tag_passthrough() {
729 let result = resolve_scalar(
730 Schema::Failsafe,
731 ScalarStyle::Plain,
732 "null",
733 Some("tag:yaml.org,2002:str"),
734 );
735 assert_eq!(result, Ok(None));
736 }
737
738 #[rstest]
741 #[case::plain_null_lowercase(ScalarStyle::Plain, "null", None, ResolvedTag::Null)]
742 #[case::plain_null_tilde(ScalarStyle::Plain, "~", None, ResolvedTag::Null)]
743 #[case::plain_null_empty(ScalarStyle::Plain, "", None, ResolvedTag::Null)]
744 #[case::plain_bool_true_lower(ScalarStyle::Plain, "true", None, ResolvedTag::Bool)]
745 #[case::plain_bool_false_upper(ScalarStyle::Plain, "FALSE", None, ResolvedTag::Bool)]
746 #[case::plain_int_decimal(ScalarStyle::Plain, "42", None, ResolvedTag::Int)]
747 #[case::plain_int_octal(ScalarStyle::Plain, "0o17", None, ResolvedTag::Int)]
748 #[case::plain_int_hex(ScalarStyle::Plain, "0xFF", None, ResolvedTag::Int)]
749 #[case::plain_float_decimal(ScalarStyle::Plain, "3.14", None, ResolvedTag::Float)]
750 #[case::plain_float_inf(ScalarStyle::Plain, ".inf", None, ResolvedTag::Float)]
751 #[case::plain_float_nan(ScalarStyle::Plain, ".nan", None, ResolvedTag::Float)]
752 #[case::plain_unmatched_str(ScalarStyle::Plain, "hello", None, ResolvedTag::Str)]
753 #[case::plain_leading_zeros(ScalarStyle::Plain, "007", None, ResolvedTag::Str)]
754 #[case::single_quoted_null(ScalarStyle::SingleQuoted, "null", None, ResolvedTag::Str)]
755 #[case::double_quoted_true(ScalarStyle::DoubleQuoted, "true", None, ResolvedTag::Str)]
756 #[case::literal_any(ScalarStyle::Literal(Chomp::Clip), "42", None, ResolvedTag::Str)]
757 #[case::folded_any(ScalarStyle::Folded(Chomp::Keep), "null", None, ResolvedTag::Str)]
758 fn resolve_scalar_core(
759 #[case] style: ScalarStyle,
760 #[case] value: &str,
761 #[case] source_tag: Option<&str>,
762 #[case] expected: ResolvedTag,
763 ) {
764 assert_eq!(
765 resolve_scalar(Schema::Core, style, value, source_tag),
766 Ok(Some(expected))
767 );
768 }
769
770 #[test]
771 fn resolve_scalar_core_explicit_tag_passthrough() {
772 let result = resolve_scalar(
773 Schema::Core,
774 ScalarStyle::Plain,
775 "null",
776 Some("tag:yaml.org,2002:int"),
777 );
778 assert_eq!(result, Ok(None));
779 }
780
781 #[rstest]
784 #[case::plain_null_lowercase(ScalarStyle::Plain, "null", None, Ok(Some(ResolvedTag::Null)))]
786 #[case::plain_null_tilde_rejected(ScalarStyle::Plain, "~", None, Err(UnresolvedScalar))]
788 #[case::plain_empty_rejected(ScalarStyle::Plain, "", None, Err(UnresolvedScalar))]
789 #[case::plain_bool_true_lower(ScalarStyle::Plain, "true", None, Ok(Some(ResolvedTag::Bool)))]
791 #[case::plain_bool_true_upper_rejected(ScalarStyle::Plain, "TRUE", None, Err(UnresolvedScalar))]
792 #[case::plain_int_decimal(ScalarStyle::Plain, "42", None, Ok(Some(ResolvedTag::Int)))]
794 #[case::plain_int_zero(ScalarStyle::Plain, "0", None, Ok(Some(ResolvedTag::Int)))]
795 #[case::plain_int_negative(ScalarStyle::Plain, "-1", None, Ok(Some(ResolvedTag::Int)))]
796 #[case::plain_int_plus_rejected(ScalarStyle::Plain, "+42", None, Err(UnresolvedScalar))]
797 #[case::plain_minus_zero_is_float(ScalarStyle::Plain, "-0", None, Ok(Some(ResolvedTag::Float)))]
799 #[case::plain_octal_rejected(ScalarStyle::Plain, "0o17", None, Err(UnresolvedScalar))]
800 #[case::plain_hex_rejected(ScalarStyle::Plain, "0xFF", None, Err(UnresolvedScalar))]
801 #[case::plain_float_decimal(ScalarStyle::Plain, "1.5", None, Ok(Some(ResolvedTag::Float)))]
803 #[case::plain_float_inf_rejected(ScalarStyle::Plain, ".inf", None, Err(UnresolvedScalar))]
804 #[case::plain_float_nan_rejected(ScalarStyle::Plain, ".nan", None, Err(UnresolvedScalar))]
805 #[case::plain_float_plus_rejected(ScalarStyle::Plain, "+1.5", None, Err(UnresolvedScalar))]
806 #[case::plain_unmatched_rejected(ScalarStyle::Plain, "hello", None, Err(UnresolvedScalar))]
808 #[case::single_quoted_becomes_str(
810 ScalarStyle::SingleQuoted,
811 "null",
812 None,
813 Ok(Some(ResolvedTag::Str))
814 )]
815 #[case::double_quoted_becomes_str(
816 ScalarStyle::DoubleQuoted,
817 "true",
818 None,
819 Ok(Some(ResolvedTag::Str))
820 )]
821 #[case::literal_becomes_str(
822 ScalarStyle::Literal(Chomp::Clip),
823 "42",
824 None,
825 Ok(Some(ResolvedTag::Str))
826 )]
827 #[case::folded_becomes_str(
828 ScalarStyle::Folded(Chomp::Strip),
829 "null",
830 None,
831 Ok(Some(ResolvedTag::Str))
832 )]
833 fn resolve_scalar_json(
834 #[case] style: ScalarStyle,
835 #[case] value: &str,
836 #[case] source_tag: Option<&str>,
837 #[case] expected: Result<Option<ResolvedTag>, UnresolvedScalar>,
838 ) {
839 assert_eq!(
840 resolve_scalar(Schema::Json, style, value, source_tag),
841 expected
842 );
843 }
844
845 #[test]
846 fn resolve_scalar_json_explicit_tag_passthrough() {
847 let result = resolve_scalar(Schema::Json, ScalarStyle::Plain, "null", Some("!custom"));
848 assert_eq!(result, Ok(None));
849 }
850
851 #[test]
854 fn resolve_scalar_explicit_tag_returns_none_failsafe() {
855 assert_eq!(
856 resolve_scalar(
857 Schema::Failsafe,
858 ScalarStyle::Plain,
859 "null",
860 Some("anything")
861 ),
862 Ok(None)
863 );
864 }
865
866 #[test]
867 fn resolve_scalar_explicit_tag_returns_none_json() {
868 assert_eq!(
869 resolve_scalar(Schema::Json, ScalarStyle::Plain, "null", Some("anything")),
870 Ok(None)
871 );
872 }
873
874 #[test]
875 fn resolve_scalar_explicit_tag_returns_none_core() {
876 assert_eq!(
877 resolve_scalar(Schema::Core, ScalarStyle::Plain, "null", Some("anything")),
878 Ok(None)
879 );
880 }
881
882 #[rstest]
885 #[case::failsafe_sequence_no_tag(
886 Schema::Failsafe,
887 CollectionKind::Sequence,
888 None,
889 Some(ResolvedTag::Seq)
890 )]
891 #[case::failsafe_mapping_no_tag(
892 Schema::Failsafe,
893 CollectionKind::Mapping,
894 None,
895 Some(ResolvedTag::Map)
896 )]
897 #[case::json_sequence_no_tag(
898 Schema::Json,
899 CollectionKind::Sequence,
900 None,
901 Some(ResolvedTag::Seq)
902 )]
903 #[case::json_mapping_no_tag(
904 Schema::Json,
905 CollectionKind::Mapping,
906 None,
907 Some(ResolvedTag::Map)
908 )]
909 #[case::core_sequence_no_tag(
910 Schema::Core,
911 CollectionKind::Sequence,
912 None,
913 Some(ResolvedTag::Seq)
914 )]
915 #[case::core_mapping_no_tag(
916 Schema::Core,
917 CollectionKind::Mapping,
918 None,
919 Some(ResolvedTag::Map)
920 )]
921 #[case::failsafe_sequence_explicit_tag(
922 Schema::Failsafe,
923 CollectionKind::Sequence,
924 Some("!custom"),
925 None
926 )]
927 #[case::failsafe_mapping_explicit_tag(
928 Schema::Failsafe,
929 CollectionKind::Mapping,
930 Some("tag:yaml.org,2002:map"),
931 None
932 )]
933 #[case::core_sequence_explicit_tag(Schema::Core, CollectionKind::Sequence, Some("!seq"), None)]
934 #[case::json_mapping_explicit_tag(Schema::Json, CollectionKind::Mapping, Some("!map"), None)]
935 fn resolve_collection_dispatch(
936 #[case] schema: Schema,
937 #[case] kind: CollectionKind,
938 #[case] source_tag: Option<&str>,
939 #[case] expected: Option<ResolvedTag>,
940 ) {
941 assert_eq!(resolve_collection(schema, kind, source_tag), expected);
942 }
943}