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