Skip to main content

twilight_http/request/user/
update_current_user.rs

1#[cfg(not(target_os = "wasi"))]
2use crate::response::{Response, ResponseFuture};
3use crate::{
4    client::Client,
5    error::Error,
6    request::{self, AuditLogReason, Nullable, Request, TryIntoRequest},
7    routing::Route,
8};
9use serde::Serialize;
10use std::future::IntoFuture;
11use twilight_model::user::User;
12use twilight_validate::request::{
13    ValidationError, audit_reason as validate_audit_reason, username as validate_username,
14};
15
16#[derive(Serialize)]
17struct UpdateCurrentUserFields<'a> {
18    #[serde(skip_serializing_if = "Option::is_none")]
19    avatar: Option<Nullable<&'a str>>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    banner: Option<Nullable<&'a str>>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    username: Option<&'a str>,
24}
25
26/// Update the current user.
27///
28/// All parameters are optional. If the username is changed, it may cause the discriminator to be
29/// randomized.
30#[must_use = "requests must be configured and executed"]
31pub struct UpdateCurrentUser<'a> {
32    fields: Result<UpdateCurrentUserFields<'a>, ValidationError>,
33    http: &'a Client,
34    reason: Result<Option<&'a str>, ValidationError>,
35}
36
37impl<'a> UpdateCurrentUser<'a> {
38    pub(crate) const fn new(http: &'a Client) -> Self {
39        Self {
40            fields: Ok(UpdateCurrentUserFields {
41                avatar: None,
42                banner: None,
43                username: None,
44            }),
45            http,
46            reason: Ok(None),
47        }
48    }
49
50    /// Set the user's avatar.
51    ///
52    /// This must be a Data URI, in the form of
53    /// `data:image/{type};base64,{data}` where `{type}` is the image MIME type
54    /// and `{data}` is the base64-encoded image. See [Discord Docs/Image Data].
55    ///
56    /// [Discord Docs/Image Data]: https://discord.com/developers/docs/reference#image-data
57    pub const fn avatar(mut self, avatar: Option<&'a str>) -> Self {
58        if let Ok(fields) = self.fields.as_mut() {
59            fields.avatar = Some(Nullable(avatar));
60        }
61
62        self
63    }
64
65    /// Set the user's banner.
66    ///
67    /// This must be a Data URI, in the form of
68    /// `data:image/{type};base64,{data}` where `{type}` is the image MIME type
69    /// and `{data}` is the base64-encoded image. See [Discord Docs/Image Data].
70    ///
71    /// [Discord Docs/Image Data]: https://discord.com/developers/docs/reference#image-data
72    pub const fn banner(mut self, banner: Option<&'a str>) -> Self {
73        if let Ok(fields) = self.fields.as_mut() {
74            fields.banner = Some(Nullable(banner));
75        }
76
77        self
78    }
79
80    /// Set the username.
81    ///
82    /// The minimum length is 1 UTF-16 character and the maximum is 32 UTF-16 characters.
83    ///
84    /// # Errors
85    ///
86    /// Returns an error of type [`Username`] if the username length is too
87    /// short or too long.
88    ///
89    /// [`Username`]: twilight_validate::request::ValidationErrorType::Username
90    pub fn username(mut self, username: &'a str) -> Self {
91        self.fields = self.fields.and_then(|mut fields| {
92            validate_username(username)?;
93            fields.username.replace(username);
94
95            Ok(fields)
96        });
97
98        self
99    }
100}
101
102impl<'a> AuditLogReason<'a> for UpdateCurrentUser<'a> {
103    fn reason(mut self, reason: &'a str) -> Self {
104        self.reason = validate_audit_reason(reason).and(Ok(Some(reason)));
105
106        self
107    }
108}
109
110#[cfg(not(target_os = "wasi"))]
111impl IntoFuture for UpdateCurrentUser<'_> {
112    type Output = Result<Response<User>, Error>;
113
114    type IntoFuture = ResponseFuture<User>;
115
116    fn into_future(self) -> Self::IntoFuture {
117        let http = self.http;
118
119        match self.try_into_request() {
120            Ok(request) => http.request(request),
121            Err(source) => ResponseFuture::error(source),
122        }
123    }
124}
125
126impl TryIntoRequest for UpdateCurrentUser<'_> {
127    fn try_into_request(self) -> Result<Request, Error> {
128        let fields = self.fields.map_err(Error::validation)?;
129
130        let mut request = Request::builder(&Route::UpdateCurrentUser).json(&fields);
131
132        if let Some(reason) = self.reason.map_err(Error::validation)? {
133            request = request.headers(request::audit_header(reason)?);
134        }
135
136        request.build()
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use std::error::Error;
144
145    #[test]
146    fn test_clear_attachment() -> Result<(), Box<dyn Error>> {
147        let client = Client::new("token".into());
148
149        {
150            let expected = r"{}";
151            let actual = UpdateCurrentUser::new(&client).try_into_request()?;
152
153            assert_eq!(Some(expected.as_bytes()), actual.body());
154        }
155
156        {
157            let expected = r#"{"avatar":null}"#;
158            let actual = UpdateCurrentUser::new(&client)
159                .avatar(None)
160                .try_into_request()?;
161
162            assert_eq!(Some(expected.as_bytes()), actual.body());
163
164            let expected = r#"{"avatar":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI"}"#;
165            let actual = UpdateCurrentUser::new(&client).avatar(Some("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI")).try_into_request()?;
166
167            assert_eq!(Some(expected.as_bytes()), actual.body());
168        }
169
170        {
171            let expected = r#"{"username":"other side"}"#;
172            let actual = UpdateCurrentUser::new(&client)
173                .username("other side")
174                .try_into_request()?;
175
176            assert_eq!(Some(expected.as_bytes()), actual.body());
177        }
178
179        {
180            let expected = r#"{"avatar":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI","username":"other side"}"#;
181            let actual = UpdateCurrentUser::new(&client).avatar(Some("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI")).username("other side").try_into_request()?;
182
183            assert_eq!(Some(expected.as_bytes()), actual.body());
184        }
185        Ok(())
186    }
187}