diff --git a/rust-runtime/aws-smithy-types/Cargo.toml b/rust-runtime/aws-smithy-types/Cargo.toml index aa2462d6d0..60af72da0f 100644 --- a/rust-runtime/aws-smithy-types/Cargo.toml +++ b/rust-runtime/aws-smithy-types/Cargo.toml @@ -9,6 +9,8 @@ repository = "https://github.com/awslabs/smithy-rs" [features] test-util = [] +serde-serialize = [] +serde-deserialize = [] [dependencies] itoa = "1.0.0" @@ -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 @@ -35,3 +38,7 @@ rustdoc-args = ["--cfg", "docsrs"] [[bench]] name = "base64" harness = false + +[target."cfg(aws_sdk_unstable)".dependencies.serde] +version = "1" +features = ["derive"] diff --git a/rust-runtime/aws-smithy-types/src/date_time/de.rs b/rust-runtime/aws-smithy-types/src/date_time/de.rs new file mode 100644 index 0000000000..c87a32e0f5 --- /dev/null +++ b/rust-runtime/aws-smithy-types/src/date_time/de.rs @@ -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(self, v: &str) -> Result + 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(self, mut seq: A) -> Result + 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(deserializer: D) -> Result + 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::(&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 + } + ); + }; + } +} diff --git a/rust-runtime/aws-smithy-types/src/date_time/mod.rs b/rust-runtime/aws-smithy-types/src/date_time/mod.rs index 84ac29799b..8dcdc53067 100644 --- a/rust-runtime/aws-smithy-types/src/date_time/mod.rs +++ b/rust-runtime/aws-smithy-types/src/date_time/mod.rs @@ -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; @@ -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 */ diff --git a/rust-runtime/aws-smithy-types/src/date_time/ser.rs b/rust-runtime/aws-smithy-types/src/date_time/ser.rs new file mode 100644 index 0000000000..5ea5f55dcd --- /dev/null +++ b/rust-runtime/aws-smithy-types/src/date_time/ser.rs @@ -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(&self, serializer: S) -> Result + 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); + }; + } +}