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