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}