use serde::de::Error as DeError;
use serde::ser::{Serialize, Serializer};
use crate::internal::prelude::*;
use crate::json::from_value;
use crate::model::prelude::*;
use crate::model::utils::{default_true, deserialize_val};
enum_number! {
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[serde(from = "u8", into = "u8")]
#[non_exhaustive]
pub enum ComponentType {
ActionRow = 1,
Button = 2,
StringSelect = 3,
InputText = 4,
UserSelect = 5,
RoleSelect = 6,
MentionableSelect = 7,
ChannelSelect = 8,
_ => Unknown(u8),
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct ActionRow {
#[serde(rename = "type")]
pub kind: ComponentType,
#[serde(default)]
pub components: Vec<ActionRowComponent>,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ActionRowComponent {
Button(Button),
SelectMenu(SelectMenu),
InputText(InputText),
}
impl<'de> Deserialize<'de> for ActionRowComponent {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
let map = JsonMap::deserialize(deserializer)?;
let raw_kind = map.get("type").ok_or_else(|| DeError::missing_field("type"))?.clone();
let value = Value::from(map);
match deserialize_val(raw_kind)? {
ComponentType::Button => from_value(value).map(ActionRowComponent::Button),
ComponentType::InputText => from_value(value).map(ActionRowComponent::InputText),
ComponentType::StringSelect
| ComponentType::UserSelect
| ComponentType::RoleSelect
| ComponentType::MentionableSelect
| ComponentType::ChannelSelect => from_value(value).map(ActionRowComponent::SelectMenu),
ComponentType::ActionRow => {
return Err(DeError::custom("Invalid component type ActionRow"))
},
ComponentType::Unknown(i) => {
return Err(DeError::custom(format_args!("Unknown component type {i}")))
},
}
.map_err(DeError::custom)
}
}
impl Serialize for ActionRowComponent {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
match self {
Self::Button(c) => c.serialize(serializer),
Self::InputText(c) => c.serialize(serializer),
Self::SelectMenu(c) => c.serialize(serializer),
}
}
}
impl From<Button> for ActionRowComponent {
fn from(component: Button) -> Self {
ActionRowComponent::Button(component)
}
}
impl From<SelectMenu> for ActionRowComponent {
fn from(component: SelectMenu) -> Self {
ActionRowComponent::SelectMenu(component)
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum ButtonKind {
Link { url: String },
Premium { sku_id: SkuId },
NonLink { custom_id: String, style: ButtonStyle },
}
impl Serialize for ButtonKind {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Helper<'a> {
style: u8,
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
custom_id: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
sku_id: Option<SkuId>,
}
let helper = match self {
ButtonKind::Link {
url,
} => Helper {
style: 5,
url: Some(url),
custom_id: None,
sku_id: None,
},
ButtonKind::Premium {
sku_id,
} => Helper {
style: 6,
url: None,
custom_id: None,
sku_id: Some(*sku_id),
},
ButtonKind::NonLink {
custom_id,
style,
} => Helper {
style: (*style).into(),
url: None,
custom_id: Some(custom_id),
sku_id: None,
},
};
helper.serialize(serializer)
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct Button {
#[serde(rename = "type")]
pub kind: ComponentType,
#[serde(flatten)]
pub data: ButtonKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub emoji: Option<ReactionType>,
#[serde(default)]
pub disabled: bool,
}
enum_number! {
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[serde(from = "u8", into = "u8")]
#[non_exhaustive]
pub enum ButtonStyle {
Primary = 1,
Secondary = 2,
Success = 3,
Danger = 4,
_ => Unknown(u8),
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct SelectMenu {
#[serde(rename = "type")]
pub kind: ComponentType,
pub custom_id: Option<String>,
#[serde(default)]
pub options: Vec<SelectMenuOption>,
#[serde(default)]
pub channel_types: Vec<ChannelType>,
pub placeholder: Option<String>,
pub min_values: Option<u8>,
pub max_values: Option<u8>,
#[serde(default)]
pub disabled: bool,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct SelectMenuOption {
pub label: String,
pub value: String,
pub description: Option<String>,
pub emoji: Option<ReactionType>,
#[serde(default)]
pub default: bool,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[non_exhaustive]
pub struct InputText {
#[serde(rename = "type")]
pub kind: ComponentType,
pub custom_id: String,
pub style: Option<InputTextStyle>,
pub label: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_length: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_length: Option<u16>,
#[serde(default = "default_true")]
pub required: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub placeholder: Option<String>,
}
enum_number! {
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[serde(from = "u8", into = "u8")]
#[non_exhaustive]
pub enum InputTextStyle {
Short = 1,
Paragraph = 2,
_ => Unknown(u8),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::json::{assert_json, json};
#[test]
fn test_button_serde() {
let mut button = Button {
kind: ComponentType::Button,
data: ButtonKind::NonLink {
custom_id: "hello".into(),
style: ButtonStyle::Danger,
},
label: Some("a".into()),
emoji: None,
disabled: false,
};
assert_json(
&button,
json!({"type": 2, "style": 4, "custom_id": "hello", "label": "a", "disabled": false}),
);
button.data = ButtonKind::Link {
url: "https://google.com".into(),
};
assert_json(
&button,
json!({"type": 2, "style": 5, "url": "https://google.com", "label": "a", "disabled": false}),
);
button.data = ButtonKind::Premium {
sku_id: 1234965026943668316.into(),
};
assert_json(
&button,
json!({"type": 2, "style": 6, "sku_id": "1234965026943668316", "label": "a", "disabled": false}),
);
}
}