terminal_jarvis/api/
api_client.rs

1#![allow(dead_code)]
2
3//! HTTP client implementation with retry logic and error handling
4//!
5//! **STATUS: FUTURE FEATURE** - Reserved for remote service communication
6//! This module provides a robust HTTP client that will be used for communicating
7//! with remote Terminal Jarvis services for tool updates, metadata, and discovery.
8
9use crate::api::api_base::ApiBase;
10use anyhow::{anyhow, Result};
11use reqwest::{Client, ClientBuilder, Response};
12use serde::de::DeserializeOwned;
13use std::time::Duration;
14
15/// HTTP client abstraction layer
16pub struct ApiClient {
17    client: Client,
18    config: ApiBase,
19}
20
21impl Default for ApiClient {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl ApiClient {
28    pub fn new() -> Self {
29        let config = ApiBase::new();
30        let client = ClientBuilder::new()
31            .timeout(Duration::from_secs(config.timeout_seconds))
32            .build()
33            .expect("Failed to create HTTP client");
34
35        Self { client, config }
36    }
37
38    pub fn with_config(config: ApiBase) -> Self {
39        let client = ClientBuilder::new()
40            .timeout(Duration::from_secs(config.timeout_seconds))
41            .build()
42            .expect("Failed to create HTTP client");
43
44        Self { client, config }
45    }
46
47    /// Make a GET request
48    pub async fn get<T>(&self, path: &str) -> Result<T>
49    where
50        T: DeserializeOwned,
51    {
52        let url = self.config.endpoint_url(path);
53        let response = self.client.get(&url).send().await?;
54
55        self.handle_response(response).await
56    }
57
58    /// Make a POST request
59    pub async fn post<T, B>(&self, path: &str, body: &B) -> Result<T>
60    where
61        T: DeserializeOwned,
62        B: serde::Serialize,
63    {
64        let url = self.config.endpoint_url(path);
65        let response = self.client.post(&url).json(body).send().await?;
66
67        self.handle_response(response).await
68    }
69
70    /// Make a PUT request
71    pub async fn put<T, B>(&self, path: &str, body: &B) -> Result<T>
72    where
73        T: DeserializeOwned,
74        B: serde::Serialize,
75    {
76        let url = self.config.endpoint_url(path);
77        let response = self.client.put(&url).json(body).send().await?;
78
79        self.handle_response(response).await
80    }
81
82    /// Make a DELETE request
83    pub async fn delete<T>(&self, path: &str) -> Result<T>
84    where
85        T: DeserializeOwned,
86    {
87        let url = self.config.endpoint_url(path);
88        let response = self.client.delete(&url).send().await?;
89
90        self.handle_response(response).await
91    }
92
93    /// Handle HTTP response and deserialize JSON
94    async fn handle_response<T>(&self, response: Response) -> Result<T>
95    where
96        T: DeserializeOwned,
97    {
98        let status = response.status();
99
100        if !status.is_success() {
101            let error_text = response
102                .text()
103                .await
104                .unwrap_or_else(|_| "Unknown error".to_string());
105            return Err(anyhow!("HTTP {} error: {}", status, error_text));
106        }
107
108        let json = response.json::<T>().await?;
109        Ok(json)
110    }
111
112    /// Get the base configuration
113    pub fn config(&self) -> &ApiBase {
114        &self.config
115    }
116}