Skip to content

Commit 74a7204

Browse files
Add serde support to date time (#2646)
## Motivation and Context This is a child PR of #2616 The changes that this PR introduces is same as the ones that were merged to `unstable-serde-support` branch before. Initially, we tried to make commit to unstable-serde-support branch and merge changes one by one in small PRs. However, in order to make it up to date with the main branch, we would need to go through a large PR of over 700 files. Thus, I decided to create individual PRs that commits directly to `main` branch. ## Description - Implements `serde` support to `DateTime` ## Testing - Test checks whether the serialized/de-serialized data matches with the expected value ## Checklist NA ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
1 parent 18aad5c commit 74a7204

File tree

4 files changed

+239
-2
lines changed

4 files changed

+239
-2
lines changed

rust-runtime/aws-smithy-types/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ repository = "https://github.com/awslabs/smithy-rs"
99

1010
[features]
1111
test-util = []
12+
serde-serialize = []
13+
serde-deserialize = []
1214

1315
[dependencies]
1416
itoa = "1.0.0"
@@ -25,6 +27,7 @@ serde = { version = "1", features = ["derive"] }
2527
serde_json = "1"
2628
criterion = "0.4"
2729
rand = "0.8.4"
30+
ciborium = { version = "0.2.1" }
2831

2932
[package.metadata.docs.rs]
3033
all-features = true
@@ -35,3 +38,7 @@ rustdoc-args = ["--cfg", "docsrs"]
3538
[[bench]]
3639
name = "base64"
3740
harness = false
41+
42+
[target."cfg(aws_sdk_unstable)".dependencies.serde]
43+
version = "1"
44+
features = ["derive"]
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
use super::*;
7+
use serde::de::{Error, Visitor};
8+
use serde::Deserialize;
9+
10+
struct DateTimeVisitor;
11+
12+
struct NonHumanReadableDateTimeVisitor;
13+
14+
impl<'de> Visitor<'de> for DateTimeVisitor {
15+
type Value = DateTime;
16+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
17+
formatter.write_str("expected RFC-3339 Date Time")
18+
}
19+
20+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
21+
where
22+
E: serde::de::Error,
23+
{
24+
match DateTime::from_str(v, Format::DateTime) {
25+
Ok(e) => Ok(e),
26+
Err(e) => Err(Error::custom(e)),
27+
}
28+
}
29+
}
30+
31+
impl<'de> Visitor<'de> for NonHumanReadableDateTimeVisitor {
32+
type Value = DateTime;
33+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
34+
formatter.write_str("DateTime type expects a tuple of i64 and u32 when deserializing from non human readable format like CBOR or AVRO, i.e. (i64, u32)")
35+
}
36+
37+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
38+
where
39+
A: serde::de::SeqAccess<'de>,
40+
{
41+
match seq.size_hint() {
42+
Some(2) | None => match (seq.next_element()?, seq.next_element()?) {
43+
(Some(seconds), Some(subsecond_nanos)) => Ok(DateTime {
44+
seconds,
45+
subsecond_nanos,
46+
}),
47+
_ => return Err(Error::custom("datatype mismatch")),
48+
},
49+
_ => Err(Error::custom("Size mismatch")),
50+
}
51+
}
52+
}
53+
54+
impl<'de> Deserialize<'de> for DateTime {
55+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
56+
where
57+
D: serde::Deserializer<'de>,
58+
{
59+
if deserializer.is_human_readable() {
60+
deserializer.deserialize_str(DateTimeVisitor)
61+
} else {
62+
deserializer.deserialize_tuple(2, NonHumanReadableDateTimeVisitor)
63+
}
64+
}
65+
}
66+
67+
#[cfg(test)]
68+
mod test {
69+
use super::*;
70+
71+
/// check for human redable format
72+
#[test]
73+
fn de_human_readable_datetime() {
74+
use serde::{Deserialize, Serialize};
75+
76+
let datetime = DateTime::from_secs(1576540098);
77+
#[derive(Serialize, Deserialize, PartialEq)]
78+
struct Test {
79+
datetime: DateTime,
80+
}
81+
let datetime_json = r#"{"datetime":"2019-12-16T23:48:18Z"}"#;
82+
let test = serde_json::from_str::<Test>(&datetime_json).ok();
83+
assert!(test == Some(Test { datetime }));
84+
}
85+
86+
/// check for non-human redable format
87+
#[test]
88+
fn de_not_human_readable_datetime() {
89+
{
90+
let cbor = ciborium::value::Value::Array(vec![
91+
ciborium::value::Value::Integer(1576540098i64.into()),
92+
ciborium::value::Value::Integer(0u32.into()),
93+
]);
94+
let mut buf = vec![];
95+
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
96+
let cbor_dt: DateTime = ciborium::de::from_reader(std::io::Cursor::new(buf)).unwrap();
97+
assert_eq!(
98+
cbor_dt,
99+
DateTime {
100+
seconds: 1576540098i64,
101+
subsecond_nanos: 0
102+
}
103+
);
104+
};
105+
106+
{
107+
let cbor = ciborium::value::Value::Array(vec![
108+
ciborium::value::Value::Integer(0i64.into()),
109+
ciborium::value::Value::Integer(0u32.into()),
110+
]);
111+
let mut buf = vec![];
112+
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
113+
let cbor_dt: DateTime = ciborium::de::from_reader(std::io::Cursor::new(buf)).unwrap();
114+
assert_eq!(
115+
cbor_dt,
116+
DateTime {
117+
seconds: 0,
118+
subsecond_nanos: 0
119+
}
120+
);
121+
};
122+
123+
{
124+
let cbor = ciborium::value::Value::Array(vec![
125+
ciborium::value::Value::Integer(i64::MAX.into()),
126+
ciborium::value::Value::Integer(u32::MAX.into()),
127+
]);
128+
let mut buf = vec![];
129+
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
130+
let cbor_dt: DateTime = ciborium::de::from_reader(std::io::Cursor::new(buf)).unwrap();
131+
assert_eq!(
132+
cbor_dt,
133+
DateTime {
134+
seconds: i64::MAX,
135+
subsecond_nanos: u32::MAX
136+
}
137+
);
138+
};
139+
}
140+
}

