Skip to main content

openlark_client/
client.rs

1//! OpenLark Client - 全新简化架构
2//!
3//! 极简设计:仅保留 meta 链式字段访问(单入口,KISS)
4
5#[macro_use]
6mod macros;
7
8mod builder;
9mod error_handling;
10#[cfg(test)]
11mod tests;
12
13pub use builder::ClientBuilder;
14pub use error_handling::ClientErrorHandling;
15
16#[allow(deprecated)]
17use crate::Config;
18use crate::{
19    DefaultServiceRegistry, Result,
20    client_build_config::{ClientBuildConfig, validate_core_config},
21    error::{with_context, with_operation_context},
22    traits::LarkClient,
23};
24use std::sync::Arc;
25
26/// 🔐 认证 meta 入口:`client.auth.app / client.auth.user / client.auth.oauth`
27#[cfg(feature = "auth")]
28#[derive(Debug, Clone)]
29pub struct AuthClient {
30    /// 应用认证服务
31    pub app: openlark_auth::AuthService,
32    /// 用户身份认证服务
33    pub user: openlark_auth::AuthenService,
34    /// OAuth 授权服务
35    pub oauth: openlark_auth::OAuthService,
36}
37
38#[cfg(feature = "auth")]
39impl AuthClient {
40    fn new(config: openlark_core::config::Config) -> Self {
41        Self {
42            app: openlark_auth::AuthService::new(config.clone()),
43            user: openlark_auth::AuthenService::new(config.clone()),
44            oauth: openlark_auth::OAuthService::new(config),
45        }
46    }
47}
48
49declare_client! {
50    {
51        feature: "cardkit",
52        field: cardkit,
53        ty: openlark_cardkit::CardkitClient,
54        doc: "CardKit meta 调用链:client.cardkit.v1.card.create(...)",
55        init: |_core_config, _base_core_config| {
56            openlark_cardkit::CardkitClient::new(_core_config.clone())
57        },
58    },
59    {
60        feature: "auth",
61        field: auth,
62        ty: AuthClient,
63        doc: "Auth meta 调用链入口:client.auth.app / client.auth.user / client.auth.oauth",
64        init: |_core_config, _base_core_config| {
65            AuthClient::new(_base_core_config.clone())
66        },
67    },
68    {
69        feature: "docs",
70        field: docs,
71        ty: openlark_docs::DocsClient,
72        doc: "Docs meta 调用链入口:client.docs.ccm / client.docs.base ...",
73        init: |_core_config, _base_core_config| {
74            openlark_docs::DocsClient::new(_core_config.clone())
75        },
76    },
77    {
78        feature: "communication",
79        field: communication,
80        ty: openlark_communication::CommunicationClient,
81        doc: "Communication meta 调用链入口:client.communication.im / client.communication.contact ...",
82        init: |_core_config, _base_core_config| {
83            openlark_communication::CommunicationClient::new(_core_config.clone())
84        },
85    },
86    {
87        feature: "hr",
88        field: hr,
89        ty: openlark_hr::HrClient,
90        doc: "HR meta 调用链入口:client.hr.attendance / client.hr.corehr / client.hr.hire ...",
91        init: |_core_config, _base_core_config| {
92            openlark_hr::HrClient::new(_core_config.clone())
93        },
94    },
95    {
96        feature: "meeting",
97        field: meeting,
98        ty: openlark_meeting::MeetingClient,
99        doc: "Meeting meta 调用链入口:client.meeting.vc.v1.room.create() ...",
100        init: |_core_config, _base_core_config| {
101            openlark_meeting::MeetingClient::new(_core_config.clone())
102        },
103    },
104    {
105        feature: "ai",
106        field: ai,
107        ty: openlark_ai::AiClient,
108        doc: "AI meta 调用链入口:client.ai.chat.create() ...",
109        init: |_core_config, _base_core_config| {
110            openlark_ai::AiClient::new(_core_config.clone())
111        },
112    },
113    {
114        feature: "workflow",
115        field: workflow,
116        ty: crate::WorkflowClient,
117        doc: "Workflow meta 调用链入口:client.workflow.task.create() ...",
118        init: |_core_config, _base_core_config| {
119            crate::WorkflowClient::new(_core_config.clone())
120        },
121    },
122    {
123        feature: "platform",
124        field: platform,
125        ty: crate::PlatformClient,
126        doc: "Platform meta 调用链入口:client.platform.app_engine... ...",
127        init: |_core_config, _base_core_config| {
128            crate::PlatformClient::new(_core_config.clone())?
129        },
130    },
131    {
132        feature: "application",
133        field: application,
134        ty: crate::ApplicationClient,
135        doc: "Application meta 调用链入口:client.application.applet... ...",
136        init: |_core_config, _base_core_config| {
137            crate::ApplicationClient::new(_core_config.clone())
138        },
139    },
140    {
141        feature: "helpdesk",
142        field: helpdesk,
143        ty: crate::HelpdeskClient,
144        doc: "Helpdesk meta 调用链入口:client.helpdesk.ticket... ...",
145        init: |_core_config, _base_core_config| {
146            crate::HelpdeskClient::new(_core_config.clone())
147        },
148    },
149    {
150        feature: "mail",
151        field: mail,
152        ty: crate::MailClient,
153        doc: "Mail meta 调用链入口:client.mail.group... ...",
154        init: |_core_config, _base_core_config| {
155            crate::MailClient::new(_core_config.clone())
156        },
157    },
158    {
159        feature: "analytics",
160        field: analytics,
161        ty: crate::AnalyticsClient,
162        doc: "Analytics meta 调用链入口:client.analytics.report... ...",
163        init: |_core_config, _base_core_config| {
164            crate::AnalyticsClient::new(_core_config.clone())?
165        },
166    },
167    {
168        feature: "user",
169        field: user,
170        ty: crate::UserClient,
171        doc: "User meta 调用链入口:client.user.setting... ...",
172        init: |_core_config, _base_core_config| {
173            crate::UserClient::new(_core_config.clone())?
174        },
175    },
176    {
177        feature: "security",
178        field: security,
179        ty: crate::SecurityClient,
180        doc: "Security meta 调用链入口:client.security.acs... ...",
181        init: |_core_config, _base_core_config| {
182            let security_config = openlark_security::models::SecurityConfig::new(
183                _core_config.app_id().to_string(),
184                _core_config.app_secret().to_string(),
185            )
186            .with_base_url(_core_config.base_url());
187            openlark_security::SecurityClient::new(security_config)
188        },
189    },
190}
191
192impl Client {
193    /// 🔥 从环境变量创建客户端
194    ///
195    /// # 环境变量
196    /// ```bash
197    /// export OPENLARK_APP_ID=your_app_id
198    /// export OPENLARK_APP_SECRET=your_app_secret
199    /// export OPENLARK_BASE_URL=https://open.feishu.cn  # 可选
200    /// ```
201    ///
202    ///
203    /// # 返回值
204    /// 返回配置好的客户端实例或错误
205    ///
206    /// # 示例
207    /// ```rust,no_run
208    /// use openlark_client::Client;
209    ///
210    /// let _client = Client::from_env();
211    /// ```
212    pub fn from_env() -> Result<Self> {
213        Self::builder().from_env().build()
214    }
215
216    /// 🏗️ 创建构建器
217    pub fn builder() -> ClientBuilder {
218        ClientBuilder::new()
219    }
220
221    /// 🔧 获取客户端配置(统一的 CoreConfig)
222    pub fn config(&self) -> &openlark_core::config::Config {
223        &self.config
224    }
225
226    /// 📋 获取服务注册表
227    pub fn registry(&self) -> &DefaultServiceRegistry {
228        &self.registry
229    }
230
231    /// 🔧 获取底层 core 配置
232    ///
233    /// 与 [`Self::config`] 返回同一份配置。保留此别名是为了向后兼容。
234    pub fn core_config(&self) -> &openlark_core::config::Config {
235        &self.config
236    }
237
238    /// 🔧 获取可直接传给函数式 API 的认证后配置
239    ///
240    /// 与 [`Self::config`] 返回同一份配置。保留此别名是为了让
241    /// 业务侧更容易理解它的用途:可直接传给 `openlark_docs::*`、
242    /// `openlark_auth::*` 等函数式 API。
243    pub fn api_config(&self) -> &openlark_core::config::Config {
244        &self.config
245    }
246
247    /// ✅ 检查客户端是否已正确配置
248    pub fn is_configured(&self) -> bool {
249        !self.config.app_id().is_empty() && !self.config.app_secret().is_empty()
250    }
251
252    /// 🆕 创建带有自定义配置的客户端
253    #[allow(deprecated)]
254    pub fn with_config(config: Config) -> Result<Self> {
255        let build_config = ClientBuildConfig::from(config);
256        if let Err(err) = build_config.validate() {
257            return with_context(Err(err), "operation", "Client::with_config");
258        }
259
260        Self::with_validated_core_config(build_config.build_core_config(), "Client::with_config")
261    }
262
263    /// 🆕 使用统一 CoreConfig 创建客户端
264    pub fn with_core_config(config: openlark_core::config::Config) -> Result<Self> {
265        if let Err(err) = validate_core_config(&config) {
266            return with_context(Err(err), "operation", "Client::with_core_config");
267        }
268
269        Self::with_validated_core_config(config, "Client::with_core_config")
270    }
271
272    pub(crate) fn with_validated_core_config(
273        base_core_config: openlark_core::config::Config,
274        operation: &str,
275    ) -> Result<Self> {
276        let mut registry = DefaultServiceRegistry::new();
277
278        if let Err(err) = crate::registry::bootstrap::register_compiled_services(&mut registry) {
279            return with_operation_context(Err(err), operation, "service_loading");
280        }
281
282        let registry = Arc::new(registry);
283
284        #[cfg(feature = "auth")]
285        let core_config = {
286            use openlark_auth::AuthTokenProvider;
287            let provider = AuthTokenProvider::new(base_core_config.clone());
288            base_core_config.with_token_provider(provider)
289        };
290        #[cfg(not(feature = "auth"))]
291        let core_config = base_core_config.clone();
292
293        Self::from_parts(registry, base_core_config, core_config)
294    }
295
296    /// 🔧 执行带有错误上下文的操作
297    pub async fn execute_with_context<F, T>(&self, operation: &str, f: F) -> Result<T>
298    where
299        F: std::future::Future<Output = Result<T>>,
300    {
301        let result = f.await;
302        with_operation_context(result, operation, "Client")
303    }
304}
305
306impl LarkClient for Client {
307    fn config(&self) -> &openlark_core::config::Config {
308        &self.config
309    }
310
311    fn is_configured(&self) -> bool {
312        self.is_configured()
313    }
314}