Skip to main content

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