rust-runtime/aws-smithy-types/src/date_time/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ use std::time::Duration;
1717
use std::time::SystemTime;
1818
use std::time::UNIX_EPOCH;
1919

20+
#[cfg(all(aws_sdk_unstable, feature = "serde-deserialize"))]
21+
mod de;
2022
mod format;
23+
#[cfg(all(aws_sdk_unstable, feature = "serde-serialize"))]
24+
mod ser;
25+
2126
pub use self::format::DateTimeFormatError;
2227
pub use self::format::DateTimeParseError;
2328

@@ -51,8 +56,8 @@ const NANOS_PER_SECOND_U32: u32 = 1_000_000_000;
5156
/// [`time`](https://crates.io/crates/time) or [`chrono`](https://crates.io/crates/chrono).
5257
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
5358
pub struct DateTime {
54-
seconds: i64,
55-
subsecond_nanos: u32,
59+
pub(crate) seconds: i64,
60+
pub(crate) subsecond_nanos: u32,
5661
}
5762

5863
/* ANCHOR_END: date_time */
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
use super::*;
7+
use serde::ser::SerializeTuple;
8+
9+
impl serde::Serialize for DateTime {
10+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
11+
where
12+
S: serde::Serializer,
13+
{
14+
if serializer.is_human_readable() {
15+
match self.fmt(Format::DateTime) {
16+
Ok(val) => serializer.serialize_str(&val),
17+
Err(e) => Err(serde::ser::Error::custom(e)),
18+
}
19+
} else {
20+
let mut tup_ser = serializer.serialize_tuple(2)?;
21+
tup_ser.serialize_element(&self.seconds)?;
22+
tup_ser.serialize_element(&self.subsecond_nanos)?;
23+
tup_ser.end()
24+
}
25+
}
26+
}
27+
28+
#[cfg(test)]
29+
mod test {
30+
use super::*;
31+
32+
/// check for human redable format
33+
#[test]
34+
fn ser_human_readable_datetime() {
35+
use serde::{Deserialize, Serialize};
36+
37+
let datetime = DateTime::from_secs(1576540098);
38+
#[derive(Serialize, Deserialize, PartialEq)]
39+
struct Test {
40+
datetime: DateTime,
41+
}
42+
let datetime_json = r#"{"datetime":"2019-12-16T23:48:18Z"}"#;
43+
assert!(serde_json::to_string(&Test { datetime }).ok() == Some(datetime_json.to_string()));
44+
}
45+
46+
/// check for non-human redable format
47+
#[test]
48+
fn ser_not_human_readable_datetime() {
49+
{
50+
let cbor = ciborium::value::Value::Array(vec![
51+
ciborium::value::Value::Integer(1576540098i64.into()),
52+
ciborium::value::Value::Integer(0u32.into()),
53+
]);
54+
let mut buf = vec![];
55+
let mut buf2 = vec![];
56+
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
57+
let _ = ciborium::ser::into_writer(&cbor, &mut buf2);
58+
assert_eq!(buf, buf2);
59+
};
60+
61+
{
62+
let cbor = ciborium::value::Value::Array(vec![
63+
ciborium::value::Value::Integer(0i64.into()),
64+
ciborium::value::Value::Integer(0u32.into()),
65+
]);
66+
let mut buf = vec![];
67+
let mut buf2 = vec![];
68+
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
69+
let _ = ciborium::ser::into_writer(&cbor, &mut buf2);
70+
assert_eq!(buf, buf2);
71+
};
72+
73+
{
74+
let cbor = ciborium::value::Value::Array(vec![
75+
ciborium::value::Value::Integer(i64::MAX.into()),
76+
ciborium::value::Value::Integer(u32::MAX.into()),
77+
]);
78+
let mut buf = vec![];
79+
let mut buf2 = vec![];
80+
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
81+
let _ = ciborium::ser::into_writer(&cbor, &mut buf2);
82+
assert_eq!(buf, buf2);
83+
};
84+
}
85+
}

0 commit comments

Comments
 (0)