1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4#[serde(tag = "type", rename_all = "snake_case")]
5pub enum PreviewValue {
6 Text { text: String },
7 TextList { items: Vec<String> },
8 Number { value: f64 },
9 Bool { value: bool },
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13#[serde(rename_all = "camelCase")]
14pub struct PreviewDetail {
15 pub label: String,
16 pub value: PreviewValue,
17}
18
19#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
20#[serde(rename_all = "snake_case")]
21pub enum InteractionTone {
22 #[default]
23 Normal,
24 Danger,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
28#[serde(
29 tag = "type",
30 rename_all = "snake_case",
31 rename_all_fields = "camelCase"
32)]
33pub enum ClientTask {
34 InstallIntegrations { integration_ids: Vec<String> },
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38#[serde(rename_all = "camelCase")]
39pub struct InteractionPreview {
40 pub action_code: String,
41 pub title: String,
42 pub message: String,
43 #[serde(default)]
44 pub tone: InteractionTone,
45 #[serde(default)]
46 pub details: Vec<PreviewDetail>,
47 pub proceed_label: String,
48 pub reject_label: String,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub client_task: Option<ClientTask>,
51}
52
53#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
54#[serde(rename_all = "snake_case")]
55pub enum InteractionKind {
56 #[default]
57 Approval,
58 UserAction,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
62#[serde(rename_all = "camelCase")]
63pub struct PendingInteractionItem {
64 pub action_id: String,
65 pub kind: InteractionKind,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub expires_at: Option<String>,
68 pub preview: InteractionPreview,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72#[serde(rename_all = "camelCase")]
73pub struct PendingInteraction {
74 pub request_id: String,
75 pub batch_id: String,
76 pub title: String,
77 pub message: String,
78 pub items: Vec<PendingInteractionItem>,
79}
80
81#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
82#[serde(rename_all = "snake_case")]
83pub enum InteractionDecisionValue {
84 Approve,
85 Reject,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
89#[serde(rename_all = "camelCase", deny_unknown_fields)]
90pub struct InteractionDecision {
91 pub action_id: String,
92 pub decision: InteractionDecisionValue,
93}
94
95impl Default for InteractionPreview {
96 fn default() -> Self {
97 Self::confirmation(
98 "interaction",
99 "Review action",
100 "Review this action before continuing.",
101 )
102 }
103}
104
105impl InteractionPreview {
106 #[must_use]
107 pub fn confirmation(
108 action_code: impl Into<String>,
109 title: impl Into<String>,
110 message: impl Into<String>,
111 ) -> Self {
112 Self {
113 action_code: action_code.into(),
114 title: title.into(),
115 message: message.into(),
116 tone: InteractionTone::Normal,
117 details: Vec::new(),
118 proceed_label: "Continue".to_owned(),
119 reject_label: "Cancel".to_owned(),
120 client_task: None,
121 }
122 }
123
124 #[must_use]
126 pub fn is_valid(&self) -> bool {
127 !self.action_code.trim().is_empty()
128 && !self.title.trim().is_empty()
129 && !self.proceed_label.trim().is_empty()
130 && !self.reject_label.trim().is_empty()
131 && self.details.iter().all(|detail| {
132 !detail.label.trim().is_empty()
133 && match &detail.value {
134 PreviewValue::Number { value } => value.is_finite(),
135 PreviewValue::TextList { items } => {
136 items.iter().all(|item| !item.trim().is_empty())
137 }
138 PreviewValue::Text { .. } | PreviewValue::Bool { .. } => true,
139 }
140 })
141 && match &self.client_task {
142 Some(ClientTask::InstallIntegrations { integration_ids }) => {
143 !integration_ids.is_empty()
144 && integration_ids
145 .iter()
146 .all(|integration_id| !integration_id.trim().is_empty())
147 }
148 None => true,
149 }
150 }
151}