Skip to main content

twilight_util/builder/
command.rs

1//! Create a [`Command`] with a builder.
2//!
3//! # Examples
4//!
5//! ```
6//! use twilight_model::application::command::CommandType;
7//! use twilight_util::builder::command::{BooleanBuilder, CommandBuilder, StringBuilder};
8//!
9//! CommandBuilder::new(
10//!     "blep",
11//!     "Send a random adorable animal photo",
12//!     CommandType::ChatInput,
13//! )
14//! .option(
15//!     StringBuilder::new("animal", "The type of animal")
16//!         .required(true)
17//!         .choices([
18//!             ("Dog", "animal_dog"),
19//!             ("Cat", "animal_cat"),
20//!             ("Penguin", "animal_penguin"),
21//!         ]),
22//! )
23//! .option(BooleanBuilder::new(
24//!     "only_smol",
25//!     "Whether to show only baby animals",
26//! ));
27//! ```
28//!
29//! ```
30//! use twilight_model::application::command::CommandType;
31//! use twilight_util::builder::command::{CommandBuilder, NumberBuilder};
32//!
33//! CommandBuilder::new(
34//!     "birthday",
35//!     "Wish a friend a happy birthday",
36//!     CommandType::ChatInput,
37//! )
38//! .name_localizations([("zh-CN", "生日"), ("el", "γενέθλια")])
39//! .description_localizations([("zh-Cn", "祝你朋友生日快乐")])
40//! .option(
41//!     NumberBuilder::new("age", "Your friend's age")
42//!         .name_localizations([("zh-CN", "岁数")])
43//!         .description_localizations([("zh-CN", "你朋友的岁数")]),
44//! );
45//! ```
46
47use twilight_model::{
48    application::{
49        command::{
50            Command, CommandOption, CommandOptionChoice, CommandOptionChoiceValue,
51            CommandOptionType, CommandType,
52        },
53        interaction::InteractionContextType,
54    },
55    channel::ChannelType,
56    guild::Permissions,
57    id::{Id, marker::GuildMarker},
58    oauth::ApplicationIntegrationType,
59};
60use twilight_validate::command::{CommandValidationError, command as validate_command};
61
62/// Builder to create a [`Command`].
63#[derive(Clone, Debug)]
64#[must_use = "must be built into a command"]
65pub struct CommandBuilder(Command);
66
67impl CommandBuilder {
68    /// Create a new default [`Command`] builder.
69    #[must_use = "builders have no effect if unused"]
70    #[allow(deprecated)]
71    pub fn new(name: impl Into<String>, description: impl Into<String>, kind: CommandType) -> Self {
72        Self(Command {
73            application_id: None,
74            default_member_permissions: None,
75            dm_permission: None,
76            description: description.into(),
77            description_localizations: None,
78            guild_id: None,
79            id: None,
80            kind,
81            name: name.into(),
82            name_localizations: None,
83            nsfw: None,
84            options: Vec::new(),
85            version: Id::new(1),
86            contexts: None,
87            integration_types: None,
88        })
89    }
90
91    /// Consume the builder, returning a [`Command`].
92    #[must_use = "must be built into a command"]
93    pub fn build(self) -> Command {
94        self.0
95    }
96
97    /// Ensure the command is valid.
98    ///
99    /// # Errors
100    ///
101    /// Refer to the errors section of [`twilight_validate::command::command`]
102    /// for possible errors.
103    pub fn validate(self) -> Result<Self, CommandValidationError> {
104        validate_command(&self.0)?;
105
106        Ok(self)
107    }
108
109    /// Set the guild ID of the command.
110    ///
111    /// Defaults to [`None`].
112    pub const fn guild_id(mut self, guild_id: Id<GuildMarker>) -> Self {
113        self.0.guild_id = Some(guild_id);
114
115        self
116    }
117
118    /// Set the contexts of the command.
119    ///
120    /// Defaults to nothing.
121    pub fn contexts(mut self, contexts: impl IntoIterator<Item = InteractionContextType>) -> Self {
122        self.0.contexts = Some(contexts.into_iter().collect());
123
124        self
125    }
126
127    /// Set the default member permission required to run the command.
128    ///
129    /// Defaults to [`None`].
130    pub const fn default_member_permissions(
131        mut self,
132        default_member_permissions: Permissions,
133    ) -> Self {
134        self.0.default_member_permissions = Some(default_member_permissions);
135
136        self
137    }
138
139    /// Set whether the command is available in DMs.
140    ///
141    /// Defaults to [`None`].
142    #[deprecated(note = "use contexts instead")]
143    #[allow(deprecated)]
144    pub const fn dm_permission(mut self, dm_permission: bool) -> Self {
145        self.0.dm_permission = Some(dm_permission);
146
147        self
148    }
149
150    /// Set the localization dictionary for the command description.
151    ///
152    /// Defaults to [`None`].
153    pub fn description_localizations<K: Into<String>, V: Into<String>>(
154        mut self,
155        localizations: impl IntoIterator<Item = (K, V)>,
156    ) -> Self {
157        self.0.description_localizations = Some(
158            localizations
159                .into_iter()
160                .map(|(a, b)| (a.into(), b.into()))
161                .collect(),
162        );
163
164        self
165    }
166
167    /// Set the integration types for the command.
168    ///
169    /// Defaults to `None`.
170    pub fn integration_types(
171        mut self,
172        integration_types: impl IntoIterator<Item = ApplicationIntegrationType>,
173    ) -> Self {
174        self.0.integration_types = Some(integration_types.into_iter().collect());
175
176        self
177    }
178
179    /// Set the localization dictionary for the command name.
180    ///
181    /// Defaults to [`None`].
182    pub fn name_localizations<K: Into<String>, V: Into<String>>(
183        mut self,
184        localizations: impl IntoIterator<Item = (K, V)>,
185    ) -> Self {
186        self.0.name_localizations = Some(
187            localizations
188                .into_iter()
189                .map(|(a, b)| (a.into(), b.into()))
190                .collect(),
191        );
192
193        self
194    }
195
196    /// Add an option to the command.
197    ///
198    /// Defaults to an empty list.
199    pub fn option(self, option: impl Into<CommandOption>) -> Self {
200        self._option(option.into())
201    }
202
203    fn _option(mut self, option: CommandOption) -> Self {
204        self.0.options.push(option);
205
206        self
207    }
208
209    /// Set whether the command is age-restricted.
210    ///
211    /// Defaults to not being specified, which uses Discord's default.
212    pub const fn nsfw(mut self, nsfw: bool) -> Self {
213        self.0.nsfw = Some(nsfw);
214
215        self
216    }
217}
218
219/// Create an attachment option with a builder.
220#[derive(Clone, Debug)]
221#[must_use = "should be used in a command builder"]
222pub struct AttachmentBuilder(CommandOption);
223
224impl AttachmentBuilder {
225    /// Create a new default [`AttachmentBuilder`].
226    #[must_use = "builders have no effect if unused"]
227    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
228        Self(CommandOption {
229            autocomplete: None,
230            channel_types: None,
231            choices: None,
232            description: description.into(),
233            description_localizations: None,
234            kind: CommandOptionType::Attachment,
235            max_length: None,
236            max_value: None,
237            min_length: None,
238            min_value: None,
239            name: name.into(),
240            name_localizations: None,
241            options: None,
242            file_types: None,
243            required: None,
244        })
245    }
246
247    /// Consume the builder, returning the built command option.
248    #[must_use = "should be used in a command builder"]
249    pub fn build(self) -> CommandOption {
250        self.0
251    }
252
253    /// Set the localization dictionary for the option description.
254    ///
255    /// Defaults to [`None`].
256    pub fn description_localizations<K: Into<String>, V: Into<String>>(
257        mut self,
258        localizations: impl IntoIterator<Item = (K, V)>,
259    ) -> Self {
260        self.0.description_localizations = Some(
261            localizations
262                .into_iter()
263                .map(|(a, b)| (a.into(), b.into()))
264                .collect(),
265        );
266
267        self
268    }
269
270    /// Set the localization dictionary for the option name.
271    ///
272    /// Defaults to [`None`].
273    pub fn name_localizations<K: Into<String>, V: Into<String>>(
274        mut self,
275        localizations: impl IntoIterator<Item = (K, V)>,
276    ) -> Self {
277        self.0.name_localizations = Some(
278            localizations
279                .into_iter()
280                .map(|(a, b)| (a.into(), b.into()))
281                .collect(),
282        );
283
284        self
285    }
286
287    /// Set the file types to filter for.
288    ///
289    /// Can be `image`, `video`, `audio`, or any dot-prefixed extension such as `.pdf`.
290    ///
291    /// Defaults to [`None`].
292    pub fn file_types(mut self, file_types: impl IntoIterator<Item = impl Into<String>>) -> Self {
293        self.0.file_types = Some(file_types.into_iter().map(Into::into).collect());
294
295        self
296    }
297
298    /// Set whether this option is required.
299    ///
300    /// Defaults to `false`.
301    pub const fn required(mut self, required: bool) -> Self {
302        self.0.required = Some(required);
303
304        self
305    }
306}
307
308impl From<AttachmentBuilder> for CommandOption {
309    fn from(builder: AttachmentBuilder) -> CommandOption {
310        builder.build()
311    }
312}
313
314/// Create a boolean option with a builder.
315#[derive(Clone, Debug)]
316#[must_use = "should be used in a command builder"]
317pub struct BooleanBuilder(CommandOption);
318
319impl BooleanBuilder {
320    /// Create a new default [`BooleanBuilder`].
321    #[must_use = "builders have no effect if unused"]
322    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
323        Self(CommandOption {
324            autocomplete: None,
325            channel_types: None,
326            choices: None,
327            description: description.into(),
328            description_localizations: None,
329            kind: CommandOptionType::Boolean,
330            max_length: None,
331            max_value: None,
332            min_length: None,
333            min_value: None,
334            name: name.into(),
335            name_localizations: None,
336            options: None,
337            file_types: None,
338            required: None,
339        })
340    }
341
342    /// Consume the builder, returning the built command option.
343    #[must_use = "should be used in a command builder"]
344    pub fn build(self) -> CommandOption {
345        self.0
346    }
347
348    /// Set the localization dictionary for the option description.
349    ///
350    /// Defaults to [`None`].
351    pub fn description_localizations<K: Into<String>, V: Into<String>>(
352        mut self,
353        localizations: impl IntoIterator<Item = (K, V)>,
354    ) -> Self {
355        self.0.description_localizations = Some(
356            localizations
357                .into_iter()
358                .map(|(a, b)| (a.into(), b.into()))
359                .collect(),
360        );
361
362        self
363    }
364
365    /// Set the localization dictionary for the option name.
366    ///
367    /// Defaults to [`None`].
368    pub fn name_localizations<K: Into<String>, V: Into<String>>(
369        mut self,
370        localizations: impl IntoIterator<Item = (K, V)>,
371    ) -> Self {
372        self.0.name_localizations = Some(
373            localizations
374                .into_iter()
375                .map(|(a, b)| (a.into(), b.into()))
376                .collect(),
377        );
378
379        self
380    }
381
382    /// Set whether this option is required.
383    ///
384    /// Defaults to `false`.
385    pub const fn required(mut self, required: bool) -> Self {
386        self.0.required = Some(required);
387
388        self
389    }
390}
391
392impl From<BooleanBuilder> for CommandOption {
393    fn from(builder: BooleanBuilder) -> CommandOption {
394        builder.build()
395    }
396}
397
398/// Create a channel option with a builder.
399#[derive(Clone, Debug)]
400#[must_use = "should be used in a command builder"]
401pub struct ChannelBuilder(CommandOption);
402
403impl ChannelBuilder {
404    /// Create a new default [`ChannelBuilder`].
405    #[must_use = "builders have no effect if unused"]
406    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
407        Self(CommandOption {
408            autocomplete: None,
409            channel_types: Some(Vec::new()),
410            choices: None,
411            description: description.into(),
412            description_localizations: None,
413            kind: CommandOptionType::Channel,
414            max_length: None,
415            max_value: None,
416            min_length: None,
417            min_value: None,
418            name: name.into(),
419            name_localizations: None,
420            options: None,
421            file_types: None,
422            required: None,
423        })
424    }
425
426    /// Consume the builder, returning the built command option.
427    #[must_use = "should be used in a command builder"]
428    pub fn build(self) -> CommandOption {
429        self.0
430    }
431
432    /// Restricts the channel choice to specific types.
433    ///
434    /// Defaults to all channel types allowed.
435    pub fn channel_types(mut self, channel_types: impl IntoIterator<Item = ChannelType>) -> Self {
436        self.0.channel_types = Some(Vec::from_iter(channel_types));
437
438        self
439    }
440
441    /// Set the localization dictionary for the option description.
442    ///
443    /// Defaults to [`None`].
444    pub fn description_localizations<K: Into<String>, V: Into<String>>(
445        mut self,
446        localizations: impl IntoIterator<Item = (K, V)>,
447    ) -> Self {
448        self.0.description_localizations = Some(
449            localizations
450                .into_iter()
451                .map(|(a, b)| (a.into(), b.into()))
452                .collect(),
453        );
454
455        self
456    }
457
458    /// Set the localization dictionary for the option name.
459    ///
460    /// Defaults to [`None`].
461    pub fn name_localizations<K: Into<String>, V: Into<String>>(
462        mut self,
463        localizations: impl IntoIterator<Item = (K, V)>,
464    ) -> Self {
465        self.0.name_localizations = Some(
466            localizations
467                .into_iter()
468                .map(|(a, b)| (a.into(), b.into()))
469                .collect(),
470        );
471
472        self
473    }
474
475    /// Set whether this option is required.
476    ///
477    /// Defaults to `false`.
478    pub const fn required(mut self, required: bool) -> Self {
479        self.0.required = Some(required);
480
481        self
482    }
483}
484
485impl From<ChannelBuilder> for CommandOption {
486    fn from(builder: ChannelBuilder) -> CommandOption {
487        builder.build()
488    }
489}
490/// Create a integer option with a builder.
491#[derive(Clone, Debug)]
492#[must_use = "should be used in a command builder"]
493pub struct IntegerBuilder(CommandOption);
494
495impl IntegerBuilder {
496    /// Create a new default [`IntegerBuilder`].
497    #[must_use = "builders have no effect if unused"]
498    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
499        Self(CommandOption {
500            autocomplete: Some(false),
501            channel_types: None,
502            choices: Some(Vec::new()),
503            description: description.into(),
504            description_localizations: None,
505            kind: CommandOptionType::Integer,
506            max_length: None,
507            max_value: None,
508            min_length: None,
509            min_value: None,
510            name: name.into(),
511            name_localizations: None,
512            options: None,
513            file_types: None,
514            required: None,
515        })
516    }
517
518    /// Consume the builder, returning the built command option.
519    #[must_use = "should be used in a command builder"]
520    pub fn build(self) -> CommandOption {
521        self.0
522    }
523
524    /// Set whether this option supports autocomplete.
525    ///
526    /// Defaults to `false`.
527    pub const fn autocomplete(mut self, autocomplete: bool) -> Self {
528        self.0.autocomplete = Some(autocomplete);
529
530        self
531    }
532
533    /// Set localization for a particular choice.
534    ///
535    /// Choices must be set with the [`choices`] method before updating their
536    /// localization.
537    ///
538    /// # Panics
539    ///
540    /// Panics if the choice was not set.
541    ///
542    /// [`choices`]: Self::choices
543    #[track_caller]
544    pub fn choice_localizations<K: Into<String>, V: Into<String>>(
545        mut self,
546        choice_name: &str,
547        name_localizations: impl IntoIterator<Item = (K, V)>,
548    ) -> Self {
549        let choice = self
550            .0
551            .choices
552            .as_mut()
553            .expect("choice exists")
554            .iter_mut()
555            .find(|choice| choice.name == choice_name)
556            .expect("choice exists");
557
558        choice.name_localizations = Some(
559            name_localizations
560                .into_iter()
561                .map(|(k, v)| (k.into(), v.into()))
562                .collect(),
563        );
564
565        self
566    }
567
568    /// Set the list of choices for an option.
569    ///
570    /// Accepts tuples of `(String, i64)` corresponding to the name and value.
571    /// Localization may be added with [`choice_localizations`].
572    ///
573    /// Defaults to no choices.
574    ///
575    /// [`choice_localizations`]: Self::choice_localizations
576    pub fn choices<K: Into<String>>(mut self, choices: impl IntoIterator<Item = (K, i64)>) -> Self {
577        self.0.choices = Some(
578            choices
579                .into_iter()
580                .map(|(name, value, ..)| CommandOptionChoice {
581                    name: name.into(),
582                    name_localizations: None,
583                    value: value.into(),
584                })
585                .collect(),
586        );
587
588        self
589    }
590
591    /// Set the localization dictionary for the option description.
592    ///
593    /// Defaults to [`None`].
594    pub fn description_localizations<K: Into<String>, V: Into<String>>(
595        mut self,
596        localizations: impl IntoIterator<Item = (K, V)>,
597    ) -> Self {
598        self.0.description_localizations = Some(
599            localizations
600                .into_iter()
601                .map(|(a, b)| (a.into(), b.into()))
602                .collect(),
603        );
604
605        self
606    }
607
608    /// Set the maximum allowed value.
609    ///
610    /// Defaults to no limit.
611    pub fn max_value(mut self, value: i64) -> Self {
612        self.0.max_value = Some(value.into());
613
614        self
615    }
616
617    /// Set the minimum allowed value.
618    ///
619    /// Defaults to no limit.
620    pub fn min_value(mut self, value: i64) -> Self {
621        self.0.min_value = Some(value.into());
622
623        self
624    }
625
626    /// Set the localization dictionary for the option name.
627    ///
628    /// Defaults to [`None`].
629    pub fn name_localizations<K: Into<String>, V: Into<String>>(
630        mut self,
631        localizations: impl IntoIterator<Item = (K, V)>,
632    ) -> Self {
633        self.0.name_localizations = Some(
634            localizations
635                .into_iter()
636                .map(|(a, b)| (a.into(), b.into()))
637                .collect(),
638        );
639
640        self
641    }
642
643    /// Set whether this option is required.
644    ///
645    /// Defaults to `false`.
646    pub const fn required(mut self, required: bool) -> Self {
647        self.0.required = Some(required);
648
649        self
650    }
651}
652
653impl From<IntegerBuilder> for CommandOption {
654    fn from(builder: IntegerBuilder) -> CommandOption {
655        builder.build()
656    }
657}
658
659/// Create a mentionable option with a builder.
660#[derive(Clone, Debug)]
661#[must_use = "should be used in a command builder"]
662pub struct MentionableBuilder(CommandOption);
663
664impl MentionableBuilder {
665    /// Create a new default [`MentionableBuilder`].
666    #[must_use = "builders have no effect if unused"]
667    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
668        Self(CommandOption {
669            autocomplete: None,
670            channel_types: None,
671            choices: None,
672            description: description.into(),
673            description_localizations: None,
674            kind: CommandOptionType::Mentionable,
675            max_length: None,
676            max_value: None,
677            min_length: None,
678            min_value: None,
679            name: name.into(),
680            name_localizations: None,
681            options: None,
682            file_types: None,
683            required: None,
684        })
685    }
686
687    /// Consume the builder, returning the built command option.
688    #[must_use = "should be used in a command builder"]
689    pub fn build(self) -> CommandOption {
690        self.0
691    }
692
693    /// Set the localization dictionary for the option description.
694    ///
695    /// Defaults to [`None`].
696    pub fn description_localizations<K: Into<String>, V: Into<String>>(
697        mut self,
698        localizations: impl IntoIterator<Item = (K, V)>,
699    ) -> Self {
700        self.0.description_localizations = Some(
701            localizations
702                .into_iter()
703                .map(|(a, b)| (a.into(), b.into()))
704                .collect(),
705        );
706
707        self
708    }
709
710    /// Set the localization dictionary for the option name.
711    ///
712    /// Defaults to [`None`].
713    pub fn name_localizations<K: Into<String>, V: Into<String>>(
714        mut self,
715        localizations: impl IntoIterator<Item = (K, V)>,
716    ) -> Self {
717        self.0.name_localizations = Some(
718            localizations
719                .into_iter()
720                .map(|(a, b)| (a.into(), b.into()))
721                .collect(),
722        );
723
724        self
725    }
726
727    /// Set whether this option is required.
728    ///
729    /// Defaults to `false`.
730    pub const fn required(mut self, required: bool) -> Self {
731        self.0.required = Some(required);
732
733        self
734    }
735}
736
737impl From<MentionableBuilder> for CommandOption {
738    fn from(builder: MentionableBuilder) -> CommandOption {
739        builder.build()
740    }
741}
742
743/// Create a number option with a builder.
744#[derive(Clone, Debug)]
745#[must_use = "should be used in a command builder"]
746pub struct NumberBuilder(CommandOption);
747
748impl NumberBuilder {
749    /// Create a new default [`NumberBuilder`].
750    #[must_use = "builders have no effect if unused"]
751    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
752        Self(CommandOption {
753            autocomplete: Some(false),
754            channel_types: None,
755            choices: Some(Vec::new()),
756            description: description.into(),
757            description_localizations: None,
758            kind: CommandOptionType::Number,
759            max_length: None,
760            max_value: None,
761            min_length: None,
762            min_value: None,
763            name: name.into(),
764            name_localizations: None,
765            options: None,
766            file_types: None,
767            required: None,
768        })
769    }
770
771    /// Consume the builder, returning the built command option.
772    #[must_use = "should be used in a command builder"]
773    pub fn build(self) -> CommandOption {
774        self.0
775    }
776
777    /// Set whether this option supports autocomplete.
778    ///
779    /// Defaults to `false`.
780    pub const fn autocomplete(mut self, autocomplete: bool) -> Self {
781        self.0.autocomplete = Some(autocomplete);
782
783        self
784    }
785
786    /// Set localization for a particular choice, by name.
787    ///
788    /// Choices must be set with the [`choices`] method before updating their
789    /// localization.
790    ///
791    /// # Panics
792    ///
793    /// Panics if the choice was not set.
794    ///
795    /// [`choices`]: Self::choices
796    #[track_caller]
797    pub fn choice_localizations<K: Into<String>, V: Into<String>>(
798        mut self,
799        choice_name: &str,
800        name_localizations: impl IntoIterator<Item = (K, V)>,
801    ) -> Self {
802        let choice = self
803            .0
804            .choices
805            .as_mut()
806            .expect("choice exists")
807            .iter_mut()
808            .find(|choice| choice.name == choice_name)
809            .expect("choice exists");
810
811        choice.name_localizations = Some(
812            name_localizations
813                .into_iter()
814                .map(|(k, v)| (k.into(), v.into()))
815                .collect(),
816        );
817
818        self
819    }
820
821    /// Set the list of choices for an option.
822    ///
823    /// Accepts tuples of `(String, f64)` corresponding to the name and
824    /// value. Localization may be added with [`choice_localizations`].
825    ///
826    /// Defaults to no choices.
827    ///
828    /// [`choice_localizations`]: Self::choice_localizations
829    pub fn choices<K: Into<String>>(mut self, choices: impl IntoIterator<Item = (K, f64)>) -> Self {
830        self.0.choices = Some(
831            choices
832                .into_iter()
833                .map(|(name, value, ..)| CommandOptionChoice {
834                    name: name.into(),
835                    name_localizations: None,
836                    value: value.into(),
837                })
838                .collect(),
839        );
840
841        self
842    }
843
844    /// Set the localization dictionary for the option description.
845    ///
846    /// Defaults to [`None`].
847    pub fn description_localizations<K: Into<String>, V: Into<String>>(
848        mut self,
849        localizations: impl IntoIterator<Item = (K, V)>,
850    ) -> Self {
851        self.0.description_localizations = Some(
852            localizations
853                .into_iter()
854                .map(|(a, b)| (a.into(), b.into()))
855                .collect(),
856        );
857
858        self
859    }
860
861    /// Set the maximum allowed value.
862    ///
863    /// Defaults to no limit.
864    pub fn max_value(mut self, value: f64) -> Self {
865        self.0.max_value = Some(value.into());
866
867        self
868    }
869
870    /// Set the minimum allowed value.
871    ///
872    /// Defaults to no limit.
873    pub fn min_value(mut self, value: f64) -> Self {
874        self.0.min_value = Some(value.into());
875
876        self
877    }
878
879    /// Set the localization dictionary for the option name.
880    ///
881    /// Defaults to [`None`].
882    pub fn name_localizations<K: Into<String>, V: Into<String>>(
883        mut self,
884        localizations: impl IntoIterator<Item = (K, V)>,
885    ) -> Self {
886        self.0.name_localizations = Some(
887            localizations
888                .into_iter()
889                .map(|(a, b)| (a.into(), b.into()))
890                .collect(),
891        );
892
893        self
894    }
895
896    /// Set whether this option is required.
897    ///
898    /// Defaults to `false`.
899    pub const fn required(mut self, required: bool) -> Self {
900        self.0.required = Some(required);
901
902        self
903    }
904}
905
906impl From<NumberBuilder> for CommandOption {
907    fn from(builder: NumberBuilder) -> CommandOption {
908        builder.build()
909    }
910}
911
912/// Create a role option with a builder.
913#[derive(Clone, Debug)]
914#[must_use = "should be used in a command builder"]
915pub struct RoleBuilder(CommandOption);
916
917impl RoleBuilder {
918    /// Create a new default [`RoleBuilder`].
919    #[must_use = "builders have no effect if unused"]
920    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
921        Self(CommandOption {
922            autocomplete: None,
923            channel_types: None,
924            choices: None,
925            description: description.into(),
926            description_localizations: None,
927            kind: CommandOptionType::Role,
928            max_length: None,
929            max_value: None,
930            min_length: None,
931            min_value: None,
932            name: name.into(),
933            name_localizations: None,
934            options: None,
935            file_types: None,
936            required: None,
937        })
938    }
939
940    /// Consume the builder, returning the built command option.
941    #[must_use = "should be used in a command builder"]
942    pub fn build(self) -> CommandOption {
943        self.0
944    }
945
946    /// Set the localization dictionary for the option description.
947    ///
948    /// Defaults to [`None`].
949    pub fn description_localizations<K: Into<String>, V: Into<String>>(
950        mut self,
951        localizations: impl IntoIterator<Item = (K, V)>,
952    ) -> Self {
953        self.0.description_localizations = Some(
954            localizations
955                .into_iter()
956                .map(|(a, b)| (a.into(), b.into()))
957                .collect(),
958        );
959
960        self
961    }
962
963    /// Set the localization dictionary for the option name.
964    ///
965    /// Defaults to [`None`].
966    pub fn name_localizations<K: Into<String>, V: Into<String>>(
967        mut self,
968        localizations: impl IntoIterator<Item = (K, V)>,
969    ) -> Self {
970        self.0.name_localizations = Some(
971            localizations
972                .into_iter()
973                .map(|(a, b)| (a.into(), b.into()))
974                .collect(),
975        );
976
977        self
978    }
979
980    /// Set whether this option is required.
981    ///
982    /// Defaults to `false`.
983    pub const fn required(mut self, required: bool) -> Self {
984        self.0.required = Some(required);
985
986        self
987    }
988}
989
990impl From<RoleBuilder> for CommandOption {
991    fn from(builder: RoleBuilder) -> CommandOption {
992        builder.build()
993    }
994}
995
996/// Create a string option with a builder.
997#[derive(Clone, Debug)]
998#[must_use = "should be used in a command builder"]
999pub struct StringBuilder(CommandOption);
1000
1001impl StringBuilder {
1002    /// Create a new default [`StringBuilder`].
1003    #[must_use = "builders have no effect if unused"]
1004    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1005        Self(CommandOption {
1006            autocomplete: Some(false),
1007            channel_types: None,
1008            choices: Some(Vec::new()),
1009            description: description.into(),
1010            description_localizations: None,
1011            kind: CommandOptionType::String,
1012            max_length: None,
1013            max_value: None,
1014            min_length: None,
1015            min_value: None,
1016            name: name.into(),
1017            name_localizations: None,
1018            options: None,
1019            file_types: None,
1020            required: None,
1021        })
1022    }
1023
1024    /// Consume the builder, returning the built command option.
1025    #[must_use = "should be used in a command builder"]
1026    pub fn build(self) -> CommandOption {
1027        self.0
1028    }
1029
1030    /// Set whether this option supports autocomplete.
1031    ///
1032    /// Defaults to `false`.
1033    pub const fn autocomplete(mut self, autocomplete: bool) -> Self {
1034        self.0.autocomplete = Some(autocomplete);
1035
1036        self
1037    }
1038
1039    /// Set localization for a particular choice, by name.
1040    ///
1041    /// Choices must be set with the [`choices`] method before updating their
1042    /// localization.
1043    ///
1044    /// # Panics
1045    ///
1046    /// Panics if the choice was not set.
1047    ///
1048    /// [`choices`]: Self::choices
1049    #[track_caller]
1050    pub fn choice_localizations<K: Into<String>, V: Into<String>>(
1051        mut self,
1052        choice_name: &str,
1053        name_localizations: impl IntoIterator<Item = (K, V)>,
1054    ) -> Self {
1055        let choice = self
1056            .0
1057            .choices
1058            .as_mut()
1059            .expect("choice exists")
1060            .iter_mut()
1061            .find(|choice| choice.name == choice_name)
1062            .expect("choice exists");
1063
1064        choice.name_localizations = Some(
1065            name_localizations
1066                .into_iter()
1067                .map(|(k, v)| (k.into(), v.into()))
1068                .collect(),
1069        );
1070
1071        self
1072    }
1073
1074    /// Set the list of choices for an option.
1075    ///
1076    /// Accepts tuples of `(String, String)` corresponding to the name and
1077    /// value. Localization may be added with [`choice_localizations`].
1078    ///
1079    /// Defaults to no choices.
1080    ///
1081    /// [`choice_localizations`]: Self::choice_localizations
1082    pub fn choices<K: Into<String>, V: Into<String>>(
1083        mut self,
1084        choices: impl IntoIterator<Item = (K, V)>,
1085    ) -> Self {
1086        self.0.choices = Some(
1087            choices
1088                .into_iter()
1089                .map(|(name, value, ..)| CommandOptionChoice {
1090                    name: name.into(),
1091                    name_localizations: None,
1092                    value: CommandOptionChoiceValue::from(value.into()),
1093                })
1094                .collect(),
1095        );
1096
1097        self
1098    }
1099
1100    /// Set the localization dictionary for the option description.
1101    ///
1102    /// Defaults to [`None`].
1103    pub fn description_localizations<K: Into<String>, V: Into<String>>(
1104        mut self,
1105        localizations: impl IntoIterator<Item = (K, V)>,
1106    ) -> Self {
1107        self.0.description_localizations = Some(
1108            localizations
1109                .into_iter()
1110                .map(|(a, b)| (a.into(), b.into()))
1111                .collect(),
1112        );
1113
1114        self
1115    }
1116
1117    /// Set the maximum allowed length.
1118    ///
1119    /// Defaults to no limit.
1120    pub const fn max_length(mut self, value: u16) -> Self {
1121        self.0.max_length = Some(value);
1122
1123        self
1124    }
1125
1126    /// Set the minimum allowed length.
1127    ///
1128    /// Defaults to no limit.
1129    pub const fn min_length(mut self, value: u16) -> Self {
1130        self.0.min_length = Some(value);
1131
1132        self
1133    }
1134
1135    /// Set the localization dictionary for the option name.
1136    ///
1137    /// Defaults to [`None`].
1138    pub fn name_localizations<K: Into<String>, V: Into<String>>(
1139        mut self,
1140        localizations: impl IntoIterator<Item = (K, V)>,
1141    ) -> Self {
1142        self.0.name_localizations = Some(
1143            localizations
1144                .into_iter()
1145                .map(|(a, b)| (a.into(), b.into()))
1146                .collect(),
1147        );
1148
1149        self
1150    }
1151
1152    /// Set whether this option is required.
1153    ///
1154    /// Defaults to `false`.
1155    pub const fn required(mut self, required: bool) -> Self {
1156        self.0.required = Some(required);
1157
1158        self
1159    }
1160}
1161
1162impl From<StringBuilder> for CommandOption {
1163    fn from(builder: StringBuilder) -> CommandOption {
1164        builder.build()
1165    }
1166}
1167
1168/// Create a subcommand option with a builder.
1169#[derive(Clone, Debug)]
1170#[must_use = "should be used in a command builder"]
1171pub struct SubCommandBuilder(CommandOption);
1172
1173impl SubCommandBuilder {
1174    /// Create a new default [`SubCommandBuilder`].
1175    #[must_use = "builders have no effect if unused"]
1176    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1177        Self(CommandOption {
1178            autocomplete: None,
1179            channel_types: None,
1180            choices: None,
1181            description: description.into(),
1182            description_localizations: None,
1183            kind: CommandOptionType::SubCommand,
1184            max_length: None,
1185            max_value: None,
1186            min_length: None,
1187            min_value: None,
1188            name: name.into(),
1189            name_localizations: None,
1190            options: Some(Vec::new()),
1191            file_types: None,
1192            required: None,
1193        })
1194    }
1195
1196    /// Consume the builder, returning the built command option.
1197    #[must_use = "should be used in a command builder"]
1198    pub fn build(self) -> CommandOption {
1199        self.0
1200    }
1201
1202    /// Set the localization dictionary for the option description.
1203    ///
1204    /// Defaults to [`None`].
1205    pub fn description_localizations<K: Into<String>, V: Into<String>>(
1206        mut self,
1207        localizations: impl IntoIterator<Item = (K, V)>,
1208    ) -> Self {
1209        self.0.description_localizations = Some(
1210            localizations
1211                .into_iter()
1212                .map(|(a, b)| (a.into(), b.into()))
1213                .collect(),
1214        );
1215
1216        self
1217    }
1218
1219    /// Set the localization dictionary for the option name.
1220    ///
1221    /// Defaults to [`None`].
1222    pub fn name_localizations<K: Into<String>, V: Into<String>>(
1223        mut self,
1224        localizations: impl IntoIterator<Item = (K, V)>,
1225    ) -> Self {
1226        self.0.name_localizations = Some(
1227            localizations
1228                .into_iter()
1229                .map(|(a, b)| (a.into(), b.into()))
1230                .collect(),
1231        );
1232
1233        self
1234    }
1235
1236    /// Add an option to the sub command.
1237    ///
1238    /// Defaults to an empty list.
1239    pub fn option(self, option: impl Into<CommandOption>) -> Self {
1240        self._option(option.into())
1241    }
1242
1243    fn _option(mut self, option: CommandOption) -> Self {
1244        self.0
1245            .options
1246            .as_mut()
1247            .expect("set to Some in `new`")
1248            .push(option);
1249
1250        self
1251    }
1252}
1253
1254impl From<SubCommandBuilder> for CommandOption {
1255    fn from(builder: SubCommandBuilder) -> CommandOption {
1256        builder.build()
1257    }
1258}
1259
1260/// Create a subcommand group option with a builder.
1261#[derive(Clone, Debug)]
1262#[must_use = "should be used in a command builder"]
1263pub struct SubCommandGroupBuilder(CommandOption);
1264
1265impl SubCommandGroupBuilder {
1266    /// Create a new default [`SubCommandGroupBuilder`].
1267    #[must_use = "builders have no effect if unused"]
1268    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1269        Self(CommandOption {
1270            autocomplete: None,
1271            channel_types: None,
1272            choices: None,
1273            description: description.into(),
1274            description_localizations: None,
1275            kind: CommandOptionType::SubCommandGroup,
1276            max_length: None,
1277            max_value: None,
1278            min_length: None,
1279            min_value: None,
1280            name: name.into(),
1281            name_localizations: None,
1282            options: Some(Vec::new()),
1283            file_types: None,
1284            required: None,
1285        })
1286    }
1287
1288    /// Consume the builder, returning the built command option.
1289    #[must_use = "should be used in a command builder"]
1290    pub fn build(self) -> CommandOption {
1291        self.0
1292    }
1293
1294    /// Set the localization dictionary for the option description.
1295    ///
1296    /// Defaults to [`None`].
1297    pub fn description_localizations<K: Into<String>, V: Into<String>>(
1298        mut self,
1299        localizations: impl IntoIterator<Item = (K, V)>,
1300    ) -> Self {
1301        self.0.description_localizations = Some(
1302            localizations
1303                .into_iter()
1304                .map(|(a, b)| (a.into(), b.into()))
1305                .collect(),
1306        );
1307
1308        self
1309    }
1310
1311    /// Set the localization dictionary for the option name.
1312    ///
1313    /// Defaults to [`None`].
1314    pub fn name_localizations<K: Into<String>, V: Into<String>>(
1315        mut self,
1316        localizations: impl IntoIterator<Item = (K, V)>,
1317    ) -> Self {
1318        self.0.name_localizations = Some(
1319            localizations
1320                .into_iter()
1321                .map(|(a, b)| (a.into(), b.into()))
1322                .collect(),
1323        );
1324
1325        self
1326    }
1327
1328    /// Set the list of sub commands to the group.
1329    ///
1330    /// Defaults to no subcommands.
1331    pub fn subcommands(mut self, subcommands: impl IntoIterator<Item = SubCommandBuilder>) -> Self {
1332        self.0.options = Some(subcommands.into_iter().map(Into::into).collect());
1333
1334        self
1335    }
1336}
1337
1338impl From<SubCommandGroupBuilder> for CommandOption {
1339    fn from(builder: SubCommandGroupBuilder) -> CommandOption {
1340        builder.build()
1341    }
1342}
1343
1344/// Create a user option with a builder.
1345#[derive(Clone, Debug)]
1346#[must_use = "should be used in a command builder"]
1347pub struct UserBuilder(CommandOption);
1348
1349impl UserBuilder {
1350    /// Create a new default [`UserBuilder`].
1351    #[must_use = "builders have no effect if unused"]
1352    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1353        Self(CommandOption {
1354            autocomplete: None,
1355            channel_types: None,
1356            choices: None,
1357            description: description.into(),
1358            description_localizations: None,
1359            kind: CommandOptionType::User,
1360            max_length: None,
1361            max_value: None,
1362            min_length: None,
1363            min_value: None,
1364            name: name.into(),
1365            name_localizations: None,
1366            options: None,
1367            file_types: None,
1368            required: None,
1369        })
1370    }
1371
1372    /// Consume the builder, returning the built command option.
1373    #[must_use = "should be used in a command builder"]
1374    pub fn build(self) -> CommandOption {
1375        self.0
1376    }
1377
1378    /// Set the localization dictionary for the option description.
1379    ///
1380    /// Defaults to [`None`].
1381    pub fn description_localizations<K: Into<String>, V: Into<String>>(
1382        mut self,
1383        localizations: impl IntoIterator<Item = (K, V)>,
1384    ) -> Self {
1385        self.0.description_localizations = Some(
1386            localizations
1387                .into_iter()
1388                .map(|(a, b)| (a.into(), b.into()))
1389                .collect(),
1390        );
1391
1392        self
1393    }
1394
1395    /// Set the localization dictionary for the option name.
1396    ///
1397    /// Defaults to [`None`].
1398    pub fn name_localizations<K: Into<String>, V: Into<String>>(
1399        mut self,
1400        localizations: impl IntoIterator<Item = (K, V)>,
1401    ) -> Self {
1402        self.0.name_localizations = Some(
1403            localizations
1404                .into_iter()
1405                .map(|(a, b)| (a.into(), b.into()))
1406                .collect(),
1407        );
1408
1409        self
1410    }
1411
1412    /// Set whether this option is required.
1413    ///
1414    /// Defaults to `false`.
1415    pub const fn required(mut self, required: bool) -> Self {
1416        self.0.required = Some(required);
1417
1418        self
1419    }
1420}
1421
1422impl From<UserBuilder> for CommandOption {
1423    fn from(builder: UserBuilder) -> CommandOption {
1424        builder.build()
1425    }
1426}
1427
1428#[cfg(test)]
1429mod tests {
1430    use super::*;
1431    use static_assertions::assert_impl_all;
1432    use std::fmt::Debug;
1433
1434    assert_impl_all!(AttachmentBuilder: Clone, Debug, Send, Sync);
1435    assert_impl_all!(CommandBuilder: Clone, Debug, Send, Sync);
1436    assert_impl_all!(BooleanBuilder: Clone, Debug, Send, Sync);
1437    assert_impl_all!(ChannelBuilder: Clone, Debug, Send, Sync);
1438    assert_impl_all!(IntegerBuilder: Clone, Debug, Send, Sync);
1439    assert_impl_all!(MentionableBuilder: Clone, Debug, Send, Sync);
1440    assert_impl_all!(RoleBuilder: Clone, Debug, Send, Sync);
1441    assert_impl_all!(StringBuilder: Clone, Debug, Send, Sync);
1442    assert_impl_all!(SubCommandBuilder: Clone, Debug, Send, Sync);
1443    assert_impl_all!(SubCommandGroupBuilder: Clone, Debug, Send, Sync);
1444    assert_impl_all!(UserBuilder: Clone, Debug, Send, Sync);
1445
1446    #[test]
1447    #[allow(clippy::too_many_lines, deprecated)]
1448    fn construct_command_with_builder() {
1449        let command =
1450            CommandBuilder::new(
1451                "permissions",
1452                "Get or edit permissions for a user or a role",
1453                CommandType::ChatInput,
1454            )
1455            .nsfw(true)
1456            .option(
1457                SubCommandGroupBuilder::new("user", "Get or edit permissions for a user")
1458                    .subcommands([
1459                        SubCommandBuilder::new("get", "Get permissions for a user")
1460                            .option(UserBuilder::new("user", "The user to get").required(true))
1461                            .option(ChannelBuilder::new(
1462                                "channel",
1463                                "The channel permissions to get. If omitted, the guild \
1464                                 permissions will be returned",
1465                            )),
1466                        SubCommandBuilder::new("edit", "Edit permissions for a user")
1467                            .option(UserBuilder::new("user", "The user to edit").required(true))
1468                            .option(ChannelBuilder::new(
1469                                "channel",
1470                                "The channel permissions to edit. If omitted, the guild \
1471                                 permissions will be edited",
1472                            )),
1473                    ]),
1474            )
1475            .option(
1476                SubCommandGroupBuilder::new("role", "Get or edit permissions for a role")
1477                    .subcommands([
1478                        SubCommandBuilder::new("get", "Get permissions for a role")
1479                            .option(RoleBuilder::new("role", "The role to get").required(true))
1480                            .option(ChannelBuilder::new(
1481                                "channel",
1482                                "The channel permissions to get. If omitted, the guild \
1483                                 permissions will be returned",
1484                            )),
1485                        SubCommandBuilder::new("edit", "Edit permissions for a role")
1486                            .option(RoleBuilder::new("role", "The role to edit").required(true))
1487                            .option(ChannelBuilder::new(
1488                                "channel",
1489                                "The channel permissions to edit. If omitted, the guild \
1490                                 permissions will be edited",
1491                            )),
1492                    ]),
1493            )
1494            .build();
1495
1496        let command_manual = Command {
1497            application_id: None,
1498            contexts: None,
1499            default_member_permissions: None,
1500            dm_permission: None,
1501            description: String::from("Get or edit permissions for a user or a role"),
1502            description_localizations: None,
1503            guild_id: None,
1504            id: None,
1505            integration_types: None,
1506            kind: CommandType::ChatInput,
1507            name: String::from("permissions"),
1508            name_localizations: None,
1509            nsfw: Some(true),
1510            options: Vec::from([
1511                CommandOption {
1512                    autocomplete: None,
1513                    channel_types: None,
1514                    choices: None,
1515                    description: "Get or edit permissions for a user".to_owned(),
1516                    description_localizations: None,
1517                    kind: CommandOptionType::SubCommandGroup,
1518                    max_length: None,
1519                    max_value: None,
1520                    min_length: None,
1521                    min_value: None,
1522                    name: "user".to_owned(),
1523                    name_localizations: None,
1524                    options: Some(Vec::from([
1525                        CommandOption {
1526                            autocomplete: None,
1527                            channel_types: None,
1528                            choices: None,
1529                            description: "Get permissions for a user".to_owned(),
1530                            description_localizations: None,
1531                            kind: CommandOptionType::SubCommand,
1532                            max_length: None,
1533                            max_value: None,
1534                            min_length: None,
1535                            min_value: None,
1536                            name: "get".to_owned(),
1537                            name_localizations: None,
1538                            options: Some(Vec::from([
1539                                CommandOption {
1540                                    autocomplete: None,
1541                                    channel_types: None,
1542                                    choices: None,
1543                                    description: "The user to get".to_owned(),
1544                                    description_localizations: None,
1545                                    kind: CommandOptionType::User,
1546                                    max_length: None,
1547                                    max_value: None,
1548                                    min_length: None,
1549                                    min_value: None,
1550                                    name: "user".to_owned(),
1551                                    name_localizations: None,
1552                                    options: None,
1553                                    file_types: None,
1554                                    required: Some(true),
1555                                },
1556                                CommandOption {
1557                                    autocomplete: None,
1558                                    channel_types: Some(Vec::new()),
1559                                    choices: None,
1560                                    description:
1561                                        "The channel permissions to get. If omitted, the guild \
1562                                        permissions will be returned"
1563                                            .to_owned(),
1564                                    description_localizations: None,
1565                                    kind: CommandOptionType::Channel,
1566                                    max_length: None,
1567                                    max_value: None,
1568                                    min_length: None,
1569                                    min_value: None,
1570                                    name: "channel".to_owned(),
1571                                    name_localizations: None,
1572                                    options: None,
1573                                    file_types: None,
1574                                    required: None,
1575                                },
1576                            ])),
1577                            file_types: None,
1578                            required: None,
1579                        },
1580                        CommandOption {
1581                            autocomplete: None,
1582                            channel_types: None,
1583                            choices: None,
1584                            description: "Edit permissions for a user".to_owned(),
1585                            description_localizations: None,
1586                            kind: CommandOptionType::SubCommand,
1587                            max_length: None,
1588                            max_value: None,
1589                            min_length: None,
1590                            min_value: None,
1591                            name: "edit".to_owned(),
1592                            name_localizations: None,
1593                            options: Some(Vec::from([
1594                                CommandOption {
1595                                    autocomplete: None,
1596                                    channel_types: None,
1597                                    choices: None,
1598                                    description: "The user to edit".to_owned(),
1599                                    description_localizations: None,
1600                                    kind: CommandOptionType::User,
1601                                    max_length: None,
1602                                    max_value: None,
1603                                    min_length: None,
1604                                    min_value: None,
1605                                    name: "user".to_owned(),
1606                                    name_localizations: None,
1607                                    options: None,
1608                                    file_types: None,
1609                                    required: Some(true),
1610                                },
1611                                CommandOption {
1612                                    autocomplete: None,
1613                                    channel_types: Some(Vec::new()),
1614                                    choices: None,
1615                                    description:
1616                                        "The channel permissions to edit. If omitted, the guild \
1617                                        permissions will be edited"
1618                                            .to_owned(),
1619                                    description_localizations: None,
1620                                    kind: CommandOptionType::Channel,
1621                                    max_length: None,
1622                                    max_value: None,
1623                                    min_length: None,
1624                                    min_value: None,
1625                                    name: "channel".to_owned(),
1626                                    name_localizations: None,
1627                                    options: None,
1628                                    file_types: None,
1629                                    required: None,
1630                                },
1631                            ])),
1632                            file_types: None,
1633                            required: None,
1634                        },
1635                    ])),
1636                    file_types: None,
1637                    required: None,
1638                },
1639                CommandOption {
1640                    autocomplete: None,
1641                    channel_types: None,
1642                    choices: None,
1643                    description: "Get or edit permissions for a role".to_owned(),
1644                    description_localizations: None,
1645                    kind: CommandOptionType::SubCommandGroup,
1646                    max_length: None,
1647                    max_value: None,
1648                    min_length: None,
1649                    min_value: None,
1650                    name: "role".to_owned(),
1651                    name_localizations: None,
1652                    options: Some(Vec::from([
1653                        CommandOption {
1654                            autocomplete: None,
1655                            channel_types: None,
1656                            choices: None,
1657                            description: "Get permissions for a role".to_owned(),
1658                            description_localizations: None,
1659                            kind: CommandOptionType::SubCommand,
1660                            max_length: None,
1661                            max_value: None,
1662                            min_length: None,
1663                            min_value: None,
1664                            name: "get".to_owned(),
1665                            name_localizations: None,
1666                            options: Some(Vec::from([
1667                                CommandOption {
1668                                    autocomplete: None,
1669                                    channel_types: None,
1670                                    choices: None,
1671                                    description: "The role to get".to_owned(),
1672                                    description_localizations: None,
1673                                    kind: CommandOptionType::Role,
1674                                    max_length: None,
1675                                    max_value: None,
1676                                    min_length: None,
1677                                    min_value: None,
1678                                    name: "role".to_owned(),
1679                                    name_localizations: None,
1680                                    options: None,
1681                                    file_types: None,
1682                                    required: Some(true),
1683                                },
1684                                CommandOption {
1685                                    autocomplete: None,
1686                                    channel_types: Some(Vec::new()),
1687                                    choices: None,
1688                                    description:
1689                                        "The channel permissions to get. If omitted, the guild \
1690                                permissions will be returned"
1691                                            .to_owned(),
1692                                    description_localizations: None,
1693                                    kind: CommandOptionType::Channel,
1694                                    max_length: None,
1695                                    max_value: None,
1696                                    min_length: None,
1697                                    min_value: None,
1698                                    name: "channel".to_owned(),
1699                                    name_localizations: None,
1700                                    options: None,
1701                                    file_types: None,
1702                                    required: None,
1703                                },
1704                            ])),
1705                            file_types: None,
1706                            required: None,
1707                        },
1708                        CommandOption {
1709                            autocomplete: None,
1710                            channel_types: None,
1711                            choices: None,
1712                            description: "Edit permissions for a role".to_owned(),
1713                            description_localizations: None,
1714                            kind: CommandOptionType::SubCommand,
1715                            max_length: None,
1716                            max_value: None,
1717                            min_length: None,
1718                            min_value: None,
1719                            name: "edit".to_owned(),
1720                            name_localizations: None,
1721                            options: Some(Vec::from([
1722                                CommandOption {
1723                                    autocomplete: None,
1724                                    channel_types: None,
1725                                    choices: None,
1726                                    description: "The role to edit".to_owned(),
1727                                    description_localizations: None,
1728                                    kind: CommandOptionType::Role,
1729                                    max_length: None,
1730                                    max_value: None,
1731                                    min_length: None,
1732                                    min_value: None,
1733                                    name: "role".to_owned(),
1734                                    name_localizations: None,
1735                                    options: None,
1736                                    file_types: None,
1737                                    required: Some(true),
1738                                },
1739                                CommandOption {
1740                                    autocomplete: None,
1741                                    channel_types: Some(Vec::new()),
1742                                    choices: None,
1743                                    description:
1744                                        "The channel permissions to edit. If omitted, the guild \
1745                                permissions will be edited"
1746                                            .to_owned(),
1747                                    description_localizations: None,
1748                                    kind: CommandOptionType::Channel,
1749                                    max_length: None,
1750                                    max_value: None,
1751                                    min_length: None,
1752                                    min_value: None,
1753                                    name: "channel".to_owned(),
1754                                    name_localizations: None,
1755                                    options: None,
1756                                    file_types: None,
1757                                    required: None,
1758                                },
1759                            ])),
1760                            file_types: None,
1761                            required: None,
1762                        },
1763                    ])),
1764                    file_types: None,
1765                    required: None,
1766                },
1767            ]),
1768            version: Id::new(1),
1769        };
1770
1771        assert_eq!(command, command_manual);
1772    }
1773
1774    #[test]
1775    fn validate() {
1776        let result = CommandBuilder::new("", "", CommandType::ChatInput).validate();
1777
1778        assert!(result.is_err());
1779    }
1780}