1use serde::{Deserialize, Serialize};
3use serde_json::{Map, Value};
4
5#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
6#[serde(rename_all = "snake_case")]
7pub enum InteractionTone {
8 #[default]
9 Normal,
10 Danger,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16#[serde(rename_all = "camelCase", deny_unknown_fields)]
17pub struct InteractionCard {
18 #[serde(rename = "type")]
19 pub card_type: String,
20 pub title: String,
21 pub message: String,
22 pub tone: InteractionTone,
23 pub continue_label: String,
24 pub cancel_label: String,
25 pub data: Map<String, Value>,
26}
27
28impl InteractionCard {
29 #[must_use]
30 pub fn new(
31 card_type: impl Into<String>,
32 title: impl Into<String>,
33 message: impl Into<String>,
34 ) -> Self {
35 Self {
36 card_type: card_type.into(),
37 title: title.into(),
38 message: message.into(),
39 tone: InteractionTone::Normal,
40 continue_label: "Continue".into(),
41 cancel_label: "Cancel".into(),
42 data: Map::new(),
43 }
44 }
45
46 #[must_use]
47 pub fn is_valid(&self) -> bool {
48 !self.card_type.is_empty()
49 && self
50 .card_type
51 .bytes()
52 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == b'.' || c == b'_')
53 && !self.title.trim().is_empty()
54 && !self.continue_label.trim().is_empty()
55 && !self.cancel_label.trim().is_empty()
56 }
57}
58
59#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
60#[serde(rename_all = "snake_case")]
61pub enum InteractionKind {
62 Approval,
63 UserAction,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
67#[serde(rename_all = "camelCase", deny_unknown_fields)]
68pub struct PendingInteractionItem {
69 pub action_id: String,
70 pub kind: InteractionKind,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub expires_at: Option<String>,
73 pub card: InteractionCard,
74}
75
76impl PendingInteractionItem {
77 #[must_use]
78 pub fn accepts(&self, response: &InteractionResponse) -> bool {
79 self.action_id == response.action_id
80 && matches!(
81 (self.kind, &response.response),
82 (
83 InteractionKind::Approval,
84 InteractionAnswer::Approve | InteractionAnswer::Reject
85 ) | (
86 InteractionKind::UserAction,
87 InteractionAnswer::Continue { .. } | InteractionAnswer::Cancel
88 )
89 )
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
94#[serde(rename_all = "camelCase", deny_unknown_fields)]
95pub struct PendingInteraction {
96 pub request_id: String,
97 pub batch_id: String,
98 pub title: String,
99 pub message: String,
100 pub items: Vec<PendingInteractionItem>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
105#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
106pub enum InteractionAnswer {
107 #[serde(deserialize_with = "crate::execution::deserialize_empty_variant")]
108 Approve,
109 #[serde(deserialize_with = "crate::execution::deserialize_empty_variant")]
110 Reject,
111 Continue {
112 data: Value,
113 },
114 #[serde(deserialize_with = "crate::execution::deserialize_empty_variant")]
115 Cancel,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
119#[serde(rename_all = "camelCase", deny_unknown_fields)]
120pub struct InteractionResponse {
121 pub action_id: String,
122 pub response: InteractionAnswer,
123}