Skip to main content

openlark_client/
client.rs

1//! OpenLark Client - 全新简化架构
2//!
3//! 极简设计:仅保留 meta 链式字段访问(单入口,KISS)
4
5#[macro_use]
6mod macros;
7
8mod builder;
9#[cfg(test)]
10#[macro_use]
11mod catalog_contract_support;
12#[cfg(test)]
13mod catalog_contract_foundational;
14#[cfg(test)]
15mod catalog_contract_listing;
16#[cfg(test)]
17mod catalog_contract_remaining;
18mod error_handling;
19#[cfg(test)]
20mod tests;
21
22pub use builder::ClientBuilder;
23pub use error_handling::ClientErrorHandling;
24
25use crate::{
26    DefaultServiceRegistry, Result,
27    error::{with_context, with_operation_context},
28    traits::LarkClient,
29    validation_error,
30};
31use std::sync::Arc;
32
33/// 🔐 认证 meta 入口:`client.auth.app / client.auth.user / client.auth.oauth`
34#[cfg(feature = "auth")]
35#[derive(Debug, Clone)]
36pub struct AuthClient {
37    /// 应用认证服务
38    pub app: openlark_auth::AuthService,
39    /// 用户身份认证服务
40    pub user: openlark_auth::AuthenService,
41    /// OAuth 授权服务
42    pub oauth: openlark_auth::OAuthService,
43}
44
45#[cfg(feature = "auth")]
46impl AuthClient {
47    fn new(config: openlark_core::config::Config) -> Self {
48        Self {
49            app: openlark_auth::AuthService::new(config.clone()),
50            user: openlark_auth::AuthenService::new(config.clone()),
51            oauth: openlark_auth::OAuthService::new(config),
52        }
53    }
54}
55
56// 全部业务域由 capability catalog 生成(#434–#436)。
57// 命名:本宏是「由 catalog 条目生成 declare_client!」,不是「声明 catalog 本身」。
58// 死匹配(name/description/...):统一条目同时含构造与诊断字段,本侧只消费构造字段;
59// 与 generate_catalog_registry! 对称,是双投影的固有成本,而非重复逻辑。
60macro_rules! append_catalog_entries {
61    ($({
62        feature: $c_feature:literal,
63        field: $c_field:ident,
64        ty: $c_ty:ty,
65        doc: $c_doc:literal,
66        init: |$c_core:ident, $c_base:ident| $c_init:block,
67        // 诊断字段:由 registry 投影消费;此处仅匹配统一条目形状
68        name: $_name:literal,
69        description: $_description:literal,
70        dependencies: [$( $_dep:literal ),* $(,)?],
71        provides: [$( $_cap:literal ),* $(,)?],
72        priority: $_priority:literal $(,)?
73    }),* $(,)?) => {
74        declare_client! {
75            $(
76                {
77                    feature: $c_feature,
78                    field: $c_field,
79                    ty: $c_ty,
80                    doc: $c_doc,
81                    init: |$c_core, $c_base| $c_init,
82                },
83            )*
84        }
85    };
86}
87
88crate::capability::for_each_compiled_capability!(append_catalog_entries);
89
90impl Client {
91    /// 🔥 从环境变量创建客户端
92    ///
93    /// # 环境变量
94    /// ```bash
95    /// export OPENLARK_APP_ID=your_app_id
96    /// export OPENLARK_APP_SECRET=your_app_secret
97    /// export OPENLARK_BASE_URL=<https://open.feishu.cn>  # 可选
98    /// ```
99    ///
100    ///
101    /// # 返回值
102    /// 返回配置好的客户端实例或错误
103    ///
104    /// # 示例
105    /// ```rust,no_run
106    /// use openlark_client::Client;
107    ///
108    /// let _client = Client::from_env();
109    /// ```
110    pub fn from_env() -> Result<Self> {
111        Self::builder().from_env().build()
112    }
113
114    /// 🏗️ 创建构建器
115    pub fn builder() -> ClientBuilder {
116        ClientBuilder::new()
117    }
118
119    /// 🔧 获取客户端配置(统一的 CoreConfig)
120    pub fn config(&self) -> &openlark_core::config::Config {
121        &self.config
122    }
123
124    /// 📋 获取服务注册表
125    pub fn registry(&self) -> &DefaultServiceRegistry {
126        &self.registry
127    }
128
129    /// 🔧 获取底层 core 配置
130    ///
131    /// 与 [`Self::config`] 返回同一份配置。保留此别名是为了向后兼容。
132    pub fn core_config(&self) -> &openlark_core::config::Config {
133        &self.config
134    }
135
136    /// 🔧 获取可直接传给函数式 API 的认证后配置
137    ///
138    /// 与 [`Self::config`] 返回同一份配置。保留此别名是为了让
139    /// 业务侧更容易理解它的用途:可直接传给 `openlark_docs::*`、
140    /// `openlark_auth::*` 等函数式 API。
141    pub fn api_config(&self) -> &openlark_core::config::Config {
142        &self.config
143    }
144
145    /// ✅ 检查客户端是否已正确配置
146    pub fn is_configured(&self) -> bool {
147        !self.config.app_id().is_empty() && !self.config.app_secret().is_empty()
148    }
149
150    /// 🆕 使用统一 CoreConfig 创建客户端
151    ///
152    /// 与 [`ClientBuilder::build`] 共用私有构造 seam:`Config::validate`(含域名白名单)
153    /// + Client 零超时规则 + registry / token provider 装配。
154    pub fn with_core_config(config: openlark_core::config::Config) -> Result<Self> {
155        Self::with_checked_core_config(config, "Client::with_core_config")
156    }
157
158    /// 已校验构造 seam:`ClientBuilder::build` 与 [`Self::with_core_config`] 的唯一入口。
159    ///
160    /// 顺序:
161    /// 1. [`openlark_core::config::Config::validate`](凭据 / URL / 白名单 / retry)
162    /// 2. Client 特有:拒绝 `req_timeout == Some(Duration::ZERO)`(`None` 允许)
163    /// 3. 校验错误附加 `operation` context
164    /// 4. registry 初始化(失败时保留 operation + `service_loading`)
165    /// 5. token provider 注入
166    /// 6. 组装 [`Client`]
167    pub(crate) fn with_checked_core_config(
168        base_core_config: openlark_core::config::Config,
169        operation: &str,
170    ) -> Result<Self> {
171        if let Err(err) = base_core_config.validate() {
172            return with_context(Err(err), "operation", operation);
173        }
174        if base_core_config
175            .req_timeout()
176            .is_some_and(|timeout| timeout.is_zero())
177        {
178            return with_context(
179                Err(validation_error("timeout", "timeout必须大于0")),
180                "operation",
181                operation,
182            );
183        }
184
185        let mut registry = DefaultServiceRegistry::new();
186
187        if let Err(err) = crate::registry::bootstrap::register_compiled_services(&mut registry) {
188            return with_operation_context(Err(err), operation, "service_loading");
189        }
190
191        let registry = Arc::new(registry);
192
193        #[cfg(feature = "auth")]
194        let core_config = {
195            use openlark_auth::AuthTokenProvider;
196            let provider = AuthTokenProvider::new(base_core_config.clone());
197            base_core_config.with_token_provider(provider)
198        };
199        #[cfg(not(feature = "auth"))]
200        let core_config = base_core_config.clone();
201
202        Self::from_parts(registry, base_core_config, core_config)
203    }
204
205    /// 🔧 执行带有错误上下文的操作
206    pub async fn execute_with_context<F, T>(&self, operation: &str, f: F) -> Result<T>
207    where
208        F: std::future::Future<Output = Result<T>>,
209    {
210        let result = f.await;
211        with_operation_context(result, operation, "Client")
212    }
213}
214
215impl LarkClient for Client {
216    fn config(&self) -> &openlark_core::config::Config {
217        &self.config
218    }
219
220    fn is_configured(&self) -> bool {
221        self.is_configured()
222    }
223}