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 id.
54    pub id: Id<UserMarker>,
55    /// User's chosen language option.
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub locale: Option<String>,
58    /// Whether the user has two factor enabled on their account.
59    pub mfa_enabled: bool,
60    /// User's username, not unique across the platform.
61    #[serde(rename = "username")]
62    pub name: String,
63    /// Type of Nitro subscription on a user's account.
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub premium_type: Option<PremiumType>,
66    /// Public flags on a user's account.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub public_flags: Option<UserFlags>,
69    /// Whether the email on this account has been verified.
70    ///
71    /// Requires the `email` oauth scope. See [Discord Docs/User Object].
72    ///
73    /// [Discord Docs/User Object]: https://discord.com/developers/docs/resources/user#user-object-user-structure
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub verified: Option<bool>,
76}
77
78impl CurrentUser {
79    /// Create a [`Display`] formatter for a user discriminator that pads the
80    /// discriminator with zeros up to 4 digits.
81    ///
82    /// [`Display`]: core::fmt::Display
83    pub const fn discriminator(&self) -> DiscriminatorDisplay {
84        DiscriminatorDisplay::new(self.discriminator)
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::{CurrentUser, PremiumType, UserFlags};
91    use crate::{id::Id, test::image_hash};
92    use serde_test::Token;
93
94    fn user_tokens(discriminator_token: Token) -> Vec<Token> {
95        vec![
96            Token::Struct {
97                name: "CurrentUser",
98                len: 12,
99            },
100            Token::Str("accent_color"),
101            Token::Some,
102            Token::U32(16_711_680),
103            Token::Str("avatar"),
104            Token::Some,
105            Token::Str(image_hash::AVATAR_INPUT),
106            Token::Str("banner"),
107            Token::None,
108            Token::Str("bot"),
109            Token::Bool(true),
110            Token::Str("discriminator"),
111            discriminator_token,
112            Token::Str("id"),
113            Token::NewtypeStruct { name: "Id" },
114            Token::Str("1"),
115            Token::Str("locale"),
116            Token::Some,
117            Token::Str("test locale"),
118            Token::Str("mfa_enabled"),
119            Token::Bool(true),
120            Token::Str("username"),
121            Token::Str("test name"),
122            Token::Str("premium_type"),
123            Token::Some,
124            Token::U8(1),
125            Token::Str("public_flags"),
126            Token::Some,
127            Token::U64(1),
128            Token::Str("verified"),
129            Token::Some,
130            Token::Bool(true),
131            Token::StructEnd,
132        ]
133    }
134
135    fn user_tokens_complete(discriminator_token: Token) -> Vec<Token> {
136        vec![
137            Token::Struct {
138                name: "CurrentUser",
139                len: 14,
140            },
141            Token::Str("accent_color"),
142            Token::None,
143            Token::Str("avatar"),
144            Token::Some,
145            Token::Str(image_hash::AVATAR_INPUT),
146            Token::Str("banner"),
147            Token::Some,
148            Token::Str(image_hash::BANNER_INPUT),
149            Token::Str("bot"),
150            Token::Bool(true),
151            Token::Str("discriminator"),
152            discriminator_token,
153            Token::Str("email"),
154            Token::Some,
155            Token::Str("test@example.com"),
156            Token::Str("flags"),
157            Token::Some,
158            Token::U64(1),
159            Token::Str("id"),
160            Token::NewtypeStruct { name: "Id" },
161            Token::Str("1"),
162            Token::Str("locale"),
163            Token::Some,
164            Token::Str("test locale"),
165            Token::Str("mfa_enabled"),
166            Token::Bool(true),
167            Token::Str("username"),
168            Token::Str("test name"),
169            Token::Str("premium_type"),
170            Token::Some,
171            Token::U8(1),
172            Token::Str("public_flags"),
173            Token::Some,
174            Token::U64(1),
175            Token::Str("verified"),
176            Token::Some,
177            Token::Bool(true),
178            Token::StructEnd,
179        ]
180    }
181
182    #[test]
183    fn current_user() {
184        let value = CurrentUser {
185            accent_color: Some(16_711_680),
186            avatar: Some(image_hash::AVATAR),
187            banner: None,
188            bot: true,
189            discriminator: 9999,
190            email: None,
191            id: Id::new(1),
192            mfa_enabled: true,
193            name: "test name".to_owned(),
194            verified: Some(true),
195            premium_type: Some(PremiumType::NitroClassic),
196            public_flags: Some(UserFlags::STAFF),
197            flags: None,
198            locale: Some("test locale".to_owned()),
199        };
200
201        // Deserializing a current user with a string discriminator (which
202        // Discord provides)
203        serde_test::assert_tokens(&value, &user_tokens(Token::Str("9999")));
204
205        // Deserializing a current user with an integer discriminator. Userland
206        // code may have this due to being a more compact memory representation
207        // of a discriminator.
208        serde_test::assert_de_tokens(&value, &user_tokens(Token::U64(9999)));
209    }
210
211    #[test]
212    fn current_user_complete() {
213        let value = CurrentUser {
214            accent_color: None,
215            avatar: Some(image_hash::AVATAR),
216            banner: Some(image_hash::BANNER),
217            bot: true,
218            discriminator: 9999,
219            email: Some("test@example.com".to_owned()),
220            id: Id::new(1),
221            mfa_enabled: true,
222            name: "test name".to_owned(),
223            verified: Some(true),
224            premium_type: Some(PremiumType::NitroClassic),
225            public_flags: Some(UserFlags::STAFF),
226            flags: Some(UserFlags::STAFF),
227            locale: Some("test locale".to_owned()),
228        };
229
230        // Deserializing a current user with a string discriminator (which
231        // Discord provides)
232        serde_test::assert_tokens(&value, &user_tokens_complete(Token::Str("9999")));
233
234        // Deserializing a current user with an integer discriminator. Userland
235        // code may have this due to being a more compact memory representation
236        // of a discriminator.
237        serde_test::assert_de_tokens(&value, &user_tokens_complete(Token::U64(9999)));
238    }
239}