#![forbid(unsafe_code)]
use facet::Facet;
use facet_format::{DeserializeError, FormatDeserializer};
use facet_format_suite::{CaseOutcome, CaseSpec, FormatSuite, all_cases};
use facet_yaml::{YamlParser, to_string};
use indoc::indoc;
use libtest_mimic::{Arguments, Failed, Trial};
use std::sync::Arc;
struct YamlSlice;
impl FormatSuite for YamlSlice {
type Error = DeserializeError;
fn format_name() -> &'static str {
"facet-yaml/slice"
}
fn highlight_language() -> Option<&'static str> {
Some("yaml")
}
fn deserialize<T>(input: &[u8]) -> Result<T, Self::Error>
where
T: Facet<'static> + core::fmt::Debug,
{
let input_str = std::str::from_utf8(input).expect("input should be valid UTF-8");
let mut parser = YamlParser::new(input_str);
let mut de = FormatDeserializer::new_owned(&mut parser);
de.deserialize_root::<T>()
}
fn serialize<T>(value: &T) -> Option<Result<Vec<u8>, String>>
where
for<'facet> T: Facet<'facet>,
T: core::fmt::Debug,
{
Some(
to_string(value)
.map(|s| s.into_bytes())
.map_err(|e| e.to_string()),
)
}
fn struct_single_field() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
name: facet
"#
))
}
fn sequence_numbers() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
- 1
- 2
- 3
"#
))
}
fn sequence_mixed_scalars() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
- -1
- 4.625
- null
- true
"#
))
}
fn struct_nested() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
id: 42
child:
code: alpha
active: true
tags:
- core
- json
"#
))
}
fn enum_complex() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
Label:
name: facet
level: 7
"#
))
}
fn attr_rename_field() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
userName: alice
age: 30
"#
))
}
fn attr_rename_all_camel() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
firstName: Jane
lastName: Doe
isActive: true
"#
))
}
fn attr_default_field() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
required: present
"#
))
}
fn attr_default_struct() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
count: 123
"#
))
.without_roundtrip("empty string serializes differently in YAML")
}
fn attr_default_function() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
name: hello
"#
))
}
fn option_none() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
name: test
"#
))
}
fn option_some() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
name: test
nickname: nick
"#
))
}
fn option_null() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
name: test
nickname: null
"#
))
.without_roundtrip("null serializes as missing field")
}
fn attr_skip_serializing() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
visible: shown
"#
))
}
fn attr_skip_serializing_if() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
name: test
"#
))
}
fn attr_skip() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
visible: data
"#
))
}
fn enum_internally_tagged() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
type: Circle
radius: 5.0
"#
))
}
fn enum_adjacently_tagged() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
t: Message
c: hello
"#
))
}
fn struct_flatten() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
name: point
x: 10
y: 20
"#
))
}
fn transparent_newtype() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
id: 42
name: alice
"#
))
}
fn deny_unknown_fields() -> CaseSpec {
CaseSpec::expect_error("foo: abc\nbar: 42\nbaz: true", "unknown field")
}
fn error_type_mismatch_string_to_int() -> CaseSpec {
CaseSpec::expect_error("value: not_a_number", "failed to parse")
}
fn error_type_mismatch_object_to_array() -> CaseSpec {
CaseSpec::expect_error("items:\n key: value", "got object, expected array")
}
fn error_missing_required_field() -> CaseSpec {
CaseSpec::expect_error("name: Alice\nage: 30", "missing field")
}
fn attr_alias() -> CaseSpec {
CaseSpec::from_str("old_name: value\ncount: 5")
.without_roundtrip("alias is only for deserialization, serializes as new_name")
}
fn attr_rename_vs_alias_precedence() -> CaseSpec {
CaseSpec::from_str("officialName: test\nid: 1")
}
fn attr_rename_all_kebab() -> CaseSpec {
CaseSpec::from_str("first-name: John\nlast-name: Doe\nuser-id: 42")
}
fn attr_rename_all_screaming() -> CaseSpec {
CaseSpec::from_str("API_KEY: secret-123\nMAX_RETRY_COUNT: 5")
}
fn attr_rename_unicode() -> CaseSpec {
CaseSpec::skip("unicode field rename needs proper emoji escaping")
}
fn attr_rename_special_chars() -> CaseSpec {
CaseSpec::skip("special char rename needs exact field matching")
}
fn proxy_container() -> CaseSpec {
CaseSpec::from_str("'42'")
}
fn proxy_field_level() -> CaseSpec {
CaseSpec::from_str("name: test\ncount: '100'")
}
fn proxy_validation_error() -> CaseSpec {
CaseSpec::expect_error("'not_a_number'", "invalid digit")
}
fn proxy_with_option() -> CaseSpec {
CaseSpec::from_str("name: test\ncount: '42'")
}
fn proxy_with_enum() -> CaseSpec {
CaseSpec::from_str("Value: '99'")
}
fn proxy_with_transparent() -> CaseSpec {
CaseSpec::from_str("'42'")
}
fn opaque_proxy() -> CaseSpec {
CaseSpec::from_str("value:\n inner: 42").with_partial_eq()
}
fn opaque_proxy_option() -> CaseSpec {
CaseSpec::from_str("value:\n inner: 99").with_partial_eq()
}
fn transparent_multilevel() -> CaseSpec {
CaseSpec::from_str("42")
}
fn transparent_option() -> CaseSpec {
CaseSpec::from_str("99")
}
fn transparent_nonzero() -> CaseSpec {
CaseSpec::from_str("42")
}
fn flatten_optional_some() -> CaseSpec {
CaseSpec::from_str("name: test\nversion: 1\nauthor: alice")
}
fn flatten_optional_none() -> CaseSpec {
CaseSpec::from_str("name: test")
}
fn flatten_overlapping_fields_error() -> CaseSpec {
CaseSpec::expect_error("field_a: a\nfield_b: b\nshared: 1", "Duplicate field")
}
fn flatten_multilevel() -> CaseSpec {
CaseSpec::from_str("top_field: top\nmid_field: 42\ndeep_field: 100")
}
fn flatten_multiple_enums() -> CaseSpec {
CaseSpec::from_str("name: service\nPassword:\n password: secret\nTcp:\n port: 8080")
}
fn scalar_bool() -> CaseSpec {
CaseSpec::from_str("yes: true\nno: false")
}
fn scalar_integers() -> CaseSpec {
CaseSpec::skip("large integers parsed as f64 by YAML parser")
}
fn scalar_floats() -> CaseSpec {
CaseSpec::from_str("float_32: 1.5\nfloat_64: 2.25")
}
fn map_string_keys() -> CaseSpec {
CaseSpec::from_str("data:\n alpha: 1\n beta: 2")
}
fn tuple_simple() -> CaseSpec {
CaseSpec::from_str("triple:\n - hello\n - 42\n - true")
}
fn tuple_nested() -> CaseSpec {
CaseSpec::skip("nested tuple serialization format differs")
}
fn tuple_empty() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
name: test
empty: []
"#
))
.without_roundtrip("empty tuple serialization format mismatch")
}
fn tuple_single_element() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
name: test
single:
- 42
"#
))
}
fn tuple_struct_variant() -> CaseSpec {
CaseSpec::from_str("Pair:\n - test\n - 42")
}
fn tuple_newtype_variant() -> CaseSpec {
CaseSpec::from_str("Some: 99")
}
fn enum_unit_variant() -> CaseSpec {
CaseSpec::from_str("Active")
}
fn enum_untagged() -> CaseSpec {
CaseSpec::from_str("x: 10\ny: 20")
}
fn enum_variant_rename() -> CaseSpec {
CaseSpec::from_str("enabled")
}
fn untagged_with_null() -> CaseSpec {
CaseSpec::from_str("null")
}
fn untagged_newtype_variant() -> CaseSpec {
CaseSpec::from_str("test")
}
fn untagged_as_field() -> CaseSpec {
CaseSpec::from_str("name: test\nvalue: 42")
}
fn untagged_unit_only() -> CaseSpec {
CaseSpec::from_str("Alpha")
}
fn box_wrapper() -> CaseSpec {
CaseSpec::from_str("inner: 42")
}
fn arc_wrapper() -> CaseSpec {
CaseSpec::from_str("inner: 42")
}
fn rc_wrapper() -> CaseSpec {
CaseSpec::from_str("inner: 42")
}
fn set_btree() -> CaseSpec {
CaseSpec::from_str("items:\n - alpha\n - beta\n - gamma")
}
fn scalar_integers_16() -> CaseSpec {
CaseSpec::from_str("signed_16: -32768\nunsigned_16: 65535")
}
fn scalar_integers_128() -> CaseSpec {
CaseSpec::skip("128-bit integers parsed as f64 by YAML parser")
}
fn scalar_integers_size() -> CaseSpec {
CaseSpec::from_str("signed_size: -1000\nunsigned_size: 2000")
}
fn nonzero_integers() -> CaseSpec {
CaseSpec::from_str("nz_u32: 42\nnz_i64: -100")
}
fn cow_str() -> CaseSpec {
CaseSpec::from_str("owned: hello world\nmessage: borrowed")
}
fn bytes_vec_u8() -> CaseSpec {
CaseSpec::from_str("data:\n - 0\n - 128\n - 255\n - 42")
}
fn array_fixed_size() -> CaseSpec {
CaseSpec::from_str("values:\n - 1\n - 2\n - 3")
}
fn skip_unknown_fields() -> CaseSpec {
CaseSpec::from_str("unknown: ignored\nknown: value")
.without_roundtrip("unknown field is not preserved")
}
fn string_escapes() -> CaseSpec {
CaseSpec::from_str("text: \"line1\\nline2\\ttab\\\"quote\\\\backslash\"")
}
fn unit_struct() -> CaseSpec {
CaseSpec::from_str("{}")
}
fn newtype_u64() -> CaseSpec {
CaseSpec::from_str("value: 42")
}
fn newtype_string() -> CaseSpec {
CaseSpec::from_str("value: hello")
}
fn char_scalar() -> CaseSpec {
CaseSpec::from_str("letter: A\nemoji: \"\u{1F980}\"")
}
fn hashset() -> CaseSpec {
CaseSpec::from_str("items:\n - alpha\n - beta")
}
fn vec_nested() -> CaseSpec {
CaseSpec::skip("nested Vec serialization format differs")
}
fn uuid() -> CaseSpec {
CaseSpec::from_str("id: 550e8400-e29b-41d4-a716-446655440000")
}
fn ulid() -> CaseSpec {
CaseSpec::from_str("id: 01ARZ3NDEKTSV4RRFFQ69G5FAV")
}
fn camino_path() -> CaseSpec {
CaseSpec::from_str("path: /home/user/documents")
}
fn ordered_float() -> CaseSpec {
CaseSpec::from_str("value: 1.23456")
}
fn rust_decimal() -> CaseSpec {
CaseSpec::from_str("amount: '24.99'")
}
fn scalar_floats_scientific() -> CaseSpec {
CaseSpec::from_str("large: 1.23e10\nsmall: -4.56e-7\npositive_exp: 5e3")
}
fn string_escapes_extended() -> CaseSpec {
CaseSpec::from_str(
"backspace: \"hello\\bworld\"\nformfeed: \"page\\fbreak\"\ncarriage_return: \"line\\rreturn\"\ncontrol_char: \"\\x01\"",
)
}
fn box_str() -> CaseSpec {
CaseSpec::from_str("inner: hello world")
}
fn arc_str() -> CaseSpec {
CaseSpec::from_str("inner: hello world")
}
fn rc_str() -> CaseSpec {
CaseSpec::from_str("inner: hello world")
}
fn arc_slice() -> CaseSpec {
CaseSpec::from_str("inner:\n - 1\n - 2\n - 3\n - 4")
}
#[cfg(feature = "yoke")]
fn yoke_cow_str() -> CaseSpec {
CaseSpec::from_str("value: hello yoke").with_partial_eq()
}
#[cfg(feature = "yoke")]
fn yoke_custom() -> CaseSpec {
CaseSpec::from_str("value: hello|yoke").with_partial_eq()
}
fn nonzero_integers_extended() -> CaseSpec {
CaseSpec::from_str(
"nz_u8: 255\nnz_i8: -128\nnz_u16: 65535\nnz_i16: -32768\nnz_u128: 1\nnz_i128: -1\nnz_usize: 1000\nnz_isize: -500",
)
}
fn time_offset_datetime() -> CaseSpec {
CaseSpec::from_str("created_at: 2023-01-15T12:34:56Z")
}
fn jiff_timestamp() -> CaseSpec {
CaseSpec::from_str("created_at: 2023-12-31T11:30:00Z")
}
fn jiff_civil_datetime() -> CaseSpec {
CaseSpec::from_str("created_at: 2024-06-19T15:22:45")
}
fn jiff_civil_date() -> CaseSpec {
CaseSpec::from_str("date: 2024-06-19")
}
fn jiff_civil_time() -> CaseSpec {
CaseSpec::from_str("time: 15:22:45")
}
fn chrono_datetime_utc() -> CaseSpec {
CaseSpec::from_str("created_at: 2023-01-15T12:34:56Z")
}
fn chrono_naive_datetime() -> CaseSpec {
CaseSpec::from_str("created_at: 2023-01-15T12:34:56")
}
fn chrono_naive_date() -> CaseSpec {
CaseSpec::from_str("birth_date: 2023-01-15")
}
fn chrono_naive_time() -> CaseSpec {
CaseSpec::from_str("alarm_time: 12:34:56")
}
fn chrono_in_vec() -> CaseSpec {
CaseSpec::from_str("timestamps:\n - 2023-01-01T00:00:00Z\n - 2023-06-15T12:30:00Z")
}
fn chrono_duration() -> CaseSpec {
CaseSpec::from_str("duration:\n - 3600\n - 500000000")
}
fn chrono_duration_negative() -> CaseSpec {
CaseSpec::from_str("duration:\n - -90\n - -250000000")
}
fn std_duration() -> CaseSpec {
CaseSpec::from_str("duration:\n - 3600\n - 500000000")
}
fn bytes_bytes() -> CaseSpec {
CaseSpec::from_str("data:\n - 1\n - 2\n - 3\n - 4\n - 255")
}
fn bytes_bytes_mut() -> CaseSpec {
CaseSpec::from_str("data:\n - 1\n - 2\n - 3\n - 4\n - 255")
}
fn bytestring() -> CaseSpec {
CaseSpec::from_str("value: hello world")
}
fn compact_string() -> CaseSpec {
CaseSpec::from_str("value: hello world")
}
fn smartstring() -> CaseSpec {
CaseSpec::from_str("value: hello world")
}
fn smol_str() -> CaseSpec {
CaseSpec::from_str("value: hello world")
}
fn iddqd_id_hash_map() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
items:
- id: 1
name: Alice
"#
))
}
fn iddqd_id_ord_map() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
items:
- id: 1
name: Alice
"#
))
}
fn iddqd_bi_hash_map() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
items:
- id: 1
code: A001
name: Alice
"#
))
}
fn iddqd_tri_hash_map() -> CaseSpec {
CaseSpec::from_str(indoc!(
r#"
items:
- id: 1
code: A001
email: alice@example.com
name: Alice
"#
))
}
fn value_null() -> CaseSpec {
CaseSpec::from_str("null")
}
fn value_bool() -> CaseSpec {
CaseSpec::from_str("true")
}
fn value_integer() -> CaseSpec {
CaseSpec::from_str("42")
}
fn value_float() -> CaseSpec {
CaseSpec::from_str("2.5")
}
fn value_string() -> CaseSpec {
CaseSpec::from_str("hello world")
}
fn value_array() -> CaseSpec {
CaseSpec::from_str("- 1\n- 2\n- 3")
}
fn value_object() -> CaseSpec {
CaseSpec::from_str("name: test\ncount: 42")
}
fn numeric_enum() -> CaseSpec {
CaseSpec::from_str("1")
}
fn signed_numeric_enum() -> CaseSpec {
CaseSpec::from_str("-1")
}
fn inferred_numeric_enum() -> CaseSpec {
CaseSpec::from_str("'0'")
}
fn net_ip_addr_v4() -> CaseSpec {
CaseSpec::from_str("addr: 192.168.1.1")
}
fn net_ip_addr_v6() -> CaseSpec {
CaseSpec::from_str("addr: 2001:db8::1")
}
fn net_ipv4_addr() -> CaseSpec {
CaseSpec::from_str("addr: 127.0.0.1")
}
fn net_ipv6_addr() -> CaseSpec {
CaseSpec::from_str("addr: \"::1\"")
}
fn net_socket_addr_v4() -> CaseSpec {
CaseSpec::from_str("addr: 192.168.1.1:8080")
}
fn net_socket_addr_v6() -> CaseSpec {
CaseSpec::from_str("addr: \"[2001:db8::1]:443\"")
}
fn net_socket_addr_v4_explicit() -> CaseSpec {
CaseSpec::from_str("addr: 10.0.0.1:3000")
}
fn net_socket_addr_v6_explicit() -> CaseSpec {
CaseSpec::from_str("addr: \"[fe80::1]:9000\"")
}
}
fn main() {
let args = Arguments::from_args();
let trials: Vec<Trial> = all_cases::<YamlSlice>()
.into_iter()
.map(|case| {
let case = Arc::new(case);
let name = format!("{}::{}", YamlSlice::format_name(), case.id);
let skip_reason = case.skip_reason();
let case_clone = Arc::clone(&case);
let mut trial = Trial::test(name, move || match case_clone.run() {
CaseOutcome::Passed => Ok(()),
CaseOutcome::Skipped(_) => Ok(()),
CaseOutcome::Failed(msg) => Err(Failed::from(msg)),
});
if skip_reason.is_some() {
trial = trial.with_ignored_flag(true);
}
trial
})
.collect();
libtest_mimic::run(&args, trials).exit()
}