1use serde::de::Error as DeError;
2use serde::ser::{Serialize, Serializer};
3
4use crate::internal::prelude::*;
5use crate::json::from_value;
6use crate::model::prelude::*;
7use crate::model::utils::{default_true, deserialize_val};
8
9enum_number! {
10 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
12 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
13 #[serde(from = "u8", into = "u8")]
14 #[non_exhaustive]
15 pub enum ComponentType {
16 ActionRow = 1,
17 Button = 2,
18 StringSelect = 3,
19 InputText = 4,
20 UserSelect = 5,
21 RoleSelect = 6,
22 MentionableSelect = 7,
23 ChannelSelect = 8,
24 _ => Unknown(u8),
25 }
26}
27
28#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
32#[derive(Clone, Debug, Deserialize, Serialize)]
33#[non_exhaustive]
34pub struct ActionRow {
35 #[serde(rename = "type")]
37 pub kind: ComponentType,
38 #[serde(default)]
40 pub components: Vec<ActionRowComponent>,
41}
42
43#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
47#[derive(Clone, Debug)]
48#[non_exhaustive]
49pub enum ActionRowComponent {
50 Button(Button),
51 SelectMenu(SelectMenu),
52 InputText(InputText),
53}
54
55impl<'de> Deserialize<'de> for ActionRowComponent {
56 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
57 let map = JsonMap::deserialize(deserializer)?;
58
59 let raw_kind = map.get("type").ok_or_else(|| DeError::missing_field("type"))?.clone();
60 let value = Value::from(map);
61
62 match deserialize_val(raw_kind)? {
63 ComponentType::Button => from_value(value).map(ActionRowComponent::Button),
64 ComponentType::InputText => from_value(value).map(ActionRowComponent::InputText),
65 ComponentType::StringSelect
66 | ComponentType::UserSelect
67 | ComponentType::RoleSelect
68 | ComponentType::MentionableSelect
69 | ComponentType::ChannelSelect => from_value(value).map(ActionRowComponent::SelectMenu),
70 ComponentType::ActionRow => {
71 return Err(DeError::custom("Invalid component type ActionRow"))
72 },
73 ComponentType::Unknown(i) => {
74 return Err(DeError::custom(format_args!("Unknown component type {i}")))
75 },
76 }
77 .map_err(DeError::custom)
78 }
79}
80
81impl Serialize for ActionRowComponent {
82 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
83 match self {
84 Self::Button(c) => c.serialize(serializer),
85 Self::InputText(c) => c.serialize(serializer),
86 Self::SelectMenu(c) => c.serialize(serializer),
87 }
88 }
89}
90
91impl From<Button> for ActionRowComponent {
92 fn from(component: Button) -> Self {
93 ActionRowComponent::Button(component)
94 }
95}
96
97impl From<SelectMenu> for ActionRowComponent {
98 fn from(component: SelectMenu) -> Self {
99 ActionRowComponent::SelectMenu(component)
100 }
101}
102
103#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
104#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
105#[serde(untagged)]
106pub enum ButtonKind {
107 Link { url: String },
108 Premium { sku_id: SkuId },
109 NonLink { custom_id: String, style: ButtonStyle },
110}
111
112impl Serialize for ButtonKind {
113 fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
114 where
115 S: Serializer,
116 {
117 #[derive(Serialize)]
118 struct Helper<'a> {
119 style: u8,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 url: Option<&'a str>,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 custom_id: Option<&'a str>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 sku_id: Option<SkuId>,
126 }
127
128 let helper = match self {
129 ButtonKind::Link {
130 url,
131 } => Helper {
132 style: 5,
133 url: Some(url),
134 custom_id: None,
135 sku_id: None,
136 },
137 ButtonKind::Premium {
138 sku_id,
139 } => Helper {
140 style: 6,
141 url: None,
142 custom_id: None,
143 sku_id: Some(*sku_id),
144 },
145 ButtonKind::NonLink {
146 custom_id,
147 style,
148 } => Helper {
149 style: (*style).into(),
150 url: None,
151 custom_id: Some(custom_id),
152 sku_id: None,
153 },
154 };
155 helper.serialize(serializer)
156 }
157}
158
159#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
163#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
164#[non_exhaustive]
165pub struct Button {
166 #[serde(rename = "type")]
168 pub kind: ComponentType,
169 #[serde(flatten)]
171 pub data: ButtonKind,
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub label: Option<String>,
175 #[serde(skip_serializing_if = "Option::is_none")]
177 pub emoji: Option<ReactionType>,
178 #[serde(default)]
180 pub disabled: bool,
181}
182
183enum_number! {
184 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
186 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
187 #[serde(from = "u8", into = "u8")]
188 #[non_exhaustive]
189 pub enum ButtonStyle {
190 Primary = 1,
191 Secondary = 2,
192 Success = 3,
193 Danger = 4,
194 _ => Unknown(u8),
196 }
197}
198
199#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
203#[derive(Clone, Debug, Deserialize, Serialize)]
204#[non_exhaustive]
205pub struct SelectMenu {
206 #[serde(rename = "type")]
210 pub kind: ComponentType,
211 pub custom_id: Option<String>,
213 #[serde(default)]
217 pub options: Vec<SelectMenuOption>,
218 #[serde(default)]
220 pub channel_types: Vec<ChannelType>,
221 pub placeholder: Option<String>,
223 pub min_values: Option<u8>,
225 pub max_values: Option<u8>,
227 #[serde(default)]
229 pub disabled: bool,
230}
231
232#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
236#[derive(Clone, Debug, Deserialize, Serialize)]
237#[non_exhaustive]
238pub struct SelectMenuOption {
239 pub label: String,
241 pub value: String,
243 pub description: Option<String>,
245 pub emoji: Option<ReactionType>,
247 #[serde(default)]
249 pub default: bool,
250}
251
252#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
256#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
257#[non_exhaustive]
258pub struct InputText {
259 #[serde(rename = "type")]
261 pub kind: ComponentType,
262 pub custom_id: String,
264 pub style: Option<InputTextStyle>,
270 pub label: Option<String>,
276 #[serde(skip_serializing_if = "Option::is_none")]
278 pub min_length: Option<u16>,
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub max_length: Option<u16>,
282 #[serde(default = "default_true")]
284 pub required: bool,
285 #[serde(skip_serializing_if = "Option::is_none")]
289 pub value: Option<String>,
290 #[serde(skip_serializing_if = "Option::is_none")]
292 pub placeholder: Option<String>,
293}
294
295enum_number! {
296 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
300 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
301 #[serde(from = "u8", into = "u8")]
302 #[non_exhaustive]
303 pub enum InputTextStyle {
304 Short = 1,
305 Paragraph = 2,
306 _ => Unknown(u8),
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use crate::json::{assert_json, json};
314
315 #[test]
316 fn test_button_serde() {
317 let mut button = Button {
318 kind: ComponentType::Button,
319 data: ButtonKind::NonLink {
320 custom_id: "hello".into(),
321 style: ButtonStyle::Danger,
322 },
323 label: Some("a".into()),
324 emoji: None,
325 disabled: false,
326 };
327 assert_json(
328 &button,
329 json!({"type": 2, "style": 4, "custom_id": "hello", "label": "a", "disabled": false}),
330 );
331
332 button.data = ButtonKind::Link {
333 url: "https://google.com".into(),
334 };
335 assert_json(
336 &button,
337 json!({"type": 2, "style": 5, "url": "https://google.com", "label": "a", "disabled": false}),
338 );
339
340 button.data = ButtonKind::Premium {
341 sku_id: 1234965026943668316.into(),
342 };
343 assert_json(
344 &button,
345 json!({"type": 2, "style": 6, "sku_id": "1234965026943668316", "label": "a", "disabled": false}),
346 );
347 }
348}