open_lark/service/cloud_docs/assistant/v1/subscription/
get.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 service::cloud_docs::assistant::v1::subscription::SubscriptionService,
16};
17
18#[derive(Debug, Serialize, Default, Clone)]
20pub struct GetSubscriptionRequest {
21 #[serde(skip)]
22 api_request: ApiRequest,
23 #[serde(skip)]
25 file_token: String,
26 #[serde(skip)]
28 file_type: String,
29}
30
31impl GetSubscriptionRequest {
32 pub fn builder() -> GetSubscriptionRequestBuilder {
33 GetSubscriptionRequestBuilder::default()
34 }
35
36 pub fn new(file_token: impl ToString, file_type: FileType) -> Self {
37 Self {
38 file_token: file_token.to_string(),
39 file_type: file_type.to_string(),
40 ..Default::default()
41 }
42 }
43}
44
45#[derive(Default)]
46pub struct GetSubscriptionRequestBuilder {
47 request: GetSubscriptionRequest,
48}
49
50impl GetSubscriptionRequestBuilder {
51 pub fn file_token(mut self, token: impl ToString) -> Self {
53 self.request.file_token = token.to_string();
54 self
55 }
56
57 pub fn file_type(mut self, file_type: FileType) -> Self {
59 self.request.file_type = file_type.to_string();
60 self
61 }
62
63 pub fn as_bitable(mut self) -> Self {
65 self.request.file_type = FileType::Bitable.to_string();
66 self
67 }
68
69 pub fn as_doc(mut self) -> Self {
71 self.request.file_type = FileType::Doc.to_string();
72 self
73 }
74
75 pub fn as_sheet(mut self) -> Self {
77 self.request.file_type = FileType::Sheet.to_string();
78 self
79 }
80
81 pub fn as_wiki(mut self) -> Self {
83 self.request.file_type = FileType::Wiki.to_string();
84 self
85 }
86
87 pub fn build(mut self) -> GetSubscriptionRequest {
88 self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
89 self.request
90 }
91}
92
93impl_executable_builder_owned!(
94 GetSubscriptionRequestBuilder,
95 SubscriptionService,
96 GetSubscriptionRequest,
97 GetSubscriptionResponse,
98 get
99);
100
101#[derive(Debug, Clone)]
103pub enum FileType {
104 Bitable,
106 Doc,
108 Sheet,
110 Wiki,
112}
113
114impl std::fmt::Display for FileType {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 match self {
117 FileType::Bitable => write!(f, "bitable"),
118 FileType::Doc => write!(f, "doc"),
119 FileType::Sheet => write!(f, "sheet"),
120 FileType::Wiki => write!(f, "wiki"),
121 }
122 }
123}
124
125impl FileType {
126 pub fn chinese_name(&self) -> &'static str {
128 match self {
129 FileType::Bitable => "多维表格",
130 FileType::Doc => "文档",
131 FileType::Sheet => "电子表格",
132 FileType::Wiki => "Wiki",
133 }
134 }
135
136 pub fn supports_assistant(&self) -> bool {
138 match self {
139 FileType::Bitable => true,
140 FileType::Doc => true,
141 FileType::Sheet => true,
142 FileType::Wiki => true,
143 }
144 }
145}
146
147#[derive(Debug, Serialize, Deserialize, Clone)]
149#[serde(rename_all = "snake_case")]
150pub enum SubscriptionStatus {
151 Subscribed,
153 Unsubscribed,
155 Paused,
157 Cancelled,
159 #[serde(other)]
161 Unknown,
162}
163
164impl SubscriptionStatus {
165 pub fn is_active(&self) -> bool {
167 matches!(self, SubscriptionStatus::Subscribed)
168 }
169
170 pub fn can_activate(&self) -> bool {
172 matches!(
173 self,
174 SubscriptionStatus::Unsubscribed | SubscriptionStatus::Paused
175 )
176 }
177
178 pub fn description(&self) -> &'static str {
180 match self {
181 SubscriptionStatus::Subscribed => "已订阅",
182 SubscriptionStatus::Unsubscribed => "未订阅",
183 SubscriptionStatus::Paused => "已暂停",
184 SubscriptionStatus::Cancelled => "已取消",
185 SubscriptionStatus::Unknown => "未知状态",
186 }
187 }
188
189 pub fn color(&self) -> &'static str {
191 match self {
192 SubscriptionStatus::Subscribed => "green",
193 SubscriptionStatus::Unsubscribed => "gray",
194 SubscriptionStatus::Paused => "orange",
195 SubscriptionStatus::Cancelled => "red",
196 SubscriptionStatus::Unknown => "gray",
197 }
198 }
199}
200
201#[derive(Debug, Deserialize, Clone)]
203pub struct SubscriptionDetail {
204 pub status: SubscriptionStatus,
206 pub start_time: Option<i64>,
208 pub end_time: Option<i64>,
210 pub last_update_time: Option<i64>,
212 pub subscriber_id: Option<String>,
214 pub subscriber_type: Option<String>,
216 pub config: Option<serde_json::Value>,
218 pub extra: Option<serde_json::Value>,
220}
221
222#[derive(Debug, Deserialize)]
224pub struct GetSubscriptionResponse {
225 pub subscription: SubscriptionDetail,
227 pub file_token: String,
229 pub file_type: String,
231}
232
233impl ApiResponseTrait for GetSubscriptionResponse {
234 fn data_format() -> ResponseFormat {
235 ResponseFormat::Data
236 }
237}
238
239pub async fn get_subscription(
241 request: GetSubscriptionRequest,
242 config: &Config,
243 option: Option<RequestOption>,
244) -> SDKResult<BaseResponse<GetSubscriptionResponse>> {
245 let mut api_req = request.api_request;
246 api_req.http_method = Method::GET;
247
248 api_req.api_path = format!(
249 "/open-apis/assistant/v1/file/{}/{}/subscription",
250 request.file_type, request.file_token
251 );
252
253 api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
254
255 let api_resp = Transport::request(api_req, config, option).await?;
256 Ok(api_resp)
257}
258
259impl SubscriptionDetail {
260 pub fn is_subscribed(&self) -> bool {
262 self.status.is_active()
263 }
264
265 pub fn can_activate(&self) -> bool {
267 self.status.can_activate()
268 }
269
270 pub fn duration_seconds(&self) -> Option<i64> {
272 match (self.start_time, self.end_time) {
273 (Some(start), Some(end)) => Some(end - start),
274 _ => None,
275 }
276 }
277
278 pub fn duration_days(&self) -> Option<f64> {
280 self.duration_seconds().map(|s| s as f64 / 86400.0)
281 }
282
283 pub fn start_time_formatted(&self) -> Option<String> {
285 self.start_time.map(|timestamp| {
286 let datetime =
287 chrono::DateTime::from_timestamp(timestamp, 0).unwrap_or_else(chrono::Utc::now);
288 datetime.format("%Y-%m-%d %H:%M:%S").to_string()
289 })
290 }
291
292 pub fn end_time_formatted(&self) -> Option<String> {
294 self.end_time.map(|timestamp| {
295 let datetime =
296 chrono::DateTime::from_timestamp(timestamp, 0).unwrap_or_else(chrono::Utc::now);
297 datetime.format("%Y-%m-%d %H:%M:%S").to_string()
298 })
299 }
300
301 pub fn last_update_time_formatted(&self) -> Option<String> {
303 self.last_update_time.map(|timestamp| {
304 let datetime =
305 chrono::DateTime::from_timestamp(timestamp, 0).unwrap_or_else(chrono::Utc::now);
306 datetime.format("%Y-%m-%d %H:%M:%S").to_string()
307 })
308 }
309
310 pub fn summary(&self) -> String {
312 let mut parts = Vec::new();
313
314 parts.push(format!("状态: {}", self.status.description()));
315
316 if let Some(start) = self.start_time_formatted() {
317 parts.push(format!("开始时间: {start}"));
318 }
319
320 if let Some(end) = self.end_time_formatted() {
321 parts.push(format!("结束时间: {end}"));
322 }
323
324 if let Some(days) = self.duration_days() {
325 parts.push(format!("持续时间: {days:.1} 天"));
326 }
327
328 if let Some(ref subscriber_id) = self.subscriber_id {
329 parts.push(format!("订阅者: {subscriber_id}"));
330 }
331
332 parts.join(" | ")
333 }
334}
335
336impl GetSubscriptionResponse {
337 pub fn file_type_enum(&self) -> FileType {
339 match self.file_type.as_str() {
340 "bitable" => FileType::Bitable,
341 "doc" => FileType::Doc,
342 "sheet" => FileType::Sheet,
343 "wiki" => FileType::Wiki,
344 _ => FileType::Doc, }
346 }
347
348 pub fn info_summary(&self) -> String {
350 format!(
351 "{} ({}) - {}",
352 self.file_type_enum().chinese_name(),
353 self.file_token,
354 self.subscription.summary()
355 )
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_get_subscription_request_builder() {
365 let request = GetSubscriptionRequest::builder()
366 .file_token("doccnxxxxxx")
367 .as_doc()
368 .build();
369
370 assert_eq!(request.file_token, "doccnxxxxxx");
371 assert_eq!(request.file_type, "doc");
372 }
373
374 #[test]
375 fn test_file_type_methods() {
376 assert_eq!(FileType::Doc.to_string(), "doc");
377 assert_eq!(FileType::Doc.chinese_name(), "文档");
378 assert!(FileType::Doc.supports_assistant());
379 }
380
381 #[test]
382 fn test_subscription_status_methods() {
383 assert!(SubscriptionStatus::Subscribed.is_active());
384 assert!(!SubscriptionStatus::Unsubscribed.is_active());
385 assert!(SubscriptionStatus::Unsubscribed.can_activate());
386 assert!(!SubscriptionStatus::Subscribed.can_activate());
387
388 assert_eq!(SubscriptionStatus::Subscribed.description(), "已订阅");
389 assert_eq!(SubscriptionStatus::Subscribed.color(), "green");
390 }
391
392 #[test]
393 fn test_subscription_detail_methods() {
394 let detail = SubscriptionDetail {
395 status: SubscriptionStatus::Subscribed,
396 start_time: Some(1700000000),
397 end_time: Some(1700086400),
398 last_update_time: Some(1700043200),
399 subscriber_id: Some("user123".to_string()),
400 subscriber_type: Some("user".to_string()),
401 config: None,
402 extra: None,
403 };
404
405 assert!(detail.is_subscribed());
406 assert!(!detail.can_activate());
407 assert_eq!(detail.duration_seconds(), Some(86400));
408 assert_eq!(detail.duration_days(), Some(1.0));
409 }
410}