Skip to content

Commit 9eec520

Browse files
authored
Add EditCommand builder (#3028)
This separates the the builders for creating versus editing a command, since it's not possible to change the `type` of a command (in serenity this is the `kind` field). Also, the `name` field is not required when editing a command.
1 parent 47d2e1a commit 9eec520

File tree

5 files changed

+216
-74
lines changed

5 files changed

+216
-74
lines changed

src/builder/create_command.rs

Lines changed: 27 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use std::borrow::Cow;
22
use std::collections::HashMap;
33

4+
use crate::builder::EditCommand;
45
#[cfg(feature = "http")]
56
use crate::http::Http;
6-
use crate::internal::prelude::*;
77
use crate::model::prelude::*;
88

99
/// A builder for creating a new [`CommandOption`].
@@ -320,56 +320,28 @@ impl<'a> CreateCommandOption<'a> {
320320

321321
/// A builder for creating a new [`Command`].
322322
///
323-
/// [`Self::name`] and [`Self::description`] are required fields.
324-
///
325323
/// [`Command`]: crate::model::application::Command
326324
///
327325
/// Discord docs:
328-
/// - [global command](https://discord.com/developers/docs/interactions/application-commands#create-global-application-command-json-params)
329-
/// - [guild command](https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command-json-params)
326+
/// - [global command](https://discord.com/developers/docs/interactions/application-commands#create-global-application-command)
327+
/// - [guild command](https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command)
330328
#[derive(Clone, Debug, Serialize)]
331329
#[must_use]
332330
pub struct CreateCommand<'a> {
333-
name: Cow<'a, str>,
334-
name_localizations: HashMap<Cow<'a, str>, Cow<'a, str>>,
335-
#[serde(skip_serializing_if = "Option::is_none")]
336-
description: Option<Cow<'a, str>>,
337-
description_localizations: HashMap<Cow<'a, str>, Cow<'a, str>>,
338-
options: Cow<'a, [CreateCommandOption<'a>]>,
339-
#[serde(skip_serializing_if = "Option::is_none")]
340-
default_member_permissions: Option<Permissions>,
341-
#[serde(skip_serializing_if = "Option::is_none")]
342-
#[cfg(not(feature = "unstable"))]
343-
dm_permission: Option<bool>,
344331
#[serde(skip_serializing_if = "Option::is_none")]
345332
#[serde(rename = "type")]
346333
kind: Option<CommandType>,
347-
#[serde(skip_serializing_if = "Option::is_none")]
348-
integration_types: Option<Vec<InstallationContext>>,
349-
#[serde(skip_serializing_if = "Option::is_none")]
350-
contexts: Option<Vec<InteractionContext>>,
351-
nsfw: bool,
334+
335+
#[serde(flatten)]
336+
fields: EditCommand<'a>,
352337
}
353338

