diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/serde_with/tests | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/serde_with/tests')
23 files changed, 5800 insertions, 0 deletions
diff --git a/third_party/rust/serde_with/tests/base64.rs b/third_party/rust/serde_with/tests/base64.rs new file mode 100644 index 0000000000..5b84c0c6f0 --- /dev/null +++ b/third_party/rust/serde_with/tests/base64.rs @@ -0,0 +1,144 @@ +#![allow( + // clippy is broken and shows wrong warnings + // clippy on stable does not know yet about the lint name + unknown_lints, + // https://github.com/rust-lang/rust-clippy/issues/8867 + clippy::derive_partial_eq_without_eq, + // This allows the tests to be written more uniform and not have to special case the last clone(). + clippy::redundant_clone, +)] + +mod utils; + +use crate::utils::{check_deserialization, check_error_deserialization, is_equal}; +use expect_test::expect; +use serde::{Deserialize, Serialize}; +use serde_with::{ + base64::{Base64, Bcrypt, BinHex, Crypt, ImapMutf7, Standard, UrlSafe}, + formats::{Padded, Unpadded}, + serde_as, +}; + +#[test] +fn base64_vec() { + let check_equal = vec![vec![0, 1, 2, 13], vec![14, 5, 6, 7]]; + let check_deser = vec![vec![0xaa, 0xbc, 0xff], vec![0xe0, 0x7d], vec![0xe0, 0x7d]]; + let check_deser_from = r#"["qrz/","4H0=","4H0"]"#; + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct BDefault(#[serde_as(as = "Vec<Base64>")] Vec<Vec<u8>>); + + is_equal( + BDefault(check_equal.clone()), + expect![[r#" + [ + "AAECDQ==", + "DgUGBw==" + ]"#]], + ); + + // Check mixed padding deserialization + check_deserialization(BDefault(check_deser.clone()), check_deser_from); + + check_error_deserialization::<BDefault>( + r#"["0"]"#, + expect![[r#"Encoded text cannot have a 6-bit remainder. at line 1 column 5"#]], + ); + check_error_deserialization::<BDefault>( + r#"["zz"]"#, + expect![[r#"Invalid last symbol 122, offset 1. at line 1 column 6"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct BPadded(#[serde_as(as = "Vec<Base64<Standard, Padded>>")] Vec<Vec<u8>>); + + is_equal( + BPadded(check_equal.clone()), + expect![[r#" + [ + "AAECDQ==", + "DgUGBw==" + ]"#]], + ); + check_deserialization(BPadded(check_deser.clone()), check_deser_from); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct BUnpadded(#[serde_as(as = "Vec<Base64<Standard, Unpadded>>")] Vec<Vec<u8>>); + + is_equal( + BUnpadded(check_equal.clone()), + expect![[r#" + [ + "AAECDQ", + "DgUGBw" + ]"#]], + ); + check_deserialization(BUnpadded(check_deser.clone()), check_deser_from); +} + +#[test] +fn base64_different_charsets() { + let bytes = [ + 0x69_u8, 0xb7, 0x1d, 0x79, 0xf8, 0x21, 0x8a, 0x39, 0x25, 0x9a, 0x7a, 0x29, 0xaa, 0xbb, + 0x2d, 0xba, 0xfc, 0x31, 0xcb, 0x30, 0x01, 0x08, 0x31, 0x05, 0x18, 0x72, 0x09, 0x28, 0xb3, + 0x0d, 0x38, 0xf4, 0x11, 0x49, 0x35, 0x15, 0x59, 0x76, 0x19, 0xd3, 0x5d, 0xb7, 0xe3, 0x9e, + 0xbb, 0xf3, 0xdf, 0xbf, 0x00, + ]; + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct B64Standard(#[serde_as(as = "Base64<Standard, Padded>")] Vec<u8>); + + is_equal( + B64Standard(bytes.to_vec()), + expect![[r#""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/AA==""#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct B64UrlSafe(#[serde_as(as = "Base64<UrlSafe, Padded>")] Vec<u8>); + + is_equal( + B64UrlSafe(bytes.to_vec()), + expect![[r#""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_AA==""#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct B64Crypt(#[serde_as(as = "Base64<Crypt, Padded>")] Vec<u8>); + + is_equal( + B64Crypt(bytes.to_vec()), + expect![[r#""OPQRSTUVWXYZabcdefghijklmn./0123456789ABCDEFGHIJKLMNopqrstuvwxyz..==""#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct B64Bcrypt(#[serde_as(as = "Base64<Bcrypt, Padded>")] Vec<u8>); + + is_equal( + B64Bcrypt(bytes.to_vec()), + expect![[r#""YZabcdefghijklmnopqrstuvwx./ABCDEFGHIJKLMNOPQRSTUVWXyz0123456789..==""#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct B64ImapMutf7(#[serde_as(as = "Base64<ImapMutf7, Padded>")] Vec<u8>); + + is_equal( + B64ImapMutf7(bytes.to_vec()), + expect![[r#""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+,AA==""#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct B64BinHex(#[serde_as(as = "Base64<BinHex, Padded>")] Vec<u8>); + + is_equal( + B64BinHex(bytes.to_vec()), + expect![[r##""CDEFGHIJKLMNPQRSTUVXYZ[`ab!\"#$%&'()*+,-0123456789@ABcdehijklmpqr!!==""##]], + ); +} diff --git a/third_party/rust/serde_with/tests/chrono.rs b/third_party/rust/serde_with/tests/chrono.rs new file mode 100644 index 0000000000..4bcb778a89 --- /dev/null +++ b/third_party/rust/serde_with/tests/chrono.rs @@ -0,0 +1,740 @@ +#![allow( + // clippy is broken and shows wrong warnings + // clippy on stable does not know yet about the lint name + unknown_lints, + // https://github.com/rust-lang/rust-clippy/issues/8867 + clippy::derive_partial_eq_without_eq, +)] + +extern crate alloc; + +mod utils; + +use crate::utils::{ + check_deserialization, check_error_deserialization, check_serialization, is_equal, +}; +use alloc::collections::BTreeMap; +use chrono_crate::{DateTime, Duration, Local, NaiveDateTime, Utc}; +use core::{iter::FromIterator, str::FromStr}; +use expect_test::expect; +use serde::{Deserialize, Serialize}; +use serde_with::{ + formats::Flexible, serde_as, DurationMicroSeconds, DurationMicroSecondsWithFrac, + DurationMilliSeconds, DurationMilliSecondsWithFrac, DurationNanoSeconds, + DurationNanoSecondsWithFrac, DurationSeconds, DurationSecondsWithFrac, TimestampMicroSeconds, + TimestampMicroSecondsWithFrac, TimestampMilliSeconds, TimestampMilliSecondsWithFrac, + TimestampNanoSeconds, TimestampNanoSecondsWithFrac, TimestampSeconds, TimestampSecondsWithFrac, +}; + +fn new_datetime(secs: i64, nsecs: u32) -> DateTime<Utc> { + DateTime::from_utc(NaiveDateTime::from_timestamp(secs, nsecs), Utc) +} + +#[test] +fn json_datetime_from_any_to_string_deserialization() { + #[derive(Debug, PartialEq, Deserialize)] + struct S(#[serde(with = "serde_with::chrono::datetime_utc_ts_seconds_from_any")] DateTime<Utc>); + + // just integers + check_deserialization( + vec![ + S(new_datetime(1_478_563_200, 0)), + S(new_datetime(0, 0)), + S(new_datetime(-86000, 0)), + ], + r#"[ + 1478563200, + 0, + -86000 + ]"#, + ); + + // floats, shows precision errors in subsecond part + check_deserialization( + vec![ + S(new_datetime(1_478_563_200, 122_999_906)), + S(new_datetime(0, 0)), + S(new_datetime(-86000, 998_999_999)), + ], + r#"[ + 1478563200.123, + 0.000, + -86000.999 + ]"#, + ); + + // string representation of floats + check_deserialization( + vec![ + S(new_datetime(1_478_563_200, 123_000_000)), + S(new_datetime(0, 0)), + S(new_datetime(-86000, 999_000_000)), + ], + r#"[ + "1478563200.123", + "0.000", + "-86000.999" + ]"#, + ); +} + +#[test] +fn test_chrono_naive_date_time() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct S(#[serde_as(as = "DateTime<Utc>")] NaiveDateTime); + + is_equal( + S(NaiveDateTime::from_str("1994-11-05T08:15:30").unwrap()), + expect![[r#""1994-11-05T08:15:30Z""#]], + ); +} + +#[test] +fn test_chrono_option_naive_date_time() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct S(#[serde_as(as = "Option<DateTime<Utc>>")] Option<NaiveDateTime>); + + is_equal( + S(NaiveDateTime::from_str("1994-11-05T08:15:30").ok()), + expect![[r#""1994-11-05T08:15:30Z""#]], + ); +} + +#[test] +fn test_chrono_vec_option_naive_date_time() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct S(#[serde_as(as = "Vec<Option<DateTime<Utc>>>")] Vec<Option<NaiveDateTime>>); + + is_equal( + S(vec![ + NaiveDateTime::from_str("1994-11-05T08:15:30").ok(), + NaiveDateTime::from_str("1994-11-05T08:15:31").ok(), + ]), + expect![[r#" + [ + "1994-11-05T08:15:30Z", + "1994-11-05T08:15:31Z" + ]"#]], + ); +} + +#[test] +fn test_chrono_btreemap_naive_date_time() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct S(#[serde_as(as = "BTreeMap<_, DateTime<Utc>>")] BTreeMap<i32, NaiveDateTime>); + + is_equal( + S(BTreeMap::from_iter(vec![ + (1, NaiveDateTime::from_str("1994-11-05T08:15:30").unwrap()), + (2, NaiveDateTime::from_str("1994-11-05T08:15:31").unwrap()), + ])), + expect![[r#" + { + "1": "1994-11-05T08:15:30Z", + "2": "1994-11-05T08:15:31Z" + }"#]], + ); +} + +#[test] +fn test_chrono_duration_seconds() { + let zero = Duration::zero(); + let one_second = Duration::seconds(1); + let half_second = Duration::nanoseconds(500_000_000); + let minus_one_second = zero - one_second; + let minus_half_second = zero - half_second; + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructIntStrict(#[serde_as(as = "DurationSeconds<i64>")] Duration); + + is_equal(StructIntStrict(zero), expect![[r#"0"#]]); + is_equal(StructIntStrict(one_second), expect![[r#"1"#]]); + is_equal(StructIntStrict(minus_one_second), expect![[r#"-1"#]]); + check_serialization(StructIntStrict(half_second), expect![[r#"1"#]]); + check_serialization(StructIntStrict(minus_half_second), expect![[r#"-1"#]]); + check_error_deserialization::<StructIntStrict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected i64 at line 1 column 3"#]], + ); + check_error_deserialization::<StructIntStrict>( + r#"9223372036854775808"#, + expect![[ + r#"invalid value: integer `9223372036854775808`, expected i64 at line 1 column 19"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructIntFlexible(#[serde_as(as = "DurationSeconds<i64, Flexible>")] Duration); + + is_equal(StructIntFlexible(zero), expect![[r#"0"#]]); + is_equal(StructIntFlexible(one_second), expect![[r#"1"#]]); + check_serialization(StructIntFlexible(half_second), expect![[r#"1"#]]); + check_serialization(StructIntFlexible(minus_half_second), expect![[r#"-1"#]]); + check_deserialization(StructIntFlexible(half_second), r#""0.5""#); + check_deserialization(StructIntFlexible(minus_half_second), r#""-0.5""#); + check_deserialization(StructIntFlexible(one_second), r#""1""#); + check_deserialization(StructIntFlexible(minus_one_second), r#""-1""#); + check_deserialization(StructIntFlexible(zero), r#""0""#); + check_error_deserialization::<StructIntFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Strict(#[serde_as(as = "DurationSeconds<f64>")] Duration); + + is_equal(Structf64Strict(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Strict(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Strict(minus_one_second), expect![[r#"-1.0"#]]); + check_serialization(Structf64Strict(half_second), expect![[r#"1.0"#]]); + check_serialization(Structf64Strict(minus_half_second), expect![[r#"-1.0"#]]); + check_deserialization(Structf64Strict(one_second), r#"0.5"#); + check_deserialization(Structf64Strict(minus_one_second), r#"-0.5"#); + check_error_deserialization::<Structf64Strict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected f64 at line 1 column 3"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Flexible(#[serde_as(as = "DurationSeconds<f64, Flexible>")] Duration); + + is_equal(Structf64Flexible(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Flexible(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Flexible(minus_one_second), expect![[r#"-1.0"#]]); + check_serialization(Structf64Flexible(half_second), expect![[r#"1.0"#]]); + check_serialization(Structf64Flexible(minus_half_second), expect![[r#"-1.0"#]]); + check_deserialization(Structf64Flexible(half_second), r#""0.5""#); + check_deserialization(Structf64Flexible(minus_half_second), r#""-0.5""#); + check_deserialization(Structf64Flexible(one_second), r#""1""#); + check_deserialization(Structf64Flexible(minus_one_second), r#""-1""#); + check_deserialization(Structf64Flexible(zero), r#""0""#); + check_error_deserialization::<Structf64Flexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringStrict(#[serde_as(as = "DurationSeconds<String>")] Duration); + + is_equal(StructStringStrict(zero), expect![[r#""0""#]]); + is_equal(StructStringStrict(one_second), expect![[r#""1""#]]); + is_equal(StructStringStrict(minus_one_second), expect![[r#""-1""#]]); + check_serialization(StructStringStrict(half_second), expect![[r#""1""#]]); + check_serialization(StructStringStrict(minus_half_second), expect![[r#""-1""#]]); + check_error_deserialization::<StructStringStrict>( + r#"1"#, + expect![[ + r#"invalid type: integer `1`, expected a string containing a number at line 1 column 1"# + ]], + ); + check_error_deserialization::<StructStringStrict>( + r#"-1"#, + expect![[ + r#"invalid type: integer `-1`, expected a string containing a number at line 1 column 2"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringFlexible(#[serde_as(as = "DurationSeconds<String, Flexible>")] Duration); + + is_equal(StructStringFlexible(zero), expect![[r#""0""#]]); + is_equal(StructStringFlexible(one_second), expect![[r#""1""#]]); + is_equal(StructStringFlexible(minus_one_second), expect![[r#""-1""#]]); + check_serialization(StructStringFlexible(half_second), expect![[r#""1""#]]); + check_deserialization(StructStringFlexible(half_second), r#""0.5""#); + check_deserialization(StructStringFlexible(one_second), r#""1""#); + check_deserialization(StructStringFlexible(zero), r#""0""#); + check_error_deserialization::<StructStringFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); +} + +#[test] +fn test_chrono_duration_seconds_with_frac() { + let zero = Duration::zero(); + let one_second = Duration::seconds(1); + let half_second = Duration::nanoseconds(500_000_000); + let minus_one_second = zero - one_second; + let minus_half_second = zero - half_second; + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Strict(#[serde_as(as = "DurationSecondsWithFrac<f64>")] Duration); + + is_equal(Structf64Strict(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Strict(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Strict(minus_one_second), expect![[r#"-1.0"#]]); + is_equal(Structf64Strict(half_second), expect![[r#"0.5"#]]); + is_equal(Structf64Strict(minus_half_second), expect![[r#"-0.5"#]]); + check_error_deserialization::<Structf64Strict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected f64 at line 1 column 3"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Flexible(#[serde_as(as = "DurationSecondsWithFrac<f64, Flexible>")] Duration); + + is_equal(Structf64Flexible(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Flexible(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Flexible(minus_one_second), expect![[r#"-1.0"#]]); + is_equal(Structf64Flexible(minus_half_second), expect![[r#"-0.5"#]]); + check_deserialization(Structf64Flexible(one_second), r#""1""#); + check_deserialization(Structf64Flexible(minus_one_second), r#""-1""#); + check_deserialization(Structf64Flexible(half_second), r#""0.5""#); + check_deserialization(Structf64Flexible(zero), r#""0""#); + check_error_deserialization::<Structf64Flexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringStrict(#[serde_as(as = "DurationSecondsWithFrac<String>")] Duration); + + is_equal(StructStringStrict(zero), expect![[r#""0""#]]); + is_equal(StructStringStrict(one_second), expect![[r#""1""#]]); + is_equal(StructStringStrict(minus_one_second), expect![[r#""-1""#]]); + is_equal(StructStringStrict(half_second), expect![[r#""0.5""#]]); + is_equal( + StructStringStrict(minus_half_second), + expect![[r#""-0.5""#]], + ); + is_equal( + StructStringStrict(minus_half_second), + expect![[r#""-0.5""#]], + ); + check_error_deserialization::<StructStringStrict>( + r#"1"#, + expect![[r#"invalid type: integer `1`, expected a string at line 1 column 1"#]], + ); + check_error_deserialization::<StructStringStrict>( + r#"-1"#, + expect![[r#"invalid type: integer `-1`, expected a string at line 1 column 2"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringFlexible( + #[serde_as(as = "DurationSecondsWithFrac<String, Flexible>")] Duration, + ); + + is_equal(StructStringFlexible(zero), expect![[r#""0""#]]); + is_equal(StructStringFlexible(one_second), expect![[r#""1""#]]); + is_equal(StructStringFlexible(minus_one_second), expect![[r#""-1""#]]); + is_equal(StructStringFlexible(half_second), expect![[r#""0.5""#]]); + is_equal( + StructStringFlexible(minus_half_second), + expect![[r#""-0.5""#]], + ); + check_deserialization(StructStringFlexible(one_second), r#""1""#); + check_deserialization(StructStringFlexible(zero), r#""0""#); + check_error_deserialization::<StructStringFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); +} + +#[test] +fn test_chrono_timestamp_seconds() { + let zero = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc); + let one_second = zero + Duration::seconds(1); + let half_second = zero + Duration::nanoseconds(500_000_000); + let minus_one_second = zero - Duration::seconds(1); + let minus_half_second = zero - Duration::nanoseconds(500_000_000); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructIntStrict(#[serde_as(as = "TimestampSeconds")] DateTime<Utc>); + + is_equal(StructIntStrict(zero), expect![[r#"0"#]]); + is_equal(StructIntStrict(one_second), expect![[r#"1"#]]); + is_equal(StructIntStrict(minus_one_second), expect![[r#"-1"#]]); + check_serialization(StructIntStrict(half_second), expect![[r#"1"#]]); + check_serialization(StructIntStrict(minus_half_second), expect![[r#"-1"#]]); + check_error_deserialization::<StructIntStrict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected i64 at line 1 column 3"#]], + ); + check_error_deserialization::<StructIntStrict>( + r#"0.123"#, + expect![[r#"invalid type: floating point `0.123`, expected i64 at line 1 column 5"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructIntFlexible(#[serde_as(as = "TimestampSeconds<i64, Flexible>")] DateTime<Utc>); + + is_equal(StructIntFlexible(zero), expect![[r#"0"#]]); + is_equal(StructIntFlexible(one_second), expect![[r#"1"#]]); + is_equal(StructIntFlexible(minus_one_second), expect![[r#"-1"#]]); + check_serialization(StructIntFlexible(half_second), expect![[r#"1"#]]); + check_serialization(StructIntFlexible(minus_half_second), expect![[r#"-1"#]]); + check_deserialization(StructIntFlexible(one_second), r#""1""#); + check_deserialization(StructIntFlexible(one_second), r#"1.0"#); + check_deserialization(StructIntFlexible(minus_half_second), r#""-0.5""#); + check_deserialization(StructIntFlexible(half_second), r#"0.5"#); + check_error_deserialization::<StructIntFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Strict(#[serde_as(as = "TimestampSeconds<f64>")] DateTime<Utc>); + + is_equal(Structf64Strict(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Strict(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Strict(minus_one_second), expect![[r#"-1.0"#]]); + check_serialization(Structf64Strict(half_second), expect![[r#"1.0"#]]); + check_serialization(Structf64Strict(minus_half_second), expect![[r#"-1.0"#]]); + check_deserialization(Structf64Strict(one_second), r#"0.5"#); + check_error_deserialization::<Structf64Strict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected f64 at line 1 column 3"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Flexible(#[serde_as(as = "TimestampSeconds<f64, Flexible>")] DateTime<Utc>); + + is_equal(Structf64Flexible(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Flexible(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Flexible(minus_one_second), expect![[r#"-1.0"#]]); + check_serialization(Structf64Flexible(half_second), expect![[r#"1.0"#]]); + check_serialization(Structf64Flexible(minus_half_second), expect![[r#"-1.0"#]]); + check_deserialization(Structf64Flexible(one_second), r#""1""#); + check_deserialization(Structf64Flexible(one_second), r#"1.0"#); + check_deserialization(Structf64Flexible(minus_half_second), r#""-0.5""#); + check_deserialization(Structf64Flexible(half_second), r#"0.5"#); + check_error_deserialization::<Structf64Flexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringStrict(#[serde_as(as = "TimestampSeconds<String>")] DateTime<Utc>); + + is_equal(StructStringStrict(zero), expect![[r#""0""#]]); + is_equal(StructStringStrict(one_second), expect![[r#""1""#]]); + is_equal(StructStringStrict(minus_one_second), expect![[r#""-1""#]]); + check_serialization(StructStringStrict(half_second), expect![[r#""1""#]]); + check_serialization(StructStringStrict(minus_half_second), expect![[r#""-1""#]]); + check_deserialization(StructStringStrict(one_second), r#""1""#); + check_error_deserialization::<StructStringStrict>( + r#""0.5""#, + expect![[r#"invalid digit found in string at line 1 column 5"#]], + ); + check_error_deserialization::<StructStringStrict>( + r#""-0.5""#, + expect![[r#"invalid digit found in string at line 1 column 6"#]], + ); + check_error_deserialization::<StructStringStrict>( + r#"1"#, + expect![[ + r#"invalid type: integer `1`, expected a string containing a number at line 1 column 1"# + ]], + ); + check_error_deserialization::<StructStringStrict>( + r#"0.0"#, + expect![[ + r#"invalid type: floating point `0`, expected a string containing a number at line 1 column 3"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringFlexible( + #[serde_as(as = "TimestampSeconds<String, Flexible>")] DateTime<Utc>, + ); + + is_equal(StructStringFlexible(zero), expect![[r#""0""#]]); + is_equal(StructStringFlexible(one_second), expect![[r#""1""#]]); + is_equal(StructStringFlexible(minus_one_second), expect![[r#""-1""#]]); + check_serialization(StructStringFlexible(half_second), expect![[r#""1""#]]); + check_serialization( + StructStringFlexible(minus_half_second), + expect![[r#""-1""#]], + ); + check_deserialization(StructStringFlexible(one_second), r#"1"#); + check_deserialization(StructStringFlexible(one_second), r#"1.0"#); + check_deserialization(StructStringFlexible(minus_half_second), r#""-0.5""#); + check_deserialization(StructStringFlexible(half_second), r#"0.5"#); + check_error_deserialization::<StructStringFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); +} + +#[test] +fn test_chrono_timestamp_seconds_with_frac() { + let zero = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc); + let one_second = zero + Duration::seconds(1); + let half_second = zero + Duration::nanoseconds(500_000_000); + let minus_one_second = zero - Duration::seconds(1); + let minus_half_second = zero - Duration::nanoseconds(500_000_000); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Strict(#[serde_as(as = "TimestampSecondsWithFrac<f64>")] DateTime<Utc>); + + is_equal(Structf64Strict(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Strict(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Strict(minus_one_second), expect![[r#"-1.0"#]]); + is_equal(Structf64Strict(half_second), expect![[r#"0.5"#]]); + is_equal(Structf64Strict(minus_half_second), expect![[r#"-0.5"#]]); + check_error_deserialization::<Structf64Strict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected f64 at line 1 column 3"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Flexible( + #[serde_as(as = "TimestampSecondsWithFrac<f64, Flexible>")] DateTime<Utc>, + ); + + is_equal(Structf64Flexible(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Flexible(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Flexible(minus_one_second), expect![[r#"-1.0"#]]); + is_equal(Structf64Flexible(half_second), expect![[r#"0.5"#]]); + is_equal(Structf64Flexible(minus_half_second), expect![[r#"-0.5"#]]); + check_deserialization(Structf64Flexible(one_second), r#""1""#); + check_deserialization(Structf64Flexible(minus_half_second), r#""-0.5""#); + check_error_deserialization::<Structf64Flexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringStrict(#[serde_as(as = "TimestampSecondsWithFrac<String>")] DateTime<Utc>); + + is_equal(StructStringStrict(zero), expect![[r#""0""#]]); + is_equal(StructStringStrict(one_second), expect![[r#""1""#]]); + is_equal(StructStringStrict(minus_one_second), expect![[r#""-1""#]]); + is_equal(StructStringStrict(half_second), expect![[r#""0.5""#]]); + is_equal( + StructStringStrict(minus_half_second), + expect![[r#""-0.5""#]], + ); + check_error_deserialization::<StructStringStrict>( + r#"1"#, + expect![[r#"invalid type: integer `1`, expected a string at line 1 column 1"#]], + ); + check_error_deserialization::<StructStringStrict>( + r#"0.0"#, + expect![[r#"invalid type: floating point `0`, expected a string at line 1 column 3"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringFlexible( + #[serde_as(as = "TimestampSecondsWithFrac<String, Flexible>")] DateTime<Utc>, + ); + + is_equal(StructStringFlexible(zero), expect![[r#""0""#]]); + is_equal(StructStringFlexible(one_second), expect![[r#""1""#]]); + is_equal(StructStringFlexible(minus_one_second), expect![[r#""-1""#]]); + is_equal(StructStringFlexible(half_second), expect![[r#""0.5""#]]); + is_equal( + StructStringFlexible(minus_half_second), + expect![[r#""-0.5""#]], + ); + check_deserialization(StructStringFlexible(one_second), r#"1"#); + check_deserialization(StructStringFlexible(one_second), r#"1.0"#); + check_deserialization(StructStringFlexible(half_second), r#"0.5"#); + check_error_deserialization::<StructStringFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); +} + +macro_rules! smoketest { + ($($valuety:ty, $adapter:literal, $value:expr, $expect:tt;)*) => { + $({ + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = $adapter)] $valuety); + #[allow(unused_braces)] + is_equal(S($value), $expect); + })* + }; +} + +#[test] +fn test_duration_smoketest() { + let zero = Duration::seconds(0); + let one_second = Duration::seconds(1); + + smoketest! { + Duration, "DurationSeconds<i64>", one_second, {expect![[r#"1"#]]}; + Duration, "DurationSeconds<f64>", one_second, {expect![[r#"1.0"#]]}; + Duration, "DurationMilliSeconds<i64>", one_second, {expect![[r#"1000"#]]}; + Duration, "DurationMilliSeconds<f64>", one_second, {expect![[r#"1000.0"#]]}; + Duration, "DurationMicroSeconds<i64>", one_second, {expect![[r#"1000000"#]]}; + Duration, "DurationMicroSeconds<f64>", one_second, {expect![[r#"1000000.0"#]]}; + Duration, "DurationNanoSeconds<i64>", one_second, {expect![[r#"1000000000"#]]}; + Duration, "DurationNanoSeconds<f64>", one_second, {expect![[r#"1000000000.0"#]]}; + }; + + smoketest! { + Duration, "DurationSecondsWithFrac", one_second, {expect![[r#"1.0"#]]}; + Duration, "DurationSecondsWithFrac<String>", one_second, {expect![[r#""1""#]]}; + Duration, "DurationMilliSecondsWithFrac", one_second, {expect![[r#"1000.0"#]]}; + Duration, "DurationMilliSecondsWithFrac<String>", one_second, {expect![[r#""1000""#]]}; + Duration, "DurationMicroSecondsWithFrac", one_second, {expect![[r#"1000000.0"#]]}; + Duration, "DurationMicroSecondsWithFrac<String>", one_second, {expect![[r#""1000000""#]]}; + Duration, "DurationNanoSecondsWithFrac", one_second, {expect![[r#"1000000000.0"#]]}; + Duration, "DurationNanoSecondsWithFrac<String>", one_second, {expect![[r#""1000000000""#]]}; + }; + + smoketest! { + Duration, "DurationSecondsWithFrac", zero, {expect![[r#"0.0"#]]}; + Duration, "DurationSecondsWithFrac", zero + Duration::nanoseconds(500_000_000), {expect![[r#"0.5"#]]}; + Duration, "DurationSecondsWithFrac", zero + Duration::seconds(1), {expect![[r#"1.0"#]]}; + Duration, "DurationSecondsWithFrac", zero - Duration::nanoseconds(500_000_000), {expect![[r#"-0.5"#]]}; + Duration, "DurationSecondsWithFrac", zero - Duration::seconds(1), {expect![[r#"-1.0"#]]}; + }; +} + +#[test] +fn test_datetime_utc_smoketest() { + let zero = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc); + let one_second = zero + Duration::seconds(1); + + smoketest! { + DateTime<Utc>, "TimestampSeconds<i64>", one_second, {expect![[r#"1"#]]}; + DateTime<Utc>, "TimestampSeconds<f64>", one_second, {expect![[r#"1.0"#]]}; + DateTime<Utc>, "TimestampMilliSeconds<i64>", one_second, {expect![[r#"1000"#]]}; + DateTime<Utc>, "TimestampMilliSeconds<f64>", one_second, {expect![[r#"1000.0"#]]}; + DateTime<Utc>, "TimestampMicroSeconds<i64>", one_second, {expect![[r#"1000000"#]]}; + DateTime<Utc>, "TimestampMicroSeconds<f64>", one_second, {expect![[r#"1000000.0"#]]}; + DateTime<Utc>, "TimestampNanoSeconds<i64>", one_second, {expect![[r#"1000000000"#]]}; + DateTime<Utc>, "TimestampNanoSeconds<f64>", one_second, {expect![[r#"1000000000.0"#]]}; + }; + + smoketest! { + DateTime<Utc>, "TimestampSecondsWithFrac", one_second, {expect![[r#"1.0"#]]}; + DateTime<Utc>, "TimestampSecondsWithFrac<String>", one_second, {expect![[r#""1""#]]}; + DateTime<Utc>, "TimestampMilliSecondsWithFrac", one_second, {expect![[r#"1000.0"#]]}; + DateTime<Utc>, "TimestampMilliSecondsWithFrac<String>", one_second, {expect![[r#""1000""#]]}; + DateTime<Utc>, "TimestampMicroSecondsWithFrac", one_second, {expect![[r#"1000000.0"#]]}; + DateTime<Utc>, "TimestampMicroSecondsWithFrac<String>", one_second, {expect![[r#""1000000""#]]}; + DateTime<Utc>, "TimestampNanoSecondsWithFrac", one_second, {expect![[r#"1000000000.0"#]]}; + DateTime<Utc>, "TimestampNanoSecondsWithFrac<String>", one_second, {expect![[r#""1000000000""#]]}; + }; + + smoketest! { + DateTime<Utc>, "TimestampSecondsWithFrac", zero, {expect![[r#"0.0"#]]}; + DateTime<Utc>, "TimestampSecondsWithFrac", zero + Duration::nanoseconds(500_000_000), {expect![[r#"0.5"#]]}; + DateTime<Utc>, "TimestampSecondsWithFrac", zero + Duration::seconds(1), {expect![[r#"1.0"#]]}; + DateTime<Utc>, "TimestampSecondsWithFrac", zero - Duration::nanoseconds(500_000_000), {expect![[r#"-0.5"#]]}; + DateTime<Utc>, "TimestampSecondsWithFrac", zero - Duration::seconds(1), {expect![[r#"-1.0"#]]}; + }; +} + +#[test] +fn test_datetime_local_smoketest() { + let zero = + DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc).with_timezone(&Local); + let one_second = zero + Duration::seconds(1); + + smoketest! { + DateTime<Local>, "TimestampSeconds<i64>", one_second, {expect![[r#"1"#]]}; + DateTime<Local>, "TimestampSeconds<f64>", one_second, {expect![[r#"1.0"#]]}; + DateTime<Local>, "TimestampMilliSeconds<i64>", one_second, {expect![[r#"1000"#]]}; + DateTime<Local>, "TimestampMilliSeconds<f64>", one_second, {expect![[r#"1000.0"#]]}; + DateTime<Local>, "TimestampMicroSeconds<i64>", one_second, {expect![[r#"1000000"#]]}; + DateTime<Local>, "TimestampMicroSeconds<f64>", one_second, {expect![[r#"1000000.0"#]]}; + DateTime<Local>, "TimestampNanoSeconds<i64>", one_second, {expect![[r#"1000000000"#]]}; + DateTime<Local>, "TimestampNanoSeconds<f64>", one_second, {expect![[r#"1000000000.0"#]]}; + }; + + smoketest! { + DateTime<Local>, "TimestampSecondsWithFrac", one_second, {expect![[r#"1.0"#]]}; + DateTime<Local>, "TimestampSecondsWithFrac<String>", one_second, {expect![[r#""1""#]]}; + DateTime<Local>, "TimestampMilliSecondsWithFrac", one_second, {expect![[r#"1000.0"#]]}; + DateTime<Local>, "TimestampMilliSecondsWithFrac<String>", one_second, {expect![[r#""1000""#]]}; + DateTime<Local>, "TimestampMicroSecondsWithFrac", one_second, {expect![[r#"1000000.0"#]]}; + DateTime<Local>, "TimestampMicroSecondsWithFrac<String>", one_second, {expect![[r#""1000000""#]]}; + DateTime<Local>, "TimestampNanoSecondsWithFrac", one_second, {expect![[r#"1000000000.0"#]]}; + DateTime<Local>, "TimestampNanoSecondsWithFrac<String>", one_second, {expect![[r#""1000000000""#]]}; + }; + + smoketest! { + DateTime<Local>, "TimestampSecondsWithFrac", zero, {expect![[r#"0.0"#]]}; + DateTime<Local>, "TimestampSecondsWithFrac", zero + Duration::nanoseconds(500_000_000), {expect![[r#"0.5"#]]}; + DateTime<Local>, "TimestampSecondsWithFrac", zero + Duration::seconds(1), {expect![[r#"1.0"#]]}; + DateTime<Local>, "TimestampSecondsWithFrac", zero - Duration::nanoseconds(500_000_000), {expect![[r#"-0.5"#]]}; + DateTime<Local>, "TimestampSecondsWithFrac", zero - Duration::seconds(1), {expect![[r#"-1.0"#]]}; + }; +} + +#[test] +fn test_naive_datetime_smoketest() { + let zero = NaiveDateTime::from_timestamp(0, 0); + let one_second = zero + Duration::seconds(1); + + smoketest! { + NaiveDateTime, "TimestampSeconds<i64>", one_second, {expect![[r#"1"#]]}; + NaiveDateTime, "TimestampSeconds<f64>", one_second, {expect![[r#"1.0"#]]}; + NaiveDateTime, "TimestampMilliSeconds<i64>", one_second, {expect![[r#"1000"#]]}; + NaiveDateTime, "TimestampMilliSeconds<f64>", one_second, {expect![[r#"1000.0"#]]}; + NaiveDateTime, "TimestampMicroSeconds<i64>", one_second, {expect![[r#"1000000"#]]}; + NaiveDateTime, "TimestampMicroSeconds<f64>", one_second, {expect![[r#"1000000.0"#]]}; + NaiveDateTime, "TimestampNanoSeconds<i64>", one_second, {expect![[r#"1000000000"#]]}; + NaiveDateTime, "TimestampNanoSeconds<f64>", one_second, {expect![[r#"1000000000.0"#]]}; + }; + + smoketest! { + NaiveDateTime, "TimestampSecondsWithFrac", one_second, {expect![[r#"1.0"#]]}; + NaiveDateTime, "TimestampSecondsWithFrac<String>", one_second, {expect![[r#""1""#]]}; + NaiveDateTime, "TimestampMilliSecondsWithFrac", one_second, {expect![[r#"1000.0"#]]}; + NaiveDateTime, "TimestampMilliSecondsWithFrac<String>", one_second, {expect![[r#""1000""#]]}; + NaiveDateTime, "TimestampMicroSecondsWithFrac", one_second, {expect![[r#"1000000.0"#]]}; + NaiveDateTime, "TimestampMicroSecondsWithFrac<String>", one_second, {expect![[r#""1000000""#]]}; + NaiveDateTime, "TimestampNanoSecondsWithFrac", one_second, {expect![[r#"1000000000.0"#]]}; + NaiveDateTime, "TimestampNanoSecondsWithFrac<String>", one_second, {expect![[r#""1000000000""#]]}; + }; + + smoketest! { + NaiveDateTime, "TimestampSecondsWithFrac", zero, {expect![[r#"0.0"#]]}; + NaiveDateTime, "TimestampSecondsWithFrac", zero + Duration::nanoseconds(500_000_000), {expect![[r#"0.5"#]]}; + NaiveDateTime, "TimestampSecondsWithFrac", zero + Duration::seconds(1), {expect![[r#"1.0"#]]}; + NaiveDateTime, "TimestampSecondsWithFrac", zero - Duration::nanoseconds(500_000_000), {expect![[r#"-0.5"#]]}; + NaiveDateTime, "TimestampSecondsWithFrac", zero - Duration::seconds(1), {expect![[r#"-1.0"#]]}; + }; +} diff --git a/third_party/rust/serde_with/tests/derives/deserialize_fromstr.rs b/third_party/rust/serde_with/tests/derives/deserialize_fromstr.rs new file mode 100644 index 0000000000..26d60bf08f --- /dev/null +++ b/third_party/rust/serde_with/tests/derives/deserialize_fromstr.rs @@ -0,0 +1,96 @@ +use super::*; +use core::{ + num::ParseIntError, + str::{FromStr, ParseBoolError}, +}; +use pretty_assertions::assert_eq; +use serde_with::DeserializeFromStr; + +#[derive(Debug, PartialEq, DeserializeFromStr)] +struct A { + a: u32, + b: bool, +} + +impl FromStr for A { + type Err = String; + + /// Parse a value like `123<>true` + fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut parts = s.split("<>"); + let number = parts + .next() + .ok_or_else(|| "Missing first value".to_string())? + .parse() + .map_err(|err: ParseIntError| err.to_string())?; + let bool = parts + .next() + .ok_or_else(|| "Missing second value".to_string())? + .parse() + .map_err(|err: ParseBoolError| err.to_string())?; + Ok(Self { a: number, b: bool }) + } +} + +#[test] +fn test_deserialize_fromstr() { + check_deserialization(A { a: 159, b: true }, "\"159<>true\""); + check_deserialization(A { a: 999, b: false }, "\"999<>false\""); + check_deserialization(A { a: 0, b: true }, "\"0<>true\""); +} + +#[test] +fn test_deserialize_from_bytes() { + use serde::de::{value::Error, Deserialize, Deserializer, Visitor}; + + // Unfortunately serde_json is too clever (i.e. handles bytes gracefully) + // so instead create a custom deserializer which can only deserialize bytes. + // All other deserialize_* fns are forwarded to deserialize_bytes + struct ByteDeserializer(&'static [u8]); + + impl<'de> Deserializer<'de> for ByteDeserializer { + type Error = Error; + + fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error> + where + V: Visitor<'de>, + { + self.deserialize_bytes(visitor) + } + + fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error> + where + V: Visitor<'de>, + { + visitor.visit_bytes(self.0) + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + byte_buf option unit unit_struct newtype_struct seq tuple + tuple_struct map struct enum identifier ignored_any + } + } + + // callstack: A::deserialize -> deserialize_str -> deserialize_any -> + // deserialize_bytes -> visit_bytes -> visit_str -> success! + let a = A::deserialize(ByteDeserializer(b"159<>true")).unwrap(); + + assert_eq!(A { a: 159, b: true }, a); +} + +#[test] +fn test_deserialize_fromstr_in_vec() { + check_deserialization( + vec![ + A { a: 123, b: false }, + A { a: 0, b: true }, + A { a: 999, b: true }, + ], + r#"[ + "123<>false", + "0<>true", + "999<>true" + ]"#, + ); +} diff --git a/third_party/rust/serde_with/tests/derives/lib.rs b/third_party/rust/serde_with/tests/derives/lib.rs new file mode 100644 index 0000000000..a43a3c3535 --- /dev/null +++ b/third_party/rust/serde_with/tests/derives/lib.rs @@ -0,0 +1,15 @@ +#![allow( + // clippy is broken and shows wrong warnings + // clippy on stable does not know yet about the lint name + unknown_lints, + // https://github.com/rust-lang/rust-clippy/issues/8867 + clippy::derive_partial_eq_without_eq, +)] + +mod deserialize_fromstr; +mod serialize_display; +#[path = "../utils.rs"] +mod utils; + +use expect_test::expect; +use utils::*; diff --git a/third_party/rust/serde_with/tests/derives/serialize_display.rs b/third_party/rust/serde_with/tests/derives/serialize_display.rs new file mode 100644 index 0000000000..e660fa7535 --- /dev/null +++ b/third_party/rust/serde_with/tests/derives/serialize_display.rs @@ -0,0 +1,39 @@ +use super::*; +use core::fmt; +use serde_with::SerializeDisplay; + +#[derive(Debug, SerializeDisplay)] +struct A { + a: u32, + b: bool, +} + +impl fmt::Display for A { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "->{} <> {}<-", self.a, self.b) + } +} + +#[test] +fn test_serialize_display() { + check_serialization(A { a: 123, b: false }, expect![[r#""->123 <> false<-""#]]); + check_serialization(A { a: 0, b: true }, expect![[r#""->0 <> true<-""#]]); + check_serialization(A { a: 999, b: true }, expect![[r#""->999 <> true<-""#]]); +} + +#[test] +fn test_serialize_display_in_vec() { + check_serialization( + vec![ + A { a: 123, b: false }, + A { a: 0, b: true }, + A { a: 999, b: true }, + ], + expect![[r#" + [ + "->123 <> false<-", + "->0 <> true<-", + "->999 <> true<-" + ]"#]], + ); +} diff --git a/third_party/rust/serde_with/tests/hex.rs b/third_party/rust/serde_with/tests/hex.rs new file mode 100644 index 0000000000..2994ae475e --- /dev/null +++ b/third_party/rust/serde_with/tests/hex.rs @@ -0,0 +1,93 @@ +#![allow( + // clippy is broken and shows wrong warnings + // clippy on stable does not know yet about the lint name + unknown_lints, + // https://github.com/rust-lang/rust-clippy/issues/8867 + clippy::derive_partial_eq_without_eq, +)] + +mod utils; + +use crate::utils::{check_deserialization, check_error_deserialization, is_equal}; +use expect_test::expect; +use serde::{Deserialize, Serialize}; +use serde_with::{ + formats::{Lowercase, Uppercase}, + hex::Hex, + serde_as, +}; + +#[test] +fn hex_vec() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct B(#[serde_as(as = "Vec<Hex>")] Vec<Vec<u8>>); + + is_equal( + B(vec![vec![0, 1, 2, 13], vec![14, 5, 6, 7]]), + expect![[r#" + [ + "0001020d", + "0e050607" + ]"#]], + ); + + // Check mixed case deserialization + check_deserialization( + B(vec![vec![0xaa, 0xbc, 0xff], vec![0xe0, 0x7d]]), + r#"["aaBCff","E07d"]"#, + ); + + check_error_deserialization::<B>( + r#"["0"]"#, + expect![[r#"Odd number of digits at line 1 column 5"#]], + ); + check_error_deserialization::<B>( + r#"["zz"]"#, + expect![[r#"Invalid character 'z' at position 0 at line 1 column 6"#]], + ); +} + +#[test] +fn hex_vec_lowercase() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct B(#[serde_as(as = "Vec<Hex<Lowercase>>")] Vec<Vec<u8>>); + + is_equal( + B(vec![vec![0, 1, 2, 13], vec![14, 5, 6, 7]]), + expect![[r#" + [ + "0001020d", + "0e050607" + ]"#]], + ); + + // Check mixed case deserialization + check_deserialization( + B(vec![vec![0xaa, 0xbc, 0xff], vec![0xe0, 0x7d]]), + r#"["aaBCff","E07d"]"#, + ); +} + +#[test] +fn hex_vec_uppercase() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct B(#[serde_as(as = "Vec<Hex<Uppercase>>")] Vec<Vec<u8>>); + + is_equal( + B(vec![vec![0, 1, 2, 13], vec![14, 5, 6, 7]]), + expect![[r#" + [ + "0001020D", + "0E050607" + ]"#]], + ); + + // Check mixed case deserialization + check_deserialization( + B(vec![vec![0xaa, 0xbc, 0xff], vec![0xe0, 0x7d]]), + r#"["aaBCff","E07d"]"#, + ); +} diff --git a/third_party/rust/serde_with/tests/indexmap.rs b/third_party/rust/serde_with/tests/indexmap.rs new file mode 100644 index 0000000000..7e46b9c5c5 --- /dev/null +++ b/third_party/rust/serde_with/tests/indexmap.rs @@ -0,0 +1,285 @@ +#![allow( + // clippy is broken and shows wrong warnings + // clippy on stable does not know yet about the lint name + unknown_lints, + // https://github.com/rust-lang/rust-clippy/issues/8867 + clippy::derive_partial_eq_without_eq, +)] + +mod utils; + +use crate::utils::{check_deserialization, check_error_deserialization, is_equal}; +use core::iter::FromIterator; +use expect_test::expect; +use indexmap_crate::{IndexMap, IndexSet}; +use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DisplayFromStr, Same}; +use std::net::IpAddr; + +#[test] +fn test_indexmap() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "IndexMap<DisplayFromStr, DisplayFromStr>")] IndexMap<u8, u32>); + + // Normal + is_equal( + S([(1, 1), (3, 3), (111, 111)].iter().cloned().collect()), + expect![[r#" + { + "1": "1", + "3": "3", + "111": "111" + }"#]], + ); + is_equal(S(IndexMap::default()), expect![[r#"{}"#]]); +} + +#[test] +fn test_indexset() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "IndexSet<DisplayFromStr>")] IndexSet<u32>); + + // Normal + is_equal( + S([1, 2, 3, 4, 5].iter().cloned().collect()), + expect![[r#" + [ + "1", + "2", + "3", + "4", + "5" + ]"#]], + ); + is_equal(S(IndexSet::default()), expect![[r#"[]"#]]); +} + +#[test] +fn test_map_as_tuple_list() { + let ip = "1.2.3.4".parse().unwrap(); + let ip2 = "255.255.255.255".parse().unwrap(); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SI(#[serde_as(as = "Vec<(DisplayFromStr, DisplayFromStr)>")] IndexMap<u32, IpAddr>); + + let map: IndexMap<_, _> = vec![(1, ip), (10, ip), (200, ip2)].into_iter().collect(); + is_equal( + SI(map.clone()), + expect![[r#" + [ + [ + "1", + "1.2.3.4" + ], + [ + "10", + "1.2.3.4" + ], + [ + "200", + "255.255.255.255" + ] + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SI2(#[serde_as(as = "Vec<(Same, DisplayFromStr)>")] IndexMap<u32, IpAddr>); + + is_equal( + SI2(map), + expect![[r#" + [ + [ + 1, + "1.2.3.4" + ], + [ + 10, + "1.2.3.4" + ], + [ + 200, + "255.255.255.255" + ] + ]"#]], + ); +} + +#[test] +fn test_tuple_list_as_map() { + let ip = "1.2.3.4".parse().unwrap(); + let ip2 = "255.255.255.255".parse().unwrap(); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SI( + #[serde_as(as = "std::collections::HashMap<DisplayFromStr, DisplayFromStr>")] + IndexSet<(u32, IpAddr)>, + ); + + is_equal( + SI(IndexSet::from_iter(vec![(1, ip), (10, ip), (200, ip2)])), + expect![[r#" + { + "1": "1.2.3.4", + "10": "1.2.3.4", + "200": "255.255.255.255" + }"#]], + ); +} + +#[test] +fn duplicate_key_first_wins_indexmap() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde(with = "::serde_with::rust::maps_first_key_wins")] IndexMap<usize, usize>); + + // Different value and key always works + is_equal( + S(IndexMap::from_iter(vec![(1, 1), (2, 2), (3, 3)])), + expect![[r#" + { + "1": 1, + "2": 2, + "3": 3 + }"#]], + ); + + // Same value for different keys is ok + is_equal( + S(IndexMap::from_iter(vec![(1, 1), (2, 1), (3, 1)])), + expect![[r#" + { + "1": 1, + "2": 1, + "3": 1 + }"#]], + ); + + // Duplicate keys, the first one is used + check_deserialization( + S(IndexMap::from_iter(vec![(1, 1), (2, 2)])), + r#"{"1": 1, "2": 2, "1": 3}"#, + ); +} + +#[test] +fn prohibit_duplicate_key_indexmap() { + #[derive(Debug, Eq, PartialEq, Deserialize, Serialize)] + struct S( + #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")] IndexMap<usize, usize>, + ); + + // Different value and key always works + is_equal( + S(IndexMap::from_iter(vec![(1, 1), (2, 2), (3, 3)])), + expect![[r#" + { + "1": 1, + "2": 2, + "3": 3 + }"#]], + ); + + // Same value for different keys is ok + is_equal( + S(IndexMap::from_iter(vec![(1, 1), (2, 1), (3, 1)])), + expect![[r#" + { + "1": 1, + "2": 1, + "3": 1 + }"#]], + ); + + // Duplicate keys are an error + check_error_deserialization::<S>( + r#"{"1": 1, "2": 2, "1": 3}"#, + expect![[r#"invalid entry: found duplicate key at line 1 column 24"#]], + ); +} + +#[test] +fn duplicate_value_last_wins_indexset() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde(with = "::serde_with::rust::sets_last_value_wins")] IndexSet<W>); + + #[derive(Debug, Eq, Deserialize, Serialize)] + struct W(i32, bool); + impl PartialEq for W { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + impl std::hash::Hash for W { + fn hash<H>(&self, state: &mut H) + where + H: std::hash::Hasher, + { + self.0.hash(state) + } + } + + // Different values always work + is_equal( + S(IndexSet::from_iter(vec![ + W(1, true), + W(2, false), + W(3, true), + ])), + expect![[r#" + [ + [ + 1, + true + ], + [ + 2, + false + ], + [ + 3, + true + ] + ]"#]], + ); + + let value: S = serde_json::from_str( + r#"[ + [1, false], + [1, true], + [2, true], + [2, false] + ]"#, + ) + .unwrap(); + let entries: Vec<_> = value.0.into_iter().collect(); + assert_eq!(1, entries[0].0); + assert!(entries[0].1); + assert_eq!(2, entries[1].0); + assert!(!entries[1].1); +} + +#[test] +fn prohibit_duplicate_value_indexset() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde(with = "::serde_with::rust::sets_duplicate_value_is_error")] IndexSet<usize>); + + is_equal( + S(IndexSet::from_iter(vec![1, 2, 3, 4])), + expect![[r#" + [ + 1, + 2, + 3, + 4 + ]"#]], + ); + check_error_deserialization::<S>( + r#"[1, 2, 3, 4, 1]"#, + expect![[r#"invalid entry: found duplicate value at line 1 column 15"#]], + ); +} diff --git a/third_party/rust/serde_with/tests/json.rs b/third_party/rust/serde_with/tests/json.rs new file mode 100644 index 0000000000..53c0baa146 --- /dev/null +++ b/third_party/rust/serde_with/tests/json.rs @@ -0,0 +1,41 @@ +#![allow( + // clippy is broken and shows wrong warnings + // clippy on stable does not know yet about the lint name + unknown_lints, + // https://github.com/rust-lang/rust-clippy/issues/8867 + clippy::derive_partial_eq_without_eq, +)] + +mod utils; + +use crate::utils::is_equal; +use expect_test::expect; +use serde::{Deserialize, Serialize}; +use serde_with::{json::JsonString, serde_as, DisplayFromStr}; + +#[test] +fn test_nested_json() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Struct { + #[serde_as(as = "JsonString")] + value: Nested, + } + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Nested { + #[serde_as(as = "DisplayFromStr")] + value: u32, + } + + is_equal( + Struct { + value: Nested { value: 444 }, + }, + expect![[r#" + { + "value": "{\"value\":\"444\"}" + }"#]], + ); +} diff --git a/third_party/rust/serde_with/tests/rust.rs b/third_party/rust/serde_with/tests/rust.rs new file mode 100644 index 0000000000..14accdb157 --- /dev/null +++ b/third_party/rust/serde_with/tests/rust.rs @@ -0,0 +1,676 @@ +#![allow( + // clippy is broken and shows wrong warnings + // clippy on stable does not know yet about the lint name + unknown_lints, + // https://github.com/rust-lang/rust-clippy/issues/8867 + clippy::derive_partial_eq_without_eq, +)] + +extern crate alloc; + +mod utils; + +use crate::utils::{check_deserialization, check_error_deserialization, is_equal}; +use alloc::collections::{BTreeMap, BTreeSet, LinkedList, VecDeque}; +use core::{cmp, iter::FromIterator as _}; +use expect_test::expect; +use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet}; +use pretty_assertions::assert_eq; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_with::CommaSeparator; + +#[test] +fn string_collection() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S( + #[serde(with = "serde_with::rust::StringWithSeparator::<CommaSeparator>")] Vec<String>, + ); + + is_equal(S(vec![]), expect![[r#""""#]]); + is_equal( + S(vec![ + "A".to_string(), + "B".to_string(), + "c".to_string(), + "D".to_string(), + ]), + expect![[r#""A,B,c,D""#]], + ); + is_equal( + S(vec!["".to_string(), "".to_string(), "".to_string()]), + expect![[r#"",,""#]], + ); + is_equal( + S(vec!["AVeryLongString".to_string()]), + expect![[r#""AVeryLongString""#]], + ); +} + +#[test] +fn prohibit_duplicate_value_hashset() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde(with = "::serde_with::rust::sets_duplicate_value_is_error")] HashSet<usize>); + + is_equal( + S(HashSet::from_iter(vec![1, 2, 3, 4])), + expect![[r#" + [ + 4, + 1, + 3, + 2 + ]"#]], + ); + check_error_deserialization::<S>( + r#"[1, 2, 3, 4, 1]"#, + expect![[r#"invalid entry: found duplicate value at line 1 column 15"#]], + ); +} + +#[test] +fn prohibit_duplicate_value_btreeset() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde(with = "::serde_with::rust::sets_duplicate_value_is_error")] BTreeSet<usize>); + + is_equal( + S(BTreeSet::from_iter(vec![1, 2, 3, 4])), + expect![[r#" + [ + 1, + 2, + 3, + 4 + ]"#]], + ); + check_error_deserialization::<S>( + r#"[1, 2, 3, 4, 1]"#, + expect![[r#"invalid entry: found duplicate value at line 1 column 15"#]], + ); +} + +#[test] +fn prohibit_duplicate_key_hashmap() { + #[derive(Debug, Eq, PartialEq, Deserialize, Serialize)] + struct S( + #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")] HashMap<usize, usize>, + ); + + // Different value and key always works + is_equal( + S(HashMap::from_iter(vec![(1, 1), (2, 2), (3, 3)])), + expect![[r#" + { + "1": 1, + "3": 3, + "2": 2 + }"#]], + ); + + // Same value for different keys is ok + is_equal( + S(HashMap::from_iter(vec![(1, 1), (2, 1), (3, 1)])), + expect![[r#" + { + "1": 1, + "3": 1, + "2": 1 + }"#]], + ); + + // Duplicate keys are an error + check_error_deserialization::<S>( + r#"{"1": 1, "2": 2, "1": 3}"#, + expect![[r#"invalid entry: found duplicate key at line 1 column 24"#]], + ); +} + +#[test] +fn prohibit_duplicate_key_btreemap() { + #[derive(Debug, Eq, PartialEq, Deserialize, Serialize)] + struct S( + #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")] BTreeMap<usize, usize>, + ); + + // Different value and key always works + is_equal( + S(BTreeMap::from_iter(vec![(1, 1), (2, 2), (3, 3)])), + expect![[r#" + { + "1": 1, + "2": 2, + "3": 3 + }"#]], + ); + + // Same value for different keys is ok + is_equal( + S(BTreeMap::from_iter(vec![(1, 1), (2, 1), (3, 1)])), + expect![[r#" + { + "1": 1, + "2": 1, + "3": 1 + }"#]], + ); + + // Duplicate keys are an error + check_error_deserialization::<S>( + r#"{"1": 1, "2": 2, "1": 3}"#, + expect![[r#"invalid entry: found duplicate key at line 1 column 24"#]], + ); +} + +#[test] +fn duplicate_key_first_wins_hashmap() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde(with = "::serde_with::rust::maps_first_key_wins")] HashMap<usize, usize>); + + // Different value and key always works + is_equal( + S(HashMap::from_iter(vec![(1, 1), (2, 2), (3, 3)])), + expect![[r#" + { + "1": 1, + "3": 3, + "2": 2 + }"#]], + ); + + // Same value for different keys is ok + is_equal( + S(HashMap::from_iter(vec![(1, 1), (2, 1), (3, 1)])), + expect![[r#" + { + "1": 1, + "3": 1, + "2": 1 + }"#]], + ); + + // Duplicate keys, the first one is used + check_deserialization( + S(HashMap::from_iter(vec![(1, 1), (2, 2)])), + r#"{"1": 1, "2": 2, "1": 3}"#, + ); +} + +#[test] +fn duplicate_key_first_wins_btreemap() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde(with = "::serde_with::rust::maps_first_key_wins")] BTreeMap<usize, usize>); + + // Different value and key always works + is_equal( + S(BTreeMap::from_iter(vec![(1, 1), (2, 2), (3, 3)])), + expect![[r#" + { + "1": 1, + "2": 2, + "3": 3 + }"#]], + ); + + // Same value for different keys is ok + is_equal( + S(BTreeMap::from_iter(vec![(1, 1), (2, 1), (3, 1)])), + expect![[r#" + { + "1": 1, + "2": 1, + "3": 1 + }"#]], + ); + + // Duplicate keys, the first one is used + check_deserialization( + S(BTreeMap::from_iter(vec![(1, 1), (2, 2)])), + r#"{"1": 1, "2": 2, "1": 3}"#, + ); +} + +#[test] +fn duplicate_value_first_wins_hashset() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(HashSet<W>); + // struct S(#[serde(with = "::serde_with::rust::sets_first_value_wins")] HashSet<W>); + + #[derive(Debug, Eq, Deserialize, Serialize)] + struct W(i32, bool); + impl PartialEq for W { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + impl std::hash::Hash for W { + fn hash<H>(&self, state: &mut H) + where + H: std::hash::Hasher, + { + self.0.hash(state) + } + } + + // Different values always work + is_equal( + S(HashSet::from_iter(vec![ + W(1, true), + W(2, false), + W(3, true), + ])), + expect![[r#" + [ + [ + 1, + true + ], + [ + 3, + true + ], + [ + 2, + false + ] + ]"#]], + ); + + let value: S = serde_json::from_str( + r#"[ + [1, false], + [1, true], + [2, true], + [2, false] + ]"#, + ) + .unwrap(); + let entries: Vec<_> = value.0.into_iter().collect(); + assert_eq!(1, entries[0].0); + assert!(!entries[0].1); + assert_eq!(2, entries[1].0); + assert!(entries[1].1); +} + +#[test] +fn duplicate_value_last_wins_hashset() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde(with = "::serde_with::rust::sets_last_value_wins")] HashSet<W>); + + #[derive(Debug, Eq, Deserialize, Serialize)] + struct W(i32, bool); + impl PartialEq for W { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + impl std::hash::Hash for W { + fn hash<H>(&self, state: &mut H) + where + H: std::hash::Hasher, + { + self.0.hash(state) + } + } + + // Different values always work + is_equal( + S(HashSet::from_iter(vec![ + W(1, true), + W(2, false), + W(3, true), + ])), + expect![[r#" + [ + [ + 1, + true + ], + [ + 3, + true + ], + [ + 2, + false + ] + ]"#]], + ); + + let value: S = serde_json::from_str( + r#"[ + [1, false], + [1, true], + [2, true], + [2, false] + ]"#, + ) + .unwrap(); + let entries: Vec<_> = value.0.into_iter().collect(); + assert_eq!(1, entries[0].0); + assert!(entries[0].1); + assert_eq!(2, entries[1].0); + assert!(!entries[1].1); +} + +#[test] +fn duplicate_value_last_wins_btreeset() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde(with = "::serde_with::rust::sets_last_value_wins")] BTreeSet<W>); + #[derive(Debug, Eq, Deserialize, Serialize)] + struct W(i32, bool); + impl PartialEq for W { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + impl Ord for W { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.0.cmp(&other.0) + } + } + impl PartialOrd for W { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + Some(self.cmp(other)) + } + } + + // Different values always work + is_equal( + S(BTreeSet::from_iter(vec![ + W(1, true), + W(2, false), + W(3, true), + ])), + expect![[r#" + [ + [ + 1, + true + ], + [ + 2, + false + ], + [ + 3, + true + ] + ]"#]], + ); + + let value: S = serde_json::from_str( + r#"[ + [1, false], + [1, true], + [2, true], + [2, false] + ]"#, + ) + .unwrap(); + let entries: Vec<_> = value.0.into_iter().collect(); + assert_eq!(1, entries[0].0); + assert!(entries[0].1); + assert_eq!(2, entries[1].0); + assert!(!entries[1].1); +} + +#[test] +fn test_map_as_tuple_list() { + #[derive(Debug, Deserialize, Serialize, PartialEq)] + struct Hash(#[serde(with = "serde_with::rust::map_as_tuple_list")] HashMap<String, u8>); + + is_equal( + Hash(HashMap::from_iter(vec![ + ("ABC".to_string(), 1), + ("Hello".to_string(), 0), + ("World".to_string(), 20), + ])), + expect![[r#" + [ + [ + "ABC", + 1 + ], + [ + "Hello", + 0 + ], + [ + "World", + 20 + ] + ]"#]], + ); + is_equal( + Hash(HashMap::from_iter(vec![("Hello".to_string(), 0)])), + expect![[r#" + [ + [ + "Hello", + 0 + ] + ]"#]], + ); + is_equal(Hash(HashMap::default()), expect![[r#"[]"#]]); + + // Test parse error, only single element instead of tuple + check_error_deserialization::<Hash>( + r#"[ [1] ]"#, + expect![[r#"invalid type: integer `1`, expected a string at line 1 column 4"#]], + ); + + #[derive(Debug, Deserialize, Serialize, PartialEq)] + struct BTree(#[serde(with = "serde_with::rust::map_as_tuple_list")] BTreeMap<String, u8>); + + is_equal( + BTree(BTreeMap::from_iter(vec![ + ("ABC".to_string(), 1), + ("Hello".to_string(), 0), + ("World".to_string(), 20), + ])), + expect![[r#" + [ + [ + "ABC", + 1 + ], + [ + "Hello", + 0 + ], + [ + "World", + 20 + ] + ]"#]], + ); + is_equal( + BTree(BTreeMap::from_iter(vec![("Hello".to_string(), 0)])), + expect![[r#" + [ + [ + "Hello", + 0 + ] + ]"#]], + ); + is_equal(BTree(BTreeMap::default()), expect![[r#"[]"#]]); + + // Test parse error, only single element instead of tuple + check_error_deserialization::<BTree>( + r#"[ [1] ]"#, + expect![[r#"invalid type: integer `1`, expected a string at line 1 column 4"#]], + ); +} + +#[test] +fn tuple_list_as_map_vec() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S( + #[serde(with = "serde_with::rust::tuple_list_as_map")] Vec<(Wrapper<i32>, Wrapper<String>)>, + ); + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] + #[serde(transparent)] + struct Wrapper<T>(T); + + is_equal( + S(vec![ + (Wrapper(1), Wrapper("Hi".into())), + (Wrapper(2), Wrapper("Cake".into())), + (Wrapper(99), Wrapper("Lie".into())), + ]), + expect![[r#" + { + "1": "Hi", + "2": "Cake", + "99": "Lie" + }"#]], + ); + is_equal(S(Vec::new()), expect![[r#"{}"#]]); + check_error_deserialization::<S>( + r#"[]"#, + expect![[r#"invalid type: sequence, expected a map at line 1 column 0"#]], + ); + check_error_deserialization::<S>( + r#"null"#, + expect![[r#"invalid type: null, expected a map at line 1 column 4"#]], + ); +} + +#[test] +fn tuple_list_as_map_linkedlist() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S( + #[serde(with = "serde_with::rust::tuple_list_as_map")] + LinkedList<(Wrapper<i32>, Wrapper<String>)>, + ); + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] + #[serde(transparent)] + struct Wrapper<T>(T); + + is_equal( + S(LinkedList::from_iter(vec![ + (Wrapper(1), Wrapper("Hi".into())), + (Wrapper(2), Wrapper("Cake".into())), + (Wrapper(99), Wrapper("Lie".into())), + ])), + expect![[r#" + { + "1": "Hi", + "2": "Cake", + "99": "Lie" + }"#]], + ); + is_equal(S(LinkedList::new()), expect![[r#"{}"#]]); + check_error_deserialization::<S>( + r#"[]"#, + expect![[r#"invalid type: sequence, expected a map at line 1 column 0"#]], + ); + check_error_deserialization::<S>( + r#"null"#, + expect![[r#"invalid type: null, expected a map at line 1 column 4"#]], + ); +} + +#[test] +fn tuple_list_as_map_vecdeque() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S( + #[serde(with = "serde_with::rust::tuple_list_as_map")] + VecDeque<(Wrapper<i32>, Wrapper<String>)>, + ); + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] + #[serde(transparent)] + struct Wrapper<T>(T); + + is_equal( + S(VecDeque::from_iter(vec![ + (Wrapper(1), Wrapper("Hi".into())), + (Wrapper(2), Wrapper("Cake".into())), + (Wrapper(99), Wrapper("Lie".into())), + ])), + expect![[r#" + { + "1": "Hi", + "2": "Cake", + "99": "Lie" + }"#]], + ); + is_equal(S(VecDeque::new()), expect![[r#"{}"#]]); + check_error_deserialization::<S>( + r#"[]"#, + expect![[r#"invalid type: sequence, expected a map at line 1 column 0"#]], + ); + check_error_deserialization::<S>( + r#"null"#, + expect![[r#"invalid type: null, expected a map at line 1 column 4"#]], + ); +} + +#[test] +fn test_string_empty_as_none() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde(with = "serde_with::rust::string_empty_as_none")] Option<String>); + + is_equal(S(Some("str".to_string())), expect![[r#""str""#]]); + check_deserialization(S(None), r#""""#); + check_deserialization(S(None), r#"null"#); +} + +#[test] +fn test_default_on_error() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S<T>(#[serde(with = "serde_with::rust::default_on_error")] T) + where + T: Default + Serialize + DeserializeOwned; + + is_equal(S(123), expect![[r#"123"#]]); + is_equal(S("Hello World".to_string()), expect![[r#""Hello World""#]]); + is_equal( + S(vec![1, 2, 3]), + expect![[r#" + [ + 1, + 2, + 3 + ]"#]], + ); + + check_deserialization(S(0), r#"{}"#); + check_deserialization(S(0), r#"[]"#); + check_deserialization(S(0), r#"null"#); + check_deserialization(S(0), r#""A""#); + + check_deserialization(S("".to_string()), r#"{}"#); + check_deserialization(S("".to_string()), r#"[]"#); + check_deserialization(S("".to_string()), r#"null"#); + check_deserialization(S("".to_string()), r#"0"#); + + check_deserialization(S::<Vec<i32>>(vec![]), r#"{}"#); + check_deserialization(S::<Vec<i32>>(vec![]), r#"null"#); + check_deserialization(S::<Vec<i32>>(vec![]), r#"0"#); + check_deserialization(S::<Vec<i32>>(vec![]), r#""A""#); +} + +#[test] +fn test_default_on_null() { + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S<T>(#[serde(with = "serde_with::rust::default_on_null")] T) + where + T: Default + Serialize + DeserializeOwned; + + is_equal(S(123), expect![[r#"123"#]]); + is_equal(S("Hello World".to_string()), expect![[r#""Hello World""#]]); + is_equal( + S(vec![1, 2, 3]), + expect![[r#" + [ + 1, + 2, + 3 + ]"#]], + ); + + check_deserialization(S(0), r#"null"#); + check_deserialization(S("".to_string()), r#"null"#); + check_deserialization(S::<Vec<i32>>(vec![]), r#"null"#); +} diff --git a/third_party/rust/serde_with/tests/serde_as/collections.rs b/third_party/rust/serde_with/tests/serde_as/collections.rs new file mode 100644 index 0000000000..dad84c4978 --- /dev/null +++ b/third_party/rust/serde_with/tests/serde_as/collections.rs @@ -0,0 +1,44 @@ +use super::*; +use fnv::{FnvHashMap, FnvHashSet}; + +/// Test that HashSets are also supported with non-default hashers. +#[test] +fn test_fnv_hashset() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "FnvHashSet<DisplayFromStr>")] FnvHashSet<u32>); + + // Normal + is_equal( + S([1, 2, 3, 4, 5].iter().cloned().collect()), + expect![[r#" + [ + "5", + "4", + "1", + "3", + "2" + ]"#]], + ); + is_equal(S(FnvHashSet::default()), expect![[r#"[]"#]]); +} + +/// Test that HashSets are also supported with non-default hashers. +#[test] +fn test_fnv_hashmap() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "FnvHashMap<DisplayFromStr, DisplayFromStr>")] FnvHashMap<u8, u32>); + + // Normal + is_equal( + S([(1, 1), (3, 3), (111, 111)].iter().cloned().collect()), + expect![[r#" + { + "1": "1", + "3": "3", + "111": "111" + }"#]], + ); + is_equal(S(FnvHashMap::default()), expect![[r#"{}"#]]); +} diff --git a/third_party/rust/serde_with/tests/serde_as/default_on.rs b/third_party/rust/serde_with/tests/serde_as/default_on.rs new file mode 100644 index 0000000000..ea3678b166 --- /dev/null +++ b/third_party/rust/serde_with/tests/serde_as/default_on.rs @@ -0,0 +1,130 @@ +use super::*; +use serde_with::{DefaultOnError, DefaultOnNull}; + +#[test] +fn test_default_on_error() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "DefaultOnError<DisplayFromStr>")] u32); + + // Normal + is_equal(S(123), expect![[r#""123""#]]); + is_equal(S(0), expect![[r#""0""#]]); + // Error cases + check_deserialization(S(0), r#""""#); + check_deserialization(S(0), r#""12+3""#); + check_deserialization(S(0), r#""abc""#); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2(#[serde_as(as = "DefaultOnError<Vec<DisplayFromStr>>")] Vec<u32>); + + // Normal + is_equal( + S2(vec![1, 2, 3]), + expect![[r#" + [ + "1", + "2", + "3" + ]"#]], + ); + is_equal(S2(vec![]), expect![[r#"[]"#]]); + // Error cases + check_deserialization(S2(vec![]), r#"2"#); + check_deserialization(S2(vec![]), r#""not_a_list""#); + check_deserialization(S2(vec![]), r#"{}"#); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Struct2 { + #[serde_as(as = "DefaultOnError<Vec<DisplayFromStr>>")] + value: Vec<u32>, + } + check_deserialization(Struct2 { value: vec![] }, r#"{"value":}"#); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S3(#[serde_as(as = "Vec<DefaultOnError<DisplayFromStr>>")] Vec<u32>); + + // Normal + is_equal( + S3(vec![1, 2, 3]), + expect![[r#" + [ + "1", + "2", + "3" + ]"#]], + ); + is_equal(S3(vec![]), expect![[r#"[]"#]]); + // Error cases + check_deserialization(S3(vec![0, 3, 0]), r#"[2,"3",4]"#); + check_deserialization(S3(vec![0, 0]), r#"["AA",5]"#); +} + +#[test] +fn test_default_on_null() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "DefaultOnNull<DisplayFromStr>")] u32); + + // Normal + is_equal(S(123), expect![[r#""123""#]]); + is_equal(S(0), expect![[r#""0""#]]); + // Null case + check_deserialization(S(0), r#"null"#); + // Error cases + check_error_deserialization::<S>( + r#""12+3""#, + expect![[r#"invalid digit found in string at line 1 column 6"#]], + ); + check_error_deserialization::<S>( + r#""abc""#, + expect![[r#"invalid digit found in string at line 1 column 5"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2(#[serde_as(as = "Vec<DefaultOnNull>")] Vec<u32>); + + // Normal + is_equal( + S2(vec![1, 2, 0, 3]), + expect![[r#" + [ + 1, + 2, + 0, + 3 + ]"#]], + ); + is_equal(S2(vec![]), expect![[r#"[]"#]]); + // Null cases + check_deserialization(S2(vec![1, 0, 2]), r#"[1, null, 2]"#); + check_error_deserialization::<S2>( + r#"["not_a_number"]"#, + expect![[r#"invalid type: string "not_a_number", expected u32 at line 1 column 15"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S3(#[serde_as(as = "Vec<DefaultOnNull<DisplayFromStr>>")] Vec<u32>); + + // Normal + is_equal( + S3(vec![1, 2, 3]), + expect![[r#" + [ + "1", + "2", + "3" + ]"#]], + ); + // Null case + check_deserialization(S3(vec![0, 3, 0]), r#"[null,"3",null]"#); + check_error_deserialization::<S3>( + r#"[null,3,null]"#, + expect![[r#"invalid type: integer `3`, expected a string at line 1 column 7"#]], + ); +} diff --git a/third_party/rust/serde_with/tests/serde_as/enum_map.rs b/third_party/rust/serde_with/tests/serde_as/enum_map.rs new file mode 100644 index 0000000000..57a0b8ade4 --- /dev/null +++ b/third_party/rust/serde_with/tests/serde_as/enum_map.rs @@ -0,0 +1,459 @@ +use super::*; +use core::{fmt::Write as _, str::FromStr}; +use serde_test::Configure; +use serde_with::EnumMap; +use std::net::IpAddr; + +fn bytes_debug_readable(bytes: &[u8]) -> String { + let mut result = String::with_capacity(bytes.len() * 2); + for &byte in bytes { + match byte { + non_printable if !(0x20..0x7f).contains(&non_printable) => { + write!(result, "\\x{:02x}", byte).unwrap(); + } + b'\\' => result.push_str("\\\\"), + _ => { + result.push(byte as char); + } + } + } + result +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +enum EnumValue { + Int(i32), + String(String), + Unit, + Tuple(i32, String, bool), + Struct { + a: i32, + b: String, + c: bool, + }, + Ip(IpAddr, IpAddr), + #[serde(rename = "$value")] + Extra(serde_json::Value), +} + +#[serde_as] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +struct VecEnumValues { + #[serde_as(as = "EnumMap")] + vec: Vec<EnumValue>, +} + +#[test] +fn json_round_trip() { + let values = VecEnumValues { + vec: vec![ + EnumValue::Int(123), + EnumValue::String("FooBar".to_string()), + EnumValue::Int(456), + EnumValue::String("XXX".to_string()), + EnumValue::Unit, + EnumValue::Tuple(1, "Middle".to_string(), false), + EnumValue::Struct { + a: 666, + b: "BBB".to_string(), + c: true, + }, + ], + }; + + let json = serde_json::to_string_pretty(&values).unwrap(); + expect_test::expect![[r#" + { + "vec": { + "Int": 123, + "String": "FooBar", + "Int": 456, + "String": "XXX", + "Unit": null, + "Tuple": [ + 1, + "Middle", + false + ], + "Struct": { + "a": 666, + "b": "BBB", + "c": true + } + } + }"#]] + .assert_eq(&json); + let deser_values: VecEnumValues = serde_json::from_str(&json).unwrap(); + assert_eq!(values, deser_values); +} + +#[test] +fn ron_serialize() { + let values = VecEnumValues { + vec: vec![ + EnumValue::Int(123), + EnumValue::String("FooBar".to_string()), + EnumValue::Int(456), + EnumValue::String("XXX".to_string()), + EnumValue::Unit, + EnumValue::Tuple(1, "Middle".to_string(), false), + EnumValue::Struct { + a: 666, + b: "BBB".to_string(), + c: true, + }, + ], + }; + + let pretty_config = ron::ser::PrettyConfig::new().new_line("\n".into()); + let ron = ron::ser::to_string_pretty(&values, pretty_config).unwrap(); + expect_test::expect![[r#" + ( + vec: { + "Int": 123, + "String": "FooBar", + "Int": 456, + "String": "XXX", + "Unit": (), + "Tuple": (1, "Middle", false), + "Struct": ( + a: 666, + b: "BBB", + c: true, + ), + }, + )"#]] + .assert_eq(&ron); + // TODO deserializing a Strings as an Identifier seems unsupported + let deser_values: ron::Value = ron::de::from_str(&ron).unwrap(); + expect_test::expect![[r#" + Map( + Map( + { + String( + "vec", + ): Map( + Map( + { + String( + "Int", + ): Number( + Integer( + 456, + ), + ), + String( + "String", + ): String( + "XXX", + ), + String( + "Struct", + ): Map( + Map( + { + String( + "a", + ): Number( + Integer( + 666, + ), + ), + String( + "b", + ): String( + "BBB", + ), + String( + "c", + ): Bool( + true, + ), + }, + ), + ), + String( + "Tuple", + ): Seq( + [ + Number( + Integer( + 1, + ), + ), + String( + "Middle", + ), + Bool( + false, + ), + ], + ), + String( + "Unit", + ): Unit, + }, + ), + ), + }, + ), + ) + "#]] + .assert_debug_eq(&deser_values); +} + +#[test] +fn xml_round_trip() { + let values = VecEnumValues { + vec: vec![ + EnumValue::Int(123), + EnumValue::String("FooBar".to_string()), + EnumValue::Int(456), + EnumValue::String("XXX".to_string()), + EnumValue::Unit, + // serialize_tuple and variants are not supported by XML + // EnumValue::Tuple(1, "Middle".to_string(), false), + // Cannot be deserialized. It serializes to: + // <Struct><EnumValue><a>666</a><b>BBB</b><c>true</c></EnumValue></Struct> + // EnumValue::Struct { + // a: 666, + // b: "BBB".to_string(), + // c: true, + // }, + ], + }; + + let xml = serde_xml_rs::to_string(&values).unwrap(); + expect_test::expect![[r#"<VecEnumValues><vec><Int>123</Int><String>FooBar</String><Int>456</Int><String>XXX</String><Unit></Unit></vec></VecEnumValues>"#]] + .assert_eq(&xml); + let deser_values: VecEnumValues = serde_xml_rs::from_str(&xml).unwrap(); + assert_eq!(values, deser_values); +} + +#[test] +fn serde_test_round_trip() { + let values = VecEnumValues { + vec: vec![ + EnumValue::Int(123), + EnumValue::String("FooBar".to_string()), + EnumValue::Int(456), + EnumValue::String("XXX".to_string()), + EnumValue::Unit, + EnumValue::Tuple(1, "Middle".to_string(), false), + EnumValue::Struct { + a: 666, + b: "BBB".to_string(), + c: true, + }, + ], + }; + + use serde_test::Token::*; + serde_test::assert_tokens( + &values.readable(), + &[ + Struct { + name: "VecEnumValues", + len: 1, + }, + Str("vec"), + Map { + len: Option::Some(7), + }, + Str("Int"), + I32(123), + Str("String"), + Str("FooBar"), + Str("Int"), + I32(456), + Str("String"), + Str("XXX"), + Str("Unit"), + Unit, + Str("Tuple"), + TupleStruct { + name: "EnumValue", + len: 3, + }, + I32(1), + Str("Middle"), + Bool(false), + TupleStructEnd, + Str("Struct"), + Struct { + name: "EnumValue", + len: 3, + }, + Str("a"), + I32(666), + Str("b"), + Str("BBB"), + Str("c"), + Bool(true), + StructEnd, + MapEnd, + StructEnd, + ], + ); +} + +#[test] +fn serde_test_round_trip_human_readable() { + let values = VecEnumValues { + vec: vec![EnumValue::Ip( + IpAddr::from_str("127.0.0.1").unwrap(), + IpAddr::from_str("::7777:dead:beef").unwrap(), + )], + }; + + use serde_test::Token::*; + serde_test::assert_tokens( + &values.clone().readable(), + &[ + Struct { + name: "VecEnumValues", + len: 1, + }, + Str("vec"), + Map { + len: Option::Some(1), + }, + Str("Ip"), + TupleStruct { + name: "EnumValue", + len: 2, + }, + Str("127.0.0.1"), + Str("::7777:dead:beef"), + TupleStructEnd, + MapEnd, + StructEnd, + ], + ); + + serde_test::assert_tokens( + &values.compact(), + &[ + Struct { + name: "VecEnumValues", + len: 1, + }, + Str("vec"), + Map { + len: Option::Some(1), + }, + Str("Ip"), + TupleStruct { + name: "EnumValue", + len: 2, + }, + NewtypeVariant { + name: "IpAddr", + variant: "V4", + }, + Tuple { len: 4 }, + U8(127), + U8(0), + U8(0), + U8(1), + TupleEnd, + NewtypeVariant { + name: "IpAddr", + variant: "V6", + }, + Tuple { len: 16 }, + U8(0), + U8(0), + U8(0), + U8(0), + U8(0), + U8(0), + U8(0), + U8(0), + U8(0), + U8(0), + U8(0x77), + U8(0x77), + U8(0xde), + U8(0xad), + U8(0xbe), + U8(0xef), + TupleEnd, + TupleStructEnd, + MapEnd, + StructEnd, + ], + ); +} + +// Bincode does not support Deserializer::deserialize_identifier +// https://github.com/bincode-org/bincode/blob/e0ac3245162ba668ba04591897dd88ff5b3096b8/src/de/mod.rs#L442 + +#[test] +fn rmp_round_trip() { + let values = VecEnumValues { + vec: vec![ + EnumValue::Int(123), + EnumValue::String("FooBar".to_string()), + EnumValue::Int(456), + EnumValue::String("XXX".to_string()), + EnumValue::Unit, + EnumValue::Tuple(1, "Middle".to_string(), false), + EnumValue::Struct { + a: 666, + b: "BBB".to_string(), + c: true, + }, + EnumValue::Ip( + IpAddr::from_str("127.0.0.1").unwrap(), + IpAddr::from_str("::7777:dead:beef").unwrap(), + ), + ], + }; + + let rmp = rmp_serde::to_vec(&values).unwrap(); + expect_test::expect![[r#"\x91\x88\xa3Int{\xa6String\xa6FooBar\xa3Int\xcd\x01\xc8\xa6String\xa3XXX\xa4Unit\xc0\xa5Tuple\x93\x01\xa6Middle\xc2\xa6Struct\x93\xcd\x02\x9a\xa3BBB\xc3\xa2Ip\x92\x81\xa2V4\x94\x7f\x00\x00\x01\x81\xa2V6\xdc\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ww\xcc\xde\xcc\xad\xcc\xbe\xcc\xef"#]] + .assert_eq(&bytes_debug_readable(&rmp)); + let deser_values: VecEnumValues = rmp_serde::from_read(&*rmp).unwrap(); + assert_eq!(values, deser_values); +} + +#[test] +fn yaml_round_trip() { + // Duplicate enum variants do not work with YAML + let values = VecEnumValues { + vec: vec![ + EnumValue::Int(123), + EnumValue::String("FooBar".to_string()), + // EnumValue::Int(456), + // EnumValue::String("XXX".to_string()), + EnumValue::Unit, + EnumValue::Tuple(1, "Middle".to_string(), false), + EnumValue::Struct { + a: 666, + b: "BBB".to_string(), + c: true, + }, + ], + }; + + let yaml = serde_yaml::to_string(&values).unwrap(); + expect_test::expect![[r#" + --- + vec: + Int: 123 + String: FooBar + Unit: ~ + Tuple: + - 1 + - Middle + - false + Struct: + a: 666 + b: BBB + c: true + "#]] + .assert_eq(&yaml); + let deser_values: VecEnumValues = serde_yaml::from_str(&yaml).unwrap(); + assert_eq!(values, deser_values); +} diff --git a/third_party/rust/serde_with/tests/serde_as/frominto.rs b/third_party/rust/serde_with/tests/serde_as/frominto.rs new file mode 100644 index 0000000000..f7f3cac213 --- /dev/null +++ b/third_party/rust/serde_with/tests/serde_as/frominto.rs @@ -0,0 +1,187 @@ +use super::*; +use core::convert::TryFrom; +use serde_with::{FromInto, TryFromInto}; + +#[derive(Clone, Debug, PartialEq)] +enum IntoSerializable { + A, + B, + C, +} + +impl From<IntoSerializable> for String { + fn from(value: IntoSerializable) -> Self { + match value { + IntoSerializable::A => "String A", + IntoSerializable::B => "Some other value", + IntoSerializable::C => "Looks like 123", + } + .to_string() + } +} + +#[derive(Debug, PartialEq)] +enum FromDeserializable { + Zero, + Odd(u32), + Even(u32), +} + +impl From<u32> for FromDeserializable { + fn from(value: u32) -> Self { + match value { + 0 => FromDeserializable::Zero, + e if e % 2 == 0 => FromDeserializable::Even(e), + o => FromDeserializable::Odd(o), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +enum LikeBool { + Trueish, + Falseisch, +} + +impl From<bool> for LikeBool { + fn from(b: bool) -> Self { + if b { + LikeBool::Trueish + } else { + LikeBool::Falseisch + } + } +} + +impl From<LikeBool> for bool { + fn from(lb: LikeBool) -> Self { + match lb { + LikeBool::Trueish => true, + LikeBool::Falseisch => false, + } + } +} + +#[test] +fn test_frominto_ser() { + #[serde_as] + #[derive(Debug, PartialEq, Serialize)] + struct S(#[serde_as(serialize_as = "FromInto<String>")] IntoSerializable); + + check_serialization(S(IntoSerializable::A), expect![[r#""String A""#]]); + check_serialization(S(IntoSerializable::B), expect![[r#""Some other value""#]]); + check_serialization(S(IntoSerializable::C), expect![[r#""Looks like 123""#]]); +} + +#[test] +fn test_tryfrominto_ser() { + #[serde_as] + #[derive(Debug, PartialEq, Serialize)] + struct S(#[serde_as(serialize_as = "TryFromInto<String>")] IntoSerializable); + + check_serialization(S(IntoSerializable::A), expect![[r#""String A""#]]); + check_serialization(S(IntoSerializable::B), expect![[r#""Some other value""#]]); + check_serialization(S(IntoSerializable::C), expect![[r#""Looks like 123""#]]); +} + +#[test] +fn test_frominto_de() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize)] + struct S(#[serde_as(deserialize_as = "FromInto<u32>")] FromDeserializable); + + check_deserialization(S(FromDeserializable::Zero), "0"); + check_deserialization(S(FromDeserializable::Odd(1)), "1"); + check_deserialization(S(FromDeserializable::Odd(101)), "101"); + check_deserialization(S(FromDeserializable::Even(2)), "2"); + check_deserialization(S(FromDeserializable::Even(202)), "202"); +} + +#[test] +fn test_tryfrominto_de() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize)] + struct S(#[serde_as(deserialize_as = "TryFromInto<u32>")] FromDeserializable); + + check_deserialization(S(FromDeserializable::Zero), "0"); + check_deserialization(S(FromDeserializable::Odd(1)), "1"); + check_deserialization(S(FromDeserializable::Odd(101)), "101"); + check_deserialization(S(FromDeserializable::Even(2)), "2"); + check_deserialization(S(FromDeserializable::Even(202)), "202"); +} + +#[test] +fn test_frominto_de_and_ser() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde_as(as = "FromInto<bool>")] LikeBool); + + is_equal(S(LikeBool::Trueish), expect![[r#"true"#]]); + is_equal(S(LikeBool::Falseisch), expect![[r#"false"#]]); +} + +#[test] +fn test_tryfrominto_de_and_ser() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde_as(as = "TryFromInto<bool>")] LikeBool); + + is_equal(S(LikeBool::Trueish), expect![[r#"true"#]]); + is_equal(S(LikeBool::Falseisch), expect![[r#"false"#]]); +} + +#[derive(Clone, Debug, PartialEq)] +enum TryIntoSerializable { + Works, + Fails, +} + +impl TryFrom<TryIntoSerializable> for String { + type Error = &'static str; + + fn try_from(value: TryIntoSerializable) -> Result<Self, Self::Error> { + match value { + TryIntoSerializable::Works => Ok("Works".to_string()), + TryIntoSerializable::Fails => Err("Fails cannot be turned into String"), + } + } +} + +#[derive(Debug, PartialEq)] +enum TryFromDeserializable { + Zero, +} + +impl TryFrom<u32> for TryFromDeserializable { + type Error = &'static str; + + fn try_from(value: u32) -> Result<Self, Self::Error> { + match value { + 0 => Ok(TryFromDeserializable::Zero), + _ => Err("Number is not zero"), + } + } +} + +#[test] +fn test_tryfrominto_ser_with_error() { + #[serde_as] + #[derive(Debug, PartialEq, Serialize)] + struct S(#[serde_as(serialize_as = "TryFromInto<String>")] TryIntoSerializable); + + check_serialization(S(TryIntoSerializable::Works), expect![[r#""Works""#]]); + check_error_serialization( + S(TryIntoSerializable::Fails), + expect![[r#"Fails cannot be turned into String"#]], + ); +} + +#[test] +fn test_tryfrominto_de_with_error() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize)] + struct S(#[serde_as(deserialize_as = "TryFromInto<u32>")] TryFromDeserializable); + + check_deserialization(S(TryFromDeserializable::Zero), "0"); + check_error_deserialization::<S>("1", expect![[r#"Number is not zero"#]]); +} diff --git a/third_party/rust/serde_with/tests/serde_as/lib.rs b/third_party/rust/serde_with/tests/serde_as/lib.rs new file mode 100644 index 0000000000..0b40ce0c26 --- /dev/null +++ b/third_party/rust/serde_with/tests/serde_as/lib.rs @@ -0,0 +1,1129 @@ +#![allow( + // clippy is broken and shows wrong warnings + // clippy on stable does not know yet about the lint name + unknown_lints, + // https://github.com/rust-lang/rust-clippy/issues/8867 + clippy::derive_partial_eq_without_eq, +)] + +extern crate alloc; + +mod collections; +mod default_on; +mod enum_map; +mod frominto; +mod map_tuple_list; +mod pickfirst; +mod serde_as_macro; +mod serde_conv; +mod time; +#[path = "../utils.rs"] +mod utils; + +use crate::utils::*; +use alloc::{ + collections::{BTreeMap, BTreeSet, LinkedList, VecDeque}, + rc::{Rc, Weak as RcWeak}, + sync::{Arc, Weak as ArcWeak}, +}; +use core::cell::{Cell, RefCell}; +use expect_test::expect; +use serde::{Deserialize, Serialize}; +use serde_with::{ + formats::{Flexible, Strict}, + serde_as, BoolFromInt, BytesOrString, CommaSeparator, DisplayFromStr, NoneAsEmptyString, + OneOrMany, Same, StringWithSeparator, +}; +use std::{ + collections::HashMap, + sync::{Mutex, RwLock}, +}; + +#[test] +fn test_basic_wrappers() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SBox(#[serde_as(as = "Box<DisplayFromStr>")] Box<u32>); + + is_equal(SBox(Box::new(123)), expect![[r#""123""#]]); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SRc(#[serde_as(as = "Rc<DisplayFromStr>")] Rc<u32>); + + is_equal(SRc(Rc::new(123)), expect![[r#""123""#]]); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize)] + struct SRcWeak(#[serde_as(as = "RcWeak<DisplayFromStr>")] RcWeak<u32>); + + check_serialization(SRcWeak(RcWeak::new()), expect![[r#"null"#]]); + let s: SRcWeak = serde_json::from_str("null").unwrap(); + assert!(s.0.upgrade().is_none()); + let s: SRcWeak = serde_json::from_str("\"123\"").unwrap(); + assert!(s.0.upgrade().is_none()); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SArc(#[serde_as(as = "Arc<DisplayFromStr>")] Arc<u32>); + + is_equal(SArc(Arc::new(123)), expect![[r#""123""#]]); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize)] + struct SArcWeak(#[serde_as(as = "ArcWeak<DisplayFromStr>")] ArcWeak<u32>); + + check_serialization(SArcWeak(ArcWeak::new()), expect![[r#"null"#]]); + let s: SArcWeak = serde_json::from_str("null").unwrap(); + assert!(s.0.upgrade().is_none()); + let s: SArcWeak = serde_json::from_str("\"123\"").unwrap(); + assert!(s.0.upgrade().is_none()); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SCell(#[serde_as(as = "Cell<DisplayFromStr>")] Cell<u32>); + + is_equal(SCell(Cell::new(123)), expect![[r#""123""#]]); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SRefCell(#[serde_as(as = "RefCell<DisplayFromStr>")] RefCell<u32>); + + is_equal(SRefCell(RefCell::new(123)), expect![[r#""123""#]]); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize)] + struct SMutex(#[serde_as(as = "Mutex<DisplayFromStr>")] Mutex<u32>); + + check_serialization(SMutex(Mutex::new(123)), expect![[r#""123""#]]); + let s: SMutex = serde_json::from_str("\"123\"").unwrap(); + assert_eq!(*s.0.lock().unwrap(), 123); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize)] + struct SRwLock(#[serde_as(as = "RwLock<DisplayFromStr>")] RwLock<u32>); + + let expected = expect![[r#""123""#]]; + check_serialization(SRwLock(RwLock::new(123)), expected); + let s: SRwLock = serde_json::from_str("\"123\"").unwrap(); + assert_eq!(*s.0.read().unwrap(), 123); +} + +#[test] +fn test_option() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "_")] Option<u32>); + + is_equal(S(None), expect![[r#"null"#]]); + is_equal(S(Some(9)), expect![[r#"9"#]]); + check_error_deserialization::<S>( + r#"{}"#, + expect![[r#"invalid type: map, expected u32 at line 1 column 0"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Struct { + #[serde_as(as = "_")] + value: Option<u32>, + } + check_error_deserialization::<Struct>( + r#"{}"#, + expect![[r#"missing field `value` at line 1 column 2"#]], + ); +} + +#[test] +fn test_result() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S( + #[serde_as(as = "Result<StringWithSeparator<CommaSeparator, u32>, DisplayFromStr>")] + Result<Vec<u32>, u32>, + ); + + is_equal( + S(Ok(vec![1, 2, 3])), + expect![[r#" + { + "Ok": "1,2,3" + }"#]], + ); + is_equal( + S(Err(9)), + expect![[r#" + { + "Err": "9" + }"#]], + ); + check_error_deserialization::<S>(r#"{}"#, expect![[r#"expected value at line 1 column 2"#]]); +} + +#[test] +fn test_display_fromstr() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "DisplayFromStr")] u32); + + is_equal(S(123), expect![[r#""123""#]]); +} + +#[test] +fn test_tuples() { + use std::net::IpAddr; + let ip = "1.2.3.4".parse().unwrap(); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S1(#[serde_as(as = "(DisplayFromStr,)")] (u32,)); + is_equal( + S1((1,)), + expect![[r#" + [ + "1" + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2a(#[serde_as(as = "(DisplayFromStr, DisplayFromStr)")] (u32, IpAddr)); + is_equal( + S2a((555_888, ip)), + expect![[r#" + [ + "555888", + "1.2.3.4" + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2b(#[serde_as(as = "(_, DisplayFromStr)")] (u32, IpAddr)); + is_equal( + S2b((987, ip)), + expect![[r#" + [ + 987, + "1.2.3.4" + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2c(#[serde_as(as = "(Same, DisplayFromStr)")] (u32, IpAddr)); + is_equal( + S2c((987, ip)), + expect![[r#" + [ + 987, + "1.2.3.4" + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S6( + #[serde_as(as = "(Same, Same, Same, Same, Same, Same)")] (u8, u16, u32, i8, i16, i32), + ); + is_equal( + S6((8, 16, 32, -8, 16, -32)), + expect![[r#" + [ + 8, + 16, + 32, + -8, + 16, + -32 + ]"#]], + ); +} + +#[test] +fn test_arrays() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S0(#[serde_as(as = "[DisplayFromStr; 0]")] [u32; 0]); + is_equal(S0([]), expect![[r#"[]"#]]); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S1(#[serde_as(as = "[DisplayFromStr; 1]")] [u32; 1]); + is_equal( + S1([1]), + expect![[r#" + [ + "1" + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2(#[serde_as(as = "[Same; 2]")] [u32; 2]); + is_equal( + S2([11, 22]), + expect![[r#" + [ + 11, + 22 + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S32(#[serde_as(as = "[Same; 32]")] [u32; 32]); + is_equal( + S32([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + ]), + expect![[r#" + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31 + ]"#]], + ); +} + +#[test] +fn test_sequence_like_types() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S1(#[serde_as(as = "Box<[Same]>")] Box<[u32]>); + is_equal( + S1(vec![1, 2, 3, 99].into()), + expect![[r#" + [ + 1, + 2, + 3, + 99 + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2(#[serde_as(as = "BTreeSet<Same>")] BTreeSet<u32>); + is_equal( + S2(vec![1, 2, 3, 99].into_iter().collect()), + expect![[r#" + [ + 1, + 2, + 3, + 99 + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S3(#[serde_as(as = "LinkedList<Same>")] LinkedList<u32>); + is_equal( + S3(vec![1, 2, 3, 99].into_iter().collect()), + expect![[r#" + [ + 1, + 2, + 3, + 99 + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S4(#[serde_as(as = "Vec<Same>")] Vec<u32>); + is_equal( + S4(vec![1, 2, 3, 99]), + expect![[r#" + [ + 1, + 2, + 3, + 99 + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S5(#[serde_as(as = "VecDeque<Same>")] VecDeque<u32>); + is_equal( + S5(vec![1, 2, 3, 99].into()), + expect![[r#" + [ + 1, + 2, + 3, + 99 + ]"#]], + ); +} + +#[test] +fn test_none_as_empty_string() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "NoneAsEmptyString")] Option<String>); + + is_equal(S(None), expect![[r#""""#]]); + is_equal(S(Some("Hello".to_string())), expect![[r#""Hello""#]]); +} + +#[test] +fn test_bytes_or_string() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "BytesOrString")] Vec<u8>); + + is_equal( + S(vec![1, 2, 3]), + expect![[r#" + [ + 1, + 2, + 3 + ]"#]], + ); + check_deserialization(S(vec![72, 101, 108, 108, 111]), r#""Hello""#); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SVec(#[serde_as(as = "Vec<BytesOrString>")] Vec<Vec<u8>>); + + is_equal( + SVec(vec![vec![1, 2, 3]]), + expect![[r#" + [ + [ + 1, + 2, + 3 + ] + ]"#]], + ); + check_deserialization( + SVec(vec![ + vec![72, 101, 108, 108, 111], + vec![87, 111, 114, 108, 100], + vec![1, 2, 3], + ]), + r#"["Hello","World",[1,2,3]]"#, + ); +} + +#[test] +fn string_with_separator() { + use serde_with::{rust::StringWithSeparator, CommaSeparator, SpaceSeparator}; + + #[serde_as] + #[derive(Deserialize, Serialize)] + struct A { + #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")] + tags: Vec<String>, + #[serde_as(as = "StringWithSeparator::<CommaSeparator, String>")] + // more_tags: Vec<String>, + more_tags: BTreeSet<String>, + } + + let v: A = serde_json::from_str( + r##"{ + "tags": "#hello #world", + "more_tags": "foo,bar,bar" +}"##, + ) + .unwrap(); + assert_eq!(vec!["#hello", "#world"], v.tags); + assert_eq!(2, v.more_tags.len()); + + let x = A { + tags: vec!["1".to_string(), "2".to_string(), "3".to_string()], + more_tags: Default::default(), + }; + assert_eq!( + r#"{"tags":"1 2 3","more_tags":""}"#, + serde_json::to_string(&x).unwrap() + ); +} + +#[test] +fn test_vec_skip_error() { + use serde_with::VecSkipError; + + #[serde_as] + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S { + tag: String, + #[serde_as(as = "VecSkipError<_>")] + values: Vec<u8>, + } + + check_deserialization( + S { + tag: "type".into(), + values: vec![0, 1], + }, + r#"{"tag":"type","values":[0, "str", 1, [10, 11], -2, {}, 300]}"#, + ); + is_equal( + S { + tag: "round-trip".into(), + values: vec![0, 255], + }, + expect![[r#" + { + "tag": "round-trip", + "values": [ + 0, + 255 + ] + }"#]], + ); +} + +#[test] +fn test_serialize_reference() { + #[serde_as] + #[derive(Debug, Serialize)] + struct S1<'a>(#[serde_as(as = "Vec<DisplayFromStr>")] &'a Vec<u32>); + check_serialization( + S1(&vec![1, 2]), + expect![[r#" + [ + "1", + "2" + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize)] + struct S1a<'a>(#[serde_as(as = "&Vec<DisplayFromStr>")] &'a Vec<u32>); + check_serialization( + S1(&vec![1, 2]), + expect![[r#" + [ + "1", + "2" + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize)] + struct S1Mut<'a>(#[serde_as(as = "Vec<DisplayFromStr>")] &'a mut Vec<u32>); + check_serialization( + S1(&vec![1, 2]), + expect![[r#" + [ + "1", + "2" + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize)] + struct S1aMut<'a>(#[serde_as(as = "&mut Vec<DisplayFromStr>")] &'a mut Vec<u32>); + check_serialization( + S1(&vec![1, 2]), + expect![[r#" + [ + "1", + "2" + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize)] + struct S2<'a>(#[serde_as(as = "&[DisplayFromStr]")] &'a [u32]); + check_serialization( + S2(&[1, 2]), + expect![[r#" + [ + "1", + "2" + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize)] + struct S3<'a>( + #[serde_as(as = "&BTreeMap<DisplayFromStr, DisplayFromStr>")] &'a BTreeMap<bool, u32>, + ); + let bmap = vec![(false, 123), (true, 456)].into_iter().collect(); + check_serialization( + S3(&bmap), + expect![[r#" + { + "false": "123", + "true": "456" + }"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize)] + struct S4<'a>(#[serde_as(as = "&Vec<(_, DisplayFromStr)>")] &'a BTreeMap<bool, u32>); + let bmap = vec![(false, 123), (true, 456)].into_iter().collect(); + check_serialization( + S4(&bmap), + expect![[r#" + [ + [ + false, + "123" + ], + [ + true, + "456" + ] + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize)] + struct S5<'a>( + #[serde_as(as = "&BTreeMap<DisplayFromStr, &Vec<(_, _)>>")] + &'a Vec<(u32, &'a BTreeMap<bool, String>)>, + ); + let bmap0 = vec![(false, "123".to_string()), (true, "456".to_string())] + .into_iter() + .collect(); + let bmap1 = vec![(true, "Hello".to_string()), (false, "World".to_string())] + .into_iter() + .collect(); + let vec = vec![(111, &bmap0), (999, &bmap1)]; + check_serialization( + S5(&vec), + expect![[r#" + { + "111": [ + [ + false, + "123" + ], + [ + true, + "456" + ] + ], + "999": [ + [ + false, + "World" + ], + [ + true, + "Hello" + ] + ] + }"#]], + ); +} + +#[test] +fn test_big_arrays() { + // Single Big Array + #[serde_as] + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct S1(#[serde_as(as = "[_; 64]")] [u8; 64]); + is_equal_compact( + S1([0; 64]), + expect![[ + r#"[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]"# + ]], + ); + // Too few entries + check_error_deserialization::<S1>( + r#"[0,0,0,0,0,0,0,0,0,0,0,0,0,0]"#, + expect![[r#"invalid length 14, expected an array of size 64 at line 1 column 29"#]], + ); + // Too many entries + check_error_deserialization::<S1>( + r#"[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]"#, + expect![[r#"trailing characters at line 1 column 130"#]], + ); + + // Single Big Array + #[serde_as] + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct S2(#[serde_as(as = "[DisplayFromStr; 40]")] [u8; 40]); + is_equal_compact( + S2([0; 40]), + expect![[ + r#"["0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"]"# + ]], + ); + + // Nested Big Arrays + #[serde_as] + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct S3(#[serde_as(as = "[[_; 34]; 33]")] [[u8; 34]; 33]); + is_equal_compact( + S3([[0; 34]; 33]), + expect![[ + r#"[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]"# + ]], + ); +} + +#[test] +fn test_bytes() { + // The test case is copied from + // https://github.com/serde-rs/bytes/blob/cbae606b9dc225fc094b031cc84eac9493da2058/tests/test_derive.rs + // Original code by @dtolnay + + use alloc::borrow::Cow; + use serde_test::{assert_de_tokens, assert_tokens, Token}; + use serde_with::Bytes; + + #[serde_as] + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Test<'a> { + #[serde_as(as = "Bytes")] + array: [u8; 52], + + #[serde_as(as = "Bytes")] + slice: &'a [u8], + + #[serde_as(as = "Bytes")] + vec: Vec<u8>, + + #[serde_as(as = "Bytes")] + cow_slice: Cow<'a, [u8]>, + + #[serde_as(as = "Box<Bytes>")] + boxed_array: Box<[u8; 52]>, + + #[serde_as(as = "Bytes")] + boxed_array2: Box<[u8; 52]>, + + #[serde_as(as = "Bytes")] + boxed_slice: Box<[u8]>, + + #[serde_as(as = "Option<Bytes>")] + opt_slice: Option<&'a [u8]>, + + #[serde_as(as = "Option<Bytes>")] + opt_vec: Option<Vec<u8>>, + + #[serde_as(as = "Option<Bytes>")] + opt_cow_slice: Option<Cow<'a, [u8]>>, + } + + let test = Test { + array: *b"ABCDEFGHIJKLMNOPQRSTUVWXZYabcdefghijklmnopqrstuvwxyz", + slice: b"...", + vec: b"...".to_vec(), + cow_slice: Cow::Borrowed(b"..."), + boxed_array: Box::new(*b"ABCDEFGHIJKLMNOPQRSTUVWXZYabcdefghijklmnopqrstuvwxyz"), + boxed_array2: Box::new(*b"ABCDEFGHIJKLMNOPQRSTUVWXZYabcdefghijklmnopqrstuvwxyz"), + boxed_slice: b"...".to_vec().into_boxed_slice(), + opt_slice: Some(b"..."), + opt_vec: Some(b"...".to_vec()), + opt_cow_slice: Some(Cow::Borrowed(b"...")), + }; + + assert_tokens( + &test, + &[ + Token::Struct { + name: "Test", + len: 10, + }, + Token::Str("array"), + Token::BorrowedBytes(b"ABCDEFGHIJKLMNOPQRSTUVWXZYabcdefghijklmnopqrstuvwxyz"), + Token::Str("slice"), + Token::BorrowedBytes(b"..."), + Token::Str("vec"), + Token::Bytes(b"..."), + Token::Str("cow_slice"), + Token::BorrowedBytes(b"..."), + Token::Str("boxed_array"), + Token::BorrowedBytes(b"ABCDEFGHIJKLMNOPQRSTUVWXZYabcdefghijklmnopqrstuvwxyz"), + Token::Str("boxed_array2"), + Token::BorrowedBytes(b"ABCDEFGHIJKLMNOPQRSTUVWXZYabcdefghijklmnopqrstuvwxyz"), + Token::Str("boxed_slice"), + Token::Bytes(b"..."), + Token::Str("opt_slice"), + Token::Some, + Token::BorrowedBytes(b"..."), + Token::Str("opt_vec"), + Token::Some, + Token::Bytes(b"..."), + Token::Str("opt_cow_slice"), + Token::Some, + Token::BorrowedBytes(b"..."), + Token::StructEnd, + ], + ); + + // Test string deserialization + assert_de_tokens( + &test, + &[ + Token::Struct { + name: "Test", + len: 10, + }, + Token::Str("array"), + Token::BorrowedStr("ABCDEFGHIJKLMNOPQRSTUVWXZYabcdefghijklmnopqrstuvwxyz"), + Token::Str("slice"), + Token::BorrowedStr("..."), + Token::Str("vec"), + Token::Bytes(b"..."), + Token::Str("cow_slice"), + Token::BorrowedStr("..."), + Token::Str("boxed_array"), + Token::BorrowedStr("ABCDEFGHIJKLMNOPQRSTUVWXZYabcdefghijklmnopqrstuvwxyz"), + Token::Str("boxed_array2"), + Token::BorrowedStr("ABCDEFGHIJKLMNOPQRSTUVWXZYabcdefghijklmnopqrstuvwxyz"), + Token::Str("boxed_slice"), + Token::Bytes(b"..."), + Token::Str("opt_slice"), + Token::Some, + Token::BorrowedStr("..."), + Token::Str("opt_vec"), + Token::Some, + Token::Bytes(b"..."), + Token::Str("opt_cow_slice"), + Token::Some, + Token::BorrowedStr("..."), + Token::StructEnd, + ], + ); +} + +#[test] +fn test_one_or_many_prefer_one() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S1Vec(#[serde_as(as = "OneOrMany<_>")] Vec<u32>); + + // Normal + is_equal(S1Vec(vec![]), expect![[r#"[]"#]]); + is_equal(S1Vec(vec![1]), expect![[r#"1"#]]); + is_equal( + S1Vec(vec![1, 2, 3]), + expect![[r#" + [ + 1, + 2, + 3 + ]"#]], + ); + check_deserialization(S1Vec(vec![1]), r#"1"#); + check_deserialization(S1Vec(vec![1]), r#"[1]"#); + check_error_deserialization::<S1Vec>(r#"{}"#, expect![[r#"a list or single element"#]]); + check_error_deserialization::<S1Vec>(r#""xx""#, expect![[r#"a list or single element"#]]); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2Vec(#[serde_as(as = "OneOrMany<DisplayFromStr>")] Vec<u32>); + + // Normal + is_equal(S2Vec(vec![]), expect![[r#"[]"#]]); + is_equal(S2Vec(vec![1]), expect![[r#""1""#]]); + is_equal( + S2Vec(vec![1, 2, 3]), + expect![[r#" + [ + "1", + "2", + "3" + ]"#]], + ); + check_deserialization(S2Vec(vec![1]), r#""1""#); + check_deserialization(S2Vec(vec![1]), r#"["1"]"#); + check_error_deserialization::<S2Vec>(r#"{}"#, expect![[r#"a list or single element"#]]); + check_error_deserialization::<S2Vec>(r#""xx""#, expect![[r#"a list or single element"#]]); +} + +#[test] +fn test_one_or_many_prefer_many() { + use serde_with::formats::PreferMany; + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S1Vec(#[serde_as(as = "OneOrMany<_, PreferMany>")] Vec<u32>); + + // Normal + is_equal(S1Vec(vec![]), expect![[r#"[]"#]]); + is_equal( + S1Vec(vec![1]), + expect![[r#" + [ + 1 + ]"#]], + ); + is_equal( + S1Vec(vec![1, 2, 3]), + expect![[r#" + [ + 1, + 2, + 3 + ]"#]], + ); + check_deserialization(S1Vec(vec![1]), r#"1"#); + check_deserialization(S1Vec(vec![1]), r#"[1]"#); + check_error_deserialization::<S1Vec>(r#"{}"#, expect![[r#"a list or single element"#]]); + check_error_deserialization::<S1Vec>(r#""xx""#, expect![[r#"a list or single element"#]]); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2Vec(#[serde_as(as = "OneOrMany<DisplayFromStr, PreferMany>")] Vec<u32>); + + // Normal + is_equal(S2Vec(vec![]), expect![[r#"[]"#]]); + is_equal( + S2Vec(vec![1]), + expect![[r#" + [ + "1" + ]"#]], + ); + is_equal( + S2Vec(vec![1, 2, 3]), + expect![[r#" + [ + "1", + "2", + "3" + ]"#]], + ); + check_deserialization(S2Vec(vec![1]), r#""1""#); + check_deserialization(S2Vec(vec![1]), r#"["1"]"#); + check_error_deserialization::<S2Vec>(r#"{}"#, expect![[r#"a list or single element"#]]); + check_error_deserialization::<S2Vec>(r#""xx""#, expect![[r#"a list or single element"#]]); +} + +/// Test that Cow borrows from the input +#[test] +fn test_borrow_cow_str() { + use alloc::borrow::Cow; + use serde_test::{assert_ser_tokens, Token}; + use serde_with::BorrowCow; + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S1<'a> { + #[serde_as(as = "BorrowCow")] + cow: Cow<'a, str>, + #[serde_as(as = "Option<BorrowCow>")] + opt: Option<Cow<'a, str>>, + #[serde_as(as = "Box<BorrowCow>")] + b: Box<Cow<'a, str>>, + #[serde_as(as = "[BorrowCow; 1]")] + arr: [Cow<'a, str>; 1], + } + + assert_ser_tokens( + &S1 { + cow: "abc".into(), + opt: Some("foo".into()), + b: Box::new("bar".into()), + arr: ["def".into()], + }, + &[ + Token::Struct { name: "S1", len: 4 }, + Token::Str("cow"), + Token::BorrowedStr("abc"), + Token::Str("opt"), + Token::Some, + Token::BorrowedStr("foo"), + Token::Str("b"), + Token::BorrowedStr("bar"), + Token::Str("arr"), + Token::Tuple { len: 1 }, + Token::BorrowedStr("def"), + Token::TupleEnd, + Token::StructEnd, + ], + ); + let s1: S1<'_> = serde_json::from_str( + r#"{ + "cow": "abc", + "opt": "foo", + "b": "bar", + "arr": ["def"] + }"#, + ) + .unwrap(); + assert!(matches!(s1.cow, Cow::Borrowed(_))); + assert!(matches!(s1.opt, Some(Cow::Borrowed(_)))); + assert!(matches!(*s1.b, Cow::Borrowed(_))); + assert!(matches!(s1.arr, [Cow::Borrowed(_)])); + let s1: S1<'_> = serde_json::from_str( + r#"{ + "cow": "a\"c", + "opt": "f\"o", + "b": "b\"r", + "arr": ["d\"f"] + }"#, + ) + .unwrap(); + assert!(matches!(s1.cow, Cow::Owned(_))); + assert!(matches!(s1.opt, Some(Cow::Owned(_)))); + assert!(matches!(*s1.b, Cow::Owned(_))); + assert!(matches!(s1.arr, [Cow::Owned(_)])); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2<'a> { + #[serde_as(as = "BorrowCow")] + cow: Cow<'a, [u8]>, + #[serde_as(as = "Option<BorrowCow>")] + opt: Option<Cow<'a, [u8]>>, + } + + assert_ser_tokens( + &S2 { + cow: b"abc"[..].into(), + opt: Some(b"foo"[..].into()), + }, + &[ + Token::Struct { name: "S2", len: 2 }, + Token::Str("cow"), + Token::Seq { len: Some(3) }, + Token::U8(b'a'), + Token::U8(b'b'), + Token::U8(b'c'), + Token::SeqEnd, + Token::Str("opt"), + Token::Some, + Token::Seq { len: Some(3) }, + Token::U8(b'f'), + Token::U8(b'o'), + Token::U8(b'o'), + Token::SeqEnd, + Token::StructEnd, + ], + ); + let tokens = &[ + Token::Struct { name: "S2", len: 2 }, + Token::Str("cow"), + Token::BorrowedBytes(b"abc"), + Token::Str("opt"), + Token::Some, + Token::BorrowedBytes(b"foo"), + Token::StructEnd, + ]; + let mut deser = serde_test::Deserializer::new(tokens); + let s2 = S2::deserialize(&mut deser).unwrap(); + assert!(matches!(s2.cow, Cow::Borrowed(_))); + assert!(matches!(s2.opt, Some(Cow::Borrowed(_)))); + + // Check that a manual borrow works too + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S3<'a>( + #[serde(borrow = "'a")] + #[serde_as(as = "BorrowCow")] + Cow<'a, [u8]>, + // TODO add a test for Cow<'b, [u8; N]> + // #[serde_as(as = "BorrowCow")] + // Cow<'b, [u8; N]>, + ); + let tokens = &[ + Token::NewtypeStruct { name: "S3" }, + Token::BorrowedBytes(b"abc"), + ]; + + let mut deser = serde_test::Deserializer::new(tokens); + let s3 = S3::deserialize(&mut deser).unwrap(); + assert!(matches!(s3.0, Cow::Borrowed(_))); +} + +#[test] +fn test_boolfromint() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "BoolFromInt")] bool); + + is_equal(S(false), expect![[r#"0"#]]); + is_equal(S(true), expect![[r#"1"#]]); + check_error_deserialization::<S>( + "2", + expect![[r#"invalid value: integer `2`, expected 0 or 1 at line 1 column 1"#]], + ); + check_error_deserialization::<S>( + "-100", + expect![[r#"invalid value: integer `-100`, expected 0 or 1 at line 1 column 4"#]], + ); + check_error_deserialization::<S>( + "18446744073709551615", + expect![[ + r#"invalid value: integer `18446744073709551615`, expected 0 or 1 at line 1 column 20"# + ]], + ); + check_error_deserialization::<S>( + r#""""#, + expect![[r#"invalid type: string "", expected an integer 0 or 1 at line 1 column 2"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SStrict(#[serde_as(as = "BoolFromInt<Strict>")] bool); + + is_equal(SStrict(false), expect![[r#"0"#]]); + is_equal(SStrict(true), expect![[r#"1"#]]); + check_error_deserialization::<SStrict>( + "2", + expect![[r#"invalid value: integer `2`, expected 0 or 1 at line 1 column 1"#]], + ); + check_error_deserialization::<SStrict>( + "-100", + expect![[r#"invalid value: integer `-100`, expected 0 or 1 at line 1 column 4"#]], + ); + check_error_deserialization::<SStrict>( + "18446744073709551615", + expect![[ + r#"invalid value: integer `18446744073709551615`, expected 0 or 1 at line 1 column 20"# + ]], + ); + check_error_deserialization::<SStrict>( + r#""""#, + expect![[r#"invalid type: string "", expected an integer 0 or 1 at line 1 column 2"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SFlexible(#[serde_as(as = "BoolFromInt<Flexible>")] bool); + + is_equal(SFlexible(false), expect![[r#"0"#]]); + is_equal(SFlexible(true), expect![[r#"1"#]]); + check_deserialization::<SFlexible>(SFlexible(true), "2"); + check_deserialization::<SFlexible>(SFlexible(true), "-100"); + check_deserialization::<SFlexible>(SFlexible(true), "18446744073709551615"); + check_error_deserialization::<SFlexible>( + r#""""#, + expect![[r#"invalid type: string "", expected an integer at line 1 column 2"#]], + ); +} diff --git a/third_party/rust/serde_with/tests/serde_as/map_tuple_list.rs b/third_party/rust/serde_with/tests/serde_as/map_tuple_list.rs new file mode 100644 index 0000000000..332dcf676e --- /dev/null +++ b/third_party/rust/serde_with/tests/serde_as/map_tuple_list.rs @@ -0,0 +1,272 @@ +use super::*; +use std::net::IpAddr; + +#[test] +fn test_map_as_tuple_list() { + let ip = "1.2.3.4".parse().unwrap(); + let ip2 = "255.255.255.255".parse().unwrap(); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SB(#[serde_as(as = "Vec<(DisplayFromStr, DisplayFromStr)>")] BTreeMap<u32, IpAddr>); + + let map: BTreeMap<_, _> = vec![(1, ip), (10, ip), (200, ip2)].into_iter().collect(); + is_equal( + SB(map.clone()), + expect![[r#" + [ + [ + "1", + "1.2.3.4" + ], + [ + "10", + "1.2.3.4" + ], + [ + "200", + "255.255.255.255" + ] + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SB2(#[serde_as(as = "Vec<(Same, DisplayFromStr)>")] BTreeMap<u32, IpAddr>); + + is_equal( + SB2(map), + expect![[r#" + [ + [ + 1, + "1.2.3.4" + ], + [ + 10, + "1.2.3.4" + ], + [ + 200, + "255.255.255.255" + ] + ]"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SH(#[serde_as(as = "Vec<(DisplayFromStr, DisplayFromStr)>")] HashMap<u32, IpAddr>); + + // HashMap serialization tests with more than 1 entry are unreliable + let map1: HashMap<_, _> = vec![(200, ip2)].into_iter().collect(); + let map: HashMap<_, _> = vec![(1, ip), (10, ip), (200, ip2)].into_iter().collect(); + is_equal( + SH(map1.clone()), + expect![[r#" + [ + [ + "200", + "255.255.255.255" + ] + ]"#]], + ); + check_deserialization( + SH(map.clone()), + r#"[["1","1.2.3.4"],["10","1.2.3.4"],["200","255.255.255.255"]]"#, + ); + check_error_deserialization::<SH>( + r#"{"200":"255.255.255.255"}"#, + expect![[r#"invalid type: map, expected a sequence at line 1 column 0"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SH2(#[serde_as(as = "Vec<(Same, DisplayFromStr)>")] HashMap<u32, IpAddr>); + + is_equal( + SH2(map1), + expect![[r#" + [ + [ + 200, + "255.255.255.255" + ] + ]"#]], + ); + check_deserialization( + SH2(map), + r#"[[1,"1.2.3.4"],[10,"1.2.3.4"],[200,"255.255.255.255"]]"#, + ); + check_error_deserialization::<SH2>( + r#"1"#, + expect![[r#"invalid type: integer `1`, expected a sequence at line 1 column 1"#]], + ); +} + +#[test] +fn test_tuple_list_as_map() { + let ip = "1.2.3.4".parse().unwrap(); + let ip2 = "255.255.255.255".parse().unwrap(); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SH(#[serde_as(as = "HashMap<DisplayFromStr, DisplayFromStr>")] Vec<(u32, IpAddr)>); + + is_equal( + SH(vec![(1, ip), (10, ip), (200, ip2)]), + expect![[r#" + { + "1": "1.2.3.4", + "10": "1.2.3.4", + "200": "255.255.255.255" + }"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SB(#[serde_as(as = "BTreeMap<DisplayFromStr, DisplayFromStr>")] Vec<(u32, IpAddr)>); + + is_equal( + SB(vec![(1, ip), (10, ip), (200, ip2)]), + expect![[r#" + { + "1": "1.2.3.4", + "10": "1.2.3.4", + "200": "255.255.255.255" + }"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SD(#[serde_as(as = "BTreeMap<DisplayFromStr, DisplayFromStr>")] VecDeque<(u32, IpAddr)>); + + is_equal( + SD(vec![(1, ip), (10, ip), (200, ip2)].into()), + expect![[r#" + { + "1": "1.2.3.4", + "10": "1.2.3.4", + "200": "255.255.255.255" + }"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Sll( + #[serde_as(as = "HashMap<DisplayFromStr, DisplayFromStr>")] LinkedList<(u32, IpAddr)>, + ); + + is_equal( + Sll(vec![(1, ip), (10, ip), (200, ip2)].into_iter().collect()), + expect![[r#" + { + "1": "1.2.3.4", + "10": "1.2.3.4", + "200": "255.255.255.255" + }"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct SO(#[serde_as(as = "HashMap<DisplayFromStr, DisplayFromStr>")] Option<(u32, IpAddr)>); + + is_equal( + SO(Some((1, ip))), + expect![[r#" + { + "1": "1.2.3.4" + }"#]], + ); + is_equal(SO(None), expect![[r#"{}"#]]); +} + +#[test] +fn test_tuple_array_as_map() { + #[serde_as] + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct S1(#[serde_as(as = "BTreeMap<_, _>")] [(u8, u8); 1]); + is_equal( + S1([(1, 2)]), + expect![[r#" + { + "1": 2 + }"#]], + ); + + #[serde_as] + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct S2(#[serde_as(as = "HashMap<_, _>")] [(u8, u8); 33]); + is_equal( + S2([ + (0, 0), + (1, 1), + (2, 2), + (3, 3), + (4, 4), + (5, 5), + (6, 6), + (7, 7), + (8, 8), + (9, 9), + (10, 10), + (11, 11), + (12, 12), + (13, 13), + (14, 14), + (15, 15), + (16, 16), + (17, 17), + (18, 18), + (19, 19), + (20, 20), + (21, 21), + (22, 22), + (23, 23), + (24, 24), + (25, 25), + (26, 26), + (27, 27), + (28, 28), + (29, 29), + (30, 30), + (31, 31), + (32, 32), + ]), + expect![[r#" + { + "0": 0, + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5, + "6": 6, + "7": 7, + "8": 8, + "9": 9, + "10": 10, + "11": 11, + "12": 12, + "13": 13, + "14": 14, + "15": 15, + "16": 16, + "17": 17, + "18": 18, + "19": 19, + "20": 20, + "21": 21, + "22": 22, + "23": 23, + "24": 24, + "25": 25, + "26": 26, + "27": 27, + "28": 28, + "29": 29, + "30": 30, + "31": 31, + "32": 32 + }"#]], + ); +} diff --git a/third_party/rust/serde_with/tests/serde_as/pickfirst.rs b/third_party/rust/serde_with/tests/serde_as/pickfirst.rs new file mode 100644 index 0000000000..ed3cacb1dd --- /dev/null +++ b/third_party/rust/serde_with/tests/serde_as/pickfirst.rs @@ -0,0 +1,134 @@ +use super::*; +use serde_with::{CommaSeparator, PickFirst, SpaceSeparator, StringWithSeparator}; + +#[test] +fn test_pick_first_two() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "PickFirst<(_, DisplayFromStr)>")] u32); + + is_equal(S(123), expect![[r#"123"#]]); + check_deserialization(S(123), r#""123""#); + check_error_deserialization::<S>( + r#""Abc""#, + expect![[r#"PickFirst could not deserialize data"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2(#[serde_as(as = "PickFirst<(DisplayFromStr, _)>")] u32); + + is_equal(S2(123), expect![[r#""123""#]]); + check_deserialization(S2(123), r#"123"#); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S3( + #[serde_as(as = "PickFirst<(_, StringWithSeparator::<SpaceSeparator, String>,)>")] + Vec<String>, + ); + is_equal( + S3(vec!["A".to_string(), "B".to_string(), "C".to_string()]), + expect![[r#" + [ + "A", + "B", + "C" + ]"#]], + ); + check_deserialization( + S3(vec!["A".to_string(), "B".to_string(), "C".to_string()]), + r#""A B C""#, + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S4( + #[serde_as(as = "PickFirst<(StringWithSeparator::<CommaSeparator, String>, _,)>")] + Vec<String>, + ); + is_equal( + S4(vec!["A".to_string(), "B".to_string(), "C".to_string()]), + expect![[r#""A,B,C""#]], + ); + check_deserialization( + S4(vec!["A".to_string(), "B".to_string(), "C".to_string()]), + r#"["A", "B", "C"]"#, + ); +} + +#[test] +fn test_pick_first_three() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S( + #[serde_as( + as = "PickFirst<(_, Vec<DisplayFromStr>, StringWithSeparator::<CommaSeparator, u32>)>" + )] + Vec<u32>, + ); + is_equal( + S(vec![1, 2, 3]), + expect![[r#" + [ + 1, + 2, + 3 + ]"#]], + ); + check_deserialization( + S(vec![1, 2, 3]), + r#" + [ + "1", + "2", + "3" + ]"#, + ); + check_deserialization(S(vec![1, 2, 3]), r#""1,2,3""#); + check_error_deserialization::<S>( + r#""Abc""#, + expect![[r#"PickFirst could not deserialize data"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S2( + #[serde_as( + as = "PickFirst<(StringWithSeparator::<CommaSeparator, u32>, _, Vec<DisplayFromStr>)>" + )] + Vec<u32>, + ); + is_equal(S2(vec![1, 2, 3]), expect![[r#""1,2,3""#]]); + check_deserialization( + S2(vec![1, 2, 3]), + r#" + [ + "1", + "2", + "3" + ]"#, + ); + check_deserialization( + S2(vec![1, 2, 3]), + r#" + [ + 1, + 2, + 3 + ]"#, + ); +} + +#[test] +fn test_pick_first_four() { + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = "PickFirst<(_, _, _, _)>")] u32); + + is_equal(S(123), expect![[r#"123"#]]); + check_error_deserialization::<S>( + r#""Abc""#, + expect![[r#"PickFirst could not deserialize data"#]], + ); +} diff --git a/third_party/rust/serde_with/tests/serde_as/serde_as_macro.rs b/third_party/rust/serde_with/tests/serde_as/serde_as_macro.rs new file mode 100644 index 0000000000..b180079d46 --- /dev/null +++ b/third_party/rust/serde_with/tests/serde_as/serde_as_macro.rs @@ -0,0 +1,191 @@ +use super::*; + +/// Test that the [`serde_as`] macro can replace the `_` type and the resulting code compiles. +#[test] +fn test_serde_as_macro_replace_infer_type() { + #[serde_as] + #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] + struct Data { + #[serde_as(as = "_")] + a: u32, + #[serde_as(as = "std::vec::Vec<_>")] + b: Vec<u32>, + #[serde_as(as = "Vec<(_, _)>")] + c: Vec<(u32, String)>, + #[serde_as(as = "[_; 2]")] + d: [u32; 2], + #[serde_as(as = "Box<[_]>")] + e: Box<[u32]>, + } + + is_equal( + Data { + a: 10, + b: vec![20, 33], + c: vec![(40, "Hello".into()), (55, "World".into()), (60, "!".into())], + d: [70, 88], + e: vec![99, 100, 110].into_boxed_slice(), + }, + expect![[r#" + { + "a": 10, + "b": [ + 20, + 33 + ], + "c": [ + [ + 40, + "Hello" + ], + [ + 55, + "World" + ], + [ + 60, + "!" + ] + ], + "d": [ + 70, + 88 + ], + "e": [ + 99, + 100, + 110 + ] + }"#]], + ); +} + +/// Test that the [`serde_as`] macro supports `deserialize_as` +#[test] +fn test_serde_as_macro_deserialize() { + #[serde_as] + #[derive(Debug, Eq, PartialEq, Deserialize)] + struct Data { + #[serde_as(deserialize_as = "DisplayFromStr")] + a: u32, + #[serde_as(deserialize_as = "Vec<DisplayFromStr>")] + b: Vec<u32>, + #[serde_as(deserialize_as = "(DisplayFromStr, _)")] + c: (u32, u32), + } + + check_deserialization( + Data { + a: 10, + b: vec![20, 33], + c: (40, 55), + }, + r##"{ + "a": "10", + "b": [ + "20", + "33" + ], + "c": [ + "40", + 55 + ] +}"##, + ); +} + +/// Test that the [`serde_as`] macro supports `serialize_as` +#[test] +fn test_serde_as_macro_serialize() { + #[serde_as] + #[derive(Debug, Eq, PartialEq, Serialize)] + struct Data { + #[serde_as(serialize_as = "DisplayFromStr")] + a: u32, + #[serde_as(serialize_as = "Vec<DisplayFromStr>")] + b: Vec<u32>, + #[serde_as(serialize_as = "(DisplayFromStr, _)")] + c: (u32, u32), + } + + check_serialization( + Data { + a: 10, + b: vec![20, 33], + c: (40, 55), + }, + expect![[r#" + { + "a": "10", + "b": [ + "20", + "33" + ], + "c": [ + "40", + 55 + ] + }"#]], + ); +} + +/// Test that the [`serde_as`] macro supports `serialize_as` and `deserialize_as` +#[test] +fn test_serde_as_macro_serialize_deserialize() { + #[serde_as] + #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] + struct Data { + #[serde_as(serialize_as = "DisplayFromStr", deserialize_as = "DisplayFromStr")] + a: u32, + #[serde_as( + serialize_as = "Vec<DisplayFromStr>", + deserialize_as = "Vec<DisplayFromStr>" + )] + b: Vec<u32>, + #[serde_as( + serialize_as = "(DisplayFromStr, _)", + deserialize_as = "(DisplayFromStr, _)" + )] + c: (u32, u32), + } + + is_equal( + Data { + a: 10, + b: vec![20, 33], + c: (40, 55), + }, + expect![[r#" + { + "a": "10", + "b": [ + "20", + "33" + ], + "c": [ + "40", + 55 + ] + }"#]], + ); +} + +/// Test that the [`serde_as`] macro works correctly if applied multiple times to a field +#[test] +fn test_serde_as_macro_multiple_field_attributes() { + #[serde_as] + #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] + struct Data { + #[serde_as(serialize_as = "DisplayFromStr")] + #[serde_as(deserialize_as = "DisplayFromStr")] + a: u32, + } + + is_equal( + Data { a: 10 }, + expect![[r#" + { + "a": "10" + }"#]], + ); +} diff --git a/third_party/rust/serde_with/tests/serde_as/serde_conv.rs b/third_party/rust/serde_with/tests/serde_as/serde_conv.rs new file mode 100644 index 0000000000..1e510f72a3 --- /dev/null +++ b/third_party/rust/serde_with/tests/serde_as/serde_conv.rs @@ -0,0 +1,52 @@ +use super::*; +use serde_with::serde_conv; + +#[test] +fn test_bool_as_string() { + serde_conv!(BoolAsString, bool, |x: &bool| x.to_string(), |x: String| x + .parse()); + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct SWith(#[serde(with = "BoolAsString")] bool); + + is_equal(SWith(false), expect![[r#""false""#]]); + is_equal(SWith(true), expect![[r#""true""#]]); + check_error_deserialization::<SWith>( + "123", + expect![[r#"invalid type: integer `123`, expected a string at line 1 column 3"#]], + ); + + #[serde_as] + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct SAs(#[serde_as(as = "BoolAsString")] bool); + + is_equal(SAs(false), expect![[r#""false""#]]); + is_equal(SAs(true), expect![[r#""true""#]]); + check_error_deserialization::<SAs>( + "123", + expect![[r#"invalid type: integer `123`, expected a string at line 1 column 3"#]], + ); + + #[serde_as] + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct SAsVec(#[serde_as(as = "Vec<BoolAsString>")] Vec<bool>); + + is_equal( + SAsVec(vec![false]), + expect![[r#" + [ + "false" + ]"#]], + ); + is_equal( + SAsVec(vec![true]), + expect![[r#" + [ + "true" + ]"#]], + ); + check_error_deserialization::<SAsVec>( + "123", + expect![[r#"invalid type: integer `123`, expected a sequence at line 1 column 3"#]], + ); +} diff --git a/third_party/rust/serde_with/tests/serde_as/time.rs b/third_party/rust/serde_with/tests/serde_as/time.rs new file mode 100644 index 0000000000..91808b4d8c --- /dev/null +++ b/third_party/rust/serde_with/tests/serde_as/time.rs @@ -0,0 +1,521 @@ +use super::*; +use core::time::Duration; +use serde_with::{ + DurationMicroSeconds, DurationMicroSecondsWithFrac, DurationMilliSeconds, + DurationMilliSecondsWithFrac, DurationNanoSeconds, DurationNanoSecondsWithFrac, + DurationSeconds, DurationSecondsWithFrac, TimestampMicroSeconds, TimestampMicroSecondsWithFrac, + TimestampMilliSeconds, TimestampMilliSecondsWithFrac, TimestampNanoSeconds, + TimestampNanoSecondsWithFrac, TimestampSeconds, TimestampSecondsWithFrac, +}; +use std::time::SystemTime; + +#[test] +fn test_duration_seconds() { + let zero = Duration::new(0, 0); + let one_second = Duration::new(1, 0); + let half_second = Duration::new(0, 500_000_000); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct IntStrict(#[serde_as(as = "DurationSeconds")] Duration); + + is_equal(IntStrict(zero), expect![[r#"0"#]]); + is_equal(IntStrict(one_second), expect![[r#"1"#]]); + check_serialization(IntStrict(half_second), expect![[r#"1"#]]); + check_error_deserialization::<IntStrict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected u64 at line 1 column 3"#]], + ); + check_error_deserialization::<IntStrict>( + r#"-1"#, + expect![[r#"invalid value: integer `-1`, expected u64 at line 1 column 2"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct IntFlexible(#[serde_as(as = "DurationSeconds<u64, Flexible>")] Duration); + + is_equal(IntFlexible(zero), expect![[r#"0"#]]); + is_equal(IntFlexible(one_second), expect![[r#"1"#]]); + check_serialization(IntFlexible(half_second), expect![[r#"1"#]]); + check_deserialization(IntFlexible(half_second), r#""0.5""#); + check_deserialization(IntFlexible(one_second), r#""1""#); + check_deserialization(IntFlexible(zero), r#""0""#); + check_error_deserialization::<IntFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + check_error_deserialization::<IntFlexible>( + r#"-1"#, + expect![[r#"std::time::Duration cannot be negative"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct F64Strict(#[serde_as(as = "DurationSeconds<f64>")] Duration); + + is_equal(F64Strict(zero), expect![[r#"0.0"#]]); + is_equal(F64Strict(one_second), expect![[r#"1.0"#]]); + check_serialization(F64Strict(half_second), expect![[r#"1.0"#]]); + check_deserialization(F64Strict(one_second), r#"0.5"#); + check_error_deserialization::<F64Strict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected f64 at line 1 column 3"#]], + ); + check_error_deserialization::<F64Strict>( + r#"-1.0"#, + expect![[r#"std::time::Duration cannot be negative"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct F64Flexible(#[serde_as(as = "DurationSeconds<f64, Flexible>")] Duration); + + is_equal(F64Flexible(zero), expect![[r#"0.0"#]]); + is_equal(F64Flexible(one_second), expect![[r#"1.0"#]]); + check_serialization(F64Flexible(half_second), expect![[r#"1.0"#]]); + check_deserialization(F64Flexible(half_second), r#""0.5""#); + check_deserialization(F64Flexible(one_second), r#""1""#); + check_deserialization(F64Flexible(zero), r#""0""#); + check_error_deserialization::<F64Flexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + check_error_deserialization::<F64Flexible>( + r#"-1"#, + expect![[r#"std::time::Duration cannot be negative"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StringStrict(#[serde_as(as = "DurationSeconds<String>")] Duration); + + is_equal(StringStrict(zero), expect![[r#""0""#]]); + is_equal(StringStrict(one_second), expect![[r#""1""#]]); + check_serialization(StringStrict(half_second), expect![[r#""1""#]]); + check_error_deserialization::<StringStrict>( + r#"1"#, + expect![[ + r#"invalid type: integer `1`, expected a string containing a number at line 1 column 1"# + ]], + ); + check_error_deserialization::<StringStrict>( + r#"-1"#, + expect![[ + r#"invalid type: integer `-1`, expected a string containing a number at line 1 column 2"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StringFlexible(#[serde_as(as = "DurationSeconds<String, Flexible>")] Duration); + + is_equal(StringFlexible(zero), expect![[r#""0""#]]); + is_equal(StringFlexible(one_second), expect![[r#""1""#]]); + check_serialization(StringFlexible(half_second), expect![[r#""1""#]]); + check_deserialization(StringFlexible(half_second), r#""0.5""#); + check_deserialization(StringFlexible(one_second), r#""1""#); + check_deserialization(StringFlexible(zero), r#""0""#); + check_error_deserialization::<StringFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + check_error_deserialization::<StringFlexible>( + r#"-1"#, + expect![[r#"std::time::Duration cannot be negative"#]], + ); +} + +#[test] +fn test_duration_seconds_with_frac() { + let zero = Duration::new(0, 0); + let one_second = Duration::new(1, 0); + let half_second = Duration::new(0, 500_000_000); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct F64Strict(#[serde_as(as = "DurationSecondsWithFrac<f64>")] Duration); + + is_equal(F64Strict(zero), expect![[r#"0.0"#]]); + is_equal(F64Strict(one_second), expect![[r#"1.0"#]]); + is_equal(F64Strict(half_second), expect![[r#"0.5"#]]); + check_error_deserialization::<F64Strict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected f64 at line 1 column 3"#]], + ); + check_error_deserialization::<F64Strict>( + r#"-1.0"#, + expect![[r#"std::time::Duration cannot be negative"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct F64Flexible(#[serde_as(as = "DurationSecondsWithFrac<f64, Flexible>")] Duration); + + is_equal(F64Flexible(zero), expect![[r#"0.0"#]]); + is_equal(F64Flexible(one_second), expect![[r#"1.0"#]]); + is_equal(F64Flexible(half_second), expect![[r#"0.5"#]]); + check_deserialization(F64Flexible(one_second), r#""1""#); + check_deserialization(F64Flexible(zero), r#""0""#); + check_error_deserialization::<F64Flexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + check_error_deserialization::<F64Flexible>( + r#"-1"#, + expect![[r#"std::time::Duration cannot be negative"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StringStrict(#[serde_as(as = "DurationSecondsWithFrac<String>")] Duration); + + is_equal(StringStrict(zero), expect![[r#""0""#]]); + is_equal(StringStrict(one_second), expect![[r#""1""#]]); + is_equal(StringStrict(half_second), expect![[r#""0.5""#]]); + check_error_deserialization::<StringStrict>( + r#"1"#, + expect![[r#"invalid type: integer `1`, expected a string at line 1 column 1"#]], + ); + check_error_deserialization::<StringStrict>( + r#"-1"#, + expect![[r#"invalid type: integer `-1`, expected a string at line 1 column 2"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StringFlexible(#[serde_as(as = "DurationSecondsWithFrac<String, Flexible>")] Duration); + + is_equal(StringFlexible(zero), expect![[r#""0""#]]); + is_equal(StringFlexible(one_second), expect![[r#""1""#]]); + is_equal(StringFlexible(half_second), expect![[r#""0.5""#]]); + check_deserialization(StringFlexible(zero), r#""0""#); + check_error_deserialization::<StringFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + check_error_deserialization::<StringFlexible>( + r#"-1"#, + expect![[r#"std::time::Duration cannot be negative"#]], + ); +} + +#[test] +fn test_timestamp_seconds_systemtime() { + let zero = SystemTime::UNIX_EPOCH; + let one_second = SystemTime::UNIX_EPOCH + .checked_add(Duration::new(1, 0)) + .unwrap(); + let half_second = SystemTime::UNIX_EPOCH + .checked_add(Duration::new(0, 500_000_000)) + .unwrap(); + let minus_one_second = SystemTime::UNIX_EPOCH + .checked_sub(Duration::new(1, 0)) + .unwrap(); + let minus_half_second = SystemTime::UNIX_EPOCH + .checked_sub(Duration::new(0, 500_000_000)) + .unwrap(); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructIntStrict(#[serde_as(as = "TimestampSeconds")] SystemTime); + + is_equal(StructIntStrict(zero), expect![[r#"0"#]]); + is_equal(StructIntStrict(one_second), expect![[r#"1"#]]); + is_equal(StructIntStrict(minus_one_second), expect![[r#"-1"#]]); + check_serialization(StructIntStrict(half_second), expect![[r#"1"#]]); + check_serialization(StructIntStrict(minus_half_second), expect![[r#"-1"#]]); + check_error_deserialization::<StructIntStrict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected i64 at line 1 column 3"#]], + ); + check_error_deserialization::<StructIntStrict>( + r#"0.123"#, + expect![[r#"invalid type: floating point `0.123`, expected i64 at line 1 column 5"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructIntFlexible(#[serde_as(as = "TimestampSeconds<i64, Flexible>")] SystemTime); + + is_equal(StructIntFlexible(zero), expect![[r#"0"#]]); + is_equal(StructIntFlexible(one_second), expect![[r#"1"#]]); + is_equal(StructIntFlexible(minus_one_second), expect![[r#"-1"#]]); + check_serialization(StructIntFlexible(half_second), expect![[r#"1"#]]); + check_serialization(StructIntFlexible(minus_half_second), expect![[r#"-1"#]]); + check_deserialization(StructIntFlexible(one_second), r#""1""#); + check_deserialization(StructIntFlexible(one_second), r#"1.0"#); + check_deserialization(StructIntFlexible(minus_half_second), r#""-0.5""#); + check_deserialization(StructIntFlexible(half_second), r#"0.5"#); + check_error_deserialization::<StructIntFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Strict(#[serde_as(as = "TimestampSeconds<f64>")] SystemTime); + + is_equal(Structf64Strict(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Strict(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Strict(minus_one_second), expect![[r#"-1.0"#]]); + check_serialization(Structf64Strict(half_second), expect![[r#"1.0"#]]); + check_serialization(Structf64Strict(minus_half_second), expect![[r#"-1.0"#]]); + check_deserialization(Structf64Strict(one_second), r#"0.5"#); + check_error_deserialization::<Structf64Strict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected f64 at line 1 column 3"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Flexible(#[serde_as(as = "TimestampSeconds<f64, Flexible>")] SystemTime); + + is_equal(Structf64Flexible(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Flexible(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Flexible(minus_one_second), expect![[r#"-1.0"#]]); + check_serialization(Structf64Flexible(half_second), expect![[r#"1.0"#]]); + check_serialization(Structf64Flexible(minus_half_second), expect![[r#"-1.0"#]]); + check_deserialization(Structf64Flexible(one_second), r#""1""#); + check_deserialization(Structf64Flexible(one_second), r#"1.0"#); + check_deserialization(Structf64Flexible(minus_half_second), r#""-0.5""#); + check_deserialization(Structf64Flexible(half_second), r#"0.5"#); + check_error_deserialization::<Structf64Flexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringStrict(#[serde_as(as = "TimestampSeconds<String>")] SystemTime); + + is_equal(StructStringStrict(zero), expect![[r#""0""#]]); + is_equal(StructStringStrict(one_second), expect![[r#""1""#]]); + is_equal(StructStringStrict(minus_one_second), expect![[r#""-1""#]]); + check_serialization(StructStringStrict(half_second), expect![[r#""1""#]]); + check_serialization(StructStringStrict(minus_half_second), expect![[r#""-1""#]]); + check_deserialization(StructStringStrict(one_second), r#""1""#); + check_error_deserialization::<StructStringStrict>( + r#""0.5""#, + expect![[r#"invalid digit found in string at line 1 column 5"#]], + ); + check_error_deserialization::<StructStringStrict>( + r#""-0.5""#, + expect![[r#"invalid digit found in string at line 1 column 6"#]], + ); + check_error_deserialization::<StructStringStrict>( + r#"1"#, + expect![[ + r#"invalid type: integer `1`, expected a string containing a number at line 1 column 1"# + ]], + ); + check_error_deserialization::<StructStringStrict>( + r#"0.0"#, + expect![[ + r#"invalid type: floating point `0`, expected a string containing a number at line 1 column 3"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringFlexible(#[serde_as(as = "TimestampSeconds<String, Flexible>")] SystemTime); + + is_equal(StructStringFlexible(zero), expect![[r#""0""#]]); + is_equal(StructStringFlexible(one_second), expect![[r#""1""#]]); + is_equal(StructStringFlexible(minus_one_second), expect![[r#""-1""#]]); + check_serialization(StructStringFlexible(half_second), expect![[r#""1""#]]); + check_serialization( + StructStringFlexible(minus_half_second), + expect![[r#""-1""#]], + ); + check_deserialization(StructStringFlexible(one_second), r#"1"#); + check_deserialization(StructStringFlexible(one_second), r#"1.0"#); + check_deserialization(StructStringFlexible(minus_half_second), r#""-0.5""#); + check_deserialization(StructStringFlexible(half_second), r#"0.5"#); + check_error_deserialization::<StructStringFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); +} + +#[test] +fn test_timestamp_seconds_with_frac_systemtime() { + let zero = SystemTime::UNIX_EPOCH; + let one_second = SystemTime::UNIX_EPOCH + .checked_add(Duration::new(1, 0)) + .unwrap(); + let half_second = SystemTime::UNIX_EPOCH + .checked_add(Duration::new(0, 500_000_000)) + .unwrap(); + let minus_one_second = SystemTime::UNIX_EPOCH + .checked_sub(Duration::new(1, 0)) + .unwrap(); + let minus_half_second = SystemTime::UNIX_EPOCH + .checked_sub(Duration::new(0, 500_000_000)) + .unwrap(); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Strict(#[serde_as(as = "TimestampSecondsWithFrac<f64>")] SystemTime); + + is_equal(Structf64Strict(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Strict(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Strict(minus_one_second), expect![[r#"-1.0"#]]); + is_equal(Structf64Strict(half_second), expect![[r#"0.5"#]]); + is_equal(Structf64Strict(minus_half_second), expect![[r#"-0.5"#]]); + check_error_deserialization::<Structf64Strict>( + r#""1""#, + expect![[r#"invalid type: string "1", expected f64 at line 1 column 3"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Structf64Flexible( + #[serde_as(as = "TimestampSecondsWithFrac<f64, Flexible>")] SystemTime, + ); + + is_equal(Structf64Flexible(zero), expect![[r#"0.0"#]]); + is_equal(Structf64Flexible(one_second), expect![[r#"1.0"#]]); + is_equal(Structf64Flexible(minus_one_second), expect![[r#"-1.0"#]]); + is_equal(Structf64Flexible(half_second), expect![[r#"0.5"#]]); + is_equal(Structf64Flexible(minus_half_second), expect![[r#"-0.5"#]]); + check_deserialization(Structf64Flexible(one_second), r#""1""#); + check_deserialization(Structf64Flexible(one_second), r#"1.0"#); + check_deserialization(Structf64Flexible(minus_half_second), r#""-0.5""#); + check_deserialization(Structf64Flexible(half_second), r#"0.5"#); + check_error_deserialization::<Structf64Flexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringStrict(#[serde_as(as = "TimestampSecondsWithFrac<String>")] SystemTime); + + is_equal(StructStringStrict(zero), expect![[r#""0""#]]); + is_equal(StructStringStrict(one_second), expect![[r#""1""#]]); + is_equal(StructStringStrict(minus_one_second), expect![[r#""-1""#]]); + is_equal(StructStringStrict(half_second), expect![[r#""0.5""#]]); + is_equal( + StructStringStrict(minus_half_second), + expect![[r#""-0.5""#]], + ); + check_error_deserialization::<StructStringStrict>( + r#"1"#, + expect![[r#"invalid type: integer `1`, expected a string at line 1 column 1"#]], + ); + check_error_deserialization::<StructStringStrict>( + r#"0.0"#, + expect![[r#"invalid type: floating point `0`, expected a string at line 1 column 3"#]], + ); + + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct StructStringFlexible( + #[serde_as(as = "TimestampSecondsWithFrac<String, Flexible>")] SystemTime, + ); + + is_equal(StructStringFlexible(zero), expect![[r#""0""#]]); + is_equal(StructStringFlexible(one_second), expect![[r#""1""#]]); + is_equal(StructStringFlexible(minus_one_second), expect![[r#""-1""#]]); + is_equal(StructStringFlexible(half_second), expect![[r#""0.5""#]]); + is_equal( + StructStringFlexible(minus_half_second), + expect![[r#""-0.5""#]], + ); + check_deserialization(StructStringFlexible(one_second), r#"1"#); + check_deserialization(StructStringFlexible(one_second), r#"1.0"#); + check_deserialization(StructStringFlexible(half_second), r#"0.5"#); + check_error_deserialization::<StructStringFlexible>( + r#""a""#, + expect![[ + r#"invalid value: string "a", expected an integer, a float, or a string containing a number at line 1 column 3"# + ]], + ); +} + +macro_rules! smoketest { + ($($valuety:ty, $adapter:literal, $value:ident, $expect:tt;)*) => { + $({ + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = $adapter)] $valuety); + #[allow(unused_braces)] + is_equal(S($value), $expect); + })* + }; +} + +#[test] +fn test_duration_smoketest() { + let one_second = Duration::new(1, 0); + + smoketest! { + Duration, "DurationSeconds<u64>", one_second, {expect![[r#"1"#]]}; + Duration, "DurationSeconds<f64>", one_second, {expect![[r#"1.0"#]]}; + Duration, "DurationMilliSeconds<u64>", one_second, {expect![[r#"1000"#]]}; + Duration, "DurationMilliSeconds<f64>", one_second, {expect![[r#"1000.0"#]]}; + Duration, "DurationMicroSeconds<u64>", one_second, {expect![[r#"1000000"#]]}; + Duration, "DurationMicroSeconds<f64>", one_second, {expect![[r#"1000000.0"#]]}; + Duration, "DurationNanoSeconds<u64>", one_second, {expect![[r#"1000000000"#]]}; + Duration, "DurationNanoSeconds<f64>", one_second, {expect![[r#"1000000000.0"#]]}; + }; + + smoketest! { + Duration, "DurationSecondsWithFrac", one_second, {expect![[r#"1.0"#]]}; + Duration, "DurationSecondsWithFrac<String>", one_second, {expect![[r#""1""#]]}; + Duration, "DurationMilliSecondsWithFrac", one_second, {expect![[r#"1000.0"#]]}; + Duration, "DurationMilliSecondsWithFrac<String>", one_second, {expect![[r#""1000""#]]}; + Duration, "DurationMicroSecondsWithFrac", one_second, {expect![[r#"1000000.0"#]]}; + Duration, "DurationMicroSecondsWithFrac<String>", one_second, {expect![[r#""1000000""#]]}; + Duration, "DurationNanoSecondsWithFrac", one_second, {expect![[r#"1000000000.0"#]]}; + Duration, "DurationNanoSecondsWithFrac<String>", one_second, {expect![[r#""1000000000""#]]}; + }; +} + +#[test] +fn test_timestamp_systemtime_smoketest() { + let one_second = SystemTime::UNIX_EPOCH + .checked_add(Duration::new(1, 0)) + .unwrap(); + + smoketest! { + SystemTime, "TimestampSeconds<i64>", one_second, {expect![[r#"1"#]]}; + SystemTime, "TimestampSeconds<f64>", one_second, {expect![[r#"1.0"#]]}; + SystemTime, "TimestampMilliSeconds<i64>", one_second, {expect![[r#"1000"#]]}; + SystemTime, "TimestampMilliSeconds<f64>", one_second, {expect![[r#"1000.0"#]]}; + SystemTime, "TimestampMicroSeconds<i64>", one_second, {expect![[r#"1000000"#]]}; + SystemTime, "TimestampMicroSeconds<f64>", one_second, {expect![[r#"1000000.0"#]]}; + SystemTime, "TimestampNanoSeconds<i64>", one_second, {expect![[r#"1000000000"#]]}; + SystemTime, "TimestampNanoSeconds<f64>", one_second, {expect![[r#"1000000000.0"#]]}; + }; + + smoketest! { + SystemTime, "TimestampSecondsWithFrac", one_second, {expect![[r#"1.0"#]]}; + SystemTime, "TimestampSecondsWithFrac<String>", one_second, {expect![[r#""1""#]]}; + SystemTime, "TimestampMilliSecondsWithFrac", one_second, {expect![[r#"1000.0"#]]}; + SystemTime, "TimestampMilliSecondsWithFrac<String>", one_second, {expect![[r#""1000""#]]}; + SystemTime, "TimestampMicroSecondsWithFrac", one_second, {expect![[r#"1000000.0"#]]}; + SystemTime, "TimestampMicroSecondsWithFrac<String>", one_second, {expect![[r#""1000000""#]]}; + SystemTime, "TimestampNanoSecondsWithFrac", one_second, {expect![[r#"1000000000.0"#]]}; + SystemTime, "TimestampNanoSecondsWithFrac<String>", one_second, {expect![[r#""1000000000""#]]}; + }; +} diff --git a/third_party/rust/serde_with/tests/time_0_3.rs b/third_party/rust/serde_with/tests/time_0_3.rs new file mode 100644 index 0000000000..4c89dbd7d4 --- /dev/null +++ b/third_party/rust/serde_with/tests/time_0_3.rs @@ -0,0 +1,229 @@ +#![allow( + // clippy is broken and shows wrong warnings + // clippy on stable does not know yet about the lint name + unknown_lints, + // https://github.com/rust-lang/rust-clippy/issues/8867 + clippy::derive_partial_eq_without_eq, +)] + +mod utils; + +use crate::utils::{check_deserialization, check_error_deserialization, is_equal}; +use expect_test::expect; +use serde::{Deserialize, Serialize}; +use serde_with::{ + serde_as, DurationMicroSeconds, DurationMicroSecondsWithFrac, DurationMilliSeconds, + DurationMilliSecondsWithFrac, DurationNanoSeconds, DurationNanoSecondsWithFrac, + DurationSeconds, DurationSecondsWithFrac, TimestampMicroSeconds, TimestampMicroSecondsWithFrac, + TimestampMilliSeconds, TimestampMilliSecondsWithFrac, TimestampNanoSeconds, + TimestampNanoSecondsWithFrac, TimestampSeconds, TimestampSecondsWithFrac, +}; +use time_0_3::{Duration, OffsetDateTime, PrimitiveDateTime, UtcOffset}; + +/// Create a [`PrimitiveDateTime`] for the Unix Epoch +fn unix_epoch_primitive() -> PrimitiveDateTime { + PrimitiveDateTime::new( + time_0_3::Date::from_ordinal_date(1970, 1).unwrap(), + time_0_3::Time::from_hms_nano(0, 0, 0, 0).unwrap(), + ) +} + +macro_rules! smoketest { + ($($valuety:ty, $adapter:literal, $value:expr, $expect:tt;)*) => { + $({ + #[serde_as] + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct S(#[serde_as(as = $adapter)] $valuety); + #[allow(unused_braces)] + is_equal(S($value), $expect); + })* + }; +} + +#[test] +fn test_duration_smoketest() { + let zero = Duration::seconds(0); + let one_second = Duration::seconds(1); + + smoketest! { + Duration, "DurationSeconds<i64>", one_second, {expect![[r#"1"#]]}; + Duration, "DurationSeconds<f64>", one_second, {expect![[r#"1.0"#]]}; + Duration, "DurationMilliSeconds<i64>", one_second, {expect![[r#"1000"#]]}; + Duration, "DurationMilliSeconds<f64>", one_second, {expect![[r#"1000.0"#]]}; + Duration, "DurationMicroSeconds<i64>", one_second, {expect![[r#"1000000"#]]}; + Duration, "DurationMicroSeconds<f64>", one_second, {expect![[r#"1000000.0"#]]}; + Duration, "DurationNanoSeconds<i64>", one_second, {expect![[r#"1000000000"#]]}; + Duration, "DurationNanoSeconds<f64>", one_second, {expect![[r#"1000000000.0"#]]}; + }; + + smoketest! { + Duration, "DurationSecondsWithFrac", one_second, {expect![[r#"1.0"#]]}; + Duration, "DurationSecondsWithFrac<String>", one_second, {expect![[r#""1""#]]}; + Duration, "DurationMilliSecondsWithFrac", one_second, {expect![[r#"1000.0"#]]}; + Duration, "DurationMilliSecondsWithFrac<String>", one_second, {expect![[r#""1000""#]]}; + Duration, "DurationMicroSecondsWithFrac", one_second, {expect![[r#"1000000.0"#]]}; + Duration, "DurationMicroSecondsWithFrac<String>", one_second, {expect![[r#""1000000""#]]}; + Duration, "DurationNanoSecondsWithFrac", one_second, {expect![[r#"1000000000.0"#]]}; + Duration, "DurationNanoSecondsWithFrac<String>", one_second, {expect![[r#""1000000000""#]]}; + }; + + smoketest! { + Duration, "DurationSecondsWithFrac", zero, {expect![[r#"0.0"#]]}; + Duration, "DurationSecondsWithFrac", zero + Duration::nanoseconds(500_000_000), {expect![[r#"0.5"#]]}; + Duration, "DurationSecondsWithFrac", zero + Duration::seconds(1), {expect![[r#"1.0"#]]}; + Duration, "DurationSecondsWithFrac", zero - Duration::nanoseconds(500_000_000), {expect![[r#"-0.5"#]]}; + Duration, "DurationSecondsWithFrac", zero - Duration::seconds(1), {expect![[r#"-1.0"#]]}; + }; +} + +#[test] +fn test_datetime_utc_smoketest() { + let zero = OffsetDateTime::UNIX_EPOCH; + let one_second = zero + Duration::seconds(1); + + smoketest! { + OffsetDateTime, "TimestampSeconds<i64>", one_second, {expect![[r#"1"#]]}; + OffsetDateTime, "TimestampSeconds<f64>", one_second, {expect![[r#"1.0"#]]}; + OffsetDateTime, "TimestampMilliSeconds<i64>", one_second, {expect![[r#"1000"#]]}; + OffsetDateTime, "TimestampMilliSeconds<f64>", one_second, {expect![[r#"1000.0"#]]}; + OffsetDateTime, "TimestampMicroSeconds<i64>", one_second, {expect![[r#"1000000"#]]}; + OffsetDateTime, "TimestampMicroSeconds<f64>", one_second, {expect![[r#"1000000.0"#]]}; + OffsetDateTime, "TimestampNanoSeconds<i64>", one_second, {expect![[r#"1000000000"#]]}; + OffsetDateTime, "TimestampNanoSeconds<f64>", one_second, {expect![[r#"1000000000.0"#]]}; + }; + + smoketest! { + OffsetDateTime, "TimestampSecondsWithFrac", one_second, {expect![[r#"1.0"#]]}; + OffsetDateTime, "TimestampSecondsWithFrac<String>", one_second, {expect![[r#""1""#]]}; + OffsetDateTime, "TimestampMilliSecondsWithFrac", one_second, {expect![[r#"1000.0"#]]}; + OffsetDateTime, "TimestampMilliSecondsWithFrac<String>", one_second, {expect![[r#""1000""#]]}; + OffsetDateTime, "TimestampMicroSecondsWithFrac", one_second, {expect![[r#"1000000.0"#]]}; + OffsetDateTime, "TimestampMicroSecondsWithFrac<String>", one_second, {expect![[r#""1000000""#]]}; + OffsetDateTime, "TimestampNanoSecondsWithFrac", one_second, {expect![[r#"1000000000.0"#]]}; + OffsetDateTime, "TimestampNanoSecondsWithFrac<String>", one_second, {expect![[r#""1000000000""#]]}; + }; + + smoketest! { + OffsetDateTime, "TimestampSecondsWithFrac", zero, {expect![[r#"0.0"#]]}; + OffsetDateTime, "TimestampSecondsWithFrac", zero + Duration::nanoseconds(500_000_000), {expect![[r#"0.5"#]]}; + OffsetDateTime, "TimestampSecondsWithFrac", zero + Duration::seconds(1), {expect![[r#"1.0"#]]}; + OffsetDateTime, "TimestampSecondsWithFrac", zero - Duration::nanoseconds(500_000_000), {expect![[r#"-0.5"#]]}; + OffsetDateTime, "TimestampSecondsWithFrac", zero - Duration::seconds(1), {expect![[r#"-1.0"#]]}; + }; +} + +#[test] +fn test_naive_datetime_smoketest() { + let zero = unix_epoch_primitive(); + let one_second = zero + Duration::seconds(1); + + smoketest! { + PrimitiveDateTime, "TimestampSeconds<i64>", one_second, {expect![[r#"1"#]]}; + PrimitiveDateTime, "TimestampSeconds<f64>", one_second, {expect![[r#"1.0"#]]}; + PrimitiveDateTime, "TimestampMilliSeconds<i64>", one_second, {expect![[r#"1000"#]]}; + PrimitiveDateTime, "TimestampMilliSeconds<f64>", one_second, {expect![[r#"1000.0"#]]}; + PrimitiveDateTime, "TimestampMicroSeconds<i64>", one_second, {expect![[r#"1000000"#]]}; + PrimitiveDateTime, "TimestampMicroSeconds<f64>", one_second, {expect![[r#"1000000.0"#]]}; + PrimitiveDateTime, "TimestampNanoSeconds<i64>", one_second, {expect![[r#"1000000000"#]]}; + PrimitiveDateTime, "TimestampNanoSeconds<f64>", one_second, {expect![[r#"1000000000.0"#]]}; + }; + + smoketest! { + PrimitiveDateTime, "TimestampSecondsWithFrac", one_second, {expect![[r#"1.0"#]]}; + PrimitiveDateTime, "TimestampSecondsWithFrac<String>", one_second, {expect![[r#""1""#]]}; + PrimitiveDateTime, "TimestampMilliSecondsWithFrac", one_second, {expect![[r#"1000.0"#]]}; + PrimitiveDateTime, "TimestampMilliSecondsWithFrac<String>", one_second, {expect![[r#""1000""#]]}; + PrimitiveDateTime, "TimestampMicroSecondsWithFrac", one_second, {expect![[r#"1000000.0"#]]}; + PrimitiveDateTime, "TimestampMicroSecondsWithFrac<String>", one_second, {expect![[r#""1000000""#]]}; + PrimitiveDateTime, "TimestampNanoSecondsWithFrac", one_second, {expect![[r#"1000000000.0"#]]}; + PrimitiveDateTime, "TimestampNanoSecondsWithFrac<String>", one_second, {expect![[r#""1000000000""#]]}; + }; + + smoketest! { + PrimitiveDateTime, "TimestampSecondsWithFrac", zero, {expect![[r#"0.0"#]]}; + PrimitiveDateTime, "TimestampSecondsWithFrac", zero + Duration::nanoseconds(500_000_000), {expect![[r#"0.5"#]]}; + PrimitiveDateTime, "TimestampSecondsWithFrac", zero + Duration::seconds(1), {expect![[r#"1.0"#]]}; + PrimitiveDateTime, "TimestampSecondsWithFrac", zero - Duration::nanoseconds(500_000_000), {expect![[r#"-0.5"#]]}; + PrimitiveDateTime, "TimestampSecondsWithFrac", zero - Duration::seconds(1), {expect![[r#"-1.0"#]]}; + }; +} + +#[test] +fn test_offset_datetime_rfc2822() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde_as(as = "time_0_3::format_description::well_known::Rfc2822")] OffsetDateTime); + + is_equal( + S(OffsetDateTime::UNIX_EPOCH), + expect![[r#""Thu, 01 Jan 1970 00:00:00 +0000""#]], + ); + + check_error_deserialization::<S>( + r#""Foobar""#, + expect![[r#"the 'weekday' component could not be parsed at line 1 column 8"#]], + ); + check_error_deserialization::<S>( + r#""Fri, 2000""#, + expect![[r#"a character literal was not valid at line 1 column 11"#]], + ); +} + +#[test] +fn test_offset_datetime_rfc3339() { + #[serde_as] + #[derive(Debug, PartialEq, Deserialize, Serialize)] + struct S(#[serde_as(as = "time_0_3::format_description::well_known::Rfc3339")] OffsetDateTime); + + is_equal( + S(OffsetDateTime::UNIX_EPOCH), + expect![[r#""1970-01-01T00:00:00Z""#]], + ); + check_deserialization::<S>( + S( + OffsetDateTime::from_unix_timestamp_nanos(482_196_050_520_000_000) + .unwrap() + .to_offset(UtcOffset::from_hms(0, 0, 0).unwrap()), + ), + r#""1985-04-12T23:20:50.52Z""#, + ); + check_deserialization::<S>( + S(OffsetDateTime::from_unix_timestamp(851_042_397) + .unwrap() + .to_offset(UtcOffset::from_hms(-8, 0, 0).unwrap())), + r#""1996-12-19T16:39:57-08:00""#, + ); + check_deserialization::<S>( + S( + OffsetDateTime::from_unix_timestamp_nanos(662_687_999_999_999_999) + .unwrap() + .to_offset(UtcOffset::from_hms(0, 0, 0).unwrap()), + ), + r#""1990-12-31T23:59:60Z""#, + ); + check_deserialization::<S>( + S( + OffsetDateTime::from_unix_timestamp_nanos(662_687_999_999_999_999) + .unwrap() + .to_offset(UtcOffset::from_hms(-8, 0, 0).unwrap()), + ), + r#""1990-12-31T15:59:60-08:00""#, + ); + check_deserialization::<S>( + S( + OffsetDateTime::from_unix_timestamp_nanos(-1_041_337_172_130_000_000) + .unwrap() + .to_offset(UtcOffset::from_hms(0, 20, 0).unwrap()), + ), + r#""1937-01-01T12:00:27.87+00:20""#, + ); + + check_error_deserialization::<S>( + r#""Foobar""#, + expect![[r#"the 'year' component could not be parsed at line 1 column 8"#]], + ); + check_error_deserialization::<S>( + r#""2000-AA""#, + expect![[r#"the 'month' component could not be parsed at line 1 column 9"#]], + ); +} diff --git a/third_party/rust/serde_with/tests/utils.rs b/third_party/rust/serde_with/tests/utils.rs new file mode 100644 index 0000000000..d9247ffff9 --- /dev/null +++ b/third_party/rust/serde_with/tests/utils.rs @@ -0,0 +1,79 @@ +#![allow(dead_code)] + +use core::fmt::Debug; +use expect_test::Expect; +use pretty_assertions::assert_eq; +use serde::{de::DeserializeOwned, Serialize}; + +#[track_caller] +pub fn is_equal<T>(value: T, expected: Expect) +where + T: Debug + DeserializeOwned + PartialEq + Serialize, +{ + let serialized = serde_json::to_string_pretty(&value).unwrap(); + expected.assert_eq(&serialized); + assert_eq!( + value, + serde_json::from_str::<T>(&serialized).unwrap(), + "Deserialization differs from expected value." + ); +} + +/// Like [`is_equal`] but not pretty-print +#[track_caller] +pub fn is_equal_compact<T>(value: T, expected: Expect) +where + T: Debug + DeserializeOwned + PartialEq + Serialize, +{ + let serialized = serde_json::to_string(&value).unwrap(); + expected.assert_eq(&serialized); + assert_eq!( + value, + serde_json::from_str::<T>(&serialized).unwrap(), + "Deserialization differs from expected value." + ); +} + +#[track_caller] +pub fn check_deserialization<T>(value: T, deserialize_from: &str) +where + T: Debug + DeserializeOwned + PartialEq, +{ + assert_eq!( + value, + serde_json::from_str::<T>(deserialize_from).unwrap(), + "Deserialization differs from expected value." + ); +} + +#[track_caller] +pub fn check_serialization<T>(value: T, serialize_to: Expect) +where + T: Debug + Serialize, +{ + serialize_to.assert_eq(&serde_json::to_string_pretty(&value).unwrap()); +} + +#[track_caller] +pub fn check_error_serialization<T>(value: T, error_msg: Expect) +where + T: Debug + Serialize, +{ + error_msg.assert_eq( + &serde_json::to_string_pretty(&value) + .unwrap_err() + .to_string(), + ); +} + +#[track_caller] +pub fn check_error_deserialization<T>(deserialize_from: &str, error_msg: Expect) +where + T: Debug + DeserializeOwned, +{ + error_msg.assert_eq( + &serde_json::from_str::<T>(deserialize_from) + .unwrap_err() + .to_string(), + ) +} diff --git a/third_party/rust/serde_with/tests/version_numbers.rs b/third_party/rust/serde_with/tests/version_numbers.rs new file mode 100644 index 0000000000..5c478d91ed --- /dev/null +++ b/third_party/rust/serde_with/tests/version_numbers.rs @@ -0,0 +1,80 @@ +// Needed to supress a 2021 incompatability warning in the macro generated code +// The non_fmt_panic lint is not yet available on most Rust versions +#![allow(unknown_lints, non_fmt_panics)] + +use version_sync::{ + assert_contains_regex, assert_html_root_url_updated, assert_markdown_deps_updated, +}; + +#[test] +fn test_readme_deps() { + assert_markdown_deps_updated!("README.md"); +} + +#[test] +fn test_readme_deps_in_lib() { + assert_contains_regex!("src/lib.rs", r#"^//! version = "{version}""#); +} + +#[test] +fn test_changelog() { + assert_contains_regex!("CHANGELOG.md", r#"## \[{version}\]"#); +} + +#[test] +fn test_html_root_url() { + assert_html_root_url_updated!("src/lib.rs"); +} + +/// Check that all docs.rs links point to the current version +/// +/// Parse all docs.rs links in `*.rs` and `*.md` files and check that they point to the current version. +/// If a link should point to latest version this can be done by using `latest` in the version. +/// The `*` version specifier is not allowed. +/// +/// Arguably this should be part of version-sync. There is an open issue for this feature: +/// https://github.com/mgeisler/version-sync/issues/72 +#[test] +fn test_docs_rs_url_point_to_current_version() -> Result<(), Box<dyn std::error::Error>> { + let pkg_name = env!("CARGO_PKG_NAME"); + let pkg_version = env!("CARGO_PKG_VERSION"); + + let re = regex::Regex::new(&format!( + "https?://docs.rs/{}/((\\d[^/]+|\\*|latest))/", + pkg_name + ))?; + let mut error = false; + + for entry in glob::glob("**/*.rs")?.chain(glob::glob("**/README.md")?) { + let entry = entry?; + let content = std::fs::read_to_string(&entry)?; + for (line_number, line) in content.split('\n').enumerate() { + for capture in re.captures_iter(line) { + match capture + .get(1) + .expect("Will exist if regex matches") + .as_str() + { + "latest" => {} + version if version != pkg_version => { + error = true; + println!( + "{}:{} pkg_version is {} but found URL {}", + entry.display(), + line_number + 1, + pkg_version, + capture.get(0).expect("Group 0 always exists").as_str() + ) + } + _ => {} + } + } + } + } + + if error { + panic!("Found wrong URLs in file(s)"); + } else { + Ok(()) + } +} diff --git a/third_party/rust/serde_with/tests/with_prefix.rs b/third_party/rust/serde_with/tests/with_prefix.rs new file mode 100644 index 0000000000..bc7a78711e --- /dev/null +++ b/third_party/rust/serde_with/tests/with_prefix.rs @@ -0,0 +1,164 @@ +#![allow( + // clippy is broken and shows wrong warnings + // clippy on stable does not know yet about the lint name + unknown_lints, + // https://github.com/rust-lang/rust-clippy/issues/8867 + clippy::derive_partial_eq_without_eq, +)] + +extern crate alloc; + +mod utils; + +use crate::utils::is_equal; +use alloc::collections::BTreeMap; +use core::iter::FromIterator; +use expect_test::expect; +use serde::{Deserialize, Serialize}; +use serde_with::with_prefix; +use std::collections::HashMap; + +#[test] +fn test_flatten_with_prefix() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Match { + #[serde(flatten, with = "prefix_player1")] + player1: Player, + #[serde(flatten, with = "prefix_player2")] + player2: Option<Player>, + #[serde(flatten, with = "prefix_player3")] + player3: Option<Player>, + #[serde(flatten, with = "prefix_tag")] + tags: HashMap<String, String>, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Player { + name: String, + votes: u64, + } + + with_prefix!(prefix_player1 "player1_"); + with_prefix!(prefix_player2 "player2_"); + with_prefix!(prefix_player3 "player3_"); + with_prefix!(prefix_tag "tag_"); + + let m = Match { + player1: Player { + name: "name1".to_owned(), + votes: 1, + }, + player2: Some(Player { + name: "name2".to_owned(), + votes: 2, + }), + player3: None, + tags: HashMap::from_iter(vec![("t".to_owned(), "T".to_owned())]), + }; + + is_equal( + m, + expect![[r#" + { + "player1_name": "name1", + "player1_votes": 1, + "player2_name": "name2", + "player2_votes": 2, + "tag_t": "T" + }"#]], + ); +} + +#[test] +fn test_plain_with_prefix() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Match { + #[serde(with = "prefix_player1")] + player1: Player, + #[serde(with = "prefix_player2")] + player2: Option<Player>, + #[serde(with = "prefix_player3")] + player3: Option<Player>, + #[serde(with = "prefix_tag")] + tags: HashMap<String, String>, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Player { + name: String, + votes: u64, + } + + with_prefix!(prefix_player1 "player1_"); + with_prefix!(prefix_player2 "player2_"); + with_prefix!(prefix_player3 "player3_"); + with_prefix!(prefix_tag "tag_"); + + let m = Match { + player1: Player { + name: "name1".to_owned(), + votes: 1, + }, + player2: Some(Player { + name: "name2".to_owned(), + votes: 2, + }), + player3: None, + tags: HashMap::from_iter(vec![("t".to_owned(), "T".to_owned())]), + }; + + is_equal( + m, + expect![[r#" + { + "player1": { + "player1_name": "name1", + "player1_votes": 1 + }, + "player2": { + "player2_name": "name2", + "player2_votes": 2 + }, + "player3": null, + "tags": { + "tag_t": "T" + } + }"#]], + ); +} + +/// Ensure that with_prefix works for unit type enum variants. +#[test] +fn test_enum_unit_variant_with_prefix() { + #[derive(Hash, PartialEq, Eq, Debug, Serialize, Deserialize, Ord, PartialOrd)] + enum Foo { + One, + Two, + Three, + } + + #[derive(Hash, PartialEq, Eq, Debug, Serialize, Deserialize, Ord, PartialOrd)] + struct Data { + stuff: String, + + #[serde(flatten, with = "foo")] + foo: BTreeMap<Foo, i32>, + } + with_prefix!(foo "foo_"); + + let d = Data { + stuff: "Stuff".to_owned(), + foo: BTreeMap::from_iter(vec![(Foo::One, 1), (Foo::Two, 2), (Foo::Three, 3)]), + }; + + is_equal( + d, + expect![[r#" + { + "stuff": "Stuff", + "foo_One": 1, + "foo_Two": 2, + "foo_Three": 3 + }"#]], + ); +} |