use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
#[serde(into = "u8")]
pub enum ButtonStyle {
Primary = 1,
Secondary = 2,
Success = 3,
Danger = 4,
Link = 5,
}
impl From<ButtonStyle> for u8 {
fn from(s: ButtonStyle) -> u8 {
s as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
#[serde(into = "u8")]
pub enum ComponentType {
Button = 1,
Select = 2,
Input = 3,
DatePicker = 4,
Radio = 5,
Animation = 6,
Grid = 7,
}
impl From<ComponentType> for u8 {
fn from(t: ComponentType) -> u8 {
t as u8
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SelectOption {
pub label: String,
pub value: String,
}
impl SelectOption {
pub fn new(label: impl Into<String>, value: impl Into<String>) -> Self {
Self {
label: label.into(),
value: value.into(),
}
}
}
#[derive(Debug, Clone, Serialize)]
struct RowComponent {
id: String,
#[serde(rename = "type")]
kind: ComponentType,
component: Value,
}
#[derive(Debug, Clone, Default)]
pub struct ActionRow {
items: Vec<RowComponent>,
}
impl ActionRow {
pub fn new() -> Self {
Self::default()
}
pub fn button(
mut self,
id: impl Into<String>,
label: impl Into<String>,
style: ButtonStyle,
) -> Self {
let comp = json!({ "label": label.into(), "style": style as u8 });
self.items.push(RowComponent {
id: id.into(),
kind: ComponentType::Button,
component: comp,
});
self
}
pub fn link_button(
mut self,
id: impl Into<String>,
label: impl Into<String>,
url: impl Into<String>,
) -> Self {
let comp = json!({
"label": label.into(),
"style": ButtonStyle::Link as u8,
"url": url.into()
});
self.items.push(RowComponent {
id: id.into(),
kind: ComponentType::Button,
component: comp,
});
self
}
pub fn select(mut self, id: impl Into<String>, options: Vec<SelectOption>) -> Self {
self.items.push(RowComponent {
id: id.into(),
kind: ComponentType::Select,
component: json!({ "options": options }),
});
self
}
pub fn build(self) -> Value {
serde_json::to_value(&ActionRowSer {
components: self.items,
})
.unwrap_or(Value::Null)
}
}
#[derive(Serialize)]
struct ActionRowSer {
components: Vec<RowComponent>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn action_row_with_buttons() {
let row = ActionRow::new()
.button("ok", "OK", ButtonStyle::Primary)
.button("no", "Cancel", ButtonStyle::Danger);
let v = row.build();
let components = v["components"].as_array().unwrap();
assert_eq!(components.len(), 2);
assert_eq!(components[0]["component"]["label"], "OK");
}
#[test]
fn action_row_with_select() {
let row = ActionRow::new().select(
"color",
vec![
SelectOption::new("Red", "red"),
SelectOption::new("Blue", "blue"),
],
);
let v = row.build();
let components = v["components"].as_array().unwrap();
assert_eq!(components.len(), 1);
let opts = components[0]["component"]["options"].as_array().unwrap();
assert_eq!(opts.len(), 2);
}
}