354339
impl<'a> CreateCommand<'a> {
355340
/// Creates a new builder with the given name, leaving all other fields empty.
356341
pub fn new(name: impl Into<Cow<'a, str>>) -> Self {
357342
Self {
358343
kind: None,
359-
360-
name: name.into(),
361-
name_localizations: HashMap::new(),
362-
description: None,
363-
description_localizations: HashMap::new(),
364-
default_member_permissions: None,
365-
#[cfg(not(feature = "unstable"))]
366-
dm_permission: None,
367-
368-
integration_types: None,
369-
contexts: None,
370-
371-
options: Cow::default(),
372-
nsfw: false,
344+
fields: EditCommand::new().name(name),
373345
}
374346
}
375347

@@ -380,15 +352,14 @@ impl<'a> CreateCommand<'a> {
380352
/// global commands of the same app cannot have the same name. Two guild-specific commands of
381353
/// the same app cannot have the same name.
382354
pub fn name(mut self, name: impl Into<Cow<'a, str>>) -> Self {
383-
self.name = name.into();
355+
self.fields = self.fields.name(name);
384356
self
385357
}
386358

387359
/// Specifies a localized name of the application command.
388360
///
389361
/// ```rust
390-
/// # serenity::builder::CreateCommand::new("")
391-
/// .name("birthday")
362+
/// # serenity::builder::CreateCommand::new("birthday")
392363
/// .name_localized("zh-CN", "生日")
393364
/// .name_localized("el", "γενέθλια")
394365
/// # ;
@@ -398,7 +369,7 @@ impl<'a> CreateCommand<'a> {
398369
locale: impl Into<Cow<'a, str>>,
399370
name: impl Into<Cow<'a, str>>,
400371
) -> Self {
401-
self.name_localizations.insert(locale.into(), name.into());
372+
self.fields = self.fields.name_localized(locale, name);
402373
self
403374
}
404375

@@ -410,29 +381,29 @@ impl<'a> CreateCommand<'a> {
410381

411382
/// Specifies the default permissions required to execute the command.
412383
pub fn default_member_permissions(mut self, permissions: Permissions) -> Self {
413-
self.default_member_permissions = Some(permissions);
384+
self.fields = self.fields.default_member_permissions(permissions);
414385
self
415386
}
416387

417388
/// Specifies if the command is available in DMs.
418389
#[cfg(not(feature = "unstable"))]
419390
pub fn dm_permission(mut self, enabled: bool) -> Self {
420-
self.dm_permission = Some(enabled);
391+
self.fields = self.fields.dm_permission(enabled);
421392
self
422393
}
423394

424395
/// Specifies the description of the application command.
425396
///
426397
/// **Note**: Must be between 1 and 100 characters long.
427398
pub fn description(mut self, description: impl Into<Cow<'a, str>>) -> Self {
428-
self.description = Some(description.into());
399+
self.fields = self.fields.description(description);
429400
self
430401
}
431402

432403
/// Specifies a localized description of the application command.
433404
///
434405
/// ```rust
435-
/// # serenity::builder::CreateCommand::new("")
406+
/// # serenity::builder::CreateCommand::new("birthday")
436407
/// .description("Wish a friend a happy birthday")
437408
/// .description_localized("zh-CN", "祝你朋友生日快乐")
438409
/// # ;
@@ -442,63 +413,61 @@ impl<'a> CreateCommand<'a> {
442413
locale: impl Into<Cow<'a, str>>,
443414
description: impl Into<Cow<'a, str>>,
444415
) -> Self {
445-
self.description_localizations.insert(locale.into(), description.into());
416+
self.fields = self.fields.description_localized(locale, description);
446417
self
447418
}
448419

449420
/// Adds an application command option for the application command.
450421
///
451422
/// **Note**: Application commands can have up to 25 options.
452423
pub fn add_option(mut self, option: CreateCommandOption<'a>) -> Self {
453-
self.options.to_mut().push(option);
424+
self.fields = self.fields.add_option(option);
454425
self
455426
}
456427

457428
/// Sets all the application command options for the application command.
458429
///
459430
/// **Note**: Application commands can have up to 25 options.
460431
pub fn set_options(mut self, options: impl Into<Cow<'a, [CreateCommandOption<'a>]>>) -> Self {
461-
self.options = options.into();
432+
self.fields = self.fields.set_options(options);
462433
self
463434
}
464435

465436
/// Adds an installation context that this application command can be used in.
466437
pub fn add_integration_type(mut self, integration_type: InstallationContext) -> Self {
467-
self.integration_types.get_or_insert_with(Vec::default).push(integration_type);
438+
self.fields = self.fields.add_integration_type(integration_type);
468439
self
469440
}
470441

471442
/// Sets the installation contexts that this application command can be used in.
472443
pub fn integration_types(mut self, integration_types: Vec<InstallationContext>) -> Self {
473-
self.integration_types = Some(integration_types);
444+
self.fields = self.fields.integration_types(integration_types);
474445
self
475446
}
476447

477448
/// Adds an interaction context that this application command can be used in.
478449
pub fn add_context(mut self, context: InteractionContext) -> Self {
479-
self.contexts.get_or_insert_with(Vec::default).push(context);
450+
self.fields = self.fields.add_context(context);
480451
self
481452
}
482453

483454
/// Sets the interaction contexts that this application command can be used in.
484455
pub fn contexts(mut self, contexts: Vec<InteractionContext>) -> Self {
485-
self.contexts = Some(contexts);
456+
self.fields = self.fields.contexts(contexts);
486457
self
487458
}
488459

489460
/// Whether this command is marked NSFW (age-restricted)
490461
pub fn nsfw(mut self, nsfw: bool) -> Self {
491-
self.nsfw = nsfw;
462+
self.fields = self.fields.nsfw(nsfw);
492463
self
493464
}
494465

495-
/// Create a [`Command`], overriding an existing one with the same name if it exists.
466+
/// Create a [`Command`], overwriting an existing one with the same name if it exists.
496467
///
497468
/// Providing a [`GuildId`] will create a command in the corresponding [`Guild`]. Otherwise, a
498469
/// global command will be created.
499470
///
500-
/// Providing a [`CommandId`] will edit the corresponding command.
501-
///
502471
/// # Errors
503472
///
504473
/// Returns [`Error::Http`] if invalid data is given. See [Discord's docs] for more details.
@@ -507,19 +476,10 @@ impl<'a> CreateCommand<'a> {
507476
///
508477
/// [Discord's docs]: https://discord.com/developers/docs/interactions/slash-commands
509478
#[cfg(feature = "http")]
510-
pub async fn execute(
511-
self,
512-
http: &Http,
513-
guild_id: Option<GuildId>,
514-
command_id: Option<CommandId>,
515-
) -> Result<Command> {
516-
match (guild_id, command_id) {
517-
(Some(guild_id), Some(cmd_id)) => {
518-
http.edit_guild_command(guild_id, cmd_id, &self).await
519-
},
520-
(Some(guild_id), None) => http.create_guild_command(guild_id, &self).await,
521-
(None, Some(cmd_id)) => http.edit_global_command(cmd_id, &self).await,
522-
(None, None) => http.create_global_command(&self).await,
479+
pub async fn execute(self, http: &Http, guild_id: Option<GuildId>) -> Result<Command> {
480+
match guild_id {
481+
Some(guild_id) => http.create_guild_command(guild_id, &self).await,
482+
None => http.create_global_command(&self).await,
523483
}
524484
}
525485
}

0 commit comments

Comments
 (0)