Skip to main content

slack_rs/api/
envelope.rs

1//! Unified output envelope for all commands
2//!
3//! Provides a consistent output structure with response and metadata
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8/// Unified command response with envelope
9#[derive(Debug, Serialize, Deserialize)]
10pub struct CommandResponse {
11    /// Schema version for introspection
12    #[serde(rename = "schemaVersion")]
13    pub schema_version: u32,
14
15    /// Response type identifier for introspection
16    #[serde(rename = "type")]
17    pub response_type: String,
18
19    /// Indicates if the operation was successful
20    pub ok: bool,
21
22    /// Original API response
23    pub response: Value,
24
25    /// Execution metadata
26    pub meta: CommandMeta,
27}
28
29/// Command execution metadata
30#[derive(Debug, Serialize, Deserialize)]
31pub struct CommandMeta {
32    pub profile_name: Option<String>,
33    pub team_id: String,
34    pub user_id: String,
35    pub method: String,
36    pub command: String,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub token_type: Option<String>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub idempotency_key: Option<String>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub idempotency_status: Option<String>,
43}
44
45impl CommandResponse {
46    /// Create a new command response with metadata
47    pub fn new(
48        response: Value,
49        profile_name: Option<String>,
50        team_id: String,
51        user_id: String,
52        method: String,
53        command: String,
54    ) -> Self {
55        // Extract 'ok' from Slack API response if present
56        let ok = response
57            .as_object()
58            .and_then(|obj| obj.get("ok"))
59            .and_then(|v| v.as_bool())
60            .unwrap_or(true);
61
62        // Generate type from method (e.g., "conversations.list" -> "conversations.list")
63        let response_type = method.clone();
64
65        Self {
66            schema_version: 1,
67            response_type,
68            ok,
69            response,
70            meta: CommandMeta {
71                profile_name,
72                team_id,
73                user_id,
74                method,
75                command,
76                token_type: None,
77                idempotency_key: None,
78                idempotency_status: None,
79            },
80        }
81    }
82
83    /// Create a new command response with metadata including token type
84    pub fn with_token_type(
85        response: Value,
86        profile_name: Option<String>,
87        team_id: String,
88        user_id: String,
89        method: String,
90        command: String,
91        token_type: Option<String>,
92    ) -> Self {
93        // Extract 'ok' from Slack API response if present
94        let ok = response
95            .as_object()
96            .and_then(|obj| obj.get("ok"))
97            .and_then(|v| v.as_bool())
98            .unwrap_or(true);
99
100        // Generate type from method (e.g., "conversations.list" -> "conversations.list")
101        let response_type = method.clone();
102
103        Self {
104            schema_version: 1,
105            response_type,
106            ok,
107            response,
108            meta: CommandMeta {
109                profile_name,
110                team_id,
111                user_id,
112                method,
113                command,
114                token_type,
115                idempotency_key: None,
116                idempotency_status: None,
117            },
118        }
119    }
120
121    /// Set idempotency metadata
122    pub fn with_idempotency(mut self, key: String, status: String) -> Self {
123        self.meta.idempotency_key = Some(key);
124        self.meta.idempotency_status = Some(status);
125        self
126    }
127}