open_lark/service/cloud_docs/assistant/v1/subscription/
create.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 get::{FileType, SubscriptionDetail},
19 SubscriptionService,
20};
21
22#[derive(Debug, Serialize, Default, Clone)]
24pub struct CreateSubscriptionRequest {
25 #[serde(skip)]
26 api_request: ApiRequest,
27 #[serde(skip)]
29 file_token: String,
30 #[serde(skip)]
32 file_type: String,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub config: Option<SubscriptionConfig>,
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub extra: Option<serde_json::Value>,
39}
40
41#[derive(Debug, Serialize, Deserialize, Clone)]
43pub struct SubscriptionConfig {
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub enable_notification: Option<bool>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub notification_interval: Option<i32>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub priority: Option<SubscriptionPriority>,
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub auto_renew: Option<bool>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub tags: Option<Vec<String>>,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub custom_properties: Option<serde_json::Value>,
62}
63
64#[derive(Debug, Serialize, Deserialize, Clone)]
66#[serde(rename_all = "snake_case")]
67pub enum SubscriptionPriority {
68 Low,
70 Normal,
72 High,
74 Urgent,
76}
77
78impl CreateSubscriptionRequest {
79 pub fn builder() -> CreateSubscriptionRequestBuilder {
80 CreateSubscriptionRequestBuilder::default()
81 }
82
83 pub fn new(file_token: impl ToString, file_type: FileType) -> Self {
84 Self {
85 file_token: file_token.to_string(),
86 file_type: file_type.to_string(),
87 ..Default::default()
88 }
89 }
90}
91
92#[derive(Default)]
93pub struct CreateSubscriptionRequestBuilder {
94 request: CreateSubscriptionRequest,
95}
96
97impl CreateSubscriptionRequestBuilder {
98 pub fn file_token(mut self, token: impl ToString) -> Self {
100 self.request.file_token = token.to_string();
101 self
102 }
103
104 pub fn file_type(mut self, file_type: FileType) -> Self {
106 self.request.file_type = file_type.to_string();
107 self
108 }
109
110 pub fn as_bitable(mut self) -> Self {
112 self.request.file_type = FileType::Bitable.to_string();
113 self
114 }
115
116 pub fn as_doc(mut self) -> Self {
118 self.request.file_type = FileType::Doc.to_string();
119 self
120 }
121
122 pub fn as_sheet(mut self) -> Self {
124 self.request.file_type = FileType::Sheet.to_string();
125 self
126 }
127
128 pub fn as_wiki(mut self) -> Self {
130 self.request.file_type = FileType::Wiki.to_string();
131 self
132 }
133
134 pub fn config(mut self, config: SubscriptionConfig) -> Self {
136 self.request.config = Some(config);
137 self
138 }
139
140 pub fn with_notification(mut self, enable: bool) -> Self {
142 let mut config = self.request.config.unwrap_or_default();
143 config.enable_notification = Some(enable);
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 self.request.config = Some(config);
153 self
154 }
155
156 pub fn priority(mut self, priority: SubscriptionPriority) -> Self {
158 let mut config = self.request.config.unwrap_or_default();
159 config.priority = Some(priority);
160 self.request.config = Some(config);
161 self
162 }
163
164 pub fn high_priority(self) -> Self {
166 self.priority(SubscriptionPriority::High)
167 }
168
169 pub fn low_priority(self) -> Self {
171 self.priority(SubscriptionPriority::Low)
172 }
173
174 pub fn auto_renew(mut self, enable: bool) -> Self {
176 let mut config = self.request.config.unwrap_or_default();
177 config.auto_renew = Some(enable);
178 self.request.config = Some(config);
179 self
180 }
181
182 pub fn add_tag(mut self, tag: impl ToString) -> Self {
184 let mut config = self.request.config.unwrap_or_default();
185 let mut tags = config.tags.unwrap_or_default();
186 tags.push(tag.to_string());
187 config.tags = Some(tags);
188 self.request.config = Some(config);
189 self
190 }
191
192 pub fn add_tags(mut self, tags: Vec<String>) -> Self {
194 let mut config = self.request.config.unwrap_or_default();
195 let mut existing_tags = config.tags.unwrap_or_default();
196 existing_tags.extend(tags);
197 config.tags = Some(existing_tags);
198 self.request.config = Some(config);
199 self
200 }
201
202 pub fn extra(mut self, extra: serde_json::Value) -> Self {
204 self.request.extra = Some(extra);
205 self
206 }
207
208 pub fn basic_subscription(mut self) -> Self {
210 let config = SubscriptionConfig {
211 enable_notification: Some(true),
212 notification_interval: Some(3600), priority: Some(SubscriptionPriority::Normal),
214 auto_renew: Some(false),
215 tags: Some(vec!["default".to_string()]),
216 custom_properties: None,
217 };
218 self.request.config = Some(config);
219 self
220 }
221
222 pub fn premium_subscription(mut self) -> Self {
224 let config = SubscriptionConfig {
225 enable_notification: Some(true),
226 notification_interval: Some(300), priority: Some(SubscriptionPriority::High),
228 auto_renew: Some(true),
229 tags: Some(vec!["premium".to_string(), "priority".to_string()]),
230 custom_properties: None,
231 };
232 self.request.config = Some(config);
233 self
234 }
235
236 pub fn build(mut self) -> CreateSubscriptionRequest {
237 self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
238 self.request
239 }
240}
241
242impl_executable_builder_owned!(
243 CreateSubscriptionRequestBuilder,
244 SubscriptionService,
245 CreateSubscriptionRequest,
246 CreateSubscriptionResponse,
247 create
248);
249
250#[derive(Debug, Deserialize)]
252pub struct CreateSubscriptionResponse {
253 pub subscription: SubscriptionDetail,
255 pub file_token: String,
257 pub file_type: String,
259 pub create_time: Option<i64>,
261 pub subscription_id: Option<String>,
263}
264
265impl ApiResponseTrait for CreateSubscriptionResponse {
266 fn data_format() -> ResponseFormat {
267 ResponseFormat::Data
268 }
269}
270
271pub async fn create_subscription(
273 request: CreateSubscriptionRequest,
274 config: &Config,
275 option: Option<RequestOption>,
276) -> SDKResult<BaseResponse<CreateSubscriptionResponse>> {
277 let mut api_req = request.api_request;
278 api_req.http_method = Method::POST;
279
280 api_req.api_path = format!(
281 "/open-apis/assistant/v1/file/{}/{}/subscription",
282 request.file_type, request.file_token
283 );
284
285 api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
286
287 let api_resp = Transport::request(api_req, config, option).await?;
288 Ok(api_resp)
289}
290
291impl Default for SubscriptionConfig {
292 fn default() -> Self {
293 Self {
294 enable_notification: Some(true),
295 notification_interval: Some(3600),
296 priority: Some(SubscriptionPriority::Normal),
297 auto_renew: Some(false),
298 tags: None,
299 custom_properties: None,
300 }
301 }
302}
303
304impl SubscriptionPriority {
305 pub fn description(&self) -> &'static str {
307 match self {
308 SubscriptionPriority::Low => "低优先级",
309 SubscriptionPriority::Normal => "普通优先级",
310 SubscriptionPriority::High => "高优先级",
311 SubscriptionPriority::Urgent => "紧急优先级",
312 }
313 }
314
315 pub fn value(&self) -> u8 {
317 match self {
318 SubscriptionPriority::Low => 1,
319 SubscriptionPriority::Normal => 2,
320 SubscriptionPriority::High => 3,
321 SubscriptionPriority::Urgent => 4,
322 }
323 }
324
325 pub fn color(&self) -> &'static str {
327 match self {
328 SubscriptionPriority::Low => "gray",
329 SubscriptionPriority::Normal => "blue",
330 SubscriptionPriority::High => "orange",
331 SubscriptionPriority::Urgent => "red",
332 }
333 }
334}
335
336impl SubscriptionConfig {
337 pub fn has_notification(&self) -> bool {
339 self.enable_notification.unwrap_or(false)
340 }
341
342 pub fn get_notification_interval(&self) -> i32 {
344 self.notification_interval.unwrap_or(3600)
345 }
346
347 pub fn get_notification_interval_minutes(&self) -> f64 {
349 self.get_notification_interval() as f64 / 60.0
350 }
351
352 pub fn get_notification_interval_hours(&self) -> f64 {
354 self.get_notification_interval() as f64 / 3600.0
355 }
356
357 pub fn is_high_frequency(&self) -> bool {
359 self.get_notification_interval() < 3600
360 }
361
362 pub fn get_priority(&self) -> SubscriptionPriority {
364 self.priority
365 .clone()
366 .unwrap_or(SubscriptionPriority::Normal)
367 }
368
369 pub fn has_auto_renew(&self) -> bool {
371 self.auto_renew.unwrap_or(false)
372 }
373
374 pub fn get_tags(&self) -> Vec<String> {
376 self.tags.clone().unwrap_or_default()
377 }
378
379 pub fn has_tag(&self, tag: &str) -> bool {
381 self.get_tags().contains(&tag.to_string())
382 }
383
384 pub fn summary(&self) -> String {
386 let mut parts = Vec::new();
387
388 parts.push(format!("优先级: {}", self.get_priority().description()));
389
390 if self.has_notification() {
391 let interval = self.get_notification_interval_hours();
392 if interval < 1.0 {
393 parts.push(format!("通知: 每{:.0}分钟", interval * 60.0));
394 } else {
395 parts.push(format!("通知: 每{interval:.1}小时"));
396 }
397 } else {
398 parts.push("通知: 已禁用".to_string());
399 }
400
401 if self.has_auto_renew() {
402 parts.push("自动续费: 是".to_string());
403 }
404
405 let tags = self.get_tags();
406 if !tags.is_empty() {
407 parts.push(format!("标签: {}", tags.join(", ")));
408 }
409
410 parts.join(" | ")
411 }
412}
413
414impl CreateSubscriptionResponse {
415 pub fn file_type_enum(&self) -> FileType {
417 match self.file_type.as_str() {
418 "bitable" => FileType::Bitable,
419 "doc" => FileType::Doc,
420 "sheet" => FileType::Sheet,
421 "wiki" => FileType::Wiki,
422 _ => FileType::Doc,
423 }
424 }
425
426 pub fn create_time_formatted(&self) -> Option<String> {
428 self.create_time.map(|timestamp| {
429 let datetime =
430 chrono::DateTime::from_timestamp(timestamp, 0).unwrap_or_else(chrono::Utc::now);
431 datetime.format("%Y-%m-%d %H:%M:%S").to_string()
432 })
433 }
434
435 pub fn info_summary(&self) -> String {
437 let mut parts = vec![
438 format!(
439 "{} ({})",
440 self.file_type_enum().chinese_name(),
441 self.file_token
442 ),
443 self.subscription.summary(),
444 ];
445
446 if let Some(ref subscription_id) = self.subscription_id {
447 parts.push(format!("订阅ID: {subscription_id}"));
448 }
449
450 if let Some(create_time) = self.create_time_formatted() {
451 parts.push(format!("创建时间: {create_time}"));
452 }
453
454 parts.join(" | ")
455 }
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn test_create_subscription_request_builder() {
464 let request = CreateSubscriptionRequest::builder()
465 .file_token("doccnxxxxxx")
466 .as_doc()
467 .basic_subscription()
468 .build();
469
470 assert_eq!(request.file_token, "doccnxxxxxx");
471 assert_eq!(request.file_type, "doc");
472 assert!(request.config.is_some());
473 }
474
475 #[test]
476 fn test_subscription_priority_methods() {
477 assert_eq!(SubscriptionPriority::High.description(), "高优先级");
478 assert_eq!(SubscriptionPriority::High.value(), 3);
479 assert_eq!(SubscriptionPriority::High.color(), "orange");
480 }
481
482 #[test]
483 fn test_subscription_config_methods() {
484 let config = SubscriptionConfig::default();
485
486 assert!(config.has_notification());
487 assert_eq!(config.get_notification_interval(), 3600);
488 assert_eq!(config.get_notification_interval_hours(), 1.0);
489 assert!(!config.is_high_frequency());
490 assert!(!config.has_auto_renew());
491 }
492
493 #[test]
494 fn test_subscription_config_builder() {
495 let request = CreateSubscriptionRequest::builder()
496 .file_token("test")
497 .as_doc()
498 .high_priority()
499 .notification_interval(300)
500 .auto_renew(true)
501 .add_tag("important")
502 .build();
503
504 let config = request.config.unwrap();
505 assert_eq!(config.get_priority().value(), 3);
506 assert_eq!(config.get_notification_interval(), 300);
507 assert!(config.has_auto_renew());
508 assert!(config.has_tag("important"));
509 }
510}