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 endpoints::cloud_docs::*,
11 http::Transport,
12 req_option::RequestOption,
13 SDKResult,
14 },
15 impl_executable_builder_owned,
16 service::cloud_docs::assistant::v1::subscription::SubscriptionService,
17};
18
19#[derive(Debug, Serialize, Default, Clone)]
21pub struct GetSubscriptionRequest {
22 #[serde(skip)]
23 api_request: ApiRequest,
24 #[serde(skip)]
26 file_token: String,
27 #[serde(skip)]
29 file_type: String,
30}
31
32impl GetSubscriptionRequest {
33 pub fn builder() -> GetSubscriptionRequestBuilder {
34 GetSubscriptionRequestBuilder::default()
35 }
36
37 pub fn new(file_token: impl ToString, file_type: FileType) -> Self {
38 Self {
39 file_token: file_token.to_string(),
40 file_type: file_type.to_string(),
41 ..Default::default()
42 }
43 }
44}
45
46#[derive(Default)]
47pub struct GetSubscriptionRequestBuilder {
48 request: GetSubscriptionRequest,
49}
50
51impl GetSubscriptionRequestBuilder {
52 pub fn file_token(mut self, token: impl ToString) -> Self {
54 self.request.file_token = token.to_string();
55 self
56 }
57
58 pub fn file_type(mut self, file_type: FileType) -> Self {
60 self.request.file_type = file_type.to_string();
61 self
62 }
63
64 pub fn as_bitable(mut self) -> Self {
66 self.request.file_type = FileType::Bitable.to_string();
67 self
68 }
69
70 pub fn as_doc(mut self) -> Self {
72 self.request.file_type = FileType::Doc.to_string();
73 self
74 }
75
76 pub fn as_sheet(mut self) -> Self {
78 self.request.file_type = FileType::Sheet.to_string();
79 self
80 }
81
82 pub fn as_wiki(mut self) -> Self {
84 self.request.file_type = FileType::Wiki.to_string();
85 self
86 }
87
88 pub fn build(mut self) -> GetSubscriptionRequest {
89 self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
90 self.request
91 }
92}
93
94impl_executable_builder_owned!(
95 GetSubscriptionRequestBuilder,
96 SubscriptionService,
97 GetSubscriptionRequest,
98 GetSubscriptionResponse,
99 get
100);
101
102#[derive(Debug, Clone)]
104pub enum FileType {
105 Bitable,
107 Doc,
109 Sheet,
111 Wiki,
113}
114
115impl std::fmt::Display for FileType {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 match self {
118 FileType::Bitable => write!(f, "bitable"),
119 FileType::Doc => write!(f, "doc"),
120 FileType::Sheet => write!(f, "sheet"),
121 FileType::Wiki => write!(f, "wiki"),
122 }
123 }
124}
125
126impl FileType {
127 pub fn chinese_name(&self) -> &'static str {
129 match self {
130 FileType::Bitable => "多维表格",
131 FileType::Doc => "文档",
132 FileType::Sheet => "电子表格",
133 FileType::Wiki => "Wiki",
134 }
135 }
136
137 pub fn supports_assistant(&self) -> bool {
139 match self {
140 FileType::Bitable => true,
141 FileType::Doc => true,
142 FileType::Sheet => true,
143 FileType::Wiki => true,
144 }
145 }
146}
147
148#[derive(Debug, Serialize, Deserialize, Clone)]
150#[serde(rename_all = "snake_case")]
151pub enum SubscriptionStatus {
152 Subscribed,
154 Unsubscribed,
156 Paused,
158 Cancelled,
160 #[serde(other)]
162 Unknown,
163}
164
165impl SubscriptionStatus {
166 pub fn is_active(&self) -> bool {
168 matches!(self, SubscriptionStatus::Subscribed)
169 }
170
171 pub fn can_activate(&self) -> bool {
173 matches!(
174 self,
175 SubscriptionStatus::Unsubscribed | SubscriptionStatus::Paused
176 )
177 }
178
179 pub fn description(&self) -> &'static str {
181 match self {
182 SubscriptionStatus::Subscribed => "已订阅",
183 SubscriptionStatus::Unsubscribed => "未订阅",
184 SubscriptionStatus::Paused => "已暂停",
185 SubscriptionStatus::Cancelled => "已取消",
186 SubscriptionStatus::Unknown => "未知状态",
187 }
188 }
189
190 pub fn color(&self) -> &'static str {
192 match self {
193 SubscriptionStatus::Subscribed => "green",
194 SubscriptionStatus::Unsubscribed => "gray",
195 SubscriptionStatus::Paused => "orange",
196 SubscriptionStatus::Cancelled => "red",
197 SubscriptionStatus::Unknown => "gray",
198 }
199 }
200}
201
202#[derive(Debug, Deserialize, Clone)]
204pub struct SubscriptionDetail {
205 pub status: SubscriptionStatus,
207 pub start_time: Option<i64>,
209 pub end_time: Option<i64>,
211 pub last_update_time: Option<i64>,
213 pub subscriber_id: Option<String>,
215 pub subscriber_type: Option<String>,
217 pub config: Option<serde_json::Value>,
219 pub extra: Option<serde_json::Value>,
221}
222
223#[derive(Debug, Deserialize)]
225pub struct GetSubscriptionResponse {
226 pub subscription: SubscriptionDetail,
228 pub file_token: String,
230 pub file_type: String,
232}
233
234impl ApiResponseTrait for GetSubscriptionResponse {
235 fn data_format() -> ResponseFormat {
236 ResponseFormat::Data
237 }
238}
239
240pub async fn get_subscription(
242 request: GetSubscriptionRequest,
243 config: &Config,
244 option: Option<RequestOption>,
245) -> SDKResult<BaseResponse<GetSubscriptionResponse>> {
246 let mut api_req = request.api_request;
247 api_req.http_method = Method::GET;
248
249 api_req.api_path = ASSISTANT_V1_FILE_SUBSCRIPTION
250 .replace("{}", &request.file_type)
251 .replace("{}", &request.file_token);
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)]
360#[allow(unused_variables, unused_unsafe)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_get_subscription_request_builder() {
366 let request = GetSubscriptionRequest::builder()
367 .file_token("doccnxxxxxx")
368 .as_doc()
369 .build();
370
371 assert_eq!(request.file_token, "doccnxxxxxx");
372 assert_eq!(request.file_type, "doc");
373 }
374
375 #[test]
376 fn test_file_type_methods() {
377 assert_eq!(FileType::Doc.to_string(), "doc");
378 assert_eq!(FileType::Doc.chinese_name(), "文档");
379 assert!(FileType::Doc.supports_assistant());
380 }
381
382 #[test]
383 fn test_subscription_status_methods() {
384 assert!(SubscriptionStatus::Subscribed.is_active());
385 assert!(!SubscriptionStatus::Unsubscribed.is_active());
386 assert!(SubscriptionStatus::Unsubscribed.can_activate());
387 assert!(!SubscriptionStatus::Subscribed.can_activate());
388
389 assert_eq!(SubscriptionStatus::Subscribed.description(), "已订阅");
390 assert_eq!(SubscriptionStatus::Subscribed.color(), "green");
391 }
392
393 #[test]
394 fn test_subscription_detail_methods() {
395 let detail = SubscriptionDetail {
396 status: SubscriptionStatus::Subscribed,
397 start_time: Some(1700000000),
398 end_time: Some(1700086400),
399 last_update_time: Some(1700043200),
400 subscriber_id: Some("user123".to_string()),
401 subscriber_type: Some("user".to_string()),
402 config: None,
403 extra: None,
404 };
405
406 assert!(detail.is_subscribed());
407 assert!(!detail.can_activate());
408 assert_eq!(detail.duration_seconds(), Some(86400));
409 assert_eq!(detail.duration_days(), Some(1.0));
410 }
411}