1use super::args::ApiCallArgs;
10use super::client::{ApiClient, RequestBody};
11use super::guidance::format_error_guidance;
12use reqwest::Method;
13use serde::{Deserialize, Serialize};
14use serde_json::{json, Value};
15use thiserror::Error;
16
17#[derive(Debug, Error)]
18pub enum ApiCallError {
19 #[error("Client error: {0}")]
20 ClientError(#[from] super::client::ApiClientError),
21
22 #[error("Failed to parse response: {0}")]
23 ParseError(String),
24}
25
26pub type Result<T> = std::result::Result<T, ApiCallError>;
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ApiCallContext {
31 pub profile_name: Option<String>,
32 pub team_id: String,
33 pub user_id: String,
34}
35
36#[derive(Debug, Serialize, Deserialize)]
38pub struct ApiCallResponse {
39 pub response: Value,
41
42 pub meta: ApiCallMeta,
44}
45
46#[derive(Debug, Serialize, Deserialize)]
48pub struct ApiCallMeta {
49 pub profile_name: Option<String>,
50 pub team_id: String,
51 pub user_id: String,
52 pub method: String,
53 pub command: String,
54 pub token_type: String,
55}
56
57pub async fn execute_api_call(
59 client: &ApiClient,
60 args: &ApiCallArgs,
61 token: &str,
62 context: &ApiCallContext,
63 token_type: &str,
64 command: &str,
65) -> Result<ApiCallResponse> {
66 let method = if args.use_get {
68 Method::GET
69 } else {
70 Method::POST
71 };
72
73 let body = if args.use_json {
75 RequestBody::Json(args.to_json())
76 } else if method == Method::POST {
77 RequestBody::Form(args.to_form())
78 } else {
79 RequestBody::None
80 };
81
82 let response = client.call(method, &args.method, token, body).await?;
84
85 let response_text = response
87 .text()
88 .await
89 .map_err(|e| ApiCallError::ParseError(e.to_string()))?;
90
91 let response_json: Value = serde_json::from_str(&response_text)
92 .map_err(|e| ApiCallError::ParseError(e.to_string()))?;
93
94 let api_response = ApiCallResponse {
96 response: response_json,
97 meta: ApiCallMeta {
98 profile_name: context.profile_name.clone(),
99 team_id: context.team_id.clone(),
100 user_id: context.user_id.clone(),
101 method: args.method.clone(),
102 command: command.to_string(),
103 token_type: token_type.to_string(),
104 },
105 };
106
107 Ok(api_response)
108}
109
110pub fn display_error_guidance(response: &ApiCallResponse) {
112 if let Some(ok) = response.response.get("ok").and_then(|v| v.as_bool()) {
114 if !ok {
115 if let Some(error_code) = response.response.get("error").and_then(|v| v.as_str()) {
117 if let Some(guidance) = format_error_guidance(error_code) {
119 eprintln!("{}", guidance);
120 }
121 }
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use std::collections::HashMap;
130
131 #[test]
132 fn test_api_call_meta_serialization() {
133 let meta = ApiCallMeta {
134 profile_name: Some("default".to_string()),
135 team_id: "T123ABC".to_string(),
136 user_id: "U456DEF".to_string(),
137 method: "chat.postMessage".to_string(),
138 command: "api call".to_string(),
139 token_type: "bot".to_string(),
140 };
141
142 let json = serde_json::to_string(&meta).unwrap();
143 let deserialized: ApiCallMeta = serde_json::from_str(&json).unwrap();
144
145 assert_eq!(deserialized.profile_name, Some("default".to_string()));
146 assert_eq!(deserialized.team_id, "T123ABC");
147 assert_eq!(deserialized.user_id, "U456DEF");
148 assert_eq!(deserialized.method, "chat.postMessage");
149 assert_eq!(deserialized.command, "api call");
150 assert_eq!(deserialized.token_type, "bot");
151 }
152
153 #[test]
154 fn test_api_call_response_structure() {
155 let response = ApiCallResponse {
156 response: json!({
157 "ok": true,
158 "channel": "C123456",
159 "ts": "1234567890.123456"
160 }),
161 meta: ApiCallMeta {
162 profile_name: Some("work".to_string()),
163 team_id: "T123ABC".to_string(),
164 user_id: "U456DEF".to_string(),
165 method: "chat.postMessage".to_string(),
166 command: "api call".to_string(),
167 token_type: "bot".to_string(),
168 },
169 };
170
171 let json = serde_json::to_value(&response).unwrap();
172
173 assert!(json["response"]["ok"].as_bool().unwrap());
174 assert_eq!(json["meta"]["team_id"], "T123ABC");
175 assert_eq!(json["meta"]["method"], "chat.postMessage");
176 assert_eq!(json["meta"]["command"], "api call");
177 assert_eq!(json["meta"]["token_type"], "bot");
178 }
179
180 #[test]
181 fn test_display_error_guidance_with_known_error() {
182 let response = ApiCallResponse {
184 response: json!({
185 "ok": false,
186 "error": "missing_scope"
187 }),
188 meta: ApiCallMeta {
189 profile_name: Some("default".to_string()),
190 team_id: "T123ABC".to_string(),
191 user_id: "U456DEF".to_string(),
192 method: "chat.postMessage".to_string(),
193 command: "api call".to_string(),
194 token_type: "bot".to_string(),
195 },
196 };
197
198 display_error_guidance(&response);
200 }
201
202 #[test]
203 fn test_display_error_guidance_with_unknown_error() {
204 let response = ApiCallResponse {
206 response: json!({
207 "ok": false,
208 "error": "unknown_error_code"
209 }),
210 meta: ApiCallMeta {
211 profile_name: Some("default".to_string()),
212 team_id: "T123ABC".to_string(),
213 user_id: "U456DEF".to_string(),
214 method: "chat.postMessage".to_string(),
215 command: "api call".to_string(),
216 token_type: "bot".to_string(),
217 },
218 };
219
220 display_error_guidance(&response);
222 }
223
224 #[test]
225 fn test_display_error_guidance_with_success() {
226 let response = ApiCallResponse {
228 response: json!({
229 "ok": true,
230 "channel": "C123456"
231 }),
232 meta: ApiCallMeta {
233 profile_name: Some("default".to_string()),
234 team_id: "T123ABC".to_string(),
235 user_id: "U456DEF".to_string(),
236 method: "chat.postMessage".to_string(),
237 command: "api call".to_string(),
238 token_type: "bot".to_string(),
239 },
240 };
241
242 display_error_guidance(&response);
244 }
245
246 #[test]
247 fn test_display_error_guidance_with_not_allowed_token_type() {
248 let response = ApiCallResponse {
250 response: json!({
251 "ok": false,
252 "error": "not_allowed_token_type"
253 }),
254 meta: ApiCallMeta {
255 profile_name: Some("default".to_string()),
256 team_id: "T123ABC".to_string(),
257 user_id: "U456DEF".to_string(),
258 method: "conversations.history".to_string(),
259 command: "api call".to_string(),
260 token_type: "bot".to_string(),
261 },
262 };
263
264 display_error_guidance(&response);
266 }
267
268 #[test]
269 fn test_display_error_guidance_with_invalid_auth() {
270 let response = ApiCallResponse {
272 response: json!({
273 "ok": false,
274 "error": "invalid_auth"
275 }),
276 meta: ApiCallMeta {
277 profile_name: Some("default".to_string()),
278 team_id: "T123ABC".to_string(),
279 user_id: "U456DEF".to_string(),
280 method: "auth.test".to_string(),
281 command: "api call".to_string(),
282 token_type: "bot".to_string(),
283 },
284 };
285
286 display_error_guidance(&response);
288 }
289}