Skip to main content

twilight_http/request/channel/invite/
create_invite.rs

1#[cfg(not(target_os = "wasi"))]
2use crate::response::{Response, ResponseFuture};
3use crate::{
4    client::Client,
5    error::Error,
6    request::{self, AuditLogReason, Request, TryIntoRequest},
7    routing::Route,
8};
9use serde::Serialize;
10use std::future::IntoFuture;
11use twilight_model::{
12    guild::invite::{Invite, TargetType},
13    id::{
14        Id,
15        marker::{ApplicationMarker, ChannelMarker, UserMarker},
16    },
17};
18use twilight_validate::request::{
19    ValidationError, audit_reason as validate_audit_reason,
20    invite_max_age as validate_invite_max_age, invite_max_uses as validate_invite_max_uses,
21};
22
23#[derive(Serialize)]
24struct CreateInviteFields {
25    #[serde(skip_serializing_if = "Option::is_none")]
26    max_age: Option<u32>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    max_uses: Option<u16>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    temporary: Option<bool>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    target_application_id: Option<Id<ApplicationMarker>>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    target_user_id: Option<Id<UserMarker>>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    target_type: Option<TargetType>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    unique: Option<bool>,
39}
40
41/// Create an invite, with options.
42///
43/// Requires the [`CREATE_INVITE`] permission.
44///
45/// # Examples
46///
47/// ```no_run
48/// use twilight_http::Client;
49/// use twilight_model::id::Id;
50///
51/// # #[tokio::main]
52/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
53/// let client = Client::new("my token".to_owned());
54///
55/// let channel_id = Id::new(123);
56/// let invite = client.create_invite(channel_id).max_uses(3).await?;
57/// # Ok(()) }
58/// ```
59///
60/// [`CREATE_INVITE`]: twilight_model::guild::Permissions::CREATE_INVITE
61#[must_use = "requests must be configured and executed"]
62pub struct CreateInvite<'a> {
63    channel_id: Id<ChannelMarker>,
64    fields: Result<CreateInviteFields, ValidationError>,
65    http: &'a Client,
66    reason: Result<Option<&'a str>, ValidationError>,
67}
68
69impl<'a> CreateInvite<'a> {
70    pub(crate) const fn new(http: &'a Client, channel_id: Id<ChannelMarker>) -> Self {
71        Self {
72            channel_id,
73            fields: Ok(CreateInviteFields {
74                max_age: None,
75                max_uses: None,
76                temporary: None,
77                target_application_id: None,
78                target_user_id: None,
79                target_type: None,
80                unique: None,
81            }),
82            http,
83            reason: Ok(None),
84        }
85    }
86
87    /// Set the maximum age for an invite.
88    ///
89    /// If no age is specified, Discord sets the age to 86400 seconds, or 24 hours.
90    /// Set to 0 to never expire.
91    ///
92    /// # Examples
93    ///
94    /// Create an invite for a channel with a maximum of 1 use and an age of 1
95    /// hour:
96    ///
97    /// ```no_run
98    /// use std::env;
99    /// use twilight_http::Client;
100    /// use twilight_model::id::Id;
101    ///
102    /// # #[tokio::main]
103    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
104    /// let client = Client::new(env::var("DISCORD_TOKEN")?);
105    /// let invite = client
106    ///     .create_invite(Id::new(1))
107    ///     .max_age(60 * 60)
108    ///     .await?
109    ///     .model()
110    ///     .await?;
111    ///
112    /// println!("invite code: {}", invite.code);
113    /// # Ok(()) }
114    /// ```
115    ///
116    /// # Errors
117    ///
118    /// Returns an error of type [`InviteMaxAge`] if the age is invalid.
119    ///
120    /// [`InviteMaxAge`]: twilight_validate::request::ValidationErrorType::InviteMaxAge
121    pub fn max_age(mut self, max_age: u32) -> Self {
122        self.fields = self.fields.and_then(|mut fields| {
123            validate_invite_max_age(max_age)?;
124            fields.max_age = Some(max_age);
125
126            Ok(fields)
127        });
128
129        self
130    }
131
132    /// Set the maximum uses for an invite, or 0 for infinite.
133    ///
134    /// Discord defaults this to 0, or infinite.
135    ///
136    /// # Examples
137    ///
138    /// Create an invite for a channel with a maximum of 5 uses:
139    ///
140    /// ```no_run
141    /// use std::env;
142    /// use twilight_http::Client;
143    /// use twilight_model::id::Id;
144    ///
145    /// # #[tokio::main]
146    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
147    /// let client = Client::new(env::var("DISCORD_TOKEN")?);
148    /// let invite = client
149    ///     .create_invite(Id::new(1))
150    ///     .max_uses(5)
151    ///     .await?
152    ///     .model()
153    ///     .await?;
154    ///
155    /// println!("invite code: {}", invite.code);
156    /// # Ok(()) }
157    /// ```
158    ///
159    /// # Errors
160    ///
161    /// Returns an error of type [`InviteMaxUses`] if the uses is invalid.
162    ///
163    /// [`InviteMaxUses`]: twilight_validate::request::ValidationErrorType::InviteMaxUses
164    pub fn max_uses(mut self, max_uses: u16) -> Self {
165        self.fields = self.fields.and_then(|mut fields| {
166            validate_invite_max_uses(max_uses)?;
167            fields.max_uses = Some(max_uses);
168
169            Ok(fields)
170        });
171
172        self
173    }
174
175    /// Set the target application ID for this invite.
176    ///
177    /// This only works if [`target_type`] is set to [`TargetType::EmbeddedApplication`].
178    ///
179    /// [`target_type`]: Self::target_type
180    pub const fn target_application_id(
181        mut self,
182        target_application_id: Id<ApplicationMarker>,
183    ) -> Self {
184        if let Ok(fields) = self.fields.as_mut() {
185            fields.target_application_id = Some(target_application_id);
186        }
187
188        self
189    }
190
191    /// Set the target user id for this invite.
192    pub const fn target_user_id(mut self, target_user_id: Id<UserMarker>) -> Self {
193        if let Ok(fields) = self.fields.as_mut() {
194            fields.target_user_id = Some(target_user_id);
195        }
196
197        self
198    }
199
200    /// Set the target type for this invite.
201    pub const fn target_type(mut self, target_type: TargetType) -> Self {
202        if let Ok(fields) = self.fields.as_mut() {
203            fields.target_type = Some(target_type);
204        }
205
206        self
207    }
208
209    /// Specify true if the invite should grant temporary membership.
210    ///
211    /// Defaults to false.
212    pub const fn temporary(mut self, temporary: bool) -> Self {
213        if let Ok(fields) = self.fields.as_mut() {
214            fields.temporary = Some(temporary);
215        }
216
217        self
218    }
219
220    /// Specify true if the invite should be unique. Defaults to false.
221    ///
222    /// If true, don't try to reuse a similar invite (useful for creating many
223    /// unique one time use invites). See [Discord Docs/Create Channel Invite].
224    ///
225    /// [Discord Docs/Create Channel Invite]: https://discord.com/developers/docs/resources/channel#create-channel-invite
226    pub const fn unique(mut self, unique: bool) -> Self {
227        if let Ok(fields) = self.fields.as_mut() {
228            fields.unique = Some(unique);
229        }
230
231        self
232    }
233}
234
235impl<'a> AuditLogReason<'a> for CreateInvite<'a> {
236    fn reason(mut self, reason: &'a str) -> Self {
237        self.reason = validate_audit_reason(reason).and(Ok(Some(reason)));
238
239        self
240    }
241}
242
243#[cfg(not(target_os = "wasi"))]
244impl IntoFuture for CreateInvite<'_> {
245    type Output = Result<Response<Invite>, Error>;
246
247    type IntoFuture = ResponseFuture<Invite>;
248
249    fn into_future(self) -> Self::IntoFuture {
250        let http = self.http;
251
252        match self.try_into_request() {
253            Ok(request) => http.request(request),
254            Err(source) => ResponseFuture::error(source),
255        }
256    }
257}
258
259impl TryIntoRequest for CreateInvite<'_> {
260    fn try_into_request(self) -> Result<Request, Error> {
261        let fields = self.fields.map_err(Error::validation)?;
262        let mut request = Request::builder(&Route::CreateInvite {
263            channel_id: self.channel_id.get(),
264        })
265        .json(&fields);
266
267        if let Some(reason) = self.reason.map_err(Error::validation)? {
268            request = request.headers(request::audit_header(reason)?);
269        }
270
271        request.build()
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::CreateInvite;
278    use crate::Client;
279    use std::error::Error;
280    use twilight_model::id::Id;
281
282    #[test]
283    fn max_age() -> Result<(), Box<dyn Error>> {
284        let client = Client::new("foo".to_owned());
285        let mut builder = CreateInvite::new(&client, Id::new(1)).max_age(0);
286        assert_eq!(Some(0), builder.fields.as_ref().unwrap().max_age);
287        builder = builder.max_age(604_800);
288        assert_eq!(Some(604_800), builder.fields.as_ref().unwrap().max_age);
289        builder = builder.max_age(604_801);
290        assert!(builder.fields.is_err());
291
292        Ok(())
293    }
294
295    #[test]
296    fn max_uses() -> Result<(), Box<dyn Error>> {
297        let client = Client::new("foo".to_owned());
298        let mut builder = CreateInvite::new(&client, Id::new(1)).max_uses(0);
299        assert_eq!(Some(0), builder.fields.as_ref().unwrap().max_uses);
300        builder = builder.max_uses(100);
301        assert_eq!(Some(100), builder.fields.as_ref().unwrap().max_uses);
302        builder = builder.max_uses(101);
303        assert!(builder.fields.is_err());
304
305        Ok(())
306    }
307}