1use std::sync::Arc;
6use std::time::Duration;
7
8use crate::common::http::{HttpClient, DEFAULT_BASE_URL};
9use crate::error::{ZtkError, ZtkResult};
10
11const DEFAULT_TIMEOUT_SECS: u64 = 30;
13
14#[derive(Debug, Clone)]
29pub struct ZtkClientBuilder {
30 appkey: String,
32 base_url: Option<String>,
34 timeout: Option<Duration>,
36}
37
38impl ZtkClientBuilder {
39 pub fn new(appkey: impl Into<String>) -> Self {
45 Self {
46 appkey: appkey.into(),
47 base_url: None,
48 timeout: None,
49 }
50 }
51
52 pub fn base_url(mut self, url: impl Into<String>) -> Self {
61 self.base_url = Some(url.into());
62 self
63 }
64
65 pub fn timeout(mut self, timeout: Duration) -> Self {
73 self.timeout = Some(timeout);
74 self
75 }
76
77 pub fn build(self) -> ZtkResult<ZtkClient> {
83 if self.appkey.is_empty() {
85 return Err(ZtkError::validation("appkey 不能为空"));
86 }
87
88 let base_url = self
89 .base_url
90 .unwrap_or_else(|| DEFAULT_BASE_URL.to_string());
91 let timeout = self
92 .timeout
93 .unwrap_or(Duration::from_secs(DEFAULT_TIMEOUT_SECS));
94
95 let http_client = HttpClient::new(&base_url, Some(timeout))?;
96
97 Ok(ZtkClient {
98 inner: Arc::new(ZtkClientInner {
99 http_client,
100 base_url,
101 appkey: self.appkey,
102 }),
103 })
104 }
105}
106
107#[derive(Debug)]
109struct ZtkClientInner {
110 http_client: HttpClient,
112 base_url: String,
114 appkey: String,
116}
117
118#[derive(Debug, Clone)]
141pub struct ZtkClient {
142 inner: Arc<ZtkClientInner>,
144}
145
146impl ZtkClient {
147 pub fn new(appkey: impl Into<String>) -> ZtkClientBuilder {
161 ZtkClientBuilder::new(appkey)
162 }
163
164 pub fn appkey(&self) -> &str {
166 &self.inner.appkey
167 }
168
169 pub fn base_url(&self) -> &str {
171 &self.inner.base_url
172 }
173
174 pub(crate) fn http_client(&self) -> &HttpClient {
176 &self.inner.http_client
177 }
178
179 #[cfg(feature = "taobao")]
187 pub fn taobao(&self) -> crate::taobao::TaobaoApi<'_> {
188 crate::taobao::TaobaoApi::new(self)
189 }
190
191 #[cfg(feature = "jd")]
199 pub fn jd(&self) -> crate::jd::JdApi<'_> {
200 crate::jd::JdApi::new(self)
201 }
202
203 #[cfg(feature = "pdd")]
211 pub fn pdd(&self) -> crate::pdd::PddApi<'_> {
212 crate::pdd::PddApi::new(self)
213 }
214
215 #[cfg(feature = "vip")]
223 pub fn vip(&self) -> crate::vip::VipApi<'_> {
224 crate::vip::VipApi::new(self)
225 }
226
227 #[cfg(feature = "meituan")]
235 pub fn meituan(&self) -> crate::meituan::MeituanApi<'_> {
236 crate::meituan::MeituanApi::new(self)
237 }
238
239 #[cfg(feature = "kaola")]
247 pub fn kaola(&self) -> crate::kaola::KaolaApi<'_> {
248 crate::kaola::KaolaApi::new(self)
249 }
250
251 #[cfg(feature = "eleme")]
259 pub fn eleme(&self) -> crate::eleme::ElemeApi<'_> {
260 crate::eleme::ElemeApi::new(self)
261 }
262
263 #[cfg(feature = "douyin")]
271 pub fn douyin(&self) -> crate::douyin::DouyinApi<'_> {
272 crate::douyin::DouyinApi::new(self)
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use crate::common::http::BACKUP_BASE_URL;
280
281 #[test]
282 fn test_builder_new() {
283 let builder = ZtkClientBuilder::new("test_appkey");
284 assert_eq!(builder.appkey, "test_appkey");
285 assert!(builder.base_url.is_none());
286 assert!(builder.timeout.is_none());
287 }
288
289 #[test]
290 fn test_builder_base_url() {
291 let builder = ZtkClientBuilder::new("test_appkey").base_url("https://custom.api.com");
292 assert_eq!(builder.base_url, Some("https://custom.api.com".to_string()));
293 }
294
295 #[test]
296 fn test_builder_timeout() {
297 let builder = ZtkClientBuilder::new("test_appkey").timeout(Duration::from_secs(60));
298 assert_eq!(builder.timeout, Some(Duration::from_secs(60)));
299 }
300
301 #[test]
302 fn test_builder_chain() {
303 let builder = ZtkClientBuilder::new("test_appkey")
304 .base_url("https://custom.api.com")
305 .timeout(Duration::from_secs(60));
306
307 assert_eq!(builder.appkey, "test_appkey");
308 assert_eq!(builder.base_url, Some("https://custom.api.com".to_string()));
309 assert_eq!(builder.timeout, Some(Duration::from_secs(60)));
310 }
311
312 #[test]
313 fn test_build_with_defaults() {
314 let client = ZtkClient::new("test_appkey").build().unwrap();
315
316 assert_eq!(client.appkey(), "test_appkey");
317 assert_eq!(client.base_url(), DEFAULT_BASE_URL);
318 }
319
320 #[test]
321 fn test_build_with_custom_base_url() {
322 let client = ZtkClient::new("test_appkey")
323 .base_url(BACKUP_BASE_URL)
324 .build()
325 .unwrap();
326
327 assert_eq!(client.appkey(), "test_appkey");
328 assert_eq!(client.base_url(), BACKUP_BASE_URL);
329 }
330
331 #[test]
332 fn test_build_empty_appkey_fails() {
333 let result = ZtkClient::new("").build();
334 assert!(result.is_err());
335
336 match result {
337 Err(ZtkError::Validation(msg)) => {
338 assert!(msg.contains("appkey"));
339 }
340 _ => panic!("Expected Validation error"),
341 }
342 }
343
344 #[test]
345 fn test_client_clone() {
346 let client = ZtkClient::new("test_appkey").build().unwrap();
347 let cloned = client.clone();
348
349 assert_eq!(client.appkey(), cloned.appkey());
350 assert_eq!(client.base_url(), cloned.base_url());
351 }
352
353 #[test]
354 fn test_client_new_returns_builder() {
355 let builder = ZtkClient::new("test_appkey");
356 let _client = builder.build().unwrap();
358 }
359}