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 endpoints::cloud_docs::*,
11 http::Transport,
12 req_option::RequestOption,
13 SDKResult,
14 },
15 impl_executable_builder_owned,
16};
17
18use super::{
19 get::{FileType, SubscriptionDetail},
20 SubscriptionService,
21};
22
23#[derive(Debug, Serialize, Default, Clone)]
25pub struct CreateSubscriptionRequest {
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 config: Option<SubscriptionConfig>,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub extra: Option<serde_json::Value>,
40}
41
42#[derive(Debug, Serialize, Deserialize, Clone, Default)]
44pub struct SubscriptionConfig {
45 pub enable_notification: Option<bool>,
46 pub notification_interval: Option<i32>,
47 pub priority: Option<SubscriptionPriority>,
48 pub auto_renew: Option<bool>,
49 pub tags: Option<Vec<String>>,
50}
51
52#[derive(Debug, Serialize, Deserialize, Clone)]
54#[serde(rename_all = "snake_case")]
55pub enum SubscriptionPriority {
56 Low,
58 Normal,
60 High,
62 Urgent,
64}
65
66impl CreateSubscriptionRequest {
67 pub fn builder() -> CreateSubscriptionRequestBuilder {
68 CreateSubscriptionRequestBuilder::default()
69 }
70
71 pub fn new(file_token: impl ToString, file_type: FileType) -> Self {
72 Self {
73 file_token: file_token.to_string(),
74 file_type: file_type.to_string(),
75 ..Default::default()
76 }
77 }
78}
79
80#[derive(Default)]
81pub struct CreateSubscriptionRequestBuilder {
82 request: CreateSubscriptionRequest,
83}
84
85impl CreateSubscriptionRequestBuilder {
86 pub fn file_token(mut self, token: impl ToString) -> Self {
88 self.request.file_token = token.to_string();
89 self
90 }
91
92 pub fn file_type(mut self, file_type: FileType) -> Self {
94 self.request.file_type = file_type.to_string();
95 self
96 }
97
98 pub fn as_bitable(mut self) -> Self {
100 self.request.file_type = FileType::Bitable.to_string();
101 self
102 }
103
104 pub fn as_doc(mut self) -> Self {
106 self.request.file_type = FileType::Doc.to_string();
107 self
108 }
109
110 pub fn as_sheet(mut self) -> Self {
112 self.request.file_type = FileType::Sheet.to_string();
113 self
114 }
115
116 pub fn as_wiki(mut self) -> Self {
118 self.request.file_type = FileType::Wiki.to_string();
119 self
120 }
121
122 pub fn config(mut self, config: SubscriptionConfig) -> Self {
124 self.request.config = Some(config);
125 self
126 }
127
128 pub fn with_notification(mut self, enable: bool) -> Self {
130 let mut config = self.request.config.unwrap_or_default();
131 config.enable_notification = Some(enable);
132 self.request.config = Some(config);
133 self
134 }
135
136 pub fn notification_interval(mut self, interval: i32) -> Self {
138 let mut config = self.request.config.unwrap_or_default();
139 config.notification_interval = Some(interval.max(1));
140 self.request.config = Some(config);
141 self
142 }
143
144 pub fn priority(mut self, priority: SubscriptionPriority) -> Self {
146 let mut config = self.request.config.unwrap_or_default();
147 config.priority = Some(priority);
148 self.request.config = Some(config);
149 self
150 }
151
152 pub fn high_priority(self) -> Self {
154 self.priority(SubscriptionPriority::High)
155 }
156
157 pub fn low_priority(self) -> Self {
159 self.priority(SubscriptionPriority::Low)
160 }
161
162 pub fn auto_renew(mut self, enable: bool) -> Self {
164 let mut config = self.request.config.unwrap_or_default();
165 config.auto_renew = Some(enable);
166 self.request.config = Some(config);
167 self
168 }
169
170 pub fn add_tag(mut self, tag: impl ToString) -> Self {
172 let mut config = self.request.config.unwrap_or_default();
173 let mut tags = config.tags.unwrap_or_default();
174 tags.push(tag.to_string());
175 config.tags = Some(tags);
176 self.request.config = Some(config);
177 self
178 }
179
180 pub fn add_tags(mut self, tags: Vec<String>) -> Self {
182 let mut config = self.request.config.unwrap_or_default();
183 let mut existing_tags = config.tags.unwrap_or_default();
184 existing_tags.extend(tags);
185 config.tags = Some(existing_tags);
186 self.request.config = Some(config);
187 self
188 }
189
190 pub fn extra(mut self, extra: serde_json::Value) -> Self {
192 self.request.extra = Some(extra);
193 self
194 }
195
196 pub fn basic_subscription(mut self) -> Self {
198 let config = SubscriptionConfig::default();
199 self.request.config = Some(config);
200 self
201 }
202
203 pub fn premium_subscription(mut self) -> Self {
205 let config = SubscriptionConfig::default();
206 self.request.config = Some(config);
207 self
208 }
209
210 pub fn build(mut self) -> CreateSubscriptionRequest {
211 self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
212 self.request
213 }
214}
215
216impl_executable_builder_owned!(
217 CreateSubscriptionRequestBuilder,
218 SubscriptionService,
219 CreateSubscriptionRequest,
220 CreateSubscriptionResponse,
221 create
222);
223
224#[derive(Debug, Deserialize)]
226pub struct CreateSubscriptionResponse {
227 pub subscription: SubscriptionDetail,
229 pub file_token: String,
231 pub file_type: String,
233 pub create_time: Option<i64>,
235 pub subscription_id: Option<String>,
237}
238
239impl ApiResponseTrait for CreateSubscriptionResponse {
240 fn data_format() -> ResponseFormat {
241 ResponseFormat::Data
242 }
243}
244
245pub async fn create_subscription(
247 request: CreateSubscriptionRequest,
248 config: &Config,
249 option: Option<RequestOption>,
250) -> SDKResult<BaseResponse<CreateSubscriptionResponse>> {
251 let mut api_req = request.api_request;
252 api_req.http_method = Method::POST;
253
254 api_req.api_path = ASSISTANT_V1_FILE_SUBSCRIPTION
255 .replace("{}", &request.file_type)
256 .replace("{}", &request.file_token);
257
258 api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
259
260 let api_resp = Transport::request(api_req, config, option).await?;
261 Ok(api_resp)
262}
263
264impl SubscriptionPriority {
265 pub fn description(&self) -> &'static str {
267 match self {
268 SubscriptionPriority::Low => "低优先级",
269 SubscriptionPriority::Normal => "普通优先级",
270 SubscriptionPriority::High => "高优先级",
271 SubscriptionPriority::Urgent => "紧急优先级",
272 }
273 }
274
275 pub fn value(&self) -> u8 {
277 match self {
278 SubscriptionPriority::Low => 1,
279 SubscriptionPriority::Normal => 2,
280 SubscriptionPriority::High => 3,
281 SubscriptionPriority::Urgent => 4,
282 }
283 }
284
285 pub fn color(&self) -> &'static str {
287 match self {
288 SubscriptionPriority::Low => "gray",
289 SubscriptionPriority::Normal => "blue",
290 SubscriptionPriority::High => "orange",
291 SubscriptionPriority::Urgent => "red",
292 }
293 }
294}
295
296impl SubscriptionConfig {
297 pub fn get_notification_interval(&self) -> i32 {
299 self.notification_interval.unwrap_or(3600)
300 }
301
302 pub fn get_notification_interval_minutes(&self) -> f64 {
304 self.get_notification_interval() as f64 / 60.0
305 }
306
307 pub fn get_notification_interval_hours(&self) -> f64 {
309 self.get_notification_interval() as f64 / 3600.0
310 }
311
312 pub fn is_high_frequency(&self) -> bool {
314 self.get_notification_interval() < 3600
315 }
316
317 pub fn has_notification(&self) -> bool {
319 self.enable_notification.unwrap_or(false)
320 }
321
322 pub fn get_priority(&self) -> SubscriptionPriority {
324 self.priority
325 .clone()
326 .unwrap_or(SubscriptionPriority::Normal)
327 }
328
329 pub fn has_auto_renew(&self) -> bool {
331 self.auto_renew.unwrap_or(false)
332 }
333
334 pub fn get_tags(&self) -> Vec<String> {
336 self.tags.clone().unwrap_or_default()
337 }
338
339 pub fn has_tag(&self, tag: &str) -> bool {
341 self.get_tags().contains(&tag.to_string())
342 }
343
344 pub fn summary(&self) -> String {
346 let mut parts = Vec::new();
347
348 parts.push(format!("优先级: {}", self.get_priority().description()));
349
350 if self.has_notification() {
351 let interval = self.get_notification_interval_hours();
352 if interval < 1.0 {
353 parts.push(format!("通知: 每{:.0}分钟", interval * 60.0));
354 } else {
355 parts.push(format!("通知: 每{interval:.1}小时"));
356 }
357 } else {
358 parts.push("通知: 已禁用".to_string());
359 }
360
361 if self.has_auto_renew() {
362 parts.push("自动续费: 是".to_string());
363 }
364
365 let tags = self.get_tags();
366 if !tags.is_empty() {
367 parts.push(format!("标签: {}", tags.join(", ")));
368 }
369
370 parts.join(" | ")
371 }
372}
373
374impl CreateSubscriptionResponse {
375 pub fn file_type_enum(&self) -> FileType {
377 match self.file_type.as_str() {
378 "bitable" => FileType::Bitable,
379 "doc" => FileType::Doc,
380 "sheet" => FileType::Sheet,
381 "wiki" => FileType::Wiki,
382 _ => FileType::Doc,
383 }
384 }
385
386 pub fn create_time_formatted(&self) -> Option<String> {
388 self.create_time.map(|timestamp| {
389 let datetime =
390 chrono::DateTime::from_timestamp(timestamp, 0).unwrap_or_else(chrono::Utc::now);
391 datetime.format("%Y-%m-%d %H:%M:%S").to_string()
392 })
393 }
394
395 pub fn info_summary(&self) -> String {
397 let mut parts = vec![
398 format!(
399 "{} ({})",
400 self.file_type_enum().chinese_name(),
401 self.file_token
402 ),
403 self.subscription.summary(),
404 ];
405
406 if let Some(ref subscription_id) = self.subscription_id {
407 parts.push(format!("订阅ID: {subscription_id}"));
408 }
409
410 if let Some(create_time) = self.create_time_formatted() {
411 parts.push(format!("创建时间: {create_time}"));
412 }
413
414 parts.join(" | ")
415 }
416}
417
418#[cfg(test)]
419#[allow(unused_variables, unused_unsafe)]
420mod tests {
421 use super::*;
422
423 #[test]
424 fn test_create_subscription_request_builder() {
425 let request = CreateSubscriptionRequest::builder()
426 .file_token("doccnxxxxxx")
427 .as_doc()
428 .basic_subscription()
429 .build();
430
431 assert_eq!(request.file_token, "doccnxxxxxx");
432 assert_eq!(request.file_type, "doc");
433 assert!(request.config.is_some());
434 }
435
436 #[test]
437 fn test_subscription_priority_methods() {
438 assert_eq!(SubscriptionPriority::High.description(), "高优先级");
439 assert_eq!(SubscriptionPriority::High.value(), 3);
440 assert_eq!(SubscriptionPriority::High.color(), "orange");
441 }
442
443 #[test]
444 fn test_subscription_config_methods() {
445 let config = SubscriptionConfig {
446 enable_notification: Some(true),
447 notification_interval: Some(3600),
448 ..Default::default()
449 };
450
451 assert!(config.has_notification());
452 assert_eq!(config.get_notification_interval(), 3600);
453 assert_eq!(config.get_notification_interval_hours(), 1.0);
454 assert!(!config.is_high_frequency());
455 assert!(!config.has_auto_renew());
456 }
457
458 #[test]
459 fn test_subscription_config_builder() {
460 let request = CreateSubscriptionRequest::builder()
461 .file_token("test")
462 .as_doc()
463 .high_priority()
464 .notification_interval(300)
465 .auto_renew(true)
466 .add_tag("important")
467 .build();
468
469 let config = request.config.unwrap();
470 assert_eq!(config.get_priority().value(), 3);
471 assert_eq!(config.get_notification_interval(), 300);
472 assert!(config.has_auto_renew());
473 assert!(config.has_tag("important"));
474 }
475}