open_lark/core/
config.rs

1use std::{collections::HashMap, ops::Deref, sync::Arc, time::Duration};
2use tokio::sync::Mutex;
3
4use crate::core::{
5    app_ticket_manager::AppTicketManager,
6    constants::{AppType, FEISHU_BASE_URL},
7    performance::OptimizedHttpConfig,
8    token_manager::TokenManager,
9};
10
11#[derive(Debug, Clone)]
12pub struct Config {
13    /// 包装在 Arc 中的共享配置数据
14    inner: Arc<ConfigInner>,
15}
16
17/// 内部配置数据,被多个服务共享
18#[derive(Debug)]
19pub struct ConfigInner {
20    pub app_id: String,
21    pub app_secret: String,
22    /// 域名, 默认为 <https://open.feishu.cn>
23    pub base_url: String,
24    pub enable_token_cache: bool,
25    /// 应用类型, 默认为自建应用
26    pub app_type: AppType,
27    pub http_client: reqwest::Client,
28    /// 客户端超时时间, 默认永不超时
29    pub req_timeout: Option<Duration>,
30    pub header: HashMap<String, String>,
31    /// Token 管理器
32    pub token_manager: Arc<Mutex<TokenManager>>,
33    /// App Ticket 管理器
34    pub app_ticket_manager: Arc<Mutex<AppTicketManager>>,
35}
36
37impl Default for ConfigInner {
38    fn default() -> Self {
39        Self {
40            app_id: "".to_string(),
41            app_secret: "".to_string(),
42            base_url: FEISHU_BASE_URL.to_string(),
43            enable_token_cache: true,
44            app_type: AppType::SelfBuild,
45            http_client: reqwest::Client::new(),
46            req_timeout: None,
47            header: Default::default(),
48            token_manager: Arc::new(Mutex::new(TokenManager::new())),
49            app_ticket_manager: Arc::new(Mutex::new(AppTicketManager::new())),
50        }
51    }
52}
53
54impl Default for Config {
55    fn default() -> Self {
56        Self {
57            inner: Arc::new(ConfigInner::default()),
58        }
59    }
60}
61
62impl Deref for Config {
63    type Target = ConfigInner;
64
65    fn deref(&self) -> &Self::Target {
66        &self.inner
67    }
68}
69
70impl Config {
71    pub fn builder() -> ConfigBuilder {
72        ConfigBuilder::default()
73    }
74
75    /// 创建新的 Config 实例,直接从 ConfigInner
76    pub fn new(inner: ConfigInner) -> Self {
77        Self {
78            inner: Arc::new(inner),
79        }
80    }
81
82    /// 获取内部 Arc 的引用计数
83    pub fn reference_count(&self) -> usize {
84        Arc::strong_count(&self.inner)
85    }
86}
87
88#[derive(Default, Clone)]
89pub struct ConfigBuilder {
90    app_id: Option<String>,
91    app_secret: Option<String>,
92    base_url: Option<String>,
93    enable_token_cache: Option<bool>,
94    app_type: Option<AppType>,
95    http_client: Option<reqwest::Client>,
96    req_timeout: Option<Duration>,
97    header: Option<HashMap<String, String>>,
98}
99
100impl ConfigBuilder {
101    pub fn app_id(mut self, app_id: impl Into<String>) -> Self {
102        self.app_id = Some(app_id.into());
103        self
104    }
105
106    pub fn app_secret(mut self, app_secret: impl Into<String>) -> Self {
107        self.app_secret = Some(app_secret.into());
108        self
109    }
110
111    pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
112        self.base_url = Some(base_url.into());
113        self
114    }
115
116    pub fn enable_token_cache(mut self, enable: bool) -> Self {
117        self.enable_token_cache = Some(enable);
118        self
119    }
120
121    pub fn app_type(mut self, app_type: AppType) -> Self {
122        self.app_type = Some(app_type);
123        self
124    }
125
126    pub fn http_client(mut self, client: reqwest::Client) -> Self {
127        self.http_client = Some(client);
128        self
129    }
130
131    /// 使用优化的HTTP配置构建客户端
132    pub fn optimized_http_client(
133        mut self,
134        config: OptimizedHttpConfig,
135    ) -> Result<Self, reqwest::Error> {
136        let client = config.build_client()?;
137        self.http_client = Some(client);
138        Ok(self)
139    }
140
141    /// 使用生产环境优化配置
142    pub fn production_http_client(self) -> Result<Self, reqwest::Error> {
143        let config = OptimizedHttpConfig::production();
144        self.optimized_http_client(config)
145    }
146
147    /// 使用高吞吐量配置
148    pub fn high_throughput_http_client(self) -> Result<Self, reqwest::Error> {
149        let config = OptimizedHttpConfig::high_throughput();
150        self.optimized_http_client(config)
151    }
152
153    /// 使用低延迟配置
154    pub fn low_latency_http_client(self) -> Result<Self, reqwest::Error> {
155        let config = OptimizedHttpConfig::low_latency();
156        self.optimized_http_client(config)
157    }
158
159    pub fn req_timeout(mut self, timeout: Duration) -> Self {
160        self.req_timeout = Some(timeout);
161        self
162    }
163
164    pub fn header(mut self, header: HashMap<String, String>) -> Self {
165        self.header = Some(header);
166        self
167    }
168
169    pub fn build(self) -> Config {
170        let default = ConfigInner::default();
171        Config::new(ConfigInner {
172            app_id: self.app_id.unwrap_or(default.app_id),
173            app_secret: self.app_secret.unwrap_or(default.app_secret),
174            base_url: self.base_url.unwrap_or(default.base_url),
175            enable_token_cache: self
176                .enable_token_cache
177                .unwrap_or(default.enable_token_cache),
178            app_type: self.app_type.unwrap_or(default.app_type),
179            http_client: self.http_client.unwrap_or(default.http_client),
180            req_timeout: self.req_timeout.or(default.req_timeout),
181            header: self.header.unwrap_or(default.header),
182            token_manager: default.token_manager,
183            app_ticket_manager: default.app_ticket_manager,
184        })
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::core::constants::{AppType, FEISHU_BASE_URL};
192    use std::time::Duration;
193
194    #[test]
195    fn test_config_creation() {
196        let config = Config::new(ConfigInner {
197            app_id: "test_app_id".to_string(),
198            app_secret: "test_app_secret".to_string(),
199            base_url: "https://test.api.com".to_string(),
200            enable_token_cache: true,
201            app_type: AppType::SelfBuild,
202            http_client: reqwest::Client::new(),
203            req_timeout: Some(Duration::from_secs(30)),
204            header: HashMap::new(),
205            token_manager: Arc::new(Mutex::new(TokenManager::new())),
206            app_ticket_manager: Arc::new(Mutex::new(AppTicketManager::new())),
207        });
208
209        assert_eq!(config.app_id, "test_app_id");
210        assert_eq!(config.app_secret, "test_app_secret");
211        assert_eq!(config.base_url, "https://test.api.com");
212        assert!(config.enable_token_cache);
213        assert_eq!(config.req_timeout, Some(Duration::from_secs(30)));
214    }
215
216    #[test]
217    fn test_config_default() {
218        let config = Config::default();
219
220        assert_eq!(config.app_id, "");
221        assert_eq!(config.app_secret, "");
222        assert_eq!(config.base_url, FEISHU_BASE_URL);
223        assert!(config.enable_token_cache);
224        assert_eq!(config.app_type, AppType::SelfBuild);
225        assert!(config.req_timeout.is_none());
226        assert!(config.header.is_empty());
227    }
228
229    #[test]
230    fn test_config_clone() {
231        let config = Config::new(ConfigInner {
232            app_id: "clone_test".to_string(),
233            app_secret: "clone_secret".to_string(),
234            base_url: "https://clone.test.com".to_string(),
235            enable_token_cache: false,
236            app_type: AppType::Marketplace,
237            http_client: reqwest::Client::new(),
238            req_timeout: Some(Duration::from_secs(60)),
239            header: {
240                let mut header = HashMap::new();
241                header.insert("Test-Header".to_string(), "test-value".to_string());
242                header
243            },
244            token_manager: Arc::new(Mutex::new(TokenManager::new())),
245            app_ticket_manager: Arc::new(Mutex::new(AppTicketManager::new())),
246        });
247
248        let cloned_config = config.clone();
249
250        assert_eq!(config.app_id, cloned_config.app_id);
251        assert_eq!(config.app_secret, cloned_config.app_secret);
252        assert_eq!(config.base_url, cloned_config.base_url);
253        assert_eq!(config.enable_token_cache, cloned_config.enable_token_cache);
254        assert_eq!(config.app_type, cloned_config.app_type);
255        assert_eq!(config.req_timeout, cloned_config.req_timeout);
256        assert_eq!(config.header.len(), cloned_config.header.len());
257        assert_eq!(
258            config.header.get("Test-Header"),
259            cloned_config.header.get("Test-Header")
260        );
261
262        // Verify Arc clone efficiency - both should point to same memory
263        assert!(Arc::ptr_eq(&config.inner, &cloned_config.inner));
264
265        // Verify reference counting works
266        assert_eq!(config.reference_count(), 2);
267    }
268
269    #[test]
270    fn test_config_debug() {
271        let config = Config::default();
272        let debug_str = format!("{:?}", config);
273
274        assert!(debug_str.contains("Config"));
275        assert!(debug_str.contains("app_id"));
276        assert!(debug_str.contains("app_secret"));
277        assert!(debug_str.contains("base_url"));
278    }
279
280    #[test]
281    fn test_config_with_custom_header() {
282        let mut header = HashMap::new();
283        header.insert("Authorization".to_string(), "Bearer token".to_string());
284        header.insert("Content-Type".to_string(), "application/json".to_string());
285
286        let config = Config::new(ConfigInner {
287            header,
288            ..ConfigInner::default()
289        });
290
291        assert_eq!(config.header.len(), 2);
292        assert_eq!(
293            config.header.get("Authorization"),
294            Some(&"Bearer token".to_string())
295        );
296        assert_eq!(
297            config.header.get("Content-Type"),
298            Some(&"application/json".to_string())
299        );
300    }
301
302    #[test]
303    fn test_config_with_different_app_types() {
304        let self_build_config = Config::new(ConfigInner {
305            app_type: AppType::SelfBuild,
306            ..ConfigInner::default()
307        });
308
309        let marketplace_config = Config::new(ConfigInner {
310            app_type: AppType::Marketplace,
311            ..ConfigInner::default()
312        });
313
314        assert_eq!(self_build_config.app_type, AppType::SelfBuild);
315        assert_eq!(marketplace_config.app_type, AppType::Marketplace);
316        assert_ne!(self_build_config.app_type, marketplace_config.app_type);
317    }
318
319    #[test]
320    fn test_config_with_timeout_variations() {
321        let no_timeout_config = Config::default();
322
323        let short_timeout_config = Config::new(ConfigInner {
324            req_timeout: Some(Duration::from_secs(5)),
325            ..ConfigInner::default()
326        });
327
328        let long_timeout_config = Config::new(ConfigInner {
329            req_timeout: Some(Duration::from_secs(300)),
330            ..ConfigInner::default()
331        });
332
333        assert!(no_timeout_config.req_timeout.is_none());
334        assert_eq!(
335            short_timeout_config.req_timeout,
336            Some(Duration::from_secs(5))
337        );
338        assert_eq!(
339            long_timeout_config.req_timeout,
340            Some(Duration::from_secs(300))
341        );
342    }
343
344    #[test]
345    fn test_config_builders() {
346        let config = Config::builder()
347            .app_id("test_app")
348            .app_secret("test_secret")
349            .build();
350
351        assert_eq!(config.app_id, "test_app");
352        assert_eq!(config.app_secret, "test_secret");
353    }
354
355    #[test]
356    fn test_config_arc_efficiency() {
357        let config = Config::default();
358        assert_eq!(config.reference_count(), 1);
359
360        let config_clone = config.clone();
361        assert_eq!(config.reference_count(), 2);
362        assert_eq!(config_clone.reference_count(), 2);
363
364        // Both configs should point to the same inner data
365        assert!(Arc::ptr_eq(&config.inner, &config_clone.inner));
366    }
367
368    #[test]
369    fn test_arc_efficiency_simulation() {
370        // 模拟服务模块中的多次克隆
371        let config = Config::default();
372
373        // 模拟 PerformanceService::new() 中的4次clone
374        let service1_config = config.clone();
375        let service2_config = config.clone();
376        let service3_config = config.clone();
377        let service4_config = config.clone();
378
379        // 所有配置应该指向同一个内存位置
380        assert!(Arc::ptr_eq(&config.inner, &service1_config.inner));
381        assert!(Arc::ptr_eq(&config.inner, &service2_config.inner));
382        assert!(Arc::ptr_eq(&config.inner, &service3_config.inner));
383        assert!(Arc::ptr_eq(&config.inner, &service4_config.inner));
384
385        // 引用计数应该是5(原始 + 4个克隆)
386        assert_eq!(config.reference_count(), 5);
387
388        println!("Arc<Config> 改造成功:5个配置实例共享同一份内存!");
389    }
390}