Skip to main content

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