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