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