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 inner: Arc<ConfigInner>,
15}
16
17#[derive(Debug)]
19pub struct ConfigInner {
20 pub app_id: String,
21 pub app_secret: String,
22 pub base_url: String,
24 pub enable_token_cache: bool,
25 pub app_type: AppType,
27 pub http_client: reqwest::Client,
28 pub req_timeout: Option<Duration>,
30 pub header: HashMap<String, String>,
31 pub token_manager: Arc<Mutex<TokenManager>>,
33 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 pub fn new(inner: ConfigInner) -> Self {
77 Self {
78 inner: Arc::new(inner),
79 }
80 }
81
82 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 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 pub fn production_http_client(self) -> Result<Self, reqwest::Error> {
143 let config = OptimizedHttpConfig::production();
144 self.optimized_http_client(config)
145 }
146
147 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 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 assert!(Arc::ptr_eq(&config.inner, &cloned_config.inner));
264
265 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 assert!(Arc::ptr_eq(&config.inner, &config_clone.inner));
366 }
367
368 #[test]
369 fn test_arc_efficiency_simulation() {
370 let config = Config::default();
372
373 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 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 assert_eq!(config.reference_count(), 5);
387
388 println!("Arc<Config> 改造成功:5个配置实例共享同一份内存!");
389 }
390}