ztk_rust_sdk/
client.rs

1//! 核心客户端实现
2//!
3//! 提供 ZtkClient 和 ZtkClientBuilder,用于配置和发起 API 请求
4
5use std::sync::Arc;
6use std::time::Duration;
7
8use crate::common::http::{HttpClient, DEFAULT_BASE_URL};
9use crate::error::{ZtkError, ZtkResult};
10
11/// 默认请求超时时间 (秒)
12const DEFAULT_TIMEOUT_SECS: u64 = 30;
13
14/// 折淘客 SDK 客户端构建器
15///
16/// 使用 Builder 模式配置客户端参数
17///
18/// # Example
19///
20/// ```rust,ignore
21/// use ztk_rust_sdk::ZtkClient;
22///
23/// let client = ZtkClient::new("your_appkey")
24///     .base_url("https://api.zhetaoke.com:10001")
25///     .timeout(std::time::Duration::from_secs(60))
26///     .build()?;
27/// ```
28#[derive(Debug, Clone)]
29pub struct ZtkClientBuilder {
30    /// 折淘客 AppKey
31    appkey: String,
32    /// API 基础地址 (可选,默认使用主接口地址)
33    base_url: Option<String>,
34    /// 请求超时时间 (可选,默认 30 秒)
35    timeout: Option<Duration>,
36}
37
38impl ZtkClientBuilder {
39    /// 创建新的客户端构建器
40    ///
41    /// # Arguments
42    ///
43    /// * `appkey` - 折淘客 AppKey
44    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    /// 设置 API 基础地址
53    ///
54    /// 默认使用主接口地址: https://api.zhetaoke.com:10001
55    /// 备用接口地址: http://api.zhetaoke.cn:10000
56    ///
57    /// # Arguments
58    ///
59    /// * `url` - API 基础地址
60    pub fn base_url(mut self, url: impl Into<String>) -> Self {
61        self.base_url = Some(url.into());
62        self
63    }
64
65    /// 设置请求超时时间
66    ///
67    /// 默认超时时间为 30 秒
68    ///
69    /// # Arguments
70    ///
71    /// * `timeout` - 超时时间
72    pub fn timeout(mut self, timeout: Duration) -> Self {
73        self.timeout = Some(timeout);
74        self
75    }
76
77    /// 构建客户端实例
78    ///
79    /// # Returns
80    ///
81    /// 返回配置好的 ZtkClient 实例或错误
82    pub fn build(self) -> ZtkResult<ZtkClient> {
83        // 验证 appkey 不为空
84        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/// 内部客户端数据
108#[derive(Debug)]
109struct ZtkClientInner {
110    /// HTTP 客户端
111    http_client: HttpClient,
112    /// API 基础地址
113    base_url: String,
114    /// 折淘客 AppKey
115    appkey: String,
116}
117
118/// 折淘客 SDK 客户端
119///
120/// 提供各平台 API 的访问入口
121///
122/// # Example
123///
124/// ```rust,ignore
125/// use ztk_rust_sdk::ZtkClient;
126///
127/// #[tokio::main]
128/// async fn main() -> ztk_rust_sdk::ZtkResult<()> {
129///     let client = ZtkClient::new("your_appkey").build()?;
130///     
131///     // 调用淘宝 API
132///     // let result = client.taobao().convert_by_item_id(request).await?;
133///     
134///     // 调用京东 API
135///     // let result = client.jd().convert(request).await?;
136///     
137///     Ok(())
138/// }
139/// ```
140#[derive(Debug, Clone)]
141pub struct ZtkClient {
142    /// 内部数据 (使用 Arc 实现 Clone)
143    inner: Arc<ZtkClientInner>,
144}
145
146impl ZtkClient {
147    /// 创建新的客户端构建器
148    ///
149    /// # Arguments
150    ///
151    /// * `appkey` - 折淘客 AppKey
152    ///
153    /// # Example
154    ///
155    /// ```rust,ignore
156    /// let client = ZtkClient::new("your_appkey")
157    ///     .base_url("https://api.zhetaoke.com:10001")
158    ///     .build()?;
159    /// ```
160    pub fn new(appkey: impl Into<String>) -> ZtkClientBuilder {
161        ZtkClientBuilder::new(appkey)
162    }
163
164    /// 获取 AppKey
165    pub fn appkey(&self) -> &str {
166        &self.inner.appkey
167    }
168
169    /// 获取 API 基础地址
170    pub fn base_url(&self) -> &str {
171        &self.inner.base_url
172    }
173
174    /// 获取 HTTP 客户端引用 (内部使用)
175    pub(crate) fn http_client(&self) -> &HttpClient {
176        &self.inner.http_client
177    }
178
179    /// 获取淘宝平台 API
180    ///
181    /// # Example
182    ///
183    /// ```rust,ignore
184    /// let result = client.taobao().convert_by_item_id(request).await?;
185    /// ```
186    #[cfg(feature = "taobao")]
187    pub fn taobao(&self) -> crate::taobao::TaobaoApi<'_> {
188        crate::taobao::TaobaoApi::new(self)
189    }
190
191    /// 获取京东平台 API
192    ///
193    /// # Example
194    ///
195    /// ```rust,ignore
196    /// let result = client.jd().convert(request).await?;
197    /// ```
198    #[cfg(feature = "jd")]
199    pub fn jd(&self) -> crate::jd::JdApi<'_> {
200        crate::jd::JdApi::new(self)
201    }
202
203    /// 获取拼多多平台 API
204    ///
205    /// # Example
206    ///
207    /// ```rust,ignore
208    /// let result = client.pdd().convert(request).await?;
209    /// ```
210    #[cfg(feature = "pdd")]
211    pub fn pdd(&self) -> crate::pdd::PddApi<'_> {
212        crate::pdd::PddApi::new(self)
213    }
214
215    /// 获取唯品会平台 API
216    ///
217    /// # Example
218    ///
219    /// ```rust,ignore
220    /// let result = client.vip().convert(request).await?;
221    /// ```
222    #[cfg(feature = "vip")]
223    pub fn vip(&self) -> crate::vip::VipApi<'_> {
224        crate::vip::VipApi::new(self)
225    }
226
227    /// 获取美团平台 API
228    ///
229    /// # Example
230    ///
231    /// ```rust,ignore
232    /// let result = client.meituan().convert(request).await?;
233    /// ```
234    #[cfg(feature = "meituan")]
235    pub fn meituan(&self) -> crate::meituan::MeituanApi<'_> {
236        crate::meituan::MeituanApi::new(self)
237    }
238
239    /// 获取考拉平台 API
240    ///
241    /// # Example
242    ///
243    /// ```rust,ignore
244    /// let result = client.kaola().convert(request).await?;
245    /// ```
246    #[cfg(feature = "kaola")]
247    pub fn kaola(&self) -> crate::kaola::KaolaApi<'_> {
248        crate::kaola::KaolaApi::new(self)
249    }
250
251    /// 获取饿了么平台 API
252    ///
253    /// # Example
254    ///
255    /// ```rust,ignore
256    /// let result = client.eleme().convert(request).await?;
257    /// ```
258    #[cfg(feature = "eleme")]
259    pub fn eleme(&self) -> crate::eleme::ElemeApi<'_> {
260        crate::eleme::ElemeApi::new(self)
261    }
262
263    /// 获取抖音平台 API
264    ///
265    /// # Example
266    ///
267    /// ```rust,ignore
268    /// let result = client.douyin().convert_goods(request).await?;
269    /// ```
270    #[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        // 验证返回的是 ZtkClientBuilder
357        let _client = builder.build().unwrap();
358    }
359}