1use anyhow::Result;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum Platform {
10 Slack,
11 Telegram,
12 Discord,
13}
14
15#[derive(Debug, Clone)]
17pub struct IncomingMessage {
18 pub platform: Platform,
19 pub channel_id: String,
20 pub user_id: String,
21 pub user_name: String,
22 pub text: String,
23 pub thread_id: Option<String>,
24 pub message_id: String,
25}
26
27#[derive(Debug, Clone)]
29pub struct OutgoingMessage {
30 pub channel_id: String,
31 pub text: String,
32 pub thread_id: Option<String>,
33 pub blocks: Option<serde_json::Value>, }
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct ApprovalRequest {
39 pub execution_id: String,
40 pub step_name: String,
41 pub description: String,
42 pub action: String,
43 #[serde(default)]
46 pub allowed_approvers: Vec<String>,
47}
48
49#[derive(Debug, Clone, Serialize)]
51pub struct ProgressUpdate {
52 pub execution_id: String,
53 pub workflow_id: String,
54 pub step_index: usize,
55 pub total_steps: usize,
56 pub step_name: String,
57 pub status: ProgressStatus,
58 pub output: Option<String>,
59 pub duration_ms: Option<u64>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
64#[serde(rename_all = "snake_case")]
65pub enum ProgressStatus {
66 Started,
67 StepRunning,
68 StepDone,
69 StepFailed,
70 Completed,
71 Failed,
72}
73
74#[derive(Debug, Clone, Serialize)]
76pub struct WorkflowNotification {
77 pub execution_id: String,
78 pub workflow_id: String,
79 pub success: bool,
80 pub steps_completed: usize,
81 pub total_steps: usize,
82 pub duration_ms: u64,
83 pub error: Option<String>,
84}
85
86#[derive(Debug, Clone, Deserialize)]
88pub struct InteractionPayload {
89 pub action_id: String,
90 pub execution_id: String,
91 pub user_id: String,
92 pub user_name: String,
93}
94
95#[allow(async_fn_in_trait)]
97pub trait ChatPlatform: Send + Sync {
98 async fn send_message(&self, msg: &OutgoingMessage) -> Result<String>;
100
101 async fn send_approval(&self, channel_id: &str, request: &ApprovalRequest) -> Result<String>;
103
104 async fn update_message(&self, channel_id: &str, message_id: &str, text: &str) -> Result<()>;
106
107 async fn add_reaction(&self, channel_id: &str, message_id: &str, emoji: &str) -> Result<()>;
109
110 async fn send_progress(
112 &self,
113 channel_id: &str,
114 thread_id: &str,
115 progress: &ProgressUpdate,
116 ) -> Result<String>;
117
118 async fn send_notification(
120 &self,
121 channel_id: &str,
122 thread_id: Option<&str>,
123 notification: &WorkflowNotification,
124 ) -> Result<String>;
125
126 async fn start_thread(
129 &self,
130 channel_id: &str,
131 execution_id: &str,
132 workflow_id: &str,
133 total_steps: usize,
134 shadow: bool,
135 ) -> Result<String>;
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test_platform_serialization() {
144 let p = Platform::Slack;
145 let json = serde_json::to_string(&p).unwrap();
146 assert_eq!(json, "\"slack\"");
147 }
148
149 #[test]
150 fn test_progress_status_serialization() {
151 let s = ProgressStatus::StepRunning;
152 let json = serde_json::to_string(&s).unwrap();
153 assert_eq!(json, "\"step_running\"");
154 }
155
156 #[test]
157 fn test_progress_update() {
158 let update = ProgressUpdate {
159 execution_id: "e1".into(),
160 workflow_id: "w1".into(),
161 step_index: 0,
162 total_steps: 3,
163 step_name: "build".into(),
164 status: ProgressStatus::StepRunning,
165 output: None,
166 duration_ms: None,
167 };
168 let json = serde_json::to_string(&update).unwrap();
169 assert!(json.contains("step_running"));
170 }
171
172 #[test]
173 fn test_workflow_notification() {
174 let notif = WorkflowNotification {
175 execution_id: "e1".into(),
176 workflow_id: "deploy".into(),
177 success: true,
178 steps_completed: 3,
179 total_steps: 3,
180 duration_ms: 1500,
181 error: None,
182 };
183 let json = serde_json::to_string(¬if).unwrap();
184 assert!(json.contains("\"success\":true"));
185 }
186
187 #[test]
188 fn test_interaction_payload_deserialize() {
189 let json = r#"{"action_id":"approve","execution_id":"e1","user_id":"U123","user_name":"alice"}"#;
190 let payload: InteractionPayload = serde_json::from_str(json).unwrap();
191 assert_eq!(payload.action_id, "approve");
192 assert_eq!(payload.execution_id, "e1");
193 }
194}