Skip to main content

rusmes_cli/
client.rs

1//! HTTP client for communicating with RusMES server
2
3use anyhow::{Context, Result};
4use serde::{de::DeserializeOwned, Serialize};
5
6pub struct Client {
7    base_url: String,
8    client: reqwest::Client,
9}
10
11impl Client {
12    /// Create a new client
13    pub fn new(base_url: &str) -> Result<Self> {
14        let client = reqwest::Client::builder()
15            .timeout(std::time::Duration::from_secs(30))
16            .build()
17            .context("Failed to create HTTP client")?;
18
19        Ok(Self {
20            base_url: base_url.to_string(),
21            client,
22        })
23    }
24
25    /// GET request
26    pub async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
27        let url = format!("{}{}", self.base_url, path);
28        let response = self
29            .client
30            .get(&url)
31            .send()
32            .await
33            .context("Failed to send GET request")?;
34
35        if !response.status().is_success() {
36            let status = response.status();
37            let text = response.text().await.unwrap_or_default();
38            anyhow::bail!("Request failed ({}): {}", status, text);
39        }
40
41        response
42            .json()
43            .await
44            .context("Failed to parse JSON response")
45    }
46
47    /// POST request
48    pub async fn post<T: DeserializeOwned, B: Serialize>(&self, path: &str, body: &B) -> Result<T> {
49        let url = format!("{}{}", self.base_url, path);
50        let response = self
51            .client
52            .post(&url)
53            .json(body)
54            .send()
55            .await
56            .context("Failed to send POST request")?;
57
58        if !response.status().is_success() {
59            let status = response.status();
60            let text = response.text().await.unwrap_or_default();
61            anyhow::bail!("Request failed ({}): {}", status, text);
62        }
63
64        response
65            .json()
66            .await
67            .context("Failed to parse JSON response")
68    }
69
70    /// PUT request
71    pub async fn put<T: DeserializeOwned, B: Serialize>(&self, path: &str, body: &B) -> Result<T> {
72        let url = format!("{}{}", self.base_url, path);
73        let response = self
74            .client
75            .put(&url)
76            .json(body)
77            .send()
78            .await
79            .context("Failed to send PUT request")?;
80
81        if !response.status().is_success() {
82            let status = response.status();
83            let text = response.text().await.unwrap_or_default();
84            anyhow::bail!("Request failed ({}): {}", status, text);
85        }
86
87        response
88            .json()
89            .await
90            .context("Failed to parse JSON response")
91    }
92
93    /// DELETE request
94    pub async fn delete<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
95        let url = format!("{}{}", self.base_url, path);
96        let response = self
97            .client
98            .delete(&url)
99            .send()
100            .await
101            .context("Failed to send DELETE request")?;
102
103        if !response.status().is_success() {
104            let status = response.status();
105            let text = response.text().await.unwrap_or_default();
106            anyhow::bail!("Request failed ({}): {}", status, text);
107        }
108
109        response
110            .json()
111            .await
112            .context("Failed to parse JSON response")
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_client_creation() {
122        let client = Client::new("http://localhost:8080").unwrap();
123        assert_eq!(client.base_url, "http://localhost:8080");
124    }
125
126    #[test]
127    fn test_url_formatting() {
128        let client = Client::new("http://localhost:8080").unwrap();
129        let url = format!("{}{}", client.base_url, "/api/users");
130        assert_eq!(url, "http://localhost:8080/api/users");
131    }
132
133    #[test]
134    fn test_client_with_https() {
135        let client = Client::new("https://mail.example.com:8080").unwrap();
136        assert_eq!(client.base_url, "https://mail.example.com:8080");
137    }
138
139    #[test]
140    fn test_url_formatting_with_query() {
141        let client = Client::new("http://localhost:8080").unwrap();
142        let url = format!("{}{}", client.base_url, "/api/queue?status=pending");
143        assert_eq!(url, "http://localhost:8080/api/queue?status=pending");
144    }
145
146    #[test]
147    fn test_client_with_trailing_slash() {
148        let client = Client::new("http://localhost:8080/").unwrap();
149        assert_eq!(client.base_url, "http://localhost:8080/");
150    }
151
152    #[test]
153    fn test_multiple_path_segments() {
154        let client = Client::new("http://localhost:8080").unwrap();
155        let url = format!(
156            "{}{}",
157            client.base_url, "/api/users/test@example.com/mailboxes"
158        );
159        assert_eq!(
160            url,
161            "http://localhost:8080/api/users/test@example.com/mailboxes"
162        );
163    }
164}