twilight_model/user/
current_user.rs

1use super::{DiscriminatorDisplay, PremiumType, UserFlags};
2use crate::{
3    id::{marker::UserMarker, Id},
4    util::image_hash::ImageHash,
5};
6use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
9pub struct CurrentUser {
10    /// Accent color of the user's banner.
11    ///
12    /// This is an integer representation of a hexadecimal color code.
13    pub accent_color: Option<u32>,
14    /// User's avatar hash.
15    ///
16    /// To retrieve the url to the avatar, see [Discord Docs/Image Formatting].
17    ///
18    /// [Discord Docs/Image Formatting]: https://discord.com/developers/docs/reference#image-formatting
19    pub avatar: Option<ImageHash>,
20    /// Hash of the user's banner image.
21    pub banner: Option<ImageHash>,
22    /// Whether the user belongs to an OAuth2 application.
23    #[serde(default)]
24    pub bot: bool,
25    /// Discriminator used to differentiate people with the same username.
26    ///
27    /// # Formatting
28    ///
29    /// Because discriminators are stored as an integer they're not in the
30    /// format of Discord user tags due to a lack of padding with zeros. The
31    /// [`discriminator`] method can be used to retrieve a formatter to pad the
32    /// discriminator with zeros.
33    ///
34    /// # serde
35    ///
36    /// The discriminator field can be deserialized from either a string or an
37    /// integer. The field will always serialize into a string due to that being
38    /// the type Discord's API uses.
39    ///
40    /// [`discriminator`]: Self::discriminator
41    #[serde(with = "super::discriminator")]
42    pub discriminator: u16,
43    /// User's email address associated to the account.
44    ///
45    /// Requires the `email` oauth scope. See [Discord Docs/User Object].
46    ///
47    /// [Discord Docs/User Object]: https://discord.com/developers/docs/resources/user#user-object-user-structure
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub email: Option<String>,
50    /// All flags on a user's account.
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub flags: Option<UserFlags>,
53    /// User's global display name, if set. For bots, this is the application name.
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub global_name: Option<String>,
56    /// User's id.
57    pub id: Id<UserMarker>,
58    /// User's chosen language option.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub locale: Option<String>,
61    /// Whether the user has two factor enabled on their account.
62    pub mfa_enabled: bool,
63    /// User's username, not unique across the platform.
64    #[serde(rename = "username")]
65    pub name: String,
66    /// Type of Nitro subscription on a user's account.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub premium_type: Option<PremiumType>,
69    /// Public flags on a user's account.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub public_flags: Option<UserFlags>,
72    /// Whether the email on this account has been verified.
73    ///
74    /// Requires the `email` oauth scope. See [Discord Docs/User Object].
75    ///
76    /// [Discord Docs/User Object]: https://discord.com/developers/docs/resources/user#user-object-user-structure
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub verified: Option<bool>,
79}
80
81impl CurrentUser {
82    /// Create a [`Display`] formatter for a user discriminator that pads the
83    /// discriminator with zeros up to 4 digits.
84    ///
85    /// [`Display`]: core::fmt::Display
86    pub const fn discriminator(&self) -> DiscriminatorDisplay {
87        DiscriminatorDisplay::new(self.discriminator)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::{CurrentUser, PremiumType, UserFlags};
94    use crate::{id::Id, test::image_hash};
95    use serde_test::Token;
96
97    fn user_tokens(discriminator_token: Token) -> Vec<Token> {
98        vec![
99            Token::Struct {
100                name: "CurrentUser",
101                len: 12,
102            },
103            Token::Str("accent_color"),
104            Token::Some,
105            Token::U32(16_711_680),
106            Token::Str("avatar"),
107            Token::Some,
108            Token::Str(image_hash::AVATAR_INPUT),
109            Token::Str("banner"),
110            Token::None,
111            Token::Str("bot"),
112            Token::Bool(true),
113            Token::Str("discriminator"),
114            discriminator_token,
115            Token::Str("id"),
116            Token::NewtypeStruct { name: "Id" },
117            Token::Str("1"),
118            Token::Str("locale"),
119            Token::Some,
120            Token::Str("test locale"),
121            Token::Str("mfa_enabled"),
122            Token::Bool(true),
123            Token::Str("username"),
124            Token::Str("test name"),
125            Token::Str("premium_type"),
126            Token::Some,
127            Token::U8(1),
128            Token::Str("public_flags"),
129            Token::Some,
130            Token::U64(1),
131            Token::Str("verified"),
132            Token::Some,
133            Token::Bool(true),
134            Token::StructEnd,
135        ]
136    }
137
138    fn user_tokens_complete(discriminator_token: Token) -> Vec<Token> {
139        vec![
140            Token::Struct {
141                name: "CurrentUser",
142                len: 15,
143            },
144            Token::Str("accent_color"),
145            Token::None,
146            Token::Str("avatar"),
147            Token::Some,
148            Token::Str(image_hash::AVATAR_INPUT),
149            Token::Str("banner"),
150            Token::Some,
151            Token::Str(image_hash::BANNER_INPUT),
152            Token::Str("bot"),
153            Token::Bool(true),
154            Token::Str("discriminator"),
155            discriminator_token,
156            Token::Str("email"),
157            Token::Some,
158            Token::Str("test@example.com"),
159            Token::Str("flags"),
160            Token::Some,
161            Token::U64(1),
162            Token::Str("global_name"),
163            Token::Some,
164            Token::Str("twilight sparkle"),
165            Token::Str("id"),
166            Token::NewtypeStruct { name: "Id" },
167            Token::Str("1"),
168            Token::Str("locale"),
169            Token::Some,
170            Token::Str("test locale"),
171            Token::Str("mfa_enabled"),
172            Token::Bool(true),
173            Token::Str("username"),
174            Token::Str("test name"),
175            Token::Str("premium_type"),
176            Token::Some,
177            Token::U8(1),
178            Token::Str("public_flags"),
179            Token::Some,
180            Token::U64(1),
181            Token::Str("verified"),
182            Token::Some,
183            Token::Bool(true),
184            Token::StructEnd,
185        ]
186    }
187
188    #[test]
189    fn current_user() {
190        let value = CurrentUser {
191            accent_color: Some(16_711_680),
192            avatar: Some(image_hash::AVATAR),
193            banner: None,
194            bot: true,
195            discriminator: 9999,
196            email: None,
197            id: Id::new(1),
198            mfa_enabled: true,
199            name: "test name".to_owned(),
200            verified: Some(true),
201            premium_type: Some(PremiumType::NitroClassic),
202            public_flags: Some(UserFlags::STAFF),
203            flags: None,
204            locale: Some("test locale".to_owned()),
205            global_name: None,
206        };
207
208        // Deserializing a current user with a string discriminator (which
209        // Discord provides)
210        serde_test::assert_tokens(&value, &user_tokens(Token::Str("9999")));
211
212        // Deserializing a current user with an integer discriminator. Userland
213        // code may have this due to being a more compact memory representation
214        // of a discriminator.
215        serde_test::assert_de_tokens(&value, &user_tokens(Token::U64(9999)));
216    }
217
218    #[test]
219    fn current_user_complete() {
220        let value = CurrentUser {
221            accent_color: None,
222            avatar: Some(image_hash::AVATAR),
223            banner: Some(image_hash::BANNER),
224            bot: true,
225            discriminator: 9999,
226            email: Some("test@example.com".to_owned()),
227            flags: Some(UserFlags::STAFF),
228            global_name: Some("twilight sparkle".to_owned()),
229            id: Id::new(1),
230            locale: Some("test locale".to_owned()),
231            mfa_enabled: true,
232            name: "test name".to_owned(),
233            premium_type: Some(PremiumType::NitroClassic),
234            public_flags: Some(UserFlags::STAFF),
235            verified: Some(true),
236        };
237
238        // Deserializing a current user with a string discriminator (which
239        // Discord provides)
240        serde_test::assert_tokens(&value, &user_tokens_complete(Token::Str("9999")));
241
242        // Deserializing a current user with an integer discriminator. Userland
243        // code may have this due to being a more compact memory representation
244        // of a discriminator.
245        serde_test::assert_de_tokens(&value, &user_tokens_complete(Token::U64(9999)));
246    }
247}