twilight_http/request/channel/thread/
update_thread.rs

1use crate::{
2    client::Client,
3    error::Error,
4    request::{self, AuditLogReason, Nullable, Request, TryIntoRequest},
5    response::{Response, ResponseFuture},
6    routing::Route,
7};
8use serde::Serialize;
9use std::future::IntoFuture;
10use twilight_model::{
11    channel::{thread::AutoArchiveDuration, Channel},
12    id::{
13        marker::{ChannelMarker, TagMarker},
14        Id,
15    },
16};
17use twilight_validate::{
18    channel::{
19        name as validate_name, rate_limit_per_user as validate_rate_limit_per_user,
20        ChannelValidationError,
21    },
22    request::{audit_reason as validate_audit_reason, ValidationError},
23};
24
25#[derive(Serialize)]
26struct UpdateThreadFields<'a> {
27    #[serde(skip_serializing_if = "Option::is_none")]
28    applied_tags: Option<Nullable<&'a [Id<TagMarker>]>>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    archived: Option<bool>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    auto_archive_duration: Option<AutoArchiveDuration>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    invitable: Option<bool>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    locked: Option<bool>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    name: Option<&'a str>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    rate_limit_per_user: Option<u16>,
41}
42
43/// Update a thread.
44///
45/// All fields are optional. The minimum length of the name is 1 UTF-16
46/// characters and the maximum is 100 UTF-16 characters.
47#[must_use = "requests must be configured and executed"]
48pub struct UpdateThread<'a> {
49    channel_id: Id<ChannelMarker>,
50    fields: Result<UpdateThreadFields<'a>, ChannelValidationError>,
51    http: &'a Client,
52    reason: Result<Option<&'a str>, ValidationError>,
53}
54
55impl<'a> UpdateThread<'a> {
56    pub(crate) const fn new(http: &'a Client, channel_id: Id<ChannelMarker>) -> Self {
57        Self {
58            channel_id,
59            fields: Ok(UpdateThreadFields {
60                applied_tags: None,
61                archived: None,
62                auto_archive_duration: None,
63                invitable: None,
64                locked: None,
65                name: None,
66                rate_limit_per_user: None,
67            }),
68            http,
69            reason: Ok(None),
70        }
71    }
72
73    /// Set the forum thread's applied tags.
74    pub fn applied_tags(mut self, applied_tags: Option<&'a [Id<TagMarker>]>) -> Self {
75        if let Ok(fields) = self.fields.as_mut() {
76            fields.applied_tags = Some(Nullable(applied_tags));
77        }
78
79        self
80    }
81
82    /// Set whether the thread is archived.
83    ///
84    /// Requires that the user have [`SEND_MESSAGES`] in the thread. However, if
85    /// the thread is locked, the user must have [`MANAGE_THREADS`].
86    ///
87    /// [`SEND_MESSAGES`]: twilight_model::guild::Permissions::SEND_MESSAGES
88    /// [`MANAGE_THREADS`]: twilight_model::guild::Permissions::MANAGE_THREADS
89    pub fn archived(mut self, archived: bool) -> Self {
90        if let Ok(fields) = self.fields.as_mut() {
91            fields.archived = Some(archived);
92        }
93
94        self
95    }
96
97    /// Set the thread's auto archive duration.
98    ///
99    /// Automatic archive durations are not locked behind the guild's boost
100    /// level.
101    pub fn auto_archive_duration(mut self, auto_archive_duration: AutoArchiveDuration) -> Self {
102        if let Ok(fields) = self.fields.as_mut() {
103            fields.auto_archive_duration = Some(auto_archive_duration);
104        }
105
106        self
107    }
108
109    /// Whether non-moderators can add other non-moderators to a thread.
110    pub fn invitable(mut self, invitable: bool) -> Self {
111        if let Ok(fields) = self.fields.as_mut() {
112            fields.invitable = Some(invitable);
113        }
114
115        self
116    }
117
118    /// Set whether the thread is locked.
119    ///
120    /// If the thread is already locked, only users with [`MANAGE_THREADS`] can
121    /// unlock it.
122    ///
123    /// [`MANAGE_THREADS`]: twilight_model::guild::Permissions::MANAGE_THREADS
124    pub fn locked(mut self, locked: bool) -> Self {
125        if let Ok(fields) = self.fields.as_mut() {
126            fields.locked = Some(locked);
127        }
128
129        self
130    }
131
132    /// Set the name of the thread.
133    ///
134    /// Must be between 1 and 100 characters in length.
135    ///
136    /// # Errors
137    ///
138    /// Returns an error of type [`NameInvalid`] if the name is invalid.
139    ///
140    /// [`NameInvalid`]: twilight_validate::channel::ChannelValidationErrorType::NameInvalid
141    pub fn name(mut self, name: &'a str) -> Self {
142        self.fields = self.fields.and_then(|mut fields| {
143            validate_name(name)?;
144            fields.name = Some(name);
145
146            Ok(fields)
147        });
148
149        self
150    }
151
152    /// Set the number of seconds that a user must wait before before they are
153    /// able to send another message.
154    ///
155    /// The minimum is 0 and the maximum is 21600. This is also known as "Slow
156    /// Mode". See [Discord Docs/Channel Object].
157    ///
158    /// # Errors
159    ///
160    /// Returns an error of type [`RateLimitPerUserInvalid`] if the name is
161    /// invalid.
162    ///
163    /// [`RateLimitPerUserInvalid`]: twilight_validate::channel::ChannelValidationErrorType::RateLimitPerUserInvalid
164    /// [Discord Docs/Channel Object]: https://discordapp.com/developers/docs/resources/channel#channel-object-channel-structure
165    pub fn rate_limit_per_user(mut self, rate_limit_per_user: u16) -> Self {
166        self.fields = self.fields.and_then(|mut fields| {
167            validate_rate_limit_per_user(rate_limit_per_user)?;
168            fields.rate_limit_per_user = Some(rate_limit_per_user);
169
170            Ok(fields)
171        });
172
173        self
174    }
175}
176
177impl<'a> AuditLogReason<'a> for UpdateThread<'a> {
178    fn reason(mut self, reason: &'a str) -> Self {
179        self.reason = validate_audit_reason(reason).and(Ok(Some(reason)));
180
181        self
182    }
183}
184
185impl IntoFuture for UpdateThread<'_> {
186    type Output = Result<Response<Channel>, Error>;
187
188    type IntoFuture = ResponseFuture<Channel>;
189
190    fn into_future(self) -> Self::IntoFuture {
191        let http = self.http;
192
193        match self.try_into_request() {
194            Ok(request) => http.request(request),
195            Err(source) => ResponseFuture::error(source),
196        }
197    }
198}
199
200impl TryIntoRequest for UpdateThread<'_> {
201    fn try_into_request(self) -> Result<Request, Error> {
202        let fields = self.fields.map_err(Error::validation)?;
203        let mut request = Request::builder(&Route::UpdateChannel {
204            channel_id: self.channel_id.get(),
205        })
206        .json(&fields);
207
208        if let Some(reason) = self.reason.map_err(Error::validation)? {
209            request = request.headers(request::audit_header(reason)?);
210        }
211
212        request.build()
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::{UpdateThread, UpdateThreadFields};
219    use crate::{
220        request::{Request, TryIntoRequest},
221        routing::Route,
222        Client,
223    };
224    use std::error::Error;
225    use twilight_model::id::Id;
226
227    #[test]
228    fn request() -> Result<(), Box<dyn Error>> {
229        let client = Client::new("token".to_string());
230        let channel_id = Id::new(123);
231
232        let actual = UpdateThread::new(&client, channel_id)
233            .rate_limit_per_user(60)
234            .try_into_request()?;
235
236        let expected = Request::builder(&Route::UpdateChannel {
237            channel_id: channel_id.get(),
238        })
239        .json(&UpdateThreadFields {
240            applied_tags: None,
241            archived: None,
242            auto_archive_duration: None,
243            invitable: None,
244            locked: None,
245            name: None,
246            rate_limit_per_user: Some(60),
247        })
248        .build()?;
249
250        assert_eq!(expected.body(), actual.body());
251        assert_eq!(expected.path(), actual.path());
252        assert_eq!(expected.ratelimit_path(), actual.ratelimit_path());
253
254        Ok(())
255    }
256}