Skip to main content

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