1use std::{collections::HashMap, ops::Deref, sync::Arc, time::Duration};
2
3use crate::{
4 auth::token_provider::{NoOpTokenProvider, TokenProvider},
5 constants::{AppType, FEISHU_BASE_URL},
6 performance::OptimizedHttpConfig,
7};
8
9#[derive(Debug, Clone)]
39pub struct Config {
40 inner: Arc<ConfigInner>,
45}
46
47pub struct ConfigInner {
49 pub(crate) app_id: String,
50 pub(crate) app_secret: String,
51 pub(crate) base_url: String,
53 pub(crate) enable_token_cache: bool,
55 pub(crate) app_type: AppType,
57 pub(crate) http_client: reqwest::Client,
58 pub(crate) req_timeout: Option<Duration>,
60 pub(crate) header: HashMap<String, String>,
61 pub(crate) token_provider: Arc<dyn TokenProvider>,
63 pub(crate) max_response_size: u64,
65}
66
67impl Default for ConfigInner {
68 fn default() -> Self {
69 Self {
70 app_id: "".to_string(),
71 app_secret: "".to_string(),
72 base_url: FEISHU_BASE_URL.to_string(),
73 enable_token_cache: true,
74 app_type: AppType::SelfBuild,
75 http_client: reqwest::Client::new(),
76 req_timeout: None,
77 header: Default::default(),
78 token_provider: Arc::new(NoOpTokenProvider),
79 max_response_size: 100 * 1024 * 1024, }
81 }
82}
83
84impl std::fmt::Debug for ConfigInner {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 f.debug_struct("ConfigInner")
87 .field("app_id", &self.app_id)
88 .field("app_secret", &"***")
89 .field("base_url", &self.base_url)
90 .field("enable_token_cache", &self.enable_token_cache)
91 .field("app_type", &self.app_type)
92 .field("req_timeout", &self.req_timeout)
93 .field("max_response_size", &self.max_response_size)
94 .field("header", &format!("{} headers", self.header.len()))
95 .finish()
96 }
97}
98
99impl Default for Config {
100 fn default() -> Self {
101 Self {
102 inner: Arc::new(ConfigInner::default()),
103 }
104 }
105}
106
107impl Deref for Config {
108 type Target = ConfigInner;
109
110 fn deref(&self) -> &Self::Target {
111 &self.inner
112 }
113}
114
115impl Config {
116 pub fn builder() -> ConfigBuilder {
118 ConfigBuilder::default()
119 }
120
121 pub fn new(inner: ConfigInner) -> Self {
123 Self {
124 inner: Arc::new(inner),
125 }
126 }
127
128 pub fn with_token_provider(&self, provider: impl TokenProvider + 'static) -> Self {
135 Config::new(ConfigInner {
136 app_id: self.app_id.clone(),
137 app_secret: self.app_secret.clone(),
138 base_url: self.base_url.clone(),
139 enable_token_cache: self.enable_token_cache,
140 app_type: self.app_type,
141 http_client: self.http_client.clone(),
142 req_timeout: self.req_timeout,
143 header: self.header.clone(),
144 token_provider: Arc::new(provider),
145 max_response_size: self.max_response_size,
146 })
147 }
148
149 pub fn reference_count(&self) -> usize {
151 Arc::strong_count(&self.inner)
152 }
153
154 pub fn app_id(&self) -> &str {
156 &self.inner.app_id
157 }
158
159 pub fn app_secret(&self) -> &str {
161 &self.inner.app_secret
162 }
163
164 pub fn base_url(&self) -> &str {
166 &self.inner.base_url
167 }
168
169 pub fn req_timeout(&self) -> Option<Duration> {
171 self.inner.req_timeout
172 }
173
174 pub fn enable_token_cache(&self) -> bool {
176 self.inner.enable_token_cache
177 }
178
179 pub fn app_type(&self) -> AppType {
181 self.inner.app_type
182 }
183
184 pub fn http_client(&self) -> &reqwest::Client {
186 &self.inner.http_client
187 }
188
189 pub fn header(&self) -> &HashMap<String, String> {
191 &self.inner.header
192 }
193
194 pub fn token_provider(&self) -> &Arc<dyn TokenProvider> {
196 &self.inner.token_provider
197 }
198
199 pub fn max_response_size(&self) -> u64 {
201 self.inner.max_response_size
202 }
203}
204
205#[derive(Default, Clone)]
207pub struct ConfigBuilder {
208 app_id: Option<String>,
209 app_secret: Option<String>,
210 base_url: Option<String>,
211 enable_token_cache: Option<bool>,
212 app_type: Option<AppType>,
213 http_client: Option<reqwest::Client>,
214 req_timeout: Option<Duration>,
215 header: Option<HashMap<String, String>>,
216 token_provider: Option<Arc<dyn TokenProvider>>,
217 max_response_size: Option<u64>,
218}
219
220impl ConfigBuilder {
221 pub fn app_id(mut self, app_id: impl Into<String>) -> Self {
223 self.app_id = Some(app_id.into());
224 self
225 }
226
227 pub fn app_secret(mut self, app_secret: impl Into<String>) -> Self {
229 self.app_secret = Some(app_secret.into());
230 self
231 }
232
233 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
235 self.base_url = Some(base_url.into());
236 self
237 }
238
239 pub fn enable_token_cache(mut self, enable: bool) -> Self {
241 self.enable_token_cache = Some(enable);
242 self
243 }
244
245 pub fn app_type(mut self, app_type: AppType) -> Self {
247 self.app_type = Some(app_type);
248 self
249 }
250
251 pub fn http_client(mut self, client: reqwest::Client) -> Self {
253 self.http_client = Some(client);
254 self
255 }
256
257 pub fn optimized_http_client(
259 mut self,
260 config: OptimizedHttpConfig,
261 ) -> Result<Self, reqwest::Error> {
262 let client = config.build_client()?;
263 self.http_client = Some(client);
264 Ok(self)
265 }
266
267 pub fn production_http_client(self) -> Result<Self, reqwest::Error> {
269 let config = OptimizedHttpConfig::production();
270 self.optimized_http_client(config)
271 }
272
273 pub fn high_throughput_http_client(self) -> Result<Self, reqwest::Error> {
275 let config = OptimizedHttpConfig::high_throughput();
276 self.optimized_http_client(config)
277 }
278
279 pub fn low_latency_http_client(self) -> Result<Self, reqwest::Error> {
281 let config = OptimizedHttpConfig::low_latency();
282 self.optimized_http_client(config)
283 }
284
285 pub fn req_timeout(mut self, timeout: Duration) -> Self {
287 self.req_timeout = Some(timeout);
288 self
289 }
290
291 pub fn header(mut self, header: HashMap<String, String>) -> Self {
293 self.header = Some(header);
294 self
295 }
296
297 pub fn token_provider(mut self, provider: impl TokenProvider + 'static) -> Self {
299 self.token_provider = Some(Arc::new(provider));
300 self
301 }
302
303 pub fn max_response_size(mut self, size: u64) -> Self {
305 self.max_response_size = Some(size);
306 self
307 }
308
309 pub fn build(self) -> Config {
311 let default = ConfigInner::default();
312 Config::new(ConfigInner {
313 app_id: self.app_id.unwrap_or(default.app_id),
314 app_secret: self.app_secret.unwrap_or(default.app_secret),
315 base_url: self.base_url.unwrap_or(default.base_url),
316 enable_token_cache: self
317 .enable_token_cache
318 .unwrap_or(default.enable_token_cache),
319 app_type: self.app_type.unwrap_or(default.app_type),
320 http_client: self.http_client.unwrap_or(default.http_client),
321 req_timeout: self.req_timeout.or(default.req_timeout),
322 header: self.header.unwrap_or(default.header),
323 token_provider: self.token_provider.unwrap_or(default.token_provider),
324 max_response_size: self.max_response_size.unwrap_or(default.max_response_size),
325 })
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use crate::auth::NoOpTokenProvider;
333 use crate::auth::TokenProvider;
334 use crate::auth::TokenRequest;
335 use crate::constants::{AppType, FEISHU_BASE_URL};
336 use std::time::Duration;
337 use std::{future::Future, pin::Pin};
338
339 #[test]
340 fn test_config_creation() {
341 let config = Config::new(ConfigInner {
342 app_id: "test_app_id".to_string(),
343 app_secret: "test_app_secret".to_string(),
344 base_url: "https://test.api.com".to_string(),
345 enable_token_cache: true,
346 app_type: AppType::SelfBuild,
347 http_client: reqwest::Client::new(),
348 req_timeout: Some(Duration::from_secs(30)),
349 header: HashMap::new(),
350 token_provider: Arc::new(NoOpTokenProvider),
351 max_response_size: 100 * 1024 * 1024,
352 });
353
354 assert_eq!(config.app_id, "test_app_id");
355 assert_eq!(config.app_secret, "test_app_secret");
356 assert_eq!(config.base_url, "https://test.api.com");
357 assert!(config.enable_token_cache);
358 assert_eq!(config.req_timeout, Some(Duration::from_secs(30)));
359 }
360
361 #[test]
362 fn test_config_default() {
363 let config = Config::default();
364
365 assert_eq!(config.app_id, "");
366 assert_eq!(config.app_secret, "");
367 assert_eq!(config.base_url, FEISHU_BASE_URL);
368 assert!(config.enable_token_cache);
369 assert_eq!(config.app_type, AppType::SelfBuild);
370 assert!(config.req_timeout.is_none());
371 assert!(config.header.is_empty());
372 }
373
374 #[test]
375 fn test_config_clone() {
376 let config = Config::new(ConfigInner {
377 app_id: "clone_test".to_string(),
378 app_secret: "clone_secret".to_string(),
379 base_url: "https://clone.test.com".to_string(),
380 enable_token_cache: false,
381 app_type: AppType::Marketplace,
382 http_client: reqwest::Client::new(),
383 req_timeout: Some(Duration::from_secs(60)),
384 header: {
385 let mut header = HashMap::new();
386 header.insert("Test-Header".to_string(), "test-value".to_string());
387 header
388 },
389 token_provider: Arc::new(NoOpTokenProvider),
390 max_response_size: 100 * 1024 * 1024,
391 });
392
393 let cloned_config = config.clone();
394
395 assert_eq!(config.app_id, cloned_config.app_id);
396 assert_eq!(config.app_secret, cloned_config.app_secret);
397 assert_eq!(config.base_url, cloned_config.base_url);
398 assert_eq!(config.enable_token_cache, cloned_config.enable_token_cache);
399 assert_eq!(config.app_type, cloned_config.app_type);
400 assert_eq!(config.req_timeout, cloned_config.req_timeout);
401 assert_eq!(config.header.len(), cloned_config.header.len());
402 assert_eq!(
403 config.header.get("Test-Header"),
404 cloned_config.header.get("Test-Header")
405 );
406
407 assert!(Arc::ptr_eq(&config.inner, &cloned_config.inner));
409
410 assert_eq!(config.reference_count(), 2);
412 }
413
414 #[test]
415 fn test_config_debug() {
416 let config = Config::default();
417 let debug_str = format!("{config:?}");
418
419 assert!(debug_str.contains("Config"));
420 assert!(debug_str.contains("app_id"));
421 assert!(debug_str.contains("app_secret"));
422 assert!(debug_str.contains("base_url"));
423 }
424
425 #[test]
426 fn test_config_with_custom_header() {
427 let mut header = HashMap::new();
428 header.insert("Authorization".to_string(), "Bearer token".to_string());
429 header.insert("Content-Type".to_string(), "application/json".to_string());
430
431 let config = Config::new(ConfigInner {
432 header,
433 ..ConfigInner::default()
434 });
435
436 assert_eq!(config.header.len(), 2);
437 assert_eq!(
438 config.header.get("Authorization"),
439 Some(&"Bearer token".to_string())
440 );
441 assert_eq!(
442 config.header.get("Content-Type"),
443 Some(&"application/json".to_string())
444 );
445 }
446
447 #[test]
448 fn test_config_with_different_app_types() {
449 let self_build_config = Config::new(ConfigInner {
450 app_type: AppType::SelfBuild,
451 ..ConfigInner::default()
452 });
453
454 let marketplace_config = Config::new(ConfigInner {
455 app_type: AppType::Marketplace,
456 ..ConfigInner::default()
457 });
458
459 assert_eq!(self_build_config.app_type, AppType::SelfBuild);
460 assert_eq!(marketplace_config.app_type, AppType::Marketplace);
461 assert_ne!(self_build_config.app_type, marketplace_config.app_type);
462 }
463
464 #[test]
465 fn test_config_with_timeout_variations() {
466 let no_timeout_config = Config::default();
467
468 let short_timeout_config = Config::new(ConfigInner {
469 req_timeout: Some(Duration::from_secs(5)),
470 ..ConfigInner::default()
471 });
472
473 let long_timeout_config = Config::new(ConfigInner {
474 req_timeout: Some(Duration::from_secs(300)),
475 ..ConfigInner::default()
476 });
477
478 assert!(no_timeout_config.req_timeout.is_none());
479 assert_eq!(
480 short_timeout_config.req_timeout,
481 Some(Duration::from_secs(5))
482 );
483 assert_eq!(
484 long_timeout_config.req_timeout,
485 Some(Duration::from_secs(300))
486 );
487 }
488
489 #[test]
490 fn test_config_builders() {
491 let config = Config::builder()
492 .app_id("test_app")
493 .app_secret("test_secret")
494 .build();
495
496 assert_eq!(config.app_id, "test_app");
497 assert_eq!(config.app_secret, "test_secret");
498 }
499
500 #[test]
501 fn test_config_arc_efficiency() {
502 let config = Config::default();
503 assert_eq!(config.reference_count(), 1);
504
505 let config_clone = config.clone();
506 assert_eq!(config.reference_count(), 2);
507 assert_eq!(config_clone.reference_count(), 2);
508
509 assert!(Arc::ptr_eq(&config.inner, &config_clone.inner));
511 }
512
513 #[test]
514 fn test_arc_efficiency_simulation() {
515 let config = Config::default();
517
518 let service1_config = config.clone();
520 let service2_config = config.clone();
521 let service3_config = config.clone();
522 let service4_config = config.clone();
523
524 assert!(Arc::ptr_eq(&config.inner, &service1_config.inner));
526 assert!(Arc::ptr_eq(&config.inner, &service2_config.inner));
527 assert!(Arc::ptr_eq(&config.inner, &service3_config.inner));
528 assert!(Arc::ptr_eq(&config.inner, &service4_config.inner));
529
530 assert_eq!(config.reference_count(), 5);
532
533 println!("Arc<Config> 改造成功:5个配置实例共享同一份内存!");
534 }
535
536 #[derive(Debug)]
537 struct TestTokenProvider;
538
539 impl TokenProvider for TestTokenProvider {
540 fn get_token(
541 &self,
542 _request: TokenRequest,
543 ) -> Pin<Box<dyn Future<Output = crate::SDKResult<String>> + Send + '_>> {
544 Box::pin(async { Ok("test_token".to_string()) })
545 }
546 }
547
548 #[tokio::test]
549 async fn test_with_token_provider() {
550 let base = Config::builder()
551 .app_id("test_app")
552 .app_secret("test_secret")
553 .build();
554
555 let config = base.with_token_provider(TestTokenProvider);
556
557 let token = config
558 .token_provider
559 .get_token(TokenRequest::app())
560 .await
561 .unwrap();
562 assert_eq!(token, "test_token");
563 }
564}