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::{Channel, thread::AutoArchiveDuration},
12    id::{
13        Id,
14        marker::{ChannelMarker, TagMarker},
15    },
16};
17use twilight_validate::{
18    channel::{
19        ChannelValidationError, name as validate_name,
20        rate_limit_per_user as validate_rate_limit_per_user,
21    },
22    request::{ValidationError, audit_reason as validate_audit_reason},
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 const 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 const 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 const fn auto_archive_duration(
102        mut self,
103        auto_archive_duration: AutoArchiveDuration,
104    ) -> Self {
105        if let Ok(fields) = self.fields.as_mut() {
106            fields.auto_archive_duration = Some(auto_archive_duration);
107        }
108
109        self
110    }
111
112    /// Whether non-moderators can add other non-moderators to a thread.
113    pub const fn invitable(mut self, invitable: bool) -> Self {
114        if let Ok(fields) = self.fields.as_mut() {
115            fields.invitable = Some(invitable);
116        }
117
118        self
119    }
120
121    /// Set whether the thread is locked.
122    ///
123    /// If the thread is already locked, only users with [`MANAGE_THREADS`] can
124    /// unlock it.
125    ///
126    /// [`MANAGE_THREADS`]: twilight_model::guild::Permissions::MANAGE_THREADS
127    pub const fn locked(mut self, locked: bool) -> Self {
128        if let Ok(fields) = self.fields.as_mut() {
129            fields.locked = Some(locked);
130        }
131
132        self
133    }
134
135    /// Set the name of the thread.
136    ///
137    /// Must be between 1 and 100 characters in length.
138    ///
139    /// # Errors
140    ///
141    /// Returns an error of type [`NameInvalid`] if the name is invalid.
142    ///
143    /// [`NameInvalid`]: twilight_validate::channel::ChannelValidationErrorType::NameInvalid
144    pub fn name(mut self, name: &'a str) -> Self {
145        self.fields = self.fields.and_then(|mut fields| {
146            validate_name(name)?;
147            fields.name = Some(name);
148
149            Ok(fields)
150        });
151
152        self
153    }
154
155    /// Set the number of seconds that a user must wait before before they are
156    /// able to send another message.
157    ///
158    /// The minimum is 0 and the maximum is 21600. This is also known as "Slow
159    /// Mode". See [Discord Docs/Channel Object].
160    ///
161    /// # Errors
162    ///
163    /// Returns an error of type [`RateLimitPerUserInvalid`] if the name is
164    /// invalid.
165    ///
166    /// [`RateLimitPerUserInvalid`]: twilight_validate::channel::ChannelValidationErrorType::RateLimitPerUserInvalid
167    /// [Discord Docs/Channel Object]: https://discordapp.com/developers/docs/resources/channel#channel-object-channel-structure
168    pub fn rate_limit_per_user(mut self, rate_limit_per_user: u16) -> Self {
169        self.fields = self.fields.and_then(|mut fields| {
170            validate_rate_limit_per_user(rate_limit_per_user)?;
171            fields.rate_limit_per_user = Some(rate_limit_per_user);
172
173            Ok(fields)
174        });
175
176        self
177    }
178}
179
180impl<'a> AuditLogReason<'a> for UpdateThread<'a> {
181    fn reason(mut self, reason: &'a str) -> Self {
182        self.reason = validate_audit_reason(reason).and(Ok(Some(reason)));
183
184        self
185    }
186}
187
188impl IntoFuture for UpdateThread<'_> {
189    type Output = Result<Response<Channel>, Error>;
190
191    type IntoFuture = ResponseFuture<Channel>;
192
193    fn into_future(self) -> Self::IntoFuture {
194        let http = self.http;
195
196        match self.try_into_request() {
197            Ok(request) => http.request(request),
198            Err(source) => ResponseFuture::error(source),
199        }
200    }
201}
202
203impl TryIntoRequest for UpdateThread<'_> {
204    fn try_into_request(self) -> Result<Request, Error> {
205        let fields = self.fields.map_err(Error::validation)?;
206        let mut request = Request::builder(&Route::UpdateChannel {
207            channel_id: self.channel_id.get(),
208        })
209        .json(&fields);
210
211        if let Some(reason) = self.reason.map_err(Error::validation)? {
212            request = request.headers(request::audit_header(reason)?);
213        }
214
215        request.build()
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::{UpdateThread, UpdateThreadFields};
222    use crate::{
223        Client,
224        request::{Request, TryIntoRequest},
225        routing::Route,
226    };
227    use std::error::Error;
228    use twilight_model::id::Id;
229
230    #[test]
231    fn request() -> Result<(), Box<dyn Error>> {
232        let client = Client::new("token".to_string());
233        let channel_id = Id::new(123);
234
235        let actual = UpdateThread::new(&client, channel_id)
236            .rate_limit_per_user(60)
237            .try_into_request()?;
238
239        let expected = Request::builder(&Route::UpdateChannel {
240            channel_id: channel_id.get(),
241        })
242        .json(&UpdateThreadFields {
243            applied_tags: None,
244            archived: None,
245            auto_archive_duration: None,
246            invitable: None,
247            locked: None,
248            name: None,
249            rate_limit_per_user: Some(60),
250        })
251        .build()?;
252
253        assert_eq!(expected.body(), actual.body());
254        assert_eq!(expected.path(), actual.path());
255
256        Ok(())
257    }
258}