Skip to main content

chip_sdk/
client.rs

1use crate::error::ChipError;
2use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
3use std::time::Duration;
4
5pub struct ChipClient {
6    pub(crate) http: reqwest::Client,
7    pub(crate) base_url: String,
8}
9
10impl ChipClient {
11    pub fn builder() -> ChipClientBuilder {
12        ChipClientBuilder::default()
13    }
14}
15
16#[derive(Default)]
17pub struct ChipClientBuilder {
18    base_url: Option<String>,
19    token: Option<String>,
20    timeout: Option<Duration>,
21}
22
23impl ChipClientBuilder {
24    pub fn base_url(mut self, url: &str) -> Self {
25        self.base_url = Some(url.trim_end_matches('/').to_string());
26        self
27    }
28
29    pub fn bearer_token(mut self, token: &str) -> Self {
30        self.token = Some(token.to_string());
31        self
32    }
33
34    pub fn timeout(mut self, duration: Duration) -> Self {
35        self.timeout = Some(duration);
36        self
37    }
38
39    pub fn build(self) -> Result<ChipClient, ChipError> {
40        let base_url = self
41            .base_url
42            .ok_or_else(|| ChipError::Config("base_url is required".into()))?;
43        let token = self
44            .token
45            .ok_or_else(|| ChipError::Config("bearer_token is required".into()))?;
46
47        let mut headers = HeaderMap::new();
48        let auth_value = HeaderValue::from_str(&format!("Bearer {}", token))
49            .map_err(|e| ChipError::Config(format!("Invalid token: {}", e)))?;
50        headers.insert(AUTHORIZATION, auth_value);
51
52        let mut client_builder = reqwest::Client::builder().default_headers(headers);
53        if let Some(timeout) = self.timeout {
54            client_builder = client_builder.timeout(timeout);
55        } else {
56            client_builder = client_builder.timeout(Duration::from_secs(60));
57        }
58
59        let http = client_builder
60            .build()
61            .map_err(|e| ChipError::Config(format!("Failed to build HTTP client: {}", e)))?;
62        Ok(ChipClient { http, base_url })
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn builder_creates_client() {
72        let client = ChipClient::builder()
73            .base_url("https://gate.chip-in.asia/api/v1")
74            .bearer_token("test-token")
75            .build();
76        assert!(client.is_ok());
77    }
78
79    #[test]
80    fn builder_fails_without_base_url() {
81        let client = ChipClient::builder().bearer_token("test-token").build();
82        assert!(client.is_err());
83    }
84
85    #[test]
86    fn builder_fails_without_token() {
87        let client = ChipClient::builder()
88            .base_url("https://gate.chip-in.asia/api/v1")
89            .build();
90        assert!(client.is_err());
91    }
92
93    #[test]
94    fn builder_strips_trailing_slash() {
95        let client = ChipClient::builder()
96            .base_url("https://gate.chip-in.asia/api/v1/")
97            .bearer_token("test-token")
98            .build()
99            .unwrap();
100        assert!(!client.base_url.ends_with('/'));
101    }
102
103    #[test]
104    fn builder_with_timeout() {
105        let client = ChipClient::builder()
106            .base_url("https://gate.chip-in.asia/api/v1")
107            .bearer_token("test-token")
108            .timeout(Duration::from_secs(30))
109            .build();
110        assert!(client.is_ok());
111    }
112}