open_lark/service/cloud_docs/comments/
list_replies.rs

1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    core::{
6        api_req::ApiRequest,
7        api_resp::{ApiResponseTrait, BaseResponse, ResponseFormat},
8        config::Config,
9        constants::AccessTokenType,
10        http::Transport,
11        req_option::RequestOption,
12        SDKResult,
13    },
14    impl_executable_builder_owned,
15};
16
17use super::list::Reply;
18
19/// 获取回复信息请求
20#[derive(Debug, Serialize, Default, Clone)]
21pub struct ListRepliesRequest {
22    #[serde(skip)]
23    api_request: ApiRequest,
24    /// 文档token
25    #[serde(skip)]
26    file_token: String,
27    /// 文档类型:doc、docx、sheet、bitable
28    #[serde(skip)]
29    file_type: String,
30    /// 评论ID
31    #[serde(skip)]
32    comment_id: String,
33    /// 分页大小
34    #[serde(skip_serializing_if = "Option::is_none")]
35    page_size: Option<i32>,
36    /// 分页标记
37    #[serde(skip_serializing_if = "Option::is_none")]
38    page_token: Option<String>,
39    /// 用户ID类型
40    #[serde(skip_serializing_if = "Option::is_none")]
41    user_id_type: Option<String>,
42}
43
44impl ListRepliesRequest {
45    pub fn builder() -> ListRepliesRequestBuilder {
46        ListRepliesRequestBuilder::default()
47    }
48
49    pub fn new(
50        file_token: impl ToString,
51        file_type: impl ToString,
52        comment_id: impl ToString,
53    ) -> Self {
54        Self {
55            file_token: file_token.to_string(),
56            file_type: file_type.to_string(),
57            comment_id: comment_id.to_string(),
58            ..Default::default()
59        }
60    }
61}
62
63#[derive(Default)]
64pub struct ListRepliesRequestBuilder {
65    request: ListRepliesRequest,
66}
67
68impl ListRepliesRequestBuilder {
69    /// 文档token
70    pub fn file_token(mut self, file_token: impl ToString) -> Self {
71        self.request.file_token = file_token.to_string();
72        self
73    }
74
75    /// 文档类型
76    pub fn file_type(mut self, file_type: impl ToString) -> Self {
77        self.request.file_type = file_type.to_string();
78        self
79    }
80
81    /// 设置为文档类型
82    pub fn with_doc_type(mut self) -> Self {
83        self.request.file_type = "doc".to_string();
84        self
85    }
86
87    /// 设置为docx类型
88    pub fn with_docx_type(mut self) -> Self {
89        self.request.file_type = "docx".to_string();
90        self
91    }
92
93    /// 设置为电子表格类型
94    pub fn with_sheet_type(mut self) -> Self {
95        self.request.file_type = "sheet".to_string();
96        self
97    }
98
99    /// 设置为多维表格类型
100    pub fn with_bitable_type(mut self) -> Self {
101        self.request.file_type = "bitable".to_string();
102        self
103    }
104
105    /// 评论ID
106    pub fn comment_id(mut self, comment_id: impl ToString) -> Self {
107        self.request.comment_id = comment_id.to_string();
108        self
109    }
110
111    /// 分页大小
112    pub fn page_size(mut self, page_size: i32) -> Self {
113        self.request.page_size = Some(page_size);
114        self
115    }
116
117    /// 分页标记
118    pub fn page_token(mut self, page_token: impl ToString) -> Self {
119        self.request.page_token = Some(page_token.to_string());
120        self
121    }
122
123    /// 用户ID类型
124    pub fn user_id_type(mut self, user_id_type: impl ToString) -> Self {
125        self.request.user_id_type = Some(user_id_type.to_string());
126        self
127    }
128
129    /// 使用OpenID
130    pub fn with_open_id(mut self) -> Self {
131        self.request.user_id_type = Some("open_id".to_string());
132        self
133    }
134
135    /// 使用UserID
136    pub fn with_user_id(mut self) -> Self {
137        self.request.user_id_type = Some("user_id".to_string());
138        self
139    }
140
141    /// 使用UnionID
142    pub fn with_union_id(mut self) -> Self {
143        self.request.user_id_type = Some("union_id".to_string());
144        self
145    }
146
147    pub fn build(mut self) -> ListRepliesRequest {
148        self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
149        self.request
150    }
151}
152
153// 应用ExecutableBuilder trait到ListRepliesRequestBuilder
154impl_executable_builder_owned!(
155    ListRepliesRequestBuilder,
156    super::CommentsService,
157    ListRepliesRequest,
158    BaseResponse<ListRepliesResponse>,
159    list_replies
160);
161
162/// 获取回复信息响应
163#[derive(Debug, Deserialize)]
164pub struct ListRepliesResponse {
165    /// 回复列表
166    pub items: Vec<Reply>,
167    /// 是否还有更多项
168    pub has_more: bool,
169    /// 分页标记
170    pub page_token: Option<String>,
171}
172
173impl ApiResponseTrait for ListRepliesResponse {
174    fn data_format() -> ResponseFormat {
175        ResponseFormat::Data
176    }
177}
178
179/// 获取回复信息
180pub async fn list_replies(
181    request: ListRepliesRequest,
182    config: &Config,
183    option: Option<RequestOption>,
184) -> SDKResult<BaseResponse<ListRepliesResponse>> {
185    let mut api_req = request.api_request;
186    api_req.http_method = Method::GET;
187    api_req.api_path = format!(
188        "/open-apis/comment/v1/comments/{}/replies?file_type={}&file_token={}",
189        request.comment_id, request.file_type, request.file_token
190    );
191
192    // 构建查询参数
193    let mut query_params = Vec::new();
194    if let Some(page_size) = request.page_size {
195        query_params.push(format!("page_size={page_size}"));
196    }
197    if let Some(page_token) = request.page_token {
198        query_params.push(format!("page_token={page_token}"));
199    }
200    if let Some(user_id_type) = request.user_id_type {
201        query_params.push(format!("user_id_type={user_id_type}"));
202    }
203
204    if !query_params.is_empty() {
205        api_req.api_path = format!("{}&{}", api_req.api_path, query_params.join("&"));
206    }
207
208    api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
209
210    let api_resp = Transport::request(api_req, config, option).await?;
211    Ok(api_resp)
212}
213
214impl ListRepliesResponse {
215    /// 获取回复数量
216    pub fn count(&self) -> usize {
217        self.items.len()
218    }
219
220    /// 是否为空
221    pub fn is_empty(&self) -> bool {
222        self.items.is_empty()
223    }
224
225    /// 根据用户ID筛选回复
226    pub fn replies_by_user(&self, user_id: &str) -> Vec<&Reply> {
227        self.items
228            .iter()
229            .filter(|reply| reply.user_id == user_id)
230            .collect()
231    }
232
233    /// 获取最新的回复
234    pub fn latest_reply(&self) -> Option<&Reply> {
235        self.items.iter().max_by_key(|reply| reply.create_time)
236    }
237
238    /// 获取最早的回复
239    pub fn earliest_reply(&self) -> Option<&Reply> {
240        self.items.iter().min_by_key(|reply| reply.create_time)
241    }
242
243    /// 按创建时间排序的回复列表
244    pub fn sorted_by_time(&self) -> Vec<&Reply> {
245        let mut replies: Vec<&Reply> = self.items.iter().collect();
246        replies.sort_by_key(|reply| reply.create_time);
247        replies
248    }
249
250    /// 按创建时间倒序排序的回复列表
251    pub fn sorted_by_time_desc(&self) -> Vec<&Reply> {
252        let mut replies: Vec<&Reply> = self.items.iter().collect();
253        replies.sort_by_key(|reply| std::cmp::Reverse(reply.create_time));
254        replies
255    }
256
257    /// 获取所有回复的文本内容
258    pub fn get_all_text_content(&self) -> Vec<String> {
259        self.items
260            .iter()
261            .map(|reply| reply.get_text_content())
262            .collect()
263    }
264
265    /// 获取回复摘要信息
266    pub fn summary(&self) -> String {
267        format!(
268            "回复总数: {}, 是否有更多: {}, 最新回复时间: {}",
269            self.count(),
270            self.has_more,
271            self.latest_reply()
272                .map(|r| r.create_time.to_string())
273                .unwrap_or_else(|| "无".to_string())
274        )
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn test_list_replies_request_builder() {
284        let request = ListRepliesRequest::builder()
285            .file_token("doccnxxxxxx")
286            .with_doc_type()
287            .comment_id("comment123")
288            .page_size(20)
289            .with_open_id()
290            .build();
291
292        assert_eq!(request.file_token, "doccnxxxxxx");
293        assert_eq!(request.file_type, "doc");
294        assert_eq!(request.comment_id, "comment123");
295        assert_eq!(request.page_size, Some(20));
296        assert_eq!(request.user_id_type, Some("open_id".to_string()));
297    }
298
299    #[test]
300    fn test_list_replies_new() {
301        let request = ListRepliesRequest::new("doccnxxxxxx", "doc", "comment123");
302        assert_eq!(request.file_token, "doccnxxxxxx");
303        assert_eq!(request.file_type, "doc");
304        assert_eq!(request.comment_id, "comment123");
305    }
306}