зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1773399 - Update serde_with to 1.14.0. r=emilio
Differential Revision: https://phabricator.services.mozilla.com/D148723
This commit is contained in:
Родитель
5c399b2466
Коммит
311f8b84a3
|
@ -4766,9 +4766,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "1.6.4"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b44be9227e214a0420707c9ca74c2d4991d9955bae9415a8f93f05cebf561be5"
|
||||
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_with_macros",
|
||||
|
|
|
@ -1330,7 +1330,7 @@ version = "0.7.1"
|
|||
criteria = "safe-to-run"
|
||||
|
||||
[[unaudited.serde_with]]
|
||||
version = "1.6.4"
|
||||
version = "1.14.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[unaudited.serde_with_macros]]
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -5,20 +5,436 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.6.4]
|
||||
## [Unreleased]
|
||||
|
||||
## [1.14.0] - 2022-05-29
|
||||
|
||||
### Added
|
||||
|
||||
* Add support for `time` crate v0.3 #450
|
||||
|
||||
`time::Duration` can now be serialized with the `DurationSeconds` and related converters.
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::DurationSeconds<u64>")]
|
||||
value: Duration,
|
||||
|
||||
// JSON
|
||||
"value": 86400,
|
||||
```
|
||||
|
||||
`time::OffsetDateTime` and `time::PrimitiveDateTime` can now be serialized with the `TimestampSeconds` and related converters.
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::TimestampMicroSecondsWithFrac<String>")]
|
||||
value: time::PrimitiveDateTime,
|
||||
|
||||
// JSON
|
||||
"value": "1000000",
|
||||
```
|
||||
|
||||
`time::OffsetDateTime` can be serialized in string format in different well-known formats.
|
||||
Two formats are supported, `time::format_description::well_known::Rfc2822` and `time::format_description::well_known::Rfc3339`.
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
#[serde_as(as = "time::format_description::well_known::Rfc2822")]
|
||||
rfc_2822: OffsetDateTime,
|
||||
#[serde_as(as = "Vec<time::format_description::well_known::Rfc3339>")]
|
||||
rfc_3339: Vec<OffsetDateTime>,
|
||||
|
||||
// JSON
|
||||
"rfc_2822": "Fri, 21 Nov 1997 09:55:06 -0600",
|
||||
"rfc_3339": ["1997-11-21T09:55:06-06:00"],
|
||||
```
|
||||
|
||||
* Deserialize `bool` from integers #456 462
|
||||
|
||||
Deserialize an integer and convert it into a `bool`.
|
||||
`BoolFromInt<Strict>` (default) deserializes 0 to `false` and `1` to `true`, other numbers are errors.
|
||||
`BoolFromInt<Flexible>` deserializes any non-zero as `true`.
|
||||
Serialization only emits 0/1.
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
#[serde_as(as = "BoolFromInt")] // BoolFromInt<Strict>
|
||||
b: bool,
|
||||
|
||||
// JSON
|
||||
"b": 1,
|
||||
```
|
||||
|
||||
### Changed
|
||||
|
||||
* Bump MSRV to 1.53, since the new dependency `time` requires that version.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Make the documentation clearer by stating that the `#[serde_as]` and `#[skip_serializing_none]` attributes must always be places before `#[derive]`.
|
||||
|
||||
## [1.13.0] - 2022-04-23
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for `indexmap::IndexMap` and `indexmap::IndexSet` types. #431, #436
|
||||
|
||||
Both types are now compatible with these functions: `maps_duplicate_key_is_error`, `maps_first_key_wins`, `sets_duplicate_value_is_error`, `sets_last_value_wins`.
|
||||
`serde_as` integration is provided by implementing both `SerializeAs` and `DeserializeAs` for both types.
|
||||
`IndexMap`s can also be serialized as a list of types via the `serde_as(as = "Vec<(_, _)>")` annotation.
|
||||
|
||||
All implementations are gated behind the `indexmap` feature.
|
||||
|
||||
Thanks to @jgrund for providing parts of the implementation.
|
||||
|
||||
## [1.12.1] - 2022-04-07
|
||||
|
||||
### Fixed
|
||||
|
||||
* Depend on a newer `serde_with_macros` version to pull in some fixes.
|
||||
* Account for generics when deriving implementations with `SerializeDisplay` and `DeserializeFromStr` #413
|
||||
* Provide better error messages when parsing types fails #423
|
||||
|
||||
|
||||
## [1.12.0] - 2022-02-07
|
||||
|
||||
### Added
|
||||
|
||||
* Deserialize a `Vec` and skip all elements failing to deserialize #383
|
||||
|
||||
`VecSkipError` acts like a `Vec`, but elements which fail to deserialize, like the `"Yellow"` are ignored.
|
||||
|
||||
```rust
|
||||
#[derive(serde::Deserialize)]
|
||||
enum Color {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
}
|
||||
// JSON
|
||||
"colors": ["Blue", "Yellow", "Green"],
|
||||
// Rust
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
colors: Vec<Color>,
|
||||
// => vec![Blue, Green]
|
||||
```
|
||||
|
||||
Thanks to @hdhoang for creating the PR.
|
||||
|
||||
* Transform between maps and `Vec<Enum>` #375
|
||||
|
||||
The new `EnumMap` type converts `Vec` of enums into a single map.
|
||||
The key is the enum variant name, and the value is the variant value.
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
VecEnumValues(vec![
|
||||
EnumValue::Int(123),
|
||||
EnumValue::String("Foo".to_string()),
|
||||
EnumValue::Unit,
|
||||
EnumValue::Tuple(1, "Bar".to_string()),
|
||||
EnumValue::Struct {
|
||||
a: 666,
|
||||
b: "Baz".to_string(),
|
||||
},
|
||||
]
|
||||
|
||||
// JSON
|
||||
{
|
||||
"Int": 123,
|
||||
"String": "Foo",
|
||||
"Unit": null,
|
||||
"Tuple": [
|
||||
1,
|
||||
"Bar",
|
||||
],
|
||||
"Struct": {
|
||||
"a": 666,
|
||||
"b": "Baz",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Changed
|
||||
|
||||
* The `Timestamp*Seconds` and `Timestamp*SecondsWithFrac` types can now be used with `chrono::NaiveDateTime`. #389
|
||||
|
||||
## [1.11.0] - 2021-10-18
|
||||
|
||||
### Added
|
||||
|
||||
* Serialize bytes as base64 encoded strings.
|
||||
The character set and padding behavior can be configured.
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::base64::Base64")]
|
||||
value: Vec<u8>,
|
||||
#[serde_as(as = "Base64<Bcrypt, Unpadded>")]
|
||||
bcrypt_unpadded: Vec<u8>,
|
||||
|
||||
// JSON
|
||||
"value": "SGVsbG8gV29ybGQ=",
|
||||
"bcrypt_unpadded": "QETqZE6eT07wZEO",
|
||||
```
|
||||
|
||||
* The minimal supported Rust version (MSRV) is now specified in the `Cargo.toml` via the `rust-version` field. The field is supported in Rust 1.56 and has no effect on versions before.
|
||||
|
||||
More details: https://doc.rust-lang.org/nightly/cargo/reference/manifest.html#the-rust-version-field
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed RUSTSEC-2020-0071 in the `time` v0.1 dependency, but changing the feature flags of the `chrono` dependency. This should not change anything. Crates requiring the `oldtime` feature of `chrono` can enable it separately.
|
||||
* Allow `HashSet`s with custom hashers to be deserialized when used in combination with `serde_as`. #408
|
||||
|
||||
## [1.10.0] - 2021-09-04
|
||||
|
||||
### Added
|
||||
|
||||
* Add `BorrowCow` which instructs serde to borrow data during deserialization of `Cow<'_, str>`, `Cow<'_, [u8]>`, or `Cow<'_, [u8; N]>`. (#347)
|
||||
The implementation is for [serde#2072](https://github.com/serde-rs/serde/pull/2072#pullrequestreview-735511713) and [serde#2016](https://github.com/serde-rs/serde/issues/2016), about `#[serde(borrow)]` not working for `Option<Cow<'a, str>>`.
|
||||
|
||||
```rust
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Data<'a> {
|
||||
#[serde_as(as = "Option<[BorrowCow; 1]>")]
|
||||
nested: Option<[Cow<'a, str>; 1]>,
|
||||
}
|
||||
```
|
||||
|
||||
The `#[serde(borrow)]` annotation is automatically added by the `#[serde_as]` attribute.
|
||||
|
||||
### Changed
|
||||
|
||||
* Bump MSRV to 1.46, since the dev-dependency `bitflags` requires that version now.
|
||||
* `flattened_maybe!` no longer requires the `serde_with` crate to be available with a specific name.
|
||||
This allows renaming the crate or using `flattened_maybe!` through a re-export without any complications.
|
||||
|
||||
## [1.9.4] - 2021-06-18
|
||||
|
||||
### Fixed
|
||||
|
||||
* `with_prefix!` now supports an optional visibility modifier. (#327, #328)
|
||||
If not specified `pub(self)` is assumed.
|
||||
|
||||
```rust
|
||||
with_prefix!(prefix_active "active_"); // => mod {...}
|
||||
with_prefix!(pub prefix_active "active_"); // => pub mod {...}
|
||||
with_prefix!(pub(crate) prefix_active "active_"); // => pub(crate) mod {...}
|
||||
with_prefix!(pub(in other_mod) prefix_active "active_"); // => pub(in other_mod) mod {...}
|
||||
```
|
||||
|
||||
Thanks to @elpiel for raising and fixing the issue.
|
||||
|
||||
## [1.9.3] - 2021-06-14
|
||||
|
||||
### Added
|
||||
|
||||
* The `Bytes` type now supports borrowed and Cow arrays of fixed size (requires Rust 1.51+)
|
||||
|
||||
```rust
|
||||
#[serde_as(as = "Bytes")]
|
||||
#[serde(borrow)]
|
||||
borrowed_array: &'a [u8; 15],
|
||||
#[serde_as(as = "Bytes")]
|
||||
#[serde(borrow)]
|
||||
cow_array: Cow<'a, [u8; 15]>,
|
||||
```
|
||||
|
||||
Note: For borrowed arrays the used Deserializer needs to support Serde's 0-copy deserialization.
|
||||
|
||||
## [1.9.2] - 2021-06-07
|
||||
|
||||
### Fixed
|
||||
|
||||
* Suppress clippy warnings, which can occur while using `serde_conv` (#320)
|
||||
Thanks to @mkroening for reporting and fixing the issue.
|
||||
|
||||
## [1.9.1] - 2021-05-15
|
||||
|
||||
### Changed
|
||||
|
||||
* `NoneAsEmptyString`: Deserialize using `FromStr` instead of using `for<'a> From<&'a str>` (#316)
|
||||
This will *not* change any behavior when applied to a field of type `Option<String>` as used in the documentation.
|
||||
Thanks to @mkroening for finding and fixing the issue.
|
||||
|
||||
## [1.9.0] - 2021-05-09
|
||||
|
||||
### Added
|
||||
|
||||
* Added `FromInto` and `TryFromInto` adapters, which enable serialization by converting into a proxy type.
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
#[serde_as(as = "FromInto<(u8, u8, u8)>")]
|
||||
value: Rgb,
|
||||
|
||||
impl From<(u8, u8, u8)> for Rgb { ... }
|
||||
impl From<Rgb> for (u8, u8, u8) { ... }
|
||||
|
||||
// JSON
|
||||
"value": [128, 64, 32],
|
||||
```
|
||||
|
||||
* New `serde_conv!` macro to create conversion types with reduced boilerplate.
|
||||
The generated types can be used with `#[serde_as]` or serde's with-attribute.
|
||||
|
||||
```rust
|
||||
serde_with::serde_conv!(
|
||||
RgbAsArray,
|
||||
Rgb,
|
||||
|rgb: &Rgb| [rgb.red, rgb.green, rgb.blue],
|
||||
|value: [u8; 3]| -> Result<_, std::convert::Infallible> {
|
||||
Ok(Rgb {
|
||||
red: value[0],
|
||||
green: value[1],
|
||||
blue: value[2],
|
||||
})
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## [1.8.1] - 2021-04-19
|
||||
|
||||
### Added
|
||||
|
||||
* The `hex::Hex` type also works for u8-arrays on Rust 1.48.
|
||||
Thanks to @TheAlgorythm for raising and fixing the issue.
|
||||
|
||||
## [1.8.0] - 2021-03-30
|
||||
|
||||
### Added
|
||||
|
||||
* Added `PickFirst` adapter for `serde_as`. [#291]
|
||||
It allows deserializing from multiple different forms.
|
||||
Deserializing a number from either a number or string can be implemented like:
|
||||
|
||||
```rust
|
||||
#[serde_as(as = "PickFirst<(_, DisplayFromStr)>")]
|
||||
value: u32,
|
||||
```
|
||||
|
||||
* Implement `SerializeAs`/`DeserializeAs` for more wrapper types. [#288], [#293]
|
||||
This now supports:
|
||||
* `Arc`, `sync::Weak`
|
||||
* `Rc`, `rc::Weak`
|
||||
* `Cell`, `RefCell`
|
||||
* `Mutex`, `RwLock`
|
||||
* `Result`
|
||||
|
||||
[#288]: https://github.com/jonasbb/serde_with/issues/288
|
||||
[#291]: https://github.com/jonasbb/serde_with/issues/291
|
||||
[#293]: https://github.com/jonasbb/serde_with/issues/293
|
||||
|
||||
### Changed
|
||||
|
||||
* Add a new `serde_with::rust::map_as_tuple_list` module as a replacement for `serde_with::rust::btreemap_as_tuple_list` and `serde_with::rust::hashmap_as_tuple_list`.
|
||||
The new module uses `IntoIterator` and `FromIterator` as trait bound making it usable in more situations.
|
||||
The old names continue to exist but are marked as deprecated.
|
||||
|
||||
### Deprecated
|
||||
|
||||
* Deprecated the module names `serde_with::rust::btreemap_as_tuple_list` and `serde_with::rust::hashmap_as_tuple_list`.
|
||||
You can use `serde_with::rust::map_as_tuple_list` as a replacement.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Implement `Timestamp*Seconds` and `Duration*Seconds` also for chrono types.
|
||||
This closes [#194]. This was incompletely implemented in [#199].
|
||||
|
||||
[#194]: https://github.com/jonasbb/serde_with/issues/194
|
||||
[#199]: https://github.com/jonasbb/serde_with/issues/199
|
||||
|
||||
## [1.7.0] - 2021-03-24
|
||||
|
||||
### Added
|
||||
|
||||
* Add support for arrays of arbitrary size. ([#272])
|
||||
This feature requires Rust 1.51+.
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
#[serde_as(as = "[[_; 64]; 33]")]
|
||||
value: [[u8; 64]; 33],
|
||||
|
||||
// JSON
|
||||
"value": [[0,0,0,0,0,...], [0,0,0,...], ...],
|
||||
```
|
||||
|
||||
Mapping of arrays was available before, but limited to arrays of length 32.
|
||||
All conversion methods are available for the array elements.
|
||||
|
||||
This is similar to the existing [`serde-big-array`] crate with three important improvements:
|
||||
|
||||
1. Support for the `serde_as` annotation.
|
||||
2. Supports non-copy elements (see [serde-big-array#6][serde-big-array-copy]).
|
||||
3. Supports arbitrary nestings of arrays (see [serde-big-array#7][serde-big-array-nested]).
|
||||
|
||||
[#272]: https://github.com/jonasbb/serde_with/pull/272
|
||||
[`serde-big-array`]: https://crates.io/crates/serde-big-array
|
||||
[serde-big-array-copy]: https://github.com/est31/serde-big-array/issues/6
|
||||
[serde-big-array-nested]: https://github.com/est31/serde-big-array/issues/7
|
||||
|
||||
* Arrays with tuple elements can now be deserialized from a map. ([#272])
|
||||
This feature requires Rust 1.51+.
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
#[serde_as(as = "BTreeMap<_, _>")]
|
||||
value: [(String, u16); 3],
|
||||
|
||||
// JSON
|
||||
"value": {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": 3
|
||||
},
|
||||
```
|
||||
|
||||
* The `Bytes` type is heavily inspired by `serde_bytes` and ports it to the `serde_as` system. ([#277])
|
||||
|
||||
```rust
|
||||
#[serde_as(as = "Bytes")]
|
||||
value: Vec<u8>,
|
||||
```
|
||||
|
||||
Compared to `serde_bytes` these improvements are available
|
||||
|
||||
1. Integration with the `serde_as` annotation (see [serde-bytes#14][serde-bytes-complex]).
|
||||
2. Implementation for arrays of arbitrary size (Rust 1.51+) (see [serde-bytes#26][serde-bytes-arrays]).
|
||||
|
||||
[#277]: https://github.com/jonasbb/serde_with/pull/277
|
||||
[serde-bytes-complex]: https://github.com/serde-rs/bytes/issues/14
|
||||
[serde-bytes-arrays]: https://github.com/serde-rs/bytes/issues/26
|
||||
|
||||
* The `OneOrMany` type allows deserializing a `Vec` from either a single element or a sequence. ([#281])
|
||||
|
||||
```rust
|
||||
#[serde_as(as = "OneOrMany<_>")]
|
||||
cities: Vec<String>,
|
||||
```
|
||||
|
||||
This allows deserializing from either `cities: "Berlin"` or `cities: ["Berlin", "Paris"]`.
|
||||
The serialization can be configured to always emit a list with `PreferMany` or emit a single element with `PreferOne`.
|
||||
|
||||
[#281]: https://github.com/jonasbb/serde_with/pull/281
|
||||
|
||||
## [1.6.4] - 2021-02-16
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix compiling when having a struct field without the `serde_as` annotation by updating `serde_with_macros`.
|
||||
This broke in 1.4.0 of `serde_with_macros`. [#267](https://github.com/jonasbb/serde_with/issues/267)
|
||||
|
||||
## [1.6.3]
|
||||
## [1.6.3] - 2021-02-15
|
||||
|
||||
### Changed
|
||||
|
||||
* Bump macro crate dependency (`serde_with_macros`) to 1.4.0 to pull in those improvements.
|
||||
|
||||
## [1.6.2]
|
||||
## [1.6.2] - 2021-01-30
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -28,7 +444,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
|
||||
Thanks to @lovasoa for suggesting and implementing it.
|
||||
|
||||
## [1.6.1]
|
||||
## [1.6.1] - 2021-01-24
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -41,7 +457,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
* Release `Sized` trait bound from `As`, `Same`, `SerializeAs`, and `SerializeAsWrap`.
|
||||
Only the serialize part is relaxed.
|
||||
|
||||
## [1.6.0]
|
||||
## [1.6.0] - 2020-11-22
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -55,15 +471,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
|
||||
* Bump minimum supported rust version to 1.40.0
|
||||
|
||||
## [1.5.1]
|
||||
## [1.5.1] - 2020-10-07
|
||||
|
||||
### Fixed
|
||||
|
||||
* Depend on serde with the `derive` feature enabled.
|
||||
The `derive` feature is required to deserliaze untagged enums which are used in the `DefaultOnError` helpers.
|
||||
The `derive` feature is required to deserialize untagged enums which are used in the `DefaultOnError` helpers.
|
||||
This fixes compilation of `serde_with` in scenarios where no other crate enables the `derive` feature.
|
||||
|
||||
## [1.5.0]
|
||||
## [1.5.0] - 2020-10-01
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -85,7 +501,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
|
||||
This is part of `serde_with_macros` v1.2.0.
|
||||
* Added some `serialize` functions to modules which previously had none.
|
||||
This makes it easier to use the conversion when also deriving `Serialialize`.
|
||||
This makes it easier to use the conversion when also deriving `Serialize`.
|
||||
The functions simply pass through to the underlying `Serialize` implementation.
|
||||
This affects `sets_duplicate_value_is_error`, `maps_duplicate_key_is_error`, `maps_first_key_wins`, `default_on_error`, and `default_on_null`.
|
||||
* Added `sets_last_value_wins` as a replacement for `sets_first_value_wins` which is deprecated now.
|
||||
|
@ -113,7 +529,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
* Deprecate `sets_first_value_wins`.
|
||||
The default behavior of serde is to take the first value, so this module is not necessary.
|
||||
|
||||
## [1.5.0-alpha.2]
|
||||
## [1.5.0-alpha.2] - 2020-08-16
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -129,7 +545,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
* The `serde_as` macro now supports serde attributes and no longer panic on unrecognized values in the attribute.
|
||||
This is part of `serde_with_macros` v1.2.0-alpha.2.
|
||||
|
||||
## [1.5.0-alpha.1]
|
||||
## [1.5.0-alpha.1] - 2020-06-27
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -154,7 +570,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
|
||||
* The `with_prefix!` macro, to add a string prefixes during serialization, now also works with unit variant enum types. #115 #116
|
||||
|
||||
## [1.4.0]
|
||||
## [1.4.0] - 2020-01-16
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -169,22 +585,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
* version-sync depends on smallvec which requires 1.36
|
||||
* Improved CI pipeline by running `cargo audit` and `tarpaulin` in all configurations now.
|
||||
|
||||
## [1.3.1]
|
||||
## [1.3.1] - 2019-04-09
|
||||
|
||||
### Fixed
|
||||
|
||||
* Use `serde_with_macros` with proper dependencies specified.
|
||||
|
||||
## [1.3.0]
|
||||
## [1.3.0] - 2019-04-02
|
||||
|
||||
### Added
|
||||
|
||||
* Add `skip_serializing_none` attribute, which adds `#[serde(skip_serializing_if = "Option::is_none")]` for each Option in a struct.
|
||||
This is helpfull for APIs which have many optional fields.
|
||||
This is helpful for APIs which have many optional fields.
|
||||
The effect of can be negated by adding `serialize_always` on those fields, which should always be serialized.
|
||||
Existing `skip_serializing_if` will never be modified and those fields keep their behavior.
|
||||
|
||||
## [1.2.0]
|
||||
## [1.2.0] - 2019-03-04
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -195,32 +611,32 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
|
||||
* Bumped minimal Rust version to 1.30.0
|
||||
|
||||
## [1.1.0]
|
||||
## [1.1.0] - 2019-02-18
|
||||
|
||||
### Added
|
||||
|
||||
* Serialize HashMap/BTreeMap as list of tuples
|
||||
|
||||
## [1.0.0]
|
||||
## [1.0.0] - 2019-01-17
|
||||
|
||||
### Added
|
||||
|
||||
* No changes in this release.
|
||||
* Bumped version number to indicate the stability of the library.
|
||||
|
||||
## [0.2.5]
|
||||
## [0.2.5] - 2018-11-29
|
||||
|
||||
### Added
|
||||
|
||||
* Helper which deserializes an empty string as `None` and otherwise uses `FromStr` and `AsRef<str>`.
|
||||
|
||||
## [0.2.4]
|
||||
## [0.2.4] - 2018-11-24
|
||||
|
||||
### Added
|
||||
|
||||
* De/Serialize sequences by using `Display` and `FromStr` implementations on each element. Contributed by @katyo
|
||||
|
||||
## [0.2.3]
|
||||
## [0.2.3] - 2018-11-08
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -232,34 +648,34 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
* Improve Travis configuration
|
||||
* Various clippy improvements
|
||||
|
||||
## [0.2.2]
|
||||
## [0.2.2] - 2018-08-05
|
||||
|
||||
### Added
|
||||
|
||||
* `unwrap_or_skip` allows to transparently serialize the inner part of a `Some(T)`
|
||||
* Add deserialization helpser for sets and maps, inspired by [comment](https://github.com/serde-rs/serde/issues/553#issuecomment-299711855)
|
||||
* Add deserialization helper for sets and maps, inspired by [comment](https://github.com/serde-rs/serde/issues/553#issuecomment-299711855)
|
||||
* Create an error if duplicate values for a set are detected
|
||||
* Create an error if duplicate keys for a map are detected
|
||||
* Implement a first-value wins strategy for sets/maps. This is different to serde's default
|
||||
which implements a last value wins strategy.
|
||||
|
||||
## [0.2.1]
|
||||
## [0.2.1] - 2018-06-05
|
||||
|
||||
### Added
|
||||
|
||||
* Double Option pattern to differentiate between missing, unset, or existing value
|
||||
* `with_prefix!` macro, which puts a prefix on every struct field
|
||||
|
||||
## [0.2.0]
|
||||
## [0.2.0] - 2018-05-31
|
||||
|
||||
### Added
|
||||
|
||||
* Add chrono support: Deserialize timestamps from int, float, and string
|
||||
* Serialization of embedded JSON strings
|
||||
* De/Serialization using `Display` and `FromStr` implementations
|
||||
* String-based collections using `Display` and `FromStr`, allows to deserialize "#foo,#bar"
|
||||
* String-based collections using `Display` and `FromStr`, allows deserializing "#foo,#bar"
|
||||
|
||||
## [0.1.0]
|
||||
## [0.1.0] - 2017-08-17
|
||||
|
||||
### Added
|
||||
|
||||
|
|
|
@ -3,62 +3,122 @@
|
|||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you believe there's an error in this file please file an
|
||||
# issue against the rust-lang/cargo repository. If you're
|
||||
# editing this file be aware that the upstream Cargo.toml
|
||||
# will likely look very different (and much more reasonable)
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2018"
|
||||
rust-version = "1.53"
|
||||
name = "serde_with"
|
||||
version = "1.6.4"
|
||||
authors = ["Jonas Bushart"]
|
||||
include = ["src/**/*", "LICENSE-*", "README.*", "CHANGELOG.md"]
|
||||
version = "1.14.0"
|
||||
authors = [
|
||||
"Jonas Bushart",
|
||||
"Marcin Kaźmierczak",
|
||||
]
|
||||
include = [
|
||||
"src/**/*",
|
||||
"tests/**/*",
|
||||
"LICENSE-*",
|
||||
"README.md",
|
||||
"CHANGELOG.md",
|
||||
]
|
||||
description = "Custom de/serialization functions for Rust's serde"
|
||||
documentation = "https://docs.rs/serde_with/"
|
||||
readme = "README.md"
|
||||
keywords = ["serde", "utilities", "serialization", "deserialization"]
|
||||
keywords = [
|
||||
"serde",
|
||||
"utilities",
|
||||
"serialization",
|
||||
"deserialization",
|
||||
]
|
||||
categories = ["encoding"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/jonasbb/serde_with"
|
||||
resolver = "2"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = [
|
||||
"--cfg=docsrs",
|
||||
"-Zunstable-options",
|
||||
"--generate-link-to-definition",
|
||||
]
|
||||
|
||||
[[test]]
|
||||
name = "base64"
|
||||
path = "tests/base64.rs"
|
||||
required-features = [
|
||||
"base64",
|
||||
"macros",
|
||||
]
|
||||
|
||||
[[test]]
|
||||
name = "chrono"
|
||||
path = "tests/chrono.rs"
|
||||
required-features = ["chrono", "macros"]
|
||||
required-features = [
|
||||
"chrono",
|
||||
"macros",
|
||||
]
|
||||
|
||||
[[test]]
|
||||
name = "hex"
|
||||
path = "tests/hex.rs"
|
||||
required-features = ["hex", "macros"]
|
||||
required-features = [
|
||||
"hex",
|
||||
"macros",
|
||||
]
|
||||
|
||||
[[test]]
|
||||
name = "indexmap"
|
||||
path = "tests/indexmap.rs"
|
||||
required-features = [
|
||||
"indexmap",
|
||||
"macros",
|
||||
]
|
||||
|
||||
[[test]]
|
||||
name = "json"
|
||||
path = "tests/json.rs"
|
||||
required-features = ["json", "macros"]
|
||||
required-features = [
|
||||
"json",
|
||||
"macros",
|
||||
]
|
||||
|
||||
[[test]]
|
||||
name = "serde_as"
|
||||
path = "tests/serde_as.rs"
|
||||
path = "tests/serde_as/lib.rs"
|
||||
required-features = ["macros"]
|
||||
|
||||
[[test]]
|
||||
name = "serde_as_duration"
|
||||
path = "tests/serde_as_duration.rs"
|
||||
required-features = ["macros"]
|
||||
name = "time_0_3"
|
||||
path = "tests/time_0_3.rs"
|
||||
required-features = [
|
||||
"macros",
|
||||
"time_0_3",
|
||||
]
|
||||
|
||||
[[test]]
|
||||
name = "serde_as_macro"
|
||||
path = "tests/serde_as_macro.rs"
|
||||
name = "derives"
|
||||
path = "tests/derives/lib.rs"
|
||||
required-features = ["macros"]
|
||||
|
||||
[dependencies.base64_crate]
|
||||
version = "0.13.0"
|
||||
optional = true
|
||||
package = "base64"
|
||||
|
||||
[dependencies.chrono_crate]
|
||||
version = "0.4.1"
|
||||
features = ["serde"]
|
||||
features = [
|
||||
"clock",
|
||||
"serde",
|
||||
"std",
|
||||
]
|
||||
optional = true
|
||||
default-features = false
|
||||
package = "chrono"
|
||||
|
||||
[dependencies.doc-comment]
|
||||
|
@ -69,8 +129,14 @@ optional = true
|
|||
version = "0.4.2"
|
||||
optional = true
|
||||
|
||||
[dependencies.indexmap_crate]
|
||||
version = "1.8"
|
||||
features = ["serde-1"]
|
||||
optional = true
|
||||
package = "indexmap"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.75"
|
||||
version = "1.0.122"
|
||||
features = ["derive"]
|
||||
|
||||
[dependencies.serde_json]
|
||||
|
@ -78,8 +144,15 @@ version = "1.0.1"
|
|||
optional = true
|
||||
|
||||
[dependencies.serde_with_macros]
|
||||
version = "1.4.1"
|
||||
version = "1.5.2"
|
||||
optional = true
|
||||
|
||||
[dependencies.time_0_3]
|
||||
version = "~0.3"
|
||||
features = ["serde-well-known"]
|
||||
optional = true
|
||||
package = "time"
|
||||
|
||||
[dev-dependencies.expect-test]
|
||||
version = "1.0.0"
|
||||
|
||||
|
@ -93,37 +166,49 @@ version = "0.3.0"
|
|||
version = "0.3.16"
|
||||
|
||||
[dev-dependencies.pretty_assertions]
|
||||
version = "0.6.1"
|
||||
version = "1.0.0"
|
||||
|
||||
[dev-dependencies.regex]
|
||||
version = "1.3.9"
|
||||
features = ["std"]
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies.rmp-serde]
|
||||
version = "1.1.0"
|
||||
|
||||
[dev-dependencies.ron]
|
||||
version = "0.6"
|
||||
version = "0.7"
|
||||
|
||||
[dev-dependencies.rustversion]
|
||||
version = "1.0.0"
|
||||
|
||||
[dev-dependencies.serde-xml-rs]
|
||||
version = "0.4.1"
|
||||
|
||||
[dev-dependencies.serde_derive]
|
||||
version = "1.0.75"
|
||||
version = "0.5.0"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0.25"
|
||||
features = ["preserve_order"]
|
||||
|
||||
[dev-dependencies.serde_test]
|
||||
version = "1.0.124"
|
||||
|
||||
[dev-dependencies.serde_yaml]
|
||||
version = "0.8.21"
|
||||
|
||||
[dev-dependencies.version-sync]
|
||||
version = "0.9.1"
|
||||
|
||||
[features]
|
||||
base64 = ["base64_crate"]
|
||||
chrono = ["chrono_crate"]
|
||||
default = ["macros"]
|
||||
guide = ["doc-comment", "macros"]
|
||||
guide = [
|
||||
"doc-comment",
|
||||
"macros",
|
||||
]
|
||||
indexmap = ["indexmap_crate"]
|
||||
json = ["serde_json"]
|
||||
macros = ["serde_with_macros"]
|
||||
|
||||
[badges.maintenance]
|
||||
status = "actively-developed"
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
# Custom de/serialization functions for Rust's [serde](https://serde.rs)
|
||||
|
||||
[![docs.rs badge](https://docs.rs/serde_with/badge.svg)](https://docs.rs/serde_with/)
|
||||
[![crates.io badge](https://img.shields.io/crates/v/serde_with.svg)](https://crates.io/crates/serde_with/)
|
||||
[![Build Status](https://github.com/jonasbb/serde_with/workflows/Rust%20CI/badge.svg)](https://github.com/jonasbb/serde_with)
|
||||
[![codecov](https://codecov.io/gh/jonasbb/serde_with/branch/master/graph/badge.svg)](https://codecov.io/gh/jonasbb/serde_with)
|
||||
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4322/badge)](https://bestpractices.coreinfrastructure.org/projects/4322)
|
||||
[![Binder](https://img.shields.io/badge/Try%20on%20-binder-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0NDQ1NbW3Nzg4ODi5+3v8PDw8/T09PX29vb39/f5+fr7+/z8/Pz9/v7+zczCxgAABC5JREFUeAHN1ul3k0UUBvCb1CTVpmpaitAGSLSpSuKCLWpbTKNJFGlcSMAFF63iUmRccNG6gLbuxkXU66JAUef/9LSpmXnyLr3T5AO/rzl5zj137p136BISy44fKJXuGN/d19PUfYeO67Znqtf2KH33Id1psXoFdW30sPZ1sMvs2D060AHqws4FHeJojLZqnw53cmfvg+XR8mC0OEjuxrXEkX5ydeVJLVIlV0e10PXk5k7dYeHu7Cj1j+49uKg7uLU61tGLw1lq27ugQYlclHC4bgv7VQ+TAyj5Zc/UjsPvs1sd5cWryWObtvWT2EPa4rtnWW3JkpjggEpbOsPr7F7EyNewtpBIslA7p43HCsnwooXTEc3UmPmCNn5lrqTJxy6nRmcavGZVt/3Da2pD5NHvsOHJCrdc1G2r3DITpU7yic7w/7Rxnjc0kt5GC4djiv2Sz3Fb2iEZg41/ddsFDoyuYrIkmFehz0HR2thPgQqMyQYb2OtB0WxsZ3BeG3+wpRb1vzl2UYBog8FfGhttFKjtAclnZYrRo9ryG9uG/FZQU4AEg8ZE9LjGMzTmqKXPLnlWVnIlQQTvxJf8ip7VgjZjyVPrjw1te5otM7RmP7xm+sK2Gv9I8Gi++BRbEkR9EBw8zRUcKxwp73xkaLiqQb+kGduJTNHG72zcW9LoJgqQxpP3/Tj//c3yB0tqzaml05/+orHLksVO+95kX7/7qgJvnjlrfr2Ggsyx0eoy9uPzN5SPd86aXggOsEKW2Prz7du3VID3/tzs/sSRs2w7ovVHKtjrX2pd7ZMlTxAYfBAL9jiDwfLkq55Tm7ifhMlTGPyCAs7RFRhn47JnlcB9RM5T97ASuZXIcVNuUDIndpDbdsfrqsOppeXl5Y+XVKdjFCTh+zGaVuj0d9zy05PPK3QzBamxdwtTCrzyg/2Rvf2EstUjordGwa/kx9mSJLr8mLLtCW8HHGJc2R5hS219IiF6PnTusOqcMl57gm0Z8kanKMAQg0qSyuZfn7zItsbGyO9QlnxY0eCuD1XL2ys/MsrQhltE7Ug0uFOzufJFE2PxBo/YAx8XPPdDwWN0MrDRYIZF0mSMKCNHgaIVFoBbNoLJ7tEQDKxGF0kcLQimojCZopv0OkNOyWCCg9XMVAi7ARJzQdM2QUh0gmBozjc3Skg6dSBRqDGYSUOu66Zg+I2fNZs/M3/f/Grl/XnyF1Gw3VKCez0PN5IUfFLqvgUN4C0qNqYs5YhPL+aVZYDE4IpUk57oSFnJm4FyCqqOE0jhY2SMyLFoo56zyo6becOS5UVDdj7Vih0zp+tcMhwRpBeLyqtIjlJKAIZSbI8SGSF3k0pA3mR5tHuwPFoa7N7reoq2bqCsAk1HqCu5uvI1n6JuRXI+S1Mco54YmYTwcn6Aeic+kssXi8XpXC4V3t7/ADuTNKaQJdScAAAAAElFTkSuQmCC)](https://mybinder.org/v2/gist/jonasbb/18b9aece4c17f617b1c2b3946d29eeb0/HEAD?filepath=serde-with-demo.ipynb)
|
||||
|
||||
---
|
||||
|
||||
This crate provides custom de/serialization helpers to use in combination with [serde's with-annotation][with-annotation] and with the improved [`serde_as`][user guide]-annotation.
|
||||
This crate provides custom de/serialization helpers to use in combination with [serde's with-annotation][with-annotation] and with the improved [`serde_as`][as-annotation]-annotation.
|
||||
Some common use cases are:
|
||||
|
||||
* De/Serializing a type using the `Display` and `FromStr` traits, e.g., for `u8`, `url::Url`, or `mime::Mime`.
|
||||
Check [`DisplayFromStr`][] or [`serde_with::rust::display_fromstr`][display_fromstr] for details.
|
||||
* Support for arrays larger than 32 elements or using const generics.
|
||||
With `serde_as` large arrays are supported, even if they are nested in other types.
|
||||
`[bool; 64]`, `Option<[u8; M]>`, and `Box<[[u8; 64]; N]>` are all supported, as [this examples shows](#large-and-const-generic-arrays).
|
||||
* Skip serializing all empty `Option` types with [`#[skip_serializing_none]`][skip_serializing_none].
|
||||
* Apply a prefix to each field name of a struct, without changing the de/serialize implementations of the struct using [`with_prefix!`][].
|
||||
* Deserialize a comma separated list like `#hash,#tags,#are,#great` into a `Vec<String>`.
|
||||
|
@ -22,7 +26,7 @@ Some common use cases are:
|
|||
**Check out the [user guide][user guide] to find out more tips and tricks about this crate.**
|
||||
|
||||
For further help using this crate you can [open a new discussion](https://github.com/jonasbb/serde_with/discussions/new) or ask on [users.rust-lang.org](https://users.rust-lang.org/).
|
||||
For bugs please open a [new issue](https://github.com/jonasbb/serde_with/issues/new) on Github.
|
||||
For bugs, please open a [new issue](https://github.com/jonasbb/serde_with/issues/new) on GitHub.
|
||||
|
||||
## Use `serde_with` in your Project
|
||||
|
||||
|
@ -30,7 +34,7 @@ Add this to your `Cargo.toml`:
|
|||
|
||||
```toml
|
||||
[dependencies.serde_with]
|
||||
version = "1.6.4"
|
||||
version = "1.14.0"
|
||||
features = [ "..." ]
|
||||
```
|
||||
|
||||
|
@ -40,6 +44,7 @@ Check the [feature flags][] section for information about all available features
|
|||
## Examples
|
||||
|
||||
Annotate your struct or enum to enable the custom de/serializer.
|
||||
The `#[serde_as]` attribute must be place *before* the `#[derive]`.
|
||||
|
||||
### `DisplayFromStr`
|
||||
|
||||
|
@ -59,10 +64,39 @@ Foo {bar: 12}
|
|||
{"bar": "12"}
|
||||
```
|
||||
|
||||
### Large and const-generic arrays
|
||||
|
||||
serde does not support arrays with more than 32 elements or using const-generics.
|
||||
The `serde_as` attribute allows to circumvent this restriction, even for nested types and nested arrays.
|
||||
|
||||
```rust
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Arrays<const N: usize, const M: usize> {
|
||||
#[serde_as(as = "[_; N]")]
|
||||
constgeneric: [bool; N],
|
||||
|
||||
#[serde_as(as = "Box<[[_; 64]; N]>")]
|
||||
nested: Box<[[u8; 64]; N]>,
|
||||
|
||||
#[serde_as(as = "Option<[_; M]>")]
|
||||
optional: Option<[u8; M]>,
|
||||
}
|
||||
|
||||
// This allows us to serialize a struct like this
|
||||
let arrays: Arrays<100, 128> = Arrays {
|
||||
constgeneric: [true; 100],
|
||||
nested: Box::new([[111; 64]; 100]),
|
||||
optional: Some([222; 128])
|
||||
};
|
||||
assert!(serde_json::to_string(&arrays).is_ok());
|
||||
```
|
||||
|
||||
### `skip_serializing_none`
|
||||
|
||||
This situation often occurs with JSON, but other formats also support optional fields.
|
||||
If many fields are optional, putting the annotations on the structs can become tedious.
|
||||
The `#[skip_serializing_none]` attribute must be place *before* the `#[derive]`.
|
||||
|
||||
```rust
|
||||
#[skip_serializing_none]
|
||||
|
@ -124,14 +158,15 @@ Foo {
|
|||
}
|
||||
```
|
||||
|
||||
[`DisplayFromStr`]: https://docs.rs/serde_with/1.6.4/serde_with/struct.DisplayFromStr.html
|
||||
[`with_prefix!`]: https://docs.rs/serde_with/1.6.4/serde_with/macro.with_prefix.html
|
||||
[display_fromstr]: https://docs.rs/serde_with/1.6.4/serde_with/rust/display_fromstr/index.html
|
||||
[feature flags]: https://docs.rs/serde_with/1.6.4/serde_with/guide/feature_flags/index.html
|
||||
[skip_serializing_none]: https://docs.rs/serde_with/1.6.4/serde_with/attr.skip_serializing_none.html
|
||||
[StringWithSeparator]: https://docs.rs/serde_with/1.6.4/serde_with/rust/struct.StringWithSeparator.html
|
||||
[user guide]: https://docs.rs/serde_with/1.6.4/serde_with/guide/index.html
|
||||
[`DisplayFromStr`]: https://docs.rs/serde_with/1.14.0/serde_with/struct.DisplayFromStr.html
|
||||
[`with_prefix!`]: https://docs.rs/serde_with/1.14.0/serde_with/macro.with_prefix.html
|
||||
[display_fromstr]: https://docs.rs/serde_with/1.14.0/serde_with/rust/display_fromstr/index.html
|
||||
[feature flags]: https://docs.rs/serde_with/1.14.0/serde_with/guide/feature_flags/index.html
|
||||
[skip_serializing_none]: https://docs.rs/serde_with/1.14.0/serde_with/attr.skip_serializing_none.html
|
||||
[StringWithSeparator]: https://docs.rs/serde_with/1.14.0/serde_with/rust/struct.StringWithSeparator.html
|
||||
[user guide]: https://docs.rs/serde_with/1.14.0/serde_with/guide/index.html
|
||||
[with-annotation]: https://serde.rs/field-attrs.html#with
|
||||
[as-annotation]: https://docs.rs/serde_with/1.14.0/serde_with/guide/serde_as/index.html
|
||||
|
||||
## License
|
||||
|
||||
|
@ -144,6 +179,10 @@ at your option.
|
|||
|
||||
## Contribution
|
||||
|
||||
For detailed contribution instructions please read [`CONTRIBUTING.md`].
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall
|
||||
be dual licensed as above, without any additional terms or conditions.
|
||||
|
||||
[`CONTRIBUTING.md`]: https://github.com/jonasbb/serde_with/blob/master/CONTRIBUTING.md
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
# Custom de/serialization functions for Rust's [serde](https://serde.rs)
|
||||
|
||||
{{readme}}
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
||||
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
## Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall
|
||||
be dual licensed as above, without any additional terms or conditions.
|
|
@ -0,0 +1,205 @@
|
|||
//! De/Serialization of base64 encoded bytes
|
||||
//!
|
||||
//! This modules is only available when using the `base64` feature of the crate.
|
||||
//!
|
||||
//! Please check the documentation on the [`Base64`] type for details.
|
||||
|
||||
use crate::{formats, DeserializeAs, SerializeAs};
|
||||
use alloc::{format, string::String, vec::Vec};
|
||||
use core::{
|
||||
convert::{TryFrom, TryInto},
|
||||
default::Default,
|
||||
marker::PhantomData,
|
||||
};
|
||||
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// Serialize bytes with base64
|
||||
///
|
||||
/// The type serializes a sequence of bytes as a base64 string.
|
||||
/// It works on any type implementing `AsRef<[u8]>` for serialization and `TryFrom<Vec<u8>>` for deserialization.
|
||||
///
|
||||
/// The type allows customizing the character set and the padding behavior.
|
||||
/// The `CHARSET` is a type implementing [`CharacterSet`].
|
||||
/// `PADDING` specifies if serializing should emit padding.
|
||||
/// Deserialization always supports padded and unpadded formats.
|
||||
/// [`formats::Padded`] emits padding and [`formats::Unpadded`] leaves it off.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_with::serde_as;
|
||||
/// use serde_with::base64::{Base64, Bcrypt, BinHex, Standard};
|
||||
/// use serde_with::formats::{Padded, Unpadded};
|
||||
///
|
||||
/// #[serde_as]
|
||||
/// # #[derive(Debug, PartialEq, Eq)]
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// struct B64 {
|
||||
/// // The default is the same as Standard character set with padding
|
||||
/// #[serde_as(as = "Base64")]
|
||||
/// default: Vec<u8>,
|
||||
/// // Only change the character set, implies padding
|
||||
/// #[serde_as(as = "Base64<BinHex>")]
|
||||
/// charset_binhex: Vec<u8>,
|
||||
///
|
||||
/// #[serde_as(as = "Base64<Standard, Padded>")]
|
||||
/// explicit_padding: Vec<u8>,
|
||||
/// #[serde_as(as = "Base64<Bcrypt, Unpadded>")]
|
||||
/// no_padding: Vec<u8>,
|
||||
/// }
|
||||
///
|
||||
/// let b64 = B64 {
|
||||
/// default: b"Hello World".to_vec(),
|
||||
/// charset_binhex: b"Hello World".to_vec(),
|
||||
/// explicit_padding: b"Hello World".to_vec(),
|
||||
/// no_padding: b"Hello World".to_vec(),
|
||||
/// };
|
||||
/// let json = serde_json::json!({
|
||||
/// "default": "SGVsbG8gV29ybGQ=",
|
||||
/// "charset_binhex": "5'8VD'mI8epaD'3=",
|
||||
/// "explicit_padding": "SGVsbG8gV29ybGQ=",
|
||||
/// "no_padding": "QETqZE6eT07wZEO",
|
||||
/// });
|
||||
///
|
||||
/// // Test serialization and deserialization
|
||||
/// assert_eq!(json, serde_json::to_value(&b64).unwrap());
|
||||
/// assert_eq!(b64, serde_json::from_value(json).unwrap());
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
// The padding might be better as `const PADDING: bool = true`
|
||||
// https://blog.rust-lang.org/inside-rust/2021/09/06/Splitting-const-generics.html#featureconst_generics_default/
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct Base64<CHARSET: CharacterSet = Standard, PADDING: formats::Format = formats::Padded>(
|
||||
PhantomData<(CHARSET, PADDING)>,
|
||||
);
|
||||
|
||||
impl<T, CHARSET> SerializeAs<T> for Base64<CHARSET, formats::Padded>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
CHARSET: CharacterSet,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
base64_crate::encode_config(source, base64_crate::Config::new(CHARSET::charset(), true))
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, CHARSET> SerializeAs<T> for Base64<CHARSET, formats::Unpadded>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
CHARSET: CharacterSet,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
base64_crate::encode_config(source, base64_crate::Config::new(CHARSET::charset(), false))
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T, CHARSET, FORMAT> DeserializeAs<'de, T> for Base64<CHARSET, FORMAT>
|
||||
where
|
||||
T: TryFrom<Vec<u8>>,
|
||||
CHARSET: CharacterSet,
|
||||
FORMAT: formats::Format,
|
||||
{
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
String::deserialize(deserializer)
|
||||
.and_then(|s| {
|
||||
base64_crate::decode_config(
|
||||
&*s,
|
||||
base64_crate::Config::new(CHARSET::charset(), false),
|
||||
)
|
||||
.map_err(Error::custom)
|
||||
})
|
||||
.and_then(|vec: Vec<u8>| {
|
||||
let length = vec.len();
|
||||
vec.try_into().map_err(|_e: T::Error| {
|
||||
Error::custom(format!(
|
||||
"Can't convert a Byte Vector of length {} to the output type.",
|
||||
length
|
||||
))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A base64 character set from [this list](base64_crate::CharacterSet).
|
||||
pub trait CharacterSet {
|
||||
/// Return a specific character set.
|
||||
///
|
||||
/// Return one enum variant of the [`base64::CharacterSet`](base64_crate::CharacterSet) enum.
|
||||
fn charset() -> base64_crate::CharacterSet;
|
||||
}
|
||||
|
||||
/// The standard character set (uses `+` and `/`).
|
||||
///
|
||||
/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-3).
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct Standard;
|
||||
impl CharacterSet for Standard {
|
||||
fn charset() -> base64_crate::CharacterSet {
|
||||
base64_crate::CharacterSet::Standard
|
||||
}
|
||||
}
|
||||
|
||||
/// The URL safe character set (uses `-` and `_`).
|
||||
///
|
||||
/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-3).
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct UrlSafe;
|
||||
impl CharacterSet for UrlSafe {
|
||||
fn charset() -> base64_crate::CharacterSet {
|
||||
base64_crate::CharacterSet::UrlSafe
|
||||
}
|
||||
}
|
||||
|
||||
/// The `crypt(3)` character set (uses `./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`).
|
||||
///
|
||||
/// Not standardized, but folk wisdom on the net asserts that this alphabet is what crypt uses.
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct Crypt;
|
||||
impl CharacterSet for Crypt {
|
||||
fn charset() -> base64_crate::CharacterSet {
|
||||
base64_crate::CharacterSet::Crypt
|
||||
}
|
||||
}
|
||||
|
||||
/// The bcrypt character set (uses `./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`).
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct Bcrypt;
|
||||
impl CharacterSet for Bcrypt {
|
||||
fn charset() -> base64_crate::CharacterSet {
|
||||
base64_crate::CharacterSet::Bcrypt
|
||||
}
|
||||
}
|
||||
|
||||
/// The character set used in IMAP-modified UTF-7 (uses `+` and `,`).
|
||||
///
|
||||
/// See [RFC 3501](https://tools.ietf.org/html/rfc3501#section-5.1.3).
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct ImapMutf7;
|
||||
impl CharacterSet for ImapMutf7 {
|
||||
fn charset() -> base64_crate::CharacterSet {
|
||||
base64_crate::CharacterSet::ImapMutf7
|
||||
}
|
||||
}
|
||||
|
||||
/// The character set used in BinHex 4.0 files.
|
||||
///
|
||||
/// See [BinHex 4.0 Definition](http://files.stairways.com/other/binhex-40-specs-info.txt).
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct BinHex;
|
||||
impl CharacterSet for BinHex {
|
||||
fn charset() -> base64_crate::CharacterSet {
|
||||
base64_crate::CharacterSet::BinHex
|
||||
}
|
||||
}
|
|
@ -8,22 +8,33 @@ use crate::{
|
|||
de::DeserializeAs,
|
||||
formats::{Flexible, Format, Strict, Strictness},
|
||||
ser::SerializeAs,
|
||||
utils, DurationSeconds, DurationSecondsWithFrac, TimestampSeconds, TimestampSecondsWithFrac,
|
||||
utils::duration::{DurationSigned, Sign},
|
||||
DurationMicroSeconds, DurationMicroSecondsWithFrac, DurationMilliSeconds,
|
||||
DurationMilliSecondsWithFrac, DurationNanoSeconds, DurationNanoSecondsWithFrac,
|
||||
DurationSeconds, DurationSecondsWithFrac, TimestampMicroSeconds, TimestampMicroSecondsWithFrac,
|
||||
TimestampMilliSeconds, TimestampMilliSecondsWithFrac, TimestampNanoSeconds,
|
||||
TimestampNanoSecondsWithFrac, TimestampSeconds, TimestampSecondsWithFrac,
|
||||
};
|
||||
use chrono_crate::{DateTime, Duration, Local, NaiveDateTime, Utc};
|
||||
use alloc::{format, string::String, vec::Vec};
|
||||
use chrono_crate::{DateTime, Duration, Local, NaiveDateTime, TimeZone, Utc};
|
||||
use core::fmt;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use utils::duration::{DurationSigned, Sign};
|
||||
|
||||
/// Create a [`DateTime`] for the Unix Epoch using the [`Utc`] timezone
|
||||
fn unix_epoch_utc() -> DateTime<Utc> {
|
||||
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc)
|
||||
}
|
||||
|
||||
/// Create a [`DateTime`] for the Unix Epoch using the [`Utc`] timezone
|
||||
/// Create a [`DateTime`] for the Unix Epoch using the [`Local`] timezone
|
||||
fn unix_epoch_local() -> DateTime<Local> {
|
||||
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc).with_timezone(&Local)
|
||||
}
|
||||
|
||||
/// Create a [`NaiveDateTime`] for the Unix Epoch
|
||||
fn unix_epoch_naive() -> NaiveDateTime {
|
||||
NaiveDateTime::from_timestamp(0, 0)
|
||||
}
|
||||
|
||||
/// Deserialize a Unix timestamp with optional subsecond precision into a `DateTime<Utc>`.
|
||||
///
|
||||
/// The `DateTime<Utc>` can be serialized from an integer, a float, or a string representing a number.
|
||||
|
@ -32,7 +43,7 @@ fn unix_epoch_local() -> DateTime<Local> {
|
|||
///
|
||||
/// ```
|
||||
/// # use chrono_crate::{DateTime, Utc};
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// #
|
||||
/// #[derive(Debug, Deserialize)]
|
||||
/// struct S {
|
||||
|
@ -47,8 +58,8 @@ fn unix_epoch_local() -> DateTime<Local> {
|
|||
/// // and strings with numbers, for high-precision values
|
||||
/// assert!(serde_json::from_str::<S>(r#"{ "date": "1478563200.123" }"#).is_ok());
|
||||
/// ```
|
||||
///
|
||||
pub mod datetime_utc_ts_seconds_from_any {
|
||||
use super::*;
|
||||
use chrono_crate::{DateTime, NaiveDateTime, Utc};
|
||||
use serde::de::{Deserializer, Error, Unexpected, Visitor};
|
||||
|
||||
|
@ -61,7 +72,7 @@ pub mod datetime_utc_ts_seconds_from_any {
|
|||
impl<'de> Visitor<'de> for Helper {
|
||||
type Value = DateTime<Utc>;
|
||||
|
||||
fn expecting(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter
|
||||
.write_str("an integer, float, or string with optional subsecond precision.")
|
||||
}
|
||||
|
@ -121,7 +132,7 @@ pub mod datetime_utc_ts_seconds_from_any {
|
|||
|
||||
match *parts.as_slice() {
|
||||
[seconds] => {
|
||||
if let Ok(seconds) = i64::from_str_radix(seconds, 10) {
|
||||
if let Ok(seconds) = seconds.parse() {
|
||||
let ndt = NaiveDateTime::from_timestamp_opt(seconds, 0);
|
||||
if let Some(ndt) = ndt {
|
||||
Ok(DateTime::<Utc>::from_utc(ndt, Utc))
|
||||
|
@ -136,7 +147,7 @@ pub mod datetime_utc_ts_seconds_from_any {
|
|||
}
|
||||
}
|
||||
[seconds, subseconds] => {
|
||||
if let Ok(seconds) = i64::from_str_radix(seconds, 10) {
|
||||
if let Ok(seconds) = seconds.parse() {
|
||||
let subseclen = subseconds.chars().count() as u32;
|
||||
if subseclen > 9 {
|
||||
return Err(Error::custom(format!(
|
||||
|
@ -145,7 +156,7 @@ pub mod datetime_utc_ts_seconds_from_any {
|
|||
)));
|
||||
}
|
||||
|
||||
if let Ok(mut subseconds) = u32::from_str_radix(subseconds, 10) {
|
||||
if let Ok(mut subseconds) = subseconds.parse() {
|
||||
// convert subseconds to nanoseconds (10^-9), require 9 places for nanoseconds
|
||||
subseconds *= 10u32.pow(9 - subseclen);
|
||||
let ndt = NaiveDateTime::from_timestamp_opt(seconds, subseconds);
|
||||
|
@ -229,13 +240,14 @@ where
|
|||
|
||||
macro_rules! use_duration_signed_ser {
|
||||
(
|
||||
$ty:ty =>
|
||||
$main_trait:ident $internal_trait:ident =>
|
||||
$source_to_dur:ident =>
|
||||
$({
|
||||
$format:ty, $strictness:ty =>
|
||||
$($tbound:ident: $bound:path $(,)?)*
|
||||
})*
|
||||
{
|
||||
$ty:ty; $converter:ident =>
|
||||
$({
|
||||
$format:ty, $strictness:ty =>
|
||||
$($tbound:ident: $bound:ident $(,)?)*
|
||||
})*
|
||||
}
|
||||
) => {
|
||||
$(
|
||||
impl<$($tbound ,)*> SerializeAs<$ty> for $main_trait<$format, $strictness>
|
||||
|
@ -246,7 +258,7 @@ macro_rules! use_duration_signed_ser {
|
|||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let dur: DurationSigned = $source_to_dur(source);
|
||||
let dur: DurationSigned = $converter(source);
|
||||
$internal_trait::<$format, $strictness>::serialize_as(
|
||||
&dur,
|
||||
serializer,
|
||||
|
@ -255,56 +267,107 @@ macro_rules! use_duration_signed_ser {
|
|||
}
|
||||
)*
|
||||
};
|
||||
(
|
||||
$( $main_trait:ident $internal_trait:ident, )+ => $rest:tt
|
||||
) => {
|
||||
$( use_duration_signed_ser!($main_trait $internal_trait => $rest); )+
|
||||
};
|
||||
}
|
||||
|
||||
fn datetime_to_duration<TZ>(source: &DateTime<TZ>) -> DurationSigned
|
||||
where
|
||||
TZ: chrono_crate::TimeZone,
|
||||
TZ: TimeZone,
|
||||
{
|
||||
duration_into_duration_signed(&source.clone().signed_duration_since(unix_epoch_utc()))
|
||||
}
|
||||
|
||||
fn naive_datetime_to_duration(source: &NaiveDateTime) -> DurationSigned {
|
||||
duration_into_duration_signed(&source.signed_duration_since(unix_epoch_naive()))
|
||||
}
|
||||
|
||||
use_duration_signed_ser!(
|
||||
Duration =>
|
||||
DurationSeconds DurationSeconds =>
|
||||
duration_into_duration_signed =>
|
||||
{i64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
DurationSeconds DurationSeconds,
|
||||
DurationMilliSeconds DurationMilliSeconds,
|
||||
DurationMicroSeconds DurationMicroSeconds,
|
||||
DurationNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
Duration; duration_into_duration_signed =>
|
||||
{i64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
use_duration_signed_ser!(
|
||||
DateTime<TZ> =>
|
||||
TimestampSeconds DurationSeconds =>
|
||||
datetime_to_duration =>
|
||||
{i64, STRICTNESS => TZ: chrono_crate::offset::TimeZone, STRICTNESS: Strictness}
|
||||
{f64, STRICTNESS => TZ: chrono_crate::offset::TimeZone, STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => TZ: chrono_crate::offset::TimeZone, STRICTNESS: Strictness}
|
||||
TimestampSeconds DurationSeconds,
|
||||
TimestampMilliSeconds DurationMilliSeconds,
|
||||
TimestampMicroSeconds DurationMicroSeconds,
|
||||
TimestampNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
DateTime<TZ>; datetime_to_duration =>
|
||||
{i64, STRICTNESS => TZ: TimeZone, STRICTNESS: Strictness}
|
||||
{f64, STRICTNESS => TZ: TimeZone, STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => TZ: TimeZone, STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
use_duration_signed_ser!(
|
||||
Duration =>
|
||||
DurationSecondsWithFrac DurationSecondsWithFrac =>
|
||||
duration_into_duration_signed =>
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
TimestampSeconds DurationSeconds,
|
||||
TimestampMilliSeconds DurationMilliSeconds,
|
||||
TimestampMicroSeconds DurationMicroSeconds,
|
||||
TimestampNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
NaiveDateTime; naive_datetime_to_duration =>
|
||||
{i64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
|
||||
// Duration/Timestamp WITH FRACTIONS
|
||||
use_duration_signed_ser!(
|
||||
DurationSecondsWithFrac DurationSecondsWithFrac,
|
||||
DurationMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
DurationMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
DurationNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
Duration; duration_into_duration_signed =>
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
use_duration_signed_ser!(
|
||||
DateTime<TZ> =>
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac =>
|
||||
datetime_to_duration =>
|
||||
{f64, STRICTNESS => TZ: chrono_crate::offset::TimeZone, STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => TZ: chrono_crate::offset::TimeZone, STRICTNESS: Strictness}
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac,
|
||||
TimestampMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
TimestampMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
TimestampNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
DateTime<TZ>; datetime_to_duration =>
|
||||
{f64, STRICTNESS => TZ: TimeZone, STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => TZ: TimeZone, STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
use_duration_signed_ser!(
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac,
|
||||
TimestampMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
TimestampMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
TimestampNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
NaiveDateTime; naive_datetime_to_duration =>
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! use_duration_signed_de {
|
||||
(
|
||||
$ty:ty =>
|
||||
$main_trait:ident $internal_trait:ident =>
|
||||
$dur_to_result:ident =>
|
||||
$({
|
||||
$format:ty, $strictness:ty =>
|
||||
$($tbound:ident: $bound:ident)*
|
||||
})*
|
||||
) => {
|
||||
{
|
||||
$ty:ty; $converter:ident =>
|
||||
$({
|
||||
$format:ty, $strictness:ty =>
|
||||
$($tbound:ident: $bound:ident)*
|
||||
})*
|
||||
}
|
||||
) =>{
|
||||
$(
|
||||
impl<'de, $($tbound,)*> DeserializeAs<'de, $ty> for $main_trait<$format, $strictness>
|
||||
where
|
||||
|
@ -315,11 +378,16 @@ macro_rules! use_duration_signed_de {
|
|||
D: Deserializer<'de>,
|
||||
{
|
||||
let dur: DurationSigned = $internal_trait::<$format, $strictness>::deserialize_as(deserializer)?;
|
||||
$dur_to_result::<D>(dur)
|
||||
$converter::<D>(dur)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
(
|
||||
$( $main_trait:ident $internal_trait:ident, )+ => $rest:tt
|
||||
) => {
|
||||
$( use_duration_signed_de!($main_trait $internal_trait => $rest); )+
|
||||
};
|
||||
}
|
||||
|
||||
fn duration_to_datetime_utc<'de, D>(dur: DurationSigned) -> Result<DateTime<Utc>, D::Error>
|
||||
|
@ -336,57 +404,113 @@ where
|
|||
Ok(unix_epoch_local() + duration_from_duration_signed::<D>(dur)?)
|
||||
}
|
||||
|
||||
fn duration_to_naive_datetime<'de, D>(dur: DurationSigned) -> Result<NaiveDateTime, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(unix_epoch_naive() + duration_from_duration_signed::<D>(dur)?)
|
||||
}
|
||||
|
||||
// No subsecond precision
|
||||
use_duration_signed_de!(
|
||||
Duration =>
|
||||
DurationSeconds DurationSeconds =>
|
||||
duration_from_duration_signed =>
|
||||
{i64, Strict =>}
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
DurationSeconds DurationSeconds,
|
||||
DurationMilliSeconds DurationMilliSeconds,
|
||||
DurationMicroSeconds DurationMicroSeconds,
|
||||
DurationNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
Duration; duration_from_duration_signed =>
|
||||
{i64, Strict =>}
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
use_duration_signed_de!(
|
||||
DateTime<Utc> =>
|
||||
TimestampSeconds DurationSeconds =>
|
||||
duration_to_datetime_utc =>
|
||||
{i64, Strict =>}
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
TimestampSeconds DurationSeconds,
|
||||
TimestampMilliSeconds DurationMilliSeconds,
|
||||
TimestampMicroSeconds DurationMicroSeconds,
|
||||
TimestampNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
DateTime<Utc>; duration_to_datetime_utc =>
|
||||
{i64, Strict =>}
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
use_duration_signed_de!(
|
||||
DateTime<Local> =>
|
||||
TimestampSeconds DurationSeconds =>
|
||||
duration_to_datetime_local =>
|
||||
{i64, Strict =>}
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
TimestampSeconds DurationSeconds,
|
||||
TimestampMilliSeconds DurationMilliSeconds,
|
||||
TimestampMicroSeconds DurationMicroSeconds,
|
||||
TimestampNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
DateTime<Local>; duration_to_datetime_local =>
|
||||
{i64, Strict =>}
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
use_duration_signed_de!(
|
||||
TimestampSeconds DurationSeconds,
|
||||
TimestampMilliSeconds DurationMilliSeconds,
|
||||
TimestampMicroSeconds DurationMicroSeconds,
|
||||
TimestampNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
NaiveDateTime; duration_to_naive_datetime =>
|
||||
{i64, Strict =>}
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
|
||||
// Duration/Timestamp WITH FRACTIONS
|
||||
use_duration_signed_de!(
|
||||
Duration =>
|
||||
DurationSecondsWithFrac DurationSecondsWithFrac =>
|
||||
duration_from_duration_signed =>
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
DurationSecondsWithFrac DurationSecondsWithFrac,
|
||||
DurationMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
DurationMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
DurationNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
Duration; duration_from_duration_signed =>
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
use_duration_signed_de!(
|
||||
DateTime<Utc> =>
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac =>
|
||||
duration_to_datetime_utc =>
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac,
|
||||
TimestampMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
TimestampMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
TimestampNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
DateTime<Utc>; duration_to_datetime_utc =>
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
use_duration_signed_de!(
|
||||
DateTime<Local> =>
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac =>
|
||||
duration_to_datetime_local =>
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac,
|
||||
TimestampMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
TimestampMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
TimestampNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
DateTime<Local>; duration_to_datetime_local =>
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
use_duration_signed_de!(
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac,
|
||||
TimestampMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
TimestampMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
TimestampNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
NaiveDateTime; duration_to_naive_datetime =>
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
//! Import of the unstable private `Content` type from `serde`.
|
||||
//!
|
||||
//! <https://github.com/serde-rs/serde/blob/55a7cedd737278a9d75a2efd038c6f38b8c38bd6/serde/src/private/ser.rs#L338-L997>
|
||||
|
||||
pub(crate) mod ser;
|
|
@ -0,0 +1,623 @@
|
|||
//! Buffer for serializing data.
|
||||
//!
|
||||
//! This is a copy and improvement of the `serde` private type:
|
||||
//! <https://github.com/serde-rs/serde/blob/55a7cedd737278a9d75a2efd038c6f38b8c38bd6/serde/src/private/ser.rs#L338-L997>
|
||||
//! The code is very stable in the `serde` crate, so no maintainability problem is expected.
|
||||
//!
|
||||
//! Since the type is private we copy the type here.
|
||||
//! `serde` is licensed as MIT+Apache2, the same as this crate.
|
||||
//!
|
||||
//! This version carries improvements compared to `serde`'s version.
|
||||
//! The types support 128-bit integers, which is supported for all targets in Rust 1.40+.
|
||||
//! The [`ContentSerializer`] can also be configured to human readable or compact representation.
|
||||
|
||||
use alloc::{borrow::ToOwned, boxed::Box, string::String, vec::Vec};
|
||||
use core::marker::PhantomData;
|
||||
use serde::ser::{self, Serialize, Serializer};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Content {
|
||||
Bool(bool),
|
||||
|
||||
U8(u8),
|
||||
U16(u16),
|
||||
U32(u32),
|
||||
U64(u64),
|
||||
U128(u128),
|
||||
|
||||
I8(i8),
|
||||
I16(i16),
|
||||
I32(i32),
|
||||
I64(i64),
|
||||
I128(i128),
|
||||
|
||||
F32(f32),
|
||||
F64(f64),
|
||||
|
||||
Char(char),
|
||||
String(String),
|
||||
Bytes(Vec<u8>),
|
||||
|
||||
None,
|
||||
Some(Box<Content>),
|
||||
|
||||
Unit,
|
||||
UnitStruct(&'static str),
|
||||
UnitVariant(&'static str, u32, &'static str),
|
||||
NewtypeStruct(&'static str, Box<Content>),
|
||||
NewtypeVariant(&'static str, u32, &'static str, Box<Content>),
|
||||
|
||||
Seq(Vec<Content>),
|
||||
Tuple(Vec<Content>),
|
||||
TupleStruct(&'static str, Vec<Content>),
|
||||
TupleVariant(&'static str, u32, &'static str, Vec<Content>),
|
||||
Map(Vec<(Content, Content)>),
|
||||
Struct(&'static str, Vec<(&'static str, Content)>),
|
||||
StructVariant(
|
||||
&'static str,
|
||||
u32,
|
||||
&'static str,
|
||||
Vec<(&'static str, Content)>,
|
||||
),
|
||||
}
|
||||
|
||||
impl Serialize for Content {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match *self {
|
||||
Content::Bool(b) => serializer.serialize_bool(b),
|
||||
Content::U8(u) => serializer.serialize_u8(u),
|
||||
Content::U16(u) => serializer.serialize_u16(u),
|
||||
Content::U32(u) => serializer.serialize_u32(u),
|
||||
Content::U64(u) => serializer.serialize_u64(u),
|
||||
Content::U128(u) => serializer.serialize_u128(u),
|
||||
Content::I8(i) => serializer.serialize_i8(i),
|
||||
Content::I16(i) => serializer.serialize_i16(i),
|
||||
Content::I32(i) => serializer.serialize_i32(i),
|
||||
Content::I64(i) => serializer.serialize_i64(i),
|
||||
Content::I128(i) => serializer.serialize_i128(i),
|
||||
Content::F32(f) => serializer.serialize_f32(f),
|
||||
Content::F64(f) => serializer.serialize_f64(f),
|
||||
Content::Char(c) => serializer.serialize_char(c),
|
||||
Content::String(ref s) => serializer.serialize_str(s),
|
||||
Content::Bytes(ref b) => serializer.serialize_bytes(b),
|
||||
Content::None => serializer.serialize_none(),
|
||||
Content::Some(ref c) => serializer.serialize_some(&**c),
|
||||
Content::Unit => serializer.serialize_unit(),
|
||||
Content::UnitStruct(n) => serializer.serialize_unit_struct(n),
|
||||
Content::UnitVariant(n, i, v) => serializer.serialize_unit_variant(n, i, v),
|
||||
Content::NewtypeStruct(n, ref c) => serializer.serialize_newtype_struct(n, &**c),
|
||||
Content::NewtypeVariant(n, i, v, ref c) => {
|
||||
serializer.serialize_newtype_variant(n, i, v, &**c)
|
||||
}
|
||||
Content::Seq(ref elements) => elements.serialize(serializer),
|
||||
Content::Tuple(ref elements) => {
|
||||
use serde::ser::SerializeTuple;
|
||||
let mut tuple = serializer.serialize_tuple(elements.len())?;
|
||||
for e in elements {
|
||||
tuple.serialize_element(e)?;
|
||||
}
|
||||
tuple.end()
|
||||
}
|
||||
Content::TupleStruct(n, ref fields) => {
|
||||
use serde::ser::SerializeTupleStruct;
|
||||
let mut ts = serializer.serialize_tuple_struct(n, fields.len())?;
|
||||
for f in fields {
|
||||
ts.serialize_field(f)?;
|
||||
}
|
||||
ts.end()
|
||||
}
|
||||
Content::TupleVariant(n, i, v, ref fields) => {
|
||||
use serde::ser::SerializeTupleVariant;
|
||||
let mut tv = serializer.serialize_tuple_variant(n, i, v, fields.len())?;
|
||||
for f in fields {
|
||||
tv.serialize_field(f)?;
|
||||
}
|
||||
tv.end()
|
||||
}
|
||||
Content::Map(ref entries) => {
|
||||
use serde::ser::SerializeMap;
|
||||
let mut map = serializer.serialize_map(Some(entries.len()))?;
|
||||
for &(ref k, ref v) in entries {
|
||||
map.serialize_entry(k, v)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
Content::Struct(n, ref fields) => {
|
||||
use serde::ser::SerializeStruct;
|
||||
let mut s = serializer.serialize_struct(n, fields.len())?;
|
||||
for &(k, ref v) in fields {
|
||||
s.serialize_field(k, v)?;
|
||||
}
|
||||
s.end()
|
||||
}
|
||||
Content::StructVariant(n, i, v, ref fields) => {
|
||||
use serde::ser::SerializeStructVariant;
|
||||
let mut sv = serializer.serialize_struct_variant(n, i, v, fields.len())?;
|
||||
for &(k, ref v) in fields {
|
||||
sv.serialize_field(k, v)?;
|
||||
}
|
||||
sv.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ContentSerializer<E> {
|
||||
is_human_readable: bool,
|
||||
error: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> ContentSerializer<E> {
|
||||
pub(crate) fn new(is_human_readable: bool) -> Self {
|
||||
ContentSerializer {
|
||||
is_human_readable,
|
||||
error: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Default for ContentSerializer<E> {
|
||||
fn default() -> Self {
|
||||
Self::new(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Serializer for ContentSerializer<E>
|
||||
where
|
||||
E: ser::Error,
|
||||
{
|
||||
type Ok = Content;
|
||||
type Error = E;
|
||||
|
||||
type SerializeSeq = SerializeSeq<E>;
|
||||
type SerializeTuple = SerializeTuple<E>;
|
||||
type SerializeTupleStruct = SerializeTupleStruct<E>;
|
||||
type SerializeTupleVariant = SerializeTupleVariant<E>;
|
||||
type SerializeMap = SerializeMap<E>;
|
||||
type SerializeStruct = SerializeStruct<E>;
|
||||
type SerializeStructVariant = SerializeStructVariant<E>;
|
||||
|
||||
fn is_human_readable(&self) -> bool {
|
||||
self.is_human_readable
|
||||
}
|
||||
|
||||
fn serialize_bool(self, v: bool) -> Result<Content, E> {
|
||||
Ok(Content::Bool(v))
|
||||
}
|
||||
|
||||
fn serialize_i8(self, v: i8) -> Result<Content, E> {
|
||||
Ok(Content::I8(v))
|
||||
}
|
||||
|
||||
fn serialize_i16(self, v: i16) -> Result<Content, E> {
|
||||
Ok(Content::I16(v))
|
||||
}
|
||||
|
||||
fn serialize_i32(self, v: i32) -> Result<Content, E> {
|
||||
Ok(Content::I32(v))
|
||||
}
|
||||
|
||||
fn serialize_i64(self, v: i64) -> Result<Content, E> {
|
||||
Ok(Content::I64(v))
|
||||
}
|
||||
|
||||
fn serialize_i128(self, v: i128) -> Result<Content, E> {
|
||||
Ok(Content::I128(v))
|
||||
}
|
||||
|
||||
fn serialize_u8(self, v: u8) -> Result<Content, E> {
|
||||
Ok(Content::U8(v))
|
||||
}
|
||||
|
||||
fn serialize_u16(self, v: u16) -> Result<Content, E> {
|
||||
Ok(Content::U16(v))
|
||||
}
|
||||
|
||||
fn serialize_u32(self, v: u32) -> Result<Content, E> {
|
||||
Ok(Content::U32(v))
|
||||
}
|
||||
|
||||
fn serialize_u64(self, v: u64) -> Result<Content, E> {
|
||||
Ok(Content::U64(v))
|
||||
}
|
||||
|
||||
fn serialize_u128(self, v: u128) -> Result<Content, E> {
|
||||
Ok(Content::U128(v))
|
||||
}
|
||||
|
||||
fn serialize_f32(self, v: f32) -> Result<Content, E> {
|
||||
Ok(Content::F32(v))
|
||||
}
|
||||
|
||||
fn serialize_f64(self, v: f64) -> Result<Content, E> {
|
||||
Ok(Content::F64(v))
|
||||
}
|
||||
|
||||
fn serialize_char(self, v: char) -> Result<Content, E> {
|
||||
Ok(Content::Char(v))
|
||||
}
|
||||
|
||||
fn serialize_str(self, value: &str) -> Result<Content, E> {
|
||||
Ok(Content::String(value.to_owned()))
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, value: &[u8]) -> Result<Content, E> {
|
||||
Ok(Content::Bytes(value.to_owned()))
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<Content, E> {
|
||||
Ok(Content::None)
|
||||
}
|
||||
|
||||
fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Content, E>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
Ok(Content::Some(Box::new(value.serialize(self)?)))
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<Content, E> {
|
||||
Ok(Content::Unit)
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, name: &'static str) -> Result<Content, E> {
|
||||
Ok(Content::UnitStruct(name))
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
name: &'static str,
|
||||
variant_index: u32,
|
||||
variant: &'static str,
|
||||
) -> Result<Content, E> {
|
||||
Ok(Content::UnitVariant(name, variant_index, variant))
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T: ?Sized>(
|
||||
self,
|
||||
name: &'static str,
|
||||
value: &T,
|
||||
) -> Result<Content, E>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
Ok(Content::NewtypeStruct(
|
||||
name,
|
||||
Box::new(value.serialize(self)?),
|
||||
))
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T: ?Sized>(
|
||||
self,
|
||||
name: &'static str,
|
||||
variant_index: u32,
|
||||
variant: &'static str,
|
||||
value: &T,
|
||||
) -> Result<Content, E>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
Ok(Content::NewtypeVariant(
|
||||
name,
|
||||
variant_index,
|
||||
variant,
|
||||
Box::new(value.serialize(self)?),
|
||||
))
|
||||
}
|
||||
|
||||
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, E> {
|
||||
Ok(SerializeSeq {
|
||||
is_human_readable: self.is_human_readable,
|
||||
elements: Vec::with_capacity(len.unwrap_or(0)),
|
||||
error: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, E> {
|
||||
Ok(SerializeTuple {
|
||||
is_human_readable: self.is_human_readable,
|
||||
elements: Vec::with_capacity(len),
|
||||
error: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct(
|
||||
self,
|
||||
name: &'static str,
|
||||
len: usize,
|
||||
) -> Result<Self::SerializeTupleStruct, E> {
|
||||
Ok(SerializeTupleStruct {
|
||||
is_human_readable: self.is_human_readable,
|
||||
name,
|
||||
fields: Vec::with_capacity(len),
|
||||
error: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(
|
||||
self,
|
||||
name: &'static str,
|
||||
variant_index: u32,
|
||||
variant: &'static str,
|
||||
len: usize,
|
||||
) -> Result<Self::SerializeTupleVariant, E> {
|
||||
Ok(SerializeTupleVariant {
|
||||
is_human_readable: self.is_human_readable,
|
||||
name,
|
||||
variant_index,
|
||||
variant,
|
||||
fields: Vec::with_capacity(len),
|
||||
error: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, E> {
|
||||
Ok(SerializeMap {
|
||||
is_human_readable: self.is_human_readable,
|
||||
entries: Vec::with_capacity(len.unwrap_or(0)),
|
||||
key: None,
|
||||
error: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_struct(self, name: &'static str, len: usize) -> Result<Self::SerializeStruct, E> {
|
||||
Ok(SerializeStruct {
|
||||
is_human_readable: self.is_human_readable,
|
||||
name,
|
||||
fields: Vec::with_capacity(len),
|
||||
error: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(
|
||||
self,
|
||||
name: &'static str,
|
||||
variant_index: u32,
|
||||
variant: &'static str,
|
||||
len: usize,
|
||||
) -> Result<Self::SerializeStructVariant, E> {
|
||||
Ok(SerializeStructVariant {
|
||||
is_human_readable: self.is_human_readable,
|
||||
name,
|
||||
variant_index,
|
||||
variant,
|
||||
fields: Vec::with_capacity(len),
|
||||
error: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SerializeSeq<E> {
|
||||
is_human_readable: bool,
|
||||
elements: Vec<Content>,
|
||||
error: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> ser::SerializeSeq for SerializeSeq<E>
|
||||
where
|
||||
E: ser::Error,
|
||||
{
|
||||
type Ok = Content;
|
||||
type Error = E;
|
||||
|
||||
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), E>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let value = value.serialize(ContentSerializer::<E>::new(self.is_human_readable))?;
|
||||
self.elements.push(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Content, E> {
|
||||
Ok(Content::Seq(self.elements))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SerializeTuple<E> {
|
||||
is_human_readable: bool,
|
||||
elements: Vec<Content>,
|
||||
error: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> ser::SerializeTuple for SerializeTuple<E>
|
||||
where
|
||||
E: ser::Error,
|
||||
{
|
||||
type Ok = Content;
|
||||
type Error = E;
|
||||
|
||||
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), E>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let value = value.serialize(ContentSerializer::<E>::new(self.is_human_readable))?;
|
||||
self.elements.push(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Content, E> {
|
||||
Ok(Content::Tuple(self.elements))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SerializeTupleStruct<E> {
|
||||
is_human_readable: bool,
|
||||
name: &'static str,
|
||||
fields: Vec<Content>,
|
||||
error: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> ser::SerializeTupleStruct for SerializeTupleStruct<E>
|
||||
where
|
||||
E: ser::Error,
|
||||
{
|
||||
type Ok = Content;
|
||||
type Error = E;
|
||||
|
||||
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), E>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let value = value.serialize(ContentSerializer::<E>::new(self.is_human_readable))?;
|
||||
self.fields.push(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Content, E> {
|
||||
Ok(Content::TupleStruct(self.name, self.fields))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SerializeTupleVariant<E> {
|
||||
is_human_readable: bool,
|
||||
name: &'static str,
|
||||
variant_index: u32,
|
||||
variant: &'static str,
|
||||
fields: Vec<Content>,
|
||||
error: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> ser::SerializeTupleVariant for SerializeTupleVariant<E>
|
||||
where
|
||||
E: ser::Error,
|
||||
{
|
||||
type Ok = Content;
|
||||
type Error = E;
|
||||
|
||||
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), E>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let value = value.serialize(ContentSerializer::<E>::new(self.is_human_readable))?;
|
||||
self.fields.push(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Content, E> {
|
||||
Ok(Content::TupleVariant(
|
||||
self.name,
|
||||
self.variant_index,
|
||||
self.variant,
|
||||
self.fields,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SerializeMap<E> {
|
||||
is_human_readable: bool,
|
||||
entries: Vec<(Content, Content)>,
|
||||
key: Option<Content>,
|
||||
error: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> ser::SerializeMap for SerializeMap<E>
|
||||
where
|
||||
E: ser::Error,
|
||||
{
|
||||
type Ok = Content;
|
||||
type Error = E;
|
||||
|
||||
fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<(), E>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let key = key.serialize(ContentSerializer::<E>::new(self.is_human_readable))?;
|
||||
self.key = Some(key);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), E>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let key = self
|
||||
.key
|
||||
.take()
|
||||
.expect("serialize_value called before serialize_key");
|
||||
let value = value.serialize(ContentSerializer::<E>::new(self.is_human_readable))?;
|
||||
self.entries.push((key, value));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Content, E> {
|
||||
Ok(Content::Map(self.entries))
|
||||
}
|
||||
|
||||
fn serialize_entry<K: ?Sized, V: ?Sized>(&mut self, key: &K, value: &V) -> Result<(), E>
|
||||
where
|
||||
K: Serialize,
|
||||
V: Serialize,
|
||||
{
|
||||
let key = key.serialize(ContentSerializer::<E>::new(self.is_human_readable))?;
|
||||
let value = value.serialize(ContentSerializer::<E>::new(self.is_human_readable))?;
|
||||
self.entries.push((key, value));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SerializeStruct<E> {
|
||||
is_human_readable: bool,
|
||||
name: &'static str,
|
||||
fields: Vec<(&'static str, Content)>,
|
||||
error: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> ser::SerializeStruct for SerializeStruct<E>
|
||||
where
|
||||
E: ser::Error,
|
||||
{
|
||||
type Ok = Content;
|
||||
type Error = E;
|
||||
|
||||
fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> Result<(), E>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let value = value.serialize(ContentSerializer::<E>::new(self.is_human_readable))?;
|
||||
self.fields.push((key, value));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Content, E> {
|
||||
Ok(Content::Struct(self.name, self.fields))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SerializeStructVariant<E> {
|
||||
is_human_readable: bool,
|
||||
name: &'static str,
|
||||
variant_index: u32,
|
||||
variant: &'static str,
|
||||
fields: Vec<(&'static str, Content)>,
|
||||
error: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> ser::SerializeStructVariant for SerializeStructVariant<E>
|
||||
where
|
||||
E: ser::Error,
|
||||
{
|
||||
type Ok = Content;
|
||||
type Error = E;
|
||||
|
||||
fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> Result<(), E>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let value = value.serialize(ContentSerializer::<E>::new(self.is_human_readable))?;
|
||||
self.fields.push((key, value));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Content, E> {
|
||||
Ok(Content::StructVariant(
|
||||
self.name,
|
||||
self.variant_index,
|
||||
self.variant,
|
||||
self.fields,
|
||||
))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
use super::*;
|
||||
use crate::utils::{MapIter, SeqIter};
|
||||
use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, string::String, vec::Vec};
|
||||
use core::{convert::TryInto, fmt, mem::MaybeUninit};
|
||||
use serde::de::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// TODO this should probably be moved into the utils module when const generics are available for MSRV
|
||||
|
||||
/// # Safety
|
||||
/// The code follow exactly the pattern of initializing an array element-by-element from the standard library.
|
||||
/// <https://doc.rust-lang.org/nightly/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element>
|
||||
fn array_from_iterator<I, T, E, const N: usize>(
|
||||
mut iter: I,
|
||||
expected: &dyn Expected,
|
||||
) -> Result<[T; N], E>
|
||||
where
|
||||
I: Iterator<Item = Result<T, E>>,
|
||||
E: Error,
|
||||
{
|
||||
fn drop_array_elems<T, const N: usize>(num: usize, mut arr: [MaybeUninit<T>; N]) {
|
||||
arr[..num].iter_mut().for_each(|elem| {
|
||||
// TODO This would be better with assume_init_drop nightly function
|
||||
// https://github.com/rust-lang/rust/issues/63567
|
||||
unsafe { core::ptr::drop_in_place(elem.as_mut_ptr()) };
|
||||
});
|
||||
}
|
||||
|
||||
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is
|
||||
// safe because the type we are claiming to have initialized here is a
|
||||
// bunch of `MaybeUninit`s, which do not require initialization.
|
||||
//
|
||||
// TODO could be simplified with nightly maybe_uninit_uninit_array feature
|
||||
// https://doc.rust-lang.org/nightly/std/mem/union.MaybeUninit.html#method.uninit_array
|
||||
let mut arr: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||
|
||||
// Dropping a `MaybeUninit` does nothing. Thus using raw pointer
|
||||
// assignment instead of `ptr::write` does not cause the old
|
||||
// uninitialized value to be dropped. Also if there is a panic during
|
||||
// this loop, we have a memory leak, but there is no memory safety
|
||||
// issue.
|
||||
for (idx, elem) in arr[..].iter_mut().enumerate() {
|
||||
*elem = match iter.next() {
|
||||
Some(Ok(value)) => MaybeUninit::new(value),
|
||||
Some(Err(err)) => {
|
||||
drop_array_elems(idx, arr);
|
||||
return Err(err);
|
||||
}
|
||||
None => {
|
||||
drop_array_elems(idx, arr);
|
||||
return Err(Error::invalid_length(idx, expected));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Everything is initialized. Transmute the array to the
|
||||
// initialized type.
|
||||
// A normal transmute is not possible because of:
|
||||
// https://github.com/rust-lang/rust/issues/61956
|
||||
Ok(unsafe { core::mem::transmute_copy::<_, [T; N]>(&arr) })
|
||||
}
|
||||
|
||||
impl<'de, T, As, const N: usize> DeserializeAs<'de, [T; N]> for [As; N]
|
||||
where
|
||||
As: DeserializeAs<'de, T>,
|
||||
{
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<[T; N], D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ArrayVisitor<T, const M: usize>(PhantomData<T>);
|
||||
|
||||
impl<'de, T, As, const M: usize> Visitor<'de> for ArrayVisitor<DeserializeAsWrap<T, As>, M>
|
||||
where
|
||||
As: DeserializeAs<'de, T>,
|
||||
{
|
||||
type Value = [T; M];
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_fmt(format_args!("an array of size {}", M))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
array_from_iterator(
|
||||
SeqIter::new(seq).map(|res: Result<DeserializeAsWrap<T, As>, A::Error>| {
|
||||
res.map(|t| t.into_inner())
|
||||
}),
|
||||
&self,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_tuple(N, ArrayVisitor::<DeserializeAsWrap<T, As>, N>(PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! tuple_seq_as_map_impl_intern {
|
||||
($tyorig:ty, $ty:ident <KAs, VAs>) => {
|
||||
#[allow(clippy::implicit_hasher)]
|
||||
impl<'de, K, KAs, V, VAs, const N: usize> DeserializeAs<'de, $tyorig> for $ty<KAs, VAs>
|
||||
where
|
||||
KAs: DeserializeAs<'de, K>,
|
||||
VAs: DeserializeAs<'de, V>,
|
||||
{
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<$tyorig, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct MapVisitor<K, KAs, V, VAs, const M: usize> {
|
||||
marker: PhantomData<(K, KAs, V, VAs)>,
|
||||
}
|
||||
|
||||
impl<'de, K, KAs, V, VAs, const M: usize> Visitor<'de> for MapVisitor<K, KAs, V, VAs, M>
|
||||
where
|
||||
KAs: DeserializeAs<'de, K>,
|
||||
VAs: DeserializeAs<'de, V>,
|
||||
{
|
||||
type Value = [(K, V); M];
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_fmt(format_args!("a map of length {}", M))
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, access: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
{
|
||||
array_from_iterator(MapIter::new(access).map(
|
||||
|res: Result<(DeserializeAsWrap<K, KAs>, DeserializeAsWrap<V, VAs>), A::Error>| {
|
||||
res.map(|(k, v)| (k.into_inner(), v.into_inner()))
|
||||
}
|
||||
), &self)
|
||||
}
|
||||
}
|
||||
|
||||
let visitor = MapVisitor::<K, KAs, V, VAs, N> {
|
||||
marker: PhantomData,
|
||||
};
|
||||
deserializer.deserialize_map(visitor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tuple_seq_as_map_impl_intern!([(K, V); N], BTreeMap<KAs, VAs>);
|
||||
tuple_seq_as_map_impl_intern!([(K, V); N], HashMap<KAs, VAs>);
|
||||
|
||||
impl<'de, const N: usize> DeserializeAs<'de, [u8; N]> for Bytes {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<[u8; N], D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ArrayVisitor<const M: usize>;
|
||||
|
||||
impl<'de, const M: usize> Visitor<'de> for ArrayVisitor<M> {
|
||||
type Value = [u8; M];
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_fmt(format_args!("an byte array of size {}", M))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
array_from_iterator(SeqIter::new(seq), &self)
|
||||
}
|
||||
|
||||
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
v.try_into()
|
||||
.map_err(|_| Error::invalid_length(v.len(), &self))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
v.as_bytes()
|
||||
.try_into()
|
||||
.map_err(|_| Error::invalid_length(v.len(), &self))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_bytes(ArrayVisitor::<N>)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const N: usize> DeserializeAs<'de, &'de [u8; N]> for Bytes {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<&'de [u8; N], D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ArrayVisitor<const M: usize>;
|
||||
|
||||
impl<'de, const M: usize> Visitor<'de> for ArrayVisitor<M> {
|
||||
type Value = &'de [u8; M];
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_fmt(format_args!("a borrowed byte array of size {}", M))
|
||||
}
|
||||
|
||||
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
v.try_into()
|
||||
.map_err(|_| Error::invalid_length(v.len(), &self))
|
||||
}
|
||||
|
||||
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
v.as_bytes()
|
||||
.try_into()
|
||||
.map_err(|_| Error::invalid_length(v.len(), &self))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_bytes(ArrayVisitor::<N>)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const N: usize> DeserializeAs<'de, Cow<'de, [u8; N]>> for Bytes {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<Cow<'de, [u8; N]>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct CowVisitor<const M: usize>;
|
||||
|
||||
impl<'de, const M: usize> Visitor<'de> for CowVisitor<M> {
|
||||
type Value = Cow<'de, [u8; M]>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a byte array")
|
||||
}
|
||||
|
||||
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(Cow::Borrowed(
|
||||
v.try_into()
|
||||
.map_err(|_| Error::invalid_length(v.len(), &self))?,
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(Cow::Borrowed(
|
||||
v.as_bytes()
|
||||
.try_into()
|
||||
.map_err(|_| Error::invalid_length(v.len(), &self))?,
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(Cow::Owned(
|
||||
v.to_vec()
|
||||
.try_into()
|
||||
.map_err(|_| Error::invalid_length(v.len(), &self))?,
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(Cow::Owned(
|
||||
v.as_bytes()
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.map_err(|_| Error::invalid_length(v.len(), &self))?,
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
let len = v.len();
|
||||
Ok(Cow::Owned(
|
||||
v.try_into()
|
||||
.map_err(|_| Error::invalid_length(len, &self))?,
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
let len = v.len();
|
||||
Ok(Cow::Owned(
|
||||
v.into_bytes()
|
||||
.try_into()
|
||||
.map_err(|_| Error::invalid_length(len, &self))?,
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_seq<V>(self, seq: V) -> Result<Self::Value, V::Error>
|
||||
where
|
||||
V: SeqAccess<'de>,
|
||||
{
|
||||
Ok(Cow::Owned(array_from_iterator(SeqIter::new(seq), &self)?))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_bytes(CowVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const N: usize> DeserializeAs<'de, Box<[u8; N]>> for Bytes {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<Box<[u8; N]>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Bytes::deserialize_as(deserializer).map(Box::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, const N: usize> DeserializeAs<'de, Cow<'de, [u8; N]>> for BorrowCow {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<Cow<'de, [u8; N]>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Bytes::deserialize_as(deserializer)
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,85 @@
|
|||
use super::*;
|
||||
use core::fmt;
|
||||
use serde::de::*;
|
||||
|
||||
macro_rules! array_impl {
|
||||
($len:literal $($idx:tt)*) => {
|
||||
impl<'de, T, As> DeserializeAs<'de, [T; $len]> for [As; $len]
|
||||
where
|
||||
As: DeserializeAs<'de, T>,
|
||||
{
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<[T; $len], D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ArrayVisitor<T>(PhantomData<T>);
|
||||
|
||||
impl<'de, T, As> Visitor<'de>
|
||||
for ArrayVisitor<DeserializeAsWrap<T, As>>
|
||||
where
|
||||
As: DeserializeAs<'de, T>,
|
||||
{
|
||||
type Value = [T; $len];
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(concat!("an array of size ", $len))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
// Because of 0-size arrays
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
Ok([$(
|
||||
match seq.next_element::<DeserializeAsWrap<T, As>>()? {
|
||||
Some(value) => value.into_inner(),
|
||||
None => return Err(Error::invalid_length($idx, &self)),
|
||||
},
|
||||
)*])
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_tuple(
|
||||
$len,
|
||||
ArrayVisitor::<DeserializeAsWrap<T, As>>(PhantomData),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
array_impl!(0);
|
||||
array_impl!(1 0);
|
||||
array_impl!(2 0 1);
|
||||
array_impl!(3 0 1 2);
|
||||
array_impl!(4 0 1 2 3);
|
||||
array_impl!(5 0 1 2 3 4);
|
||||
array_impl!(6 0 1 2 3 4 5);
|
||||
array_impl!(7 0 1 2 3 4 5 6);
|
||||
array_impl!(8 0 1 2 3 4 5 6 7);
|
||||
array_impl!(9 0 1 2 3 4 5 6 7 8);
|
||||
array_impl!(10 0 1 2 3 4 5 6 7 8 9);
|
||||
array_impl!(11 0 1 2 3 4 5 6 7 8 9 10);
|
||||
array_impl!(12 0 1 2 3 4 5 6 7 8 9 10 11);
|
||||
array_impl!(13 0 1 2 3 4 5 6 7 8 9 10 11 12);
|
||||
array_impl!(14 0 1 2 3 4 5 6 7 8 9 10 11 12 13);
|
||||
array_impl!(15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14);
|
||||
array_impl!(16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15);
|
||||
array_impl!(17 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
||||
array_impl!(18 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17);
|
||||
array_impl!(19 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18);
|
||||
array_impl!(20 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19);
|
||||
array_impl!(21 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20);
|
||||
array_impl!(22 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21);
|
||||
array_impl!(23 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22);
|
||||
array_impl!(24 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23);
|
||||
array_impl!(25 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);
|
||||
array_impl!(26 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);
|
||||
array_impl!(27 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);
|
||||
array_impl!(28 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);
|
||||
array_impl!(29 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);
|
||||
array_impl!(30 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);
|
||||
array_impl!(31 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);
|
||||
array_impl!(32 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);
|
|
@ -7,7 +7,8 @@
|
|||
//!
|
||||
//! [user guide]: crate::guide
|
||||
|
||||
pub(crate) mod impls;
|
||||
mod const_arrays;
|
||||
mod impls;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -20,7 +21,7 @@ use super::*;
|
|||
/// # Differences to [`Deserialize`]
|
||||
///
|
||||
/// The trait is only required for container-like types or types implementing specific conversion functions.
|
||||
/// Container-like types are [`Vec`][], [`BTreeMap`][], but also [`Option`][] and [`Box`][].
|
||||
/// Container-like types are [`Vec`], [`BTreeMap`], but also [`Option`] and [`Box`].
|
||||
/// Conversion types deserialize into a different Rust type.
|
||||
/// For example, [`DisplayFromStr`] uses the [`FromStr`] trait after deserializing a string and [`DurationSeconds`] creates a [`Duration`] from either String or integer values.
|
||||
///
|
||||
|
@ -99,9 +100,11 @@ use super::*;
|
|||
/// # assert_eq!(false, serde_json::from_str::<S>(r#""false""#).unwrap().0);
|
||||
/// # }
|
||||
/// ```
|
||||
/// [`Box`]: std::boxed::Box
|
||||
/// [`BTreeMap`]: std::collections::BTreeMap
|
||||
/// [`Duration`]: std::time::Duration
|
||||
/// [`FromStr`]: std::str::FromStr
|
||||
/// [`Vec`]: std::vec::Vec
|
||||
/// [impl-deserialize]: https://serde.rs/impl-deserialize.html
|
||||
pub trait DeserializeAs<'de, T>: Sized {
|
||||
/// Deserialize this value from the given Serde deserializer.
|
||||
|
@ -117,7 +120,7 @@ pub struct DeserializeAsWrap<T, U> {
|
|||
marker: PhantomData<U>,
|
||||
}
|
||||
|
||||
impl<'de, T, U> DeserializeAsWrap<T, U> {
|
||||
impl<T, U> DeserializeAsWrap<T, U> {
|
||||
/// Return the inner value of type `T`.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.value
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
hash::{BuildHasher, Hash},
|
||||
};
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use core::hash::{BuildHasher, Hash};
|
||||
#[cfg(feature = "indexmap")]
|
||||
use indexmap_crate::{IndexMap, IndexSet};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub trait PreventDuplicateInsertsSet<T> {
|
||||
fn new(size_hint: Option<usize>) -> Self;
|
||||
|
@ -36,6 +37,26 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "indexmap")]
|
||||
impl<T, S> PreventDuplicateInsertsSet<T> for IndexSet<T, S>
|
||||
where
|
||||
T: Eq + Hash,
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
#[inline]
|
||||
fn new(size_hint: Option<usize>) -> Self {
|
||||
match size_hint {
|
||||
Some(size) => Self::with_capacity_and_hasher(size, S::default()),
|
||||
None => Self::with_hasher(S::default()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn insert(&mut self, value: T) -> bool {
|
||||
self.insert(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PreventDuplicateInsertsSet<T> for BTreeSet<T>
|
||||
where
|
||||
T: Ord,
|
||||
|
@ -70,6 +91,26 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "indexmap")]
|
||||
impl<K, V, S> PreventDuplicateInsertsMap<K, V> for IndexMap<K, V, S>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
#[inline]
|
||||
fn new(size_hint: Option<usize>) -> Self {
|
||||
match size_hint {
|
||||
Some(size) => Self::with_capacity_and_hasher(size, S::default()),
|
||||
None => Self::with_hasher(S::default()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn insert(&mut self, key: K, value: V) -> bool {
|
||||
self.insert(key, value).is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> PreventDuplicateInsertsMap<K, V> for BTreeMap<K, V>
|
||||
where
|
||||
K: Ord,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
hash::{BuildHasher, Hash},
|
||||
};
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use core::hash::{BuildHasher, Hash};
|
||||
#[cfg(feature = "indexmap")]
|
||||
use indexmap_crate::IndexMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[deprecated = "This is serde's default behavior."]
|
||||
pub trait DuplicateInsertsFirstWinsSet<T> {
|
||||
|
@ -83,6 +84,34 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "indexmap")]
|
||||
impl<K, V, S> DuplicateInsertsFirstWinsMap<K, V> for IndexMap<K, V, S>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
#[inline]
|
||||
fn new(size_hint: Option<usize>) -> Self {
|
||||
match size_hint {
|
||||
Some(size) => Self::with_capacity_and_hasher(size, S::default()),
|
||||
None => Self::with_hasher(S::default()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn insert(&mut self, key: K, value: V) {
|
||||
use indexmap_crate::map::Entry;
|
||||
|
||||
match self.entry(key) {
|
||||
// we want to keep the first value, so do nothing
|
||||
Entry::Occupied(_) => {}
|
||||
Entry::Vacant(vacant) => {
|
||||
vacant.insert(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> DuplicateInsertsFirstWinsMap<K, V> for BTreeMap<K, V>
|
||||
where
|
||||
K: Ord,
|
||||
|
@ -94,7 +123,7 @@ where
|
|||
|
||||
#[inline]
|
||||
fn insert(&mut self, key: K, value: V) {
|
||||
use std::collections::btree_map::Entry;
|
||||
use alloc::collections::btree_map::Entry;
|
||||
|
||||
match self.entry(key) {
|
||||
// we want to keep the first value, so do nothing
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::{
|
||||
collections::{BTreeSet, HashSet},
|
||||
hash::{BuildHasher, Hash},
|
||||
};
|
||||
use alloc::collections::BTreeSet;
|
||||
use core::hash::{BuildHasher, Hash};
|
||||
#[cfg(feature = "indexmap")]
|
||||
use indexmap_crate::IndexSet;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub trait DuplicateInsertsLastWinsSet<T> {
|
||||
fn new(size_hint: Option<usize>) -> Self;
|
||||
|
@ -30,6 +31,27 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "indexmap")]
|
||||
impl<T, S> DuplicateInsertsLastWinsSet<T> for IndexSet<T, S>
|
||||
where
|
||||
T: Eq + Hash,
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
#[inline]
|
||||
fn new(size_hint: Option<usize>) -> Self {
|
||||
match size_hint {
|
||||
Some(size) => Self::with_capacity_and_hasher(size, S::default()),
|
||||
None => Self::with_hasher(S::default()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn replace(&mut self, value: T) {
|
||||
// Hashset already fulfils the contract
|
||||
self.replace(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DuplicateInsertsLastWinsSet<T> for BTreeSet<T>
|
||||
where
|
||||
T: Ord,
|
||||
|
|
|
@ -0,0 +1,888 @@
|
|||
use crate::{
|
||||
content::ser::{Content, ContentSerializer},
|
||||
DeserializeAs, SerializeAs,
|
||||
};
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
use core::{fmt, marker::PhantomData};
|
||||
use serde::{
|
||||
de::{DeserializeSeed, EnumAccess, Error, MapAccess, SeqAccess, VariantAccess, Visitor},
|
||||
ser,
|
||||
ser::{Impossible, SerializeMap, SerializeSeq, SerializeStructVariant, SerializeTupleVariant},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
|
||||
/// Represent a list of enum values as a map.
|
||||
///
|
||||
/// This **only works** if the enum uses the default *externally tagged* representation.
|
||||
/// Other enum representations are not supported.
|
||||
///
|
||||
/// serde data formats often represent *externally tagged* enums as maps with a single key.
|
||||
/// The key is the enum variant name, and the value is the variant value.
|
||||
/// Sometimes a map with multiple keys should be treated like a list of enum values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## JSON Map with multiple keys
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// use serde_with::EnumMap;
|
||||
///
|
||||
/// # #[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// enum EnumValue {
|
||||
/// Int(i32),
|
||||
/// String(String),
|
||||
/// Unit,
|
||||
/// Tuple(i32, String, bool),
|
||||
/// Struct {
|
||||
/// a: i32,
|
||||
/// b: String,
|
||||
/// c: bool,
|
||||
/// },
|
||||
/// }
|
||||
///
|
||||
/// #[serde_with::serde_as]
|
||||
/// # #[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// struct VecEnumValues (
|
||||
/// #[serde_as(as = "EnumMap")]
|
||||
/// Vec<EnumValue>,
|
||||
/// );
|
||||
///
|
||||
/// // ---
|
||||
///
|
||||
/// // This will serialize this list of values
|
||||
/// let values = VecEnumValues(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,
|
||||
/// },
|
||||
/// ]);
|
||||
///
|
||||
/// // into this JSON map
|
||||
/// // Duplicate keys are emitted for identical enum variants.
|
||||
/// let expected =
|
||||
/// r#"{
|
||||
/// "Int": 123,
|
||||
/// "String": "FooBar",
|
||||
/// "Int": 456,
|
||||
/// "String": "XXX",
|
||||
/// "Unit": null,
|
||||
/// "Tuple": [
|
||||
/// 1,
|
||||
/// "Middle",
|
||||
/// false
|
||||
/// ],
|
||||
/// "Struct": {
|
||||
/// "a": 666,
|
||||
/// "b": "BBB",
|
||||
/// "c": true
|
||||
/// }
|
||||
/// }"#;
|
||||
///
|
||||
/// // Both serialization and deserialization work flawlessly.
|
||||
/// let serialized = serde_json::to_string_pretty(&values).unwrap();
|
||||
/// assert_eq!(expected, serialized);
|
||||
/// let deserialized: VecEnumValues = serde_json::from_str(&serialized).unwrap();
|
||||
/// assert_eq!(values, deserialized);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## XML structure with varying keys
|
||||
///
|
||||
/// With `serde_xml_rs` tuple and struct variants are not supported since they fail to roundtrip.
|
||||
/// The enum may have such variants as long as they are not serialized or deserialized.
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// use serde_with::EnumMap;
|
||||
///
|
||||
/// # #[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// enum EnumValue {
|
||||
/// Int(i32),
|
||||
/// String(String),
|
||||
/// Unit,
|
||||
/// }
|
||||
///
|
||||
/// #[serde_with::serde_as]
|
||||
/// # #[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// struct VecEnumValues {
|
||||
/// #[serde_as(as = "EnumMap")]
|
||||
/// vec: Vec<EnumValue>,
|
||||
/// }
|
||||
///
|
||||
/// // ---
|
||||
///
|
||||
/// // This will serialize this list of values
|
||||
/// let values = VecEnumValues {
|
||||
/// vec: vec![
|
||||
/// EnumValue::Int(123),
|
||||
/// EnumValue::String("FooBar".to_string()),
|
||||
/// EnumValue::Int(456),
|
||||
/// EnumValue::String("XXX".to_string()),
|
||||
/// EnumValue::Unit,
|
||||
/// ],
|
||||
/// };
|
||||
///
|
||||
/// // into this XML document
|
||||
/// // Duplicate keys are emitted for identical enum variants.
|
||||
/// let expected = r#"
|
||||
/// <VecEnumValues>
|
||||
/// <vec>
|
||||
/// <Int>123</Int>
|
||||
/// <String>FooBar</String>
|
||||
/// <Int>456</Int>
|
||||
/// <String>XXX</String>
|
||||
/// <Unit></Unit>
|
||||
/// </vec>
|
||||
/// </VecEnumValues>"#
|
||||
/// // Remove whitespace
|
||||
/// .replace(' ', "")
|
||||
/// .replace('\n', "");
|
||||
///
|
||||
/// // Both serialization and deserialization work flawlessly.
|
||||
/// let serialized = serde_xml_rs::to_string(&values).unwrap();
|
||||
/// assert_eq!(expected, serialized);
|
||||
/// let deserialized: VecEnumValues = serde_xml_rs::from_str(&serialized).unwrap();
|
||||
/// assert_eq!(values, deserialized);
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct EnumMap;
|
||||
|
||||
impl<T> SerializeAs<Vec<T>> for EnumMap
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
fn serialize_as<S>(source: &Vec<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
source.serialize(SeqAsMapSerializer(serializer))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> DeserializeAs<'de, Vec<T>> for EnumMap
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<Vec<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct EnumMapVisitor<T>(PhantomData<T>);
|
||||
|
||||
impl<'de, T> Visitor<'de> for EnumMapVisitor<T>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
{
|
||||
type Value = Vec<T>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(formatter, "a map or enum values")
|
||||
}
|
||||
|
||||
fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
|
||||
Vec::deserialize(SeqDeserializer(map))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_map(EnumMapVisitor(PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
static END_OF_MAP_IDENTIFIER: &str = "__PRIVATE_END_OF_MAP_MARKER__";
|
||||
|
||||
// Serialization code below here
|
||||
|
||||
/// Convert a sequence to a map during serialization.
|
||||
///
|
||||
/// Only `serialize_seq` is implemented and forwarded to `serialize_map` on the inner `Serializer`.
|
||||
/// The elements are serialized with [`SerializeSeqElement`].
|
||||
struct SeqAsMapSerializer<S>(S);
|
||||
|
||||
impl<S> Serializer for SeqAsMapSerializer<S>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
type Ok = S::Ok;
|
||||
type Error = S::Error;
|
||||
|
||||
type SerializeSeq = SerializeSeqElement<S::SerializeMap>;
|
||||
type SerializeTuple = Impossible<S::Ok, S::Error>;
|
||||
type SerializeTupleStruct = Impossible<S::Ok, S::Error>;
|
||||
type SerializeTupleVariant = Impossible<S::Ok, S::Error>;
|
||||
type SerializeMap = Impossible<S::Ok, S::Error>;
|
||||
type SerializeStruct = Impossible<S::Ok, S::Error>;
|
||||
type SerializeStructVariant = Impossible<S::Ok, S::Error>;
|
||||
|
||||
fn serialize_bool(self, _v: bool) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_i8(self, _v: i8) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_i16(self, _v: i16) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_i32(self, _v: i32) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_i64(self, _v: i64) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_i128(self, _v: i128) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_u8(self, _v: u8) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_u16(self, _v: u16) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_u32(self, _v: u32) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_u64(self, _v: u64) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_u128(self, _v: u128) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_f32(self, _v: f32) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_f64(self, _v: f64) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_char(self, _v: char) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_str(self, _v: &str) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_some<T: ?Sized>(self, _value: &T) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T: ?Sized>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_value: &T,
|
||||
) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T: ?Sized>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_value: &T,
|
||||
) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
|
||||
let is_human_readable = self.0.is_human_readable();
|
||||
self.0
|
||||
.serialize_map(len)
|
||||
.map(|delegate| SerializeSeqElement {
|
||||
delegate,
|
||||
is_human_readable,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleStruct, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleVariant, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStruct, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStructVariant, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize a single element but turn the sequence into a map logic.
|
||||
///
|
||||
/// It uses [`SerializeEnumAsMapElement`] for the map element serialization.
|
||||
///
|
||||
/// The [`Serializer`] implementation handles all the `serialize_*_variant` functions and defers to [`SerializeVariant`] for the more complicated tuple and struct variants.
|
||||
struct SerializeSeqElement<M> {
|
||||
delegate: M,
|
||||
is_human_readable: bool,
|
||||
}
|
||||
|
||||
impl<M> SerializeSeq for SerializeSeqElement<M>
|
||||
where
|
||||
M: SerializeMap,
|
||||
{
|
||||
type Ok = M::Ok;
|
||||
type Error = M::Error;
|
||||
|
||||
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
value.serialize(EnumAsMapElementSerializer {
|
||||
delegate: &mut self.delegate,
|
||||
is_human_readable: self.is_human_readable,
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||
self.delegate.end()
|
||||
}
|
||||
}
|
||||
|
||||
struct EnumAsMapElementSerializer<'a, M> {
|
||||
delegate: &'a mut M,
|
||||
is_human_readable: bool,
|
||||
}
|
||||
|
||||
impl<'a, M> Serializer for EnumAsMapElementSerializer<'a, M>
|
||||
where
|
||||
M: SerializeMap,
|
||||
{
|
||||
type Ok = ();
|
||||
type Error = M::Error;
|
||||
|
||||
type SerializeSeq = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeTuple = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeTupleVariant = SerializeVariant<'a, M>;
|
||||
type SerializeMap = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeStruct = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeStructVariant = SerializeVariant<'a, M>;
|
||||
|
||||
fn serialize_bool(self, _v: bool) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_i8(self, _v: i8) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_i16(self, _v: i16) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_i32(self, _v: i32) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_i64(self, _v: i64) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_i128(self, _v: i128) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_u8(self, _v: u8) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_u16(self, _v: u16) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_u32(self, _v: u32) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_u64(self, _v: u64) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_u128(self, _v: u128) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_f32(self, _v: f32) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_f64(self, _v: f64) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_char(self, _v: char) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_str(self, _v: &str) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_some<T: ?Sized>(self, _value: &T) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
) -> Result<Self::Ok, Self::Error> {
|
||||
self.delegate.serialize_entry(variant, &())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T: ?Sized>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_value: &T,
|
||||
) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T: ?Sized>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
value: &T,
|
||||
) -> Result<Self::Ok, Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
self.delegate.serialize_entry(variant, value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleStruct, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(
|
||||
self,
|
||||
name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
len: usize,
|
||||
) -> Result<Self::SerializeTupleVariant, Self::Error> {
|
||||
Ok(SerializeVariant {
|
||||
delegate: self.delegate,
|
||||
is_human_readable: self.is_human_readable,
|
||||
variant,
|
||||
content: Content::TupleStruct(name, Vec::with_capacity(len)),
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStruct, Self::Error> {
|
||||
Err(ser::Error::custom("wrong type for EnumMap"))
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(
|
||||
self,
|
||||
name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
len: usize,
|
||||
) -> Result<Self::SerializeStructVariant, Self::Error> {
|
||||
Ok(SerializeVariant {
|
||||
delegate: self.delegate,
|
||||
is_human_readable: self.is_human_readable,
|
||||
variant,
|
||||
content: Content::Struct(name, Vec::with_capacity(len)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize a struct or tuple variant enum as a map element
|
||||
///
|
||||
/// [`SerializeStructVariant`] serializes a struct variant, and [`SerializeTupleVariant`] a tuple variant.
|
||||
struct SerializeVariant<'a, M> {
|
||||
delegate: &'a mut M,
|
||||
is_human_readable: bool,
|
||||
variant: &'static str,
|
||||
content: Content,
|
||||
}
|
||||
|
||||
impl<'a, M> SerializeStructVariant for SerializeVariant<'a, M>
|
||||
where
|
||||
M: SerializeMap,
|
||||
{
|
||||
type Ok = ();
|
||||
|
||||
type Error = M::Error;
|
||||
|
||||
fn serialize_field<T: ?Sized>(
|
||||
&mut self,
|
||||
key: &'static str,
|
||||
value: &T,
|
||||
) -> Result<(), Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
// Serialize to a Content type first
|
||||
let value: Content = value.serialize(ContentSerializer::new(self.is_human_readable))?;
|
||||
if let Content::Struct(_name, fields) = &mut self.content {
|
||||
fields.push((key, value));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||
self.delegate.serialize_entry(&self.variant, &self.content)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M> SerializeTupleVariant for SerializeVariant<'a, M>
|
||||
where
|
||||
M: SerializeMap,
|
||||
{
|
||||
type Ok = ();
|
||||
|
||||
type Error = M::Error;
|
||||
|
||||
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
// Serialize to a Content type first
|
||||
let value: Content = value.serialize(ContentSerializer::new(self.is_human_readable))?;
|
||||
if let Content::TupleStruct(_name, fields) = &mut self.content {
|
||||
fields.push(value);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||
self.delegate.serialize_entry(&self.variant, &self.content)
|
||||
}
|
||||
}
|
||||
|
||||
// Below is deserialization code
|
||||
|
||||
/// Deserialize the sequence of enum instances.
|
||||
///
|
||||
/// The main [`Deserializer`] implementation handles the outer sequence (e.g., `Vec`), while the [`SeqAccess`] implementation is responsible for the inner elements.
|
||||
struct SeqDeserializer<M>(M);
|
||||
|
||||
impl<'de, M> Deserializer<'de> for SeqDeserializer<M>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
type Error = M::Error;
|
||||
|
||||
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_seq(self)
|
||||
}
|
||||
|
||||
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
self.deserialize_seq(visitor)
|
||||
}
|
||||
|
||||
serde::forward_to_deserialize_any! {
|
||||
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
|
||||
bytes byte_buf option unit unit_struct newtype_struct tuple
|
||||
tuple_struct map struct enum identifier ignored_any
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, M> SeqAccess<'de> for SeqDeserializer<M>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
type Error = M::Error;
|
||||
|
||||
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
|
||||
where
|
||||
T: DeserializeSeed<'de>,
|
||||
{
|
||||
match seed.deserialize(EnumDeserializer(&mut self.0)) {
|
||||
Ok(value) => Ok(Some(value)),
|
||||
Err(err) => {
|
||||
// Unfortunately we loose the optional aspect of MapAccess, so we need to special case an error value to mark the end of the map.
|
||||
if err.to_string().contains(END_OF_MAP_IDENTIFIER) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> Option<usize> {
|
||||
self.0.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize an enum from a map element
|
||||
///
|
||||
/// The [`Deserializer`] implementation is the starting point, which first calls the [`EnumAccess`] methods.
|
||||
/// The [`EnumAccess`] is used to deserialize the enum variant type of the enum.
|
||||
/// The [`VariantAccess`] is used to deserialize the value part of the enum.
|
||||
struct EnumDeserializer<M>(M);
|
||||
|
||||
impl<'de, M> Deserializer<'de> for EnumDeserializer<M>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
type Error = M::Error;
|
||||
|
||||
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
self.deserialize_enum("", &[], visitor)
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variants: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_enum(self)
|
||||
}
|
||||
|
||||
serde::forward_to_deserialize_any! {
|
||||
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
|
||||
bytes byte_buf option unit unit_struct newtype_struct seq tuple
|
||||
tuple_struct map struct identifier ignored_any
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, M> EnumAccess<'de> for EnumDeserializer<M>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
type Error = M::Error;
|
||||
type Variant = Self;
|
||||
|
||||
fn variant_seed<T>(mut self, seed: T) -> Result<(T::Value, Self::Variant), Self::Error>
|
||||
where
|
||||
T: DeserializeSeed<'de>,
|
||||
{
|
||||
match self.0.next_key_seed(seed)? {
|
||||
Some(key) => Ok((key, self)),
|
||||
|
||||
// Unfortunately we loose the optional aspect of MapAccess, so we need to special case an error value to mark the end of the map.
|
||||
None => Err(Error::custom(END_OF_MAP_IDENTIFIER)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, M> VariantAccess<'de> for EnumDeserializer<M>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
type Error = M::Error;
|
||||
|
||||
fn unit_variant(mut self) -> Result<(), Self::Error> {
|
||||
self.0.next_value()
|
||||
}
|
||||
|
||||
fn newtype_variant_seed<T>(mut self, seed: T) -> Result<T::Value, Self::Error>
|
||||
where
|
||||
T: DeserializeSeed<'de>,
|
||||
{
|
||||
self.0.next_value_seed(seed)
|
||||
}
|
||||
|
||||
fn tuple_variant<V>(mut self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
self.0.next_value_seed(SeedTupleVariant { len, visitor })
|
||||
}
|
||||
|
||||
fn struct_variant<V>(
|
||||
mut self,
|
||||
_fields: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
self.0.next_value_seed(SeedStructVariant { visitor })
|
||||
}
|
||||
}
|
||||
|
||||
struct SeedTupleVariant<V> {
|
||||
len: usize,
|
||||
visitor: V,
|
||||
}
|
||||
|
||||
impl<'de, V> DeserializeSeed<'de> for SeedTupleVariant<V>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
type Value = V::Value;
|
||||
|
||||
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_tuple(self.len, self.visitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct SeedStructVariant<V> {
|
||||
visitor: V,
|
||||
}
|
||||
|
||||
impl<'de, V> DeserializeSeed<'de> for SeedStructVariant<V>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
type Value = V::Value;
|
||||
|
||||
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_map(self.visitor)
|
||||
}
|
||||
}
|
|
@ -9,10 +9,6 @@
|
|||
/// required on the field such that the helper works. The serialization format will always be
|
||||
/// flattened.
|
||||
///
|
||||
/// **Note**:
|
||||
/// This macro requires that the crate `serde_with` is in scope with that exact name.
|
||||
/// If you import `serde_with` with a different name, you need to temporarily rename it for this macro to work.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
|
@ -34,7 +30,6 @@
|
|||
/// // You need to specify a function name and the field name of the flattened field.
|
||||
/// serde_with::flattened_maybe!(deserialize_t, "t");
|
||||
///
|
||||
///
|
||||
/// # fn main() {
|
||||
/// // Supports both flattened
|
||||
/// let j = r#" {"i":1} "#;
|
||||
|
@ -55,20 +50,20 @@
|
|||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! flattened_maybe {
|
||||
// TODO Change $field to literal, once the compiler version is bumped enough.
|
||||
($fn:ident, $field:expr) => {
|
||||
($fn:ident, $field:literal) => {
|
||||
fn $fn<'de, T, D>(deserializer: D) -> ::std::result::Result<T, D::Error>
|
||||
where
|
||||
T: serde_with::serde::Deserialize<'de>,
|
||||
D: serde_with::serde::Deserializer<'de>,
|
||||
T: $crate::serde::Deserialize<'de>,
|
||||
D: $crate::serde::Deserializer<'de>,
|
||||
{
|
||||
use ::std::{
|
||||
option::Option::{self, None, Some},
|
||||
result::Result::{self, Err, Ok},
|
||||
};
|
||||
use $crate::serde;
|
||||
|
||||
#[derive(serde_with::serde::Deserialize)]
|
||||
#[serde(crate = "serde_with::serde")]
|
||||
#[derive($crate::serde::Deserialize)]
|
||||
#[serde(crate = "serde")]
|
||||
pub struct Both<T> {
|
||||
#[serde(flatten)]
|
||||
flat: Option<T>,
|
||||
|
@ -76,11 +71,11 @@ macro_rules! flattened_maybe {
|
|||
not_flat: Option<T>,
|
||||
}
|
||||
|
||||
let both: Both<T> = serde_with::serde::Deserialize::deserialize(deserializer)?;
|
||||
let both: Both<T> = $crate::serde::Deserialize::deserialize(deserializer)?;
|
||||
match (both.flat, both.not_flat) {
|
||||
(Some(t), None) | (None, Some(t)) => Ok(t),
|
||||
(None, None) => Err(serde_with::serde::de::Error::missing_field($field)),
|
||||
(Some(_), Some(_)) => Err(serde_with::serde::de::Error::custom(concat!(
|
||||
(None, None) => Err($crate::serde::de::Error::missing_field($field)),
|
||||
(Some(_), Some(_)) => Err($crate::serde::de::Error::custom(concat!(
|
||||
"`",
|
||||
$field,
|
||||
"` is both flattened and not"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Specify the format and how lenient the deserialization is
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
/// Specify how to serialize/deserialize a type
|
||||
///
|
||||
/// The format specifier allows to configure how a value is serialized/deserialized.
|
||||
|
@ -66,6 +68,16 @@ create_format!(
|
|||
Uppercase
|
||||
/// Use lowercase characters
|
||||
Lowercase
|
||||
|
||||
/// Use in combination with [`OneOrMany`](crate::OneOrMany). Emit single element for lists of size 1.
|
||||
PreferOne
|
||||
/// Use in combination with [`OneOrMany`](crate::OneOrMany). Always emit the list form.
|
||||
PreferMany
|
||||
|
||||
/// Emit padding during serialization.
|
||||
Padded
|
||||
/// Do not emit padding during serialization.
|
||||
Unpadded
|
||||
);
|
||||
|
||||
/// Specify how lenient the deserialization process should be
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
# `serde_with` User Guide
|
||||
|
||||
This crate provides helper functions to extend and change how [`serde`] serializes different datatypes.
|
||||
This crate provides helper functions to extend and change how [`serde`] serializes different data types.
|
||||
For example, you can serialize [a map as a sequence of tuples][crate::guide::serde_as#maps-to-vec-of-tuples], serialize [using the `Display` and `FromStr` traits][`DisplayFromStr`], or serialize [an empty `String` like `None`][NoneAsEmptyString].
|
||||
`serde_with` covers types from the Rust Standard Library and some common crates like [`chrono`][serde_with_chrono].
|
||||
|
||||
[**A list of all supported transformations is available on this page.**](crate::guide::serde_as_transformations)
|
||||
|
||||
The crate offers four types of functionality.
|
||||
|
||||
## 1. A more flexible and composable replacement for the with annotation, called `serde_as` *(v1.5.0+)*
|
||||
|
@ -18,7 +20,7 @@ The `serde_as` scheme is based on two new traits: [`SerializeAs`] and [`Deserial
|
|||
### Example
|
||||
|
||||
```rust
|
||||
# use serde_derive::{Deserialize, Serialize};
|
||||
# use serde::{Deserialize, Serialize};
|
||||
# use serde_with::{serde_as, DisplayFromStr};
|
||||
# use std::collections::HashMap;
|
||||
# use std::net::Ipv4Addr;
|
||||
|
@ -62,12 +64,12 @@ assert_eq!(data, serde_json::from_str(json).unwrap());
|
|||
|
||||
## 2. Integration with serde's with-annotation
|
||||
|
||||
[serde's with-annotation][with-annotation] allows to specify a different serialization or deserialization function for a field.
|
||||
It is usefull to adapt the serialization of existing types to the requirements of a protocol.
|
||||
[serde's with-annotation][with-annotation] allows specifying a different serialization or deserialization function for a field.
|
||||
It is useful to adapt the serialization of existing types to the requirements of a protocol.
|
||||
Most modules in this crate can be used together with the with-annotation.
|
||||
|
||||
The annotation approach has one big drawback, in that it is very inflexible.
|
||||
It allows to specify arbitrary serialization code, but the code has to perform the correct transformations.
|
||||
It allows specifying arbitrary serialization code, but the code has to perform the correct transformations.
|
||||
It is not possible to combine multiple of those functions.
|
||||
One common use case for this is the serialization of collections like `Vec`.
|
||||
If you have a field of type `T`, you can apply the with-annotation, but if you have a field of type `Vec<T>`, there is no way to re-use the same functions for the with-annotation.
|
||||
|
@ -78,7 +80,7 @@ The example shows a similar setup as in the `serde_as` example above, but using
|
|||
### Example
|
||||
|
||||
```rust
|
||||
# use serde_derive::{Deserialize, Serialize};
|
||||
# use serde::{Deserialize, Serialize};
|
||||
# use std::net::Ipv4Addr;
|
||||
#
|
||||
# #[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -118,16 +120,16 @@ assert_eq!(data, serde_json::from_str(json).unwrap());
|
|||
|
||||
## 3. proc-macros to make it easier to use both above parts
|
||||
|
||||
The proc-macros are an optional addition and improve the user exerience for common tasks.
|
||||
The proc-macros are an optional addition and improve the user experience for common tasks.
|
||||
We have already seen how the `serde_as` attribute is used to define the serialization instructions.
|
||||
|
||||
The proc-macro attributes are defined in the [`serde_with_macros`] crate and re-exported from the root of this crate.
|
||||
The proc-macros are optional, but enabled by default.
|
||||
For futher details, please refer to the documentation of each proc-macro.
|
||||
For further details, please refer to the documentation of each proc-macro.
|
||||
|
||||
## 4. Derive macros to implement `Deserialize` and `Serialize`
|
||||
|
||||
The derive macros work similar to the serde provided ones but they do implement other de/serialization schemes.
|
||||
The derive macros work similar to the serde provided ones, but they do implement other de/serialization schemes.
|
||||
For example, the derives [`DeserializeFromStr`] and [`SerializeDisplay`] require that the type also implement [`FromStr`] and [`Display`] and de/serializes from/to a string instead of the usual way of iterating over all fields.
|
||||
|
||||
## Migrating from the with-annotations to `serde_as`
|
||||
|
|
|
@ -3,11 +3,20 @@
|
|||
This crate has the following features which can be enabled.
|
||||
Each entry will explain the feature in more detail.
|
||||
|
||||
1. [`chrono`](#chrono)
|
||||
2. [`guide`](#guide)
|
||||
3. [`hex`](#hex)
|
||||
4. [`json`](#json)
|
||||
5. [`macros`](#macros)
|
||||
1. [`base64`](#base64)
|
||||
2. [`chrono`](#chrono)
|
||||
3. [`guide`](#guide)
|
||||
4. [`hex`](#hex)
|
||||
5. [`indexmap`](#indexmap)
|
||||
6. [`json`](#json)
|
||||
7. [`macros`](#macros)
|
||||
8. [`time_0_3`](#time_0_3)
|
||||
|
||||
## `base64`
|
||||
|
||||
The `base64` feature enables serializing data in base64 format.
|
||||
|
||||
This pulls in `base64` as a dependency.
|
||||
|
||||
## `chrono`
|
||||
|
||||
|
@ -27,6 +36,11 @@ The `hex` feature enables serializing data in hex format.
|
|||
|
||||
This pulls in `hex` as a dependency.
|
||||
|
||||
## `indexmap`
|
||||
|
||||
The `indexmap` feature enables implementations of `indexmap` specific checks.
|
||||
This includes support for checking duplicate keys
|
||||
|
||||
## `json`
|
||||
|
||||
The `json` features enables JSON conversions from the `json` module.
|
||||
|
@ -39,3 +53,10 @@ The `macros` features enables all helper macros and derives.
|
|||
It is enabled by default, since the macros provide a usability benefit, especially for `serde_as`.
|
||||
|
||||
This pulls in `serde_with_macros` as a dependency.
|
||||
|
||||
## `time_0_3`
|
||||
|
||||
The `time_0_3` enables integration of `time` v0.3 specific conversions.
|
||||
This includes support for the timestamp and duration types.
|
||||
|
||||
This pulls in `time` v0.3 as a dependency.
|
||||
|
|
|
@ -1,36 +1,26 @@
|
|||
# `serde_as` Annotation
|
||||
|
||||
This is an alternative to serde's with-annotation.
|
||||
It is more flexible and composable but work with fewer types.
|
||||
It is more flexible and composable, but work with fewer types.
|
||||
|
||||
The scheme is based on two new traits, [`SerializeAs`] and [`DeserializeAs`], which need to be implemented by all types which want to be compatible with `serde_as`.
|
||||
The proc macro attribute [`#[serde_as]`][crate::serde_as] exists as a usability boost for users.
|
||||
The proc-macro attribute [`#[serde_as]`][crate::serde_as] exists as a usability boost for users.
|
||||
The basic design of `serde_as` was developed by [@markazmierczak](https://github.com/markazmierczak).
|
||||
|
||||
This site contains some general advice how to use this crate and then lists the implemented conversions for `serde_as`.
|
||||
The basic design of the system was done by [@markazmierczak](https://github.com/markazmierczak).
|
||||
This page contains some general advice on the usage of `serde_as` and on implementing the necessary traits.
|
||||
[**A list of all supported transformations enabled by `serde_as` is available on this page.**](crate::guide::serde_as_transformations)
|
||||
|
||||
1. [Switching from serde's with to `serde_as`](#switching-from-serdes-with-to-serde_as)
|
||||
1. [Deserializing Optional Fields](#deserializing-optional-fields)
|
||||
2. [Implementing `SerializeAs` / `DeserializeAs`](#implementing-serializeas--deserializeas)
|
||||
3. [Using `#[serde_as]` on types without `SerializeAs` and `Serialize` implementations](#using-serde_as-on-types-without-serializeas-and-serialize-implementations)
|
||||
4. [Using `#[serde_as]` with serde's remote derives](#using-serde_as-with-serdes-remote-derives)
|
||||
5. [Re-exporting `serde_as`](#re-exporting-serde_as)
|
||||
2. [De/Serialize Implementations Available](#deserialize-implementations-available)
|
||||
1. [Bytes / `Vec<u8>` to hex string](#bytes--vecu8-to-hex-string)
|
||||
2. [`Default` from `null`](#default-from-null)
|
||||
3. [De/Serialize with `FromStr` and `Display`](#deserialize-with-fromstr-and-display)
|
||||
4. [`Duration` as seconds](#duration-as-seconds)
|
||||
5. [Ignore deserialization errors](#ignore-deserialization-errors)
|
||||
6. [`Maps` to `Vec` of tuples](#maps-to-vec-of-tuples)
|
||||
7. [`NaiveDateTime` like UTC timestamp](#naivedatetime-like-utc-timestamp)
|
||||
8. [`None` as empty `String`](#none-as-empty-string)
|
||||
9. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
|
||||
10. [Value into JSON String](#value-into-json-string)
|
||||
11. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
|
||||
2. [Gating `serde_as` on Features](#gating-serde_as-on-features)
|
||||
2. [Implementing `SerializeAs` / `DeserializeAs`](#implementing-serializeas--deserializeas)
|
||||
1. [Using `#[serde_as]` on types without `SerializeAs` and `Serialize` implementations](#using-serde_as-on-types-without-serializeas-and-serialize-implementations)
|
||||
2. [Using `#[serde_as]` with serde's remote derives](#using-serde_as-with-serdes-remote-derives)
|
||||
3. [Re-exporting `serde_as`](#re-exporting-serde_as)
|
||||
|
||||
## Switching from serde's with to `serde_as`
|
||||
|
||||
For the user the main difference is that instead of
|
||||
For the user, the main difference is that instead of
|
||||
|
||||
```rust,ignore
|
||||
#[serde(with = "...")]
|
||||
|
@ -45,7 +35,7 @@ you now have to write
|
|||
and place the `#[serde_as]` attribute *before* the `#[derive]` attribute.
|
||||
You still need the `#[derive(Serialize, Deserialize)]` on the struct/enum.
|
||||
|
||||
All together this looks like:
|
||||
All together, this looks like:
|
||||
|
||||
```rust
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -59,7 +49,7 @@ struct A {
|
|||
}
|
||||
```
|
||||
|
||||
The main advantage is that you can compose `serde_as` stuff, which is not possible with the with-annotation.
|
||||
The main advantage is that you can compose `serde_as` stuff, which is impossible with the with-annotation.
|
||||
For example, the `mime` field from above could be nested in one or more data structures:
|
||||
|
||||
```rust
|
||||
|
@ -77,7 +67,7 @@ struct A {
|
|||
|
||||
### Deserializing Optional Fields
|
||||
|
||||
During deserialization serde treats fields of `Option<T>` as optional and does not require them to be present.
|
||||
During deserialization, serde treats fields of `Option<T>` as optional and does not require them to be present.
|
||||
This breaks when adding either the `serde_as` annotation or serde's `with` annotation.
|
||||
The default behavior can be restored by adding serde's `default` attribute.
|
||||
|
||||
|
@ -95,18 +85,55 @@ struct A {
|
|||
}
|
||||
```
|
||||
|
||||
In the future this behavior might change and `default` would be applied on `Option<T>` fields.
|
||||
In the future, this behavior might change and `default` would be applied on `Option<T>` fields.
|
||||
You can add your feedback at [serde_with#185].
|
||||
|
||||
### Implementing `SerializeAs` / `DeserializeAs`
|
||||
### Gating `serde_as` on Features
|
||||
|
||||
Gating `serde_as` behind optional features is currently not supported.
|
||||
More details can be found in the corresponding issue [serde_with#355].
|
||||
|
||||
```rust,ignore
|
||||
#[cfg_attr(feature="serde" ,serde_as)]
|
||||
#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
|
||||
struct StructC {
|
||||
#[cfg_attr(feature="serde" ,serde_as(as = "Vec<(_, _)>"))]
|
||||
map: HashMap<(i32,i32), i32>,
|
||||
}
|
||||
```
|
||||
|
||||
The `serde_as` proc-macro attribute will not recognize the `serde_as` attribute on the field and will not perform the necessary translation steps.
|
||||
The problem can be avoided by forcing Rust to evaluate all cfg-expressions before running `serde_as`.
|
||||
This is possible with the `#[cfg_eval]` attribute, which is considered for stabilization ([rust#82679], [rust#87221]).
|
||||
|
||||
As a workaround, it is possible to remove the `serde_as` proc-macro attribute and perform the transformation manually.
|
||||
The transformation steps are listed in the [`serde_as`] documentations.
|
||||
For the example above, this means to replace the field attribute with:
|
||||
|
||||
```rust,ignore
|
||||
use serde_with::{As, Same};
|
||||
|
||||
#[cfg_attr(feature="serde", serde(with = "As::<Vec<(Same, Same)>>"))]
|
||||
map: HashMap<(i32,i32), i32>,
|
||||
```
|
||||
|
||||
[rust#82679]: https://github.com/rust-lang/rust/issues/82679
|
||||
[rust#87221]: https://github.com/rust-lang/rust/pull/87221
|
||||
[serde_with#355]: https://github.com/jonasbb/serde_with/issues/355
|
||||
|
||||
## Implementing `SerializeAs` / `DeserializeAs`
|
||||
|
||||
You can support [`SerializeAs`] / [`DeserializeAs`] on your own types too.
|
||||
Most "leaf" types do not need to implement these traits since they are supported implicitly.
|
||||
Most "leaf" types do not need to implement these traits, since they are supported implicitly.
|
||||
"Leaf" type refers to types which directly serialize like plain data types.
|
||||
[`SerializeAs`] / [`DeserializeAs`] is very important for collection types, like `Vec` or `BTreeMap`, since they need special handling for they key/value de/serialization such that the conversions can be done on the key/values.
|
||||
[`SerializeAs`] / [`DeserializeAs`] is very important for collection types, like `Vec` or `BTreeMap`, since they need special handling for the key/value de/serialization such that the conversions can be done on the key/values.
|
||||
You also find them implemented on the conversion types, such as the [`DisplayFromStr`] type.
|
||||
These make up the bulk of this crate and allow you to perform all the nice conversions to [hex strings], the [bytes to string converter], or [duration to UNIX epoch].
|
||||
|
||||
In many cases, conversion is only required from one serializable type to another one, without requiring the full power of the `Serialize` or `Deserialize` traits.
|
||||
In these cases, the [`serde_conv!`] macro conveniently allows defining conversion types without the boilerplate.
|
||||
The documentation of [`serde_conv!`] contains more details how to use it.
|
||||
|
||||
The trait documentations for [`SerializeAs`] and [`DeserializeAs`] describe in details how to implement them for container types like `Box` or `Vec` and other types.
|
||||
|
||||
### Using `#[serde_as]` on types without `SerializeAs` and `Serialize` implementations
|
||||
|
@ -118,7 +145,7 @@ We assume we have a module containing a `serialize` and a `deserialize` function
|
|||
You find an example in the [official serde documentation](https://serde.rs/custom-date-format.html).
|
||||
|
||||
Our goal is to serialize this `Data` struct.
|
||||
Right now we do not have anything we can use to replace `???` with, since `_` only works if `RemoteType` would implement `Serialize`, which it does not.
|
||||
Right now, we do not have anything we can use to replace `???` with, since `_` only works if `RemoteType` would implement `Serialize`, which it does not.
|
||||
|
||||
```rust
|
||||
# #[cfg(FALSE)] {
|
||||
|
@ -137,7 +164,7 @@ This allows it to seamlessly work with types from dependencies without running i
|
|||
|
||||
```rust
|
||||
# #[cfg(FALSE)] {
|
||||
struct Localtype;
|
||||
struct LocalType;
|
||||
|
||||
impl SerializeAs<RemoteType> for LocalType {
|
||||
fn serialize_as<S>(value: &RemoteType, serializer: S) -> Result<S::Ok, S::Error>
|
||||
|
@ -147,12 +174,21 @@ impl SerializeAs<RemoteType> for LocalType {
|
|||
MODULE::serialize(value, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeAs<'de, RemoteType> for LocalType {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<RemoteType, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
MODULE::deserialize(deserializer)
|
||||
}
|
||||
}
|
||||
# }
|
||||
```
|
||||
|
||||
This is how the final implementation looks like.
|
||||
We assumed we have a module `MODULE` with a `serialize` function already, which we use here to provide the implementation.
|
||||
As can be seen this is mostly boilerplate, since the most part is encapsulated in `$module::serialize`.
|
||||
As can be seen, this is mostly boilerplate, since the most part is encapsulated in `$module::serialize`.
|
||||
The final `Data` struct will now look like:
|
||||
|
||||
```rust
|
||||
|
@ -169,7 +205,7 @@ struct Data {
|
|||
### Using `#[serde_as]` with serde's remote derives
|
||||
|
||||
A special case of the above section is using it on remote derives.
|
||||
This is a special functionality of serde where it derives the de-/serialization code for a type from another crate if all fields are `pub`.
|
||||
This is a special functionality of serde, where it derives the de/serialization code for a type from another crate if all fields are `pub`.
|
||||
You can find all the details in the [official serde documentation](https://serde.rs/remote-derive.html).
|
||||
|
||||
```rust
|
||||
|
@ -200,8 +236,8 @@ struct DurationDef {
|
|||
# }
|
||||
```
|
||||
|
||||
Our goal is it now to use `Duration` within `serde_as`.
|
||||
We make use of the existing `DurationDef` type and its `serialize` and `deserialize` functions.
|
||||
Our goal is now to use `Duration` within `serde_as`.
|
||||
We use the existing `DurationDef` type and its `serialize` and `deserialize` functions.
|
||||
We can write this implementation.
|
||||
The implementation for `DeserializeAs` works analogue.
|
||||
|
||||
|
@ -233,7 +269,7 @@ struct Data {
|
|||
# }
|
||||
```
|
||||
|
||||
### Re-exporting `serde_as`
|
||||
## Re-exporting `serde_as`
|
||||
|
||||
If `serde_as` is being used in a context where the `serde_with` crate is not available from the root
|
||||
path, but is re-exported at some other path, the `crate = "..."` attribute argument should be used
|
||||
|
@ -276,7 +312,7 @@ pub use serde_with;
|
|||
pub use some_other_lib_derive::define_some_type;
|
||||
```
|
||||
|
||||
And the procedural macro can be used by other crates without any additional imports:
|
||||
The procedural macro can be used by other crates without any additional imports:
|
||||
|
||||
```rust,ignore
|
||||
// consuming_crate/src/main.rs
|
||||
|
@ -284,236 +320,13 @@ And the procedural macro can be used by other crates without any additional impo
|
|||
some_other_lib::define_some_type!();
|
||||
```
|
||||
|
||||
## De/Serialize Implementations Available
|
||||
|
||||
### Bytes / `Vec<u8>` to hex string
|
||||
|
||||
[`Hex`]
|
||||
|
||||
Requires the `hex` feature.
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::hex::Hex")]
|
||||
value: Vec<u8>,
|
||||
|
||||
// JSON
|
||||
"value": "deadbeef",
|
||||
```
|
||||
|
||||
### `Default` from `null`
|
||||
|
||||
[`DefaultOnNull`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "DefaultOnNull")]
|
||||
value: u32,
|
||||
#[serde_as(as = "DefaultOnNull<DisplayFromStr>")]
|
||||
value2: u32,
|
||||
|
||||
// JSON
|
||||
"value": 123,
|
||||
"value2": "999",
|
||||
|
||||
// Deserializes null into the Default value, i.e.,
|
||||
null => 0
|
||||
```
|
||||
|
||||
### De/Serialize with `FromStr` and `Display`
|
||||
|
||||
Useful if a type implements `FromStr` / `Display` but not `Deserialize` / `Serialize`.
|
||||
|
||||
[`DisplayFromStr`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::DisplayFromStr")]
|
||||
value: u128,
|
||||
#[serde_as(as = "serde_with::DisplayFromStr")]
|
||||
mime: mime::Mime,
|
||||
|
||||
// JSON
|
||||
"value": "340282366920938463463374607431768211455",
|
||||
"mime": "text/*",
|
||||
```
|
||||
|
||||
### `Duration` as seconds
|
||||
|
||||
[`DurationSeconds`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::DurationSeconds<u64>")]
|
||||
value: Duration,
|
||||
|
||||
// JSON
|
||||
"value": 86400,
|
||||
```
|
||||
|
||||
[`DurationSecondsWithFrac`] supports subsecond precision:
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::DurationSecondsWithFrac<f64>")]
|
||||
value: Duration,
|
||||
|
||||
// JSON
|
||||
"value": 1.234,
|
||||
```
|
||||
|
||||
Different serialization formats are possible:
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::DurationSecondsWithFrac<String>")]
|
||||
value: Duration,
|
||||
|
||||
// JSON
|
||||
"value": "1.234",
|
||||
```
|
||||
|
||||
The same conversions are also implemented for [`chrono::Duration`] with the `chrono` feature.
|
||||
|
||||
### Ignore deserialization errors
|
||||
|
||||
Check the documentation for [`DefaultOnError`].
|
||||
|
||||
### `Maps` to `Vec` of tuples
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "Vec<(_, _)>")]
|
||||
value: HashMap<String, u32>, // also works with BTreeMap
|
||||
|
||||
// JSON
|
||||
"value": [
|
||||
["hello", 1],
|
||||
["world", 2]
|
||||
],
|
||||
```
|
||||
|
||||
The [inverse operation](#vec-of-tuples-to-maps) is also available.
|
||||
|
||||
### `NaiveDateTime` like UTC timestamp
|
||||
|
||||
Requires the `chrono` feature.
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "chrono::DateTime<chrono::Utc>")]
|
||||
value: chrono::NaiveDateTime,
|
||||
|
||||
// JSON
|
||||
"value": "1994-11-05T08:15:30Z",
|
||||
^ Pretend DateTime is UTC
|
||||
```
|
||||
|
||||
### `None` as empty `String`
|
||||
|
||||
[`NoneAsEmptyString`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::NoneAsEmptyString")]
|
||||
value: Option<String>,
|
||||
|
||||
// JSON
|
||||
"value": "", // converts to None
|
||||
|
||||
"value": "Hello World!", // converts to Some
|
||||
```
|
||||
|
||||
### Timestamps as seconds since UNIX epoch
|
||||
|
||||
[`TimestampSeconds`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::TimestampSeconds<i64>")]
|
||||
value: SystemTime,
|
||||
|
||||
// JSON
|
||||
"value": 86400,
|
||||
```
|
||||
|
||||
[`TimestampSecondsWithFrac`] supports subsecond precision:
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::TimestampSecondsWithFrac<f64>")]
|
||||
value: SystemTime,
|
||||
|
||||
// JSON
|
||||
"value": 1.234,
|
||||
```
|
||||
|
||||
Different serialization formats are possible:
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::TimestampSecondsWithFrac<String>")]
|
||||
value: SystemTime,
|
||||
|
||||
// JSON
|
||||
"value": "1.234",
|
||||
```
|
||||
|
||||
The same conversions are also implemented for [`chrono::DateTime<Utc>`] and [`chrono::DateTime<Local>`] with the `chrono` feature.
|
||||
|
||||
### Value into JSON String
|
||||
|
||||
Some JSON APIs are weird and return a JSON encoded string in a JSON response
|
||||
|
||||
[`JsonString`]
|
||||
|
||||
Requires the `json` feature.
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct OtherStruct {
|
||||
value: usize,
|
||||
}
|
||||
|
||||
#[serde_as(as = "serde_with::json::JsonString")]
|
||||
value: OtherStruct,
|
||||
|
||||
// JSON
|
||||
"value": "{\"value\":5}",
|
||||
```
|
||||
|
||||
### `Vec` of tuples to `Maps`
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "HashMap<_, _>")] // also works with BTreeMap
|
||||
value: Vec<(String, u32)>,
|
||||
|
||||
// JSON
|
||||
"value": {
|
||||
"hello": 1,
|
||||
"world": 2
|
||||
},
|
||||
```
|
||||
|
||||
The [inverse operation](#maps-to-vec-of-tuples) is also available.
|
||||
|
||||
[`chrono::DateTime<Local>`]: chrono_crate::DateTime
|
||||
[`chrono::DateTime<Utc>`]: chrono_crate::DateTime
|
||||
[`chrono::Duration`]: https://docs.rs/chrono/latest/chrono/struct.Duration.html
|
||||
[`DefaultOnError`]: crate::DefaultOnError
|
||||
[`DefaultOnNull`]: crate::DefaultOnNull
|
||||
[`DeserializeAs`]: crate::DeserializeAs
|
||||
[`DisplayFromStr`]: crate::DisplayFromStr
|
||||
[`DurationSeconds`]: crate::DurationSeconds
|
||||
[`DurationSecondsWithFrac`]: crate::DurationSecondsWithFrac
|
||||
[`Hex`]: crate::hex::Hex
|
||||
[`JsonString`]: crate::json::JsonString
|
||||
[`NoneAsEmptyString`]: crate::NoneAsEmptyString
|
||||
[`serde_as`]: crate::serde_as
|
||||
[`serde_conv!`]: crate::serde_conv!
|
||||
[`serde`'s own `crate` argument]: https://serde.rs/container-attrs.html#crate
|
||||
[`SerializeAs`]: crate::SerializeAs
|
||||
[bytes to string converter]: crate::BytesOrString
|
||||
[duration to UNIX epoch]: crate::DurationSeconds
|
||||
[hex strings]: crate::hex::Hex
|
||||
[serde_with#185]: https://github.com/jonasbb/serde_with/issues/185
|
||||
[`serde`'s own `crate` argument]: https://serde.rs/container-attrs.html#crate
|
||||
|
|
|
@ -0,0 +1,518 @@
|
|||
# De/Serialize Transformations Available
|
||||
|
||||
This page lists the transformations implemented in this crate and supported by `serde_as`.
|
||||
|
||||
1. [Base64 encode bytes](#base64-encode-bytes)
|
||||
2. [Big Array support](#big-array-support)
|
||||
3. [`bool` from integer](#bool-from-integer)
|
||||
4. [Borrow from the input for `Cow` type](#borrow-from-the-input-for-cow-type)
|
||||
5. [`Bytes` with more efficiency](#bytes-with-more-efficiency)
|
||||
6. [Convert to an intermediate type using `Into`](#convert-to-an-intermediate-type-using-into)
|
||||
7. [Convert to an intermediate type using `TryInto`](#convert-to-an-intermediate-type-using-tryinto)
|
||||
8. [`Default` from `null`](#default-from-null)
|
||||
9. [De/Serialize into `Vec`, ignoring errors](#deserialize-into-vec-ignoring-errors)
|
||||
10. [De/Serialize with `FromStr` and `Display`](#deserialize-with-fromstr-and-display)
|
||||
11. [`Duration` as seconds](#duration-as-seconds)
|
||||
12. [Hex encode bytes](#hex-encode-bytes)
|
||||
13. [Ignore deserialization errors](#ignore-deserialization-errors)
|
||||
14. [`Maps` to `Vec` of enums](#maps-to-vec-of-enums)
|
||||
15. [`Maps` to `Vec` of tuples](#maps-to-vec-of-tuples)
|
||||
16. [`NaiveDateTime` like UTC timestamp](#naivedatetime-like-utc-timestamp)
|
||||
17. [`None` as empty `String`](#none-as-empty-string)
|
||||
18. [One or many elements into `Vec`](#one-or-many-elements-into-vec)
|
||||
19. [Pick first successful deserialization](#pick-first-successful-deserialization)
|
||||
20. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
|
||||
21. [Value into JSON String](#value-into-json-string)
|
||||
22. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
|
||||
23. [Well-known time formats for `OffsetDateTime`](#well-known-time-formats-for-offsetdatetime)
|
||||
|
||||
## Base64 encode bytes
|
||||
|
||||
[`Base64`]
|
||||
|
||||
Requires the `base64` feature.
|
||||
The character set and padding behavior can be configured.
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::base64::Base64")]
|
||||
value: Vec<u8>,
|
||||
#[serde_as(as = "Base64<Bcrypt, Unpadded>")]
|
||||
bcrypt_unpadded: Vec<u8>,
|
||||
|
||||
// JSON
|
||||
"value": "SGVsbG8gV29ybGQ=",
|
||||
"bcrypt_unpadded": "QETqZE6eT07wZEO",
|
||||
```
|
||||
|
||||
## Big Array support
|
||||
|
||||
Support for arrays of arbitrary size.
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "[[_; 64]; 33]")]
|
||||
value: [[u8; 64]; 33],
|
||||
|
||||
// JSON
|
||||
"value": [[0,0,0,0,0,...], [0,0,0,...], ...],
|
||||
```
|
||||
|
||||
## `bool` from integer
|
||||
|
||||
Deserialize an integer and convert it into a `bool`.
|
||||
[`BoolFromInt<Strict>`] (default) deserializes 0 to `false` and `1` to `true`, other numbers are errors.
|
||||
[`BoolFromInt<Flexible>`] deserializes any non-zero as `true`.
|
||||
Serialization only emits 0/1.
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "BoolFromInt")] // BoolFromInt<Strict>
|
||||
b: bool,
|
||||
|
||||
// JSON
|
||||
"b": 1,
|
||||
```
|
||||
|
||||
## Borrow from the input for `Cow` type
|
||||
|
||||
The types `Cow<'_, str>`, `Cow<'_, [u8]>`, or `Cow<'_, [u8; N]>` can borrow from the input, avoiding extra copies.
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "BorrowCow")]
|
||||
value: Cow<'a, str>,
|
||||
|
||||
// JSON
|
||||
"value": "foobar",
|
||||
```
|
||||
|
||||
## `Bytes` with more efficiency
|
||||
|
||||
[`Bytes`]
|
||||
|
||||
More efficient serialization for byte slices and similar.
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "Bytes")]
|
||||
value: Vec<u8>,
|
||||
|
||||
// JSON
|
||||
"value": [0, 1, 2, 3, ...],
|
||||
```
|
||||
|
||||
## Convert to an intermediate type using `Into`
|
||||
|
||||
[`FromInto`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "FromInto<(u8, u8, u8)>")]
|
||||
value: Rgb,
|
||||
|
||||
impl From<(u8, u8, u8)> for Rgb { ... }
|
||||
impl From<Rgb> for (u8, u8, u8) { ... }
|
||||
|
||||
// JSON
|
||||
"value": [128, 64, 32],
|
||||
```
|
||||
|
||||
## Convert to an intermediate type using `TryInto`
|
||||
|
||||
[`TryFromInto`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "TryFromInto<i8>")]
|
||||
value: u8,
|
||||
|
||||
// JSON
|
||||
"value": 127,
|
||||
```
|
||||
|
||||
## `Default` from `null`
|
||||
|
||||
[`DefaultOnNull`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "DefaultOnNull")]
|
||||
value: u32,
|
||||
#[serde_as(as = "DefaultOnNull<DisplayFromStr>")]
|
||||
value2: u32,
|
||||
|
||||
// JSON
|
||||
"value": 123,
|
||||
"value2": "999",
|
||||
|
||||
// Deserializes null into the Default value, i.e.,
|
||||
null => 0
|
||||
```
|
||||
|
||||
## De/Serialize into `Vec`, ignoring errors
|
||||
|
||||
[`VecSkipError`]
|
||||
|
||||
For formats with heterogenous-typed sequences, we can collect only the deserializable elements.
|
||||
This is also useful for unknown enum variants.
|
||||
|
||||
```ignore
|
||||
#[derive(serde::Deserialize)]
|
||||
enum Color {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
}
|
||||
|
||||
// JSON
|
||||
"colors": ["Blue", "Yellow", "Green"],
|
||||
|
||||
// Rust
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
colors: Vec<Color>,
|
||||
|
||||
// => vec![Blue, Green]
|
||||
```
|
||||
|
||||
## De/Serialize with `FromStr` and `Display`
|
||||
|
||||
Useful if a type implements `FromStr` / `Display` but not `Deserialize` / `Serialize`.
|
||||
|
||||
[`DisplayFromStr`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::DisplayFromStr")]
|
||||
value: u128,
|
||||
#[serde_as(as = "serde_with::DisplayFromStr")]
|
||||
mime: mime::Mime,
|
||||
|
||||
// JSON
|
||||
"value": "340282366920938463463374607431768211455",
|
||||
"mime": "text/*",
|
||||
```
|
||||
|
||||
## `Duration` as seconds
|
||||
|
||||
[`DurationSeconds`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::DurationSeconds<u64>")]
|
||||
value: Duration,
|
||||
|
||||
// JSON
|
||||
"value": 86400,
|
||||
```
|
||||
|
||||
[`DurationSecondsWithFrac`] supports subsecond precision:
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::DurationSecondsWithFrac<f64>")]
|
||||
value: Duration,
|
||||
|
||||
// JSON
|
||||
"value": 1.234,
|
||||
```
|
||||
|
||||
Different serialization formats are possible:
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::DurationSecondsWithFrac<String>")]
|
||||
value: Duration,
|
||||
|
||||
// JSON
|
||||
"value": "1.234",
|
||||
```
|
||||
|
||||
The same conversions are also implemented for [`chrono::Duration`] with the `chrono` feature.
|
||||
|
||||
The same conversions are also implemented for [`time::Duration`] with the `time_0_3` feature.
|
||||
|
||||
## Hex encode bytes
|
||||
|
||||
[`Hex`]
|
||||
|
||||
Requires the `hex` feature.
|
||||
The hex string can use upper- and lowercase characters.
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::hex::Hex")]
|
||||
lowercase: Vec<u8>,
|
||||
#[serde_as(as = "serde_with::hex::Hex<serde_with::formats::Uppercase>")]
|
||||
uppercase: Vec<u8>,
|
||||
|
||||
// JSON
|
||||
"lowercase": "deadbeef",
|
||||
"uppercase": "DEADBEEF",
|
||||
```
|
||||
|
||||
## Ignore deserialization errors
|
||||
|
||||
Check the documentation for [`DefaultOnError`].
|
||||
|
||||
## `Maps` to `Vec` of enums
|
||||
|
||||
[`EnumMap`]
|
||||
|
||||
Combine multiple enum values into a single map.
|
||||
The key is the enum variant name, and the value is the variant value.
|
||||
This only works with [*externally tagged*] enums, the default enum representation.
|
||||
Other forms cannot be supported.
|
||||
|
||||
```ignore
|
||||
enum EnumValue {
|
||||
Int(i32),
|
||||
String(String),
|
||||
Unit,
|
||||
Tuple(i32, String),
|
||||
Struct {
|
||||
a: i32,
|
||||
b: String,
|
||||
},
|
||||
}
|
||||
|
||||
// Rust
|
||||
struct VecEnumValues (
|
||||
#[serde_as(as = "EnumMap")]
|
||||
Vec<EnumValue>,
|
||||
);
|
||||
|
||||
VecEnumValues(vec![
|
||||
EnumValue::Int(123),
|
||||
EnumValue::String("Foo".to_string()),
|
||||
EnumValue::Unit,
|
||||
EnumValue::Tuple(1, "Bar".to_string()),
|
||||
EnumValue::Struct {
|
||||
a: 666,
|
||||
b: "Baz".to_string(),
|
||||
},
|
||||
])
|
||||
|
||||
// JSON
|
||||
{
|
||||
"Int": 123,
|
||||
"String": "Foo",
|
||||
"Unit": null,
|
||||
"Tuple": [
|
||||
1,
|
||||
"Bar",
|
||||
],
|
||||
"Struct": {
|
||||
"a": 666,
|
||||
"b": "Baz",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[*externally tagged*]: https://serde.rs/enum-representations.html#externally-tagged
|
||||
|
||||
## `Maps` to `Vec` of tuples
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "Vec<(_, _)>")]
|
||||
value: HashMap<String, u32>, // also works with BTreeMap
|
||||
|
||||
// JSON
|
||||
"value": [
|
||||
["hello", 1],
|
||||
["world", 2]
|
||||
],
|
||||
```
|
||||
|
||||
The [inverse operation](#vec-of-tuples-to-maps) is also available.
|
||||
|
||||
## `NaiveDateTime` like UTC timestamp
|
||||
|
||||
Requires the `chrono` feature.
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "chrono::DateTime<chrono::Utc>")]
|
||||
value: chrono::NaiveDateTime,
|
||||
|
||||
// JSON
|
||||
"value": "1994-11-05T08:15:30Z",
|
||||
^ Pretend DateTime is UTC
|
||||
```
|
||||
|
||||
## `None` as empty `String`
|
||||
|
||||
[`NoneAsEmptyString`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::NoneAsEmptyString")]
|
||||
value: Option<String>,
|
||||
|
||||
// JSON
|
||||
"value": "", // converts to None
|
||||
|
||||
"value": "Hello World!", // converts to Some
|
||||
```
|
||||
|
||||
## One or many elements into `Vec`
|
||||
|
||||
[`OneOrMany`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::OneOrMany<_>")]
|
||||
value: Vec<String>,
|
||||
|
||||
// JSON
|
||||
"value": "", // Deserializes single elements
|
||||
|
||||
"value": ["Hello", "World!"], // or lists of many
|
||||
```
|
||||
|
||||
## Pick first successful deserialization
|
||||
|
||||
[`PickFirst`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::PickFirst<(_, serde_with::DisplayFromStr)>")]
|
||||
value: u32,
|
||||
|
||||
// JSON
|
||||
// serialize into
|
||||
"value": 666,
|
||||
// deserialize from either
|
||||
"value": 666,
|
||||
"value": "666",
|
||||
```
|
||||
|
||||
## Timestamps as seconds since UNIX epoch
|
||||
|
||||
[`TimestampSeconds`]
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::TimestampSeconds<i64>")]
|
||||
value: SystemTime,
|
||||
|
||||
// JSON
|
||||
"value": 86400,
|
||||
```
|
||||
|
||||
[`TimestampSecondsWithFrac`] supports subsecond precision:
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::TimestampSecondsWithFrac<f64>")]
|
||||
value: SystemTime,
|
||||
|
||||
// JSON
|
||||
"value": 1.234,
|
||||
```
|
||||
|
||||
Different serialization formats are possible:
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "serde_with::TimestampSecondsWithFrac<String>")]
|
||||
value: SystemTime,
|
||||
|
||||
// JSON
|
||||
"value": "1.234",
|
||||
```
|
||||
|
||||
The same conversions are also implemented for [`chrono::DateTime<Utc>`], [`chrono::DateTime<Local>`], and [`chrono::NaiveDateTime`] with the `chrono` feature.
|
||||
|
||||
The conversions are availble for [`time::OffsetDateTime`] and [`time::PrimitiveDateTime`] with the `time_0_3` feature enabled.
|
||||
|
||||
## Value into JSON String
|
||||
|
||||
Some JSON APIs are weird and return a JSON encoded string in a JSON response
|
||||
|
||||
[`JsonString`]
|
||||
|
||||
Requires the `json` feature.
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct OtherStruct {
|
||||
value: usize,
|
||||
}
|
||||
|
||||
#[serde_as(as = "serde_with::json::JsonString")]
|
||||
value: OtherStruct,
|
||||
|
||||
// JSON
|
||||
"value": "{\"value\":5}",
|
||||
```
|
||||
|
||||
## `Vec` of tuples to `Maps`
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "HashMap<_, _>")] // also works with BTreeMap
|
||||
value: Vec<(String, u32)>,
|
||||
|
||||
// JSON
|
||||
"value": {
|
||||
"hello": 1,
|
||||
"world": 2
|
||||
},
|
||||
```
|
||||
|
||||
This operation is also available for other sequence types.
|
||||
This includes `BinaryHeap<(K, V)>`, `BTreeSet<(K, V)>`, `HashSet<(K, V)>`, `LinkedList<(K, V)>`, `VecDeque<(K, V)>`, `Option<(K, V)>` and `[(K, V); N]` for all sizes of N.
|
||||
|
||||
The [inverse operation](#maps-to-vec-of-tuples) is also available.
|
||||
|
||||
## Well-known time formats for `OffsetDateTime`
|
||||
|
||||
[`time::OffsetDateTime`] can be serialized in string format in different well-known formats.
|
||||
Two formats are supported, [`time::format_description::well_known::Rfc2822`] and [`time::format_description::well_known::Rfc3339`].
|
||||
|
||||
```ignore
|
||||
// Rust
|
||||
#[serde_as(as = "time::format_description::well_known::Rfc2822")]
|
||||
rfc_2822: OffsetDateTime,
|
||||
#[serde_as(as = "time::format_description::well_known::Rfc3339")]
|
||||
rfc_3339: OffsetDateTime,
|
||||
|
||||
// JSON
|
||||
"rfc_2822": "Fri, 21 Nov 1997 09:55:06 -0600",
|
||||
"rfc_3339": "1997-11-21T09:55:06-06:00",
|
||||
```
|
||||
|
||||
These conversions are availble with the `time_0_3` feature flag.
|
||||
|
||||
[`Base64`]: crate::base64::Base64
|
||||
[`BoolFromInt<Flexible>`]: crate::BoolFromInt
|
||||
[`BoolFromInt<Strict>`]: crate::BoolFromInt
|
||||
[`Bytes`]: crate::Bytes
|
||||
[`chrono::DateTime<Local>`]: chrono_crate::DateTime
|
||||
[`chrono::DateTime<Utc>`]: chrono_crate::DateTime
|
||||
[`chrono::Duration`]: https://docs.rs/chrono/latest/chrono/struct.Duration.html
|
||||
[`chrono::NaiveDateTime`]: chrono_crate::NaiveDateTime
|
||||
[`DefaultOnError`]: crate::DefaultOnError
|
||||
[`DefaultOnNull`]: crate::DefaultOnNull
|
||||
[`DisplayFromStr`]: crate::DisplayFromStr
|
||||
[`DurationSeconds`]: crate::DurationSeconds
|
||||
[`DurationSecondsWithFrac`]: crate::DurationSecondsWithFrac
|
||||
[`EnumMap`]: crate::EnumMap
|
||||
[`FromInto`]: crate::FromInto
|
||||
[`Hex`]: crate::hex::Hex
|
||||
[`JsonString`]: crate::json::JsonString
|
||||
[`NoneAsEmptyString`]: crate::NoneAsEmptyString
|
||||
[`OneOrMany`]: crate::OneOrMany
|
||||
[`PickFirst`]: crate::PickFirst
|
||||
[`time::Duration`]: time_0_3::Duration
|
||||
[`time::format_description::well_known::Rfc2822`]: time_0_3::format_description::well_known::Rfc2822
|
||||
[`time::format_description::well_known::Rfc3339`]: time_0_3::format_description::well_known::Rfc3339
|
||||
[`time::OffsetDateTime`]: time_0_3::OffsetDateTime
|
||||
[`time::PrimitiveDateTime`]: time_0_3::PrimitiveDateTime
|
||||
[`TimestampSeconds`]: crate::TimestampSeconds
|
||||
[`TimestampSecondsWithFrac`]: crate::TimestampSecondsWithFrac
|
||||
[`TryFromInto`]: crate::TryFromInto
|
||||
[`VecSkipError`]: crate::VecSkipError
|
|
@ -1,19 +1,25 @@
|
|||
//! De/Serialization of hexadecimal encoded bytes
|
||||
//!
|
||||
//! This modules is only available when using the `hex` feature of the crate.
|
||||
//!
|
||||
//! Please check the documentation on the [`Hex`] type for details.
|
||||
|
||||
use crate::{
|
||||
de::DeserializeAs,
|
||||
formats::{Format, Lowercase, Uppercase},
|
||||
ser::SerializeAs,
|
||||
};
|
||||
use alloc::{borrow::Cow, format, vec::Vec};
|
||||
use core::{
|
||||
convert::{TryFrom, TryInto},
|
||||
marker::PhantomData,
|
||||
};
|
||||
use serde::{de::Error, Deserialize, Deserializer, Serializer};
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
|
||||
/// Serialize bytes as a hex string
|
||||
///
|
||||
/// The type serializes a sequence of bytes as a hexadecimal string.
|
||||
/// It works on any type implementing `AsRef<[u8]>` for serialization and `From<Vec<u8>>` for deserialization.
|
||||
/// It works on any type implementing `AsRef<[u8]>` for serialization and `TryFrom<Vec<u8>>` for deserialization.
|
||||
///
|
||||
/// The format type parameter specifies if the hex string should use lower- or uppercase characters.
|
||||
/// Valid options are the types [`Lowercase`] and [`Uppercase`].
|
||||
|
@ -22,9 +28,8 @@ use std::{borrow::Cow, marker::PhantomData};
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
///
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_json::json;
|
||||
/// # use serde_with::serde_as;
|
||||
/// #
|
||||
|
@ -48,13 +53,55 @@ use std::{borrow::Cow, marker::PhantomData};
|
|||
/// let b = b"Hello World!";
|
||||
///
|
||||
/// // Hex with lowercase letters
|
||||
/// assert_eq!(json!("48656c6c6f20576f726c6421"), serde_json::to_value(BytesLowercase(b.to_vec())).unwrap());
|
||||
/// assert_eq!(
|
||||
/// json!("48656c6c6f20576f726c6421"),
|
||||
/// serde_json::to_value(BytesLowercase(b.to_vec())).unwrap()
|
||||
/// );
|
||||
/// // Hex with uppercase letters
|
||||
/// assert_eq!(json!("48656C6C6F20576F726C6421"), serde_json::to_value(BytesUppercase(b.to_vec())).unwrap());
|
||||
/// assert_eq!(
|
||||
/// json!("48656C6C6F20576F726C6421"),
|
||||
/// serde_json::to_value(BytesUppercase(b.to_vec())).unwrap()
|
||||
/// );
|
||||
///
|
||||
/// // Serialization always work from lower- and uppercase characters, even mixed case.
|
||||
/// assert_eq!(BytesLowercase(vec![0x00, 0xaa, 0xbc, 0x99, 0xff]), serde_json::from_value(json!("00aAbc99FF")).unwrap());
|
||||
/// assert_eq!(BytesUppercase(vec![0x00, 0xaa, 0xbc, 0x99, 0xff]), serde_json::from_value(json!("00aAbc99FF")).unwrap());
|
||||
/// assert_eq!(
|
||||
/// BytesLowercase(vec![0x00, 0xaa, 0xbc, 0x99, 0xff]),
|
||||
/// serde_json::from_value(json!("00aAbc99FF")).unwrap()
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// BytesUppercase(vec![0x00, 0xaa, 0xbc, 0x99, 0xff]),
|
||||
/// serde_json::from_value(json!("00aAbc99FF")).unwrap()
|
||||
/// );
|
||||
///
|
||||
/// #[serde_as]
|
||||
/// # #[derive(Debug, PartialEq, Eq)]
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct ByteArray(
|
||||
/// // Equivalent to serde_with::hex::Hex<serde_with::formats::Lowercase>
|
||||
/// #[serde_as(as = "serde_with::hex::Hex")]
|
||||
/// [u8; 12]
|
||||
/// );
|
||||
///
|
||||
/// let b = b"Hello World!";
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// json!("48656c6c6f20576f726c6421"),
|
||||
/// serde_json::to_value(ByteArray(b.clone())).unwrap()
|
||||
/// );
|
||||
///
|
||||
/// // Serialization always work from lower- and uppercase characters, even mixed case.
|
||||
/// assert_eq!(
|
||||
/// ByteArray([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0xaa, 0xbc, 0x99, 0xff]),
|
||||
/// serde_json::from_value(json!("0011223344556677aAbc99FF")).unwrap()
|
||||
/// );
|
||||
///
|
||||
/// // Remember that the conversion may fail. (The following errors are specific to fixed-size arrays)
|
||||
/// let error_result: Result<ByteArray, _> = serde_json::from_value(json!("42")); // Too short
|
||||
/// error_result.unwrap_err();
|
||||
///
|
||||
/// let error_result: Result<ByteArray, _> =
|
||||
/// serde_json::from_value(json!("000000000000000000000000000000")); // Too long
|
||||
/// error_result.unwrap_err();
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
|
@ -86,7 +133,7 @@ where
|
|||
|
||||
impl<'de, T, FORMAT> DeserializeAs<'de, T> for Hex<FORMAT>
|
||||
where
|
||||
T: From<Vec<u8>>,
|
||||
T: TryFrom<Vec<u8>>,
|
||||
FORMAT: Format,
|
||||
{
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<T, D::Error>
|
||||
|
@ -95,6 +142,14 @@ where
|
|||
{
|
||||
<Cow<'de, str> as Deserialize<'de>>::deserialize(deserializer)
|
||||
.and_then(|s| hex::decode(&*s).map_err(Error::custom))
|
||||
.map(Into::into)
|
||||
.and_then(|vec: Vec<u8>| {
|
||||
let length = vec.len();
|
||||
vec.try_into().map_err(|_e: T::Error| {
|
||||
Error::custom(format!(
|
||||
"Can't convert a Byte Vector of length {} to the output type.",
|
||||
length
|
||||
))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use serde::{de::DeserializeOwned, Deserializer, Serialize, Serializer};
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// #
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct A {
|
||||
|
@ -30,14 +30,17 @@ use serde::{de::DeserializeOwned, Deserializer, Serialize, Serializer};
|
|||
/// let x = A {
|
||||
/// other_struct: B { value: 10 },
|
||||
/// };
|
||||
/// assert_eq!(r#"{"other_struct":"{\"value\":10}"}"#, serde_json::to_string(&x).unwrap());
|
||||
/// assert_eq!(
|
||||
/// r#"{"other_struct":"{\"value\":10}"}"#,
|
||||
/// serde_json::to_string(&x).unwrap()
|
||||
/// );
|
||||
/// ```
|
||||
pub mod nested {
|
||||
use core::{fmt, marker::PhantomData};
|
||||
use serde::{
|
||||
de::{DeserializeOwned, Deserializer, Error, Visitor},
|
||||
ser::{self, Serialize, Serializer},
|
||||
};
|
||||
use std::{fmt, marker::PhantomData};
|
||||
|
||||
/// Deserialize value from a string which is valid JSON
|
||||
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
|
||||
|
@ -93,7 +96,7 @@ pub mod nested {
|
|||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_with::{serde_as, json::JsonString};
|
||||
/// #
|
||||
/// #[serde_as]
|
||||
|
@ -113,7 +116,10 @@ pub mod nested {
|
|||
/// let x = A {
|
||||
/// other_struct: B { value: 10 },
|
||||
/// };
|
||||
/// assert_eq!(r#"{"other_struct":"{\"value\":10}"}"#, serde_json::to_string(&x).unwrap());
|
||||
/// assert_eq!(
|
||||
/// r#"{"other_struct":"{\"value\":10}"}"#,
|
||||
/// serde_json::to_string(&x).unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,23 +1,30 @@
|
|||
//! De/Serialization for Rust's builtin and std types
|
||||
|
||||
use crate::Separator;
|
||||
use serde::{
|
||||
de::{Deserialize, DeserializeOwned, Deserializer, Error, MapAccess, SeqAccess, Visitor},
|
||||
ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
|
||||
use crate::{utils, Separator};
|
||||
#[cfg(doc)]
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::{
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use std::{
|
||||
use core::{
|
||||
cmp::Eq,
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt::{self, Display},
|
||||
hash::{BuildHasher, Hash},
|
||||
hash::Hash,
|
||||
iter::FromIterator,
|
||||
marker::PhantomData,
|
||||
str::FromStr,
|
||||
};
|
||||
use serde::{
|
||||
de::{Deserialize, DeserializeOwned, Deserializer, Error, MapAccess, SeqAccess, Visitor},
|
||||
ser::{Serialize, Serializer},
|
||||
};
|
||||
#[cfg(doc)]
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// De/Serialize using [`Display`] and [`FromStr`] implementation
|
||||
///
|
||||
/// This allows to deserialize a string as a number.
|
||||
/// This allows deserializing a string as a number.
|
||||
/// It can be very useful for serialization formats like JSON, which do not support integer
|
||||
/// numbers and have to resort to strings to represent them.
|
||||
///
|
||||
|
@ -30,7 +37,7 @@ use std::{
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// # use serde_with::{serde_as, DisplayFromStr};
|
||||
/// #
|
||||
/// #[serde_as]
|
||||
|
@ -45,7 +52,7 @@ use std::{
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// #
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct A {
|
||||
|
@ -66,7 +73,10 @@ use std::{
|
|||
/// mime: mime::STAR_STAR,
|
||||
/// number: 777,
|
||||
/// };
|
||||
/// assert_eq!(r#"{"mime":"*/*","number":"777"}"#, serde_json::to_string(&x).unwrap());
|
||||
/// assert_eq!(
|
||||
/// r#"{"mime":"*/*","number":"777"}"#,
|
||||
/// serde_json::to_string(&x).unwrap()
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// [`DeserializeFromStr`]: serde_with_macros::DeserializeFromStr
|
||||
|
@ -75,7 +85,6 @@ use std::{
|
|||
/// [`SerializeDisplay`]: serde_with_macros::SerializeDisplay
|
||||
pub mod display_fromstr {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Deserialize T using [`FromStr`]
|
||||
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
|
||||
|
@ -114,7 +123,7 @@ pub mod display_fromstr {
|
|||
T: Display,
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&*value.to_string())
|
||||
serializer.collect_str(&value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +155,7 @@ pub mod display_fromstr {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// #
|
||||
/// use std::collections::BTreeSet;
|
||||
/// use std::net::Ipv4Addr;
|
||||
|
@ -179,22 +188,16 @@ pub mod display_fromstr {
|
|||
/// ].into_iter().collect(),
|
||||
/// bs: vec![false, true],
|
||||
/// };
|
||||
/// assert_eq!(r#"{"addresses":["127.53.0.1","127.53.0.2","127.53.1.1"],"bs":["false","true"]}"#, serde_json::to_string(&x).unwrap());
|
||||
/// assert_eq!(
|
||||
/// r#"{"addresses":["127.53.0.1","127.53.0.2","127.53.1.1"],"bs":["false","true"]}"#,
|
||||
/// serde_json::to_string(&x).unwrap()
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// [`DisplayFromStr`]: crate::DisplayFromStr
|
||||
/// [`serde_as`]: crate::guide::serde_as
|
||||
pub mod seq_display_fromstr {
|
||||
use serde::{
|
||||
de::{Deserializer, Error, SeqAccess, Visitor},
|
||||
ser::{SerializeSeq, Serializer},
|
||||
};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
iter::{FromIterator, IntoIterator},
|
||||
marker::PhantomData,
|
||||
str::FromStr,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
/// Deserialize collection T using [FromIterator] and [FromStr] for each element
|
||||
pub fn deserialize<'de, D, T, I>(deserializer: D) -> Result<T, D::Error>
|
||||
|
@ -217,20 +220,15 @@ pub mod seq_display_fromstr {
|
|||
write!(formatter, "a sequence")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut access: A) -> Result<Self::Value, A::Error>
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let mut values = access
|
||||
.size_hint()
|
||||
.map(Self::Value::with_capacity)
|
||||
.unwrap_or_else(Self::Value::new);
|
||||
|
||||
while let Some(value) = access.next_element::<&str>()? {
|
||||
values.push(value.parse::<S>().map_err(Error::custom)?);
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
utils::SeqIter::new(seq)
|
||||
.map(|res| {
|
||||
res.and_then(|value: &str| value.parse::<S>().map_err(Error::custom))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,13 +244,21 @@ pub mod seq_display_fromstr {
|
|||
for<'a> &'a T: IntoIterator<Item = &'a I>,
|
||||
I: Display,
|
||||
{
|
||||
let iter = value.into_iter();
|
||||
let (_, to) = iter.size_hint();
|
||||
let mut seq = serializer.serialize_seq(to)?;
|
||||
for item in iter {
|
||||
seq.serialize_element(&item.to_string())?;
|
||||
struct SerializeString<'a, I>(&'a I);
|
||||
|
||||
impl<'a, I> Serialize for SerializeString<'a, I>
|
||||
where
|
||||
I: Display,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(self.0)
|
||||
}
|
||||
}
|
||||
seq.end()
|
||||
|
||||
serializer.collect_seq(value.into_iter().map(SerializeString))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -271,7 +277,7 @@ pub mod seq_display_fromstr {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// # use serde_with::{serde_as, SpaceSeparator, StringWithSeparator};
|
||||
/// #
|
||||
/// #[serde_as]
|
||||
|
@ -286,7 +292,7 @@ pub mod seq_display_fromstr {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// #
|
||||
/// use serde_with::{CommaSeparator, SpaceSeparator};
|
||||
/// use std::collections::BTreeSet;
|
||||
|
@ -310,7 +316,10 @@ pub mod seq_display_fromstr {
|
|||
/// tags: vec!["1".to_string(), "2".to_string(), "3".to_string()],
|
||||
/// more_tags: BTreeSet::new(),
|
||||
/// };
|
||||
/// assert_eq!(r#"{"tags":"1 2 3","more_tags":""}"#, serde_json::to_string(&x).unwrap());
|
||||
/// assert_eq!(
|
||||
/// r#"{"tags":"1 2 3","more_tags":""}"#,
|
||||
/// serde_json::to_string(&x).unwrap()
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// [`serde_as`]: crate::guide::serde_as
|
||||
|
@ -375,7 +384,7 @@ where
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// #
|
||||
/// # #[derive(Debug, PartialEq, Eq)]
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
|
@ -389,18 +398,18 @@ where
|
|||
/// }
|
||||
/// // Missing Value
|
||||
/// let s = r#"{}"#;
|
||||
/// assert_eq!(Doc {a: None}, serde_json::from_str(s).unwrap());
|
||||
/// assert_eq!(s, serde_json::to_string(&Doc {a: None}).unwrap());
|
||||
/// assert_eq!(Doc { a: None }, serde_json::from_str(s).unwrap());
|
||||
/// assert_eq!(s, serde_json::to_string(&Doc { a: None }).unwrap());
|
||||
///
|
||||
/// // Unset Value
|
||||
/// let s = r#"{"a":null}"#;
|
||||
/// assert_eq!(Doc {a: Some(None)}, serde_json::from_str(s).unwrap());
|
||||
/// assert_eq!(s, serde_json::to_string(&Doc {a: Some(None)}).unwrap());
|
||||
/// assert_eq!(Doc { a: Some(None) }, serde_json::from_str(s).unwrap());
|
||||
/// assert_eq!(s, serde_json::to_string(&Doc { a: Some(None) }).unwrap());
|
||||
///
|
||||
/// // Existing Value
|
||||
/// let s = r#"{"a":5}"#;
|
||||
/// assert_eq!(Doc {a: Some(Some(5))}, serde_json::from_str(s).unwrap());
|
||||
/// assert_eq!(s, serde_json::to_string(&Doc {a: Some(Some(5))}).unwrap());
|
||||
/// assert_eq!(Doc { a: Some(Some(5)) }, serde_json::from_str(s).unwrap());
|
||||
/// assert_eq!(s, serde_json::to_string(&Doc { a: Some(Some(5)) }).unwrap());
|
||||
/// ```
|
||||
#[allow(clippy::option_option)]
|
||||
pub mod double_option {
|
||||
|
@ -446,7 +455,7 @@ pub mod double_option {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// #
|
||||
/// # #[derive(Debug, Eq, PartialEq)]
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
|
@ -462,7 +471,7 @@ pub mod double_option {
|
|||
///
|
||||
/// // Transparently add/remove Some() wrapper
|
||||
/// # let pretty_config = ron::ser::PrettyConfig::new()
|
||||
/// # .with_new_line("\n".into());
|
||||
/// # .new_line("\n".into());
|
||||
/// let s = r#"(
|
||||
/// mandatory: 1,
|
||||
/// optional: 2,
|
||||
|
@ -477,7 +486,7 @@ pub mod double_option {
|
|||
/// // Missing values are deserialized as `None`
|
||||
/// // while `None` values are skipped during serialization.
|
||||
/// # let pretty_config = ron::ser::PrettyConfig::new()
|
||||
/// # .with_new_line("\n".into());
|
||||
/// # .new_line("\n".into());
|
||||
/// let s = r#"(
|
||||
/// mandatory: 1,
|
||||
/// )"#;
|
||||
|
@ -531,7 +540,7 @@ pub mod unwrap_or_skip {
|
|||
///
|
||||
/// ```rust
|
||||
/// # use std::{collections::HashSet, iter::FromIterator};
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// #
|
||||
/// # #[derive(Debug, Eq, PartialEq)]
|
||||
/// #[derive(Deserialize)]
|
||||
|
@ -629,7 +638,7 @@ pub mod sets_duplicate_value_is_error {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// # use std::collections::HashMap;
|
||||
/// #
|
||||
/// # #[derive(Debug, Eq, PartialEq)]
|
||||
|
@ -866,7 +875,7 @@ pub mod sets_last_value_wins {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// # use std::collections::HashMap;
|
||||
/// #
|
||||
/// # #[derive(Debug, Eq, PartialEq)]
|
||||
|
@ -958,9 +967,9 @@ pub mod maps_first_key_wins {
|
|||
}
|
||||
}
|
||||
|
||||
/// De/Serialize a [`Option`]`<`[`String`]`>` type while transforming the empty string to [`None`]
|
||||
/// De/Serialize a [`Option<String>`] type while transforming the empty string to [`None`]
|
||||
///
|
||||
/// Convert an [`Option`]`<T>` from/to string using [`FromStr`] and [`AsRef`]`<`[`str`]`>` implementations.
|
||||
/// Convert an [`Option<T>`] from/to string using [`FromStr`] and [`AsRef<str>`] implementations.
|
||||
/// An empty string is deserialized as [`None`] and a [`None`] vice versa.
|
||||
///
|
||||
/// ## Converting to `serde_as`
|
||||
|
@ -969,7 +978,7 @@ pub mod maps_first_key_wins {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// # use serde_with::{serde_as, NoneAsEmptyString};
|
||||
/// #
|
||||
/// #[serde_as]
|
||||
|
@ -984,7 +993,7 @@ pub mod maps_first_key_wins {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_json::json;
|
||||
/// # use serde_with::rust::string_empty_as_none;
|
||||
/// #
|
||||
|
@ -1081,7 +1090,144 @@ pub mod string_empty_as_none {
|
|||
}
|
||||
}
|
||||
|
||||
/// De/Serialize a [`HashMap`] into a list of tuples
|
||||
/// De/Serialize a Map into a list of tuples
|
||||
///
|
||||
/// Some formats, like JSON, have limitations on the type of keys for maps.
|
||||
/// In case of JSON, keys are restricted to strings.
|
||||
/// Rust features more powerful keys, for example tuple, which can not be serialized to JSON.
|
||||
///
|
||||
/// This helper serializes the Map into a list of tuples, which does not have the same type restrictions.
|
||||
/// The module can be applied on any type implementing `IntoIterator<Item = (&'a K, &'a V)>` and `FromIterator<(K, V)>`, with `K` and `V` being the key and value types.
|
||||
/// From the standard library both [`HashMap`] and [`BTreeMap`] fullfil the condition and can be used here.
|
||||
///
|
||||
/// ## Converting to `serde_as`
|
||||
///
|
||||
/// If the map is of type [`HashMap`] or [`BTreeMap`] the same functionality can be expressed more clearly using the [`serde_as`] macro.
|
||||
/// The `_` is a placeholder which works for any type which implements [`Serialize`]/[`Deserialize`], such as the tuple and `u32` type.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_with::serde_as;
|
||||
/// # use std::collections::{BTreeMap, HashMap};
|
||||
/// #
|
||||
/// #[serde_as]
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct A {
|
||||
/// #[serde_as(as = "Vec<(_, _)>")]
|
||||
/// hashmap: HashMap<(String, u32), u32>,
|
||||
/// #[serde_as(as = "Vec<(_, _)>")]
|
||||
/// btreemap: BTreeMap<(String, u32), u32>,
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_json::json;
|
||||
/// # use std::collections::BTreeMap;
|
||||
/// #
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct A {
|
||||
/// #[serde(with = "serde_with::rust::map_as_tuple_list")]
|
||||
/// s: BTreeMap<(String, u32), u32>,
|
||||
/// }
|
||||
///
|
||||
/// let v: A = serde_json::from_value(json!({
|
||||
/// "s": [
|
||||
/// [["Hello", 123], 0],
|
||||
/// [["World", 456], 1]
|
||||
/// ]
|
||||
/// })).unwrap();
|
||||
///
|
||||
/// assert_eq!(2, v.s.len());
|
||||
/// assert_eq!(1, v.s[&("World".to_string(), 456)]);
|
||||
/// ```
|
||||
///
|
||||
/// The helper is generic over the hasher type of the [`HashMap`] and works with different variants, such as `FnvHashMap`.
|
||||
///
|
||||
/// ```
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_json::json;
|
||||
/// #
|
||||
/// use fnv::FnvHashMap;
|
||||
///
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// struct A {
|
||||
/// #[serde(with = "serde_with::rust::map_as_tuple_list")]
|
||||
/// s: FnvHashMap<u32, bool>,
|
||||
/// }
|
||||
///
|
||||
/// let v: A = serde_json::from_value(json!({
|
||||
/// "s": [
|
||||
/// [0, false],
|
||||
/// [1, true]
|
||||
/// ]
|
||||
/// })).unwrap();
|
||||
///
|
||||
/// assert_eq!(2, v.s.len());
|
||||
/// assert_eq!(true, v.s[&1]);
|
||||
/// ```
|
||||
///
|
||||
/// [`serde_as`]: crate::guide::serde_as
|
||||
pub mod map_as_tuple_list {
|
||||
// Trait bounds based on this answer: https://stackoverflow.com/a/66600486/15470286
|
||||
use super::*;
|
||||
|
||||
/// Serialize the map as a list of tuples
|
||||
pub fn serialize<'a, T, K, V, S>(map: T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
T: IntoIterator<Item = (&'a K, &'a V)>,
|
||||
T::IntoIter: ExactSizeIterator,
|
||||
K: Serialize + 'a,
|
||||
V: Serialize + 'a,
|
||||
{
|
||||
serializer.collect_seq(map)
|
||||
}
|
||||
|
||||
/// Deserialize a map from a list of tuples
|
||||
pub fn deserialize<'de, T, K, V, D>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: FromIterator<(K, V)>,
|
||||
K: Deserialize<'de>,
|
||||
V: Deserialize<'de>,
|
||||
{
|
||||
struct SeqVisitor<T, K, V>(PhantomData<(T, K, V)>);
|
||||
|
||||
impl<'de, T, K, V> Visitor<'de> for SeqVisitor<T, K, V>
|
||||
where
|
||||
T: FromIterator<(K, V)>,
|
||||
K: Deserialize<'de>,
|
||||
V: Deserialize<'de>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a list of key-value pairs")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
utils::SeqIter::new(seq).collect()
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_seq(SeqVisitor(PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
/// DEPRECATED De/Serialize a [`HashMap`] into a list of tuples
|
||||
///
|
||||
/// Use the [`map_as_tuple_list`] module which is more general than this.
|
||||
/// It should work with everything convertible to and from an `Iterator` including [`BTreeMap`] and [`HashMap`].
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// Some formats, like JSON, have limitations on the type of keys for maps.
|
||||
/// In case of JSON, keys are restricted to strings.
|
||||
|
@ -1098,7 +1244,7 @@ pub mod string_empty_as_none {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_with::serde_as;
|
||||
/// # use std::collections::HashMap;
|
||||
/// #
|
||||
|
@ -1114,7 +1260,7 @@ pub mod string_empty_as_none {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_json::json;
|
||||
/// # use std::collections::HashMap;
|
||||
/// #
|
||||
|
@ -1138,7 +1284,7 @@ pub mod string_empty_as_none {
|
|||
/// The helper is generic over the hasher type of the [`HashMap`] and works with different variants, such as `FnvHashMap`.
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_json::json;
|
||||
/// #
|
||||
/// use fnv::FnvHashMap;
|
||||
|
@ -1161,65 +1307,31 @@ pub mod string_empty_as_none {
|
|||
/// ```
|
||||
///
|
||||
/// [`serde_as`]: crate::guide::serde_as
|
||||
#[deprecated(
|
||||
since = "1.8.0",
|
||||
note = "Use the more general map_as_tuple_list module."
|
||||
)]
|
||||
pub mod hashmap_as_tuple_list {
|
||||
use super::{SerializeSeq, *}; // Needed to remove the unused import warning in the parent scope
|
||||
|
||||
/// Serialize the [`HashMap`] as a list of tuples
|
||||
pub fn serialize<K, V, S, BH>(map: &HashMap<K, V, BH>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
K: Eq + Hash + Serialize,
|
||||
V: Serialize,
|
||||
BH: BuildHasher,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(map.len()))?;
|
||||
for item in map.iter() {
|
||||
seq.serialize_element(&item)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
|
||||
/// Deserialize a [`HashMap`] from a list of tuples
|
||||
pub fn deserialize<'de, K, V, BH, D>(deserializer: D) -> Result<HashMap<K, V, BH>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
K: Eq + Hash + Deserialize<'de>,
|
||||
V: Deserialize<'de>,
|
||||
BH: BuildHasher + Default,
|
||||
{
|
||||
deserializer.deserialize_seq(HashMapVisitor(PhantomData))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
struct HashMapVisitor<K, V, BH>(PhantomData<fn() -> HashMap<K, V, BH>>);
|
||||
|
||||
impl<'de, K, V, BH> Visitor<'de> for HashMapVisitor<K, V, BH>
|
||||
where
|
||||
K: Deserialize<'de> + Eq + Hash,
|
||||
V: Deserialize<'de>,
|
||||
BH: BuildHasher + Default,
|
||||
{
|
||||
type Value = HashMap<K, V, BH>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a list of key-value pairs")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let mut map =
|
||||
HashMap::with_capacity_and_hasher(seq.size_hint().unwrap_or(0), BH::default());
|
||||
while let Some((key, value)) = seq.next_element()? {
|
||||
map.insert(key, value);
|
||||
}
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
#[doc(inline)]
|
||||
#[deprecated(
|
||||
since = "1.8.0",
|
||||
note = "Use the more general map_as_tuple_list::deserialize function."
|
||||
)]
|
||||
pub use super::map_as_tuple_list::deserialize;
|
||||
#[doc(inline)]
|
||||
#[deprecated(
|
||||
since = "1.8.0",
|
||||
note = "Use the more general map_as_tuple_list::serialize function."
|
||||
)]
|
||||
pub use super::map_as_tuple_list::serialize;
|
||||
}
|
||||
|
||||
/// De/Serialize a [`BTreeMap`] into a list of tuples
|
||||
/// DEPRECATED De/Serialize a [`BTreeMap`] into a list of tuples
|
||||
///
|
||||
/// Use the [`map_as_tuple_list`] module which is more general than this.
|
||||
/// It should work with everything convertible to and from an `Iterator` including [`BTreeMap`] and [`HashMap`].
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// Some formats, like JSON, have limitations on the type of keys for maps.
|
||||
/// In case of JSON, keys are restricted to strings.
|
||||
|
@ -1236,7 +1348,7 @@ pub mod hashmap_as_tuple_list {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_with::serde_as;
|
||||
/// # use std::collections::BTreeMap;
|
||||
/// #
|
||||
|
@ -1252,7 +1364,7 @@ pub mod hashmap_as_tuple_list {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_json::json;
|
||||
/// # use std::collections::BTreeMap;
|
||||
/// #
|
||||
|
@ -1274,58 +1386,23 @@ pub mod hashmap_as_tuple_list {
|
|||
/// ```
|
||||
///
|
||||
/// [`serde_as`]: crate::guide::serde_as
|
||||
#[deprecated(
|
||||
since = "1.8.0",
|
||||
note = "Use the more general map_as_tuple_list module."
|
||||
)]
|
||||
pub mod btreemap_as_tuple_list {
|
||||
use super::*;
|
||||
|
||||
/// Serialize the [`BTreeMap`] as a list of tuples
|
||||
pub fn serialize<K, V, S>(map: &BTreeMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
K: Eq + Hash + Serialize,
|
||||
V: Serialize,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(map.len()))?;
|
||||
for item in map.iter() {
|
||||
seq.serialize_element(&item)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
|
||||
/// Deserialize a [`BTreeMap`] from a list of tuples
|
||||
pub fn deserialize<'de, K, V, D>(deserializer: D) -> Result<BTreeMap<K, V>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
K: Deserialize<'de> + Ord,
|
||||
V: Deserialize<'de>,
|
||||
{
|
||||
deserializer.deserialize_seq(BTreeMapVisitor(PhantomData))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
struct BTreeMapVisitor<K, V>(PhantomData<fn() -> BTreeMap<K, V>>);
|
||||
|
||||
impl<'de, K, V> Visitor<'de> for BTreeMapVisitor<K, V>
|
||||
where
|
||||
K: Deserialize<'de> + Ord,
|
||||
V: Deserialize<'de>,
|
||||
{
|
||||
type Value = BTreeMap<K, V>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a list of key-value pairs")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let mut map = BTreeMap::default();
|
||||
while let Some((key, value)) = seq.next_element()? {
|
||||
map.insert(key, value);
|
||||
}
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
#[doc(inline)]
|
||||
#[deprecated(
|
||||
since = "1.8.0",
|
||||
note = "Use the more general map_as_tuple_list::deserialize function."
|
||||
)]
|
||||
pub use super::map_as_tuple_list::deserialize;
|
||||
#[doc(inline)]
|
||||
#[deprecated(
|
||||
since = "1.8.0",
|
||||
note = "Use the more general map_as_tuple_list::serialize function."
|
||||
)]
|
||||
pub use super::map_as_tuple_list::serialize;
|
||||
}
|
||||
|
||||
/// This serializes a list of tuples into a map and back
|
||||
|
@ -1344,7 +1421,7 @@ pub mod btreemap_as_tuple_list {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// # use serde_with::serde_as;
|
||||
/// # use std::collections::BTreeMap;
|
||||
/// #
|
||||
|
@ -1362,7 +1439,7 @@ pub mod btreemap_as_tuple_list {
|
|||
/// `Wrapper` does not implement [`Hash`] nor [`Ord`], thus prohibiting the use [`HashMap`] or [`BTreeMap`].
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// #
|
||||
/// #[derive(Debug, Deserialize, Serialize, Default)]
|
||||
/// struct S {
|
||||
|
@ -1397,7 +1474,7 @@ pub mod btreemap_as_tuple_list {
|
|||
/// In this example, the serialized format contains duplicate keys, which is not supported with [`HashMap`] or [`BTreeMap`].
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// #
|
||||
/// #[derive(Debug, Deserialize, Serialize, PartialEq, Default)]
|
||||
/// struct S {
|
||||
|
@ -1425,7 +1502,7 @@ pub mod btreemap_as_tuple_list {
|
|||
///
|
||||
/// [`serde_as`]: crate::guide::serde_as
|
||||
pub mod tuple_list_as_map {
|
||||
use super::{SerializeMap, *}; // Needed to remove the unused import warning in the parent scope
|
||||
use super::*;
|
||||
|
||||
/// Serialize any iteration of tuples into a map.
|
||||
pub fn serialize<'a, I, K, V, S>(iter: I, serializer: S) -> Result<S::Ok, S::Error>
|
||||
|
@ -1436,12 +1513,9 @@ pub mod tuple_list_as_map {
|
|||
V: Serialize + 'a,
|
||||
S: Serializer,
|
||||
{
|
||||
let iter = iter.into_iter();
|
||||
let mut map = serializer.serialize_map(Some(iter.len()))?;
|
||||
for (key, value) in iter {
|
||||
map.serialize_entry(&key, &value)?;
|
||||
}
|
||||
map.end()
|
||||
// Convert &(K, V) to (&K, &V) for collect_map.
|
||||
let iter = iter.into_iter().map(|(k, v)| (k, v));
|
||||
serializer.collect_map(iter)
|
||||
}
|
||||
|
||||
/// Deserialize a map into an iterator of tuples.
|
||||
|
@ -1474,34 +1548,7 @@ pub mod tuple_list_as_map {
|
|||
where
|
||||
A: MapAccess<'de>,
|
||||
{
|
||||
let iter = MapIter(map, PhantomData);
|
||||
iter.collect()
|
||||
}
|
||||
}
|
||||
|
||||
struct MapIter<'de, A, K, V>(A, PhantomData<(&'de (), A, K, V)>);
|
||||
|
||||
impl<'de, A, K, V> Iterator for MapIter<'de, A, K, V>
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
K: Deserialize<'de>,
|
||||
V: Deserialize<'de>,
|
||||
{
|
||||
type Item = Result<(K, V), A::Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.0.next_entry() {
|
||||
Ok(Some(x)) => Some(Ok(x)),
|
||||
Ok(None) => None,
|
||||
Err(err) => Some(Err(err)),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self.0.size_hint() {
|
||||
Some(size) => (size, Some(size)),
|
||||
None => (0, None),
|
||||
}
|
||||
utils::MapIter::new(map).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1518,7 +1565,7 @@ pub mod tuple_list_as_map {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// # use serde_with::{serde_as, BytesOrString};
|
||||
/// #
|
||||
/// #[serde_as]
|
||||
|
@ -1532,7 +1579,7 @@ pub mod tuple_list_as_map {
|
|||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use serde_derive::{Deserialize, Serialize};
|
||||
/// # use serde::{Deserialize, Serialize};
|
||||
/// #
|
||||
/// #[derive(Debug, Deserialize, Serialize, PartialEq, Default)]
|
||||
/// struct S {
|
||||
|
@ -1576,7 +1623,7 @@ pub mod tuple_list_as_map {
|
|||
pub mod bytes_or_string {
|
||||
use super::*;
|
||||
|
||||
/// Deserialize a [`Vec`]`<u8>` from either bytes or string
|
||||
/// Deserialize a [`Vec<u8>`] from either bytes or string
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
|
@ -1609,15 +1656,11 @@ pub mod bytes_or_string {
|
|||
Ok(v.into_bytes())
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let mut res = Vec::with_capacity(seq.size_hint().unwrap_or(0));
|
||||
while let Some(value) = seq.next_element()? {
|
||||
res.push(value);
|
||||
}
|
||||
Ok(res)
|
||||
utils::SeqIter::new(seq).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1631,11 +1674,11 @@ pub mod bytes_or_string {
|
|||
/// ## Converting to `serde_as`
|
||||
///
|
||||
/// The same functionality can be more clearly expressed via [`DefaultOnError`] and using the [`serde_as`] macro.
|
||||
/// It can be combined with other convertes as shown.
|
||||
/// It can be combined with other converts as shown.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// # use serde_with::{serde_as, DefaultOnError, DisplayFromStr};
|
||||
/// #
|
||||
/// #[serde_as]
|
||||
|
@ -1655,7 +1698,7 @@ pub mod bytes_or_string {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// #
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct A {
|
||||
|
@ -1685,7 +1728,7 @@ pub mod bytes_or_string {
|
|||
/// Deserializing missing values can be supported by adding the `default` field attribute:
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// #
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct B {
|
||||
|
@ -1693,7 +1736,6 @@ pub mod bytes_or_string {
|
|||
/// value: u32,
|
||||
/// }
|
||||
///
|
||||
///
|
||||
/// let b: B = serde_json::from_str(r#"{ }"#).unwrap();
|
||||
/// assert_eq!(0, b.value);
|
||||
/// ```
|
||||
|
@ -1744,7 +1786,7 @@ pub mod default_on_error {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// # use serde_with::{serde_as, DefaultOnNull, DisplayFromStr};
|
||||
/// #
|
||||
/// #[serde_as]
|
||||
|
@ -1764,7 +1806,7 @@ pub mod default_on_error {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use serde_derive::Deserialize;
|
||||
/// # use serde::Deserialize;
|
||||
/// #
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct A {
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
use super::*;
|
||||
use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap};
|
||||
use std::collections::HashMap;
|
||||
|
||||
impl<T, As, const N: usize> SerializeAs<[T; N]> for [As; N]
|
||||
where
|
||||
As: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(array: &[T; N], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
use serde::ser::SerializeTuple;
|
||||
let mut arr = serializer.serialize_tuple(N)?;
|
||||
for elem in array {
|
||||
arr.serialize_element(&SerializeAsWrap::<T, As>::new(elem))?;
|
||||
}
|
||||
arr.end()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! tuple_seq_as_map_impl_intern {
|
||||
($tyorig:ty, $ty:ident <K, V>) => {
|
||||
#[allow(clippy::implicit_hasher)]
|
||||
impl<K, KAs, V, VAs, const N: usize> SerializeAs<$tyorig> for $ty<KAs, VAs>
|
||||
where
|
||||
KAs: SerializeAs<K>,
|
||||
VAs: SerializeAs<V>,
|
||||
{
|
||||
fn serialize_as<S>(source: &$tyorig, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_map(source.iter().map(|(k, v)| {
|
||||
(
|
||||
SerializeAsWrap::<K, KAs>::new(k),
|
||||
SerializeAsWrap::<V, VAs>::new(v),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
tuple_seq_as_map_impl_intern!([(K, V); N], BTreeMap<K, V>);
|
||||
tuple_seq_as_map_impl_intern!([(K, V); N], HashMap<K, V>);
|
||||
|
||||
impl<const N: usize> SerializeAs<[u8; N]> for Bytes {
|
||||
fn serialize_as<S>(bytes: &[u8; N], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> SerializeAs<&[u8; N]> for Bytes {
|
||||
fn serialize_as<S>(bytes: &&[u8; N], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(*bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> SerializeAs<Box<[u8; N]>> for Bytes {
|
||||
fn serialize_as<S>(bytes: &Box<[u8; N]>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(&**bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> SerializeAs<Cow<'a, [u8; N]>> for Bytes {
|
||||
fn serialize_as<S>(bytes: &Cow<'a, [u8; N]>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(bytes.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> SerializeAs<Cow<'a, [u8; N]>> for BorrowCow {
|
||||
fn serialize_as<S>(value: &Cow<'a, [u8; N]>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_seq(value.iter())
|
||||
}
|
||||
}
|
|
@ -1,13 +1,33 @@
|
|||
use utils::duration::DurationSigned;
|
||||
|
||||
use super::*;
|
||||
use crate::{formats::Strictness, rust::StringWithSeparator, Separator};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque},
|
||||
fmt::Display,
|
||||
hash::{BuildHasher, Hash},
|
||||
time::{Duration, SystemTime},
|
||||
use crate::{
|
||||
formats::Strictness, rust::StringWithSeparator, utils::duration::DurationSigned, Separator,
|
||||
};
|
||||
use alloc::{
|
||||
borrow::Cow,
|
||||
boxed::Box,
|
||||
collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque},
|
||||
rc::{Rc, Weak as RcWeak},
|
||||
string::{String, ToString},
|
||||
sync::{Arc, Weak as ArcWeak},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::{
|
||||
cell::{Cell, RefCell},
|
||||
convert::TryInto,
|
||||
fmt::Display,
|
||||
time::Duration,
|
||||
};
|
||||
#[cfg(feature = "indexmap")]
|
||||
use indexmap_crate::{IndexMap, IndexSet};
|
||||
use serde::ser::Error;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{Mutex, RwLock},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// region: Simple Wrapper types (e.g., Box, Option)
|
||||
|
||||
impl<'a, T, U> SerializeAs<&'a T> for &'a U
|
||||
where
|
||||
|
@ -23,6 +43,20 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T, U> SerializeAs<&'a mut T> for &'a mut U
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
T: ?Sized,
|
||||
U: ?Sized,
|
||||
{
|
||||
fn serialize_as<S>(source: &&'a mut T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
SerializeAsWrap::<T, U>::new(source).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<Box<T>> for Box<U>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
|
@ -50,13 +84,142 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<Rc<T>> for Rc<U>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &Rc<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
SerializeAsWrap::<T, U>::new(source).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<RcWeak<T>> for RcWeak<U>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &RcWeak<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
SerializeAsWrap::<Option<Rc<T>>, Option<Rc<U>>>::new(&source.upgrade())
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<Arc<T>> for Arc<U>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &Arc<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
SerializeAsWrap::<T, U>::new(source).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<ArcWeak<T>> for ArcWeak<U>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &ArcWeak<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
SerializeAsWrap::<Option<Arc<T>>, Option<Arc<U>>>::new(&source.upgrade())
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<Cell<T>> for Cell<U>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
T: Copy,
|
||||
{
|
||||
fn serialize_as<S>(source: &Cell<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
SerializeAsWrap::<T, U>::new(&source.get()).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<RefCell<T>> for RefCell<U>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &RefCell<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match source.try_borrow() {
|
||||
Ok(source) => SerializeAsWrap::<T, U>::new(&*source).serialize(serializer),
|
||||
Err(_) => Err(S::Error::custom("already mutably borrowed")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<Mutex<T>> for Mutex<U>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &Mutex<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match source.lock() {
|
||||
Ok(source) => SerializeAsWrap::<T, U>::new(&*source).serialize(serializer),
|
||||
Err(_) => Err(S::Error::custom("lock poison error while serializing")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<RwLock<T>> for RwLock<U>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &RwLock<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match source.read() {
|
||||
Ok(source) => SerializeAsWrap::<T, U>::new(&*source).serialize(serializer),
|
||||
Err(_) => Err(S::Error::custom("lock poison error while serializing")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TAs, E, EAs> SerializeAs<Result<T, E>> for Result<TAs, EAs>
|
||||
where
|
||||
TAs: SerializeAs<T>,
|
||||
EAs: SerializeAs<E>,
|
||||
{
|
||||
fn serialize_as<S>(source: &Result<T, E>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
source
|
||||
.as_ref()
|
||||
.map(SerializeAsWrap::<T, TAs>::new)
|
||||
.map_err(SerializeAsWrap::<E, EAs>::new)
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// region: Collection Types (e.g., Maps, Sets, Vec)
|
||||
|
||||
macro_rules! seq_impl {
|
||||
($ty:ident < T $(: $tbound1:ident $(+ $tbound2:ident)*)* $(, $typaram:ident : $bound:ident $(+ $bound2:ident)*)* >) => {
|
||||
($ty:ident < T $(: $tbound1:ident $(+ $tbound2:ident)*)* $(, $typaram:ident : $bound:ident )* >) => {
|
||||
impl<T, U $(, $typaram)*> SerializeAs<$ty<T $(, $typaram)*>> for $ty<U $(, $typaram)*>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
$(T: ?Sized + $tbound1 $(+ $tbound2)*,)*
|
||||
$($typaram: ?Sized + $bound $(+ $bound2)*,)*
|
||||
$($typaram: ?Sized + $bound,)*
|
||||
{
|
||||
fn serialize_as<S>(source: &$ty<T $(, $typaram)*>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
|
@ -70,14 +233,16 @@ macro_rules! seq_impl {
|
|||
|
||||
type BoxedSlice<T> = Box<[T]>;
|
||||
type Slice<T> = [T];
|
||||
seq_impl!(BinaryHeap<T: Ord + Sized>);
|
||||
seq_impl!(BinaryHeap<T>);
|
||||
seq_impl!(BoxedSlice<T>);
|
||||
seq_impl!(BTreeSet<T: Ord + Sized>);
|
||||
seq_impl!(HashSet<T: Eq + Hash + Sized, H: BuildHasher + Sized>);
|
||||
seq_impl!(BTreeSet<T>);
|
||||
seq_impl!(HashSet<T, H: Sized>);
|
||||
seq_impl!(LinkedList<T>);
|
||||
seq_impl!(Slice<T>);
|
||||
seq_impl!(Vec<T>);
|
||||
seq_impl!(VecDeque<T>);
|
||||
#[cfg(feature = "indexmap")]
|
||||
seq_impl!(IndexSet<T, H: Sized>);
|
||||
|
||||
macro_rules! map_impl {
|
||||
($ty:ident < K $(: $kbound1:ident $(+ $kbound2:ident)*)*, V $(, $typaram:ident : $bound:ident)* >) => {
|
||||
|
@ -85,8 +250,8 @@ macro_rules! map_impl {
|
|||
where
|
||||
KU: SerializeAs<K>,
|
||||
VU: SerializeAs<V>,
|
||||
$(K: $kbound1 $(+ $kbound2)*,)*
|
||||
$($typaram: $bound,)*
|
||||
$(K: ?Sized + $kbound1 $(+ $kbound2)*,)*
|
||||
$($typaram: ?Sized + $bound,)*
|
||||
{
|
||||
fn serialize_as<S>(source: &$ty<K, V $(, $typaram)*>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
|
@ -98,20 +263,10 @@ macro_rules! map_impl {
|
|||
}
|
||||
}
|
||||
|
||||
map_impl!(BTreeMap<K: Ord, V>);
|
||||
map_impl!(HashMap<K: Eq + Hash, V, H: BuildHasher>);
|
||||
|
||||
impl<T> SerializeAs<T> for DisplayFromStr
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
crate::rust::display_fromstr::serialize(source, serializer)
|
||||
}
|
||||
}
|
||||
map_impl!(BTreeMap<K, V>);
|
||||
map_impl!(HashMap<K, V, H: Sized>);
|
||||
#[cfg(feature = "indexmap")]
|
||||
map_impl!(IndexMap<K, V, H: Sized>);
|
||||
|
||||
macro_rules! tuple_impl {
|
||||
($len:literal $($n:tt $t:ident $tas:ident)+) => {
|
||||
|
@ -151,75 +306,8 @@ tuple_impl!(14 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7
|
|||
tuple_impl!(15 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11 12 T12 As12 13 T13 As13 14 T14 As14);
|
||||
tuple_impl!(16 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11 12 T12 As12 13 T13 As13 14 T14 As14 15 T15 As15);
|
||||
|
||||
macro_rules! array_impl {
|
||||
($len:literal) => {
|
||||
impl<T, As> SerializeAs<[T; $len]> for [As; $len]
|
||||
where
|
||||
As: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(array: &[T; $len], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
use serde::ser::SerializeTuple;
|
||||
let mut arr = serializer.serialize_tuple($len)?;
|
||||
for elem in array {
|
||||
arr.serialize_element(&SerializeAsWrap::<T, As>::new(elem))?;
|
||||
}
|
||||
arr.end()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
array_impl!(0);
|
||||
array_impl!(1);
|
||||
array_impl!(2);
|
||||
array_impl!(3);
|
||||
array_impl!(4);
|
||||
array_impl!(5);
|
||||
array_impl!(6);
|
||||
array_impl!(7);
|
||||
array_impl!(8);
|
||||
array_impl!(9);
|
||||
array_impl!(10);
|
||||
array_impl!(11);
|
||||
array_impl!(12);
|
||||
array_impl!(13);
|
||||
array_impl!(14);
|
||||
array_impl!(15);
|
||||
array_impl!(16);
|
||||
array_impl!(17);
|
||||
array_impl!(18);
|
||||
array_impl!(19);
|
||||
array_impl!(20);
|
||||
array_impl!(21);
|
||||
array_impl!(22);
|
||||
array_impl!(23);
|
||||
array_impl!(24);
|
||||
array_impl!(25);
|
||||
array_impl!(26);
|
||||
array_impl!(27);
|
||||
array_impl!(28);
|
||||
array_impl!(29);
|
||||
array_impl!(30);
|
||||
array_impl!(31);
|
||||
array_impl!(32);
|
||||
|
||||
impl<T> SerializeAs<T> for Same
|
||||
where
|
||||
T: Serialize + ?Sized,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
source.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! map_as_tuple_seq {
|
||||
($ty:ident < K $(: $kbound1:ident $(+ $kbound2:ident)*)*, V $(, $typaram:ident : $bound:ident)* >) => {
|
||||
($ty:ident < K $(: $kbound1:ident $(+ $kbound2:ident)*)*, V >) => {
|
||||
impl<K, KAs, V, VAs> SerializeAs<$ty<K, V>> for Vec<(KAs, VAs)>
|
||||
where
|
||||
KAs: SerializeAs<K>,
|
||||
|
@ -239,8 +327,51 @@ macro_rules! map_as_tuple_seq {
|
|||
}
|
||||
};
|
||||
}
|
||||
map_as_tuple_seq!(BTreeMap<K: Ord, V>);
|
||||
map_as_tuple_seq!(HashMap<K: Eq + Hash, V, H: BuildHasher>);
|
||||
map_as_tuple_seq!(BTreeMap<K, V>);
|
||||
// TODO HashMap with a custom hasher support would be better, but results in "unconstrained type parameter"
|
||||
map_as_tuple_seq!(HashMap<K, V>);
|
||||
#[cfg(feature = "indexmap")]
|
||||
map_as_tuple_seq!(IndexMap<K, V>);
|
||||
|
||||
// endregion
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// region: Conversion types which cause different serialization behavior
|
||||
|
||||
impl<T> SerializeAs<T> for Same
|
||||
where
|
||||
T: Serialize + ?Sized,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
source.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SerializeAs<T> for DisplayFromStr
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
crate::rust::display_fromstr::serialize(source, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<Vec<T>> for VecSkipError<U>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &Vec<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
Vec::<U>::serialize_as(source, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<AsRefStr> SerializeAs<Option<AsRefStr>> for NoneAsEmptyString
|
||||
where
|
||||
|
@ -286,19 +417,14 @@ macro_rules! tuple_seq_as_map_impl {
|
|||
tuple_seq_as_map_impl! {
|
||||
BinaryHeap<(K, V)>,
|
||||
BTreeSet<(K, V)>,
|
||||
HashSet<(K, V)>,
|
||||
LinkedList<(K, V)>,
|
||||
Option<(K, V)>,
|
||||
Vec<(K, V)>,
|
||||
VecDeque<(K, V)>,
|
||||
}
|
||||
tuple_seq_as_map_impl! {
|
||||
[(K, V); 0], [(K, V); 1], [(K, V); 2], [(K, V); 3], [(K, V); 4], [(K, V); 5], [(K, V); 6],
|
||||
[(K, V); 7], [(K, V); 8], [(K, V); 9], [(K, V); 10], [(K, V); 11], [(K, V); 12], [(K, V); 13],
|
||||
[(K, V); 14], [(K, V); 15], [(K, V); 16], [(K, V); 17], [(K, V); 18], [(K, V); 19], [(K, V); 20],
|
||||
[(K, V); 21], [(K, V); 22], [(K, V); 23], [(K, V); 24], [(K, V); 25], [(K, V); 26], [(K, V); 27],
|
||||
[(K, V); 28], [(K, V); 29], [(K, V); 30], [(K, V); 31], [(K, V); 32],
|
||||
}
|
||||
tuple_seq_as_map_impl!(HashSet<(K, V)>);
|
||||
#[cfg(feature = "indexmap")]
|
||||
tuple_seq_as_map_impl!(IndexSet<(K, V)>);
|
||||
|
||||
impl<T, TAs> SerializeAs<T> for DefaultOnError<TAs>
|
||||
where
|
||||
|
@ -349,10 +475,10 @@ macro_rules! use_signed_duration {
|
|||
(
|
||||
$main_trait:ident $internal_trait:ident =>
|
||||
{
|
||||
$ty:ty; $converter:ident =>
|
||||
$ty:ty =>
|
||||
$({
|
||||
$format:ty, $strictness:ty =>
|
||||
$($tbound:ident: $bound:ident)*
|
||||
$($tbound:ident: $bound:ident $(,)?)*
|
||||
})*
|
||||
}
|
||||
) => {
|
||||
|
@ -386,7 +512,7 @@ use_signed_duration!(
|
|||
DurationMicroSeconds DurationMicroSeconds,
|
||||
DurationNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
Duration; to_std_duration =>
|
||||
Duration =>
|
||||
{u64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
|
@ -398,7 +524,7 @@ use_signed_duration!(
|
|||
DurationMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
DurationNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
Duration; to_std_duration =>
|
||||
Duration =>
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
|
@ -410,7 +536,7 @@ use_signed_duration!(
|
|||
TimestampMicroSeconds DurationMicroSeconds,
|
||||
TimestampNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
SystemTime; to_system_time =>
|
||||
SystemTime =>
|
||||
{i64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
|
@ -422,7 +548,7 @@ use_signed_duration!(
|
|||
TimestampMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
TimestampNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
SystemTime; to_system_time =>
|
||||
SystemTime =>
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
|
@ -439,3 +565,175 @@ where
|
|||
serializer.serialize_some(&SerializeAsWrap::<T, U>::new(source))
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeAs<&[u8]> for Bytes {
|
||||
fn serialize_as<S>(bytes: &&[u8], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeAs<Vec<u8>> for Bytes {
|
||||
fn serialize_as<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeAs<Box<[u8]>> for Bytes {
|
||||
fn serialize_as<S>(bytes: &Box<[u8]>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SerializeAs<Cow<'a, [u8]>> for Bytes {
|
||||
fn serialize_as<S>(bytes: &Cow<'a, [u8]>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<Vec<T>> for OneOrMany<U, formats::PreferOne>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &Vec<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match source.len() {
|
||||
1 => SerializeAsWrap::<T, U>::new(source.iter().next().expect("Cannot be empty"))
|
||||
.serialize(serializer),
|
||||
_ => SerializeAsWrap::<Vec<T>, Vec<U>>::new(source).serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<Vec<T>> for OneOrMany<U, formats::PreferMany>
|
||||
where
|
||||
U: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &Vec<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
SerializeAsWrap::<Vec<T>, Vec<U>>::new(source).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TAs1> SerializeAs<T> for PickFirst<(TAs1,)>
|
||||
where
|
||||
TAs1: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
SerializeAsWrap::<T, TAs1>::new(source).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TAs1, TAs2> SerializeAs<T> for PickFirst<(TAs1, TAs2)>
|
||||
where
|
||||
TAs1: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
SerializeAsWrap::<T, TAs1>::new(source).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TAs1, TAs2, TAs3> SerializeAs<T> for PickFirst<(TAs1, TAs2, TAs3)>
|
||||
where
|
||||
TAs1: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
SerializeAsWrap::<T, TAs1>::new(source).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TAs1, TAs2, TAs3, TAs4> SerializeAs<T> for PickFirst<(TAs1, TAs2, TAs3, TAs4)>
|
||||
where
|
||||
TAs1: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
SerializeAsWrap::<T, TAs1>::new(source).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<T> for FromInto<U>
|
||||
where
|
||||
T: Into<U> + Clone,
|
||||
U: Serialize,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
source.clone().into().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> SerializeAs<T> for TryFromInto<U>
|
||||
where
|
||||
T: TryInto<U> + Clone,
|
||||
<T as TryInto<U>>::Error: Display,
|
||||
U: Serialize,
|
||||
{
|
||||
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
source
|
||||
.clone()
|
||||
.try_into()
|
||||
.map_err(S::Error::custom)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SerializeAs<Cow<'a, str>> for BorrowCow {
|
||||
fn serialize_as<S>(source: &Cow<'a, str>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(source)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SerializeAs<Cow<'a, [u8]>> for BorrowCow {
|
||||
fn serialize_as<S>(value: &Cow<'a, [u8]>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_seq(value.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl<STRICTNESS: Strictness> SerializeAs<bool> for BoolFromInt<STRICTNESS> {
|
||||
fn serialize_as<S>(source: &bool, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u8(*source as u8)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
use super::*;
|
||||
use alloc::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
macro_rules! array_impl {
|
||||
($($len:literal)+) => {$(
|
||||
impl<T, As> SerializeAs<[T; $len]> for [As; $len]
|
||||
where
|
||||
As: SerializeAs<T>,
|
||||
{
|
||||
fn serialize_as<S>(array: &[T; $len], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
use serde::ser::SerializeTuple;
|
||||
let mut arr = serializer.serialize_tuple($len)?;
|
||||
for elem in array {
|
||||
arr.serialize_element(&SerializeAsWrap::<T, As>::new(elem))?;
|
||||
}
|
||||
arr.end()
|
||||
}
|
||||
}
|
||||
)+};
|
||||
}
|
||||
|
||||
array_impl!(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 32);
|
||||
|
||||
tuple_seq_as_map_impl! {
|
||||
[(K, V); 0], [(K, V); 1], [(K, V); 2], [(K, V); 3], [(K, V); 4], [(K, V); 5], [(K, V); 6],
|
||||
[(K, V); 7], [(K, V); 8], [(K, V); 9], [(K, V); 10], [(K, V); 11], [(K, V); 12], [(K, V); 13],
|
||||
[(K, V); 14], [(K, V); 15], [(K, V); 16], [(K, V); 17], [(K, V); 18], [(K, V); 19], [(K, V); 20],
|
||||
[(K, V); 21], [(K, V); 22], [(K, V); 23], [(K, V); 24], [(K, V); 25], [(K, V); 26], [(K, V); 27],
|
||||
[(K, V); 28], [(K, V); 29], [(K, V); 30], [(K, V); 31], [(K, V); 32],
|
||||
}
|
|
@ -7,6 +7,8 @@
|
|||
//!
|
||||
//! [user guide]: crate::guide
|
||||
|
||||
mod const_arrays;
|
||||
#[macro_use]
|
||||
mod impls;
|
||||
|
||||
use super::*;
|
||||
|
@ -20,7 +22,7 @@ use super::*;
|
|||
/// # Differences to [`Serialize`]
|
||||
///
|
||||
/// The trait is only required for container-like types or types implementing specific conversion functions.
|
||||
/// Container-like types are [`Vec`][], [`BTreeMap`][], but also [`Option`][] and [`Box`][].
|
||||
/// Container-like types are [`Vec`], [`BTreeMap`], but also [`Option`] and [`Box`].
|
||||
/// Conversion types serialize into a different serde data type.
|
||||
/// For example, [`DisplayFromStr`] uses the [`Display`] trait to serialize a String and [`DurationSeconds`] converts a [`Duration`] into either String or integer values.
|
||||
///
|
||||
|
@ -84,7 +86,7 @@ use super::*;
|
|||
/// where
|
||||
/// S: serde::Serializer,
|
||||
/// {
|
||||
/// serializer.serialize_str(&source.to_string())
|
||||
/// serializer.collect_str(&source)
|
||||
/// }
|
||||
/// }
|
||||
/// #
|
||||
|
@ -96,9 +98,11 @@ use super::*;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`Box`]: std::boxed::Box
|
||||
/// [`BTreeMap`]: std::collections::BTreeMap
|
||||
/// [`Display`]: std::fmt::Display
|
||||
/// [`Duration`]: std::time::Duration
|
||||
/// [`Vec`]: std::vec::Vec
|
||||
/// [impl-serialize]: https://serde.rs/impl-serialize.html
|
||||
pub trait SerializeAs<T: ?Sized> {
|
||||
/// Serialize this value into the given Serde serializer.
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
/// Create new conversion adapters from functions
|
||||
///
|
||||
/// The macro lets you create a new converter, which is usable for serde's with-attribute and `#[serde_as]`.
|
||||
/// Its main use case is to write simple converters for types, which are not serializable.
|
||||
/// Another use-case is to change the serialization behavior if the implemented `Serialize`/`Deserialize` trait is insufficient.
|
||||
///
|
||||
/// The macro takes four arguments:
|
||||
///
|
||||
/// 1. The name of the converter type.
|
||||
/// The type can be prefixed with a visibility modifies like `pub` or `pub(crate)`.
|
||||
/// By default, the type is not marked as public (`pub(self)`).
|
||||
/// 2. The type `T` we want to extend with custom behavior.
|
||||
/// 3. A function or macro taking a `&T` and returning a serializable type.
|
||||
/// 4. A function or macro taking a deserializable type and returning a `Result<T, E>`.
|
||||
/// The error type `E` must implement `Display`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// In this example, we write custom serialization behavior for a `Rgb` type.
|
||||
/// We want to serialize it as a `[u8; 3]`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "macros")] {
|
||||
/// # use serde::{Serialize, Deserialize};
|
||||
///
|
||||
/// #[derive(Clone, Copy, Debug, PartialEq)]
|
||||
/// struct Rgb {
|
||||
/// red: u8,
|
||||
/// green: u8,
|
||||
/// blue: u8,
|
||||
/// }
|
||||
///
|
||||
/// serde_with::serde_conv!(
|
||||
/// RgbAsArray,
|
||||
/// Rgb,
|
||||
/// |rgb: &Rgb| [rgb.red, rgb.green, rgb.blue],
|
||||
/// |value: [u8; 3]| -> Result<_, std::convert::Infallible> {
|
||||
/// Ok(Rgb {
|
||||
/// red: value[0],
|
||||
/// green: value[1],
|
||||
/// blue: value[2],
|
||||
/// })
|
||||
/// }
|
||||
/// );
|
||||
///
|
||||
/// //////////////////////////////////////////////////
|
||||
///
|
||||
/// // We define some colors to be used later
|
||||
///
|
||||
/// let green = Rgb {red: 0, green: 255, blue: 0};
|
||||
/// let orange = Rgb {red: 255, green: 128, blue: 0};
|
||||
/// let pink = Rgb {red: 255, green: 0, blue: 255};
|
||||
///
|
||||
/// //////////////////////////////////////////////////
|
||||
///
|
||||
/// // We can now use the `RgbAsArray` adapter with `serde_as`.
|
||||
///
|
||||
/// #[serde_with::serde_as]
|
||||
/// #[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
/// struct Colors {
|
||||
/// #[serde_as(as = "RgbAsArray")]
|
||||
/// one_rgb: Rgb,
|
||||
/// #[serde_as(as = "Vec<RgbAsArray>")]
|
||||
/// rgbs_in_vec: Vec<Rgb>,
|
||||
/// }
|
||||
///
|
||||
/// let data = Colors {
|
||||
/// one_rgb: orange,
|
||||
/// rgbs_in_vec: vec![green, pink],
|
||||
/// };
|
||||
/// let json = serde_json::json!({
|
||||
/// "one_rgb": [255, 128, 0],
|
||||
/// "rgbs_in_vec": [
|
||||
/// [0, 255, 0],
|
||||
/// [255, 0, 255]
|
||||
/// ]
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(json, serde_json::to_value(&data).unwrap());
|
||||
/// assert_eq!(data, serde_json::from_value(json).unwrap());
|
||||
///
|
||||
/// //////////////////////////////////////////////////
|
||||
///
|
||||
/// // The types generated by `serde_conv` is also compatible with serde's with attribute
|
||||
///
|
||||
/// #[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
/// struct ColorsWith {
|
||||
/// #[serde(with = "RgbAsArray")]
|
||||
/// rgb_with: Rgb,
|
||||
/// }
|
||||
///
|
||||
/// let data = ColorsWith {
|
||||
/// rgb_with: pink,
|
||||
/// };
|
||||
/// let json = serde_json::json!({
|
||||
/// "rgb_with": [255, 0, 255]
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(json, serde_json::to_value(&data).unwrap());
|
||||
/// assert_eq!(data, serde_json::from_value(json).unwrap());
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! serde_conv {
|
||||
($m:ident, $t:ty, $ser:expr, $de:expr) => {$crate::serde_conv!(pub(self) $m, $t, $ser, $de);};
|
||||
($vis:vis $m:ident, $t:ty, $ser:expr, $de:expr) => {
|
||||
#[allow(non_camel_case_types)]
|
||||
$vis struct $m;
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
impl $m {
|
||||
$vis fn serialize<S>(x: &$t, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: $crate::serde::Serializer,
|
||||
{
|
||||
let y = $ser(x);
|
||||
$crate::serde::Serialize::serialize(&y, serializer)
|
||||
}
|
||||
|
||||
$vis fn deserialize<'de, D>(deserializer: D) -> ::std::result::Result<$t, D::Error>
|
||||
where
|
||||
D: $crate::serde::Deserializer<'de>,
|
||||
{
|
||||
let y = $crate::serde::Deserialize::deserialize(deserializer)?;
|
||||
$de(y).map_err($crate::serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::SerializeAs<$t> for $m {
|
||||
fn serialize_as<S>(x: &$t, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: $crate::serde::Serializer,
|
||||
{
|
||||
Self::serialize(x, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> $crate::DeserializeAs<'de, $t> for $m {
|
||||
fn deserialize_as<D>(deserializer: D) -> ::std::result::Result<$t, D::Error>
|
||||
where
|
||||
D: $crate::serde::Deserializer<'de>,
|
||||
{
|
||||
Self::deserialize(deserializer)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,382 @@
|
|||
//! De/Serialization of [time v0.3][time] types
|
||||
//!
|
||||
//! This modules is only available if using the `time_0_3` feature of the crate.
|
||||
//!
|
||||
//! [time]: https://docs.rs/time/0.3/
|
||||
|
||||
use crate::{
|
||||
de::DeserializeAs,
|
||||
formats::{Flexible, Format, Strict, Strictness},
|
||||
ser::SerializeAs,
|
||||
utils::duration::{DurationSigned, Sign},
|
||||
DurationMicroSeconds, DurationMicroSecondsWithFrac, DurationMilliSeconds,
|
||||
DurationMilliSecondsWithFrac, DurationNanoSeconds, DurationNanoSecondsWithFrac,
|
||||
DurationSeconds, DurationSecondsWithFrac, TimestampMicroSeconds, TimestampMicroSecondsWithFrac,
|
||||
TimestampMilliSeconds, TimestampMilliSecondsWithFrac, TimestampNanoSeconds,
|
||||
TimestampNanoSecondsWithFrac, TimestampSeconds, TimestampSecondsWithFrac,
|
||||
};
|
||||
use alloc::{format, string::String};
|
||||
use serde::{de, ser::Error as _, Deserializer, Serialize, Serializer};
|
||||
use std::{convert::TryInto, fmt, time::Duration as StdDuration};
|
||||
use time_0_3::{
|
||||
format_description::well_known::{Rfc2822, Rfc3339},
|
||||
Duration, OffsetDateTime, PrimitiveDateTime,
|
||||
};
|
||||
|
||||
/// 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(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert a [`time::Duration`][time_0_3::Duration] into a [`DurationSigned`]
|
||||
fn duration_into_duration_signed(dur: &Duration) -> DurationSigned {
|
||||
let std_dur = StdDuration::new(
|
||||
dur.whole_seconds().unsigned_abs(),
|
||||
dur.subsec_nanoseconds().unsigned_abs(),
|
||||
);
|
||||
|
||||
DurationSigned::with_duration(
|
||||
// A duration of 0 is not positive, so check for negative value.
|
||||
if dur.is_negative() {
|
||||
Sign::Negative
|
||||
} else {
|
||||
Sign::Positive
|
||||
},
|
||||
std_dur,
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert a [`DurationSigned`] into a [`time_0_3::Duration`]
|
||||
fn duration_from_duration_signed<'de, D>(sdur: DurationSigned) -> Result<Duration, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let mut dur: Duration = match sdur.duration.try_into() {
|
||||
Ok(dur) => dur,
|
||||
Err(msg) => {
|
||||
return Err(de::Error::custom(format!(
|
||||
"Duration is outside of the representable range: {}",
|
||||
msg
|
||||
)))
|
||||
}
|
||||
};
|
||||
if sdur.sign.is_negative() {
|
||||
dur = -dur;
|
||||
}
|
||||
Ok(dur)
|
||||
}
|
||||
|
||||
macro_rules! use_duration_signed_ser {
|
||||
(
|
||||
$main_trait:ident $internal_trait:ident =>
|
||||
{
|
||||
$ty:ty; $converter:ident =>
|
||||
$({
|
||||
$format:ty, $strictness:ty =>
|
||||
$($tbound:ident: $bound:ident $(,)?)*
|
||||
})*
|
||||
}
|
||||
) => {
|
||||
$(
|
||||
impl<$($tbound ,)*> SerializeAs<$ty> for $main_trait<$format, $strictness>
|
||||
where
|
||||
$($tbound: $bound,)*
|
||||
{
|
||||
fn serialize_as<S>(source: &$ty, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let dur: DurationSigned = $converter(source);
|
||||
$internal_trait::<$format, $strictness>::serialize_as(
|
||||
&dur,
|
||||
serializer,
|
||||
)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
(
|
||||
$( $main_trait:ident $internal_trait:ident, )+ => $rest:tt
|
||||
) => {
|
||||
$( use_duration_signed_ser!($main_trait $internal_trait => $rest); )+
|
||||
};
|
||||
}
|
||||
|
||||
fn offset_datetime_to_duration(source: &OffsetDateTime) -> DurationSigned {
|
||||
duration_into_duration_signed(&(*source - OffsetDateTime::UNIX_EPOCH))
|
||||
}
|
||||
|
||||
fn primitive_datetime_to_duration(source: &PrimitiveDateTime) -> DurationSigned {
|
||||
duration_into_duration_signed(&(*source - unix_epoch_primitive()))
|
||||
}
|
||||
|
||||
use_duration_signed_ser!(
|
||||
DurationSeconds DurationSeconds,
|
||||
DurationMilliSeconds DurationMilliSeconds,
|
||||
DurationMicroSeconds DurationMicroSeconds,
|
||||
DurationNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
Duration; duration_into_duration_signed =>
|
||||
{i64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
use_duration_signed_ser!(
|
||||
TimestampSeconds DurationSeconds,
|
||||
TimestampMilliSeconds DurationMilliSeconds,
|
||||
TimestampMicroSeconds DurationMicroSeconds,
|
||||
TimestampNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
OffsetDateTime; offset_datetime_to_duration =>
|
||||
{i64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
use_duration_signed_ser!(
|
||||
TimestampSeconds DurationSeconds,
|
||||
TimestampMilliSeconds DurationMilliSeconds,
|
||||
TimestampMicroSeconds DurationMicroSeconds,
|
||||
TimestampNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
PrimitiveDateTime; primitive_datetime_to_duration =>
|
||||
{i64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
|
||||
// Duration/Timestamp WITH FRACTIONS
|
||||
use_duration_signed_ser!(
|
||||
DurationSecondsWithFrac DurationSecondsWithFrac,
|
||||
DurationMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
DurationMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
DurationNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
Duration; duration_into_duration_signed =>
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
use_duration_signed_ser!(
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac,
|
||||
TimestampMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
TimestampMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
TimestampNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
OffsetDateTime; offset_datetime_to_duration =>
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
use_duration_signed_ser!(
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac,
|
||||
TimestampMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
TimestampMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
TimestampNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
PrimitiveDateTime; primitive_datetime_to_duration =>
|
||||
{f64, STRICTNESS => STRICTNESS: Strictness}
|
||||
{String, STRICTNESS => STRICTNESS: Strictness}
|
||||
}
|
||||
);
|
||||
|
||||
macro_rules! use_duration_signed_de {
|
||||
(
|
||||
$main_trait:ident $internal_trait:ident =>
|
||||
{
|
||||
$ty:ty; $converter:ident =>
|
||||
$({
|
||||
$format:ty, $strictness:ty =>
|
||||
$($tbound:ident: $bound:ident)*
|
||||
})*
|
||||
}
|
||||
) =>{
|
||||
$(
|
||||
impl<'de, $($tbound,)*> DeserializeAs<'de, $ty> for $main_trait<$format, $strictness>
|
||||
where
|
||||
$($tbound: $bound,)*
|
||||
{
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<$ty, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let dur: DurationSigned = $internal_trait::<$format, $strictness>::deserialize_as(deserializer)?;
|
||||
$converter::<D>(dur)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
(
|
||||
$( $main_trait:ident $internal_trait:ident, )+ => $rest:tt
|
||||
) => {
|
||||
$( use_duration_signed_de!($main_trait $internal_trait => $rest); )+
|
||||
};
|
||||
}
|
||||
|
||||
fn duration_to_offset_datetime<'de, D>(dur: DurationSigned) -> Result<OffsetDateTime, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(OffsetDateTime::UNIX_EPOCH + duration_from_duration_signed::<D>(dur)?)
|
||||
}
|
||||
|
||||
fn duration_to_primitive_datetime<'de, D>(
|
||||
dur: DurationSigned,
|
||||
) -> Result<PrimitiveDateTime, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(unix_epoch_primitive() + duration_from_duration_signed::<D>(dur)?)
|
||||
}
|
||||
|
||||
// No subsecond precision
|
||||
use_duration_signed_de!(
|
||||
DurationSeconds DurationSeconds,
|
||||
DurationMilliSeconds DurationMilliSeconds,
|
||||
DurationMicroSeconds DurationMicroSeconds,
|
||||
DurationNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
Duration; duration_from_duration_signed =>
|
||||
{i64, Strict =>}
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
use_duration_signed_de!(
|
||||
TimestampSeconds DurationSeconds,
|
||||
TimestampMilliSeconds DurationMilliSeconds,
|
||||
TimestampMicroSeconds DurationMicroSeconds,
|
||||
TimestampNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
OffsetDateTime; duration_to_offset_datetime =>
|
||||
{i64, Strict =>}
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
use_duration_signed_de!(
|
||||
TimestampSeconds DurationSeconds,
|
||||
TimestampMilliSeconds DurationMilliSeconds,
|
||||
TimestampMicroSeconds DurationMicroSeconds,
|
||||
TimestampNanoSeconds DurationNanoSeconds,
|
||||
=> {
|
||||
PrimitiveDateTime; duration_to_primitive_datetime =>
|
||||
{i64, Strict =>}
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
|
||||
// Duration/Timestamp WITH FRACTIONS
|
||||
use_duration_signed_de!(
|
||||
DurationSecondsWithFrac DurationSecondsWithFrac,
|
||||
DurationMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
DurationMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
DurationNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
Duration; duration_from_duration_signed =>
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
use_duration_signed_de!(
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac,
|
||||
TimestampMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
TimestampMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
TimestampNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
OffsetDateTime; duration_to_offset_datetime =>
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
use_duration_signed_de!(
|
||||
TimestampSecondsWithFrac DurationSecondsWithFrac,
|
||||
TimestampMilliSecondsWithFrac DurationMilliSecondsWithFrac,
|
||||
TimestampMicroSecondsWithFrac DurationMicroSecondsWithFrac,
|
||||
TimestampNanoSecondsWithFrac DurationNanoSecondsWithFrac,
|
||||
=> {
|
||||
PrimitiveDateTime; duration_to_primitive_datetime =>
|
||||
{f64, Strict =>}
|
||||
{String, Strict =>}
|
||||
{FORMAT, Flexible => FORMAT: Format}
|
||||
}
|
||||
);
|
||||
|
||||
impl SerializeAs<OffsetDateTime> for Rfc2822 {
|
||||
fn serialize_as<S>(datetime: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
datetime
|
||||
.format(&Rfc2822)
|
||||
.map_err(S::Error::custom)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeAs<'de, OffsetDateTime> for Rfc2822 {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct Visitor;
|
||||
impl<'de> de::Visitor<'de> for Visitor {
|
||||
type Value = OffsetDateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a RFC2822-formatted `OffsetDateTime`")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
Self::Value::parse(value, &Rfc2822).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeAs<OffsetDateTime> for Rfc3339 {
|
||||
fn serialize_as<S>(datetime: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
datetime
|
||||
.format(&Rfc3339)
|
||||
.map_err(S::Error::custom)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeAs<'de, OffsetDateTime> for Rfc3339 {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct Visitor;
|
||||
impl<'de> de::Visitor<'de> for Visitor {
|
||||
type Value = OffsetDateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a RFC3339-formatted `OffsetDateTime`")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
Self::Value::parse(value, &Rfc3339).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(Visitor)
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
pub(crate) mod duration;
|
||||
|
||||
use crate::de::{DeserializeAs, DeserializeAsWrap};
|
||||
use serde::de::{MapAccess, SeqAccess};
|
||||
use std::marker::PhantomData;
|
||||
use alloc::string::String;
|
||||
use core::marker::PhantomData;
|
||||
use serde::de::{Deserialize, MapAccess, SeqAccess};
|
||||
|
||||
/// Re-Implementation of `serde::private::de::size_hint::cautious`
|
||||
#[inline]
|
||||
pub(crate) fn size_hint_cautious(hint: Option<usize>) -> usize {
|
||||
std::cmp::min(hint.unwrap_or(0), 4096)
|
||||
core::cmp::min(hint.unwrap_or(0), 4096)
|
||||
}
|
||||
|
||||
pub(crate) const NANOS_PER_SEC: u32 = 1_000_000_000;
|
||||
|
@ -16,12 +16,12 @@ pub(crate) const NANOS_PER_SEC: u32 = 1_000_000_000;
|
|||
// pub(crate) const MILLIS_PER_SEC: u64 = 1_000;
|
||||
// pub(crate) const MICROS_PER_SEC: u64 = 1_000_000;
|
||||
|
||||
pub(crate) struct MapIter<'de, A, K, KAs, V, VAs> {
|
||||
pub(crate) struct MapIter<'de, A, K, V> {
|
||||
pub(crate) access: A,
|
||||
marker: PhantomData<(&'de (), K, KAs, V, VAs)>,
|
||||
marker: PhantomData<(&'de (), K, V)>,
|
||||
}
|
||||
|
||||
impl<'de, A, K, KAs, V, VAs> MapIter<'de, A, K, KAs, V, VAs> {
|
||||
impl<'de, A, K, V> MapIter<'de, A, K, V> {
|
||||
pub(crate) fn new(access: A) -> Self
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
|
@ -33,14 +33,13 @@ impl<'de, A, K, KAs, V, VAs> MapIter<'de, A, K, KAs, V, VAs> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'de, A, K, KAs, V, VAs> Iterator for MapIter<'de, A, K, KAs, V, VAs>
|
||||
impl<'de, A, K, V> Iterator for MapIter<'de, A, K, V>
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
KAs: DeserializeAs<'de, K>,
|
||||
VAs: DeserializeAs<'de, V>,
|
||||
K: Deserialize<'de>,
|
||||
V: Deserialize<'de>,
|
||||
{
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Item = Result<(DeserializeAsWrap<K, KAs>, DeserializeAsWrap<V, VAs>), A::Error>;
|
||||
type Item = Result<(K, V), A::Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.access.next_entry().transpose()
|
||||
|
@ -54,12 +53,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SeqIter<'de, A, K, KAs, V, VAs> {
|
||||
pub(crate) struct SeqIter<'de, A, T> {
|
||||
access: A,
|
||||
marker: PhantomData<(&'de (), K, KAs, V, VAs)>,
|
||||
marker: PhantomData<(&'de (), T)>,
|
||||
}
|
||||
|
||||
impl<'de, A, K, KAs, V, VAs> SeqIter<'de, A, K, KAs, V, VAs> {
|
||||
impl<'de, A, T> SeqIter<'de, A, T> {
|
||||
pub(crate) fn new(access: A) -> Self
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
|
@ -71,14 +70,12 @@ impl<'de, A, K, KAs, V, VAs> SeqIter<'de, A, K, KAs, V, VAs> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'de, A, K, KAs, V, VAs> Iterator for SeqIter<'de, A, K, KAs, V, VAs>
|
||||
impl<'de, A, T> Iterator for SeqIter<'de, A, T>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
KAs: DeserializeAs<'de, K>,
|
||||
VAs: DeserializeAs<'de, V>,
|
||||
T: Deserialize<'de>,
|
||||
{
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Item = Result<(DeserializeAsWrap<K, KAs>, DeserializeAsWrap<V, VAs>), A::Error>;
|
||||
type Item = Result<T, A::Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.access.next_element().transpose()
|
||||
|
@ -92,7 +89,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn duration_as_secs_f64(dur: &std::time::Duration) -> f64 {
|
||||
pub(crate) fn duration_as_secs_f64(dur: &core::time::Duration) -> f64 {
|
||||
(dur.as_secs() as f64) + (dur.subsec_nanos() as f64) / (NANOS_PER_SEC as f64)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,15 +6,17 @@ use crate::{
|
|||
DurationMilliSecondsWithFrac, DurationNanoSeconds, DurationNanoSecondsWithFrac,
|
||||
DurationSeconds, DurationSecondsWithFrac, SerializeAs,
|
||||
};
|
||||
use alloc::{
|
||||
format,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::{fmt, ops::Neg, time::Duration};
|
||||
use serde::{
|
||||
de::{self, Unexpected, Visitor},
|
||||
ser, Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use std::{
|
||||
fmt,
|
||||
ops::Neg,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use std::time::SystemTime;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum Sign {
|
||||
|
@ -58,12 +60,12 @@ impl DurationSigned {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg(any(feature = "chrono", feature = "time_0_3"))]
|
||||
pub(crate) fn with_duration(sign: Sign, duration: Duration) -> Self {
|
||||
Self { sign, duration }
|
||||
}
|
||||
|
||||
pub(crate) fn to_system_time<'de, D>(&self) -> Result<SystemTime, D::Error>
|
||||
pub(crate) fn to_system_time<'de, D>(self) -> Result<SystemTime, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
|
@ -76,7 +78,7 @@ impl DurationSigned {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn to_std_duration<'de, D>(&self) -> Result<Duration, D::Error>
|
||||
pub(crate) fn to_std_duration<'de, D>(self) -> Result<Duration, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
|
@ -111,7 +113,7 @@ impl From<&SystemTime> for DurationSigned {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<u32> for DurationSigned {
|
||||
impl core::ops::Mul<u32> for DurationSigned {
|
||||
type Output = DurationSigned;
|
||||
|
||||
fn mul(mut self, rhs: u32) -> Self::Output {
|
||||
|
@ -120,7 +122,7 @@ impl std::ops::Mul<u32> for DurationSigned {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<u32> for DurationSigned {
|
||||
impl core::ops::Div<u32> for DurationSigned {
|
||||
type Output = DurationSigned;
|
||||
|
||||
fn div(mut self, rhs: u32) -> Self::Output {
|
||||
|
@ -306,7 +308,7 @@ struct DurationVisitorFlexible;
|
|||
impl<'de> Visitor<'de> for DurationVisitorFlexible {
|
||||
type Value = DurationSigned;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("an integer, a float, or a string containing a number")
|
||||
}
|
||||
|
||||
|
@ -493,14 +495,14 @@ fn parse_float_into_time_parts(mut value: &str) -> Result<(Sign, u64, u32), Pars
|
|||
let parts: Vec<_> = value.split('.').collect();
|
||||
match *parts.as_slice() {
|
||||
[seconds] => {
|
||||
if let Ok(seconds) = u64::from_str_radix(seconds, 10) {
|
||||
if let Ok(seconds) = seconds.parse() {
|
||||
Ok((sign, seconds, 0))
|
||||
} else {
|
||||
Err(ParseFloatError::InvalidValue)
|
||||
}
|
||||
}
|
||||
[seconds, subseconds] => {
|
||||
if let Ok(seconds) = u64::from_str_radix(seconds, 10) {
|
||||
if let Ok(seconds) = seconds.parse() {
|
||||
let subseclen = subseconds.chars().count() as u32;
|
||||
if subseclen > 9 {
|
||||
return Err(ParseFloatError::Custom(format!(
|
||||
|
@ -509,7 +511,7 @@ fn parse_float_into_time_parts(mut value: &str) -> Result<(Sign, u64, u32), Pars
|
|||
)));
|
||||
}
|
||||
|
||||
if let Ok(mut subseconds) = u32::from_str_radix(subseconds, 10) {
|
||||
if let Ok(mut subseconds) = subseconds.parse() {
|
||||
// convert subseconds to nanoseconds (10^-9), require 9 places for nanoseconds
|
||||
subseconds *= 10u32.pow(9 - subseclen);
|
||||
Ok((sign, seconds, subseconds))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::fmt;
|
||||
|
||||
use alloc::string::String;
|
||||
use core::fmt;
|
||||
use serde::{
|
||||
de::{self, DeserializeSeed, Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor},
|
||||
forward_to_deserialize_any,
|
||||
|
@ -9,6 +9,10 @@ use serde::{
|
|||
/// Serialize with an added prefix on every field name and deserialize by
|
||||
/// trimming away the prefix.
|
||||
///
|
||||
/// You can set the visibility of the generated module by prefixing the module name with a module visibility.
|
||||
/// `with_prefix!(pub(crate) prefix_foo "foo_");` creates a module with `pub(crate)` visibility.
|
||||
/// The visibility is optional and by default `pub(self)`, i.e., private visibility is assumed.
|
||||
///
|
||||
/// **Note:** Use of this macro is incompatible with applying the [`deny_unknown_fields`] attribute
|
||||
/// on the container.
|
||||
/// While deserializing, it will always warn about unknown fields, even though they are processed
|
||||
|
@ -31,7 +35,7 @@ use serde::{
|
|||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// In Rust we would ideally like to model this data as a pair of `Player`
|
||||
/// In Rust, we would ideally like to model this data as a pair of `Player`
|
||||
/// structs, rather than repeating the fields of `Player` for each prefix.
|
||||
///
|
||||
/// ```rust
|
||||
|
@ -53,7 +57,7 @@ use serde::{
|
|||
/// An implementation of the Challonge API would use `with_prefix!` like this:
|
||||
///
|
||||
/// ```rust
|
||||
/// use serde_derive::{Deserialize, Serialize};
|
||||
/// use serde::{Deserialize, Serialize};
|
||||
/// use serde_with::with_prefix;
|
||||
///
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
|
@ -71,7 +75,8 @@ use serde::{
|
|||
/// }
|
||||
///
|
||||
/// with_prefix!(prefix_player1 "player1_");
|
||||
/// with_prefix!(prefix_player2 "player2_");
|
||||
/// // You can also set the visibility of the generated prefix module, the default is private.
|
||||
/// with_prefix!(pub prefix_player2 "player2_");
|
||||
/// #
|
||||
/// # const EXPECTED: &str = r#"{
|
||||
/// # "player1_name": "name1",
|
||||
|
@ -103,15 +108,14 @@ use serde::{
|
|||
/// [issue-with_prefix-deny_unknown_fields]: https://github.com/jonasbb/serde_with/issues/57
|
||||
#[macro_export]
|
||||
macro_rules! with_prefix {
|
||||
($module:ident $prefix:expr) => {
|
||||
mod $module {
|
||||
use $crate::{
|
||||
serde::{Deserialize, Deserializer, Serialize, Serializer},
|
||||
with_prefix::WithPrefix,
|
||||
};
|
||||
($module:ident $prefix:expr) => {$crate::with_prefix!(pub(self) $module $prefix);};
|
||||
($vis:vis $module:ident $prefix:expr) => {
|
||||
$vis mod $module {
|
||||
use $crate::serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use $crate::with_prefix::WithPrefix;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn serialize<T, S>(object: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
pub fn serialize<T, S>(object: &T, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
S: Serializer,
|
||||
|
@ -123,7 +127,7 @@ macro_rules! with_prefix {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||
pub fn deserialize<'de, T, D>(deserializer: D) -> ::std::result::Result<T, D::Error>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
D: Deserializer<'de>,
|
||||
|
@ -489,16 +493,15 @@ where
|
|||
{
|
||||
type Error = A::Error;
|
||||
|
||||
// Use `strip_prefix` with Rust 1.45
|
||||
#[allow(clippy::manual_strip)]
|
||||
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
|
||||
where
|
||||
K: DeserializeSeed<'de>,
|
||||
{
|
||||
while let Some(s) = self.delegate.next_key::<String>()? {
|
||||
if s.starts_with(self.prefix) {
|
||||
let without_prefix = s[self.prefix.len()..].into_deserializer();
|
||||
return seed.deserialize(without_prefix).map(Some);
|
||||
if let Some(without_prefix) = s.strip_prefix(self.prefix) {
|
||||
return seed
|
||||
.deserialize(without_prefix.into_deserializer())
|
||||
.map(Some);
|
||||
}
|
||||
self.delegate.next_value::<IgnoredAny>()?;
|
||||
}
|
||||
|
@ -581,8 +584,6 @@ where
|
|||
{
|
||||
type Error = A::Error;
|
||||
|
||||
// Use `strip_prefix` with Rust 1.45
|
||||
#[allow(clippy::manual_strip)]
|
||||
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
|
||||
where
|
||||
K: DeserializeSeed<'de>,
|
||||
|
@ -592,9 +593,10 @@ where
|
|||
return seed.deserialize(without_prefix).map(Some);
|
||||
}
|
||||
while let Some(s) = self.delegate.next_key::<String>()? {
|
||||
if s.starts_with(self.prefix) {
|
||||
let without_prefix = s[self.prefix.len()..].into_deserializer();
|
||||
return seed.deserialize(without_prefix).map(Some);
|
||||
if let Some(without_prefix) = s.strip_prefix(self.prefix) {
|
||||
return seed
|
||||
.deserialize(without_prefix.into_deserializer())
|
||||
.map(Some);
|
||||
}
|
||||
self.delegate.next_value::<IgnoredAny>()?;
|
||||
}
|
||||
|
|
|
@ -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!!==""##]],
|
||||
);
|
||||
}
|
|
@ -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"#]]};
|
||||
};
|
||||
}
|
|
@ -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"
|
||||
]"#,
|
||||
);
|
||||
}
|
|
@ -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::*;
|
|
@ -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<-"
|
||||
]"#]],
|
||||
);
|
||||
}
|
|
@ -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"]"#,
|
||||
);
|
||||
}
|
|
@ -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"#]],
|
||||
);
|
||||
}
|
|
@ -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\"}"
|
||||
}"#]],
|
||||
);
|
||||
}
|
|
@ -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"#);
|
||||
}
|
|
@ -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#"{}"#]]);
|
||||
}
|
|
@ -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"#]],
|
||||
);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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"#]]);
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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
|
||||
}"#]],
|
||||
);
|
||||
}
|
|
@ -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"#]],
|
||||
);
|
||||
}
|
|
@ -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"
|
||||
}"#]],
|
||||
);
|
||||
}
|
|
@ -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"#]],
|
||||
);
|
||||
}
|
|
@ -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""#]]};
|
||||
};
|
||||
}
|
|
@ -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"#]],
|
||||
);
|
||||
}
|
|
@ -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(),
|
||||
)
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}"#]],
|
||||
);
|
||||
}
|
Загрузка…
Ссылка в новой задаче