nt_time/serde_with/
rfc_3339.rs

1// SPDX-FileCopyrightText: 2023 Shun Sakai
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Use the well-known [RFC 3339 format] when serializing and deserializing a
6//! [`FileTime`].
7//!
8//! Use this module in combination with Serde's [`with`] attribute.
9//!
10//! # Examples
11//!
12//! ```
13//! use nt_time::{
14//!     FileTime,
15//!     serde::{Deserialize, Serialize},
16//!     serde_with::rfc_3339,
17//! };
18//!
19//! #[derive(Deserialize, Serialize)]
20//! struct Time {
21//!     #[serde(with = "rfc_3339")]
22//!     time: FileTime,
23//! }
24//!
25//! let ft = Time {
26//!     time: FileTime::UNIX_EPOCH,
27//! };
28//! let json = serde_json::to_string(&ft).unwrap();
29//! assert_eq!(json, r#"{"time":"1970-01-01T00:00:00Z"}"#);
30//!
31//! let ft: Time = serde_json::from_str(&json).unwrap();
32//! assert_eq!(ft.time, FileTime::UNIX_EPOCH);
33//! ```
34//!
35//! [RFC 3339 format]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
36//! [`with`]: https://serde.rs/field-attrs.html#with
37
38pub mod option;
39
40use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _};
41use time::serde::rfc3339;
42
43use crate::FileTime;
44
45#[allow(clippy::missing_errors_doc)]
46/// Serializes a [`FileTime`] into the given Serde serializer.
47///
48/// This serializes using the well-known [RFC 3339 format].
49///
50/// [RFC 3339 format]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
51#[inline]
52pub fn serialize<S: Serializer>(ft: &FileTime, serializer: S) -> Result<S::Ok, S::Error> {
53    rfc3339::serialize(&(*ft).try_into().map_err(S::Error::custom)?, serializer)
54}
55
56#[allow(clippy::missing_errors_doc)]
57/// Deserializes a [`FileTime`] from the given Serde deserializer.
58///
59/// This deserializes from its [RFC 3339 representation].
60///
61/// [RFC 3339 representation]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
62#[inline]
63pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<FileTime, D::Error> {
64    FileTime::try_from(rfc3339::deserialize(deserializer)?).map_err(D::Error::custom)
65}
66
67#[cfg(test)]
68mod tests {
69    use serde::{Deserialize, Serialize};
70    use serde_test::{Token, assert_de_tokens_error, assert_ser_tokens_error, assert_tokens};
71
72    use super::*;
73
74    #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
75    struct Test {
76        #[serde(with = "crate::serde_with::rfc_3339")]
77        time: FileTime,
78    }
79
80    #[test]
81    fn serde() {
82        assert_tokens(
83            &Test {
84                time: FileTime::NT_TIME_EPOCH,
85            },
86            &[
87                Token::Struct {
88                    name: "Test",
89                    len: 1,
90                },
91                Token::Str("time"),
92                Token::BorrowedStr("1601-01-01T00:00:00Z"),
93                Token::StructEnd,
94            ],
95        );
96        assert_tokens(
97            &Test {
98                time: FileTime::UNIX_EPOCH,
99            },
100            &[
101                Token::Struct {
102                    name: "Test",
103                    len: 1,
104                },
105                Token::Str("time"),
106                Token::BorrowedStr("1970-01-01T00:00:00Z"),
107                Token::StructEnd,
108            ],
109        );
110    }
111
112    #[cfg(not(feature = "large-dates"))]
113    #[test]
114    fn serialize_error_without_large_dates() {
115        assert_ser_tokens_error::<Test>(
116            &Test {
117                time: FileTime::MAX,
118            },
119            &[
120                Token::Struct {
121                    name: "Test",
122                    len: 1,
123                },
124                Token::Str("time"),
125            ],
126            "timestamp must be in the range -377705116800..=253402300799",
127        );
128    }
129
130    #[cfg(feature = "large-dates")]
131    #[test]
132    fn serialize_error_with_large_dates() {
133        assert_ser_tokens_error::<Test>(
134            &Test {
135                time: FileTime::MAX,
136            },
137            &[
138                Token::Struct {
139                    name: "Test",
140                    len: 1,
141                },
142                Token::Str("time"),
143            ],
144            "The year component cannot be formatted into the requested format.",
145        );
146    }
147
148    #[test]
149    fn deserialize_error() {
150        assert_de_tokens_error::<Test>(
151            &[
152                Token::Struct {
153                    name: "Test",
154                    len: 1,
155                },
156                Token::Str("time"),
157                Token::BorrowedStr("1600-12-31T23:59:59.999999900Z"),
158                Token::StructEnd,
159            ],
160            "date and time is before `1601-01-01 00:00:00 UTC`",
161        );
162    }
163
164    #[test]
165    fn serialize_json() {
166        assert_eq!(
167            serde_json::to_string(&Test {
168                time: FileTime::NT_TIME_EPOCH
169            })
170            .unwrap(),
171            r#"{"time":"1601-01-01T00:00:00Z"}"#
172        );
173        assert_eq!(
174            serde_json::to_string(&Test {
175                time: FileTime::UNIX_EPOCH
176            })
177            .unwrap(),
178            r#"{"time":"1970-01-01T00:00:00Z"}"#
179        );
180    }
181
182    #[test]
183    fn deserialize_json() {
184        assert_eq!(
185            serde_json::from_str::<Test>(r#"{"time":"1601-01-01T00:00:00Z"}"#).unwrap(),
186            Test {
187                time: FileTime::NT_TIME_EPOCH
188            }
189        );
190        assert_eq!(
191            serde_json::from_str::<Test>(r#"{"time":"1970-01-01T00:00:00Z"}"#).unwrap(),
192            Test {
193                time: FileTime::UNIX_EPOCH
194            }
195        );
196    }
197}