serenity/model/application/
command_interaction.rs

1use std::collections::HashMap;
2
3use serde::de::{Deserializer, Error as DeError};
4use serde::ser::{Error as _, Serializer};
5use serde::{Deserialize, Serialize};
6
7use super::{AuthorizingIntegrationOwners, InteractionContext};
8#[cfg(feature = "model")]
9use crate::builder::{
10    Builder,
11    CreateInteractionResponse,
12    CreateInteractionResponseFollowup,
13    CreateInteractionResponseMessage,
14    EditInteractionResponse,
15};
16#[cfg(feature = "collector")]
17use crate::client::Context;
18#[cfg(feature = "model")]
19use crate::http::{CacheHttp, Http};
20use crate::internal::prelude::*;
21use crate::json::{self, JsonError};
22use crate::model::application::{CommandOptionType, CommandType};
23use crate::model::channel::{Attachment, Message, PartialChannel};
24use crate::model::guild::{Member, PartialMember, Role};
25use crate::model::id::{
26    ApplicationId,
27    AttachmentId,
28    ChannelId,
29    CommandId,
30    GenericId,
31    GuildId,
32    InteractionId,
33    MessageId,
34    RoleId,
35    TargetId,
36    UserId,
37};
38use crate::model::monetization::Entitlement;
39use crate::model::user::User;
40use crate::model::Permissions;
41#[cfg(all(feature = "collector", feature = "utils"))]
42use crate::utils::{CreateQuickModal, QuickModalResponse};
43
44/// An interaction when a user invokes a slash command.
45///
46/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object).
47#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
48#[derive(Clone, Debug, Deserialize, Serialize)]
49#[serde(remote = "Self")]
50#[non_exhaustive]
51pub struct CommandInteraction {
52    /// Id of the interaction.
53    pub id: InteractionId,
54    /// Id of the application this interaction is for.
55    pub application_id: ApplicationId,
56    /// The data of the interaction which was triggered.
57    pub data: CommandData,
58    /// The guild Id this interaction was sent from, if there is one.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub guild_id: Option<GuildId>,
61    /// Channel that the interaction was sent from.
62    pub channel: Option<PartialChannel>,
63    /// The channel Id this interaction was sent from.
64    pub channel_id: ChannelId,
65    /// The `member` data for the invoking user.
66    ///
67    /// **Note**: It is only present if the interaction is triggered in a guild.
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub member: Option<Box<Member>>,
70    /// The `user` object for the invoking user.
71    #[serde(default)]
72    pub user: User,
73    /// A continuation token for responding to the interaction.
74    pub token: String,
75    /// Always `1`.
76    pub version: u8,
77    /// Permissions the app or bot has within the channel the interaction was sent from.
78    // TODO(next): This is now always serialized.
79    pub app_permissions: Option<Permissions>,
80    /// The selected language of the invoking user.
81    pub locale: String,
82    /// The guild's preferred locale.
83    pub guild_locale: Option<String>,
84    /// For monetized applications, any entitlements of the invoking user.
85    pub entitlements: Vec<Entitlement>,
86    /// The owners of the applications that authorized the interaction, such as a guild or user.
87    #[serde(default)]
88    pub authorizing_integration_owners: AuthorizingIntegrationOwners,
89    /// The context where the interaction was triggered from.
90    pub context: Option<InteractionContext>,
91}
92
93#[cfg(feature = "model")]
94impl CommandInteraction {
95    /// Gets the interaction response.
96    ///
97    /// # Errors
98    ///
99    /// Returns an [`Error::Http`] if there is no interaction response.
100    pub async fn get_response(&self, http: impl AsRef<Http>) -> Result<Message> {
101        http.as_ref().get_original_interaction_response(&self.token).await
102    }
103
104    /// Creates a response to the interaction received.
105    ///
106    /// **Note**: Message contents must be under 2000 unicode code points.
107    ///
108    /// # Errors
109    ///
110    /// Returns an [`Error::Model`] if the message content is too long. May also return an
111    /// [`Error::Http`] if the API returns an error, or an [`Error::Json`] if there is an error in
112    /// deserializing the API response.
113    pub async fn create_response(
114        &self,
115        cache_http: impl CacheHttp,
116        builder: CreateInteractionResponse,
117    ) -> Result<()> {
118        builder.execute(cache_http, (self.id, &self.token)).await
119    }
120
121    /// Edits the initial interaction response.
122    ///
123    /// **Note**: Message contents must be under 2000 unicode code points.
124    ///
125    /// # Errors
126    ///
127    /// Returns an [`Error::Model`] if the message content is too long. May also return an
128    /// [`Error::Http`] if the API returns an error, or an [`Error::Json`] if there is an error in
129    /// deserializing the API response.
130    pub async fn edit_response(
131        &self,
132        cache_http: impl CacheHttp,
133        builder: EditInteractionResponse,
134    ) -> Result<Message> {
135        builder.execute(cache_http, &self.token).await
136    }
137
138    /// Deletes the initial interaction response.
139    ///
140    /// Does not work on ephemeral messages.
141    ///
142    /// # Errors
143    ///
144    /// May return [`Error::Http`] if the API returns an error. Such as if the response was already
145    /// deleted.
146    pub async fn delete_response(&self, http: impl AsRef<Http>) -> Result<()> {
147        http.as_ref().delete_original_interaction_response(&self.token).await
148    }
149
150    /// Creates a followup response to the response sent.
151    ///
152    /// **Note**: Message contents must be under 2000 unicode code points.
153    ///
154    /// # Errors
155    ///
156    /// Returns [`Error::Model`] if the content is too long. May also return [`Error::Http`] if the
157    /// API returns an error, or [`Error::Json`] if there is an error in deserializing the
158    /// response.
159    pub async fn create_followup(
160        &self,
161        cache_http: impl CacheHttp,
162        builder: CreateInteractionResponseFollowup,
163    ) -> Result<Message> {
164        builder.execute(cache_http, (None, &self.token)).await
165    }
166
167    /// Edits a followup response to the response sent.
168    ///
169    /// **Note**: Message contents must be under 2000 unicode code points.
170    ///
171    /// # Errors
172    ///
173    /// Returns [`Error::Model`] if the content is too long. May also return [`Error::Http`] if the
174    /// API returns an error, or [`Error::Json`] if there is an error in deserializing the
175    /// response.
176    pub async fn edit_followup(
177        &self,
178        cache_http: impl CacheHttp,
179        message_id: impl Into<MessageId>,
180        builder: CreateInteractionResponseFollowup,
181    ) -> Result<Message> {
182        builder.execute(cache_http, (Some(message_id.into()), &self.token)).await
183    }
184
185    /// Deletes a followup message.
186    ///
187    /// # Errors
188    ///
189    /// May return [`Error::Http`] if the API returns an error. Such as if the response was already
190    /// deleted.
191    pub async fn delete_followup<M: Into<MessageId>>(
192        &self,
193        http: impl AsRef<Http>,
194        message_id: M,
195    ) -> Result<()> {
196        http.as_ref().delete_followup_message(&self.token, message_id.into()).await
197    }
198
199    /// Gets a followup message.
200    ///
201    /// # Errors
202    ///
203    /// May return [`Error::Http`] if the API returns an error. Such as if the response was
204    /// deleted.
205    pub async fn get_followup<M: Into<MessageId>>(
206        &self,
207        http: impl AsRef<Http>,
208        message_id: M,
209    ) -> Result<Message> {
210        http.as_ref().get_followup_message(&self.token, message_id.into()).await
211    }
212
213    /// Helper function to defer an interaction.
214    ///
215    /// # Errors
216    ///
217    /// Returns an [`Error::Http`] if the API returns an error, or an [`Error::Json`] if there is
218    /// an error in deserializing the API response.
219    pub async fn defer(&self, cache_http: impl CacheHttp) -> Result<()> {
220        let builder = CreateInteractionResponse::Defer(CreateInteractionResponseMessage::default());
221        self.create_response(cache_http, builder).await
222    }
223
224    /// Helper function to defer an interaction ephemerally
225    ///
226    /// # Errors
227    ///
228    /// May also return an [`Error::Http`] if the API returns an error, or an [`Error::Json`] if
229    /// there is an error in deserializing the API response.
230    pub async fn defer_ephemeral(&self, cache_http: impl CacheHttp) -> Result<()> {
231        let builder = CreateInteractionResponse::Defer(
232            CreateInteractionResponseMessage::new().ephemeral(true),
233        );
234        self.create_response(cache_http, builder).await
235    }
236
237    /// See [`CreateQuickModal`].
238    ///
239    /// # Errors
240    ///
241    /// See [`CreateQuickModal::execute()`].
242    #[cfg(all(feature = "collector", feature = "utils"))]
243    pub async fn quick_modal(
244        &self,
245        ctx: &Context,
246        builder: CreateQuickModal,
247    ) -> Result<Option<QuickModalResponse>> {
248        builder.execute(ctx, self.id, &self.token).await
249    }
250}
251
252// Manual impl needed to insert guild_id into resolved Role's
253impl<'de> Deserialize<'de> for CommandInteraction {
254    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
255        // calls #[serde(remote)]-generated inherent method
256        let mut interaction = Self::deserialize(deserializer)?;
257        if let Some(guild_id) = interaction.guild_id {
258            if let Some(member) = &mut interaction.member {
259                member.guild_id = guild_id;
260                // If `member` is present, `user` wasn't sent and is still filled with default data
261                interaction.user = member.user.clone();
262            }
263            interaction.data.resolved.roles.values_mut().for_each(|r| r.guild_id = guild_id);
264        }
265        Ok(interaction)
266    }
267}
268
269impl Serialize for CommandInteraction {
270    fn serialize<S: serde::Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
271        // calls #[serde(remote)]-generated inherent method
272        Self::serialize(self, serializer)
273    }
274}
275
276/// The command data payload.
277///
278/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-data-structure).
279#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
280#[derive(Clone, Debug, Deserialize, Serialize)]
281#[non_exhaustive]
282pub struct CommandData {
283    /// The Id of the invoked command.
284    pub id: CommandId,
285    /// The name of the invoked command.
286    pub name: String,
287    /// The application command type of the triggered application command.
288    #[serde(rename = "type")]
289    pub kind: CommandType,
290    /// The parameters and the given values. The converted objects from the given options.
291    #[serde(default)]
292    pub resolved: CommandDataResolved,
293    #[serde(default)]
294    pub options: Vec<CommandDataOption>,
295    /// The Id of the guild the command is registered to.
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub guild_id: Option<GuildId>,
298    /// The targeted user or message, if the triggered application command type is [`User`] or
299    /// [`Message`].
300    ///
301    /// Its object data can be found in the [`resolved`] field.
302    ///
303    /// [`resolved`]: Self::resolved
304    /// [`User`]: CommandType::User
305    /// [`Message`]: CommandType::Message
306    pub target_id: Option<TargetId>,
307}
308
309impl CommandData {
310    /// Returns the autocomplete option from `CommandData::options`.
311    #[must_use]
312    pub fn autocomplete(&self) -> Option<AutocompleteOption<'_>> {
313        fn find_option(opts: &[CommandDataOption]) -> Option<AutocompleteOption<'_>> {
314            for opt in opts {
315                match &opt.value {
316                    CommandDataOptionValue::SubCommand(opts)
317                    | CommandDataOptionValue::SubCommandGroup(opts) => {
318                        return find_option(opts);
319                    },
320                    CommandDataOptionValue::Autocomplete {
321                        kind,
322                        value,
323                    } => {
324                        return Some(AutocompleteOption {
325                            name: &opt.name,
326                            kind: *kind,
327                            value,
328                        });
329                    },
330                    _ => {},
331                }
332            }
333            None
334        }
335        find_option(&self.options)
336    }
337
338    /// Returns the resolved options from `CommandData::options` and [`CommandData::resolved`].
339    #[must_use]
340    pub fn options(&self) -> Vec<ResolvedOption<'_>> {
341        fn resolve_options<'a>(
342            opts: &'a [CommandDataOption],
343            resolved: &'a CommandDataResolved,
344        ) -> Vec<ResolvedOption<'a>> {
345            let mut options = Vec::new();
346            for opt in opts {
347                let value = match &opt.value {
348                    CommandDataOptionValue::SubCommand(opts) => {
349                        ResolvedValue::SubCommand(resolve_options(opts, resolved))
350                    },
351                    CommandDataOptionValue::SubCommandGroup(opts) => {
352                        ResolvedValue::SubCommandGroup(resolve_options(opts, resolved))
353                    },
354                    CommandDataOptionValue::Autocomplete {
355                        kind,
356                        value,
357                    } => ResolvedValue::Autocomplete {
358                        kind: *kind,
359                        value,
360                    },
361                    CommandDataOptionValue::Boolean(v) => ResolvedValue::Boolean(*v),
362                    CommandDataOptionValue::Integer(v) => ResolvedValue::Integer(*v),
363                    CommandDataOptionValue::Number(v) => ResolvedValue::Number(*v),
364                    CommandDataOptionValue::String(v) => ResolvedValue::String(v),
365                    CommandDataOptionValue::Attachment(id) => resolved.attachments.get(id).map_or(
366                        ResolvedValue::Unresolved(Unresolved::Attachment(*id)),
367                        ResolvedValue::Attachment,
368                    ),
369                    CommandDataOptionValue::Channel(id) => resolved.channels.get(id).map_or(
370                        ResolvedValue::Unresolved(Unresolved::Channel(*id)),
371                        ResolvedValue::Channel,
372                    ),
373                    CommandDataOptionValue::Mentionable(id) => {
374                        let user_id = UserId::new(id.get());
375                        let value = if let Some(user) = resolved.users.get(&user_id) {
376                            Some(ResolvedValue::User(user, resolved.members.get(&user_id)))
377                        } else {
378                            resolved.roles.get(&RoleId::new(id.get())).map(ResolvedValue::Role)
379                        };
380                        value.unwrap_or(ResolvedValue::Unresolved(Unresolved::Mentionable(*id)))
381                    },
382                    CommandDataOptionValue::User(id) => resolved
383                        .users
384                        .get(id)
385                        .map(|u| ResolvedValue::User(u, resolved.members.get(id)))
386                        .unwrap_or(ResolvedValue::Unresolved(Unresolved::User(*id))),
387                    CommandDataOptionValue::Role(id) => resolved.roles.get(id).map_or(
388                        ResolvedValue::Unresolved(Unresolved::RoleId(*id)),
389                        ResolvedValue::Role,
390                    ),
391                    CommandDataOptionValue::Unknown(unknown) => {
392                        ResolvedValue::Unresolved(Unresolved::Unknown(*unknown))
393                    },
394                };
395
396                options.push(ResolvedOption {
397                    name: &opt.name,
398                    value,
399                });
400            }
401            options
402        }
403
404        resolve_options(&self.options, &self.resolved)
405    }
406
407    /// The target resolved data of [`target_id`]
408    ///
409    /// [`target_id`]: Self::target_id
410    #[must_use]
411    pub fn target(&self) -> Option<ResolvedTarget<'_>> {
412        match (self.kind, self.target_id) {
413            (CommandType::User, Some(id)) => {
414                let user_id = id.to_user_id();
415
416                let user = self.resolved.users.get(&user_id)?;
417                let member = self.resolved.members.get(&user_id);
418
419                Some(ResolvedTarget::User(user, member))
420            },
421            (CommandType::Message, Some(id)) => {
422                let message_id = id.to_message_id();
423                let message = self.resolved.messages.get(&message_id)?;
424
425                Some(ResolvedTarget::Message(message))
426            },
427            _ => None,
428        }
429    }
430}
431
432/// The focused option for autocomplete interactions return by [`CommandData::autocomplete`].
433#[derive(Clone, Debug)]
434#[non_exhaustive]
435pub struct AutocompleteOption<'a> {
436    pub name: &'a str,
437    pub kind: CommandOptionType,
438    pub value: &'a str,
439}
440
441#[derive(Clone, Debug)]
442#[non_exhaustive]
443pub struct ResolvedOption<'a> {
444    pub name: &'a str,
445    pub value: ResolvedValue<'a>,
446}
447
448/// The resolved value of a [`CommandDataOption`].
449#[derive(Clone, Debug)]
450#[non_exhaustive]
451pub enum ResolvedValue<'a> {
452    Autocomplete { kind: CommandOptionType, value: &'a str },
453    Boolean(bool),
454    Integer(i64),
455    Number(f64),
456    String(&'a str),
457    SubCommand(Vec<ResolvedOption<'a>>),
458    SubCommandGroup(Vec<ResolvedOption<'a>>),
459    Attachment(&'a Attachment),
460    Channel(&'a PartialChannel),
461    Role(&'a Role),
462    User(&'a User, Option<&'a PartialMember>),
463    Unresolved(Unresolved),
464}
465
466/// Option value variants that couldn't be resolved by `CommandData::options()`.
467#[derive(Clone, Debug)]
468#[non_exhaustive]
469pub enum Unresolved {
470    Attachment(AttachmentId),
471    Channel(ChannelId),
472    Mentionable(GenericId),
473    RoleId(RoleId),
474    User(UserId),
475    /// Variant value for unknown option types.
476    Unknown(u8),
477}
478
479/// The resolved value of a [`CommandData::target_id`].
480#[derive(Clone, Debug)]
481#[non_exhaustive]
482pub enum ResolvedTarget<'a> {
483    User(&'a User, Option<&'a PartialMember>),
484    Message(&'a Message),
485}
486
487/// The resolved data of a command data interaction payload. It contains the objects of
488/// [`CommandDataOption`]s.
489///
490/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure).
491#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
492#[derive(Clone, Debug, Default, Deserialize, Serialize)]
493#[non_exhaustive]
494pub struct CommandDataResolved {
495    /// The resolved users.
496    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
497    pub users: HashMap<UserId, User>,
498    /// The resolved partial members.
499    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
500    pub members: HashMap<UserId, PartialMember>,
501    /// The resolved roles.
502    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
503    pub roles: HashMap<RoleId, Role>,
504    /// The resolved partial channels.
505    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
506    pub channels: HashMap<ChannelId, PartialChannel>,
507    /// The resolved messages.
508    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
509    pub messages: HashMap<MessageId, Message>,
510    /// The resolved attachments.
511    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
512    pub attachments: HashMap<AttachmentId, Attachment>,
513}
514
515/// A set of a parameter and a value from the user.
516///
517/// All options have names and an option can either be a parameter and input `value` or it can
518/// denote a sub-command or group, in which case it will contain a top-level key and another vector
519/// of `options`.
520///
521/// Their resolved objects can be found on [`CommandData::resolved`].
522///
523/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-application-command-interaction-data-option-structure).
524#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
525#[derive(Clone, Debug, PartialEq)]
526#[non_exhaustive]
527pub struct CommandDataOption {
528    /// The name of the parameter.
529    pub name: String,
530    /// The given value.
531    pub value: CommandDataOptionValue,
532}
533
534impl CommandDataOption {
535    #[must_use]
536    pub fn kind(&self) -> CommandOptionType {
537        self.value.kind()
538    }
539}
540
541#[derive(Deserialize, Serialize)]
542struct RawCommandDataOption {
543    name: String,
544    #[serde(rename = "type")]
545    kind: CommandOptionType,
546    #[serde(skip_serializing_if = "Option::is_none")]
547    value: Option<json::Value>,
548    #[serde(skip_serializing_if = "Option::is_none")]
549    options: Option<Vec<RawCommandDataOption>>,
550    #[serde(skip_serializing_if = "Option::is_none")]
551    focused: Option<bool>,
552}
553
554fn option_from_raw(raw: RawCommandDataOption) -> Result<CommandDataOption> {
555    macro_rules! value {
556        () => {{
557            json::from_value(
558                raw.value.ok_or_else::<JsonError, _>(|| DeError::missing_field("value"))?,
559            )?
560        }};
561    }
562
563    let value = match raw.kind {
564        _ if raw.focused == Some(true) => CommandDataOptionValue::Autocomplete {
565            kind: raw.kind,
566            value: value!(),
567        },
568        CommandOptionType::Boolean => CommandDataOptionValue::Boolean(value!()),
569        CommandOptionType::Integer => CommandDataOptionValue::Integer(value!()),
570        CommandOptionType::Number => CommandDataOptionValue::Number(value!()),
571        CommandOptionType::String => CommandDataOptionValue::String(value!()),
572        CommandOptionType::SubCommand => {
573            let options =
574                raw.options.ok_or_else::<JsonError, _>(|| DeError::missing_field("options"))?;
575            let options = options.into_iter().map(option_from_raw).collect::<Result<_>>()?;
576            CommandDataOptionValue::SubCommand(options)
577        },
578        CommandOptionType::SubCommandGroup => {
579            let options =
580                raw.options.ok_or_else::<JsonError, _>(|| DeError::missing_field("options"))?;
581            let options = options.into_iter().map(option_from_raw).collect::<Result<_>>()?;
582            CommandDataOptionValue::SubCommandGroup(options)
583        },
584        CommandOptionType::Attachment => CommandDataOptionValue::Attachment(value!()),
585        CommandOptionType::Channel => CommandDataOptionValue::Channel(value!()),
586        CommandOptionType::Mentionable => CommandDataOptionValue::Mentionable(value!()),
587        CommandOptionType::Role => CommandDataOptionValue::Role(value!()),
588        CommandOptionType::User => CommandDataOptionValue::User(value!()),
589        CommandOptionType::Unknown(unknown) => CommandDataOptionValue::Unknown(unknown),
590    };
591
592    Ok(CommandDataOption {
593        name: raw.name,
594        value,
595    })
596}
597
598fn option_to_raw(option: &CommandDataOption) -> Result<RawCommandDataOption> {
599    let mut raw = RawCommandDataOption {
600        name: option.name.clone(),
601        kind: option.kind(),
602        value: None,
603        options: None,
604        focused: None,
605    };
606
607    match &option.value {
608        CommandDataOptionValue::Autocomplete {
609            kind: _,
610            value,
611        } => {
612            raw.value = Some(json::to_value(value)?);
613            raw.focused = Some(true);
614        },
615        CommandDataOptionValue::Boolean(v) => raw.value = Some(json::to_value(v)?),
616        CommandDataOptionValue::Integer(v) => raw.value = Some(json::to_value(v)?),
617        CommandDataOptionValue::Number(v) => raw.value = Some(json::to_value(v)?),
618        CommandDataOptionValue::String(v) => raw.value = Some(json::to_value(v)?),
619        CommandDataOptionValue::SubCommand(o) | CommandDataOptionValue::SubCommandGroup(o) => {
620            raw.options = Some(o.iter().map(option_to_raw).collect::<Result<_>>()?);
621        },
622        CommandDataOptionValue::Attachment(v) => raw.value = Some(json::to_value(v)?),
623        CommandDataOptionValue::Channel(v) => raw.value = Some(json::to_value(v)?),
624        CommandDataOptionValue::Mentionable(v) => raw.value = Some(json::to_value(v)?),
625        CommandDataOptionValue::Role(v) => raw.value = Some(json::to_value(v)?),
626        CommandDataOptionValue::User(v) => raw.value = Some(json::to_value(v)?),
627        CommandDataOptionValue::Unknown(_) => {},
628    }
629
630    Ok(raw)
631}
632
633// Manual impl needed to emulate integer enum tags
634impl<'de> Deserialize<'de> for CommandDataOption {
635    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
636        option_from_raw(RawCommandDataOption::deserialize(deserializer)?).map_err(D::Error::custom)
637    }
638}
639
640impl Serialize for CommandDataOption {
641    fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
642        option_to_raw(self).map_err(S::Error::custom)?.serialize(serializer)
643    }
644}
645
646/// The value of an [`CommandDataOption`].
647///
648/// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type).
649#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
650#[derive(Clone, Debug, PartialEq)]
651#[non_exhaustive]
652pub enum CommandDataOptionValue {
653    Autocomplete { kind: CommandOptionType, value: String },
654    Boolean(bool),
655    Integer(i64),
656    Number(f64),
657    String(String),
658    SubCommand(Vec<CommandDataOption>),
659    SubCommandGroup(Vec<CommandDataOption>),
660    Attachment(AttachmentId),
661    Channel(ChannelId),
662    Mentionable(GenericId),
663    Role(RoleId),
664    User(UserId),
665    Unknown(u8),
666}
667
668impl CommandDataOptionValue {
669    #[must_use]
670    pub fn kind(&self) -> CommandOptionType {
671        match self {
672            Self::Autocomplete {
673                kind, ..
674            } => *kind,
675            Self::Boolean(_) => CommandOptionType::Boolean,
676            Self::Integer(_) => CommandOptionType::Integer,
677            Self::Number(_) => CommandOptionType::Number,
678            Self::String(_) => CommandOptionType::String,
679            Self::SubCommand(_) => CommandOptionType::SubCommand,
680            Self::SubCommandGroup(_) => CommandOptionType::SubCommandGroup,
681            Self::Attachment(_) => CommandOptionType::Attachment,
682            Self::Channel(_) => CommandOptionType::Channel,
683            Self::Mentionable(_) => CommandOptionType::Mentionable,
684            Self::Role(_) => CommandOptionType::Role,
685            Self::User(_) => CommandOptionType::User,
686            Self::Unknown(unknown) => CommandOptionType::Unknown(*unknown),
687        }
688    }
689
690    /// If the value is a boolean, returns the associated f64. Returns None otherwise.
691    #[must_use]
692    pub fn as_bool(&self) -> Option<bool> {
693        match *self {
694            Self::Boolean(b) => Some(b),
695            _ => None,
696        }
697    }
698
699    /// If the value is an integer, returns the associated f64. Returns None otherwise.
700    #[must_use]
701    pub fn as_i64(&self) -> Option<i64> {
702        match *self {
703            Self::Integer(v) => Some(v),
704            _ => None,
705        }
706    }
707
708    /// If the value is a number, returns the associated f64. Returns None otherwise.
709    #[must_use]
710    pub fn as_f64(&self) -> Option<f64> {
711        match *self {
712            Self::Number(v) => Some(v),
713            _ => None,
714        }
715    }
716
717    /// If the value is a string, returns the associated str. Returns None otherwise.
718    #[must_use]
719    pub fn as_str(&self) -> Option<&str> {
720        match self {
721            Self::String(s) => Some(s),
722            Self::Autocomplete {
723                value, ..
724            } => Some(value),
725            _ => None,
726        }
727    }
728
729    /// If the value is an `AttachmentId`, returns the associated ID. Returns None otherwise.
730    #[must_use]
731    pub fn as_attachment_id(&self) -> Option<AttachmentId> {
732        match self {
733            Self::Attachment(id) => Some(*id),
734            _ => None,
735        }
736    }
737
738    /// If the value is an `ChannelId`, returns the associated ID. Returns None otherwise.
739    #[must_use]
740    pub fn as_channel_id(&self) -> Option<ChannelId> {
741        match self {
742            Self::Channel(id) => Some(*id),
743            _ => None,
744        }
745    }
746
747    /// If the value is an `GenericId`, returns the associated ID. Returns None otherwise.
748    #[must_use]
749    pub fn as_mentionable(&self) -> Option<GenericId> {
750        match self {
751            Self::Mentionable(id) => Some(*id),
752            _ => None,
753        }
754    }
755
756    /// If the value is an `UserId`, returns the associated ID. Returns None otherwise.
757    #[must_use]
758    pub fn as_user_id(&self) -> Option<UserId> {
759        match self {
760            Self::User(id) => Some(*id),
761            _ => None,
762        }
763    }
764
765    /// If the value is an `RoleId`, returns the associated ID. Returns None otherwise.
766    #[must_use]
767    pub fn as_role_id(&self) -> Option<RoleId> {
768        match self {
769            Self::Role(id) => Some(*id),
770            _ => None,
771        }
772    }
773}
774
775impl TargetId {
776    /// Converts this [`TargetId`] to [`UserId`].
777    #[must_use]
778    pub fn to_user_id(self) -> UserId {
779        self.get().into()
780    }
781
782    /// Converts this [`TargetId`] to [`MessageId`].
783    #[must_use]
784    pub fn to_message_id(self) -> MessageId {
785        self.get().into()
786    }
787}
788
789impl From<MessageId> for TargetId {
790    fn from(id: MessageId) -> Self {
791        Self::new(id.into())
792    }
793}
794
795impl From<UserId> for TargetId {
796    fn from(id: UserId) -> Self {
797        Self::new(id.into())
798    }
799}
800
801impl From<TargetId> for MessageId {
802    fn from(id: TargetId) -> Self {
803        Self::new(id.into())
804    }
805}
806
807impl From<TargetId> for UserId {
808    fn from(id: TargetId) -> Self {
809        Self::new(id.into())
810    }
811}
812
813#[cfg(test)]
814mod tests {
815    use super::*;
816    use crate::json::{assert_json, json};
817
818    #[test]
819    fn nested_options() {
820        let value = CommandDataOption {
821            name: "subcommand_group".into(),
822            value: CommandDataOptionValue::SubCommandGroup(vec![CommandDataOption {
823                name: "subcommand".into(),
824                value: CommandDataOptionValue::SubCommand(vec![CommandDataOption {
825                    name: "channel".into(),
826                    value: CommandDataOptionValue::Channel(ChannelId::new(3)),
827                }]),
828            }]),
829        };
830
831        assert_json(
832            &value,
833            json!({
834                "name": "subcommand_group",
835                "type": 2,
836                "options": [{
837                    "name": "subcommand",
838                    "type": 1,
839                    "options": [{"name": "channel", "type": 7, "value": "3"}],
840                }]
841            }),
842        );
843    }
844
845    #[test]
846    fn mixed_options() {
847        let value = vec![
848            CommandDataOption {
849                name: "boolean".into(),
850                value: CommandDataOptionValue::Boolean(true),
851            },
852            CommandDataOption {
853                name: "integer".into(),
854                value: CommandDataOptionValue::Integer(1),
855            },
856            CommandDataOption {
857                name: "number".into(),
858                value: CommandDataOptionValue::Number(2.0),
859            },
860            CommandDataOption {
861                name: "string".into(),
862                value: CommandDataOptionValue::String("foobar".into()),
863            },
864            CommandDataOption {
865                name: "empty_subcommand".into(),
866                value: CommandDataOptionValue::SubCommand(vec![]),
867            },
868            CommandDataOption {
869                name: "autocomplete".into(),
870                value: CommandDataOptionValue::Autocomplete {
871                    kind: CommandOptionType::Integer,
872                    value: "not an integer".into(),
873                },
874            },
875        ];
876
877        assert_json(
878            &value,
879            json!([
880                {"name": "boolean", "type": 5, "value": true},
881                {"name": "integer", "type": 4, "value": 1},
882                {"name": "number", "type": 10, "value": 2.0},
883                {"name": "string", "type": 3, "value": "foobar"},
884                {"name": "empty_subcommand", "type": 1, "options": []},
885                {"name": "autocomplete", "type": 4, "value": "not an integer", "focused": true},
886            ]),
887        );
888    }
889}