Skip to content

Commit

Permalink
Add serde support to date time (#2646)
Browse files Browse the repository at this point in the history
## 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._
  • Loading branch information
thomas-k-cameron authored Jun 5, 2023
1 parent 18aad5c commit 74a7204
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 2 deletions.
7 changes: 7 additions & 0 deletions rust-runtime/aws-smithy-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ repository = "https://github.com/awslabs/smithy-rs"

[features]
test-util = []
serde-serialize = []
serde-deserialize = []

[dependencies]
itoa = "1.0.0"
Expand All @@ -25,6 +27,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
criterion = "0.4"
rand = "0.8.4"
ciborium = { version = "0.2.1" }

[package.metadata.docs.rs]
all-features = true
Expand All @@ -35,3 +38,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[[bench]]
name = "base64"
harness = false

[target."cfg(aws_sdk_unstable)".dependencies.serde]
version = "1"
features = ["derive"]
140 changes: 140 additions & 0 deletions rust-runtime/aws-smithy-types/src/date_time/de.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use super::*;
use serde::de::{Error, Visitor};
use serde::Deserialize;

struct DateTimeVisitor;

struct NonHumanReadableDateTimeVisitor;

impl<'de> Visitor<'de> for DateTimeVisitor {
type Value = DateTime;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("expected RFC-3339 Date Time")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match DateTime::from_str(v, Format::DateTime) {
Ok(e) => Ok(e),
Err(e) => Err(Error::custom(e)),
}
}
}

impl<'de> Visitor<'de> for NonHumanReadableDateTimeVisitor {
type Value = DateTime;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
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)")
}

fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
match seq.size_hint() {
Some(2) | None => match (seq.next_element()?, seq.next_element()?) {
(Some(seconds), Some(subsecond_nanos)) => Ok(DateTime {
seconds,
subsecond_nanos,
}),
_ => return Err(Error::custom("datatype mismatch")),
},
_ => Err(Error::custom("Size mismatch")),
}
}
}

impl<'de> Deserialize<'de> for DateTime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
deserializer.deserialize_str(DateTimeVisitor)
} else {
deserializer.deserialize_tuple(2, NonHumanReadableDateTimeVisitor)
}
}
}

#[cfg(test)]
mod test {
use super::*;

/// check for human redable format
#[test]
fn de_human_readable_datetime() {
use serde::{Deserialize, Serialize};

let datetime = DateTime::from_secs(1576540098);
#[derive(Serialize, Deserialize, PartialEq)]
struct Test {
datetime: DateTime,
}
let datetime_json = r#"{"datetime":"2019-12-16T23:48:18Z"}"#;
let test = serde_json::from_str::<Test>(&datetime_json).ok();
assert!(test == Some(Test { datetime }));
}

/// check for non-human redable format
#[test]
fn de_not_human_readable_datetime() {
{
let cbor = ciborium::value::Value::Array(vec![
ciborium::value::Value::Integer(1576540098i64.into()),
ciborium::value::Value::Integer(0u32.into()),
]);
let mut buf = vec![];
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
let cbor_dt: DateTime = ciborium::de::from_reader(std::io::Cursor::new(buf)).unwrap();
assert_eq!(
cbor_dt,
DateTime {
seconds: 1576540098i64,
subsecond_nanos: 0
}
);
};

{
let cbor = ciborium::value::Value::Array(vec![
ciborium::value::Value::Integer(0i64.into()),
ciborium::value::Value::Integer(0u32.into()),
]);
let mut buf = vec![];
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
let cbor_dt: DateTime = ciborium::de::from_reader(std::io::Cursor::new(buf)).unwrap();
assert_eq!(
cbor_dt,
DateTime {
seconds: 0,
subsecond_nanos: 0
}
);
};

{
let cbor = ciborium::value::Value::Array(vec![
ciborium::value::Value::Integer(i64::MAX.into()),
ciborium::value::Value::Integer(u32::MAX.into()),
]);
let mut buf = vec![];
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
let cbor_dt: DateTime = ciborium::de::from_reader(std::io::Cursor::new(buf)).unwrap();
assert_eq!(
cbor_dt,
DateTime {
seconds: i64::MAX,
subsecond_nanos: u32::MAX
}
);
};
}
}
9 changes: 7 additions & 2 deletions rust-runtime/aws-smithy-types/src/date_time/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ use std::time::Duration;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;

#[cfg(all(aws_sdk_unstable, feature = "serde-deserialize"))]
mod de;
mod format;
#[cfg(all(aws_sdk_unstable, feature = "serde-serialize"))]
mod ser;

pub use self::format::DateTimeFormatError;
pub use self::format::DateTimeParseError;

Expand Down Expand Up @@ -51,8 +56,8 @@ const NANOS_PER_SECOND_U32: u32 = 1_000_000_000;
/// [`time`](https://crates.io/crates/time) or [`chrono`](https://crates.io/crates/chrono).
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct DateTime {
seconds: i64,
subsecond_nanos: u32,
pub(crate) seconds: i64,
pub(crate) subsecond_nanos: u32,
}

/* ANCHOR_END: date_time */
Expand Down
85 changes: 85 additions & 0 deletions rust-runtime/aws-smithy-types/src/date_time/ser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

use super::*;
use serde::ser::SerializeTuple;

impl serde::Serialize for DateTime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
match self.fmt(Format::DateTime) {
Ok(val) => serializer.serialize_str(&val),
Err(e) => Err(serde::ser::Error::custom(e)),
}
} else {
let mut tup_ser = serializer.serialize_tuple(2)?;
tup_ser.serialize_element(&self.seconds)?;
tup_ser.serialize_element(&self.subsecond_nanos)?;
tup_ser.end()
}
}
}

#[cfg(test)]
mod test {
use super::*;

/// check for human redable format
#[test]
fn ser_human_readable_datetime() {
use serde::{Deserialize, Serialize};

let datetime = DateTime::from_secs(1576540098);
#[derive(Serialize, Deserialize, PartialEq)]
struct Test {
datetime: DateTime,
}
let datetime_json = r#"{"datetime":"2019-12-16T23:48:18Z"}"#;
assert!(serde_json::to_string(&Test { datetime }).ok() == Some(datetime_json.to_string()));
}

/// check for non-human redable format
#[test]
fn ser_not_human_readable_datetime() {
{
let cbor = ciborium::value::Value::Array(vec![
ciborium::value::Value::Integer(1576540098i64.into()),
ciborium::value::Value::Integer(0u32.into()),
]);
let mut buf = vec![];
let mut buf2 = vec![];
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
let _ = ciborium::ser::into_writer(&cbor, &mut buf2);
assert_eq!(buf, buf2);
};

{
let cbor = ciborium::value::Value::Array(vec![
ciborium::value::Value::Integer(0i64.into()),
ciborium::value::Value::Integer(0u32.into()),
]);
let mut buf = vec![];
let mut buf2 = vec![];
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
let _ = ciborium::ser::into_writer(&cbor, &mut buf2);
assert_eq!(buf, buf2);
};

{
let cbor = ciborium::value::Value::Array(vec![
ciborium::value::Value::Integer(i64::MAX.into()),
ciborium::value::Value::Integer(u32::MAX.into()),
]);
let mut buf = vec![];
let mut buf2 = vec![];
let _ = ciborium::ser::into_writer(&cbor, &mut buf);
let _ = ciborium::ser::into_writer(&cbor, &mut buf2);
assert_eq!(buf, buf2);
};
}
}

0 comments on commit 74a7204

Please sign in to comment.