twilight_model/
visitor.rs

1/// Deserializers for optional nullable fields.
2///
3/// Some booleans in the Discord API are null when true, and not present when
4/// false. `serde` doesn't have a way of natively handling this, so we need some
5/// custom (de)serialization magic. [`RoleTags`] in particular has these fields.
6///
7/// [`RoleTags`]: crate::guild::RoleTags
8pub mod null_boolean {
9    use serde::{
10        de::{Deserializer, Error as DeError, Visitor},
11        ser::Serializer,
12    };
13    use std::fmt::{Formatter, Result as FmtResult};
14
15    struct NullBooleanVisitor;
16
17    impl Visitor<'_> for NullBooleanVisitor {
18        type Value = bool;
19
20        fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
21            f.write_str("null")
22        }
23
24        fn visit_none<E: DeError>(self) -> Result<Self::Value, E> {
25            Ok(true)
26        }
27
28        // `visit_none` is used by `serde_json` when a present `null` value is
29        // encountered, but other implementations - such as `simd_json` - may
30        // use `visit_unit` instead.
31        fn visit_unit<E: DeError>(self) -> Result<Self::Value, E> {
32            Ok(true)
33        }
34    }
35
36    // Clippy will say this bool can be taken by value, but we need it to be
37    // passed by reference because that's what serde does.
38    #[allow(clippy::trivially_copy_pass_by_ref)]
39    pub fn serialize<S: Serializer>(_: &bool, serializer: S) -> Result<S::Ok, S::Error> {
40        serializer.serialize_none()
41    }
42
43    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
44        deserializer.deserialize_option(NullBooleanVisitor)
45    }
46}
47
48/// (De)serializers for IDs that can be "zero", parsing as None in the case of
49/// being zero.
50///
51/// This is a bug on Discord's end, but has been rather consistent for some
52/// model fields such as [`ForumTag::emoji_id`].
53///
54/// [`ForumTag::emoji_id`]: crate::channel::forum::ForumTag
55pub mod zeroable_id {
56    use crate::id::Id;
57    use serde::{
58        de::{Deserializer, Error as DeError, Visitor},
59        ser::Serializer,
60        Deserialize,
61    };
62    use std::{
63        fmt::{Formatter, Result as FmtResult},
64        marker::PhantomData,
65        str::FromStr,
66    };
67
68    struct ZeroableIdVisitor<T> {
69        phantom: PhantomData<T>,
70    }
71
72    impl<'de, T> Visitor<'de> for ZeroableIdVisitor<T> {
73        type Value = Option<Id<T>>;
74
75        fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
76            f.write_str(r#"ID, 0, "0", or null"#)
77        }
78
79        fn visit_newtype_struct<D: Deserializer<'de>>(
80            self,
81            deserializer: D,
82        ) -> Result<Self::Value, D::Error> {
83            let stringified_number = String::deserialize(deserializer)?;
84
85            self.visit_str(&stringified_number)
86        }
87
88        fn visit_none<E: DeError>(self) -> Result<Self::Value, E> {
89            Ok(None)
90        }
91
92        fn visit_some<D: Deserializer<'de>>(
93            self,
94            deserializer: D,
95        ) -> Result<Self::Value, D::Error> {
96            deserializer.deserialize_any(self)
97        }
98
99        fn visit_str<E: DeError>(self, v: &str) -> Result<Self::Value, E> {
100            Id::from_str(v).map(Some).map_err(DeError::custom)
101        }
102
103        fn visit_u64<E: DeError>(self, v: u64) -> Result<Self::Value, E> {
104            Ok(Id::new_checked(v))
105        }
106
107        fn visit_unit<E: DeError>(self) -> Result<Self::Value, E> {
108            Ok(None)
109        }
110    }
111
112    // Clippy will say this bool can be taken by value, but we need it to be
113    // passed by reference because that's what serde does.
114    #[allow(clippy::ref_option, clippy::trivially_copy_pass_by_ref)]
115    pub fn serialize<S: Serializer, T>(
116        value: &Option<Id<T>>,
117        serializer: S,
118    ) -> Result<S::Ok, S::Error> {
119        if let Some(id) = value {
120            serializer.serialize_some(id)
121        } else {
122            serializer.serialize_none()
123        }
124    }
125
126    pub fn deserialize<'de, D: Deserializer<'de>, T>(
127        deserializer: D,
128    ) -> Result<Option<Id<T>>, D::Error> {
129        deserializer.deserialize_any(ZeroableIdVisitor::<T> {
130            phantom: PhantomData,
131        })
132    }
133}