Unverified Commit 74a72041 authored by Thomas Cameron's avatar Thomas Cameron Committed by GitHub
Browse files

Add serde support to date time (#2646)

## Motivation and Context
This is a child PR of https://github.com/awslabs/smithy-rs/pull/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._
parent 18aad5c6
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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"]
+140 −0
Original line number Diff line number Diff line
/*
 * 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
                }
            );
        };
    }
}
+7 −2
Original line number Diff line number Diff line
@@ -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 */
+85 −0
Original line number Diff line number Diff line
/*
 * 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);
        };
    }
}