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