Skip to main content

openlark_docs/common/
request_builder.rs

1//! 强制 Builder 模式实现
2//!
3//! 确保请求对象在构建时必填字段已设置,避免运行时验证错误。
4//!
5//! # 设计理念
6//!
7//! - **编译时验证**: 在 build() 时验证所有必填字段
8//! - **类型安全**: 使用 `Option<T>` 明确标记字段状态
9//! - **零开销**: 仅在构建时验证,运行时无额外开销
10//! - **向后兼容**: 保留旧 API,标记为 deprecated
11//!
12//! # 使用示例
13//!
14//! ```rust,ignore
15//! use openlark_docs::common::request_builder::impl_required_builder;
16//!
17//! impl_required_builder!(
18//!     CreateRecordRequest,
19//!     CreateRecordBuilder,
20//!     required: [
21//!         app_token: String,
22//!         table_id: String
23//!     ],
24//!     optional: [
25//!         user_id_type: String,
26//!         client_token: String
27//!     ]
28//! );
29//!
30//! // 使用方式
31//! let request = CreateRecordRequest::builder()
32//!     .app_token("app_xxx")
33//!     .table_id("table_xxx")
34//!     .user_id_type("open_id")
35//!     .config(config)
36//!     .build()?;
37//! ```
38
39/// 强制构建器宏
40///
41/// 为 Request 结构生成类型安全的 Builder,确保必填字段在编译时被验证。
42///
43/// # 参数说明
44///
45/// - `$request_name`: 请求结构体名称
46/// - `$builder_name`: 构建器结构体名称
47/// - `required: [...]`: 必填字段列表(格式: `field_name: FieldType`)
48/// - `optional: [...]`: 可选字段列表(格式: `field_name: FieldType`)
49///
50/// # 生成内容
51///
52/// 该宏会生成:
53/// 1. Builder 结构体(所有字段都是 `Option<T>`)
54/// 2. Builder::new() 和 Builder::default() 方法
55/// 3. 每个字段的 setter 方法
56/// 4. Builder::build() 方法(验证必填字段)
57/// 5. Request::builder() 关联方法
58///
59/// # 示例
60///
61/// ```rust,ignore
62/// impl_required_builder!(
63///     CreateRecordRequest,
64///     CreateRecordBuilder,
65///     required: [
66///         app_token: String,
67///         table_id: String
68///     ],
69///     optional: [
70///         user_id_type: String,
71///         client_token: String
72///     ]
73/// );
74/// ```
75#[macro_export]
76macro_rules! impl_required_builder {
77    (
78        $request_name:ident,
79        $builder_name:ident,
80        required: [$($req_field:ident: $req_type:ty),* $(,)?],
81        optional: [$($opt_field:ident: $opt_type:ty),* $(,)?]
82    ) => {
83        #[derive(Debug, Default)]
84        pub struct $builder_name {
85            $(
86                $req_field: Option<$req_type>,
87            )*
88            $(
89                $opt_field: Option<$opt_type>,
90            )*
91            config: Option<openlark_core::config::Config>,
92            _phantom: std::marker::PhantomData<$request_name>,
93        }
94
95        impl $builder_name {
96            /// 创建新的构建器实例(废弃,请使用 builder())
97            #[deprecated(since = "0.5.0", note = "使用 builder() 替代")]
98            #[allow(dead_code)]
99            pub fn new() -> Self {
100                Self::default()
101            }
102
103            /// 设置配置(链式调用)
104            pub fn with_config(mut self, config: openlark_core::config::Config) -> Self {
105                self.config = Some(config);
106                self
107            }
108
109            $(
110                /// 设置必填字段
111                pub fn $req_field(mut self, value: impl Into<$req_type>) -> Self {
112                    self.$req_field = Some(value.into());
113                    self
114                }
115            )*
116
117            $(
118                /// 设置可选字段
119                pub fn $opt_field(mut self, value: impl Into<$opt_type>) -> Self {
120                    self.$opt_field = Some(value.into());
121                    self
122                }
123            )*
124
125            /// 构建请求实例,验证所有必填字段
126            pub fn build(self) -> openlark_core::SDKResult<$request_name> {
127                // 验证必填字段
128                $(
129                    let $req_field = self.$req_field.ok_or_else(|| {
130                        openlark_core::error::validation_error(
131                            stringify!($req_field),
132                            concat!("必填字段 '", stringify!($req_field), "' 未设置")
133                        )
134                    })?;
135                )*
136
137                let config = self.config.ok_or_else(|| {
138                    openlark_core::error::validation_error(
139                        "config",
140                        "Config 未设置,请使用 with_config() 方法"
141                    )
142                })?;
143
144                Ok($request_name {
145                    $(
146                        $req_field,
147                    )*
148                    $(
149                        $opt_field: self.$opt_field,
150                    )*
151                    config,
152                })
153            }
154        }
155
156        impl $request_name {
157            /// 创建构建器实例
158            pub fn builder() -> $builder_name {
159                $builder_name::default()
160            }
161        }
162    };
163}
164
165/// 简化的流式构建器宏
166///
167/// 为已有的 Request 结构添加 builder() 方法,保持向后兼容。
168///
169/// # 使用场景
170///
171/// 当 Request 结构已经定义,且使用 String::new() 初始化字段时,
172/// 使用此宏添加 Builder 模式支持。
173///
174/// # 示例
175///
176/// ```rust,ignore
177/// // 已有的 Request 结构
178/// #[derive(Debug, Clone)]
179/// pub struct MyRequest {
180///     pub app_token: String,
181///     pub table_id: String,
182///     pub config: Config,
183/// }
184///
185/// // 添加 Builder 支持
186/// impl_fluent_builder!(MyRequest, app_token, table_id);
187/// ```
188#[macro_export]
189macro_rules! impl_fluent_builder {
190    (
191        $request_name:ident,
192        config: Config,
193        required: [$($req_field:ident: $req_type:ty),* $(,)?],
194        optional: [$($opt_field:ident: $opt_type:ty),* $(,)?]
195    ) => {
196        impl $request_name {
197            /// 创建 Builder 实例
198            pub fn builder() -> $request_name {
199                Self {
200                    $(
201                        $req_field: String::new(),
202                    )*
203                    $(
204                        $opt_field: None,
205                    )*
206                    config: Config::default(),
207                }
208            }
209
210            $(
211                /// 设置必填字段(流式接口)
212                pub fn $req_field(mut self, value: impl Into<$req_type>) -> Self {
213                    self.$req_field = value.into();
214                    self
215                }
216            )*
217
218            $(
219                /// 设置可选字段(流式接口)
220                pub fn $opt_field(mut self, value: impl Into<$opt_type>) -> Self {
221                    self.$opt_field = Some(value.into());
222                    self
223                }
224            )*
225        }
226    };
227}
228
229#[cfg(test)]
230mod tests {
231    use openlark_core::config::Config;
232
233    // 测试用的请求结构
234    #[derive(Debug, Clone)]
235    pub struct TestRequest {
236        app_token: String,
237        table_id: String,
238        user_id_type: Option<String>,
239        #[allow(dead_code)]
240        config: Config,
241    }
242
243    // 使用宏生成 Builder
244    impl_required_builder!(
245        TestRequest,
246        TestRequestBuilder,
247        required: [
248            app_token: String,
249            table_id: String
250        ],
251        optional: [
252            user_id_type: String
253        ]
254    );
255
256    #[test]
257    fn test_required_builder_success() {
258        let config = Config::builder().app_id("test").app_secret("test").build();
259
260        let request = TestRequest::builder()
261            .with_config(config)
262            .app_token("token")
263            .table_id("table")
264            .build()
265            .unwrap();
266
267        assert_eq!(request.app_token, "token");
268        assert_eq!(request.table_id, "table");
269        assert!(request.user_id_type.is_none());
270    }
271
272    #[test]
273    fn test_required_builder_with_optional() {
274        let config = Config::builder().app_id("test").app_secret("test").build();
275
276        let request = TestRequest::builder()
277            .with_config(config)
278            .app_token("token")
279            .table_id("table")
280            .user_id_type("open_id")
281            .build()
282            .unwrap();
283
284        assert_eq!(request.user_id_type, Some("open_id".to_string()));
285    }
286
287    #[test]
288    fn test_required_builder_missing_required_field() {
289        let config = Config::builder().app_id("test").app_secret("test").build();
290
291        let result = TestRequest::builder()
292            .with_config(config)
293            .app_token("token")
294            // 缺少 table_id
295            .build();
296
297        assert!(result.is_err());
298        assert!(result.unwrap_err().to_string().contains("table_id"));
299    }
300
301    #[test]
302    fn test_required_builder_missing_config() {
303        let result = TestRequest::builder()
304            .app_token("token")
305            .table_id("table")
306            // 缺少 config
307            .build();
308
309        assert!(result.is_err());
310        assert!(result.unwrap_err().to_string().contains("Config"));
311    }
312
313    #[test]
314    fn test_required_builder_into_string() {
315        let config = Config::builder().app_id("test").app_secret("test").build();
316
317        // 测试 impl Into<String>
318        let request = TestRequest::builder()
319            .with_config(config)
320            .app_token("token") // &str
321            .table_id(String::from("table")) // String
322            .build()
323            .unwrap();
324
325        assert_eq!(request.app_token, "token");
326        assert_eq!(request.table_id, "table");
327    }
328}