Skip to main content

openlark_docs/common/
api_endpoints.rs

1//! API端点定义(类型安全枚举系统)
2//!
3//! 本模块提供基于枚举的 API 端点定义,用于生产代码中的类型安全调用。
4//!
5//! # 使用场景
6//!
7//! ## 生产代码(推荐)
8//! 使用枚举端点获得编译时类型检查和动态 URL 生成能力:
9//! ```rust
10//! use openlark_docs::common::api_endpoints::BitableApiV1;
11//!
12//! let app_token = "app_token".to_string();
13//! let table_id = "table_id".to_string();
14//! let endpoint = BitableApiV1::RecordCreate(app_token, table_id);
15//! let url = endpoint.to_url(); // 类型安全,动态生成
16//! assert!(url.contains("/open-apis/bitable/v1/"));
17//! ```
18//!
19//! # 特性
20//! - ✅ **类型安全**:编译时验证参数
21//! - ✅ **动态生成**:支持参数化 URL
22//! - ✅ **易于维护**:集中管理端点定义
23//! - ✅ **避免错误**:消除字符串拼接错误
24//!
25//! # 与常量端点系统的关系
26//!
27//! 本模块与 `endpoints/mod.rs` 中的常量端点系统配合使用:
28//! - **枚举端点**:用于生产代码(推荐)
29//! - **常量端点**:用于测试和文档示例
30//!
31//! 不建议混合使用两个系统,应根据场景选择合适的端点方式。
32
33use openlark_core::api::{ApiRequest, HttpMethod};
34use openlark_core::constants::AccessTokenType;
35
36/// 端点 catalog 的通用语义接口(#424 / #438)。
37/// 允许 to_request 等逻辑共享,减少重复。
38pub trait CatalogEndpoint {
39    /// 返回端点 URL。
40    fn to_url(&self) -> String;
41
42    /// 返回 HTTP 方法。
43    fn method(&self) -> HttpMethod;
44
45    /// 稳定的访问令牌要求。
46    /// Docs 域的绝大多数端点都同时接受 User 和 Tenant token(#424 系列)。
47    fn supported_access_token_types(&self) -> Option<Vec<AccessTokenType>> {
48        Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
49    }
50
51    /// 构建带正确方法的请求。
52    fn to_request<R>(&self) -> ApiRequest<R> {
53        self.to_request_with_url(self.to_url())
54    }
55
56    /// 使用调用方补充了动态 query 的 URL 构建请求,同时保留 catalog 的 method/auth 语义。
57    fn to_request_with_url<R>(&self, url: impl Into<String>) -> ApiRequest<R> {
58        let url = url.into();
59        let mut req = match self.method() {
60            HttpMethod::Get => ApiRequest::get(url),
61            HttpMethod::Post => ApiRequest::post(url),
62            HttpMethod::Put => ApiRequest::put(url),
63            HttpMethod::Delete => ApiRequest::delete(url),
64            HttpMethod::Patch => ApiRequest::patch(url),
65            _ => unreachable!(
66                "CatalogEndpoint encountered unknown HttpMethod variant — this should never happen"
67            ),
68        };
69        if let Some(tokens) = self.supported_access_token_types() {
70            req = req.with_supported_access_token_types(tokens);
71        }
72        req
73    }
74}
75
76#[cfg(test)]
77pub(crate) mod test_support {
78    use super::CatalogEndpoint;
79    use openlark_core::{
80        api::{ApiRequest, HttpMethod},
81        constants::AccessTokenType,
82    };
83    use std::fmt::Debug;
84    use strum::IntoEnumIterator;
85
86    /// 同时锁定 catalog 的 path、method、auth 以及生成的请求语义。
87    pub(crate) fn assert_endpoint_semantics<E>(
88        endpoint: E,
89        expected_method: HttpMethod,
90        expected_path: &str,
91    ) where
92        E: CatalogEndpoint,
93    {
94        assert_eq!(endpoint.to_url(), expected_path);
95        assert_eq!(endpoint.method(), expected_method);
96        assert_eq!(
97            endpoint.supported_access_token_types(),
98            Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
99        );
100
101        let request: ApiRequest<()> = endpoint.to_request();
102        assert_eq!(request.method(), &expected_method);
103        assert_eq!(request.api_path(), expected_path);
104        assert_eq!(
105            request.supported_access_token_types(),
106            vec![AccessTokenType::User, AccessTokenType::Tenant]
107        );
108    }
109
110    /// 枚举全部 catalog variant,锁定其 method/path/auth 以及生成请求的一致性。
111    pub(crate) fn catalog_semantics_snapshot<E>() -> String
112    where
113        E: CatalogEndpoint + IntoEnumIterator + Debug,
114    {
115        E::iter()
116            .map(|endpoint| {
117                let path = endpoint.to_url();
118                let method = endpoint.method();
119                let auth = endpoint.supported_access_token_types();
120                let request: ApiRequest<()> = endpoint.to_request();
121
122                assert_eq!(request.method(), &method);
123                assert_eq!(request.api_path(), path);
124                assert_eq!(
125                    request.supported_access_token_types(),
126                    auth.clone().unwrap_or_default()
127                );
128
129                format!("{endpoint:?} | {method:?} | {path} | {auth:?}")
130            })
131            .collect::<Vec<_>>()
132            .join("\n")
133    }
134}
135
136pub mod base;
137pub use base::BaseApiV2;
138
139pub mod bitable;
140pub use bitable::BitableApiV1;
141
142pub mod docs;
143pub use docs::DocsApiV1;
144
145pub mod docx;
146pub use docx::DocxApiV1;
147
148/// CCM Doc API Old V1 端点枚举
149/// 对应 meta.project = ccm_doc, meta.version = old
150#[derive(Debug, Clone, PartialEq)]
151#[cfg_attr(test, derive(strum_macros::EnumIter))]
152pub enum CcmDocApiOld {
153    /// 创建旧版文档
154    Create,
155    /// 获取旧版文档元信息
156    Meta(String), // doc_token
157    /// 获取旧版文档中的电子表格元数据
158    SheetMeta(String), // doc_token
159    /// 获取旧版文档纯文本内容
160    RawContent(String), // doc_token
161    /// 获取旧版文档富文本内容
162    Content(String), // doc_token
163    /// 编辑旧版文档内容
164    BatchUpdate(String), // doc_token
165}
166
167impl CcmDocApiOld {
168    /// 生成对应的 URL
169    pub fn to_url(&self) -> String {
170        match self {
171            CcmDocApiOld::Create => "/open-apis/doc/v2/create".to_string(),
172            CcmDocApiOld::Meta(doc_token) => {
173                format!("/open-apis/doc/v2/meta/{doc_token}")
174            }
175            CcmDocApiOld::SheetMeta(doc_token) => {
176                format!("/open-apis/doc/v2/{doc_token}/sheet_meta")
177            }
178            CcmDocApiOld::RawContent(doc_token) => {
179                format!("/open-apis/doc/v2/{doc_token}/raw_content")
180            }
181            CcmDocApiOld::Content(doc_token) => {
182                format!("/open-apis/doc/v2/{doc_token}/content")
183            }
184            CcmDocApiOld::BatchUpdate(doc_token) => {
185                format!("/open-apis/doc/v2/{doc_token}/batch_update")
186            }
187        }
188    }
189
190    /// 返回配置了稳定请求语义的请求。
191    pub fn to_request<R>(&self) -> ApiRequest<R> {
192        <Self as CatalogEndpoint>::to_request(self)
193    }
194}
195
196impl CatalogEndpoint for CcmDocApiOld {
197    fn to_url(&self) -> String {
198        CcmDocApiOld::to_url(self)
199    }
200
201    fn method(&self) -> HttpMethod {
202        match self {
203            Self::Create | Self::BatchUpdate(_) => HttpMethod::Post,
204            Self::Meta(_) | Self::SheetMeta(_) | Self::RawContent(_) | Self::Content(_) => {
205                HttpMethod::Get
206            }
207        }
208    }
209
210    // supported_access_token_types 使用 trait 默认实现(User + Tenant)
211}
212
213/// CCM Docs API Old V1 端点枚举
214/// 对应 meta.project = ccm_docs, meta.version = old
215#[derive(Debug, Clone, PartialEq)]
216#[cfg_attr(test, derive(strum_macros::EnumIter))]
217pub enum CcmDocsApiOld {
218    /// 搜索云文档
219    SearchObject,
220    /// 获取元数据
221    Meta,
222}
223
224impl CcmDocsApiOld {
225    /// 生成对应的 URL
226    pub fn to_url(&self) -> String {
227        match self {
228            CcmDocsApiOld::SearchObject => "/open-apis/suite/docs-api/search/object".to_string(),
229            CcmDocsApiOld::Meta => "/open-apis/suite/docs-api/meta".to_string(),
230        }
231    }
232
233    /// 返回配置了稳定请求语义的请求。
234    pub fn to_request<R>(&self) -> ApiRequest<R> {
235        <Self as CatalogEndpoint>::to_request(self)
236    }
237}
238
239impl CatalogEndpoint for CcmDocsApiOld {
240    fn to_url(&self) -> String {
241        CcmDocsApiOld::to_url(self)
242    }
243
244    fn method(&self) -> HttpMethod {
245        match self {
246            Self::SearchObject => HttpMethod::Post,
247            Self::Meta => HttpMethod::Get,
248        }
249    }
250
251    // supported_access_token_types 使用 trait 默认实现(User + Tenant)
252}
253
254pub mod drive;
255pub use drive::{
256    CcmDriveExplorerApi, CcmDriveExplorerApiOld, DriveApi, PermissionApi, PermissionApiOld,
257};
258
259pub mod sheets;
260pub use sheets::{CcmSheetApiOld, SheetsApiV3};
261
262pub mod wiki;
263pub use wiki::{WikiApi, WikiApiV1, WikiApiV2};
264
265pub mod lingo;
266pub use lingo::LingoApiV1;
267pub use lingo::LingoApiV1 as BaikeApiV1;
268
269pub mod minutes;
270pub use minutes::MinutesApiV1;
271
272/// Lingo API v1 端点基础路径(兼容保留)。
273pub const LINGO_API_V1: &str = "/open-apis/lingo/v1";
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278    use crate::common::api_endpoints::test_support::catalog_semantics_snapshot;
279    use openlark_core::api::{ApiRequest, HttpMethod};
280    use openlark_core::constants::AccessTokenType;
281
282    #[test]
283    fn base_catalog_semantics_snapshot() {
284        insta::assert_snapshot!(catalog_semantics_snapshot::<BaseApiV2>());
285    }
286
287    #[test]
288    fn ccm_doc_old_catalog_semantics_snapshot() {
289        insta::assert_snapshot!(catalog_semantics_snapshot::<CcmDocApiOld>());
290    }
291
292    #[test]
293    fn ccm_docs_old_catalog_semantics_snapshot() {
294        insta::assert_snapshot!(catalog_semantics_snapshot::<CcmDocsApiOld>());
295    }
296
297    // ========== BaseApiV2 Tests ==========
298    #[test]
299    fn test_base_api_v2_role_create() {
300        let endpoint = BaseApiV2::RoleCreate("app_token_123".to_string());
301        assert_eq!(
302            endpoint.to_url(),
303            "/open-apis/base/v2/apps/app_token_123/roles"
304        );
305        assert_eq!(endpoint.method(), HttpMethod::Post);
306        let req: ApiRequest<()> = endpoint.to_request();
307        assert_eq!(req.method(), &HttpMethod::Post);
308        // #438: catalog 拥有认证要求
309        assert_eq!(
310            endpoint.supported_access_token_types(),
311            Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
312        );
313        assert_eq!(
314            req.supported_access_token_types(),
315            vec![AccessTokenType::User, AccessTokenType::Tenant]
316        );
317    }
318
319    #[test]
320    fn test_base_api_v2_role_update() {
321        let endpoint =
322            BaseApiV2::RoleUpdate("app_token_123".to_string(), "role_id_456".to_string());
323        assert_eq!(
324            endpoint.to_url(),
325            "/open-apis/base/v2/apps/app_token_123/roles/role_id_456"
326        );
327        assert_eq!(endpoint.method(), HttpMethod::Put);
328        let req: ApiRequest<()> = endpoint.to_request();
329        assert_eq!(req.method(), &HttpMethod::Put);
330        assert_eq!(
331            endpoint.supported_access_token_types(),
332            Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
333        );
334        assert_eq!(
335            req.supported_access_token_types(),
336            vec![AccessTokenType::User, AccessTokenType::Tenant]
337        );
338    }
339
340    #[test]
341    fn test_base_api_v2_role_list() {
342        let endpoint = BaseApiV2::RoleList("app_token_123".to_string());
343        assert_eq!(
344            endpoint.to_url(),
345            "/open-apis/base/v2/apps/app_token_123/roles"
346        );
347        assert_eq!(endpoint.method(), HttpMethod::Get);
348        let req: ApiRequest<()> = endpoint.to_request();
349        assert_eq!(req.method(), &HttpMethod::Get);
350        assert_eq!(
351            endpoint.supported_access_token_types(),
352            Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
353        );
354        assert_eq!(
355            req.supported_access_token_types(),
356            vec![AccessTokenType::User, AccessTokenType::Tenant]
357        );
358    }
359
360    #[test]
361    fn test_base_api_v2_with_special_chars() {
362        let endpoint = BaseApiV2::RoleCreate("app-token_123".to_string());
363        assert!(endpoint.to_url().contains("app-token_123"));
364    }
365
366    // ========== MinutesApiV1 Tests ==========
367    #[test]
368    fn test_minutes_api_v1_get() {
369        let endpoint = MinutesApiV1::Get("minute_token_123".to_string());
370        assert_eq!(
371            endpoint.to_url(),
372            "/open-apis/minutes/v1/minutes/minute_token_123"
373        );
374    }
375
376    #[test]
377    fn test_minutes_api_v1_media_get() {
378        let endpoint = MinutesApiV1::MediaGet("minute_token_123".to_string());
379        assert_eq!(
380            endpoint.to_url(),
381            "/open-apis/minutes/v1/minutes/minute_token_123/media"
382        );
383    }
384
385    #[test]
386    fn test_minutes_api_v1_transcript_get() {
387        let endpoint = MinutesApiV1::TranscriptGet("minute_token_123".to_string());
388        assert_eq!(
389            endpoint.to_url(),
390            "/open-apis/minutes/v1/minutes/minute_token_123/transcript"
391        );
392    }
393
394    #[test]
395    fn test_minutes_api_v1_statistics_get() {
396        let endpoint = MinutesApiV1::StatisticsGet("minute_token_123".to_string());
397        assert_eq!(
398            endpoint.to_url(),
399            "/open-apis/minutes/v1/minutes/minute_token_123/statistics"
400        );
401    }
402
403    #[test]
404    fn minute_subscription_issue_194_endpoints() {
405        assert_eq!(
406            MinutesApiV1::Subscription.to_url(),
407            "/open-apis/minutes/v1/minutes/subscription"
408        );
409        assert_eq!(
410            MinutesApiV1::Unsubscription.to_url(),
411            "/open-apis/minutes/v1/minutes/unsubscription"
412        );
413    }
414
415    // ========== WikiApiV1 Tests ==========
416    #[test]
417    fn test_wiki_api_v1_node_search() {
418        let endpoint = WikiApiV1::NodeSearch;
419        assert_eq!(endpoint.to_url(), "/open-apis/wiki/v1/nodes/search");
420    }
421
422    // ========== DocsApiV1 Tests ==========
423    #[test]
424    fn test_docs_api_v1_content_get() {
425        let endpoint = DocsApiV1::ContentGet;
426        assert_eq!(endpoint.to_url(), "/open-apis/docs/v1/content");
427    }
428
429    // ========== DocxApiV1 Tests ==========
430    #[test]
431    fn test_docx_api_v1_document_create() {
432        let endpoint = DocxApiV1::DocumentCreate;
433        assert_eq!(endpoint.to_url(), "/open-apis/docx/v1/documents");
434    }
435
436    #[test]
437    fn test_docx_api_v1_document_get() {
438        let endpoint = DocxApiV1::DocumentGet("doc_id_123".to_string());
439        assert_eq!(endpoint.to_url(), "/open-apis/docx/v1/documents/doc_id_123");
440    }
441
442    #[test]
443    fn test_docx_api_v1_document_block_list() {
444        let endpoint = DocxApiV1::DocumentBlockList("doc_id_123".to_string());
445        assert_eq!(
446            endpoint.to_url(),
447            "/open-apis/docx/v1/documents/doc_id_123/blocks"
448        );
449    }
450
451    #[test]
452    fn test_docx_api_v1_chat_announcement_get() {
453        let endpoint = DocxApiV1::ChatAnnouncementGet("chat_id_123".to_string());
454        assert_eq!(
455            endpoint.to_url(),
456            "/open-apis/docx/v1/chats/chat_id_123/announcement"
457        );
458    }
459
460    #[test]
461    fn test_docx_api_v1_document_convert() {
462        let endpoint = DocxApiV1::DocumentConvert;
463        assert_eq!(
464            endpoint.to_url(),
465            "/open-apis/docx/documents/blocks/convert"
466        );
467    }
468
469    #[test]
470    fn test_docx_api_v1_document_block_children_create() {
471        let endpoint = DocxApiV1::DocumentBlockChildrenCreate(
472            "doc_id_123".to_string(),
473            "block_id_456".to_string(),
474        );
475        assert_eq!(
476            endpoint.to_url(),
477            "/open-apis/docx/v1/documents/doc_id_123/blocks/block_id_456/children"
478        );
479    }
480
481    // ========== WikiApiV2 Tests ==========
482    #[test]
483    fn test_wiki_api_v2_space_list() {
484        let endpoint = WikiApiV2::SpaceList;
485        assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/spaces");
486    }
487
488    #[test]
489    fn test_wiki_api_v2_space_get() {
490        let endpoint = WikiApiV2::SpaceGet("space_id_123".to_string());
491        assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/spaces/space_id_123");
492    }
493
494    #[test]
495    fn test_wiki_api_v2_space_create() {
496        let endpoint = WikiApiV2::SpaceCreate;
497        assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/spaces");
498    }
499
500    #[test]
501    fn test_wiki_api_v2_space_node_list() {
502        let endpoint = WikiApiV2::SpaceNodeList("space_id_123".to_string());
503        assert_eq!(
504            endpoint.to_url(),
505            "/open-apis/wiki/v2/spaces/space_id_123/nodes"
506        );
507    }
508
509    #[test]
510    fn test_wiki_api_v2_space_member_delete() {
511        let endpoint =
512            WikiApiV2::SpaceMemberDelete("space_id_123".to_string(), "member_id_456".to_string());
513        assert_eq!(
514            endpoint.to_url(),
515            "/open-apis/wiki/v2/spaces/space_id_123/members/member_id_456"
516        );
517    }
518
519    #[test]
520    fn test_wiki_api_v2_task_get() {
521        let endpoint = WikiApiV2::TaskGet("task_id_123".to_string());
522        assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/tasks/task_id_123");
523    }
524
525    // ========== CcmDocApiOld Tests ==========
526    #[test]
527    fn test_ccm_doc_api_old_create() {
528        let endpoint = CcmDocApiOld::Create;
529        assert_eq!(endpoint.to_url(), "/open-apis/doc/v2/create");
530    }
531
532    #[test]
533    fn test_ccm_doc_api_old_meta() {
534        let endpoint = CcmDocApiOld::Meta("doc_token_123".to_string());
535        assert_eq!(endpoint.to_url(), "/open-apis/doc/v2/meta/doc_token_123");
536    }
537
538    #[test]
539    fn test_ccm_doc_api_old_raw_content() {
540        let endpoint = CcmDocApiOld::RawContent("doc_token_123".to_string());
541        assert_eq!(
542            endpoint.to_url(),
543            "/open-apis/doc/v2/doc_token_123/raw_content"
544        );
545    }
546
547    #[test]
548    fn test_ccm_doc_api_old_batch_update() {
549        let endpoint = CcmDocApiOld::BatchUpdate("doc_token_123".to_string());
550        assert_eq!(
551            endpoint.to_url(),
552            "/open-apis/doc/v2/doc_token_123/batch_update"
553        );
554    }
555
556    // ========== CcmDocsApiOld Tests ==========
557    #[test]
558    fn test_ccm_docs_api_old_search_object() {
559        let endpoint = CcmDocsApiOld::SearchObject;
560        assert_eq!(endpoint.to_url(), "/open-apis/suite/docs-api/search/object");
561    }
562
563    #[test]
564    fn test_ccm_docs_api_old_meta() {
565        let endpoint = CcmDocsApiOld::Meta;
566        assert_eq!(endpoint.to_url(), "/open-apis/suite/docs-api/meta");
567    }
568
569    // ========== CcmDriveExplorerApiOld Tests ==========
570    #[test]
571    fn test_ccm_drive_explorer_api_old_root_folder_meta() {
572        let endpoint = CcmDriveExplorerApiOld::RootFolderMeta;
573        assert_eq!(
574            endpoint.to_url(),
575            "/open-apis/drive/explorer/v2/root_folder/meta"
576        );
577    }
578
579    #[test]
580    fn test_ccm_drive_explorer_api_old_folder_meta() {
581        let endpoint = CcmDriveExplorerApiOld::FolderMeta("folder_token_123".to_string());
582        assert_eq!(
583            endpoint.to_url(),
584            "/open-apis/drive/explorer/v2/folder/folder_token_123/meta"
585        );
586    }
587
588    #[test]
589    fn test_ccm_drive_explorer_api_old_file_copy() {
590        let endpoint = CcmDriveExplorerApiOld::FileCopy("file_token_123".to_string());
591        assert_eq!(
592            endpoint.to_url(),
593            "/open-apis/drive/explorer/v2/file/copy/files/file_token_123"
594        );
595    }
596
597    // ========== CcmDriveExplorerApi Tests ==========
598    #[test]
599    fn test_ccm_drive_explorer_api_root_folder_meta() {
600        let endpoint = CcmDriveExplorerApi::RootFolderMeta;
601        assert_eq!(
602            endpoint.to_url(),
603            "/open-apis/drive/v1/explorer/root_folder/meta"
604        );
605    }
606
607    #[test]
608    fn test_ccm_drive_explorer_api_folder_meta() {
609        let endpoint = CcmDriveExplorerApi::FolderMeta("folder_token_123".to_string());
610        assert_eq!(
611            endpoint.to_url(),
612            "/open-apis/drive/v1/explorer/folder/folder_token_123/meta"
613        );
614    }
615
616    #[test]
617    fn test_ccm_drive_explorer_api_folder() {
618        let endpoint = CcmDriveExplorerApi::Folder;
619        assert_eq!(endpoint.to_url(), "/open-apis/drive/v1/explorer/folder");
620    }
621
622    #[test]
623    fn test_ccm_drive_explorer_api_to_url_with_params() {
624        let endpoint = CcmDriveExplorerApi::RootFolderMeta;
625        let params = vec![("key", "value".to_string())];
626        let url = endpoint.to_url_with_params(&params);
627        assert!(url.contains("?"));
628        assert!(url.contains("key=value"));
629    }
630
631    #[test]
632    fn test_ccm_drive_explorer_api_to_url_with_empty_params() {
633        let endpoint = CcmDriveExplorerApi::RootFolderMeta;
634        let params: Vec<(&str, String)> = vec![];
635        let url = endpoint.to_url_with_params(&params);
636        assert!(!url.contains("?"));
637    }
638
639    #[test]
640    fn test_ccm_drive_explorer_api_to_url_with_special_chars() {
641        let endpoint = CcmDriveExplorerApi::RootFolderMeta;
642        let params = vec![("query", "hello world".to_string())];
643        let url = endpoint.to_url_with_params(&params);
644        assert!(url.contains("%20"));
645    }
646
647    // ========== PermissionApi Tests ==========
648    #[test]
649    fn test_permission_api_member_permitted() {
650        let endpoint = PermissionApi::MemberPermitted;
651        assert_eq!(
652            endpoint.to_url(),
653            "/open-apis/drive/v1/permission/member/permitted"
654        );
655    }
656
657    #[test]
658    fn test_permission_api_member_transfer() {
659        let endpoint = PermissionApi::MemberTransfer;
660        assert_eq!(
661            endpoint.to_url(),
662            "/open-apis/drive/v1/permission/member/transfer"
663        );
664    }
665
666    #[test]
667    fn test_permission_api_public() {
668        let endpoint = PermissionApi::Public;
669        assert_eq!(
670            endpoint.to_url(),
671            "/open-apis/drive/v1/permission/v2/public/"
672        );
673    }
674
675    // ========== PermissionApiOld Tests ==========
676    #[test]
677    fn test_permission_api_old_member_permitted() {
678        let endpoint = PermissionApiOld::MemberPermitted;
679        assert_eq!(
680            endpoint.to_url(),
681            "/open-apis/drive/v1/permission/member/permitted"
682        );
683    }
684
685    #[test]
686    fn test_permission_api_old_public() {
687        let endpoint = PermissionApiOld::Public;
688        assert_eq!(
689            endpoint.to_url(),
690            "/open-apis/drive/v1/permission/v2/public/"
691        );
692    }
693}