1use crate::{Error, Result};
2use reqwest::{Client, Method};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone)]
8pub struct HttpHandler {
9 pub endpoint: String,
10 pub method: HttpMethod,
11 pub headers: HashMap<String, String>,
12 pub auth: Option<AuthConfig>,
13 client: Client,
14}
15
16#[derive(Debug, Clone)]
17pub enum HttpMethod {
18 Get,
19 Post,
20 Put,
21 Delete,
22 Patch,
23}
24
25#[derive(Debug, Clone)]
26pub enum AuthConfig {
27 Bearer { token: String },
28 Basic { username: String, password: String },
29 ApiKey { key: String, header: String },
30}
31
32#[derive(Debug, Deserialize, JsonSchema)]
33pub struct HttpInput {
34 #[serde(default)]
35 pub body: Option<serde_json::Value>,
36 #[serde(default)]
37 pub query: HashMap<String, String>,
38}
39
40#[derive(Debug, Serialize, JsonSchema)]
41pub struct HttpOutput {
42 pub status: u16,
43 pub body: serde_json::Value,
44 pub headers: HashMap<String, String>,
45}
46
47impl HttpHandler {
48 pub fn new(
49 endpoint: String,
50 method: HttpMethod,
51 headers: HashMap<String, String>,
52 auth: Option<AuthConfig>,
53 ) -> Self {
54 Self {
55 endpoint,
56 method,
57 headers,
58 auth,
59 client: Client::new(),
60 }
61 }
62
63 pub async fn execute(&self, input: HttpInput) -> Result<HttpOutput> {
64 let method = match self.method {
65 HttpMethod::Get => Method::GET,
66 HttpMethod::Post => Method::POST,
67 HttpMethod::Put => Method::PUT,
68 HttpMethod::Delete => Method::DELETE,
69 HttpMethod::Patch => Method::PATCH,
70 };
71
72 let mut request = self.client.request(method, &self.endpoint);
73
74 for (k, v) in &self.headers {
76 request = request.header(k, v);
77 }
78
79 if let Some(auth) = &self.auth {
81 request = match auth {
82 AuthConfig::Bearer { token } => request.bearer_auth(token),
83 AuthConfig::Basic { username, password } => {
84 request.basic_auth(username, Some(password))
85 }
86 AuthConfig::ApiKey { key, header } => request.header(header, key),
87 };
88 }
89
90 if !input.query.is_empty() {
92 request = request.query(&input.query);
93 }
94
95 if let Some(body) = input.body {
97 request = request.json(&body);
98 }
99
100 let response = request
102 .send()
103 .await
104 .map_err(|e| Error::Http(format!("Request failed: {}", e)))?;
105
106 let status = response.status().as_u16();
107
108 let mut headers = HashMap::new();
110 for (k, v) in response.headers() {
111 if let Ok(v_str) = v.to_str() {
112 headers.insert(k.to_string(), v_str.to_string());
113 }
114 }
115
116 let body = response
118 .json::<serde_json::Value>()
119 .await
120 .unwrap_or(serde_json::json!({}));
121
122 Ok(HttpOutput {
123 status,
124 body,
125 headers,
126 })
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn test_http_handler_new() {
136 let handler = HttpHandler::new(
137 "https://api.example.com".to_string(),
138 HttpMethod::Get,
139 HashMap::new(),
140 None,
141 );
142
143 assert_eq!(handler.endpoint, "https://api.example.com");
144 assert!(handler.headers.is_empty());
145 assert!(handler.auth.is_none());
146 }
147
148 #[test]
149 fn test_http_handler_new_with_auth() {
150 let mut headers = HashMap::new();
151 headers.insert("Content-Type".to_string(), "application/json".to_string());
152
153 let auth = Some(AuthConfig::Bearer {
154 token: "test_token".to_string(),
155 });
156
157 let handler = HttpHandler::new(
158 "https://api.example.com".to_string(),
159 HttpMethod::Post,
160 headers.clone(),
161 auth,
162 );
163
164 assert_eq!(handler.endpoint, "https://api.example.com");
165 assert_eq!(handler.headers.len(), 1);
166 assert!(handler.auth.is_some());
167 }
168
169 #[test]
170 fn test_http_input_with_body() {
171 let json = r#"{"body": {"key": "value"}, "query": {}}"#;
172 let input: HttpInput = serde_json::from_str(json).unwrap();
173
174 assert!(input.body.is_some());
175 assert_eq!(input.body.unwrap()["key"], "value");
176 }
177
178 #[test]
179 fn test_http_input_with_query() {
180 let json = r#"{"body": null, "query": {"param": "value"}}"#;
181 let input: HttpInput = serde_json::from_str(json).unwrap();
182
183 assert!(input.body.is_none());
184 assert_eq!(input.query.get("param"), Some(&"value".to_string()));
185 }
186
187 #[test]
188 fn test_http_output_serialization() {
189 let mut headers = HashMap::new();
190 headers.insert("content-type".to_string(), "application/json".to_string());
191
192 let output = HttpOutput {
193 status: 200,
194 body: serde_json::json!({"result": "success"}),
195 headers,
196 };
197
198 let json = serde_json::to_string(&output).unwrap();
199 assert!(json.contains("\"status\":200"));
200 assert!(json.contains("\"result\":\"success\""));
201 }
202
203 #[tokio::test]
204 async fn test_execute_get_request() {
205 let mut server = mockito::Server::new_async().await;
206 let mock = server
207 .mock("GET", "/test")
208 .with_status(200)
209 .with_header("content-type", "application/json")
210 .with_body(r#"{"message": "success"}"#)
211 .create_async()
212 .await;
213
214 let handler = HttpHandler::new(
215 format!("{}/test", server.url()),
216 HttpMethod::Get,
217 HashMap::new(),
218 None,
219 );
220
221 let input = HttpInput {
222 body: None,
223 query: HashMap::new(),
224 };
225
226 let output = handler.execute(input).await.unwrap();
227
228 assert_eq!(output.status, 200);
229 assert_eq!(output.body["message"], "success");
230 mock.assert_async().await;
231 }
232
233 #[tokio::test]
234 async fn test_execute_post_request_with_body() {
235 let mut server = mockito::Server::new_async().await;
236 let mock = server
237 .mock("POST", "/api/data")
238 .match_header("content-type", "application/json")
239 .match_body(mockito::Matcher::JsonString(
240 r#"{"key":"value"}"#.to_string(),
241 ))
242 .with_status(201)
243 .with_body(r#"{"id": "123"}"#)
244 .create_async()
245 .await;
246
247 let handler = HttpHandler::new(
248 format!("{}/api/data", server.url()),
249 HttpMethod::Post,
250 HashMap::new(),
251 None,
252 );
253
254 let input = HttpInput {
255 body: Some(serde_json::json!({"key": "value"})),
256 query: HashMap::new(),
257 };
258
259 let output = handler.execute(input).await.unwrap();
260
261 assert_eq!(output.status, 201);
262 assert_eq!(output.body["id"], "123");
263 mock.assert_async().await;
264 }
265
266 #[tokio::test]
267 async fn test_execute_with_query_params() {
268 let mut server = mockito::Server::new_async().await;
269 let mock = server
270 .mock("GET", "/search")
271 .match_query(mockito::Matcher::AllOf(vec![
272 mockito::Matcher::UrlEncoded("q".to_string(), "rust".to_string()),
273 mockito::Matcher::UrlEncoded("limit".to_string(), "10".to_string()),
274 ]))
275 .with_status(200)
276 .with_body(r#"{"results": []}"#)
277 .create_async()
278 .await;
279
280 let handler = HttpHandler::new(
281 format!("{}/search", server.url()),
282 HttpMethod::Get,
283 HashMap::new(),
284 None,
285 );
286
287 let mut query = HashMap::new();
288 query.insert("q".to_string(), "rust".to_string());
289 query.insert("limit".to_string(), "10".to_string());
290
291 let input = HttpInput { body: None, query };
292
293 let output = handler.execute(input).await.unwrap();
294
295 assert_eq!(output.status, 200);
296 mock.assert_async().await;
297 }
298
299 #[tokio::test]
300 async fn test_execute_with_bearer_auth() {
301 let mut server = mockito::Server::new_async().await;
302 let mock = server
303 .mock("GET", "/protected")
304 .match_header("authorization", "Bearer secret_token")
305 .with_status(200)
306 .with_body(r#"{"authorized": true}"#)
307 .create_async()
308 .await;
309
310 let handler = HttpHandler::new(
311 format!("{}/protected", server.url()),
312 HttpMethod::Get,
313 HashMap::new(),
314 Some(AuthConfig::Bearer {
315 token: "secret_token".to_string(),
316 }),
317 );
318
319 let input = HttpInput {
320 body: None,
321 query: HashMap::new(),
322 };
323
324 let output = handler.execute(input).await.unwrap();
325
326 assert_eq!(output.status, 200);
327 assert_eq!(output.body["authorized"], true);
328 mock.assert_async().await;
329 }
330
331 #[tokio::test]
332 async fn test_execute_with_basic_auth() {
333 let mut server = mockito::Server::new_async().await;
334 let mock = server
335 .mock("GET", "/admin")
336 .match_header("authorization", "Basic dXNlcjpwYXNz")
337 .with_status(200)
338 .with_body(r#"{"admin": true}"#)
339 .create_async()
340 .await;
341
342 let handler = HttpHandler::new(
343 format!("{}/admin", server.url()),
344 HttpMethod::Get,
345 HashMap::new(),
346 Some(AuthConfig::Basic {
347 username: "user".to_string(),
348 password: "pass".to_string(),
349 }),
350 );
351
352 let input = HttpInput {
353 body: None,
354 query: HashMap::new(),
355 };
356
357 let output = handler.execute(input).await.unwrap();
358
359 assert_eq!(output.status, 200);
360 assert_eq!(output.body["admin"], true);
361 mock.assert_async().await;
362 }
363
364 #[tokio::test]
365 async fn test_execute_with_api_key() {
366 let mut server = mockito::Server::new_async().await;
367 let mock = server
368 .mock("GET", "/api")
369 .match_header("x-api-key", "my_api_key")
370 .with_status(200)
371 .with_body(r#"{"valid": true}"#)
372 .create_async()
373 .await;
374
375 let handler = HttpHandler::new(
376 format!("{}/api", server.url()),
377 HttpMethod::Get,
378 HashMap::new(),
379 Some(AuthConfig::ApiKey {
380 key: "my_api_key".to_string(),
381 header: "x-api-key".to_string(),
382 }),
383 );
384
385 let input = HttpInput {
386 body: None,
387 query: HashMap::new(),
388 };
389
390 let output = handler.execute(input).await.unwrap();
391
392 assert_eq!(output.status, 200);
393 assert_eq!(output.body["valid"], true);
394 mock.assert_async().await;
395 }
396
397 #[tokio::test]
398 async fn test_execute_with_custom_headers() {
399 let mut server = mockito::Server::new_async().await;
400 let mock = server
401 .mock("GET", "/headers")
402 .match_header("x-custom", "custom_value")
403 .match_header("x-request-id", "123")
404 .with_status(200)
405 .with_body(r#"{"ok": true}"#)
406 .create_async()
407 .await;
408
409 let mut headers = HashMap::new();
410 headers.insert("x-custom".to_string(), "custom_value".to_string());
411 headers.insert("x-request-id".to_string(), "123".to_string());
412
413 let handler = HttpHandler::new(
414 format!("{}/headers", server.url()),
415 HttpMethod::Get,
416 headers,
417 None,
418 );
419
420 let input = HttpInput {
421 body: None,
422 query: HashMap::new(),
423 };
424
425 let output = handler.execute(input).await.unwrap();
426
427 assert_eq!(output.status, 200);
428 mock.assert_async().await;
429 }
430
431 #[tokio::test]
432 async fn test_execute_put_request() {
433 let mut server = mockito::Server::new_async().await;
434 let mock = server
435 .mock("PUT", "/update")
436 .with_status(200)
437 .with_body(r#"{"updated": true}"#)
438 .create_async()
439 .await;
440
441 let handler = HttpHandler::new(
442 format!("{}/update", server.url()),
443 HttpMethod::Put,
444 HashMap::new(),
445 None,
446 );
447
448 let input = HttpInput {
449 body: Some(serde_json::json!({"data": "new_value"})),
450 query: HashMap::new(),
451 };
452
453 let output = handler.execute(input).await.unwrap();
454
455 assert_eq!(output.status, 200);
456 assert_eq!(output.body["updated"], true);
457 mock.assert_async().await;
458 }
459
460 #[tokio::test]
461 async fn test_execute_delete_request() {
462 let mut server = mockito::Server::new_async().await;
463 let mock = server
464 .mock("DELETE", "/resource/123")
465 .with_status(204)
466 .with_body("")
467 .create_async()
468 .await;
469
470 let handler = HttpHandler::new(
471 format!("{}/resource/123", server.url()),
472 HttpMethod::Delete,
473 HashMap::new(),
474 None,
475 );
476
477 let input = HttpInput {
478 body: None,
479 query: HashMap::new(),
480 };
481
482 let output = handler.execute(input).await.unwrap();
483
484 assert_eq!(output.status, 204);
485 mock.assert_async().await;
486 }
487
488 #[tokio::test]
489 async fn test_execute_patch_request() {
490 let mut server = mockito::Server::new_async().await;
491 let mock = server
492 .mock("PATCH", "/partial")
493 .with_status(200)
494 .with_body(r#"{"patched": true}"#)
495 .create_async()
496 .await;
497
498 let handler = HttpHandler::new(
499 format!("{}/partial", server.url()),
500 HttpMethod::Patch,
501 HashMap::new(),
502 None,
503 );
504
505 let input = HttpInput {
506 body: Some(serde_json::json!({"field": "value"})),
507 query: HashMap::new(),
508 };
509
510 let output = handler.execute(input).await.unwrap();
511
512 assert_eq!(output.status, 200);
513 assert_eq!(output.body["patched"], true);
514 mock.assert_async().await;
515 }
516
517 #[tokio::test]
518 async fn test_execute_error_handling() {
519 let handler = HttpHandler::new(
520 "http://localhost:1/nonexistent".to_string(),
521 HttpMethod::Get,
522 HashMap::new(),
523 None,
524 );
525
526 let input = HttpInput {
527 body: None,
528 query: HashMap::new(),
529 };
530
531 let result = handler.execute(input).await;
532 assert!(result.is_err());
533 }
534}