Skip to main content

openlark_docs/common/
builders.rs

1//! 通用构建器模式模块
2//!
3//! 提供统一的 Builder 模式实现,减少代码重复并保持 API 一致性。
4
5/// 实现API请求构建器的通用宏
6///
7/// # 参数说明
8/// - `$builder_name`: 构建器结构体名称
9/// - `$request_name`: 请求结构体名称
10/// - `$field`: 字段名称
11/// - `$field_type`: 字段类型
12///
13/// # 示例
14/// ```rust,ignore
15/// impl_api_builder!(
16///     DeleteRoleV2Builder,
17///     DeleteRoleV2Request,
18///     app_token: String,
19///     role_id: String,
20/// );
21/// ```
22#[macro_export]
23macro_rules! impl_api_builder {
24    (
25        $builder_name:ident,
26        $request_name:ident,
27        $( $field:ident: $field_type:ty ),* $(,)?
28    ) => {
29        #[derive(Default)]
30        pub struct $builder_name {
31            request: $request_name,
32        }
33
34        impl $builder_name {
35            /// 创建新的构建器实例
36            pub fn new() -> Self {
37                Self::default()
38            }
39
40            $(
41                /// 设置字段 $field
42                pub fn $field(mut self, $field: impl Into<$field_type>) -> Self {
43                    self.request.$field = $field.into();
44                    self
45                }
46            )*
47
48            /// 构建最终的请求实例
49            pub fn build(self) -> $request_name {
50                self.request
51            }
52        }
53
54        impl $request_name {
55            /// 创建构建器实例
56            pub fn builder() -> $builder_name {
57                $builder_name::new()
58            }
59        }
60    };
61}
62
63/// 实现基础API请求字段的宏
64/// 包含常用的字段如 app_token, table_id 等
65#[macro_export]
66macro_rules! impl_base_api_fields {
67    ($request_name:ident) => {
68        impl $request_name {
69            /// 设置应用令牌
70            pub fn app_token(mut self, app_token: impl Into<String>) -> Self {
71                self.app_token = app_token.into();
72                self
73            }
74
75            /// 设置表格ID(如果适用)
76            #[cfg(feature = "bitable")]
77            pub fn table_id(mut self, table_id: impl Into<String>) -> Self {
78                self.table_id = table_id.into();
79                self
80            }
81        }
82    };
83}
84
85/// 参数验证宏
86/// 用于统一验证必填参数
87#[macro_export]
88macro_rules! validate_required {
89    ($field:expr, $error_msg:expr) => {
90        if $field.is_empty() {
91            return Err(openlark_core::error::CoreError::validation_msg($error_msg));
92        }
93    };
94
95    ($field:expr, $error_msg:expr, $($fields:expr, $error_msgs:expr),+ $(,)?) => {
96        if $field.is_empty() {
97            return Err(openlark_core::error::CoreError::validation_msg($error_msg));
98        }
99        $(
100            if $fields.is_empty() {
101                return Err(openlark_core::error::CoreError::validation_msg($error_msgs));
102            }
103        )*
104    };
105}
106
107/// 路径构建宏
108/// 用于统一构建API路径
109#[macro_export]
110macro_rules! build_api_path {
111    ($base:expr, $($segment:expr),+ $(,)?) => {
112        format!("/{}/{}", $base.trim_matches('/'), [$($segment),+].join("/"))
113    };
114}
115
116/// 响应数据结构体宏
117/// 统一响应结构的基本字段
118#[macro_export]
119macro_rules! impl_response_data {
120    (
121        $response_name:ident,
122        $data_name:ident {
123            $( $field:ident: $field_type:ty ),* $(,)?
124        }
125    ) => {
126        pub struct $data_name {
127            $( pub $field: $field_type, )*
128        }
129
130        pub struct $response_name {
131            pub data: $data_name,
132        }
133
134        impl openlark_core::api::ApiResponseTrait for $response_name {
135            fn data_format() -> openlark_core::api::ResponseFormat {
136                openlark_core::api::ResponseFormat::Data
137            }
138        }
139    };
140}
141
142#[cfg(test)]
143mod tests {
144    use openlark_core::config::Config;
145
146    // 测试结构体定义
147    #[derive(Default)]
148    pub struct TestRequest {
149        app_token: String,
150        table_id: String,
151        name: Option<String>,
152    }
153
154    // 使用宏生成构建器
155    impl_api_builder!(
156        TestRequestBuilder,
157        TestRequest,
158        app_token: String,
159        table_id: String,
160        name: Option<String>,
161    );
162
163    #[test]
164    fn test_builder_macro() {
165        let _config = Config::builder().app_id("test").app_secret("test").build();
166
167        let request = TestRequest::builder()
168            .app_token("test_token")
169            .table_id("test_table")
170            .name("test_name".to_string())
171            .build();
172
173        assert_eq!(request.app_token, "test_token");
174        assert_eq!(request.table_id, "test_table");
175        assert_eq!(request.name, Some("test_name".to_string()));
176    }
177
178    #[test]
179    fn test_validate_macro() {
180        // 测试必填参数验证 - 在正常函数上下文中使用
181        fn test_function() -> Result<(), openlark_core::error::CoreError> {
182            // 正常情况不应该返回错误
183            validate_required!("valid_field", "字段不能为空");
184
185            // 空字段情况应该返回错误
186            validate_required!("", "字段不能为空");
187
188            Ok(())
189        }
190
191        // 验证空字段确实会返回错误
192        let result = test_function();
193        assert!(result.is_err());
194    }
195
196    #[test]
197    fn test_path_building_macro() {
198        let path = build_api_path!("open-apis", "v1", "apps", "123", "tables", "456");
199        assert_eq!(path, "/open-apis/v1/apps/123/tables/456");
200    }
201
202    #[test]
203    fn test_response_data_macro() {
204        impl_response_data!(
205            TestResponse,
206            TestData {
207                id: String,
208                name: String,
209                created_time: String,
210            }
211        );
212
213        // 验证结构体是否正确生成
214        let data = TestData {
215            id: "123".to_string(),
216            name: "测试".to_string(),
217            created_time: "2023-01-01".to_string(),
218        };
219        let response = TestResponse { data };
220
221        assert_eq!(response.data.id, "123");
222        assert_eq!(response.data.name, "测试");
223        assert_eq!(response.data.created_time, "2023-01-01");
224    }
225}