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