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