open_lark/service/cloud_docs/assistant/v1/subscription/
patch.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::{
19    create::{SubscriptionConfig, SubscriptionPriority},
20    get::{FileType, SubscriptionDetail, SubscriptionStatus},
21    SubscriptionService,
22};
23
24/// 更新订阅状态请求
25#[derive(Debug, Serialize, Default, Clone)]
26pub struct PatchSubscriptionRequest {
27    #[serde(skip)]
28    api_request: ApiRequest,
29    /// 文档token
30    #[serde(skip)]
31    file_token: String,
32    /// 文档类型
33    #[serde(skip)]
34    file_type: String,
35    /// 订阅状态
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub status: Option<SubscriptionStatus>,
38    /// 订阅配置
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub config: Option<SubscriptionConfig>,
41    /// 扩展信息
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub extra: Option<serde_json::Value>,
44}
45
46impl PatchSubscriptionRequest {
47    pub fn builder() -> PatchSubscriptionRequestBuilder {
48        PatchSubscriptionRequestBuilder::default()
49    }
50
51    pub fn new(file_token: impl ToString, file_type: FileType) -> Self {
52        Self {
53            file_token: file_token.to_string(),
54            file_type: file_type.to_string(),
55            ..Default::default()
56        }
57    }
58}
59
60#[derive(Default)]
61pub struct PatchSubscriptionRequestBuilder {
62    request: PatchSubscriptionRequest,
63    changes: Vec<String>,
64}
65
66impl PatchSubscriptionRequestBuilder {
67    /// 文档token
68    pub fn file_token(mut self, token: impl ToString) -> Self {
69        self.request.file_token = token.to_string();
70        self
71    }
72
73    /// 文档类型
74    pub fn file_type(mut self, file_type: FileType) -> Self {
75        self.request.file_type = file_type.to_string();
76        self
77    }
78
79    /// 设置为多维表格
80    pub fn as_bitable(mut self) -> Self {
81        self.request.file_type = FileType::Bitable.to_string();
82        self
83    }
84
85    /// 设置为文档
86    pub fn as_doc(mut self) -> Self {
87        self.request.file_type = FileType::Doc.to_string();
88        self
89    }
90
91    /// 设置为表格
92    pub fn as_sheet(mut self) -> Self {
93        self.request.file_type = FileType::Sheet.to_string();
94        self
95    }
96
97    /// 设置为Wiki
98    pub fn as_wiki(mut self) -> Self {
99        self.request.file_type = FileType::Wiki.to_string();
100        self
101    }
102
103    /// 设置订阅状态
104    pub fn status(mut self, status: SubscriptionStatus) -> Self {
105        self.changes.push(format!("状态: {}", status.description()));
106        self.request.status = Some(status);
107        self
108    }
109
110    /// 激活订阅
111    pub fn activate(self) -> Self {
112        self.status(SubscriptionStatus::Subscribed)
113    }
114
115    /// 暂停订阅
116    pub fn pause(self) -> Self {
117        self.status(SubscriptionStatus::Paused)
118    }
119
120    /// 取消订阅
121    pub fn cancel(self) -> Self {
122        self.status(SubscriptionStatus::Cancelled)
123    }
124
125    /// 恢复订阅(从暂停状态)
126    pub fn resume(self) -> Self {
127        self.status(SubscriptionStatus::Subscribed)
128    }
129
130    /// 设置订阅配置
131    pub fn config(mut self, config: SubscriptionConfig) -> Self {
132        self.changes.push("配置已更新".to_string());
133        self.request.config = Some(config);
134        self
135    }
136
137    /// 启用/禁用实时通知
138    pub fn notification(mut self, enable: bool) -> Self {
139        let mut config = self.request.config.unwrap_or_default();
140        config.enable_notification = Some(enable);
141        self.changes.push(format!(
142            "通知: {}",
143            if enable { "已启用" } else { "已禁用" }
144        ));
145        self.request.config = Some(config);
146        self
147    }
148
149    /// 设置通知频率(秒)
150    pub fn notification_interval(mut self, interval: i32) -> Self {
151        let mut config = self.request.config.unwrap_or_default();
152        config.notification_interval = Some(interval.max(1));
153        let hours = interval as f64 / 3600.0;
154        if hours < 1.0 {
155            self.changes
156                .push(format!("通知频率: 每{:.0}分钟", hours * 60.0));
157        } else {
158            self.changes.push(format!("通知频率: 每{hours:.1}小时"));
159        }
160        self.request.config = Some(config);
161        self
162    }
163
164    /// 设置快速通知(每5分钟)
165    pub fn quick_notification(self) -> Self {
166        self.notification_interval(300)
167    }
168
169    /// 设置标准通知(每小时)
170    pub fn standard_notification(self) -> Self {
171        self.notification_interval(3600)
172    }
173
174    /// 设置慢速通知(每6小时)
175    pub fn slow_notification(self) -> Self {
176        self.notification_interval(21600)
177    }
178
179    /// 设置订阅优先级
180    pub fn priority(mut self, priority: SubscriptionPriority) -> Self {
181        let mut config = self.request.config.unwrap_or_default();
182        config.priority = Some(priority.clone());
183        self.changes
184            .push(format!("优先级: {}", priority.description()));
185        self.request.config = Some(config);
186        self
187    }
188
189    /// 设置为高优先级
190    pub fn high_priority(self) -> Self {
191        self.priority(SubscriptionPriority::High)
192    }
193
194    /// 设置为低优先级
195    pub fn low_priority(self) -> Self {
196        self.priority(SubscriptionPriority::Low)
197    }
198
199    /// 设置为紧急优先级
200    pub fn urgent_priority(self) -> Self {
201        self.priority(SubscriptionPriority::Urgent)
202    }
203
204    /// 启用/禁用自动续费
205    pub fn auto_renew(mut self, enable: bool) -> Self {
206        let mut config = self.request.config.unwrap_or_default();
207        config.auto_renew = Some(enable);
208        self.changes.push(format!(
209            "自动续费: {}",
210            if enable { "已启用" } else { "已禁用" }
211        ));
212        self.request.config = Some(config);
213        self
214    }
215
216    /// 添加标签
217    pub fn add_tag(mut self, tag: impl ToString) -> Self {
218        let tag_str = tag.to_string();
219        let mut config = self.request.config.unwrap_or_default();
220        let mut tags = config.tags.unwrap_or_default();
221        if !tags.contains(&tag_str) {
222            tags.push(tag_str.clone());
223            self.changes.push(format!("添加标签: {tag_str}"));
224        }
225        config.tags = Some(tags);
226        self.request.config = Some(config);
227        self
228    }
229
230    /// 移除标签
231    pub fn remove_tag(mut self, tag: impl ToString) -> Self {
232        let tag_str = tag.to_string();
233        let mut config = self.request.config.unwrap_or_default();
234        let mut tags = config.tags.unwrap_or_default();
235        if let Some(pos) = tags.iter().position(|t| t == &tag_str) {
236            tags.remove(pos);
237            self.changes.push(format!("移除标签: {tag_str}"));
238        }
239        config.tags = Some(tags);
240        self.request.config = Some(config);
241        self
242    }
243
244    /// 清空所有标签
245    pub fn clear_tags(mut self) -> Self {
246        let mut config = self.request.config.unwrap_or_default();
247        config.tags = Some(vec![]);
248        self.changes.push("清空所有标签".to_string());
249        self.request.config = Some(config);
250        self
251    }
252
253    /// 设置扩展信息
254    pub fn extra(mut self, extra: serde_json::Value) -> Self {
255        self.request.extra = Some(extra);
256        self.changes.push("扩展信息已更新".to_string());
257        self
258    }
259
260    /// 批量暂停订阅(安全模式)
261    pub fn safe_pause(self) -> Self {
262        self.pause().notification(false).add_tag("paused_by_system")
263    }
264
265    /// 批量激活订阅(快速模式)
266    pub fn quick_activate(self) -> Self {
267        self.activate()
268            .notification(true)
269            .quick_notification()
270            .high_priority()
271            .remove_tag("paused_by_system")
272    }
273
274    /// 批量激活订阅(节能模式)
275    pub fn eco_activate(self) -> Self {
276        self.activate()
277            .notification(true)
278            .slow_notification()
279            .low_priority()
280            .auto_renew(false)
281    }
282
283    /// 获取更改摘要
284    pub fn get_changes(&self) -> Vec<String> {
285        self.changes.clone()
286    }
287
288    /// 获取更改摘要字符串
289    pub fn changes_summary(&self) -> String {
290        if self.changes.is_empty() {
291            "无更改".to_string()
292        } else {
293            self.changes.join(", ")
294        }
295    }
296
297    pub fn build(mut self) -> PatchSubscriptionRequest {
298        self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
299        self.request
300    }
301}
302
303impl_executable_builder_owned!(
304    PatchSubscriptionRequestBuilder,
305    SubscriptionService,
306    PatchSubscriptionRequest,
307    PatchSubscriptionResponse,
308    patch
309);
310
311/// 更新订阅状态响应
312#[derive(Debug, Deserialize)]
313pub struct PatchSubscriptionResponse {
314    /// 订阅详情
315    pub subscription: SubscriptionDetail,
316    /// 文档token
317    pub file_token: String,
318    /// 文档类型
319    pub file_type: String,
320    /// 更新时间
321    pub update_time: Option<i64>,
322    /// 订阅ID
323    pub subscription_id: Option<String>,
324    /// 更新的字段
325    pub updated_fields: Option<Vec<String>>,
326}
327
328impl ApiResponseTrait for PatchSubscriptionResponse {
329    fn data_format() -> ResponseFormat {
330        ResponseFormat::Data
331    }
332}
333
334/// 更新订阅状态
335pub async fn patch_subscription(
336    request: PatchSubscriptionRequest,
337    config: &Config,
338    option: Option<RequestOption>,
339) -> SDKResult<BaseResponse<PatchSubscriptionResponse>> {
340    let mut api_req = request.api_request;
341    api_req.http_method = Method::PATCH;
342
343    api_req.api_path = ASSISTANT_V1_FILE_SUBSCRIPTION
344        .replace("{}", &request.file_type)
345        .replace("{}", &request.file_token);
346
347    api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
348
349    let api_resp = Transport::request(api_req, config, option).await?;
350    Ok(api_resp)
351}
352
353impl PatchSubscriptionRequest {
354    /// 是否有状态更改
355    pub fn has_status_change(&self) -> bool {
356        self.status.is_some()
357    }
358
359    /// 是否有配置更改
360    pub fn has_config_change(&self) -> bool {
361        self.config.is_some()
362    }
363
364    /// 是否有任何更改
365    pub fn has_changes(&self) -> bool {
366        self.has_status_change() || self.has_config_change() || self.extra.is_some()
367    }
368
369    /// 获取请求摘要
370    pub fn summary(&self) -> String {
371        let mut parts = vec![format!("文档: {} ({})", self.file_token, self.file_type)];
372
373        if let Some(ref status) = self.status {
374            parts.push(format!("状态: {}", status.description()));
375        }
376
377        if let Some(ref config) = self.config {
378            parts.push(format!("配置: {}", config.summary()));
379        }
380
381        parts.join(" | ")
382    }
383}
384
385impl PatchSubscriptionResponse {
386    /// 获取文档类型枚举
387    pub fn file_type_enum(&self) -> FileType {
388        match self.file_type.as_str() {
389            "bitable" => FileType::Bitable,
390            "doc" => FileType::Doc,
391            "sheet" => FileType::Sheet,
392            "wiki" => FileType::Wiki,
393            _ => FileType::Doc,
394        }
395    }
396
397    /// 获取更新时间格式化字符串
398    pub fn update_time_formatted(&self) -> Option<String> {
399        self.update_time.map(|timestamp| {
400            let datetime =
401                chrono::DateTime::from_timestamp(timestamp, 0).unwrap_or_else(chrono::Utc::now);
402            datetime.format("%Y-%m-%d %H:%M:%S").to_string()
403        })
404    }
405
406    /// 获取更新字段列表
407    pub fn get_updated_fields(&self) -> Vec<String> {
408        self.updated_fields.clone().unwrap_or_default()
409    }
410
411    /// 获取完整信息摘要
412    pub fn info_summary(&self) -> String {
413        let mut parts = vec![
414            format!(
415                "{} ({})",
416                self.file_type_enum().chinese_name(),
417                self.file_token
418            ),
419            self.subscription.summary(),
420        ];
421
422        if let Some(ref subscription_id) = self.subscription_id {
423            parts.push(format!("订阅ID: {subscription_id}"));
424        }
425
426        if let Some(update_time) = self.update_time_formatted() {
427            parts.push(format!("更新时间: {update_time}"));
428        }
429
430        let updated_fields = self.get_updated_fields();
431        if !updated_fields.is_empty() {
432            parts.push(format!("更新字段: {}", updated_fields.join(", ")));
433        }
434
435        parts.join(" | ")
436    }
437
438    /// 是否更新成功
439    pub fn is_updated(&self) -> bool {
440        !self.get_updated_fields().is_empty() || self.update_time.is_some()
441    }
442}
443
444#[cfg(test)]
445#[allow(unused_variables, unused_unsafe)]
446mod tests {
447    use super::*;
448
449    #[test]
450    fn test_patch_subscription_request_builder() {
451        let builder = PatchSubscriptionRequest::builder()
452            .file_token("doccnxxxxxx")
453            .as_doc()
454            .activate()
455            .high_priority()
456            .quick_notification()
457            .auto_renew(true)
458            .add_tag("updated");
459
460        let changes = builder.get_changes();
461        assert!(!changes.is_empty());
462        assert!(changes.iter().any(|c| c.contains("状态")));
463        assert!(changes.iter().any(|c| c.contains("优先级")));
464
465        let request = builder.build();
466        assert_eq!(request.file_token, "doccnxxxxxx");
467        assert!(request.has_status_change());
468        assert!(request.has_config_change());
469    }
470
471    #[test]
472    fn test_patch_subscription_quick_modes() {
473        let request = PatchSubscriptionRequest::builder()
474            .file_token("test")
475            .as_doc()
476            .quick_activate()
477            .build();
478
479        assert!(request.has_status_change());
480        assert!(request.has_config_change());
481
482        let config = request.config.unwrap();
483        assert!(config.has_notification());
484        assert_eq!(config.get_notification_interval(), 300);
485        assert_eq!(config.get_priority().value(), 3);
486    }
487
488    #[test]
489    fn test_patch_subscription_eco_mode() {
490        let request = PatchSubscriptionRequest::builder()
491            .file_token("test")
492            .as_doc()
493            .eco_activate()
494            .build();
495
496        let config = request.config.unwrap();
497        assert!(config.has_notification());
498        assert_eq!(config.get_notification_interval(), 21600);
499        assert_eq!(config.get_priority().value(), 1);
500        assert!(!config.has_auto_renew());
501    }
502
503    #[test]
504    fn test_patch_subscription_tag_management() {
505        let request = PatchSubscriptionRequest::builder()
506            .file_token("test")
507            .as_doc()
508            .add_tag("important")
509            .add_tag("urgent")
510            .remove_tag("old")
511            .build();
512
513        let config = request.config.unwrap();
514        let tags = config.get_tags();
515        assert!(tags.contains(&"important".to_string()));
516        assert!(tags.contains(&"urgent".to_string()));
517    }
518}