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 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#[derive(Debug, Serialize, Default, Clone)]
26pub struct PatchSubscriptionRequest {
27 #[serde(skip)]
28 api_request: ApiRequest,
29 #[serde(skip)]
31 file_token: String,
32 #[serde(skip)]
34 file_type: String,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub status: Option<SubscriptionStatus>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub config: Option<SubscriptionConfig>,
41 #[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 pub fn file_token(mut self, token: impl ToString) -> Self {
69 self.request.file_token = token.to_string();
70 self
71 }
72
73 pub fn file_type(mut self, file_type: FileType) -> Self {
75 self.request.file_type = file_type.to_string();
76 self
77 }
78
79 pub fn as_bitable(mut self) -> Self {
81 self.request.file_type = FileType::Bitable.to_string();
82 self
83 }
84
85 pub fn as_doc(mut self) -> Self {
87 self.request.file_type = FileType::Doc.to_string();
88 self
89 }
90
91 pub fn as_sheet(mut self) -> Self {
93 self.request.file_type = FileType::Sheet.to_string();
94 self
95 }
96
97 pub fn as_wiki(mut self) -> Self {
99 self.request.file_type = FileType::Wiki.to_string();
100 self
101 }
102
103 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 pub fn activate(self) -> Self {
112 self.status(SubscriptionStatus::Subscribed)
113 }
114
115 pub fn pause(self) -> Self {
117 self.status(SubscriptionStatus::Paused)
118 }
119
120 pub fn cancel(self) -> Self {
122 self.status(SubscriptionStatus::Cancelled)
123 }
124
125 pub fn resume(self) -> Self {
127 self.status(SubscriptionStatus::Subscribed)
128 }
129
130 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 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 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 pub fn quick_notification(self) -> Self {
166 self.notification_interval(300)
167 }
168
169 pub fn standard_notification(self) -> Self {
171 self.notification_interval(3600)
172 }
173
174 pub fn slow_notification(self) -> Self {
176 self.notification_interval(21600)
177 }
178
179 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 pub fn high_priority(self) -> Self {
191 self.priority(SubscriptionPriority::High)
192 }
193
194 pub fn low_priority(self) -> Self {
196 self.priority(SubscriptionPriority::Low)
197 }
198
199 pub fn urgent_priority(self) -> Self {
201 self.priority(SubscriptionPriority::Urgent)
202 }
203
204 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 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 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 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 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 pub fn safe_pause(self) -> Self {
262 self.pause().notification(false).add_tag("paused_by_system")
263 }
264
265 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 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 pub fn get_changes(&self) -> Vec<String> {
285 self.changes.clone()
286 }
287
288 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#[derive(Debug, Deserialize)]
313pub struct PatchSubscriptionResponse {
314 pub subscription: SubscriptionDetail,
316 pub file_token: String,
318 pub file_type: String,
320 pub update_time: Option<i64>,
322 pub subscription_id: Option<String>,
324 pub updated_fields: Option<Vec<String>>,
326}
327
328impl ApiResponseTrait for PatchSubscriptionResponse {
329 fn data_format() -> ResponseFormat {
330 ResponseFormat::Data
331 }
332}
333
334pub 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 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)]
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}