open_lark/service/cloud_docs/comments/
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        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::{ContentElement, ReplyContent, TextRun};
18
19/// 添加全文评论请求
20#[derive(Debug, Serialize, Default, Clone)]
21pub struct CreateCommentRequest {
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    /// 回复内容
31    content: ReplyContent,
32    /// 用户ID类型
33    #[serde(skip_serializing_if = "Option::is_none")]
34    user_id_type: Option<String>,
35}
36
37impl CreateCommentRequest {
38    pub fn builder() -> CreateCommentRequestBuilder {
39        CreateCommentRequestBuilder::default()
40    }
41
42    pub fn new(file_token: impl ToString, file_type: impl ToString, content: ReplyContent) -> Self {
43        Self {
44            file_token: file_token.to_string(),
45            file_type: file_type.to_string(),
46            content,
47            ..Default::default()
48        }
49    }
50
51    /// 创建简单文本评论
52    pub fn with_text(
53        file_token: impl ToString,
54        file_type: impl ToString,
55        text: impl ToString,
56    ) -> Self {
57        let content = ReplyContent {
58            elements: vec![ContentElement {
59                element_type: "text_run".to_string(),
60                text_run: Some(TextRun {
61                    text: text.to_string(),
62                    style: None,
63                }),
64            }],
65        };
66
67        Self::new(file_token, file_type, content)
68    }
69}
70
71#[derive(Default)]
72pub struct CreateCommentRequestBuilder {
73    request: CreateCommentRequest,
74}
75
76impl CreateCommentRequestBuilder {
77    /// 文档token
78    pub fn file_token(mut self, file_token: impl ToString) -> Self {
79        self.request.file_token = file_token.to_string();
80        self
81    }
82
83    /// 文档类型
84    pub fn file_type(mut self, file_type: impl ToString) -> Self {
85        self.request.file_type = file_type.to_string();
86        self
87    }
88
89    /// 设置为文档类型
90    pub fn with_doc_type(mut self) -> Self {
91        self.request.file_type = "doc".to_string();
92        self
93    }
94
95    /// 设置为docx类型
96    pub fn with_docx_type(mut self) -> Self {
97        self.request.file_type = "docx".to_string();
98        self
99    }
100
101    /// 设置为电子表格类型
102    pub fn with_sheet_type(mut self) -> Self {
103        self.request.file_type = "sheet".to_string();
104        self
105    }
106
107    /// 设置为多维表格类型
108    pub fn with_bitable_type(mut self) -> Self {
109        self.request.file_type = "bitable".to_string();
110        self
111    }
112
113    /// 回复内容
114    pub fn content(mut self, content: ReplyContent) -> Self {
115        self.request.content = content;
116        self
117    }
118
119    /// 添加文本内容
120    pub fn text(mut self, text: impl ToString) -> Self {
121        let element = ContentElement {
122            element_type: "text_run".to_string(),
123            text_run: Some(TextRun {
124                text: text.to_string(),
125                style: None,
126            }),
127        };
128        self.request.content.elements.push(element);
129        self
130    }
131
132    /// 添加带样式的文本内容
133    pub fn styled_text(mut self, text: impl ToString, style: serde_json::Value) -> Self {
134        let element = ContentElement {
135            element_type: "text_run".to_string(),
136            text_run: Some(TextRun {
137                text: text.to_string(),
138                style: Some(style),
139            }),
140        };
141        self.request.content.elements.push(element);
142        self
143    }
144
145    /// 添加粗体文本
146    pub fn bold_text(self, text: impl ToString) -> Self {
147        let style = serde_json::json!({
148            "bold": true
149        });
150        self.styled_text(text, style)
151    }
152
153    /// 添加斜体文本
154    pub fn italic_text(self, text: impl ToString) -> Self {
155        let style = serde_json::json!({
156            "italic": true
157        });
158        self.styled_text(text, style)
159    }
160
161    /// 用户ID类型
162    pub fn user_id_type(mut self, user_id_type: impl ToString) -> Self {
163        self.request.user_id_type = Some(user_id_type.to_string());
164        self
165    }
166
167    /// 使用OpenID
168    pub fn with_open_id(mut self) -> Self {
169        self.request.user_id_type = Some("open_id".to_string());
170        self
171    }
172
173    /// 使用UserID
174    pub fn with_user_id(mut self) -> Self {
175        self.request.user_id_type = Some("user_id".to_string());
176        self
177    }
178
179    /// 使用UnionID
180    pub fn with_union_id(mut self) -> Self {
181        self.request.user_id_type = Some("union_id".to_string());
182        self
183    }
184
185    pub fn build(mut self) -> CreateCommentRequest {
186        self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
187        self.request
188    }
189}
190
191/// 创建的评论信息
192#[derive(Debug, Deserialize)]
193pub struct CreatedComment {
194    /// 评论ID
195    pub comment_id: String,
196    /// 用户ID
197    pub user_id: String,
198    /// 创建时间(毫秒时间戳)
199    pub create_time: i64,
200    /// 更新时间(毫秒时间戳)
201    pub update_time: i64,
202    /// 是否解决
203    pub is_solved: bool,
204    /// 是否是全文评论
205    pub is_whole: Option<bool>,
206}
207
208// 应用ExecutableBuilder trait到CreateCommentRequestBuilder
209impl_executable_builder_owned!(
210    CreateCommentRequestBuilder,
211    super::CommentsService,
212    CreateCommentRequest,
213    BaseResponse<CreateCommentResponse>,
214    create
215);
216
217/// 添加全文评论响应
218#[derive(Debug, Deserialize)]
219pub struct CreateCommentResponse {
220    /// 创建的评论信息
221    pub comment: CreatedComment,
222}
223
224impl ApiResponseTrait for CreateCommentResponse {
225    fn data_format() -> ResponseFormat {
226        ResponseFormat::Data
227    }
228}
229
230/// 添加全文评论
231///
232/// <https://open.feishu.cn/document/server-docs/docs/comment/create>
233pub async fn create_comment(
234    request: CreateCommentRequest,
235    config: &Config,
236    option: Option<RequestOption>,
237) -> SDKResult<BaseResponse<CreateCommentResponse>> {
238    let mut api_req = request.api_request;
239    api_req.http_method = Method::POST;
240    api_req.api_path = format!(
241        "/open-apis/comment/v1/comments?file_type={}&file_token={}",
242        request.file_type, request.file_token
243    );
244
245    // 添加用户ID类型查询参数
246    if let Some(user_id_type) = request.user_id_type {
247        api_req.api_path = format!("{}&user_id_type={}", api_req.api_path, user_id_type);
248    }
249
250    api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
251
252    let api_resp = Transport::request(api_req, config, option).await?;
253    Ok(api_resp)
254}
255
256impl CreatedComment {
257    /// 是否为全文评论
258    pub fn is_whole_comment(&self) -> bool {
259        self.is_whole.unwrap_or(false)
260    }
261
262    /// 获取创建时间的格式化字符串
263    pub fn created_at_formatted(&self) -> String {
264        format!("创建时间: {}", self.create_time)
265    }
266
267    /// 是否已解决
268    pub fn is_solved(&self) -> bool {
269        self.is_solved
270    }
271}
272
273/// 内容构建器,用于构建复杂的评论内容
274pub struct ContentBuilder {
275    elements: Vec<ContentElement>,
276}
277
278impl ContentBuilder {
279    pub fn new() -> Self {
280        Self {
281            elements: Vec::new(),
282        }
283    }
284
285    /// 添加普通文本
286    pub fn add_text(mut self, text: impl ToString) -> Self {
287        self.elements.push(ContentElement {
288            element_type: "text_run".to_string(),
289            text_run: Some(TextRun {
290                text: text.to_string(),
291                style: None,
292            }),
293        });
294        self
295    }
296
297    /// 添加带样式的文本
298    pub fn add_styled_text(mut self, text: impl ToString, style: serde_json::Value) -> Self {
299        self.elements.push(ContentElement {
300            element_type: "text_run".to_string(),
301            text_run: Some(TextRun {
302                text: text.to_string(),
303                style: Some(style),
304            }),
305        });
306        self
307    }
308
309    /// 添加粗体文本
310    pub fn add_bold(self, text: impl ToString) -> Self {
311        let style = serde_json::json!({ "bold": true });
312        self.add_styled_text(text, style)
313    }
314
315    /// 添加斜体文本
316    pub fn add_italic(self, text: impl ToString) -> Self {
317        let style = serde_json::json!({ "italic": true });
318        self.add_styled_text(text, style)
319    }
320
321    /// 添加下划线文本
322    pub fn add_underline(self, text: impl ToString) -> Self {
323        let style = serde_json::json!({ "underline": true });
324        self.add_styled_text(text, style)
325    }
326
327    /// 构建回复内容
328    pub fn build(self) -> ReplyContent {
329        ReplyContent {
330            elements: self.elements,
331        }
332    }
333}
334
335impl Default for ContentBuilder {
336    fn default() -> Self {
337        Self::new()
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    #[test]
346    fn test_create_comment_request_builder() {
347        let request = CreateCommentRequest::builder()
348            .file_token("doccnxxxxxx")
349            .with_doc_type()
350            .text("这是一条评论")
351            .bold_text("重要内容")
352            .with_open_id()
353            .build();
354
355        assert_eq!(request.file_token, "doccnxxxxxx");
356        assert_eq!(request.file_type, "doc");
357        assert_eq!(request.content.elements.len(), 2);
358        assert_eq!(request.user_id_type, Some("open_id".to_string()));
359    }
360
361    #[test]
362    fn test_create_comment_with_text() {
363        let request = CreateCommentRequest::with_text("doccnxxxxxx", "doc", "简单评论");
364        assert_eq!(request.file_token, "doccnxxxxxx");
365        assert_eq!(request.file_type, "doc");
366        assert_eq!(request.content.elements.len(), 1);
367    }
368
369    #[test]
370    fn test_content_builder() {
371        let content = ContentBuilder::new()
372            .add_text("普通文本 ")
373            .add_bold("粗体文本 ")
374            .add_italic("斜体文本")
375            .build();
376
377        assert_eq!(content.elements.len(), 3);
378        assert_eq!(content.elements[0].element_type, "text_run");
379    }
380}