twilight_model/application/command/
option.rs

1use crate::channel::ChannelType;
2use serde::{Deserialize, Serialize};
3use serde_repr::{Deserialize_repr, Serialize_repr};
4use std::{cmp::Eq, collections::HashMap};
5
6/// Option for a [`Command`].
7///
8/// Fields not applicable to the command option's [`CommandOptionType`] should
9/// be set to [`None`].
10///
11/// Fields' default values may be used by setting them to [`None`].
12///
13/// Choices, descriptions and names may be localized in any [available locale],
14/// see [Discord Docs/Localization].
15///
16/// [available locale]: https://discord.com/developers/docs/reference#locales
17/// [`Command`]: super::Command
18/// [Discord Docs/Localization]: https://discord.com/developers/docs/interactions/application-commands#localization
19#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
20pub struct CommandOption {
21    /// Whether the command supports autocomplete.
22    ///
23    /// Applicable for options of type [`Integer`], [`Number`], and [`String`].
24    ///
25    /// Defaults to `false`.
26    ///
27    /// **Note**: may not be set to `true` if `choices` are set.
28    ///
29    /// [`Integer`]: CommandOptionType::Integer
30    /// [`Number`]: CommandOptionType::Number
31    /// [`String`]: CommandOptionType::String
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub autocomplete: Option<bool>,
34    /// List of possible channel types users can select from.
35    ///
36    /// Applicable for options of type [`Channel`].
37    ///
38    /// Defaults to any channel type.
39    ///
40    /// [`Channel`]: CommandOptionType::Channel
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub channel_types: Option<Vec<ChannelType>>,
43    /// List of predetermined choices users can select from.
44    ///
45    /// Applicable for options of type [`Integer`], [`Number`], and [`String`].
46    ///
47    /// Defaults to no choices; users may input a value of their choice.
48    ///
49    /// Must be at most 25 options.
50    ///
51    /// **Note**: all choices must be of the same type.
52    ///
53    /// [`Integer`]: CommandOptionType::Integer
54    /// [`Number`]: CommandOptionType::Number
55    /// [`String`]: CommandOptionType::String
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub choices: Option<Vec<CommandOptionChoice>>,
58    /// Description of the option. Must be 100 characters or less.
59    pub description: String,
60    /// Localization dictionary for the [`description`] field.
61    ///
62    /// Defaults to no localizations.
63    ///
64    /// Keys must be valid locales and values must be 100 characters or less.
65    ///
66    /// [`description`]: Self::description
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub description_localizations: Option<HashMap<String, String>>,
69    /// Type of option.
70    #[serde(rename = "type")]
71    pub kind: CommandOptionType,
72    /// Maximum allowed value length.
73    ///
74    /// Applicable for options of type [`String`].
75    ///
76    /// Defaults to `6000`.
77    ///
78    /// Must be at least `1` and at most `6000`.
79    ///
80    /// [`String`]: CommandOptionType::String
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub max_length: Option<u16>,
83    /// Maximum allowed value.
84    ///
85    /// Applicable for options of type [`Integer`] and [`Number`].
86    ///
87    /// Defaults to no maximum.
88    ///
89    /// [`Integer`]: CommandOptionType::Integer
90    /// [`Number`]: CommandOptionType::Number
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub max_value: Option<CommandOptionValue>,
93    /// Minimum allowed value length.
94    ///
95    /// Applicable for options of type [`String`].
96    ///
97    /// Defaults to `0`.
98    ///
99    /// Must be at most `6000`.
100    ///
101    /// [`String`]: CommandOptionType::String
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub min_length: Option<u16>,
104    /// Minimum allowed value.
105    ///
106    /// Applicable for options of type [`Integer`] and [`Number`].
107    ///
108    /// Defaults to no minimum.
109    ///
110    /// [`Integer`]: CommandOptionType::Integer
111    /// [`Number`]: CommandOptionType::Number
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub min_value: Option<CommandOptionValue>,
114    /// Name of the option. Must be 32 characters or less.
115    pub name: String,
116    /// Localization dictionary for the [`name`] field.
117    ///
118    /// Defaults to no localizations.
119    ///
120    /// Keys must be valid locales and values must be 32 characters or less.
121    ///
122    /// [`name`]: Self::name
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub name_localizations: Option<HashMap<String, String>>,
125    /// Nested options.
126    ///
127    /// Applicable for options of type [`SubCommand`] and [`SubCommandGroup`].
128    ///
129    /// Defaults to no options.
130    ///
131    /// **Note**: at least one option is required and [`SubCommandGroup`] may
132    /// only contain [`SubCommand`]s.
133    ///
134    /// See [Discord Docs/Subcommands and Subcommand Groups].
135    ///
136    /// [Discord Docs/Subcommands and Subcommand Groups]: https://discord.com/developers/docs/interactions/application-commands#subcommands-and-subcommand-groups
137    /// [`SubCommand`]: CommandOptionType::SubCommand
138    /// [`SubCommandGroup`]: CommandOptionType::SubCommandGroup
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub options: Option<Vec<CommandOption>>,
141    /// Whether the option is required.
142    ///
143    /// Applicable for all options except those of type [`SubCommand`] and
144    /// [`SubCommandGroup`].
145    ///
146    /// Defaults to `false`.
147    ///
148    /// [`SubCommand`]: CommandOptionType::SubCommand
149    /// [`SubCommandGroup`]: CommandOptionType::SubCommandGroup
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub required: Option<bool>,
152}
153
154/// A predetermined choice users can select.
155#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
156pub struct CommandOptionChoice {
157    /// Name of the choice. Must be 100 characters or less.
158    pub name: String,
159    /// Localization dictionary for the [`name`] field.
160    ///
161    /// Defaults to no localizations.
162    ///
163    /// Keys must be valid locales and values must be 100 characters or less.
164    ///
165    /// See [`CommandOption`]'s documentation for more info.
166    ///
167    /// [`name`]: Self::name
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub name_localizations: Option<HashMap<String, String>>,
170    /// Value of the choice.
171    pub value: CommandOptionChoiceValue,
172}
173
174/// Value of a [`CommandOptionChoice`].
175///
176/// Note that the right variant must be selected based on the
177/// [`CommandOption`]'s [`CommandOptionType`].
178#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
179#[serde(untagged)]
180pub enum CommandOptionChoiceValue {
181    /// String choice. Must be 100 characters or less.
182    String(String),
183    /// Integer choice.
184    Integer(i64),
185    /// Number choice.
186    Number(f64),
187}
188
189/// Type used in the `max_value` and `min_value` [`CommandOption`] field.
190///
191/// Note that the right variant must be selected based on the
192/// [`CommandOption`]'s [`CommandOptionType`].
193#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
194#[serde(untagged)]
195pub enum CommandOptionValue {
196    /// Integer type.
197    Integer(i64),
198    /// Number type.
199    Number(f64),
200}
201
202/// Type of a [`CommandOption`].
203#[derive(Clone, Copy, Debug, Deserialize_repr, Eq, Hash, PartialEq, Serialize_repr)]
204#[non_exhaustive]
205#[repr(u8)]
206pub enum CommandOptionType {
207    SubCommand = 1,
208    SubCommandGroup = 2,
209    String = 3,
210    Integer = 4,
211    Boolean = 5,
212    User = 6,
213    Channel = 7,
214    Role = 8,
215    Mentionable = 9,
216    Number = 10,
217    Attachment = 11,
218}
219
220impl CommandOptionType {
221    pub const fn kind(self) -> &'static str {
222        match self {
223            CommandOptionType::SubCommand => "SubCommand",
224            CommandOptionType::SubCommandGroup => "SubCommandGroup",
225            CommandOptionType::String => "String",
226            CommandOptionType::Integer => "Integer",
227            CommandOptionType::Boolean => "Boolean",
228            CommandOptionType::User => "User",
229            CommandOptionType::Channel => "Channel",
230            CommandOptionType::Role => "Role",
231            CommandOptionType::Mentionable => "Mentionable",
232            CommandOptionType::Number => "Number",
233            CommandOptionType::Attachment => "Attachment",
234        }
235    }
236}