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)]
10mod catalog_wiring_tests;
11#[cfg(test)]
12mod tests;
13
14pub use builder::ClientBuilder;
15
16use crate::{
17    Result,
18    error::{with_context, with_operation_context},
19    validation_error,
20};
21
22/// 🔐 认证 meta 入口:`client.auth.app / client.auth.user / client.auth.oauth`
23#[cfg(feature = "auth")]
24#[derive(Debug, Clone)]
25pub struct AuthClient {
26    /// 应用认证服务
27    pub app: openlark_auth::AuthService,
28    /// 用户身份认证服务
29    pub user: openlark_auth::AuthenService,
30    /// OAuth 授权服务
31    pub oauth: openlark_auth::OAuthService,
32}
33
34#[cfg(feature = "auth")]
35impl AuthClient {
36    fn new(config: openlark_core::config::Config) -> Self {
37        Self {
38            app: openlark_auth::AuthService::new(config.clone()),
39            user: openlark_auth::AuthenService::new(config.clone()),
40            oauth: openlark_auth::OAuthService::new(config),
41        }
42    }
43}
44
45// 全部业务域由 capability catalog 生成(#434–#437 / #471)。
46// 命名:本宏是「由 catalog 条目生成 declare_client!」,不是「声明 catalog 本身」。
47// 字段唯一性检查在 capability 模块内独立执行(`catalog.rs` 的
48// `for_each_compiled_capability!(assert_catalog_fields_unique)`),不在此耦合。
49macro_rules! append_catalog_entries {
50    ($({
51        feature: $c_feature:literal,
52        field: $c_field:ident,
53        ty: $c_ty:ty,
54        doc: $c_doc:literal,
55        init: |$c_core:ident, $c_base:ident| $c_init:block $(,)?
56    }),* $(,)?) => {
57        declare_client! {
58            $(
59                {
60                    feature: $c_feature,
61                    field: $c_field,
62                    ty: $c_ty,
63                    doc: $c_doc,
64                    init: |$c_core, $c_base| $c_init,
65                },
66            )*
67        }
68    };
69}
70
71crate::capability::for_each_compiled_capability!(append_catalog_entries);
72
73impl Client {
74    /// 🔥 从环境变量创建客户端
75    ///
76    /// # 环境变量
77    /// ```bash
78    /// export OPENLARK_APP_ID=your_app_id
79    /// export OPENLARK_APP_SECRET=your_app_secret
80    /// export OPENLARK_BASE_URL=<https://open.feishu.cn>  # 可选
81    /// ```
82    ///
83    ///
84    /// # 返回值
85    /// 返回配置好的客户端实例或错误
86    ///
87    /// # 示例
88    /// ```rust,no_run
89    /// use openlark_client::Client;
90    ///
91    /// let _client = Client::from_env();
92    /// ```
93    pub fn from_env() -> Result<Self> {
94        Self::builder().from_env().build()
95    }
96
97    /// 🏗️ 创建构建器
98    pub fn builder() -> ClientBuilder {
99        ClientBuilder::new()
100    }
101
102    /// 🔧 获取客户端配置(统一的 CoreConfig)
103    pub fn config(&self) -> &openlark_core::config::Config {
104        &self.config
105    }
106
107    /// 🔧 获取底层 core 配置
108    ///
109    /// 与 [`Self::config`] 返回同一份配置。保留此别名是为了向后兼容。
110    pub fn core_config(&self) -> &openlark_core::config::Config {
111        &self.config
112    }
113
114    /// 🔧 获取可直接传给函数式 API 的认证后配置
115    ///
116    /// 与 [`Self::config`] 返回同一份配置。保留此别名是为了让
117    /// 业务侧更容易理解它的用途:可直接传给 `openlark_docs::*`、
118    /// `openlark_auth::*` 等函数式 API。
119    pub fn api_config(&self) -> &openlark_core::config::Config {
120        &self.config
121    }
122
123    /// ✅ 检查客户端是否已正确配置
124    pub fn is_configured(&self) -> bool {
125        !self.config.app_id().is_empty() && !self.config.app_secret().is_empty()
126    }
127
128    /// 🆕 使用统一 CoreConfig 创建客户端
129    ///
130    /// 与 [`ClientBuilder::build`] 共用私有构造 seam:`Config::validate`(含域名白名单)
131    /// + Client 零超时规则 + token provider 装配。
132    pub fn with_core_config(config: openlark_core::config::Config) -> Result<Self> {
133        Self::with_checked_core_config(config, "Client::with_core_config")
134    }
135
136    /// 已校验构造 seam:`ClientBuilder::build` 与 [`Self::with_core_config`] 的唯一入口。
137    ///
138    /// 顺序:
139    /// 1. [`openlark_core::config::Config::validate`](凭据 / URL / 白名单 / retry)
140    /// 2. Client 特有:拒绝 `req_timeout == Some(Duration::ZERO)`(`None` 允许)
141    /// 3. 校验错误附加 `operation` context
142    /// 4. token provider 注入
143    /// 5. 组装 [`Client`](catalog 生成全部业务域字段)
144    pub(crate) fn with_checked_core_config(
145        base_core_config: openlark_core::config::Config,
146        operation: &str,
147    ) -> Result<Self> {
148        if let Err(err) = base_core_config.validate() {
149            return with_context(Err(err), "operation", operation);
150        }
151        if base_core_config
152            .req_timeout()
153            .is_some_and(|timeout| timeout.is_zero())
154        {
155            return with_context(
156                Err(validation_error("timeout", "timeout必须大于0")),
157                "operation",
158                operation,
159            );
160        }
161
162        #[cfg(feature = "auth")]
163        let core_config = {
164            use openlark_auth::AuthTokenProvider;
165            let provider = AuthTokenProvider::new(base_core_config.clone());
166            base_core_config.with_token_provider(provider)
167        };
168        #[cfg(not(feature = "auth"))]
169        let core_config = base_core_config.clone();
170
171        Self::from_parts(base_core_config, core_config)
172    }
173
174    /// 🔧 执行带有错误上下文的操作
175    pub async fn execute_with_context<F, T>(&self, operation: &str, f: F) -> Result<T>
176    where
177        F: std::future::Future<Output = Result<T>>,
178    {
179        let result = f.await;
180        with_operation_context(result, operation, "Client")
181    }
182}