nt_time/file_time/
serde.rs

1// SPDX-FileCopyrightText: 2023 Shun Sakai
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! [Serde] support for [`FileTime`].
6//!
7//! [Serde]: https://serde.rs/
8
9use core::fmt;
10
11use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
12
13use super::FileTime;
14
15impl Serialize for FileTime {
16    /// Serializes a `FileTime` into the given Serde serializer.
17    ///
18    /// This serializes using the underlying [`u64`] format.
19    ///
20    /// # Examples
21    ///
22    /// ```
23    /// # use nt_time::{serde::Serialize, FileTime};
24    /// #
25    /// #[derive(Serialize)]
26    /// struct Time {
27    ///     time: FileTime,
28    /// }
29    ///
30    /// let ft = Time {
31    ///     time: FileTime::UNIX_EPOCH,
32    /// };
33    /// let json = serde_json::to_string(&ft).unwrap();
34    /// assert_eq!(json, r#"{"time":116444736000000000}"#);
35    /// ```
36    ///
37    /// ```
38    /// # use nt_time::{serde::Serialize, FileTime};
39    /// #
40    /// #[derive(Serialize)]
41    /// struct Time {
42    ///     time: Option<FileTime>,
43    /// }
44    ///
45    /// let ft = Time {
46    ///     time: Some(FileTime::UNIX_EPOCH),
47    /// };
48    /// let json = serde_json::to_string(&ft).unwrap();
49    /// assert_eq!(json, r#"{"time":116444736000000000}"#);
50    ///
51    /// let ft = Time { time: None };
52    /// let json = serde_json::to_string(&ft).unwrap();
53    /// assert_eq!(json, r#"{"time":null}"#);
54    /// ```
55    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
56        serializer.serialize_newtype_struct("FileTime", &self.to_raw())
57    }
58}
59
60impl<'de> Deserialize<'de> for FileTime {
61    /// Deserializes a `FileTime` from the given Serde deserializer.
62    ///
63    /// This deserializes from its underlying [`u64`] representation.
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// # use nt_time::{serde::Deserialize, FileTime};
69    /// #
70    /// #[derive(Deserialize)]
71    /// struct Time {
72    ///     time: FileTime,
73    /// }
74    ///
75    /// let ft: Time = serde_json::from_str(r#"{"time":116444736000000000}"#).unwrap();
76    /// assert_eq!(ft.time, FileTime::UNIX_EPOCH);
77    /// ```
78    ///
79    /// ```
80    /// # use nt_time::{serde::Deserialize, FileTime};
81    /// #
82    /// #[derive(Deserialize)]
83    /// struct Time {
84    ///     time: Option<FileTime>,
85    /// }
86    ///
87    /// let ft: Time = serde_json::from_str(r#"{"time":116444736000000000}"#).unwrap();
88    /// assert_eq!(ft.time, Some(FileTime::UNIX_EPOCH));
89    ///
90    /// let ft: Time = serde_json::from_str(r#"{"time":null}"#).unwrap();
91    /// assert_eq!(ft.time, None);
92    /// ```
93    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
94        struct FileTimeVisitor;
95
96        impl<'de> Visitor<'de> for FileTimeVisitor {
97            type Value = FileTime;
98
99            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
100                write!(formatter, "a newtype struct `FileTime`")
101            }
102
103            fn visit_newtype_struct<D: Deserializer<'de>>(
104                self,
105                deserializer: D,
106            ) -> Result<Self::Value, D::Error> {
107                <_>::deserialize(deserializer).map(FileTime::new)
108            }
109        }
110
111        deserializer.deserialize_newtype_struct("FileTime", FileTimeVisitor)
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    #[cfg(feature = "std")]
118    use std::string::String;
119
120    #[cfg(feature = "std")]
121    use proptest::prop_assert_eq;
122    use serde_test::Token;
123    #[cfg(feature = "std")]
124    use test_strategy::proptest;
125
126    use super::*;
127
128    #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
129    struct Test {
130        time: FileTime,
131    }
132
133    #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
134    struct TestOption {
135        time: Option<FileTime>,
136    }
137
138    #[test]
139    fn serde() {
140        serde_test::assert_tokens(
141            &Test {
142                time: FileTime::NT_TIME_EPOCH,
143            },
144            &[
145                Token::Struct {
146                    name: "Test",
147                    len: 1,
148                },
149                Token::Str("time"),
150                Token::NewtypeStruct { name: "FileTime" },
151                Token::U64(u64::MIN),
152                Token::StructEnd,
153            ],
154        );
155        serde_test::assert_tokens(
156            &Test {
157                time: FileTime::UNIX_EPOCH,
158            },
159            &[
160                Token::Struct {
161                    name: "Test",
162                    len: 1,
163                },
164                Token::Str("time"),
165                Token::NewtypeStruct { name: "FileTime" },
166                Token::U64(116_444_736_000_000_000),
167                Token::StructEnd,
168            ],
169        );
170        serde_test::assert_tokens(
171            &Test {
172                time: FileTime::MAX,
173            },
174            &[
175                Token::Struct {
176                    name: "Test",
177                    len: 1,
178                },
179                Token::Str("time"),
180                Token::NewtypeStruct { name: "FileTime" },
181                Token::U64(u64::MAX),
182                Token::StructEnd,
183            ],
184        );
185    }
186
187    #[test]
188    fn deserialize_error() {
189        serde_test::assert_de_tokens_error::<Test>(
190            &[
191                Token::Struct {
192                    name: "Test",
193                    len: 1,
194                },
195                Token::Str("time"),
196                Token::BorrowedStr("FileTime"),
197            ],
198            r#"invalid type: string "FileTime", expected a newtype struct `FileTime`"#,
199        );
200        serde_test::assert_de_tokens_error::<Test>(
201            &[
202                Token::Struct {
203                    name: "Test",
204                    len: 1,
205                },
206                Token::Str("time"),
207                Token::NewtypeStruct { name: "FileTime" },
208                Token::Bool(bool::default()),
209            ],
210            "invalid type: boolean `false`, expected u64",
211        );
212        serde_test::assert_de_tokens_error::<Test>(
213            &[
214                Token::Struct {
215                    name: "Test",
216                    len: 1,
217                },
218                Token::Str("time"),
219                Token::NewtypeStruct { name: "FileTime" },
220                Token::I64(i64::MIN),
221            ],
222            "invalid value: integer `-9223372036854775808`, expected u64",
223        );
224    }
225
226    #[test]
227    fn serde_optional() {
228        serde_test::assert_tokens(
229            &TestOption {
230                time: Some(FileTime::NT_TIME_EPOCH),
231            },
232            &[
233                Token::Struct {
234                    name: "TestOption",
235                    len: 1,
236                },
237                Token::Str("time"),
238                Token::Some,
239                Token::NewtypeStruct { name: "FileTime" },
240                Token::U64(u64::MIN),
241                Token::StructEnd,
242            ],
243        );
244        serde_test::assert_tokens(
245            &TestOption {
246                time: Some(FileTime::UNIX_EPOCH),
247            },
248            &[
249                Token::Struct {
250                    name: "TestOption",
251                    len: 1,
252                },
253                Token::Str("time"),
254                Token::Some,
255                Token::NewtypeStruct { name: "FileTime" },
256                Token::U64(116_444_736_000_000_000),
257                Token::StructEnd,
258            ],
259        );
260        serde_test::assert_tokens(
261            &TestOption {
262                time: Some(FileTime::MAX),
263            },
264            &[
265                Token::Struct {
266                    name: "TestOption",
267                    len: 1,
268                },
269                Token::Str("time"),
270                Token::Some,
271                Token::NewtypeStruct { name: "FileTime" },
272                Token::U64(u64::MAX),
273                Token::StructEnd,
274            ],
275        );
276        serde_test::assert_tokens(
277            &TestOption { time: None },
278            &[
279                Token::Struct {
280                    name: "TestOption",
281                    len: 1,
282                },
283                Token::Str("time"),
284                Token::None,
285                Token::StructEnd,
286            ],
287        );
288    }
289
290    #[test]
291    fn deserialize_optional_error() {
292        serde_test::assert_de_tokens_error::<TestOption>(
293            &[
294                Token::Struct {
295                    name: "TestOption",
296                    len: 1,
297                },
298                Token::Str("time"),
299                Token::BorrowedStr("FileTime"),
300            ],
301            r#"invalid type: string "FileTime", expected option"#,
302        );
303        serde_test::assert_de_tokens_error::<TestOption>(
304            &[
305                Token::Struct {
306                    name: "TestOption",
307                    len: 1,
308                },
309                Token::Str("time"),
310                Token::Some,
311                Token::BorrowedStr("FileTime"),
312            ],
313            r#"invalid type: string "FileTime", expected a newtype struct `FileTime`"#,
314        );
315        serde_test::assert_de_tokens_error::<TestOption>(
316            &[
317                Token::Struct {
318                    name: "TestOption",
319                    len: 1,
320                },
321                Token::Str("time"),
322                Token::Some,
323                Token::NewtypeStruct { name: "FileTime" },
324                Token::Bool(bool::default()),
325            ],
326            "invalid type: boolean `false`, expected u64",
327        );
328        serde_test::assert_de_tokens_error::<TestOption>(
329            &[
330                Token::Struct {
331                    name: "TestOption",
332                    len: 1,
333                },
334                Token::Str("time"),
335                Token::Some,
336                Token::NewtypeStruct { name: "FileTime" },
337                Token::I64(i64::MIN),
338            ],
339            "invalid value: integer `-9223372036854775808`, expected u64",
340        );
341    }
342
343    #[test]
344    fn serialize_json() {
345        assert_eq!(
346            serde_json::to_string(&Test {
347                time: FileTime::NT_TIME_EPOCH
348            })
349            .unwrap(),
350            r#"{"time":0}"#
351        );
352        assert_eq!(
353            serde_json::to_string(&Test {
354                time: FileTime::UNIX_EPOCH
355            })
356            .unwrap(),
357            r#"{"time":116444736000000000}"#
358        );
359        assert_eq!(
360            serde_json::to_string(&Test {
361                time: FileTime::MAX
362            })
363            .unwrap(),
364            r#"{"time":18446744073709551615}"#
365        );
366    }
367
368    #[cfg(feature = "std")]
369    #[proptest]
370    fn serialize_json_roundtrip(raw: u64) {
371        let ft = Test {
372            time: FileTime::new(raw),
373        };
374        let json = serde_json::to_string(&ft).unwrap();
375        prop_assert_eq!(json, format!(r#"{{"time":{raw}}}"#));
376    }
377
378    #[test]
379    fn serialize_optional_json() {
380        assert_eq!(
381            serde_json::to_string(&TestOption {
382                time: Some(FileTime::NT_TIME_EPOCH)
383            })
384            .unwrap(),
385            r#"{"time":0}"#
386        );
387        assert_eq!(
388            serde_json::to_string(&TestOption {
389                time: Some(FileTime::UNIX_EPOCH)
390            })
391            .unwrap(),
392            r#"{"time":116444736000000000}"#
393        );
394        assert_eq!(
395            serde_json::to_string(&TestOption {
396                time: Some(FileTime::MAX)
397            })
398            .unwrap(),
399            r#"{"time":18446744073709551615}"#
400        );
401        assert_eq!(
402            serde_json::to_string(&TestOption { time: None }).unwrap(),
403            r#"{"time":null}"#
404        );
405    }
406
407    #[cfg(feature = "std")]
408    #[proptest]
409    fn serialize_optional_json_roundtrip(raw: Option<u64>) {
410        let ft = TestOption {
411            time: raw.map(FileTime::new),
412        };
413        let json = serde_json::to_string(&ft).unwrap();
414        if let Some(r) = raw {
415            prop_assert_eq!(json, format!(r#"{{"time":{r}}}"#));
416        } else {
417            prop_assert_eq!(json, r#"{"time":null}"#);
418        }
419    }
420
421    #[test]
422    fn deserialize_json() {
423        assert_eq!(
424            serde_json::from_str::<Test>(r#"{"time":0}"#).unwrap(),
425            Test {
426                time: FileTime::NT_TIME_EPOCH
427            }
428        );
429        assert_eq!(
430            serde_json::from_str::<Test>(r#"{"time":116444736000000000}"#).unwrap(),
431            Test {
432                time: FileTime::UNIX_EPOCH
433            }
434        );
435        assert_eq!(
436            serde_json::from_str::<Test>(r#"{"time":18446744073709551615}"#).unwrap(),
437            Test {
438                time: FileTime::MAX
439            }
440        );
441    }
442
443    #[cfg(feature = "std")]
444    #[proptest]
445    fn deserialize_json_roundtrip(raw: u64) {
446        let json = format!(r#"{{"time":{raw}}}"#);
447        let ft = serde_json::from_str::<Test>(&json).unwrap();
448        prop_assert_eq!(ft.time, FileTime::new(raw));
449    }
450
451    #[test]
452    fn deserialize_optional_json() {
453        assert_eq!(
454            serde_json::from_str::<TestOption>(r#"{"time":0}"#).unwrap(),
455            TestOption {
456                time: Some(FileTime::NT_TIME_EPOCH)
457            }
458        );
459        assert_eq!(
460            serde_json::from_str::<TestOption>(r#"{"time":116444736000000000}"#).unwrap(),
461            TestOption {
462                time: Some(FileTime::UNIX_EPOCH)
463            }
464        );
465        assert_eq!(
466            serde_json::from_str::<TestOption>(r#"{"time":18446744073709551615}"#).unwrap(),
467            TestOption {
468                time: Some(FileTime::MAX)
469            }
470        );
471        assert_eq!(
472            serde_json::from_str::<TestOption>(r#"{"time":null}"#).unwrap(),
473            TestOption { time: None }
474        );
475    }
476
477    #[cfg(feature = "std")]
478    #[proptest]
479    fn deserialize_optional_json_roundtrip(raw: Option<u64>) {
480        let json = if let Some(r) = raw {
481            format!(r#"{{"time":{r}}}"#)
482        } else {
483            String::from(r#"{"time":null}"#)
484        };
485        let ft = serde_json::from_str::<TestOption>(&json).unwrap();
486        prop_assert_eq!(ft.time, raw.map(FileTime::new));
487    }
488}