twilight_http/request/guild/create_guild/
mod.rs

1use crate::{
2    client::Client,
3    error::Error as HttpError,
4    request::{Request, TryIntoRequest},
5    response::{Response, ResponseFuture},
6    routing::Route,
7};
8use serde::Serialize;
9use std::future::IntoFuture;
10use std::{
11    error::Error,
12    fmt::{Display, Formatter, Result as FmtResult},
13};
14use twilight_model::{
15    channel::ChannelType,
16    guild::{
17        AfkTimeout, DefaultMessageNotificationLevel, ExplicitContentFilter, PartialGuild,
18        Permissions, SystemChannelFlags, VerificationLevel,
19    },
20    http::permission_overwrite::PermissionOverwrite,
21    id::{
22        marker::{ChannelMarker, RoleMarker},
23        Id,
24    },
25};
26use twilight_validate::request::guild_name as validate_guild_name;
27
28mod builder;
29
30pub use self::builder::*;
31
32/// The error returned when the guild can not be created as configured.
33#[derive(Debug)]
34pub struct CreateGuildError {
35    kind: CreateGuildErrorType,
36    source: Option<Box<dyn Error + Send + Sync>>,
37}
38
39impl CreateGuildError {
40    /// Immutable reference to the type of error that occurred.
41    #[must_use = "retrieving the type has no effect if left unused"]
42    pub const fn kind(&self) -> &CreateGuildErrorType {
43        &self.kind
44    }
45
46    /// Consume the error, returning the source error if there is any.
47    #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
48    pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
49        self.source
50    }
51
52    /// Consume the error, returning the owned error type and the source error.
53    #[must_use = "consuming the error into its parts has no effect if left unused"]
54    pub fn into_parts(self) -> (CreateGuildErrorType, Option<Box<dyn Error + Send + Sync>>) {
55        (self.kind, self.source)
56    }
57}
58
59impl Display for CreateGuildError {
60    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
61        match &self.kind {
62            CreateGuildErrorType::NameInvalid { .. } => f.write_str("the guild name is invalid"),
63            CreateGuildErrorType::TooManyChannels { .. } => {
64                f.write_str("too many channels were provided")
65            }
66            CreateGuildErrorType::TooManyRoles { .. } => {
67                f.write_str("too many roles were provided")
68            }
69        }
70    }
71}
72
73impl Error for CreateGuildError {}
74
75/// Type of [`CreateGuildError`] that occurred.
76#[derive(Debug)]
77#[non_exhaustive]
78pub enum CreateGuildErrorType {
79    /// The name of the guild is either fewer than 2 UTF-16 characters or more than 100 UTF-16
80    /// characters.
81    NameInvalid {
82        /// Provided name.
83        name: String,
84    },
85    /// The number of channels provided is too many.
86    ///
87    /// The maximum amount is 500.
88    TooManyChannels {
89        /// Provided channels.
90        channels: Vec<GuildChannelFields>,
91    },
92    /// The number of roles provided is too many.
93    ///
94    /// The maximum amount is 250.
95    TooManyRoles {
96        /// Provided roles.
97        roles: Vec<RoleFields>,
98    },
99}
100
101#[derive(Serialize)]
102struct CreateGuildFields<'a> {
103    #[serde(skip_serializing_if = "Option::is_none")]
104    afk_channel_id: Option<Id<ChannelMarker>>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    afk_timeout: Option<AfkTimeout>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    channels: Option<Vec<GuildChannelFields>>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    default_message_notifications: Option<DefaultMessageNotificationLevel>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    explicit_content_filter: Option<ExplicitContentFilter>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    icon: Option<&'a str>,
115    name: String,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    roles: Option<Vec<RoleFields>>,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    system_channel_id: Option<Id<ChannelMarker>>,
120    #[serde(skip_serializing_if = "Option::is_none")]
121    system_channel_flags: Option<SystemChannelFlags>,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    verification_level: Option<VerificationLevel>,
124}
125
126/// Role fields sent to Discord.
127///
128/// Use [`RoleFieldsBuilder`] to build one.
129#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
130pub struct RoleFields {
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub color: Option<u32>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub hoist: Option<bool>,
135    pub id: Id<RoleMarker>,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub mentionable: Option<bool>,
138    pub name: String,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub permissions: Option<Permissions>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub position: Option<i64>,
143}
144
145/// Variants of channel fields sent to Discord.
146///
147/// Use [`GuildChannelFieldsBuilder`] to build one.
148#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
149#[non_exhaustive]
150#[serde(untagged)]
151pub enum GuildChannelFields {
152    Category(CategoryFields),
153    Text(TextFields),
154    Voice(VoiceFields),
155}
156
157impl GuildChannelFields {
158    pub const fn id(&self) -> Id<ChannelMarker> {
159        match self {
160            Self::Category(c) => c.id,
161            Self::Text(t) => t.id,
162            Self::Voice(v) => v.id,
163        }
164    }
165}
166
167/// Category channel fields sent to Discord.
168///
169/// Use [`CategoryFieldsBuilder`] to build one.
170#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
171pub struct CategoryFields {
172    pub id: Id<ChannelMarker>,
173    #[serde(rename = "type")]
174    pub kind: ChannelType,
175    pub name: String,
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub permission_overwrites: Option<Vec<PermissionOverwrite>>,
178}
179
180/// Text channel fields sent to Discord.
181///
182/// Use [`TextFieldsBuilder`] to build one.
183#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
184pub struct TextFields {
185    pub id: Id<ChannelMarker>,
186    #[serde(rename = "type")]
187    pub kind: ChannelType,
188    pub name: String,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub nsfw: Option<bool>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub permission_overwrites: Option<Vec<PermissionOverwrite>>,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub parent_id: Option<Id<ChannelMarker>>,
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub rate_limit_per_user: Option<u16>,
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub topic: Option<String>,
199}
200
201/// Voice channel fields sent to Discord.
202///
203/// Use [`VoiceFieldsBuilder`] to build one.
204#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
205pub struct VoiceFields {
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub bitrate: Option<u32>,
208    pub id: Id<ChannelMarker>,
209    #[serde(rename = "type")]
210    pub kind: ChannelType,
211    pub name: String,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub permission_overwrites: Option<Vec<PermissionOverwrite>>,
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub parent_id: Option<Id<ChannelMarker>>,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub user_limit: Option<u16>,
218}
219
220/// Create a new request to create a guild.
221///
222/// The minimum length of the name is 2 UTF-16 characters and the maximum is 100 UTF-16 characters.
223/// This endpoint can only be used by bots in less than 10 guilds.
224#[must_use = "requests must be configured and executed"]
225pub struct CreateGuild<'a> {
226    fields: Result<CreateGuildFields<'a>, CreateGuildError>,
227    http: &'a Client,
228}
229
230impl<'a> CreateGuild<'a> {
231    pub(crate) fn new(http: &'a Client, name: String) -> Self {
232        let fields = Ok(CreateGuildFields {
233            afk_channel_id: None,
234            afk_timeout: None,
235            channels: None,
236            default_message_notifications: None,
237            explicit_content_filter: None,
238            icon: None,
239            name: String::new(),
240            roles: None,
241            system_channel_id: None,
242            system_channel_flags: None,
243            verification_level: None,
244        })
245        .and_then(|mut fields| {
246            validate_guild_name(&name).map_err(|source| CreateGuildError {
247                kind: CreateGuildErrorType::NameInvalid { name: name.clone() },
248                source: Some(Box::new(source)),
249            })?;
250
251            fields.name = name;
252
253            Ok(fields)
254        });
255
256        Self { fields, http }
257    }
258
259    /// Add a role to the list of roles.
260    #[allow(clippy::missing_panics_doc)]
261    pub fn add_role(mut self, role: RoleFields) -> Self {
262        if let Ok(fields) = self.fields.as_mut() {
263            if fields.roles.is_none() {
264                let builder = RoleFieldsBuilder::new("@everyone".to_owned());
265                fields.roles.replace(vec![builder.build().unwrap()]);
266            }
267
268            if let Some(roles) = fields.roles.as_mut() {
269                roles.push(role);
270            }
271        }
272
273        self
274    }
275
276    /// Set the ID of the AFK voice channel.
277    ///
278    /// This must be an ID specified in [`channels`].
279    ///
280    /// [`channels`]: Self::channels
281    pub fn afk_channel_id(mut self, afk_channel_id: Id<ChannelMarker>) -> Self {
282        if let Ok(fields) = self.fields.as_mut() {
283            fields.afk_channel_id = Some(afk_channel_id);
284        }
285
286        self
287    }
288
289    /// Set the AFK timeout, in seconds.
290    pub fn afk_timeout(mut self, afk_timeout: AfkTimeout) -> Self {
291        if let Ok(fields) = self.fields.as_mut() {
292            fields.afk_timeout = Some(afk_timeout);
293        }
294
295        self
296    }
297
298    /// Set the channels to create with the guild.
299    ///
300    /// The maximum number of channels that can be provided is 500.
301    ///
302    /// # Examples
303    ///
304    /// ```no_run
305    /// use twilight_http::{
306    ///     request::guild::create_guild::{
307    ///         CategoryFieldsBuilder, GuildChannelFieldsBuilder, TextFieldsBuilder, VoiceFieldsBuilder,
308    ///     },
309    ///     Client,
310    /// };
311    /// # #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> {
312    /// # let client = Client::new("my token".to_owned());
313    ///
314    /// let text = TextFieldsBuilder::new("text channel".to_owned()).build()?;
315    /// let voice = VoiceFieldsBuilder::new("voice channel".to_owned()).build()?;
316    /// let text2 = TextFieldsBuilder::new("other text channel".to_owned())
317    ///     .topic("posting".to_owned())
318    ///     .build()?;
319    ///
320    /// let category = CategoryFieldsBuilder::new("category channel".to_owned())
321    ///     .add_text(text2)
322    ///     .add_voice(voice);
323    ///
324    /// let channels = GuildChannelFieldsBuilder::new()
325    ///     .add_text(text)
326    ///     .add_category_builder(category)
327    ///     .build()?;
328    ///
329    /// let guild = client
330    ///     .create_guild("guild name".to_owned())
331    ///     .channels(channels)
332    ///     .await?;
333    /// # Ok(()) }
334    /// ```
335    ///
336    /// # Errors
337    ///
338    /// Returns a [`CreateGuildErrorType::TooManyChannels`] error type if the
339    /// number of channels is over 500.
340    pub fn channels(mut self, channels: Vec<GuildChannelFields>) -> Self {
341        self.fields = self.fields.and_then(|mut fields| {
342            // Error 30013
343            // <https://discordapp.com/developers/docs/topics/opcodes-and-status-codes#json>
344            if channels.len() > 500 {
345                return Err(CreateGuildError {
346                    kind: CreateGuildErrorType::TooManyChannels { channels },
347                    source: None,
348                });
349            }
350
351            fields.channels.replace(channels);
352
353            Ok(fields)
354        });
355
356        self
357    }
358
359    /// Set the default message notification level. See
360    /// [Discord Docs/Create Guild].
361    ///
362    /// [Discord Docs/Create Guild]: https://discord.com/developers/docs/resources/guild#create-guild
363    pub fn default_message_notifications(
364        mut self,
365        default_message_notifications: DefaultMessageNotificationLevel,
366    ) -> Self {
367        if let Ok(fields) = self.fields.as_mut() {
368            fields.default_message_notifications = Some(default_message_notifications);
369        }
370
371        self
372    }
373
374    /// Set the explicit content filter level.
375    pub fn explicit_content_filter(
376        mut self,
377        explicit_content_filter: ExplicitContentFilter,
378    ) -> Self {
379        if let Ok(fields) = self.fields.as_mut() {
380            fields.explicit_content_filter = Some(explicit_content_filter);
381        }
382
383        self
384    }
385
386    /// Set the icon.
387    ///
388    /// This must be a Data URI, in the form of
389    /// `data:image/{type};base64,{data}` where `{type}` is the image MIME type
390    /// and `{data}` is the base64-encoded image. See [Discord Docs/Image Data].
391    ///
392    /// [Discord Docs/Image Data]: https://discord.com/developers/docs/reference#image-data
393    pub fn icon(mut self, icon: &'a str) -> Self {
394        if let Ok(fields) = self.fields.as_mut() {
395            fields.icon.replace(icon);
396        }
397
398        self
399    }
400
401    /// Override the everyone role of the guild.
402    ///
403    /// If there are not yet roles set with [`roles`], this will create a role override in the
404    /// first position. Discord understands the first role in the list to override @everyone.
405    /// If there are roles, this replaces the first role in the position.
406    ///
407    /// [`roles`]: Self::roles
408    pub fn override_everyone(mut self, everyone: RoleFields) -> Self {
409        if let Ok(fields) = self.fields.as_mut() {
410            if let Some(roles) = fields.roles.as_mut() {
411                roles.remove(0);
412                roles.insert(0, everyone);
413            } else {
414                fields.roles.replace(vec![everyone]);
415            }
416        }
417
418        self
419    }
420
421    /// Set the channel where system messages will be posted.
422    ///
423    /// This must be an ID specified in [`channels`].
424    ///
425    /// [`channels`]: Self::channels
426    pub fn system_channel_id(mut self, system_channel_id: Id<ChannelMarker>) -> Self {
427        if let Ok(fields) = self.fields.as_mut() {
428            fields.system_channel_id = Some(system_channel_id);
429        }
430
431        self
432    }
433
434    /// Set the guild's [`SystemChannelFlags`].
435    pub fn system_channel_flags(mut self, system_channel_flags: SystemChannelFlags) -> Self {
436        if let Ok(fields) = self.fields.as_mut() {
437            fields.system_channel_flags = Some(system_channel_flags);
438        }
439
440        self
441    }
442
443    /// Set the roles to create with the guild.
444    ///
445    /// The maximum number of roles that can be provided is 250.
446    ///
447    /// # Examples
448    ///
449    /// ```no_run
450    /// use twilight_http::{request::guild::create_guild::RoleFieldsBuilder, Client};
451    /// # #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> {
452    /// # let client = Client::new("my token".to_owned());
453    ///
454    /// let roles = vec![RoleFieldsBuilder::new("role 1".to_owned())
455    ///     .color(0x543923)
456    ///     .build()?];
457    /// client
458    ///     .create_guild("guild name".to_owned())
459    ///     .roles(roles)
460    ///     .await?;
461    /// # Ok(()) }
462    /// ```
463    ///
464    /// # Errors
465    ///
466    /// Returns a [`CreateGuildErrorType::TooManyRoles`] error type if the
467    /// number of roles is over 250.
468    #[allow(clippy::missing_panics_doc)]
469    pub fn roles(mut self, mut roles: Vec<RoleFields>) -> Self {
470        self.fields = self.fields.and_then(|mut fields| {
471            if roles.len() > 250 {
472                return Err(CreateGuildError {
473                    kind: CreateGuildErrorType::TooManyRoles { roles },
474                    source: None,
475                });
476            }
477
478            if let Some(prev_roles) = fields.roles.as_mut() {
479                roles.insert(0, prev_roles.remove(0));
480            } else {
481                let builder = RoleFieldsBuilder::new("@everyone".to_owned());
482                roles.insert(0, builder.build().unwrap());
483            }
484
485            fields.roles.replace(roles);
486
487            Ok(fields)
488        });
489
490        self
491    }
492}
493
494impl IntoFuture for CreateGuild<'_> {
495    type Output = Result<Response<PartialGuild>, HttpError>;
496
497    type IntoFuture = ResponseFuture<PartialGuild>;
498
499    fn into_future(self) -> Self::IntoFuture {
500        let http = self.http;
501
502        match self.try_into_request() {
503            Ok(request) => http.request(request),
504            Err(source) => ResponseFuture::error(source),
505        }
506    }
507}
508
509impl TryIntoRequest for CreateGuild<'_> {
510    fn try_into_request(self) -> Result<Request, HttpError> {
511        let fields = self.fields.map_err(HttpError::validation)?;
512
513        Request::builder(&Route::CreateGuild).json(&fields).build()
514    }
515}