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