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;
267
268pub mod baike;
269pub use baike::BaikeApiV1;
270
271pub mod minutes;
272pub use minutes::MinutesApiV1;
273
274/// Lingo API v1 端点基础路径(兼容保留)。
275pub const LINGO_API_V1: &str = "/open-apis/lingo/v1";
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280    use crate::common::api_endpoints::test_support::catalog_semantics_snapshot;
281    use openlark_core::api::{ApiRequest, HttpMethod};
282    use openlark_core::constants::AccessTokenType;
283
284    #[test]
285    fn base_catalog_semantics_snapshot() {
286        insta::assert_snapshot!(catalog_semantics_snapshot::<BaseApiV2>());
287    }
288
289    #[test]
290    fn ccm_doc_old_catalog_semantics_snapshot() {
291        insta::assert_snapshot!(catalog_semantics_snapshot::<CcmDocApiOld>());
292    }
293
294    #[test]
295    fn ccm_docs_old_catalog_semantics_snapshot() {
296        insta::assert_snapshot!(catalog_semantics_snapshot::<CcmDocsApiOld>());
297    }
298
299    // ========== BaseApiV2 Tests ==========
300    #[test]
301    fn test_base_api_v2_role_create() {
302        let endpoint = BaseApiV2::RoleCreate("app_token_123".to_string());
303        assert_eq!(
304            endpoint.to_url(),
305            "/open-apis/base/v2/apps/app_token_123/roles"
306        );
307        assert_eq!(endpoint.method(), HttpMethod::Post);
308        let req: ApiRequest<()> = endpoint.to_request();
309        assert_eq!(req.method(), &HttpMethod::Post);
310        // #438: catalog 拥有认证要求
311        assert_eq!(
312            endpoint.supported_access_token_types(),
313            Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
314        );
315        assert_eq!(
316            req.supported_access_token_types(),
317            vec![AccessTokenType::User, AccessTokenType::Tenant]
318        );
319    }
320
321    #[test]
322    fn test_base_api_v2_role_update() {
323        let endpoint =
324            BaseApiV2::RoleUpdate("app_token_123".to_string(), "role_id_456".to_string());
325        assert_eq!(
326            endpoint.to_url(),
327            "/open-apis/base/v2/apps/app_token_123/roles/role_id_456"
328        );
329        assert_eq!(endpoint.method(), HttpMethod::Put);
330        let req: ApiRequest<()> = endpoint.to_request();
331        assert_eq!(req.method(), &HttpMethod::Put);
332        assert_eq!(
333            endpoint.supported_access_token_types(),
334            Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
335        );
336        assert_eq!(
337            req.supported_access_token_types(),
338            vec![AccessTokenType::User, AccessTokenType::Tenant]
339        );
340    }
341
342    #[test]
343    fn test_base_api_v2_role_list() {
344        let endpoint = BaseApiV2::RoleList("app_token_123".to_string());
345        assert_eq!(
346            endpoint.to_url(),
347            "/open-apis/base/v2/apps/app_token_123/roles"
348        );
349        assert_eq!(endpoint.method(), HttpMethod::Get);
350        let req: ApiRequest<()> = endpoint.to_request();
351        assert_eq!(req.method(), &HttpMethod::Get);
352        assert_eq!(
353            endpoint.supported_access_token_types(),
354            Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
355        );
356        assert_eq!(
357            req.supported_access_token_types(),
358            vec![AccessTokenType::User, AccessTokenType::Tenant]
359        );
360    }
361
362    #[test]
363    fn test_base_api_v2_with_special_chars() {
364        let endpoint = BaseApiV2::RoleCreate("app-token_123".to_string());
365        assert!(endpoint.to_url().contains("app-token_123"));
366    }
367
368    // ========== MinutesApiV1 Tests ==========
369    #[test]
370    fn test_minutes_api_v1_get() {
371        let endpoint = MinutesApiV1::Get("minute_token_123".to_string());
372        assert_eq!(
373            endpoint.to_url(),
374            "/open-apis/minutes/v1/minutes/minute_token_123"
375        );
376    }
377
378    #[test]
379    fn test_minutes_api_v1_media_get() {
380        let endpoint = MinutesApiV1::MediaGet("minute_token_123".to_string());
381        assert_eq!(
382            endpoint.to_url(),
383            "/open-apis/minutes/v1/minutes/minute_token_123/media"
384        );
385    }
386
387    #[test]
388    fn test_minutes_api_v1_transcript_get() {
389        let endpoint = MinutesApiV1::TranscriptGet("minute_token_123".to_string());
390        assert_eq!(
391            endpoint.to_url(),
392            "/open-apis/minutes/v1/minutes/minute_token_123/transcript"
393        );
394    }
395
396    #[test]
397    fn test_minutes_api_v1_statistics_get() {
398        let endpoint = MinutesApiV1::StatisticsGet("minute_token_123".to_string());
399        assert_eq!(
400            endpoint.to_url(),
401            "/open-apis/minutes/v1/minutes/minute_token_123/statistics"
402        );
403    }
404
405    #[test]
406    fn minute_subscription_issue_194_endpoints() {
407        assert_eq!(
408            MinutesApiV1::Subscription.to_url(),
409            "/open-apis/minutes/v1/minutes/subscription"
410        );
411        assert_eq!(
412            MinutesApiV1::Unsubscription.to_url(),
413            "/open-apis/minutes/v1/minutes/unsubscription"
414        );
415    }
416
417    // ========== WikiApiV1 Tests ==========
418    #[test]
419    fn test_wiki_api_v1_node_search() {
420        let endpoint = WikiApiV1::NodeSearch;
421        assert_eq!(endpoint.to_url(), "/open-apis/wiki/v1/nodes/search");
422    }
423
424    // ========== DocsApiV1 Tests ==========
425    #[test]
426    fn test_docs_api_v1_content_get() {
427        let endpoint = DocsApiV1::ContentGet;
428        assert_eq!(endpoint.to_url(), "/open-apis/docs/v1/content");
429    }
430
431    // ========== DocxApiV1 Tests ==========
432    #[test]
433    fn test_docx_api_v1_document_create() {
434        let endpoint = DocxApiV1::DocumentCreate;
435        assert_eq!(endpoint.to_url(), "/open-apis/docx/v1/documents");
436    }
437
438    #[test]
439    fn test_docx_api_v1_document_get() {
440        let endpoint = DocxApiV1::DocumentGet("doc_id_123".to_string());
441        assert_eq!(endpoint.to_url(), "/open-apis/docx/v1/documents/doc_id_123");
442    }
443
444    #[test]
445    fn test_docx_api_v1_document_block_list() {
446        let endpoint = DocxApiV1::DocumentBlockList("doc_id_123".to_string());
447        assert_eq!(
448            endpoint.to_url(),
449            "/open-apis/docx/v1/documents/doc_id_123/blocks"
450        );
451    }
452
453    #[test]
454    fn test_docx_api_v1_chat_announcement_get() {
455        let endpoint = DocxApiV1::ChatAnnouncementGet("chat_id_123".to_string());
456        assert_eq!(
457            endpoint.to_url(),
458            "/open-apis/docx/v1/chats/chat_id_123/announcement"
459        );
460    }
461
462    #[test]
463    fn test_docx_api_v1_document_convert() {
464        let endpoint = DocxApiV1::DocumentConvert;
465        assert_eq!(
466            endpoint.to_url(),
467            "/open-apis/docx/documents/blocks/convert"
468        );
469    }
470
471    #[test]
472    fn test_docx_api_v1_document_block_children_create() {
473        let endpoint = DocxApiV1::DocumentBlockChildrenCreate(
474            "doc_id_123".to_string(),
475            "block_id_456".to_string(),
476        );
477        assert_eq!(
478            endpoint.to_url(),
479            "/open-apis/docx/v1/documents/doc_id_123/blocks/block_id_456/children"
480        );
481    }
482
483    // ========== WikiApiV2 Tests ==========
484    #[test]
485    fn test_wiki_api_v2_space_list() {
486        let endpoint = WikiApiV2::SpaceList;
487        assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/spaces");
488    }
489
490    #[test]
491    fn test_wiki_api_v2_space_get() {
492        let endpoint = WikiApiV2::SpaceGet("space_id_123".to_string());
493        assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/spaces/space_id_123");
494    }
495
496    #[test]
497    fn test_wiki_api_v2_space_create() {
498        let endpoint = WikiApiV2::SpaceCreate;
499        assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/spaces");
500    }
501
502    #[test]
503    fn test_wiki_api_v2_space_node_list() {
504        let endpoint = WikiApiV2::SpaceNodeList("space_id_123".to_string());
505        assert_eq!(
506            endpoint.to_url(),
507            "/open-apis/wiki/v2/spaces/space_id_123/nodes"
508        );
509    }
510
511    #[test]
512    fn test_wiki_api_v2_space_member_delete() {
513        let endpoint =
514            WikiApiV2::SpaceMemberDelete("space_id_123".to_string(), "member_id_456".to_string());
515        assert_eq!(
516            endpoint.to_url(),
517            "/open-apis/wiki/v2/spaces/space_id_123/members/member_id_456"
518        );
519    }
520
521    #[test]
522    fn test_wiki_api_v2_task_get() {
523        let endpoint = WikiApiV2::TaskGet("task_id_123".to_string());
524        assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/tasks/task_id_123");
525    }
526
527    // ========== CcmDocApiOld Tests ==========
528    #[test]
529    fn test_ccm_doc_api_old_create() {
530        let endpoint = CcmDocApiOld::Create;
531        assert_eq!(endpoint.to_url(), "/open-apis/doc/v2/create");
532    }
533
534    #[test]
535    fn test_ccm_doc_api_old_meta() {
536        let endpoint = CcmDocApiOld::Meta("doc_token_123".to_string());
537        assert_eq!(endpoint.to_url(), "/open-apis/doc/v2/meta/doc_token_123");
538    }
539
540    #[test]
541    fn test_ccm_doc_api_old_raw_content() {
542        let endpoint = CcmDocApiOld::RawContent("doc_token_123".to_string());
543        assert_eq!(
544            endpoint.to_url(),
545            "/open-apis/doc/v2/doc_token_123/raw_content"
546        );
547    }
548
549    #[test]
550    fn test_ccm_doc_api_old_batch_update() {
551        let endpoint = CcmDocApiOld::BatchUpdate("doc_token_123".to_string());
552        assert_eq!(
553            endpoint.to_url(),
554            "/open-apis/doc/v2/doc_token_123/batch_update"
555        );
556    }
557
558    // ========== CcmDocsApiOld Tests ==========
559    #[test]
560    fn test_ccm_docs_api_old_search_object() {
561        let endpoint = CcmDocsApiOld::SearchObject;
562        assert_eq!(endpoint.to_url(), "/open-apis/suite/docs-api/search/object");
563    }
564
565    #[test]
566    fn test_ccm_docs_api_old_meta() {
567        let endpoint = CcmDocsApiOld::Meta;
568        assert_eq!(endpoint.to_url(), "/open-apis/suite/docs-api/meta");
569    }
570
571    // ========== CcmDriveExplorerApiOld Tests ==========
572    #[test]
573    fn test_ccm_drive_explorer_api_old_root_folder_meta() {
574        let endpoint = CcmDriveExplorerApiOld::RootFolderMeta;
575        assert_eq!(
576            endpoint.to_url(),
577            "/open-apis/drive/explorer/v2/root_folder/meta"
578        );
579    }
580
581    #[test]
582    fn test_ccm_drive_explorer_api_old_folder_meta() {
583        let endpoint = CcmDriveExplorerApiOld::FolderMeta("folder_token_123".to_string());
584        assert_eq!(
585            endpoint.to_url(),
586            "/open-apis/drive/explorer/v2/folder/folder_token_123/meta"
587        );
588    }
589
590    #[test]
591    fn test_ccm_drive_explorer_api_old_file_copy() {
592        let endpoint = CcmDriveExplorerApiOld::FileCopy("file_token_123".to_string());
593        assert_eq!(
594            endpoint.to_url(),
595            "/open-apis/drive/explorer/v2/file/copy/files/file_token_123"
596        );
597    }
598
599    // ========== CcmDriveExplorerApi Tests ==========
600    #[test]
601    fn test_ccm_drive_explorer_api_root_folder_meta() {
602        let endpoint = CcmDriveExplorerApi::RootFolderMeta;
603        assert_eq!(
604            endpoint.to_url(),
605            "/open-apis/drive/v1/explorer/root_folder/meta"
606        );
607    }
608
609    #[test]
610    fn test_ccm_drive_explorer_api_folder_meta() {
611        let endpoint = CcmDriveExplorerApi::FolderMeta("folder_token_123".to_string());
612        assert_eq!(
613            endpoint.to_url(),
614            "/open-apis/drive/v1/explorer/folder/folder_token_123/meta"
615        );
616    }
617
618    #[test]
619    fn test_ccm_drive_explorer_api_folder() {
620        let endpoint = CcmDriveExplorerApi::Folder;
621        assert_eq!(endpoint.to_url(), "/open-apis/drive/v1/explorer/folder");
622    }
623
624    #[test]
625    fn test_ccm_drive_explorer_api_to_url_with_params() {
626        let endpoint = CcmDriveExplorerApi::RootFolderMeta;
627        let params = vec![("key", "value".to_string())];
628        let url = endpoint.to_url_with_params(&params);
629        assert!(url.contains("?"));
630        assert!(url.contains("key=value"));
631    }
632
633    #[test]
634    fn test_ccm_drive_explorer_api_to_url_with_empty_params() {
635        let endpoint = CcmDriveExplorerApi::RootFolderMeta;
636        let params: Vec<(&str, String)> = vec![];
637        let url = endpoint.to_url_with_params(&params);
638        assert!(!url.contains("?"));
639    }
640
641    #[test]
642    fn test_ccm_drive_explorer_api_to_url_with_special_chars() {
643        let endpoint = CcmDriveExplorerApi::RootFolderMeta;
644        let params = vec![("query", "hello world".to_string())];
645        let url = endpoint.to_url_with_params(&params);
646        assert!(url.contains("%20"));
647    }
648
649    // ========== PermissionApi Tests ==========
650    #[test]
651    fn test_permission_api_member_permitted() {
652        let endpoint = PermissionApi::MemberPermitted;
653        assert_eq!(
654            endpoint.to_url(),
655            "/open-apis/drive/v1/permission/member/permitted"
656        );
657    }
658
659    #[test]
660    fn test_permission_api_member_transfer() {
661        let endpoint = PermissionApi::MemberTransfer;
662        assert_eq!(
663            endpoint.to_url(),
664            "/open-apis/drive/v1/permission/member/transfer"
665        );
666    }
667
668    #[test]
669    fn test_permission_api_public() {
670        let endpoint = PermissionApi::Public;
671        assert_eq!(
672            endpoint.to_url(),
673            "/open-apis/drive/v1/permission/v2/public/"
674        );
675    }
676
677    // ========== PermissionApiOld Tests ==========
678    #[test]
679    fn test_permission_api_old_member_permitted() {
680        let endpoint = PermissionApiOld::MemberPermitted;
681        assert_eq!(
682            endpoint.to_url(),
683            "/open-apis/drive/v1/permission/member/permitted"
684        );
685    }
686
687    #[test]
688    fn test_permission_api_old_public() {
689        let endpoint = PermissionApiOld::Public;
690        assert_eq!(
691            endpoint.to_url(),
692            "/open-apis/drive/v1/permission/v2/public/"
693        );
694    }
695}