open_lark/service/cloud_docs/drive/v1/
file.rs

1use log::error;
2use reqwest::Method;
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    core::{
7        api_req::ApiRequest,
8        api_resp::{ApiResponseTrait, BaseResponse, ResponseFormat},
9        config::Config,
10        constants::AccessTokenType,
11        endpoints::cloud_docs::*,
12        http::Transport,
13        req_option::RequestOption,
14        standard_response::StandardResponse,
15        trait_system::Service,
16        SDKResult,
17    },
18    impl_executable_builder_owned,
19};
20
21/// 文件服务 - 处理除上传下载外的其他文件操作
22pub struct FileService {
23    config: Config,
24}
25
26impl FileService {
27    pub fn new(config: Config) -> Self {
28        Self { config }
29    }
30
31    /// 获取文件元数据
32    ///
33    /// 该接口用于根据文件token获取文件的元数据信息。
34    ///
35    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/meta/batch_query>
36    pub async fn get_file_meta(
37        &self,
38        request: GetFileMetaRequest,
39        option: Option<RequestOption>,
40    ) -> SDKResult<GetFileMetaRespData> {
41        let api_req = ApiRequest {
42            http_method: Method::POST,
43            api_path: DRIVE_V1_METAS_BATCH_QUERY.to_string(),
44            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
45            body: serde_json::to_vec(&request)?,
46            ..Default::default()
47        };
48
49        let api_resp: BaseResponse<GetFileMetaRespData> =
50            Transport::request(api_req, &self.config, option).await?;
51        api_resp.into_result()
52    }
53
54    /// 获取文件统计信息
55    ///
56    /// 该接口用于根据文件token获取文件的统计信息,如浏览次数等。
57    ///
58    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/file-statistics/get>
59    pub async fn get_file_statistics(
60        &self,
61        request: GetFileStatisticsRequest,
62        option: Option<RequestOption>,
63    ) -> SDKResult<GetFileStatisticsRespData> {
64        let api_req = ApiRequest {
65            http_method: Method::GET,
66            api_path: DRIVE_V1_FILE_STATISTICS.replace("{}", &request.file_token),
67            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
68            ..Default::default()
69        };
70
71        let api_resp: BaseResponse<GetFileStatisticsRespData> =
72            Transport::request(api_req, &self.config, option).await?;
73        api_resp.into_result()
74    }
75
76    /// 获取文件访问记录
77    ///
78    /// 该接口用于获取文件的访问记录列表。
79    ///
80    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/file-view_record/list>
81    pub async fn list_file_view_records(
82        &self,
83        request: ListFileViewRecordsRequest,
84        option: Option<RequestOption>,
85    ) -> SDKResult<ListFileViewRecordsRespData> {
86        let mut api_req = ApiRequest {
87            http_method: Method::GET,
88            api_path: DRIVE_V1_FILE_VIEW_RECORDS.replace("{}", &request.file_token),
89            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
90            ..Default::default()
91        };
92
93        // 添加查询参数
94        if let Some(page_token) = request.page_token {
95            api_req.query_params.insert("page_token", page_token);
96        }
97        if let Some(page_size) = request.page_size {
98            api_req
99                .query_params
100                .insert("page_size", page_size.to_string());
101        }
102
103        let api_resp: BaseResponse<ListFileViewRecordsRespData> =
104            Transport::request(api_req, &self.config, option).await?;
105        api_resp.into_result()
106    }
107
108    /// 新建文件
109    ///
110    /// 该接口用于在指定文件夹中新建文件。
111    ///
112    /// <https://open.feishu.cn/document/ukTMukTMukTM/uQTNzUjL0UzM14CN1MTN>
113    pub async fn create_file(
114        &self,
115        request: CreateFileRequest,
116        option: Option<RequestOption>,
117    ) -> SDKResult<CreateFileRespData> {
118        let api_req = ApiRequest {
119            http_method: Method::POST,
120            api_path: DRIVE_V1_FILES.to_string(),
121            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
122            body: serde_json::to_vec(&request)?,
123            ..Default::default()
124        };
125
126        let api_resp: BaseResponse<CreateFileRespData> =
127            Transport::request(api_req, &self.config, option).await?;
128        api_resp.into_result()
129    }
130
131    /// 复制文件
132    ///
133    /// 该接口用于复制文件到指定文件夹。
134    ///
135    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/file/copy>
136    pub async fn copy_file(
137        &self,
138        request: CopyFileRequest,
139        option: Option<RequestOption>,
140    ) -> SDKResult<CopyFileRespData> {
141        // 构建请求体
142        let body = serde_json::json!({
143            "name": request.name,
144            "type": request.copy_type,
145            "parent_token": request.parent_token
146        });
147
148        let api_req = ApiRequest {
149            http_method: Method::POST,
150            api_path: DRIVE_V1_FILE_COPY.replace("{}", &request.file_token),
151            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
152            body: serde_json::to_vec(&body)?,
153            ..Default::default()
154        };
155
156        let api_resp: BaseResponse<CopyFileRespData> =
157            Transport::request(api_req, &self.config, option).await?;
158        api_resp.into_result()
159    }
160
161    /// 删除文件
162    ///
163    /// 该接口用于删除文件。
164    ///
165    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/file/delete>
166    pub async fn delete_file(
167        &self,
168        request: DeleteFileRequest,
169        option: Option<RequestOption>,
170    ) -> SDKResult<DeleteFileRespData> {
171        let api_req = ApiRequest {
172            http_method: Method::DELETE,
173            api_path: DRIVE_V1_FILE_GET.replace("{}", &request.file_token),
174            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
175            ..Default::default()
176        };
177
178        let api_resp: BaseResponse<DeleteFileRespData> =
179            Transport::request(api_req, &self.config, option).await?;
180        api_resp.into_result()
181    }
182
183    /// 创建文件快捷方式
184    ///
185    /// 该接口用于创建文件的快捷方式。
186    ///
187    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/file/create_shortcut>
188    pub async fn create_file_shortcut(
189        &self,
190        request: CreateFileShortcutRequest,
191        option: Option<RequestOption>,
192    ) -> SDKResult<CreateFileShortcutRespData> {
193        let api_req = ApiRequest {
194            http_method: Method::POST,
195            api_path: DRIVE_V1_FILES_CREATE_SHORTCUT.to_string(),
196            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
197            body: serde_json::to_vec(&request)?,
198            ..Default::default()
199        };
200
201        let api_resp: BaseResponse<CreateFileShortcutRespData> =
202            Transport::request(api_req, &self.config, option).await?;
203        api_resp.into_result()
204    }
205
206    /// 搜索文件
207    ///
208    /// 该接口用于搜索文件。
209    ///
210    /// <https://open.feishu.cn/document/ukTMukTMukTM/ugDM4UjL4ADO14COwgTN>
211    pub async fn search_files(
212        &self,
213        request: SearchFilesRequest,
214        option: Option<RequestOption>,
215    ) -> SDKResult<SearchFilesRespData> {
216        let mut api_req = ApiRequest {
217            http_method: Method::GET,
218            api_path: DRIVE_V1_FILES_SEARCH.to_string(),
219            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
220            ..Default::default()
221        };
222
223        // 添加查询参数
224        api_req
225            .query_params
226            .insert("search_key", request.search_key);
227        if let Some(count) = request.count {
228            api_req.query_params.insert("count", count.to_string());
229        }
230        if let Some(offset) = request.offset {
231            api_req.query_params.insert("offset", offset.to_string());
232        }
233        if let Some(owner_ids) = request.owner_ids {
234            api_req
235                .query_params
236                .insert("owner_ids", owner_ids.join(","));
237        }
238
239        let api_resp: BaseResponse<SearchFilesRespData> =
240            Transport::request(api_req, &self.config, option).await?;
241        api_resp.into_result()
242    }
243
244    /// 分片上传文件-预上传
245    ///
246    /// 该接口用于分片上传的预上传步骤,获取上传事务ID和分片信息。
247    ///
248    /// <https://open.feishu.cn/document/server-docs/docs/drive-v1/upload/multipart-upload-file-/upload_prepare>
249    pub async fn upload_prepare(
250        &self,
251        request: FileUploadPrepareRequest,
252        option: Option<RequestOption>,
253    ) -> SDKResult<FileUploadPrepareRespData> {
254        let api_req = ApiRequest {
255            http_method: Method::POST,
256            api_path: DRIVE_V1_FILES_UPLOAD_PREPARE.to_string(),
257            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
258            body: serde_json::to_vec(&request)?,
259            ..Default::default()
260        };
261
262        let api_resp: BaseResponse<FileUploadPrepareRespData> =
263            Transport::request(api_req, &self.config, option).await?;
264        api_resp.into_result()
265    }
266
267    /// 分片上传文件-上传分片
268    ///
269    /// 该接口用于上传文件分片。
270    ///
271    /// <https://open.feishu.cn/document/server-docs/docs/drive-v1/upload/multipart-upload-file-/upload_part>
272    pub async fn upload_part(
273        &self,
274        request: FileUploadPartRequest,
275        option: Option<RequestOption>,
276    ) -> SDKResult<FileUploadPartRespData> {
277        let mut api_req = request.api_req;
278        api_req.http_method = Method::POST;
279        api_req.api_path = DRIVE_V1_FILES_UPLOAD_PART.to_string();
280        api_req.supported_access_token_types = vec![AccessTokenType::User, AccessTokenType::Tenant];
281
282        let api_resp: BaseResponse<FileUploadPartRespData> =
283            Transport::request(api_req, &self.config, option).await?;
284        api_resp.into_result()
285    }
286
287    /// 分片上传文件-完成上传
288    ///
289    /// 该接口用于完成分片上传。
290    ///
291    /// <https://open.feishu.cn/document/server-docs/docs/drive-v1/upload/multipart-upload-file-/upload_finish>
292    pub async fn upload_finish(
293        &self,
294        request: FileUploadFinishRequest,
295        option: Option<RequestOption>,
296    ) -> SDKResult<FileUploadFinishRespData> {
297        let api_req = ApiRequest {
298            http_method: Method::POST,
299            api_path: DRIVE_V1_FILES_UPLOAD_FINISH.to_string(),
300            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
301            body: serde_json::to_vec(&request)?,
302            ..Default::default()
303        };
304
305        let api_resp: BaseResponse<FileUploadFinishRespData> =
306            Transport::request(api_req, &self.config, option).await?;
307        api_resp.into_result()
308    }
309
310    /// 创建导入任务
311    ///
312    /// 该接口用于创建文档导入任务。
313    ///
314    /// <https://open.feishu.cn/document/server-docs/docs/drive-v1/import_task/create>
315    pub async fn create_import_task(
316        &self,
317        request: CreateImportTaskRequest,
318        option: Option<RequestOption>,
319    ) -> SDKResult<CreateImportTaskRespData> {
320        let api_req = ApiRequest {
321            http_method: Method::POST,
322            api_path: DRIVE_V1_IMPORT_TASKS.to_string(),
323            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
324            body: serde_json::to_vec(&request)?,
325            ..Default::default()
326        };
327
328        let api_resp: BaseResponse<CreateImportTaskRespData> =
329            Transport::request(api_req, &self.config, option).await?;
330        api_resp.into_result()
331    }
332
333    /// 查询导入任务结果
334    ///
335    /// 该接口用于查询导入任务的执行结果。
336    ///
337    /// <https://open.feishu.cn/document/server-docs/docs/drive-v1/import_task/get>
338    pub async fn get_import_task(
339        &self,
340        request: GetImportTaskRequest,
341        option: Option<RequestOption>,
342    ) -> SDKResult<GetImportTaskRespData> {
343        let api_req = ApiRequest {
344            http_method: Method::GET,
345            api_path: DRIVE_V1_IMPORT_TASK_GET.replace("{}", &request.ticket),
346            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
347            ..Default::default()
348        };
349
350        let api_resp: BaseResponse<GetImportTaskRespData> =
351            Transport::request(api_req, &self.config, option).await?;
352        api_resp.into_result()
353    }
354}
355
356// === 请求和响应数据结构 ===
357
358/// 获取文件元数据请求参数
359#[derive(Debug, Clone, Serialize, Deserialize)]
360pub struct GetFileMetaRequest {
361    /// 文件token列表
362    pub request_docs: Vec<RequestDoc>,
363    /// 是否获取额外信息
364    pub with_url: Option<bool>,
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct RequestDoc {
369    /// 文件token
370    pub doc_token: String,
371    /// 文件类型
372    pub doc_type: String,
373}
374
375impl GetFileMetaRequest {
376    pub fn new(docs: Vec<(String, String)>) -> Self {
377        Self {
378            request_docs: docs
379                .into_iter()
380                .map(|(token, doc_type)| RequestDoc {
381                    doc_token: token,
382                    doc_type,
383                })
384                .collect(),
385            with_url: Some(true),
386        }
387    }
388}
389
390/// 获取文件元数据响应数据
391#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
392pub struct GetFileMetaRespData {
393    /// 文件元数据列表
394    pub metas: Vec<FileMeta>,
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
398pub struct FileMeta {
399    /// 文件token
400    pub doc_token: String,
401    /// 文件类型
402    pub doc_type: String,
403    /// 文件标题
404    pub title: String,
405    /// 拥有者ID
406    pub owner_id: String,
407    /// 创建时间
408    pub create_time: String,
409    /// 更新时间
410    pub update_time: String,
411    /// 文件URL
412    pub url: Option<String>,
413}
414
415impl ApiResponseTrait for GetFileMetaRespData {
416    fn data_format() -> ResponseFormat {
417        ResponseFormat::Data
418    }
419}
420
421/// 获取文件统计信息请求参数
422#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct GetFileStatisticsRequest {
424    /// 文件token
425    pub file_token: String,
426}
427
428impl GetFileStatisticsRequest {
429    pub fn new(file_token: impl Into<String>) -> Self {
430        Self {
431            file_token: file_token.into(),
432        }
433    }
434}
435
436/// 获取文件统计信息响应数据
437#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
438pub struct GetFileStatisticsRespData {
439    /// 文件浏览次数
440    pub uv: i64,
441    /// 文件浏览人数
442    pub pv: i64,
443    /// 文件点赞数
444    pub like_count: i64,
445    /// 文件评论数
446    pub comment_count: i64,
447}
448
449impl ApiResponseTrait for GetFileStatisticsRespData {
450    fn data_format() -> ResponseFormat {
451        ResponseFormat::Data
452    }
453}
454
455/// 获取文件访问记录请求参数
456#[derive(Debug, Clone, Serialize, Deserialize)]
457pub struct ListFileViewRecordsRequest {
458    /// 文件token
459    pub file_token: String,
460    /// 分页token
461    pub page_token: Option<String>,
462    /// 分页大小
463    pub page_size: Option<i32>,
464}
465
466impl ListFileViewRecordsRequest {
467    pub fn new(file_token: impl Into<String>) -> Self {
468        Self {
469            file_token: file_token.into(),
470            page_token: None,
471            page_size: None,
472        }
473    }
474
475    pub fn with_page_token(mut self, page_token: impl Into<String>) -> Self {
476        self.page_token = Some(page_token.into());
477        self
478    }
479
480    pub fn with_page_size(mut self, page_size: i32) -> Self {
481        self.page_size = Some(page_size);
482        self
483    }
484}
485
486/// 获取文件访问记录响应数据
487#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
488pub struct ListFileViewRecordsRespData {
489    /// 是否还有更多数据
490    pub has_more: bool,
491    /// 下一页token
492    pub page_token: Option<String>,
493    /// 访问记录列表
494    pub items: Vec<FileViewRecord>,
495}
496
497#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
498pub struct FileViewRecord {
499    /// 访问者ID
500    pub viewer_id: String,
501    /// 访问者名称
502    pub viewer_name: String,
503    /// 访问时间
504    pub view_time: String,
505    /// 访问设备
506    pub view_device: String,
507}
508
509impl ApiResponseTrait for ListFileViewRecordsRespData {
510    fn data_format() -> ResponseFormat {
511        ResponseFormat::Data
512    }
513}
514
515/// 新建文件请求参数
516#[derive(Debug, Clone, Serialize, Deserialize)]
517pub struct CreateFileRequest {
518    /// 文件名称
519    pub title: String,
520    /// 文件类型
521    #[serde(rename = "type")]
522    pub file_type: String,
523    /// 父文件夹token
524    pub parent_token: String,
525}
526
527impl CreateFileRequest {
528    pub fn new(
529        title: impl Into<String>,
530        file_type: impl Into<String>,
531        parent_token: impl Into<String>,
532    ) -> Self {
533        Self {
534            title: title.into(),
535            file_type: file_type.into(),
536            parent_token: parent_token.into(),
537        }
538    }
539}
540
541/// 新建文件响应数据
542#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
543pub struct CreateFileRespData {
544    /// 新建文件的token
545    pub token: String,
546    /// 新建文件的链接
547    pub url: String,
548}
549
550impl ApiResponseTrait for CreateFileRespData {
551    fn data_format() -> ResponseFormat {
552        ResponseFormat::Data
553    }
554}
555
556/// 复制文件请求参数
557#[derive(Debug, Clone, Serialize, Deserialize)]
558pub struct CopyFileRequest {
559    /// 文件token
560    pub file_token: String,
561    /// 新文件名称
562    pub name: String,
563    /// 复制类型
564    #[serde(rename = "type")]
565    pub copy_type: String,
566    /// 目标父文件夹token
567    pub parent_token: String,
568}
569
570impl CopyFileRequest {
571    pub fn new(
572        file_token: impl Into<String>,
573        name: impl Into<String>,
574        parent_token: impl Into<String>,
575    ) -> Self {
576        Self {
577            file_token: file_token.into(),
578            name: name.into(),
579            copy_type: "copy".to_string(),
580            parent_token: parent_token.into(),
581        }
582    }
583}
584
585/// 复制文件响应数据
586#[derive(Debug, Clone, Serialize, Deserialize)]
587pub struct CopyFileRespData {
588    /// 复制后文件的token
589    pub token: String,
590    /// 复制后文件的链接
591    pub url: String,
592}
593
594impl ApiResponseTrait for CopyFileRespData {
595    fn data_format() -> ResponseFormat {
596        ResponseFormat::Data
597    }
598}
599
600/// 删除文件请求参数
601#[derive(Debug, Clone, Serialize, Deserialize)]
602pub struct DeleteFileRequest {
603    /// 文件token
604    pub file_token: String,
605}
606
607impl DeleteFileRequest {
608    pub fn new(file_token: impl Into<String>) -> Self {
609        Self {
610            file_token: file_token.into(),
611        }
612    }
613}
614
615/// 删除文件响应数据
616#[derive(Debug, Clone, Serialize, Deserialize)]
617pub struct DeleteFileRespData {
618    /// 异步任务ID
619    pub task_id: Option<String>,
620}
621
622impl ApiResponseTrait for DeleteFileRespData {
623    fn data_format() -> ResponseFormat {
624        ResponseFormat::Data
625    }
626}
627
628/// 创建文件快捷方式请求参数
629#[derive(Debug, Clone, Serialize, Deserialize)]
630pub struct CreateFileShortcutRequest {
631    /// 原文件token
632    pub refer_entity: ReferEntity,
633    /// 快捷方式名称
634    pub name: String,
635    /// 父文件夹token
636    pub parent_token: String,
637}
638
639#[derive(Debug, Clone, Serialize, Deserialize)]
640pub struct ReferEntity {
641    /// 原文件类型
642    #[serde(rename = "type")]
643    pub entity_type: String,
644    /// 原文件token
645    pub token: String,
646}
647
648impl CreateFileShortcutRequest {
649    pub fn new(
650        file_type: impl Into<String>,
651        file_token: impl Into<String>,
652        name: impl Into<String>,
653        parent_token: impl Into<String>,
654    ) -> Self {
655        Self {
656            refer_entity: ReferEntity {
657                entity_type: file_type.into(),
658                token: file_token.into(),
659            },
660            name: name.into(),
661            parent_token: parent_token.into(),
662        }
663    }
664}
665
666/// 创建文件快捷方式响应数据
667#[derive(Debug, Clone, Serialize, Deserialize)]
668pub struct CreateFileShortcutRespData {
669    /// 快捷方式token
670    pub token: String,
671    /// 快捷方式链接
672    pub url: String,
673}
674
675impl ApiResponseTrait for CreateFileShortcutRespData {
676    fn data_format() -> ResponseFormat {
677        ResponseFormat::Data
678    }
679}
680
681/// 搜索文件请求参数
682#[derive(Debug, Clone, Serialize, Deserialize)]
683pub struct SearchFilesRequest {
684    /// 搜索关键词
685    pub search_key: String,
686    /// 返回数量
687    pub count: Option<i32>,
688    /// 偏移量
689    pub offset: Option<i32>,
690    /// 所有者ID列表
691    pub owner_ids: Option<Vec<String>>,
692}
693
694impl SearchFilesRequest {
695    pub fn new(search_key: impl Into<String>) -> Self {
696        Self {
697            search_key: search_key.into(),
698            count: None,
699            offset: None,
700            owner_ids: None,
701        }
702    }
703
704    pub fn with_count(mut self, count: i32) -> Self {
705        self.count = Some(count);
706        self
707    }
708
709    pub fn with_offset(mut self, offset: i32) -> Self {
710        self.offset = Some(offset);
711        self
712    }
713
714    pub fn with_owner_ids(mut self, owner_ids: Vec<String>) -> Self {
715        self.owner_ids = Some(owner_ids);
716        self
717    }
718}
719
720/// 搜索文件响应数据
721#[derive(Debug, Clone, Serialize, Deserialize)]
722pub struct SearchFilesRespData {
723    /// 搜索结果文件列表
724    pub files: Vec<SearchFileItem>,
725}
726
727#[derive(Debug, Clone, Serialize, Deserialize)]
728pub struct SearchFileItem {
729    /// 文件token
730    pub token: String,
731    /// 文件名称
732    pub name: String,
733    /// 文件类型
734    #[serde(rename = "type")]
735    pub file_type: String,
736    /// 文件链接
737    pub url: String,
738    /// 拥有者ID
739    pub owner_id: String,
740}
741
742impl ApiResponseTrait for SearchFilesRespData {
743    fn data_format() -> ResponseFormat {
744        ResponseFormat::Data
745    }
746}
747
748/// 分片上传文件-预上传请求参数
749#[derive(Debug, Clone, Serialize, Deserialize)]
750pub struct FileUploadPrepareRequest {
751    /// 文件名称
752    pub file_name: String,
753    /// 父文件夹token
754    pub parent_token: String,
755    /// 文件大小
756    pub size: i64,
757    /// 分片大小(可选)
758    pub block_size: Option<i32>,
759    /// 文件校验和(可选)
760    pub checksum: Option<String>,
761}
762
763impl FileUploadPrepareRequest {
764    pub fn new(file_name: impl Into<String>, parent_token: impl Into<String>, size: i64) -> Self {
765        Self {
766            file_name: file_name.into(),
767            parent_token: parent_token.into(),
768            size,
769            block_size: None,
770            checksum: None,
771        }
772    }
773
774    pub fn with_block_size(mut self, block_size: i32) -> Self {
775        self.block_size = Some(block_size);
776        self
777    }
778
779    pub fn with_checksum(mut self, checksum: impl Into<String>) -> Self {
780        self.checksum = Some(checksum.into());
781        self
782    }
783}
784
785/// 分片上传文件-预上传响应数据
786#[derive(Debug, Clone, Serialize, Deserialize)]
787pub struct FileUploadPrepareRespData {
788    /// 上传事务ID
789    pub upload_id: String,
790    /// 分片大小
791    pub block_size: i32,
792    /// 分片数量
793    pub block_num: i32,
794}
795
796impl ApiResponseTrait for FileUploadPrepareRespData {
797    fn data_format() -> ResponseFormat {
798        ResponseFormat::Data
799    }
800}
801
802/// 分片上传文件-上传分片请求参数
803#[derive(Debug, Clone, Default, Serialize, Deserialize)]
804pub struct FileUploadPartRequest {
805    /// 请求体
806    #[serde(skip)]
807    pub api_req: ApiRequest,
808    /// 上传事务ID
809    upload_id: String,
810    /// 分片序号
811    seq: i32,
812    /// 分片大小
813    size: i32,
814    /// 分片校验和(可选)
815    checksum: Option<String>,
816}
817
818impl FileUploadPartRequest {
819    pub fn builder() -> FileUploadPartRequestBuilder {
820        FileUploadPartRequestBuilder::default()
821    }
822}
823
824/// 分片上传文件-上传分片请求构建器
825#[derive(Default)]
826pub struct FileUploadPartRequestBuilder {
827    request: FileUploadPartRequest,
828}
829
830impl FileUploadPartRequestBuilder {
831    pub fn upload_id(mut self, upload_id: impl Into<String>) -> Self {
832        self.request.upload_id = upload_id.into();
833        self
834    }
835
836    pub fn seq(mut self, seq: i32) -> Self {
837        self.request.seq = seq;
838        self
839    }
840
841    pub fn size(mut self, size: i32) -> Self {
842        self.request.size = size;
843        self
844    }
845
846    pub fn checksum(mut self, checksum: impl Into<String>) -> Self {
847        self.request.checksum = Some(checksum.into());
848        self
849    }
850
851    pub fn file_chunk(mut self, chunk: Vec<u8>) -> Self {
852        self.request.api_req.file = chunk;
853        self
854    }
855
856    pub fn build(mut self) -> FileUploadPartRequest {
857        match serde_json::to_vec(&self.request) {
858            Ok(bytes) => {
859                self.request.api_req.body = bytes;
860            }
861            Err(e) => {
862                error!("Failed to serialize file upload part request: {}", e);
863                self.request.api_req.body = Vec::new();
864            }
865        }
866        self.request
867    }
868}
869
870impl_executable_builder_owned!(
871    FileUploadPartRequestBuilder,
872    FileService,
873    FileUploadPartRequest,
874    FileUploadPartRespData,
875    upload_part
876);
877
878/// 分片上传文件-上传分片响应数据
879#[derive(Debug, Clone, Serialize, Deserialize)]
880pub struct FileUploadPartRespData {
881    /// 分片ETag
882    pub etag: String,
883}
884
885impl ApiResponseTrait for FileUploadPartRespData {
886    fn data_format() -> ResponseFormat {
887        ResponseFormat::Data
888    }
889}
890
891/// 分片上传文件-完成上传请求参数
892#[derive(Debug, Clone, Serialize, Deserialize)]
893pub struct FileUploadFinishRequest {
894    /// 上传事务ID
895    pub upload_id: String,
896    /// 分片信息列表
897    pub block_infos: Vec<FileBlockInfo>,
898}
899
900#[derive(Debug, Clone, Serialize, Deserialize)]
901pub struct FileBlockInfo {
902    /// 分片ETag
903    pub etag: String,
904    /// 分片序号
905    pub seq: i32,
906}
907
908impl FileUploadFinishRequest {
909    pub fn new(upload_id: impl Into<String>, block_infos: Vec<FileBlockInfo>) -> Self {
910        Self {
911            upload_id: upload_id.into(),
912            block_infos,
913        }
914    }
915}
916
917/// 分片上传文件-完成上传响应数据
918#[derive(Debug, Clone, Serialize, Deserialize)]
919pub struct FileUploadFinishRespData {
920    /// 文件token
921    pub file_token: String,
922}
923
924impl ApiResponseTrait for FileUploadFinishRespData {
925    fn data_format() -> ResponseFormat {
926        ResponseFormat::Data
927    }
928}
929
930/// 创建导入任务请求参数
931#[derive(Debug, Clone, Serialize, Deserialize)]
932pub struct CreateImportTaskRequest {
933    /// 导入文件的token
934    pub file_extension: String,
935    /// 导入文件类型
936    pub file_token: String,
937    /// 导入文件类型
938    #[serde(rename = "type")]
939    pub import_type: String,
940    /// 导入的目标文件夹token
941    pub parent_token: String,
942    /// 导入的文件名称
943    pub file_name: String,
944    /// 导入点类型
945    pub parent_type: String,
946}
947
948impl CreateImportTaskRequest {
949    pub fn new(
950        file_extension: impl Into<String>,
951        file_token: impl Into<String>,
952        import_type: impl Into<String>,
953        parent_token: impl Into<String>,
954        file_name: impl Into<String>,
955        parent_type: impl Into<String>,
956    ) -> Self {
957        Self {
958            file_extension: file_extension.into(),
959            file_token: file_token.into(),
960            import_type: import_type.into(),
961            parent_token: parent_token.into(),
962            file_name: file_name.into(),
963            parent_type: parent_type.into(),
964        }
965    }
966}
967
968/// 创建导入任务响应数据
969#[derive(Debug, Clone, Serialize, Deserialize)]
970pub struct CreateImportTaskRespData {
971    /// 导入任务ID
972    pub ticket: String,
973}
974
975impl ApiResponseTrait for CreateImportTaskRespData {
976    fn data_format() -> ResponseFormat {
977        ResponseFormat::Data
978    }
979}
980
981/// 查询导入任务结果请求参数
982#[derive(Debug, Clone, Serialize, Deserialize)]
983pub struct GetImportTaskRequest {
984    /// 导入任务ID
985    pub ticket: String,
986}
987
988impl GetImportTaskRequest {
989    pub fn new(ticket: impl Into<String>) -> Self {
990        Self {
991            ticket: ticket.into(),
992        }
993    }
994}
995
996/// 查询导入任务结果响应数据
997#[derive(Debug, Clone, Serialize, Deserialize)]
998pub struct GetImportTaskRespData {
999    /// 任务结果
1000    pub result: ImportTaskResult,
1001}
1002
1003#[derive(Debug, Clone, Serialize, Deserialize)]
1004pub struct ImportTaskResult {
1005    /// 任务类型
1006    #[serde(rename = "type")]
1007    pub task_type: String,
1008    /// 任务ID
1009    pub ticket: String,
1010    /// 任务状态
1011    pub job_status: i32,
1012    /// 任务错误码
1013    pub job_error_msg: Option<String>,
1014    /// 导入结果
1015    pub token: Option<String>,
1016    /// 导入结果类型
1017    pub url: Option<String>,
1018}
1019
1020impl ApiResponseTrait for GetImportTaskRespData {
1021    fn data_format() -> ResponseFormat {
1022        ResponseFormat::Data
1023    }
1024}
1025
1026impl Service for FileService {
1027    fn config(&self) -> &Config {
1028        &self.config
1029    }
1030
1031    fn service_name() -> &'static str {
1032        "file"
1033    }
1034
1035    fn service_version() -> &'static str {
1036        "v1"
1037    }
1038}
1039
1040#[cfg(test)]
1041mod tests {
1042    use super::*;
1043    use crate::core::api_resp::ResponseFormat;
1044    use rstest::rstest;
1045
1046    fn create_test_config() -> Config {
1047        Config::builder()
1048            .app_id("test_app_id")
1049            .app_secret("test_app_secret")
1050            .build()
1051    }
1052
1053    fn create_test_service() -> FileService {
1054        FileService::new(create_test_config())
1055    }
1056
1057    #[test]
1058    fn test_file_service_new() {
1059        let config = create_test_config();
1060        let service = FileService::new(config.clone());
1061
1062        assert_eq!(service.config.app_id, config.app_id);
1063        assert_eq!(FileService::service_name(), "file");
1064        assert_eq!(FileService::service_version(), "v1");
1065    }
1066
1067    #[test]
1068    fn test_service_trait_implementation() {
1069        let service = create_test_service();
1070        assert_eq!(service.config().app_id, "test_app_id");
1071        assert_eq!(FileService::service_name(), "file");
1072        assert_eq!(FileService::service_version(), "v1");
1073    }
1074
1075    // === Request/Response Data Structure Tests ===
1076
1077    #[test]
1078    fn test_get_file_meta_request() {
1079        let docs = vec![
1080            ("file_token_1".to_string(), "doc".to_string()),
1081            ("file_token_2".to_string(), "sheet".to_string()),
1082        ];
1083        let request = GetFileMetaRequest::new(docs.clone());
1084
1085        assert_eq!(request.request_docs.len(), 2);
1086        assert_eq!(request.request_docs[0].doc_token, "file_token_1");
1087        assert_eq!(request.request_docs[0].doc_type, "doc");
1088        assert_eq!(request.request_docs[1].doc_token, "file_token_2");
1089        assert_eq!(request.request_docs[1].doc_type, "sheet");
1090        assert_eq!(request.with_url, Some(true));
1091    }
1092
1093    #[test]
1094    fn test_get_file_meta_request_serialization() {
1095        let docs = vec![("test_token".to_string(), "doc".to_string())];
1096        let request = GetFileMetaRequest::new(docs);
1097
1098        let json = serde_json::to_string(&request).unwrap();
1099        assert!(json.contains("request_docs"));
1100        assert!(json.contains("with_url"));
1101        assert!(json.contains("test_token"));
1102        assert!(json.contains("doc"));
1103
1104        let deserialized: GetFileMetaRequest = serde_json::from_str(&json).unwrap();
1105        assert_eq!(deserialized.request_docs.len(), 1);
1106        assert_eq!(deserialized.request_docs[0].doc_token, "test_token");
1107    }
1108
1109    #[test]
1110    fn test_get_file_meta_resp_data_api_response_trait() {
1111        assert_eq!(GetFileMetaRespData::data_format(), ResponseFormat::Data);
1112    }
1113
1114    #[test]
1115    fn test_get_file_statistics_request() {
1116        let request = GetFileStatisticsRequest::new("test_file_token");
1117        assert_eq!(request.file_token, "test_file_token");
1118
1119        let request2 = GetFileStatisticsRequest::new("another_token".to_string());
1120        assert_eq!(request2.file_token, "another_token");
1121    }
1122
1123    #[test]
1124    fn test_get_file_statistics_resp_data() {
1125        let resp_data = GetFileStatisticsRespData {
1126            uv: 100,
1127            pv: 250,
1128            like_count: 15,
1129            comment_count: 8,
1130        };
1131
1132        assert_eq!(resp_data.uv, 100);
1133        assert_eq!(resp_data.pv, 250);
1134        assert_eq!(resp_data.like_count, 15);
1135        assert_eq!(resp_data.comment_count, 8);
1136        assert_eq!(
1137            GetFileStatisticsRespData::data_format(),
1138            ResponseFormat::Data
1139        );
1140
1141        // Test serialization
1142        let json = serde_json::to_string(&resp_data).unwrap();
1143        let deserialized: GetFileStatisticsRespData = serde_json::from_str(&json).unwrap();
1144        assert_eq!(deserialized.uv, resp_data.uv);
1145        assert_eq!(deserialized.pv, resp_data.pv);
1146    }
1147
1148    #[test]
1149    fn test_list_file_view_records_request_builder() {
1150        let request = ListFileViewRecordsRequest::new("test_token")
1151            .with_page_token("next_page")
1152            .with_page_size(20);
1153
1154        assert_eq!(request.file_token, "test_token");
1155        assert_eq!(request.page_token, Some("next_page".to_string()));
1156        assert_eq!(request.page_size, Some(20));
1157    }
1158
1159    #[test]
1160    fn test_list_file_view_records_request_minimal() {
1161        let request = ListFileViewRecordsRequest::new("minimal_token");
1162        assert_eq!(request.file_token, "minimal_token");
1163        assert_eq!(request.page_token, None);
1164        assert_eq!(request.page_size, None);
1165    }
1166
1167    #[test]
1168    fn test_list_file_view_records_resp_data() {
1169        let records = vec![
1170            FileViewRecord {
1171                viewer_id: "user1".to_string(),
1172                viewer_name: "John Doe".to_string(),
1173                view_time: "2023-12-01T10:00:00Z".to_string(),
1174                view_device: "web".to_string(),
1175            },
1176            FileViewRecord {
1177                viewer_id: "user2".to_string(),
1178                viewer_name: "Jane Smith".to_string(),
1179                view_time: "2023-12-01T11:00:00Z".to_string(),
1180                view_device: "mobile".to_string(),
1181            },
1182        ];
1183
1184        let resp_data = ListFileViewRecordsRespData {
1185            has_more: true,
1186            page_token: Some("next_token".to_string()),
1187            items: records.clone(),
1188        };
1189
1190        assert!(resp_data.has_more);
1191        assert_eq!(resp_data.page_token, Some("next_token".to_string()));
1192        assert_eq!(resp_data.items.len(), 2);
1193        assert_eq!(resp_data.items[0].viewer_name, "John Doe");
1194        assert_eq!(resp_data.items[1].view_device, "mobile");
1195        assert_eq!(
1196            ListFileViewRecordsRespData::data_format(),
1197            ResponseFormat::Data
1198        );
1199    }
1200
1201    #[test]
1202    fn test_create_file_request() {
1203        let request = CreateFileRequest::new("My Document", "doc", "parent_folder_token");
1204
1205        assert_eq!(request.title, "My Document");
1206        assert_eq!(request.file_type, "doc");
1207        assert_eq!(request.parent_token, "parent_folder_token");
1208    }
1209
1210    #[test]
1211    fn test_create_file_request_serialization() {
1212        let request = CreateFileRequest::new("Test File", "sheet", "folder123");
1213        let json = serde_json::to_string(&request).unwrap();
1214
1215        assert!(json.contains("Test File"));
1216        assert!(json.contains("\"type\":\"sheet\""));
1217        assert!(json.contains("parent_token"));
1218
1219        let deserialized: CreateFileRequest = serde_json::from_str(&json).unwrap();
1220        assert_eq!(deserialized.title, "Test File");
1221        assert_eq!(deserialized.file_type, "sheet");
1222    }
1223
1224    #[test]
1225    fn test_copy_file_request() {
1226        let request = CopyFileRequest::new("source_token", "Copy of Document", "target_folder");
1227
1228        assert_eq!(request.file_token, "source_token");
1229        assert_eq!(request.name, "Copy of Document");
1230        assert_eq!(request.copy_type, "copy");
1231        assert_eq!(request.parent_token, "target_folder");
1232    }
1233
1234    #[test]
1235    fn test_delete_file_request() {
1236        let request = DeleteFileRequest::new("file_to_delete");
1237        assert_eq!(request.file_token, "file_to_delete");
1238
1239        let request2 = DeleteFileRequest::new("another_file".to_string());
1240        assert_eq!(request2.file_token, "another_file");
1241    }
1242
1243    #[test]
1244    fn test_create_file_shortcut_request() {
1245        let request = CreateFileShortcutRequest::new(
1246            "doc",
1247            "original_file_token",
1248            "Shortcut to Document",
1249            "shortcut_folder",
1250        );
1251
1252        assert_eq!(request.refer_entity.entity_type, "doc");
1253        assert_eq!(request.refer_entity.token, "original_file_token");
1254        assert_eq!(request.name, "Shortcut to Document");
1255        assert_eq!(request.parent_token, "shortcut_folder");
1256    }
1257
1258    #[test]
1259    fn test_search_files_request_builder() {
1260        let request = SearchFilesRequest::new("important documents")
1261            .with_count(50)
1262            .with_offset(100)
1263            .with_owner_ids(vec!["user1".to_string(), "user2".to_string()]);
1264
1265        assert_eq!(request.search_key, "important documents");
1266        assert_eq!(request.count, Some(50));
1267        assert_eq!(request.offset, Some(100));
1268        assert_eq!(
1269            request.owner_ids,
1270            Some(vec!["user1".to_string(), "user2".to_string()])
1271        );
1272    }
1273
1274    #[test]
1275    fn test_search_files_request_minimal() {
1276        let request = SearchFilesRequest::new("test");
1277        assert_eq!(request.search_key, "test");
1278        assert_eq!(request.count, None);
1279        assert_eq!(request.offset, None);
1280        assert_eq!(request.owner_ids, None);
1281    }
1282
1283    #[test]
1284    fn test_file_upload_prepare_request() {
1285        let request = FileUploadPrepareRequest::new("document.pdf", "upload_folder", 1024000)
1286            .with_block_size(4096)
1287            .with_checksum("sha256:abcdef123456");
1288
1289        assert_eq!(request.file_name, "document.pdf");
1290        assert_eq!(request.parent_token, "upload_folder");
1291        assert_eq!(request.size, 1024000);
1292        assert_eq!(request.block_size, Some(4096));
1293        assert_eq!(request.checksum, Some("sha256:abcdef123456".to_string()));
1294    }
1295
1296    #[test]
1297    fn test_file_upload_prepare_request_minimal() {
1298        let request = FileUploadPrepareRequest::new("simple.txt", "folder", 500);
1299        assert_eq!(request.file_name, "simple.txt");
1300        assert_eq!(request.size, 500);
1301        assert_eq!(request.block_size, None);
1302        assert_eq!(request.checksum, None);
1303    }
1304
1305    #[test]
1306    fn test_file_upload_part_request_builder() {
1307        let test_chunk = vec![1, 2, 3, 4, 5];
1308        let request = FileUploadPartRequest::builder()
1309            .upload_id("upload_123")
1310            .seq(1)
1311            .size(5)
1312            .checksum("chunk_checksum")
1313            .file_chunk(test_chunk.clone())
1314            .build();
1315
1316        assert_eq!(request.upload_id, "upload_123");
1317        assert_eq!(request.seq, 1);
1318        assert_eq!(request.size, 5);
1319        assert_eq!(request.checksum, Some("chunk_checksum".to_string()));
1320        assert_eq!(request.api_req.file, test_chunk);
1321    }
1322
1323    #[test]
1324    fn test_file_upload_part_request_builder_minimal() {
1325        let request = FileUploadPartRequest::builder()
1326            .upload_id("minimal_upload")
1327            .seq(0)
1328            .size(100)
1329            .build();
1330
1331        assert_eq!(request.upload_id, "minimal_upload");
1332        assert_eq!(request.seq, 0);
1333        assert_eq!(request.size, 100);
1334        assert_eq!(request.checksum, None);
1335    }
1336
1337    #[test]
1338    fn test_file_upload_finish_request() {
1339        let block_infos = vec![
1340            FileBlockInfo {
1341                etag: "etag1".to_string(),
1342                seq: 1,
1343            },
1344            FileBlockInfo {
1345                etag: "etag2".to_string(),
1346                seq: 2,
1347            },
1348        ];
1349        let request = FileUploadFinishRequest::new("upload_123", block_infos.clone());
1350
1351        assert_eq!(request.upload_id, "upload_123");
1352        assert_eq!(request.block_infos.len(), 2);
1353        assert_eq!(request.block_infos[0].etag, "etag1");
1354        assert_eq!(request.block_infos[1].seq, 2);
1355    }
1356
1357    #[test]
1358    fn test_create_import_task_request() {
1359        let request = CreateImportTaskRequest::new(
1360            "pdf",
1361            "source_file_token",
1362            "import_type_doc",
1363            "target_folder",
1364            "imported_document.docx",
1365            "folder",
1366        );
1367
1368        assert_eq!(request.file_extension, "pdf");
1369        assert_eq!(request.file_token, "source_file_token");
1370        assert_eq!(request.import_type, "import_type_doc");
1371        assert_eq!(request.parent_token, "target_folder");
1372        assert_eq!(request.file_name, "imported_document.docx");
1373        assert_eq!(request.parent_type, "folder");
1374    }
1375
1376    #[test]
1377    fn test_get_import_task_request() {
1378        let request = GetImportTaskRequest::new("task_ticket_123");
1379        assert_eq!(request.ticket, "task_ticket_123");
1380
1381        let request2 = GetImportTaskRequest::new("another_ticket".to_string());
1382        assert_eq!(request2.ticket, "another_ticket");
1383    }
1384
1385    // === Serialization/Deserialization Tests ===
1386
1387    #[rstest]
1388    #[case(GetFileMetaRespData { metas: vec![] })]
1389    #[case(GetFileStatisticsRespData { uv: 0, pv: 0, like_count: 0, comment_count: 0 })]
1390    #[case(ListFileViewRecordsRespData { has_more: false, page_token: None, items: vec![] })]
1391    #[case(CreateFileRespData { token: "test".to_string(), url: "http://test.com".to_string() })]
1392    fn test_response_data_serialization<T>(#[case] data: T)
1393    where
1394        T: serde::Serialize + for<'de> serde::Deserialize<'de> + PartialEq + std::fmt::Debug,
1395    {
1396        let json = serde_json::to_string(&data).unwrap();
1397        let deserialized: T = serde_json::from_str(&json).unwrap();
1398        assert_eq!(data, deserialized);
1399    }
1400
1401    #[test]
1402    fn test_file_meta_serialization() {
1403        let file_meta = FileMeta {
1404            doc_token: "test_token".to_string(),
1405            doc_type: "doc".to_string(),
1406            title: "Test Document".to_string(),
1407            owner_id: "owner123".to_string(),
1408            create_time: "2023-01-01T00:00:00Z".to_string(),
1409            update_time: "2023-01-02T00:00:00Z".to_string(),
1410            url: Some("https://example.com/doc".to_string()),
1411        };
1412
1413        let json = serde_json::to_string(&file_meta).unwrap();
1414        let deserialized: FileMeta = serde_json::from_str(&json).unwrap();
1415
1416        assert_eq!(deserialized.doc_token, file_meta.doc_token);
1417        assert_eq!(deserialized.title, file_meta.title);
1418        assert_eq!(deserialized.url, file_meta.url);
1419    }
1420
1421    #[test]
1422    fn test_search_file_item_serialization() {
1423        let item = SearchFileItem {
1424            token: "file_token".to_string(),
1425            name: "Important File".to_string(),
1426            file_type: "doc".to_string(),
1427            url: "https://example.com/file".to_string(),
1428            owner_id: "user123".to_string(),
1429        };
1430
1431        let json = serde_json::to_string(&item).unwrap();
1432        assert!(json.contains("\"type\":\"doc\""));
1433
1434        let deserialized: SearchFileItem = serde_json::from_str(&json).unwrap();
1435        assert_eq!(deserialized.file_type, "doc");
1436        assert_eq!(deserialized.name, "Important File");
1437    }
1438
1439    #[test]
1440    fn test_import_task_result_serialization() {
1441        let result = ImportTaskResult {
1442            task_type: "import".to_string(),
1443            ticket: "task_123".to_string(),
1444            job_status: 1,
1445            job_error_msg: Some("Error occurred".to_string()),
1446            token: Some("result_token".to_string()),
1447            url: Some("https://result.com".to_string()),
1448        };
1449
1450        let json = serde_json::to_string(&result).unwrap();
1451        assert!(json.contains("\"type\":\"import\""));
1452
1453        let deserialized: ImportTaskResult = serde_json::from_str(&json).unwrap();
1454        assert_eq!(deserialized.task_type, "import");
1455        assert_eq!(deserialized.job_status, 1);
1456        assert_eq!(
1457            deserialized.job_error_msg,
1458            Some("Error occurred".to_string())
1459        );
1460    }
1461
1462    // === Edge Cases and Error Handling Tests ===
1463
1464    #[test]
1465    fn test_empty_file_meta_request() {
1466        let request = GetFileMetaRequest::new(vec![]);
1467        assert_eq!(request.request_docs.len(), 0);
1468        assert_eq!(request.with_url, Some(true));
1469    }
1470
1471    #[test]
1472    fn test_large_file_upload_prepare() {
1473        let large_size = i64::MAX;
1474        let request = FileUploadPrepareRequest::new("huge_file.dat", "folder", large_size);
1475        assert_eq!(request.size, large_size);
1476    }
1477
1478    #[test]
1479    fn test_file_upload_part_zero_size() {
1480        let request = FileUploadPartRequest::builder()
1481            .upload_id("test")
1482            .seq(0)
1483            .size(0)
1484            .build();
1485        assert_eq!(request.size, 0);
1486    }
1487
1488    #[test]
1489    fn test_search_files_empty_search_key() {
1490        let request = SearchFilesRequest::new("");
1491        assert_eq!(request.search_key, "");
1492    }
1493
1494    #[test]
1495    fn test_search_files_negative_values() {
1496        let request = SearchFilesRequest::new("test")
1497            .with_count(-1)
1498            .with_offset(-10);
1499        assert_eq!(request.count, Some(-1));
1500        assert_eq!(request.offset, Some(-10));
1501    }
1502
1503    #[test]
1504    fn test_list_file_view_records_empty_response() {
1505        let resp_data = ListFileViewRecordsRespData {
1506            has_more: false,
1507            page_token: None,
1508            items: vec![],
1509        };
1510        assert!(!resp_data.has_more);
1511        assert_eq!(resp_data.items.len(), 0);
1512    }
1513
1514    #[test]
1515    fn test_file_upload_finish_empty_blocks() {
1516        let request = FileUploadFinishRequest::new("upload_id", vec![]);
1517        assert_eq!(request.block_infos.len(), 0);
1518    }
1519
1520    // === API Response Trait Tests ===
1521
1522    #[rstest]
1523    #[case(GetFileMetaRespData::data_format(), ResponseFormat::Data)]
1524    #[case(GetFileStatisticsRespData::data_format(), ResponseFormat::Data)]
1525    #[case(ListFileViewRecordsRespData::data_format(), ResponseFormat::Data)]
1526    #[case(CreateFileRespData::data_format(), ResponseFormat::Data)]
1527    #[case(CopyFileRespData::data_format(), ResponseFormat::Data)]
1528    #[case(DeleteFileRespData::data_format(), ResponseFormat::Data)]
1529    #[case(CreateFileShortcutRespData::data_format(), ResponseFormat::Data)]
1530    #[case(SearchFilesRespData::data_format(), ResponseFormat::Data)]
1531    #[case(FileUploadPrepareRespData::data_format(), ResponseFormat::Data)]
1532    #[case(FileUploadPartRespData::data_format(), ResponseFormat::Data)]
1533    #[case(FileUploadFinishRespData::data_format(), ResponseFormat::Data)]
1534    #[case(CreateImportTaskRespData::data_format(), ResponseFormat::Data)]
1535    #[case(GetImportTaskRespData::data_format(), ResponseFormat::Data)]
1536    fn test_api_response_trait_format(
1537        #[case] actual: ResponseFormat,
1538        #[case] expected: ResponseFormat,
1539    ) {
1540        assert_eq!(actual, expected);
1541    }
1542
1543    // === Builder Pattern Tests ===
1544
1545    #[test]
1546    fn test_list_file_view_records_builder_chain() {
1547        let request = ListFileViewRecordsRequest::new("token")
1548            .with_page_token("page1")
1549            .with_page_size(25)
1550            .with_page_token("page2"); // Override previous page_token
1551
1552        assert_eq!(request.page_token, Some("page2".to_string()));
1553        assert_eq!(request.page_size, Some(25));
1554    }
1555
1556    #[test]
1557    fn test_search_files_builder_chain() {
1558        let owners = vec![
1559            "user1".to_string(),
1560            "user2".to_string(),
1561            "user3".to_string(),
1562        ];
1563        let request = SearchFilesRequest::new("documents")
1564            .with_count(100)
1565            .with_offset(50)
1566            .with_owner_ids(owners.clone())
1567            .with_count(200); // Override previous count
1568
1569        assert_eq!(request.count, Some(200));
1570        assert_eq!(request.offset, Some(50));
1571        assert_eq!(request.owner_ids, Some(owners));
1572    }
1573
1574    #[test]
1575    fn test_file_upload_prepare_builder_chain() {
1576        let request = FileUploadPrepareRequest::new("file.dat", "folder", 1000)
1577            .with_block_size(512)
1578            .with_checksum("checksum1")
1579            .with_block_size(1024) // Override
1580            .with_checksum("checksum2"); // Override
1581
1582        assert_eq!(request.block_size, Some(1024));
1583        assert_eq!(request.checksum, Some("checksum2".to_string()));
1584    }
1585
1586    // === Unicode and Special Character Tests ===
1587
1588    #[test]
1589    fn test_unicode_file_names() {
1590        let request = CreateFileRequest::new("文档测试🚀", "doc", "folder");
1591        assert_eq!(request.title, "文档测试🚀");
1592
1593        let json = serde_json::to_string(&request).unwrap();
1594        let deserialized: CreateFileRequest = serde_json::from_str(&json).unwrap();
1595        assert_eq!(deserialized.title, "文档测试🚀");
1596    }
1597
1598    #[test]
1599    fn test_special_characters_in_search() {
1600        let request = SearchFilesRequest::new("file@#$%^&*()[]{}");
1601        assert_eq!(request.search_key, "file@#$%^&*()[]{}");
1602    }
1603
1604    #[test]
1605    fn test_long_file_names() {
1606        let long_name = "a".repeat(1000);
1607        let request = CreateFileRequest::new(&long_name, "doc", "folder");
1608        assert_eq!(request.title.len(), 1000);
1609    }
1610
1611    // === Default and Clone Tests ===
1612
1613    #[test]
1614    fn test_file_upload_part_request_default() {
1615        let request = FileUploadPartRequest::default();
1616        assert_eq!(request.upload_id, "");
1617        assert_eq!(request.seq, 0);
1618        assert_eq!(request.size, 0);
1619        assert_eq!(request.checksum, None);
1620    }
1621
1622    #[test]
1623    fn test_request_cloning() {
1624        let original = CreateFileRequest::new("Original", "doc", "folder");
1625        let cloned = original.clone();
1626
1627        assert_eq!(original.title, cloned.title);
1628        assert_eq!(original.file_type, cloned.file_type);
1629        assert_eq!(original.parent_token, cloned.parent_token);
1630    }
1631
1632    // === Error Serialization Tests ===
1633
1634    #[test]
1635    fn test_file_upload_part_builder_serialization_error() {
1636        // Create a request that might cause serialization issues
1637        let request = FileUploadPartRequest::builder()
1638            .upload_id("test")
1639            .seq(1)
1640            .size(0)
1641            .build();
1642
1643        // Even with potential serialization error, the request should be built
1644        assert_eq!(request.upload_id, "test");
1645        assert_eq!(request.seq, 1);
1646    }
1647
1648    #[test]
1649    fn test_api_response_trait_consistency() {
1650        // Ensure all response types consistently use Data format
1651        let formats = vec![
1652            GetFileMetaRespData::data_format(),
1653            GetFileStatisticsRespData::data_format(),
1654            CreateFileRespData::data_format(),
1655            DeleteFileRespData::data_format(),
1656        ];
1657
1658        for format in formats {
1659            assert_eq!(format, ResponseFormat::Data);
1660        }
1661    }
1662}