summaryrefslogtreecommitdiffstats
path: root/third_party/rust/serde_with/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/serde_with/tests
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--third_party/rust/serde_with/tests/base64.rs144
-rw-r--r--third_party/rust/serde_with/tests/chrono.rs740
-rw-r--r--third_party/rust/serde_with/tests/derives/deserialize_fromstr.rs96
-rw-r--r--third_party/rust/serde_with/tests/derives/lib.rs15
-rw-r--r--third_party/rust/serde_with/tests/derives/serialize_display.rs39
-rw-r--r--third_party/rust/serde_with/tests/hex.rs93
-rw-r--r--third_party/rust/serde_with/tests/indexmap.rs285
-rw-r--r--third_party/rust/serde_with/tests/json.rs41
-rw-r--r--third_party/rust/serde_with/tests/rust.rs676
-rw-r--r--third_party/rust/serde_with/tests/serde_as/collections.rs44
-rw-r--r--third_party/rust/serde_with/tests/serde_as/default_on.rs130
-rw-r--r--third_party/rust/serde_with/tests/serde_as/enum_map.rs459
-rw-r--r--third_party/rust/serde_with/tests/serde_as/frominto.rs187
-rw-r--r--third_party/rust/serde_with/tests/serde_as/lib.rs1129
-rw-r--r--third_party/rust/serde_with/tests/serde_as/map_tuple_list.rs272
-rw-r--r--third_party/rust/serde_with/tests/serde_as/pickfirst.rs134
-rw-r--r--third_party/rust/serde_with/tests/serde_as/serde_as_macro.rs191
-rw-r--r--third_party/rust/serde_with/tests/serde_as/serde_conv.rs52
-rw-r--r--third_party/rust/serde_with/tests/serde_as/time.rs521
-rw-r--r--third_party/rust/serde_with/tests/time_0_3.rs229
-rw-r--r--third_party/rust/serde_with/tests/utils.rs79
-rw-r--r--third_party/rust/serde_with/tests/version_numbers.rs80
-rw-r--r--third_party/rust/serde_with/tests/with_prefix.rs164
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
+ }"#]],
+ );
+}