use serde_json::{json, Value};
const TEXT_SHORT: u8 = 1;
const TEXT_PARAGRAPH: u8 = 2;
pub struct ModalBuilder {
custom_id: String,
title: String,
components: Vec<Value>,
}
impl ModalBuilder {
pub fn new(custom_id: impl Into<String>, title: impl Into<String>) -> Self {
Self { custom_id: custom_id.into(), title: title.into(), components: Vec::new() }
}
pub fn short_field(mut self, custom_id: impl Into<String>, label: impl Into<String>) -> Self {
self.push_text_input(custom_id.into(), label.into(), TEXT_SHORT, None, false);
self
}
pub fn required_short_field(mut self, custom_id: impl Into<String>, label: impl Into<String>) -> Self {
self.push_text_input(custom_id.into(), label.into(), TEXT_SHORT, None, true);
self
}
pub fn paragraph_field(mut self, custom_id: impl Into<String>, label: impl Into<String>, placeholder: Option<&str>) -> Self {
self.push_text_input(custom_id.into(), label.into(), TEXT_PARAGRAPH, placeholder.map(str::to_string), false);
self
}
pub fn required_paragraph_field(mut self, custom_id: impl Into<String>, label: impl Into<String>, placeholder: Option<&str>) -> Self {
self.push_text_input(custom_id.into(), label.into(), TEXT_PARAGRAPH, placeholder.map(str::to_string), true);
self
}
fn push_text_input(&mut self, custom_id: String, label: String, style: u8, placeholder: Option<String>, required: bool) {
let mut input = json!({
"type": 4, "custom_id": custom_id,
"label": label,
"style": style,
"required": required,
});
if let Some(ph) = placeholder {
input["placeholder"] = json!(ph);
}
self.components.push(json!({
"type": 1,
"components": [input],
}));
}
pub fn build(self) -> Value {
json!({
"custom_id": self.custom_id,
"title": self.title,
"components": self.components,
})
}
pub fn into_response(self) -> Value {
json!({
"type": 9, "data": self.build(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_modal() {
let m = ModalBuilder::new("id", "Title").build();
assert_eq!(m["custom_id"], "id");
assert_eq!(m["title"], "Title");
assert_eq!(m["components"].as_array().unwrap().len(), 0);
}
#[test]
fn short_field_wraps_in_action_row() {
let m = ModalBuilder::new("id", "Title").short_field("f1", "Name").build();
let rows = m["components"].as_array().unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["type"], 1); let inner = &rows[0]["components"][0];
assert_eq!(inner["type"], 4); assert_eq!(inner["style"], TEXT_SHORT as i64);
assert_eq!(inner["custom_id"], "f1");
assert_eq!(inner["label"], "Name");
}
#[test]
fn paragraph_with_placeholder() {
let m = ModalBuilder::new("id", "T").paragraph_field("bio", "Bio", Some("Enter bio…")).build();
let inner = &m["components"][0]["components"][0];
assert_eq!(inner["style"], TEXT_PARAGRAPH as i64);
assert_eq!(inner["placeholder"], "Enter bio…");
}
#[test]
fn two_fields() {
let m = ModalBuilder::new("id", "T").short_field("f1", "First").paragraph_field("f2", "Second", None).build();
assert_eq!(m["components"].as_array().unwrap().len(), 2);
}
#[test]
fn into_response_wraps_type_9() {
let r = ModalBuilder::new("id", "T").into_response();
assert_eq!(r["type"], 9);
assert_eq!(r["data"]["custom_id"], "id");
}
}