1use std::sync::Arc;
2use std::time::Duration;
3
4use crate::core::{
5 config::{Config, ConfigBuilder},
6 constants::AppType,
7};
8
9#[cfg(feature = "acs")]
11use crate::service::acs::AcsService;
12#[cfg(feature = "admin")]
13use crate::service::admin::AdminService;
14#[cfg(feature = "ai")]
15use crate::service::ai::AiService;
16#[cfg(feature = "aily")]
17use crate::service::aily::AilyService;
18#[cfg(feature = "apass")]
19use crate::service::apass::ApassService;
20#[cfg(feature = "application")]
21use crate::service::application::ApplicationService;
22#[cfg(feature = "approval")]
23use crate::service::approval::ApprovalService;
24#[cfg(feature = "attendance")]
25use crate::service::attendance::AttendanceService;
26#[cfg(feature = "authentication")]
27use crate::service::authentication::AuthenService;
28#[cfg(feature = "bot")]
29use crate::service::bot::BotService;
30#[cfg(feature = "calendar")]
31use crate::service::calendar::CalendarService;
32#[cfg(feature = "cardkit")]
33use crate::service::cardkit::CardkitService;
34#[cfg(feature = "cloud-docs")]
35use crate::service::cloud_docs::CloudDocsService;
36#[cfg(feature = "contact")]
37use crate::service::contact::ContactService;
38#[cfg(feature = "corehr")]
39use crate::service::corehr::CoreHRService;
40#[cfg(feature = "directory")]
41use crate::service::directory::DirectoryService;
42#[cfg(feature = "ehr")]
43use crate::service::ehr::EhrService;
44#[cfg(feature = "elearning")]
45use crate::service::elearning::ELearningService;
46#[cfg(feature = "group")]
47use crate::service::group::GroupService;
48#[cfg(feature = "helpdesk")]
49use crate::service::helpdesk::HelpdeskService;
50#[cfg(feature = "hire")]
51use crate::service::hire::HireService;
52#[cfg(feature = "human-authentication")]
53use crate::service::human_authentication::HumanAuthenticationService;
54#[cfg(feature = "im")]
55use crate::service::im::ImService;
56#[cfg(feature = "lingo")]
57use crate::service::lingo::LingoService;
58#[cfg(feature = "mail")]
59use crate::service::mail::MailService;
60#[cfg(feature = "mdm")]
61use crate::service::mdm::MdmService;
62#[cfg(feature = "minutes")]
63use crate::service::minutes::MinutesService;
64#[cfg(feature = "moments")]
65use crate::service::moments::MomentsService;
66#[cfg(feature = "okr")]
67use crate::service::okr::OkrService;
68#[cfg(feature = "payroll")]
69use crate::service::payroll::PayrollService;
70#[cfg(feature = "performance")]
71use crate::service::performance::PerformanceService;
72#[cfg(feature = "personal-settings")]
73use crate::service::personal_settings::PersonalSettingsService;
74#[cfg(feature = "report")]
75use crate::service::report::ReportService;
76#[cfg(feature = "search")]
77use crate::service::search::SearchService;
78#[cfg(feature = "security-and-compliance")]
79use crate::service::security_and_compliance::SecurityAndComplianceService;
80#[cfg(feature = "task")]
81use crate::service::task::TaskV2Service;
82#[cfg(feature = "tenant")]
83use crate::service::tenant::TenantService;
84#[cfg(feature = "tenant-tag")]
85use crate::service::tenant_tag::TenantTagService;
86#[cfg(feature = "trust-party")]
87use crate::service::trust_party::TrustPartyService;
88#[cfg(feature = "vc")]
89use crate::service::vc::VcService;
90#[cfg(feature = "verification")]
91use crate::service::verification::VerificationService;
92#[cfg(feature = "workplace")]
93use crate::service::workplace::WorkplaceService;
94
95#[cfg(feature = "cloud-docs")]
97use crate::service::{
98 AssistantService, BitableService, BoardService, CommentsService, DocsService, DriveService,
99 PermissionService, SheetsService, WikiService,
100};
101
102#[cfg(feature = "websocket")]
103pub mod ws_client;
104
105pub struct LarkClient {
155 pub config: Config,
156 #[allow(dead_code)] shared_config: Arc<Config>,
159 #[cfg(feature = "acs")]
161 pub acs: AcsService,
162 #[cfg(feature = "admin")]
163 pub admin: AdminService,
164 #[cfg(feature = "ai")]
165 pub ai: AiService,
166 #[cfg(feature = "aily")]
167 pub aily: AilyService,
168 #[cfg(feature = "apass")]
169 pub apass: ApassService,
170 #[cfg(feature = "application")]
171 pub application: ApplicationService,
172 #[cfg(feature = "approval")]
173 pub approval: ApprovalService,
174 #[cfg(feature = "attendance")]
175 pub attendance: AttendanceService,
176 #[cfg(feature = "authentication")]
177 pub auth: AuthenService,
178 #[cfg(feature = "bot")]
179 pub bot: BotService,
180 #[cfg(feature = "calendar")]
181 pub calendar: CalendarService,
182 #[cfg(feature = "cardkit")]
183 pub cardkit: CardkitService,
184 #[cfg(feature = "contact")]
185 pub contact: ContactService,
186 #[cfg(feature = "corehr")]
187 pub corehr: CoreHRService,
188 #[cfg(feature = "directory")]
189 pub directory: DirectoryService,
190 #[cfg(feature = "ehr")]
191 pub ehr: EhrService,
192 #[cfg(feature = "elearning")]
193 pub elearning: ELearningService,
194 #[cfg(feature = "group")]
195 pub group: GroupService,
196 #[cfg(feature = "helpdesk")]
197 pub helpdesk: HelpdeskService,
198 #[cfg(feature = "hire")]
199 pub hire: HireService,
200 #[cfg(feature = "human-authentication")]
201 pub human_authentication: HumanAuthenticationService,
202 #[cfg(feature = "im")]
203 pub im: ImService,
204 #[cfg(feature = "lingo")]
205 pub lingo: LingoService,
206 #[cfg(feature = "mail")]
207 pub mail: MailService,
208 #[cfg(feature = "mdm")]
209 pub mdm: MdmService,
210 #[cfg(feature = "minutes")]
211 pub minutes: MinutesService,
212 #[cfg(feature = "moments")]
213 pub moments: MomentsService,
214 #[cfg(feature = "okr")]
215 pub okr: OkrService,
216 #[cfg(feature = "payroll")]
217 pub payroll: PayrollService,
218 #[cfg(feature = "performance")]
219 pub performance: PerformanceService,
220 #[cfg(feature = "personal-settings")]
221 pub personal_settings: PersonalSettingsService,
222 #[cfg(feature = "report")]
223 pub report: ReportService,
224 #[cfg(feature = "search")]
225 pub search: SearchService,
226 #[cfg(feature = "security-and-compliance")]
227 pub security_and_compliance: SecurityAndComplianceService,
228 #[cfg(feature = "task")]
229 pub task: TaskV2Service,
230 #[cfg(feature = "tenant")]
231 pub tenant: TenantService,
232 #[cfg(feature = "tenant-tag")]
233 pub tenant_tag: TenantTagService,
234 #[cfg(feature = "trust-party")]
235 pub trust_party: TrustPartyService,
236 #[cfg(feature = "vc")]
237 pub vc: VcService,
238 #[cfg(feature = "verification")]
239 pub verification: VerificationService,
240 #[cfg(feature = "workplace")]
241 pub workplace: WorkplaceService,
242 #[cfg(feature = "cloud-docs")]
244 pub cloud_docs: CloudDocsService,
245 #[cfg(feature = "cloud-docs")]
247 pub assistant: AssistantService,
248 #[cfg(feature = "cloud-docs")]
249 pub docs: DocsService,
250 #[cfg(feature = "cloud-docs")]
251 pub drive: DriveService,
252 #[cfg(feature = "cloud-docs")]
253 pub sheets: SheetsService,
254 #[cfg(feature = "cloud-docs")]
255 pub bitable: BitableService,
256 #[cfg(feature = "cloud-docs")]
257 pub wiki: WikiService,
258 #[cfg(feature = "cloud-docs")]
259 pub comments: CommentsService,
260 #[cfg(feature = "cloud-docs")]
261 pub permission: PermissionService,
262 #[cfg(feature = "cloud-docs")]
263 pub board: BoardService,
264}
265
266pub struct LarkClientBuilder {
282 config_builder: ConfigBuilder,
283}
284
285impl LarkClientBuilder {
286 #[cfg(test)]
288 fn build_config(&self) -> Config {
289 self.config_builder.clone().build()
290 }
291
292 pub fn with_app_type(mut self, app_type: AppType) -> Self {
297 self.config_builder = self.config_builder.app_type(app_type);
298 self
299 }
300
301 pub fn with_marketplace_app(mut self) -> Self {
303 self.config_builder = self.config_builder.app_type(AppType::Marketplace);
304 self
305 }
306
307 pub fn with_open_base_url(mut self, base_url: String) -> Self {
312 self.config_builder = self.config_builder.base_url(base_url);
313 self
314 }
315
316 pub fn with_enable_token_cache(mut self, enable: bool) -> Self {
321 self.config_builder = self.config_builder.enable_token_cache(enable);
322 self
323 }
324
325 pub fn with_req_timeout(mut self, timeout: Option<f32>) -> Self {
330 if let Some(timeout) = timeout {
331 self.config_builder = self
332 .config_builder
333 .req_timeout(Duration::from_secs_f32(timeout));
334 }
335 self
336 }
337
338 pub fn build(self) -> LarkClient {
342 let config = self.config_builder.build();
343 let shared_config = Arc::new(config.clone());
344 LarkClient {
345 config: config.clone(),
346 shared_config: shared_config.clone(),
347 #[cfg(feature = "acs")]
349 acs: AcsService::new(config.clone()),
350 #[cfg(feature = "admin")]
351 admin: AdminService::new(config.clone()),
352 #[cfg(feature = "ai")]
353 ai: AiService::new(config.clone()),
354 #[cfg(feature = "aily")]
355 aily: AilyService::new(config.clone()),
356 #[cfg(feature = "apass")]
357 apass: ApassService::new(config.clone()),
358 #[cfg(feature = "application")]
359 application: ApplicationService::new_from_shared(shared_config.clone()),
360 #[cfg(feature = "approval")]
361 approval: ApprovalService::new(config.clone()),
362 #[cfg(feature = "attendance")]
363 attendance: AttendanceService::new(config.clone()),
364 #[cfg(feature = "authentication")]
365 auth: AuthenService::new(config.clone()),
366 #[cfg(feature = "bot")]
367 bot: BotService::new(config.clone()),
368 #[cfg(feature = "calendar")]
369 calendar: CalendarService::new_from_shared(shared_config.clone()),
370 #[cfg(feature = "cardkit")]
371 cardkit: CardkitService::new(config.clone()),
372 #[cfg(feature = "contact")]
373 contact: ContactService::new_from_shared(shared_config.clone()),
374 #[cfg(feature = "corehr")]
375 corehr: CoreHRService::new(config.clone()),
376 #[cfg(feature = "directory")]
377 directory: DirectoryService::new_from_shared(shared_config.clone()),
378 #[cfg(feature = "ehr")]
379 ehr: EhrService::new_from_shared(shared_config.clone()),
380 #[cfg(feature = "elearning")]
381 elearning: ELearningService::new(config.clone()),
382 #[cfg(feature = "group")]
383 group: GroupService::new(config.clone()),
384 #[cfg(feature = "helpdesk")]
385 helpdesk: HelpdeskService::new(config.clone()),
386 #[cfg(feature = "hire")]
387 hire: HireService::new(config.clone()),
388 #[cfg(feature = "human-authentication")]
389 human_authentication: HumanAuthenticationService::new(config.clone()),
390 #[cfg(feature = "im")]
391 im: ImService::new_from_shared(shared_config.clone()),
392 #[cfg(feature = "lingo")]
393 lingo: LingoService::new(config.clone()),
394 #[cfg(feature = "mail")]
395 mail: MailService::new(config.clone()),
396 #[cfg(feature = "mdm")]
397 mdm: MdmService::new(config.clone()),
398 #[cfg(feature = "minutes")]
399 minutes: MinutesService::new(config.clone()),
400 #[cfg(feature = "moments")]
401 moments: MomentsService::new(config.clone()),
402 #[cfg(feature = "okr")]
403 okr: OkrService::new(config.clone()),
404 #[cfg(feature = "payroll")]
405 payroll: PayrollService::new(config.clone()),
406 #[cfg(feature = "performance")]
407 performance: PerformanceService::new(config.clone()),
408 #[cfg(feature = "personal-settings")]
409 personal_settings: PersonalSettingsService::new(config.clone()),
410 #[cfg(feature = "report")]
411 report: ReportService::new_from_shared(shared_config.clone()),
412 #[cfg(feature = "search")]
413 search: SearchService::new_from_shared(shared_config.clone()),
414 #[cfg(feature = "security-and-compliance")]
415 security_and_compliance: SecurityAndComplianceService::new_from_shared(
416 shared_config.clone(),
417 ),
418 #[cfg(feature = "task")]
419 task: TaskV2Service::new_from_shared(shared_config.clone()),
420 #[cfg(feature = "tenant")]
421 tenant: TenantService::new_from_shared(shared_config.clone()),
422 #[cfg(feature = "tenant-tag")]
423 tenant_tag: TenantTagService::new(config.clone()),
424 #[cfg(feature = "trust-party")]
425 trust_party: TrustPartyService::new(config.clone()),
426 #[cfg(feature = "vc")]
427 vc: VcService::new(config.clone()),
428 #[cfg(feature = "verification")]
429 verification: VerificationService::new(config.clone()),
430 #[cfg(feature = "workplace")]
431 workplace: WorkplaceService::new(config.clone()),
432 #[cfg(feature = "cloud-docs")]
434 cloud_docs: CloudDocsService::new_from_shared(shared_config.clone()),
435 #[cfg(feature = "cloud-docs")]
437 assistant: AssistantService::new_from_shared(shared_config.clone()),
438 #[cfg(feature = "cloud-docs")]
439 docs: DocsService::new(config.clone()),
440 #[cfg(feature = "cloud-docs")]
441 drive: DriveService::new_from_shared(shared_config.clone()),
442 #[cfg(feature = "cloud-docs")]
443 sheets: SheetsService::new_from_shared(shared_config.clone()),
444 #[cfg(feature = "cloud-docs")]
445 bitable: BitableService::new_from_shared(shared_config.clone()),
446 #[cfg(feature = "cloud-docs")]
447 wiki: WikiService::new_from_shared(shared_config.clone()),
448 #[cfg(feature = "cloud-docs")]
449 comments: CommentsService::new_from_shared(shared_config.clone()),
450 #[cfg(feature = "cloud-docs")]
451 permission: PermissionService::new_from_shared(shared_config.clone()),
452 #[cfg(feature = "cloud-docs")]
453 board: BoardService::new_from_shared(shared_config.clone()),
454 }
455 }
456}
457
458impl LarkClient {
459 pub fn builder(app_id: &str, app_secret: &str) -> LarkClientBuilder {
474 LarkClientBuilder {
475 config_builder: Config::builder().app_id(app_id).app_secret(app_secret),
476 }
477 }
478
479 #[allow(dead_code)] pub(crate) fn shared_config(&self) -> Arc<Config> {
482 self.shared_config.clone()
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use std::time::Duration;
490
491 fn create_test_builder() -> LarkClientBuilder {
492 LarkClient::builder("test_app_id", "test_app_secret")
493 }
494
495 #[test]
496 fn test_client_builder_creation() {
497 let client = LarkClient::builder("test_id", "test_secret").build();
498 assert_eq!(client.config.app_id, "test_id");
499 assert_eq!(client.config.app_secret, "test_secret");
500 assert_eq!(client.config.app_type, AppType::SelfBuild); }
502
503 #[test]
504 fn test_builder_with_app_type() {
505 let client = create_test_builder()
506 .with_app_type(AppType::Marketplace)
507 .build();
508 assert_eq!(client.config.app_type, AppType::Marketplace);
509 }
510
511 #[test]
512 fn test_builder_with_marketplace_app() {
513 let client = create_test_builder().with_marketplace_app().build();
514 assert_eq!(client.config.app_type, AppType::Marketplace);
515 }
516
517 #[test]
518 fn test_builder_with_custom_base_url() {
519 let custom_url = "https://custom.api.feishu.cn";
520 let client = create_test_builder()
521 .with_open_base_url(custom_url.to_string())
522 .build();
523 assert_eq!(client.config.base_url, custom_url);
524 }
525
526 #[test]
527 fn test_builder_with_enable_token_cache() {
528 let client_enabled = create_test_builder().with_enable_token_cache(true).build();
529 assert!(client_enabled.config.enable_token_cache);
530
531 let client_disabled = create_test_builder().with_enable_token_cache(false).build();
532 assert!(!client_disabled.config.enable_token_cache);
533 }
534
535 #[test]
536 fn test_builder_with_req_timeout() {
537 let timeout_seconds = 30.0;
538 let client = create_test_builder()
539 .with_req_timeout(Some(timeout_seconds))
540 .build();
541
542 let expected_duration = Duration::from_secs_f32(timeout_seconds);
543 assert_eq!(client.config.req_timeout, Some(expected_duration));
544 }
545
546 #[test]
547 fn test_builder_with_none_timeout() {
548 let client = create_test_builder().with_req_timeout(None).build();
549 assert_eq!(client.config.req_timeout, None);
550 }
551
552 #[test]
553 fn test_builder_chaining() {
554 let client = create_test_builder()
555 .with_app_type(AppType::Marketplace)
556 .with_enable_token_cache(true)
557 .with_req_timeout(Some(45.0))
558 .with_open_base_url("https://test.api.feishu.cn".to_string())
559 .build();
560
561 assert_eq!(client.config.app_type, AppType::Marketplace);
562 assert!(client.config.enable_token_cache);
563 assert_eq!(
564 client.config.req_timeout,
565 Some(Duration::from_secs_f32(45.0))
566 );
567 assert_eq!(client.config.base_url, "https://test.api.feishu.cn");
568 }
569
570 #[test]
571 fn test_client_build() {
572 let client = create_test_builder().build();
573
574 assert_eq!(client.config.app_id, "test_app_id");
575 assert_eq!(client.config.app_secret, "test_app_secret");
576 assert_eq!(client.config.app_type, AppType::SelfBuild);
577 }
578
579 #[test]
580 fn test_client_build_with_timeout() {
581 let client = create_test_builder().with_req_timeout(Some(60.0)).build();
582
583 assert_eq!(
584 client.config.req_timeout,
585 Some(Duration::from_secs_f32(60.0))
586 );
587 }
588
589 #[test]
590 fn test_client_build_marketplace_app() {
591 let client = create_test_builder().with_marketplace_app().build();
592
593 assert_eq!(client.config.app_type, AppType::Marketplace);
594 }
595
596 #[test]
597 fn test_client_build_with_custom_config() {
598 let client = create_test_builder()
599 .with_app_type(AppType::Marketplace)
600 .with_enable_token_cache(false)
601 .with_open_base_url("https://custom.feishu.cn".to_string())
602 .with_req_timeout(Some(120.0))
603 .build();
604
605 assert_eq!(client.config.app_type, AppType::Marketplace);
606 assert!(!client.config.enable_token_cache);
607 assert_eq!(client.config.base_url, "https://custom.feishu.cn");
608 assert_eq!(
609 client.config.req_timeout,
610 Some(Duration::from_secs_f32(120.0))
611 );
612 }
613
614 #[test]
615 fn test_builder_empty_credentials() {
616 let builder = LarkClient::builder("", "");
617 let config = builder.build_config();
618 assert_eq!(config.app_id, "");
619 assert_eq!(config.app_secret, "");
620 }
621
622 #[test]
623 fn test_builder_unicode_credentials() {
624 let app_id = "测试_app_id_🔑";
625 let app_secret = "测试_secret_🔐";
626 let builder = LarkClient::builder(app_id, app_secret);
627 let config = builder.build_config();
628
629 assert_eq!(config.app_id, app_id);
630 assert_eq!(config.app_secret, app_secret);
631 }
632
633 #[test]
634 fn test_builder_very_long_credentials() {
635 let long_id = "a".repeat(1000);
636 let long_secret = "b".repeat(1000);
637 let builder = LarkClient::builder(&long_id, &long_secret);
638 let config = builder.build_config();
639
640 assert_eq!(config.app_id, long_id);
641 assert_eq!(config.app_secret, long_secret);
642 }
643
644 #[test]
645 fn test_builder_special_characters() {
646 let special_id = "app-id_123!@#$%^&*()";
647 let special_secret = "secret/\\?<>|:\"{}";
648 let builder = LarkClient::builder(special_id, special_secret);
649 let config = builder.build_config();
650
651 assert_eq!(config.app_id, special_id);
652 assert_eq!(config.app_secret, special_secret);
653 }
654
655 #[test]
656 fn test_builder_extreme_timeout_values() {
657 let small_timeout = create_test_builder().with_req_timeout(Some(0.001)).build();
659 assert_eq!(
660 small_timeout.config.req_timeout,
661 Some(Duration::from_secs_f32(0.001))
662 );
663
664 let large_timeout = create_test_builder()
666 .with_req_timeout(Some(3600.0)) .build();
668 assert_eq!(
669 large_timeout.config.req_timeout,
670 Some(Duration::from_secs_f32(3600.0))
671 );
672 }
673
674 #[test]
675 fn test_config_independence() {
676 let builder1 = create_test_builder().with_app_type(AppType::Marketplace);
678
679 let builder2 = create_test_builder().with_app_type(AppType::SelfBuild);
680
681 assert_eq!(builder1.build_config().app_type, AppType::Marketplace);
682 assert_eq!(builder2.build_config().app_type, AppType::SelfBuild);
683 }
684
685 #[test]
686 fn test_builder_default_values() {
687 let builder = create_test_builder();
688 let config = builder.build_config();
689
690 assert_eq!(config.app_type, AppType::SelfBuild);
692 assert!(config.enable_token_cache); assert_eq!(config.req_timeout, None);
694 assert!(!config.base_url.is_empty()); }
696
697 #[cfg(feature = "cloud-docs")]
698 #[test]
699 fn test_client_cloud_docs_services() {
700 let client = create_test_builder().build();
701
702 let _assistant = &client.assistant;
706 let _drive = &client.drive;
707 let _sheets = &client.sheets;
708 let _bitable = &client.bitable;
709 let _wiki = &client.wiki;
710 let _docs = &client.docs;
711 }
712
713 #[test]
714 fn test_client_builder_multiple_configurations() {
715 let client1 = create_test_builder().with_marketplace_app().build();
717
718 let client2 = LarkClient::builder("different_id", "different_secret")
719 .with_enable_token_cache(false)
720 .build();
721
722 assert_eq!(client1.config.app_type, AppType::Marketplace);
723 assert_eq!(client1.config.app_id, "test_app_id");
724
725 assert_eq!(client2.config.app_type, AppType::SelfBuild);
726 assert_eq!(client2.config.app_id, "different_id");
727 assert!(!client2.config.enable_token_cache);
728 }
729}