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