1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
use crate::channel::ChannelType;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::{cmp::Eq, collections::HashMap};

/// Option for a [`Command`].
///
/// Fields not applicable to the command option's [`CommandOptionType`] should
/// be set to [`None`].
///
/// Fields' default values may be used by setting them to [`None`].
///
/// Choices, descriptions and names may be localized in any [available locale],
/// see [Discord Docs/Localization].
///
/// [available locale]: https://discord.com/developers/docs/reference#locales
/// [`Command`]: super::Command
/// [Discord Docs/Localization]: https://discord.com/developers/docs/interactions/application-commands#localization
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct CommandOption {
    /// Whether the command supports autocomplete.
    ///
    /// Applicable for options of type [`Integer`], [`Number`], and [`String`].
    ///
    /// Defaults to `false`.
    ///
    /// **Note**: may not be set to `true` if `choices` are set.
    ///
    /// [`Integer`]: CommandOptionType::Integer
    /// [`Number`]: CommandOptionType::Number
    /// [`String`]: CommandOptionType::String
    #[serde(skip_serializing_if = "Option::is_none")]
    pub autocomplete: Option<bool>,
    /// List of possible channel types users can select from.
    ///
    /// Applicable for options of type [`Channel`].
    ///
    /// Defaults to any channel type.
    ///
    /// [`Channel`]: CommandOptionType::Channel
    #[serde(skip_serializing_if = "Option::is_none")]
    pub channel_types: Option<Vec<ChannelType>>,
    /// List of predetermined choices users can select from.
    ///
    /// Applicable for options of type [`Integer`], [`Number`], and [`String`].
    ///
    /// Defaults to no choices; users may input a value of their choice.
    ///
    /// Must be at most 25 options.
    ///
    /// **Note**: all choices must be of the same type.
    ///
    /// [`Integer`]: CommandOptionType::Integer
    /// [`Number`]: CommandOptionType::Number
    /// [`String`]: CommandOptionType::String
    #[serde(skip_serializing_if = "Option::is_none")]
    pub choices: Option<Vec<CommandOptionChoice>>,
    /// Description of the option. Must be 100 characters or less.
    pub description: String,
    /// Localization dictionary for the [`description`] field.
    ///
    /// Defaults to no localizations.
    ///
    /// Keys must be valid locales and values must be 100 characters or less.
    ///
    /// [`description`]: Self::description
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description_localizations: Option<HashMap<String, String>>,
    /// Type of option.
    #[serde(rename = "type")]
    pub kind: CommandOptionType,
    /// Maximum allowed value length.
    ///
    /// Applicable for options of type [`String`].
    ///
    /// Defaults to `6000`.
    ///
    /// Must be at least `1` and at most `6000`.
    ///
    /// [`String`]: CommandOptionType::String
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_length: Option<u16>,
    /// Maximum allowed value.
    ///
    /// Applicable for options of type [`Integer`] and [`Number`].
    ///
    /// Defaults to no maximum.
    ///
    /// [`Integer`]: CommandOptionType::Integer
    /// [`Number`]: CommandOptionType::Number
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_value: Option<CommandOptionValue>,
    /// Minimum allowed value length.
    ///
    /// Applicable for options of type [`String`].
    ///
    /// Defaults to `0`.
    ///
    /// Must be at most `6000`.
    ///
    /// [`String`]: CommandOptionType::String
    #[serde(skip_serializing_if = "Option::is_none")]
    pub min_length: Option<u16>,
    /// Minimum allowed value.
    ///
    /// Applicable for options of type [`Integer`] and [`Number`].
    ///
    /// Defaults to no minimum.
    ///
    /// [`Integer`]: CommandOptionType::Integer
    /// [`Number`]: CommandOptionType::Number
    #[serde(skip_serializing_if = "Option::is_none")]
    pub min_value: Option<CommandOptionValue>,
    /// Name of the option. Must be 32 characters or less.
    pub name: String,
    /// Localization dictionary for the [`name`] field.
    ///
    /// Defaults to no localizations.
    ///
    /// Keys must be valid locales and values must be 32 characters or less.
    ///
    /// [`name`]: Self::name
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name_localizations: Option<HashMap<String, String>>,
    /// Nested options.
    ///
    /// Applicable for options of type [`SubCommand`] and [`SubCommandGroup`].
    ///
    /// Defaults to no options.
    ///
    /// **Note**: at least one option is required and [`SubCommandGroup`] may
    /// only contain [`SubCommand`]s.
    ///
    /// See [Discord Docs/Subcommands and Subcommand Groups].
    ///
    /// [Discord Docs/Subcommands and Subcommand Groups]: https://discord.com/developers/docs/interactions/application-commands#subcommands-and-subcommand-groups
    /// [`SubCommand`]: CommandOptionType::SubCommand
    /// [`SubCommandGroup`]: CommandOptionType::SubCommandGroup
    #[serde(skip_serializing_if = "Option::is_none")]
    pub options: Option<Vec<CommandOption>>,
    /// Whether the option is required.
    ///
    /// Applicable for all options except those of type [`SubCommand`] and
    /// [`SubCommandGroup`].
    ///
    /// Defaults to `false`.
    ///
    /// [`SubCommand`]: CommandOptionType::SubCommand
    /// [`SubCommandGroup`]: CommandOptionType::SubCommandGroup
    #[serde(skip_serializing_if = "Option::is_none")]
    pub required: Option<bool>,
}

