open_lark/service/cloud_docs/assistant/v1/subscription/
patch.rs1use 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#[derive(Debug, Serialize, Default, Clone)]
25pub struct PatchSubscriptionRequest {
26 #[serde(skip)]
27 api_request: ApiRequest,
28 #[serde(skip)]
30 file_token: String,
31 #[serde(skip)]
33 file_type: String,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub status: Option<SubscriptionStatus>,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub config: Option<SubscriptionConfig>,
40 #[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 pub fn file_token(mut self, token: impl ToString) -> Self {
68 self.request.file_token = token.to_string();
69 self
70 }
71
72 pub fn file_type(mut self, file_type: FileType) -> Self {
74 self.request.file_type = file_type.to_string();
75 self
76 }
77
78 pub fn as_bitable(mut self) -> Self {
80 self.request.file_type = FileType::Bitable.to_string();
81 self
82 }
83
84 pub fn as_doc(mut self) -> Self {
86 self.request.file_type = FileType::Doc.to_string();
87 self
88 }
89
90 pub fn as_sheet(mut self) -> Self {
92 self.request.file_type = FileType::Sheet.to_string();
93 self
94 }
95
96 pub fn as_wiki(mut self) -> Self {
98 self.request.file_type = FileType::Wiki.to_string();
99 self
100 }
101
102 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 pub fn activate(self) -> Self {
111 self.status(SubscriptionStatus::Subscribed)
112 }
113
114 pub fn pause(self) -> Self {
116 self.status(SubscriptionStatus::Paused)
117 }
118
119 pub fn cancel(self) -> Self {
121 self.status(SubscriptionStatus::Cancelled)
122 }
123
124 pub fn resume(self) -> Self {
126 self.status(SubscriptionStatus::Subscribed)
127 }
128
129 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 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 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 pub fn quick_notification(self) -> Self {
165 self.notification_interval(300)
166 }
167
168 pub fn standard_notification(self) -> Self {
170 self.notification_interval(3600)
171 }
172
173 pub fn slow_notification(self) -> Self {
175 self.notification_interval(21600)
176 }
177
178 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 pub fn high_priority(self) -> Self {
190 self.priority(SubscriptionPriority::High)
191 }
192
193 pub fn low_priority(self) -> Self {
195 self.priority(SubscriptionPriority::Low)
196 }
197
198 pub fn urgent_priority(self) -> Self {
200 self.priority(SubscriptionPriority::Urgent)
201 }
202
203 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 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 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 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 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 pub fn safe_pause(self) -> Self {
261 self.pause().notification(false).add_tag("paused_by_system")
262 }
263
264 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 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 pub fn get_changes(&self) -> Vec<String> {
284 self.changes.clone()
285 }
286
287 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#[derive(Debug, Deserialize)]
312pub struct PatchSubscriptionResponse {
313 pub subscription: SubscriptionDetail,
315 pub file_token: String,
317 pub file_type: String,
319 pub update_time: Option<i64>,
321 pub subscription_id: Option<String>,
323 pub updated_fields: Option<Vec<String>>,
325}
326
327impl ApiResponseTrait for PatchSubscriptionResponse {
328 fn data_format() -> ResponseFormat {
329 ResponseFormat::Data
330 }
331}
332
333pub 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 pub fn has_status_change(&self) -> bool {
356 self.status.is_some()
357 }
358
359 pub fn has_config_change(&self) -> bool {
361 self.config.is_some()
362 }
363
364 pub fn has_changes(&self) -> bool {
366 self.has_status_change() || self.has_config_change() || self.extra.is_some()
367 }
368
369 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 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 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 pub fn get_updated_fields(&self) -> Vec<String> {
408 self.updated_fields.clone().unwrap_or_default()
409 }
410
411 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 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}