twilight_http/request/channel/invite/
create_invite.rs

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