/// A predetermined choice users can select.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct CommandOptionChoice {
    /// Name of the choice. Must be 100 characters or less.
    pub name: String,
    /// Localization dictionary for the [`name`] field.
    ///
    /// Defaults to no localizations.
    ///
    /// Keys must be valid locales and values must be 100 characters or less.
    ///
    /// See [`CommandOption`]'s documentation for more info.
    ///
    /// [`name`]: Self::name
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name_localizations: Option<HashMap<String, String>>,
    /// Value of the choice.
    pub value: CommandOptionChoiceValue,
}

/// Value of a [`CommandOptionChoice`].
///
/// Note that the right variant must be selected based on the
/// [`CommandOption`]'s [`CommandOptionType`].
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum CommandOptionChoiceValue {
    /// String choice. Must be 100 characters or less.
    String(String),
    /// Integer choice.
    Integer(i64),
    /// Number choice.
    Number(f64),
}

/// Type used in the `max_value` and `min_value` [`CommandOption`] field.
///
/// Note that the right variant must be selected based on the
/// [`CommandOption`]'s [`CommandOptionType`].
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum CommandOptionValue {
    /// Integer type.
    Integer(i64),
    /// Number type.
    Number(f64),
}

/// Type of a [`CommandOption`].
#[derive(Clone, Copy, Debug, Deserialize_repr, Eq, Hash, PartialEq, Serialize_repr)]
#[non_exhaustive]
#[repr(u8)]
pub enum CommandOptionType {
    SubCommand = 1,
    SubCommandGroup = 2,
    String = 3,
    Integer = 4,
    Boolean = 5,
    User = 6,
    Channel = 7,
    Role = 8,
    Mentionable = 9,
    Number = 10,
    Attachment = 11,
}

impl CommandOptionType {
    pub const fn kind(self) -> &'static str {
        match self {
            CommandOptionType::SubCommand => "SubCommand",
            CommandOptionType::SubCommandGroup => "SubCommandGroup",
            CommandOptionType::String => "String",
            CommandOptionType::Integer => "Integer",
            CommandOptionType::Boolean => "Boolean",
            CommandOptionType::User => "User",
            CommandOptionType::Channel => "Channel",
            CommandOptionType::Role => "Role",
            CommandOptionType::Mentionable => "Mentionable",
            CommandOptionType::Number => "Number",
            CommandOptionType::Attachment => "Attachment",
        }
    }
}