Skip to main content

firestore/firestore_serde/
timestamp_serializers.rs

1use crate::FirestoreInstant;
2use gcloud_sdk::google::firestore::v1::value;
3use rvstruct::ValueStruct;
4use serde::{Deserialize, Serialize, Serializer};
5
6use crate::{
7    errors::FirestoreSerializationError, timestamp_utils::to_timestamp, FirestoreError,
8    FirestoreValue,
9};
10
11/// A wrapper around [`FirestoreInstant`] that is always serialized as a
12/// Firestore timestamp value, without needing a `#[serde(with)]` attribute.
13///
14/// Use [`FirestoreInstant`] directly when you prefer the attributes, and this
15/// wrapper when you would rather carry the behaviour in the type. It still
16/// serializes as a string to JSON, so the same model can be reused for both
17/// JSON and Firestore.
18#[derive(
19    Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Default, ValueStruct,
20)]
21pub struct FirestoreTimestamp(pub FirestoreInstant);
22
23impl FirestoreTimestamp {
24    /// Returns the current instant, with the precision Firestore stores.
25    #[inline]
26    pub fn now() -> Self {
27        FirestoreTimestamp(FirestoreInstant::now()).truncated_to_firestore_precision()
28    }
29
30    /// Truncates the value to the microsecond precision Firestore stores.
31    ///
32    /// Firestore keeps the timestamps with microsecond precision and discards
33    /// the nanoseconds on write, so a value that carries them never comes back
34    /// unchanged. Every constructor and conversion of this type truncates for
35    /// you, and this method is here for the values built directly through the
36    /// public field.
37    ///
38    /// # Examples
39    ///
40    /// ```rust
41    /// use firestore::*;
42    ///
43    /// let exact = FirestoreTimestamp(
44    ///     "2022-12-02T16:53:20.123456789Z".parse::<FirestoreInstant>().unwrap(),
45    /// );
46    ///
47    /// assert_eq!(
48    ///     exact.truncated_to_firestore_precision().to_string(),
49    ///     "2022-12-02T16:53:20.123456Z"
50    /// );
51    /// ```
52    #[inline]
53    pub fn truncated_to_firestore_precision(self) -> Self {
54        // Going through the protobuf representation keeps this correct for the
55        // instants before the Unix epoch: it always carries a non negative
56        // subsecond part, so dropping the sub microseconds rounds the value
57        // down, the same way Firestore does.
58        let ts = crate::timestamp_utils::to_timestamp(self.0);
59
60        crate::timestamp_utils::from_timestamp(gcloud_sdk::prost_types::Timestamp {
61            seconds: ts.seconds,
62            nanos: (ts.nanos / 1_000) * 1_000,
63        })
64        .map(FirestoreTimestamp)
65        .unwrap_or(self)
66    }
67}
68
69impl std::fmt::Display for FirestoreTimestamp {
70    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
71        self.0.fmt(f)
72    }
73}
74
75impl std::str::FromStr for FirestoreTimestamp {
76    type Err = crate::errors::FirestoreError;
77
78    fn from_str(s: &str) -> Result<Self, Self::Err> {
79        Ok(FirestoreTimestamp(s.parse::<FirestoreInstant>()?).truncated_to_firestore_precision())
80    }
81}
82
83impl From<FirestoreTimestamp> for FirestoreInstant {
84    fn from(ts: FirestoreTimestamp) -> Self {
85        ts.0
86    }
87}
88
89/// Converting to a `SystemTime` is infallible, since every representable
90/// timestamp fits into it.
91impl From<FirestoreTimestamp> for std::time::SystemTime {
92    fn from(ts: FirestoreTimestamp) -> Self {
93        ts.0.into()
94    }
95}
96
97/// Converting from a `SystemTime` is fallible, since it may be outside of the
98/// range supported by Firestore timestamps.
99impl TryFrom<std::time::SystemTime> for FirestoreTimestamp {
100    type Error = crate::errors::FirestoreError;
101
102    fn try_from(system_time: std::time::SystemTime) -> Result<Self, Self::Error> {
103        Ok(FirestoreTimestamp(FirestoreInstant::try_from(system_time)?)
104            .truncated_to_firestore_precision())
105    }
106}
107
108/// Converts from a Google `prost_types::Timestamp`, as received from Firestore.
109impl TryFrom<gcloud_sdk::prost_types::Timestamp> for FirestoreTimestamp {
110    type Error = crate::errors::FirestoreError;
111
112    fn try_from(ts: gcloud_sdk::prost_types::Timestamp) -> Result<Self, Self::Error> {
113        Ok(
114            FirestoreTimestamp(crate::timestamp_utils::from_timestamp(ts)?)
115                .truncated_to_firestore_precision(),
116        )
117    }
118}
119
120/// Converts into a Google `prost_types::Timestamp`, as sent to Firestore.
121impl From<FirestoreTimestamp> for gcloud_sdk::prost_types::Timestamp {
122    fn from(ts: FirestoreTimestamp) -> Self {
123        crate::timestamp_utils::to_timestamp(ts.0)
124    }
125}
126
127pub(crate) const FIRESTORE_TS_TYPE_TAG_TYPE: &str = "FirestoreTimestamp";
128
129pub(crate) const FIRESTORE_TS_NULL_TYPE_TAG_TYPE: &str = "FirestoreTimestampAsNull";
130
131pub mod serialize_as_timestamp {
132    use crate::FirestoreInstant;
133    use serde::{Deserialize, Deserializer, Serializer};
134
135    pub fn serialize<S>(ts: &FirestoreInstant, serializer: S) -> Result<S::Ok, S::Error>
136    where
137        S: Serializer,
138    {
139        serializer.serialize_newtype_struct(crate::firestore_serde::FIRESTORE_TS_TYPE_TAG_TYPE, &ts)
140    }
141
142    pub fn deserialize<'de, D>(deserializer: D) -> Result<FirestoreInstant, D::Error>
143    where
144        D: Deserializer<'de>,
145    {
146        FirestoreInstant::deserialize(deserializer)
147    }
148}
149
150pub mod serialize_as_optional_timestamp {
151    use crate::FirestoreInstant;
152    use serde::{Deserialize, Deserializer, Serializer};
153
154    pub fn serialize<S>(ts: &Option<FirestoreInstant>, serializer: S) -> Result<S::Ok, S::Error>
155    where
156        S: Serializer,
157    {
158        match ts {
159            Some(v) => serializer
160                .serialize_newtype_struct(crate::firestore_serde::FIRESTORE_TS_TYPE_TAG_TYPE, v),
161            None => serializer.serialize_none(),
162        }
163    }
164
165    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<FirestoreInstant>, D::Error>
166    where
167        D: Deserializer<'de>,
168    {
169        Option::<FirestoreInstant>::deserialize(deserializer)
170    }
171}
172
173pub mod serialize_as_null_timestamp {
174    use crate::FirestoreInstant;
175    use serde::{Deserialize, Deserializer, Serializer};
176
177    pub fn serialize<S>(ts: &Option<FirestoreInstant>, serializer: S) -> Result<S::Ok, S::Error>
178    where
179        S: Serializer,
180    {
181        serializer
182            .serialize_newtype_struct(crate::firestore_serde::FIRESTORE_TS_NULL_TYPE_TAG_TYPE, ts)
183    }
184
185    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<FirestoreInstant>, D::Error>
186    where
187        D: Deserializer<'de>,
188    {
189        Option::<FirestoreInstant>::deserialize(deserializer)
190    }
191}
192
193pub fn serialize_timestamp_for_firestore<T: ?Sized + Serialize>(
194    value: &T,
195    none_as_null: bool,
196) -> Result<FirestoreValue, FirestoreError> {
197    struct TimestampSerializer {
198        none_as_null: bool,
199    }
200
201    impl Serializer for TimestampSerializer {
202        type Ok = FirestoreValue;
203        type Error = FirestoreError;
204        type SerializeSeq = crate::firestore_serde::serializer::SerializeVec;
205        type SerializeTuple = crate::firestore_serde::serializer::SerializeVec;
206        type SerializeTupleStruct = crate::firestore_serde::serializer::SerializeVec;
207        type SerializeTupleVariant = crate::firestore_serde::serializer::SerializeTupleVariant;
208        type SerializeMap = crate::firestore_serde::serializer::SerializeMap;
209        type SerializeStruct = crate::firestore_serde::serializer::SerializeMap;
210        type SerializeStructVariant = crate::firestore_serde::serializer::SerializeStructVariant;
211
212        fn serialize_bool(self, _v: bool) -> Result<Self::Ok, Self::Error> {
213            Err(FirestoreError::SerializeError(
214                FirestoreSerializationError::from_message(
215                    "Timestamp serializer doesn't support this type",
216                ),
217            ))
218        }
219
220        fn serialize_i8(self, _v: i8) -> Result<Self::Ok, Self::Error> {
221            Err(FirestoreError::SerializeError(
222                FirestoreSerializationError::from_message(
223                    "Timestamp serializer doesn't support this type",
224                ),
225            ))
226        }
227
228        fn serialize_i16(self, _v: i16) -> Result<Self::Ok, Self::Error> {
229            Err(FirestoreError::SerializeError(
230                FirestoreSerializationError::from_message(
231                    "Timestamp serializer doesn't support this type",
232                ),
233            ))
234        }
235
236        fn serialize_i32(self, _v: i32) -> Result<Self::Ok, Self::Error> {
237            Err(FirestoreError::SerializeError(
238                FirestoreSerializationError::from_message(
239                    "Timestamp serializer doesn't support this type",
240                ),
241            ))
242        }
243
244        fn serialize_i64(self, _v: i64) -> Result<Self::Ok, Self::Error> {
245            Err(FirestoreError::SerializeError(
246                FirestoreSerializationError::from_message(
247                    "Timestamp serializer doesn't support this type",
248                ),
249            ))
250        }
251
252        fn serialize_u8(self, _v: u8) -> Result<Self::Ok, Self::Error> {
253            Err(FirestoreError::SerializeError(
254                FirestoreSerializationError::from_message(
255                    "Timestamp serializer doesn't support this type",
256                ),
257            ))
258        }
259
260        fn serialize_u16(self, _v: u16) -> Result<Self::Ok, Self::Error> {
261            Err(FirestoreError::SerializeError(
262                FirestoreSerializationError::from_message(
263                    "Timestamp serializer doesn't support this type",
264                ),
265            ))
266        }
267
268        fn serialize_u32(self, _v: u32) -> Result<Self::Ok, Self::Error> {
269            Err(FirestoreError::SerializeError(
270                FirestoreSerializationError::from_message(
271                    "Timestamp serializer doesn't support this type",
272                ),
273            ))
274        }
275
276        fn serialize_u64(self, _v: u64) -> Result<Self::Ok, Self::Error> {
277            Err(FirestoreError::SerializeError(
278                FirestoreSerializationError::from_message(
279                    "Timestamp serializer doesn't support this type",
280                ),
281            ))
282        }
283
284        fn serialize_f32(self, _v: f32) -> Result<Self::Ok, Self::Error> {
285            Err(FirestoreError::SerializeError(
286                FirestoreSerializationError::from_message(
287                    "Timestamp serializer doesn't support this type",
288                ),
289            ))
290        }
291
292        fn serialize_f64(self, _v: f64) -> Result<Self::Ok, Self::Error> {
293            Err(FirestoreError::SerializeError(
294                FirestoreSerializationError::from_message(
295                    "Timestamp serializer doesn't support this type",
296                ),
297            ))
298        }
299
300        fn serialize_char(self, _v: char) -> Result<Self::Ok, Self::Error> {
301            Err(FirestoreError::SerializeError(
302                FirestoreSerializationError::from_message(
303                    "Timestamp serializer doesn't support this type",
304                ),
305            ))
306        }
307
308        fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
309            let ts = v.parse::<FirestoreInstant>()?;
310            Ok(FirestoreValue::from(
311                gcloud_sdk::google::firestore::v1::Value {
312                    value_type: Some(value::ValueType::TimestampValue(to_timestamp(ts))),
313                },
314            ))
315        }
316
317        fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok, Self::Error> {
318            Err(FirestoreError::SerializeError(
319                FirestoreSerializationError::from_message(
320                    "Timestamp serializer doesn't support this type",
321                ),
322            ))
323        }
324
325        fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
326            if self.none_as_null {
327                Ok(FirestoreValue::from(
328                    gcloud_sdk::google::firestore::v1::Value {
329                        value_type: Some(value::ValueType::NullValue(0)),
330                    },
331                ))
332            } else {
333                Ok(FirestoreValue::from(
334                    gcloud_sdk::google::firestore::v1::Value { value_type: None },
335                ))
336            }
337        }
338
339        fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<Self::Ok, Self::Error> {
340            value.serialize(self)
341        }
342
343        fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
344            Ok(FirestoreValue::from(
345                gcloud_sdk::google::firestore::v1::Value { value_type: None },
346            ))
347        }
348
349        fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> {
350            self.serialize_unit()
351        }
352
353        fn serialize_unit_variant(
354            self,
355            _name: &'static str,
356            _variant_index: u32,
357            variant: &'static str,
358        ) -> Result<Self::Ok, Self::Error> {
359            self.serialize_str(variant)
360        }
361
362        fn serialize_newtype_struct<T: ?Sized + Serialize>(
363            self,
364            _name: &'static str,
365            value: &T,
366        ) -> Result<Self::Ok, Self::Error> {
367            value.serialize(self)
368        }
369
370        fn serialize_newtype_variant<T: ?Sized + Serialize>(
371            self,
372            _name: &'static str,
373            _variant_index: u32,
374            _variant: &'static str,
375            _value: &T,
376        ) -> Result<Self::Ok, Self::Error> {
377            Err(FirestoreError::SerializeError(
378                FirestoreSerializationError::from_message(
379                    "Timestamp serializer doesn't support this type",
380                ),
381            ))
382        }
383
384        fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
385            Err(FirestoreError::SerializeError(
386                FirestoreSerializationError::from_message(
387                    "Timestamp serializer doesn't support this type",
388                ),
389            ))
390        }
391
392        fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
393            Err(FirestoreError::SerializeError(
394                FirestoreSerializationError::from_message(
395                    "Timestamp serializer doesn't support this type",
396                ),
397            ))
398        }
399
400        fn serialize_tuple_struct(
401            self,
402            _name: &'static str,
403            _len: usize,
404        ) -> Result<Self::SerializeTupleStruct, Self::Error> {
405            Err(FirestoreError::SerializeError(
406                FirestoreSerializationError::from_message(
407                    "Timestamp serializer doesn't support this type",
408                ),
409            ))
410        }
411
412        fn serialize_tuple_variant(
413            self,
414            _name: &'static str,
415            _variant_index: u32,
416            _variant: &'static str,
417            _len: usize,
418        ) -> Result<Self::SerializeTupleVariant, Self::Error> {
419            Err(FirestoreError::SerializeError(
420                FirestoreSerializationError::from_message(
421                    "Timestamp serializer doesn't support this type",
422                ),
423            ))
424        }
425
426        fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
427            Err(FirestoreError::SerializeError(
428                FirestoreSerializationError::from_message(
429                    "Timestamp serializer doesn't support this type",
430                ),
431            ))
432        }
433
434        fn serialize_struct(
435            self,
436            _name: &'static str,
437            _len: usize,
438        ) -> Result<Self::SerializeStruct, Self::Error> {
439            Err(FirestoreError::SerializeError(
440                FirestoreSerializationError::from_message(
441                    "Timestamp serializer doesn't support this type",
442                ),
443            ))
444        }
445
446        fn serialize_struct_variant(
447            self,
448            _name: &'static str,
449            _variant_index: u32,
450            _variant: &'static str,
451            _len: usize,
452        ) -> Result<Self::SerializeStructVariant, Self::Error> {
453            Err(FirestoreError::SerializeError(
454                FirestoreSerializationError::from_message(
455                    "Timestamp serializer doesn't support this type",
456                ),
457            ))
458        }
459    }
460
461    value.serialize(TimestampSerializer { none_as_null })
462}
463
464#[cfg(test)]
465mod tests {
466    use crate::{
467        firestore_document_from_serializable, firestore_document_to_serializable, FirestoreInstant,
468        FirestoreTimestamp,
469    };
470    use rvstruct::ValueStruct;
471    use serde::{Deserialize, Serialize};
472
473    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
474    struct TestStructure {
475        #[serde(with = "crate::serialize_as_timestamp")]
476        created_at: FirestoreInstant,
477        #[serde(default)]
478        #[serde(with = "crate::serialize_as_optional_timestamp")]
479        updated_at: Option<FirestoreInstant>,
480        wrapped_at: FirestoreTimestamp,
481    }
482
483    #[test]
484    fn test_timestamp_serde_roundtrip() {
485        let created_at: FirestoreInstant = "2022-12-02T16:53:20.123456789Z".parse().unwrap();
486        let wrapped_at: FirestoreInstant = "2018-01-01T00:00:00Z".parse().unwrap();
487
488        let test_structure = TestStructure {
489            created_at,
490            updated_at: Some(created_at),
491            wrapped_at: FirestoreTimestamp(wrapped_at),
492        };
493
494        let document =
495            firestore_document_from_serializable("test/doc-id", &test_structure).unwrap();
496
497        // The timestamps must land as Firestore timestamp values, not as strings
498        assert!(matches!(
499            document.fields["created_at"].value_type,
500            Some(gcloud_sdk::google::firestore::v1::value::ValueType::TimestampValue(_))
501        ));
502
503        let deserialized: TestStructure =
504            firestore_document_to_serializable(&document).expect("Unable to deserialize");
505
506        assert_eq!(deserialized, test_structure);
507        // Firestore timestamps keep nanosecond precision
508        assert_eq!(deserialized.created_at.subsec_nanosecond(), 123_456_789);
509    }
510
511    #[test]
512    fn test_firestore_precision_truncation() {
513        use std::str::FromStr;
514        use std::time::{Duration, SystemTime, UNIX_EPOCH};
515
516        // Firestore keeps microseconds, so the nanoseconds are dropped
517        assert_eq!(
518            FirestoreTimestamp::from_str("2022-12-02T16:53:20.123456789Z")
519                .unwrap()
520                .to_string(),
521            "2022-12-02T16:53:20.123456Z"
522        );
523
524        // Rounding down, not towards zero, so that the instants before the Unix
525        // epoch truncate the same way Firestore does
526        assert_eq!(
527            FirestoreTimestamp::from_str("1969-12-31T23:59:59.542758875Z")
528                .unwrap()
529                .to_string(),
530            "1969-12-31T23:59:59.542758Z"
531        );
532
533        // Every constructor and conversion applies it
534        let from_system_time: FirestoreTimestamp = (UNIX_EPOCH + Duration::new(1, 542_758_875))
535            .try_into()
536            .unwrap();
537        assert_eq!(from_system_time.to_string(), "1970-01-01T00:00:01.542758Z");
538
539        let from_proto: FirestoreTimestamp = gcloud_sdk::prost_types::Timestamp {
540            seconds: 1,
541            nanos: 542_758_875,
542        }
543        .try_into()
544        .unwrap();
545        assert_eq!(from_proto.to_string(), "1970-01-01T00:00:01.542758Z");
546
547        assert_eq!(FirestoreTimestamp::now().0.subsec_nanosecond() % 1_000, 0);
548
549        // Truncating is idempotent, and a value already at that precision is left alone
550        let already_truncated =
551            FirestoreTimestamp::from_str("2022-12-02T16:53:20.123456Z").unwrap();
552        assert_eq!(
553            already_truncated.truncated_to_firestore_precision(),
554            already_truncated
555        );
556
557        // The public field stays an exact escape hatch
558        let exact = FirestoreTimestamp(
559            FirestoreInstant::from_str("2022-12-02T16:53:20.123456789Z").unwrap(),
560        );
561        assert_eq!(exact.0.subsec_nanosecond(), 123_456_789);
562
563        let _ = SystemTime::from(exact);
564    }
565
566    #[test]
567    fn test_timestamp_conversions() {
568        use std::str::FromStr;
569        use std::time::SystemTime;
570
571        // Already at the precision Firestore stores, so nothing is truncated here
572        let ts = FirestoreTimestamp::from_str("2022-12-02T16:53:20.123456Z").unwrap();
573
574        // Display round trips through FromStr
575        assert_eq!(FirestoreTimestamp::from_str(&ts.to_string()).unwrap(), ts);
576
577        // SystemTime, infallible one way and fallible back
578        let system_time: SystemTime = ts.into();
579        assert_eq!(FirestoreTimestamp::try_from(system_time).unwrap(), ts);
580
581        // Google protobuf timestamps
582        let proto: gcloud_sdk::prost_types::Timestamp = ts.into();
583        assert_eq!(proto.seconds, 1670000000);
584        assert_eq!(proto.nanos, 123_456_000);
585        assert_eq!(FirestoreTimestamp::try_from(proto).unwrap(), ts);
586
587        // The inner instant, both ways
588        let instant: FirestoreInstant = ts.into();
589        assert_eq!(FirestoreTimestamp::from(instant), ts);
590        assert_eq!(ts.value(), &instant);
591
592        assert_eq!(
593            FirestoreTimestamp::try_from(SystemTime::UNIX_EPOCH).unwrap(),
594            FirestoreTimestamp::default()
595        );
596    }
597
598    /// Mirrors the structure used by `examples/timestamp.rs`, so that all the
599    /// documented ways of writing a Firestore timestamp stay verified.
600    #[test]
601    fn test_all_documented_timestamp_forms() {
602        use gcloud_sdk::google::firestore::v1::value::ValueType;
603        use std::time::SystemTime;
604
605        #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
606        struct ExampleStructure {
607            some_id: String,
608            created_at_system_time: std::time::SystemTime,
609            created_at: FirestoreTimestamp,
610            updated_at: Option<FirestoreTimestamp>,
611            updated_at_always_none: Option<FirestoreTimestamp>,
612            #[serde(with = "crate::serialize_as_timestamp")]
613            created_at_attr: FirestoreInstant,
614            #[serde(default)]
615            #[serde(with = "crate::serialize_as_optional_timestamp")]
616            updated_at_attr: Option<FirestoreInstant>,
617            #[serde(default)]
618            #[serde(with = "crate::serialize_as_optional_timestamp")]
619            updated_at_attr_always_none: Option<FirestoreInstant>,
620        }
621
622        let from_system_time: FirestoreTimestamp = SystemTime::now().try_into().unwrap();
623
624        let example = ExampleStructure {
625            some_id: "test-1".to_string(),
626            created_at_system_time: std::time::SystemTime::now(),
627            created_at: FirestoreTimestamp::now(),
628            updated_at: Some(from_system_time),
629            updated_at_always_none: None,
630            created_at_attr: FirestoreInstant::now(),
631            updated_at_attr: Some(FirestoreInstant::now()),
632            updated_at_attr_always_none: None,
633        };
634
635        let document = firestore_document_from_serializable("test/doc-id", &example).unwrap();
636
637        // Every populated form must land as a Firestore timestamp value, not a string
638        for field in [
639            "created_at",
640            "updated_at",
641            "created_at_attr",
642            "updated_at_attr",
643        ] {
644            assert!(
645                matches!(
646                    document.fields[field].value_type,
647                    Some(ValueType::TimestampValue(_))
648                ),
649                "{field} must serialize as a Firestore timestamp"
650            );
651        }
652
653        let deserialized: ExampleStructure =
654            firestore_document_to_serializable(&document).expect("Unable to deserialize");
655        assert_eq!(deserialized, example);
656    }
657
658    #[test]
659    fn test_optional_timestamp_none_roundtrip() {
660        let created_at: FirestoreInstant = "2022-12-02T16:53:20Z".parse().unwrap();
661
662        let test_structure = TestStructure {
663            created_at,
664            updated_at: None,
665            wrapped_at: FirestoreTimestamp(created_at),
666        };
667
668        let document =
669            firestore_document_from_serializable("test/doc-id", &test_structure).unwrap();
670
671        let deserialized: TestStructure =
672            firestore_document_to_serializable(&document).expect("Unable to deserialize");
673
674        assert_eq!(deserialized, test_structure);
675    }
676}