open_lark/service/cloud_docs/bitable/v1/app/
create.rs

1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    core::{
6        api_req::ApiRequest,
7        api_resp::{ApiResponseTrait, BaseResponse, ResponseFormat},
8        constants::AccessTokenType,
9        endpoints::cloud_docs::*,
10        http::Transport,
11        req_option::RequestOption,
12        SDKResult,
13    },
14    impl_executable_builder_owned,
15};
16
17use super::AppService;
18
19impl AppService {
20    /// 创建多维表格
21    ///
22    /// <https://open.feishu.cn/document/server-docs/docs/bitable-v1/app/create>
23    pub async fn create(
24        &self,
25        request: CreateAppRequest,
26        option: Option<RequestOption>,
27    ) -> SDKResult<BaseResponse<CreateAppResponse>> {
28        let mut api_req = request.api_request;
29        api_req.http_method = Method::POST;
30        api_req.api_path = BITABLE_V1_APPS.to_string();
31        api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
32        api_req.body = serde_json::to_vec(&CreateAppRequestBody {
33            name: request.name,
34            folder_token: request.folder_token,
35            time_zone: request.time_zone,
36        })?;
37
38        let api_resp = Transport::request(api_req, &self.config, option).await?;
39        Ok(api_resp)
40    }
41}
42
43/// 创建多维表格请求
44#[derive(Debug, Default)]
45pub struct CreateAppRequest {
46    api_request: ApiRequest,
47    /// 多维表格 App 名字
48    name: String,
49    /// 多维表格所在文件夹的 token,若不传则默认添加到用户云空间的根目录下
50    folder_token: Option<String>,
51    /// 时区
52    time_zone: Option<String>,
53}
54
55impl CreateAppRequest {
56    pub fn builder() -> CreateAppRequestBuilder {
57        CreateAppRequestBuilder::default()
58    }
59}
60
61#[derive(Default)]
62pub struct CreateAppRequestBuilder {
63    request: CreateAppRequest,
64}
65
66impl CreateAppRequestBuilder {
67    /// 多维表格 App 名字
68    pub fn name(mut self, name: impl ToString) -> Self {
69        self.request.name = name.to_string();
70        self
71    }
72
73    /// 多维表格所在文件夹的 token
74    pub fn folder_token(mut self, folder_token: impl ToString) -> Self {
75        self.request.folder_token = Some(folder_token.to_string());
76        self
77    }
78
79    /// 时区
80    pub fn time_zone(mut self, time_zone: impl ToString) -> Self {
81        self.request.time_zone = Some(time_zone.to_string());
82        self
83    }
84
85    pub fn build(self) -> CreateAppRequest {
86        self.request
87    }
88}
89
90impl_executable_builder_owned!(
91    CreateAppRequestBuilder,
92    AppService,
93    CreateAppRequest,
94    BaseResponse<CreateAppResponse>,
95    create
96);
97
98#[derive(Serialize)]
99struct CreateAppRequestBody {
100    name: String,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    folder_token: Option<String>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    time_zone: Option<String>,
105}
106
107#[derive(Deserialize, Debug)]
108pub struct CreateAppResponse {
109    /// 多维表格的 app 信息
110    pub app: CreateAppResponseData,
111}
112
113#[derive(Deserialize, Debug)]
114pub struct CreateAppResponseData {
115    /// 多维表格的 app_token
116    pub app_token: String,
117    /// 多维表格的名字
118    pub name: String,
119    /// 多维表格的版本号
120    pub revision: i32,
121    /// 多维表格的链接
122    pub url: String,
123}
124
125impl ApiResponseTrait for CreateAppResponse {
126    fn data_format() -> ResponseFormat {
127        ResponseFormat::Data
128    }
129}
130
131#[cfg(test)]
132#[allow(unused_variables, unused_unsafe)]
133mod tests {
134    use super::*;
135    use serde_json::json;
136
137    #[test]
138    fn test_create_app_request() {
139        let request = CreateAppRequest::builder()
140            .name("测试多维表格")
141            .folder_token("fldcnmBA*****yGehy8")
142            .time_zone("Asia/Shanghai")
143            .build();
144
145        assert_eq!(request.name, "测试多维表格");
146        assert_eq!(
147            request.folder_token,
148            Some("fldcnmBA*****yGehy8".to_string())
149        );
150        assert_eq!(request.time_zone, Some("Asia/Shanghai".to_string()));
151    }
152
153    #[test]
154    fn test_create_app_request_body_serialization() {
155        let body = CreateAppRequestBody {
156            name: "测试多维表格".to_string(),
157            folder_token: Some("fldcnmBA*****yGehy8".to_string()),
158            time_zone: Some("Asia/Shanghai".to_string()),
159        };
160
161        let serialized = serde_json::to_value(&body).unwrap();
162        let expected = json!({
163            "name": "测试多维表格",
164            "folder_token": "fldcnmBA*****yGehy8",
165            "time_zone": "Asia/Shanghai"
166        });
167
168        assert_eq!(serialized, expected);
169    }
170
171    #[test]
172    fn test_create_app_request_builder_default() {
173        let builder = CreateAppRequestBuilder::default();
174        let request = builder.build();
175
176        assert_eq!(request.name, "");
177        assert_eq!(request.folder_token, None);
178        assert_eq!(request.time_zone, None);
179    }
180
181    #[test]
182    fn test_create_app_request_default() {
183        let request = CreateAppRequest::default();
184
185        assert_eq!(request.name, "");
186        assert_eq!(request.folder_token, None);
187        assert_eq!(request.time_zone, None);
188    }
189
190    #[test]
191    fn test_create_app_request_minimal() {
192        let request = CreateAppRequest::builder().name("简单表格").build();
193
194        assert_eq!(request.name, "简单表格");
195        assert_eq!(request.folder_token, None);
196        assert_eq!(request.time_zone, None);
197    }
198
199    #[test]
200    fn test_create_app_request_with_folder_only() {
201        let request = CreateAppRequest::builder()
202            .name("文件夹表格")
203            .folder_token("folder123")
204            .build();
205
206        assert_eq!(request.name, "文件夹表格");
207        assert_eq!(request.folder_token, Some("folder123".to_string()));
208        assert_eq!(request.time_zone, None);
209    }
210
211    #[test]
212    fn test_create_app_request_with_timezone_only() {
213        let request = CreateAppRequest::builder()
214            .name("时区表格")
215            .time_zone("UTC")
216            .build();
217
218        assert_eq!(request.name, "时区表格");
219        assert_eq!(request.folder_token, None);
220        assert_eq!(request.time_zone, Some("UTC".to_string()));
221    }
222
223    #[test]
224    fn test_create_app_request_builder_chaining() {
225        let request = CreateAppRequest::builder()
226            .name("链式调用")
227            .folder_token("folder456")
228            .time_zone("Europe/London")
229            .name("更新名称")
230            .build();
231
232        assert_eq!(request.name, "更新名称");
233        assert_eq!(request.folder_token, Some("folder456".to_string()));
234        assert_eq!(request.time_zone, Some("Europe/London".to_string()));
235    }
236
237    #[test]
238    fn test_create_app_request_debug() {
239        let request = CreateAppRequest::builder().name("调试测试").build();
240
241        let debug_str = format!("{:?}", request);
242        assert!(debug_str.contains("CreateAppRequest"));
243        assert!(debug_str.contains("调试测试"));
244    }
245
246    #[test]
247    fn test_create_app_request_with_unicode_name() {
248        let unicode_name = "测试表格🚀📊📈";
249        let request = CreateAppRequest::builder().name(unicode_name).build();
250
251        assert_eq!(request.name, unicode_name);
252    }
253
254    #[test]
255    fn test_create_app_request_with_string_types() {
256        let owned_string = String::from("拥有字符串");
257        let request1 = CreateAppRequest::builder().name(owned_string).build();
258        assert_eq!(request1.name, "拥有字符串");
259
260        let string_ref = "引用字符串";
261        let request2 = CreateAppRequest::builder().name(string_ref).build();
262        assert_eq!(request2.name, "引用字符串");
263    }
264
265    #[test]
266    fn test_create_app_request_body_with_none_values() {
267        let body = CreateAppRequestBody {
268            name: "基础表格".to_string(),
269            folder_token: None,
270            time_zone: None,
271        };
272
273        let serialized = serde_json::to_value(&body).unwrap();
274        let expected = json!({
275            "name": "基础表格"
276        });
277
278        assert_eq!(serialized, expected);
279    }
280
281    #[test]
282    fn test_create_app_request_body_with_empty_strings() {
283        let body = CreateAppRequestBody {
284            name: "".to_string(),
285            folder_token: Some("".to_string()),
286            time_zone: Some("".to_string()),
287        };
288
289        let serialized = serde_json::to_value(&body).unwrap();
290        let expected = json!({
291            "name": "",
292            "folder_token": "",
293            "time_zone": ""
294        });
295
296        assert_eq!(serialized, expected);
297    }
298
299    #[test]
300    fn test_create_app_response_deserialization() {
301        let json = r#"{
302            "app": {
303                "app_token": "bascnmBA*****yGehy8",
304                "name": "新建多维表格",
305                "revision": 1,
306                "url": "https://example.feishu.cn/base/bascnmBA*****yGehy8"
307            }
308        }"#;
309
310        let response: CreateAppResponse = serde_json::from_str(json).unwrap();
311        assert_eq!(response.app.app_token, "bascnmBA*****yGehy8");
312        assert_eq!(response.app.name, "新建多维表格");
313        assert_eq!(response.app.revision, 1);
314        assert_eq!(
315            response.app.url,
316            "https://example.feishu.cn/base/bascnmBA*****yGehy8"
317        );
318    }
319
320    #[test]
321    fn test_create_app_response_data_debug() {
322        let app_data = CreateAppResponseData {
323            app_token: "test_token".to_string(),
324            name: "Test App".to_string(),
325            revision: 1,
326            url: "https://test.url".to_string(),
327        };
328
329        let debug_str = format!("{:?}", app_data);
330        assert!(debug_str.contains("CreateAppResponseData"));
331        assert!(debug_str.contains("test_token"));
332        assert!(debug_str.contains("Test App"));
333        assert!(debug_str.contains("https://test.url"));
334    }
335
336    #[test]
337    fn test_create_app_response_debug() {
338        let response = CreateAppResponse {
339            app: CreateAppResponseData {
340                app_token: "debug_token".to_string(),
341                name: "Debug App".to_string(),
342                revision: 2,
343                url: "https://debug.url".to_string(),
344            },
345        };
346
347        let debug_str = format!("{:?}", response);
348        assert!(debug_str.contains("CreateAppResponse"));
349        assert!(debug_str.contains("debug_token"));
350        assert!(debug_str.contains("Debug App"));
351    }
352
353    #[test]
354    fn test_create_app_response_data_format() {
355        let format = CreateAppResponse::data_format();
356        assert!(matches!(format, ResponseFormat::Data));
357    }
358
359    #[test]
360    fn test_create_app_response_with_different_revisions() {
361        let revisions = vec![0, 1, 5, 100, 999999];
362
363        for revision in revisions {
364            let json = format!(
365                r#"{{
366                    "app": {{
367                        "app_token": "test_token",
368                        "name": "Test App",
369                        "revision": {},
370                        "url": "https://test.url"
371                    }}
372                }}"#,
373                revision
374            );
375
376            let response: CreateAppResponse = serde_json::from_str(&json).unwrap();
377            assert_eq!(response.app.revision, revision);
378        }
379    }
380
381    #[test]
382    fn test_create_app_response_with_unicode_data() {
383        let json = r#"{
384            "app": {
385                "app_token": "unicode_token",
386                "name": "多维表格📊数据分析🔍",
387                "revision": 1,
388                "url": "https://飞书.cn/base/unicode_token"
389            }
390        }"#;
391
392        let response: CreateAppResponse = serde_json::from_str(json).unwrap();
393        assert_eq!(response.app.name, "多维表格📊数据分析🔍");
394        assert_eq!(response.app.url, "https://飞书.cn/base/unicode_token");
395    }
396
397    #[test]
398    fn test_create_app_request_body_various_timezones() {
399        let timezones = vec![
400            "UTC",
401            "Asia/Shanghai",
402            "America/New_York",
403            "Europe/London",
404            "Asia/Tokyo",
405            "Australia/Sydney",
406        ];
407
408        for tz in timezones {
409            let body = CreateAppRequestBody {
410                name: "时区测试".to_string(),
411                folder_token: None,
412                time_zone: Some(tz.to_string()),
413            };
414
415            let serialized = serde_json::to_value(&body).unwrap();
416            let expected = json!({
417                "name": "时区测试",
418                "time_zone": tz
419            });
420
421            assert_eq!(serialized, expected);
422        }
423    }
424
425    #[test]
426    fn test_memory_efficiency() {
427        let request = CreateAppRequest::builder().name("内存测试").build();
428
429        let size = std::mem::size_of_val(&request);
430        assert!(size > 0);
431        assert!(size < 1024);
432    }
433
434    #[test]
435    fn test_create_app_request_with_long_name() {
436        let long_name = "a".repeat(1000);
437        let request = CreateAppRequest::builder().name(&long_name).build();
438
439        assert_eq!(request.name, long_name);
440    }
441
442    #[test]
443    fn test_create_app_request_builder_method_returns() {
444        let builder = CreateAppRequest::builder().name("测试链式");
445
446        // 确保builder方法返回正确的类型
447        let _chained = builder.folder_token("folder").time_zone("UTC");
448    }
449}