use openlark_core::api::{ApiRequest, HttpMethod};
use openlark_core::constants::AccessTokenType;
pub trait CatalogEndpoint {
fn to_url(&self) -> String;
fn method(&self) -> HttpMethod;
fn supported_access_token_types(&self) -> Option<Vec<AccessTokenType>> {
Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
}
fn to_request<R>(&self) -> ApiRequest<R> {
self.to_request_with_url(self.to_url())
}
fn to_request_with_url<R>(&self, url: impl Into<String>) -> ApiRequest<R> {
let url = url.into();
let mut req = match self.method() {
HttpMethod::Get => ApiRequest::get(url),
HttpMethod::Post => ApiRequest::post(url),
HttpMethod::Put => ApiRequest::put(url),
HttpMethod::Delete => ApiRequest::delete(url),
HttpMethod::Patch => ApiRequest::patch(url),
_ => unreachable!(
"CatalogEndpoint encountered unknown HttpMethod variant — this should never happen"
),
};
if let Some(tokens) = self.supported_access_token_types() {
req = req.with_supported_access_token_types(tokens);
}
req
}
}
#[cfg(test)]
pub(crate) mod test_support {
use super::CatalogEndpoint;
use openlark_core::{
api::{ApiRequest, HttpMethod},
constants::AccessTokenType,
};
use std::fmt::Debug;
use strum::IntoEnumIterator;
pub(crate) fn assert_endpoint_semantics<E>(
endpoint: E,
expected_method: HttpMethod,
expected_path: &str,
) where
E: CatalogEndpoint,
{
assert_eq!(endpoint.to_url(), expected_path);
assert_eq!(endpoint.method(), expected_method);
assert_eq!(
endpoint.supported_access_token_types(),
Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
);
let request: ApiRequest<()> = endpoint.to_request();
assert_eq!(request.method(), &expected_method);
assert_eq!(request.api_path(), expected_path);
assert_eq!(
request.supported_access_token_types(),
vec![AccessTokenType::User, AccessTokenType::Tenant]
);
}
pub(crate) fn catalog_semantics_snapshot<E>() -> String
where
E: CatalogEndpoint + IntoEnumIterator + Debug,
{
E::iter()
.map(|endpoint| {
let path = endpoint.to_url();
let method = endpoint.method();
let auth = endpoint.supported_access_token_types();
let request: ApiRequest<()> = endpoint.to_request();
assert_eq!(request.method(), &method);
assert_eq!(request.api_path(), path);
assert_eq!(
request.supported_access_token_types(),
auth.clone().unwrap_or_default()
);
format!("{endpoint:?} | {method:?} | {path} | {auth:?}")
})
.collect::<Vec<_>>()
.join("\n")
}
}
pub mod base;
pub use base::BaseApiV2;
pub mod bitable;
pub use bitable::BitableApiV1;
pub mod docs;
pub use docs::DocsApiV1;
pub mod docx;
pub use docx::DocxApiV1;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(test, derive(strum_macros::EnumIter))]
pub enum CcmDocApiOld {
Create,
Meta(String), SheetMeta(String), RawContent(String), Content(String), BatchUpdate(String), }
impl CcmDocApiOld {
pub fn to_url(&self) -> String {
match self {
CcmDocApiOld::Create => "/open-apis/doc/v2/create".to_string(),
CcmDocApiOld::Meta(doc_token) => {
format!("/open-apis/doc/v2/meta/{doc_token}")
}
CcmDocApiOld::SheetMeta(doc_token) => {
format!("/open-apis/doc/v2/{doc_token}/sheet_meta")
}
CcmDocApiOld::RawContent(doc_token) => {
format!("/open-apis/doc/v2/{doc_token}/raw_content")
}
CcmDocApiOld::Content(doc_token) => {
format!("/open-apis/doc/v2/{doc_token}/content")
}
CcmDocApiOld::BatchUpdate(doc_token) => {
format!("/open-apis/doc/v2/{doc_token}/batch_update")
}
}
}
pub fn to_request<R>(&self) -> ApiRequest<R> {
<Self as CatalogEndpoint>::to_request(self)
}
}
impl CatalogEndpoint for CcmDocApiOld {
fn to_url(&self) -> String {
CcmDocApiOld::to_url(self)
}
fn method(&self) -> HttpMethod {
match self {
Self::Create | Self::BatchUpdate(_) => HttpMethod::Post,
Self::Meta(_) | Self::SheetMeta(_) | Self::RawContent(_) | Self::Content(_) => {
HttpMethod::Get
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(test, derive(strum_macros::EnumIter))]
pub enum CcmDocsApiOld {
SearchObject,
Meta,
}
impl CcmDocsApiOld {
pub fn to_url(&self) -> String {
match self {
CcmDocsApiOld::SearchObject => "/open-apis/suite/docs-api/search/object".to_string(),
CcmDocsApiOld::Meta => "/open-apis/suite/docs-api/meta".to_string(),
}
}
pub fn to_request<R>(&self) -> ApiRequest<R> {
<Self as CatalogEndpoint>::to_request(self)
}
}
impl CatalogEndpoint for CcmDocsApiOld {
fn to_url(&self) -> String {
CcmDocsApiOld::to_url(self)
}
fn method(&self) -> HttpMethod {
match self {
Self::SearchObject => HttpMethod::Post,
Self::Meta => HttpMethod::Get,
}
}
}
pub mod drive;
pub use drive::{
CcmDriveExplorerApi, CcmDriveExplorerApiOld, DriveApi, PermissionApi, PermissionApiOld,
};
pub mod sheets;
pub use sheets::{CcmSheetApiOld, SheetsApiV3};
pub mod wiki;
pub use wiki::{WikiApi, WikiApiV1, WikiApiV2};
pub mod lingo;
pub use lingo::LingoApiV1;
pub use lingo::LingoApiV1 as BaikeApiV1;
pub mod minutes;
pub use minutes::MinutesApiV1;
pub const LINGO_API_V1: &str = "/open-apis/lingo/v1";
#[cfg(test)]
mod tests {
use super::*;
use crate::common::api_endpoints::test_support::catalog_semantics_snapshot;
use openlark_core::api::{ApiRequest, HttpMethod};
use openlark_core::constants::AccessTokenType;
#[test]
fn base_catalog_semantics_snapshot() {
insta::assert_snapshot!(catalog_semantics_snapshot::<BaseApiV2>());
}
#[test]
fn ccm_doc_old_catalog_semantics_snapshot() {
insta::assert_snapshot!(catalog_semantics_snapshot::<CcmDocApiOld>());
}
#[test]
fn ccm_docs_old_catalog_semantics_snapshot() {
insta::assert_snapshot!(catalog_semantics_snapshot::<CcmDocsApiOld>());
}
#[test]
fn test_base_api_v2_role_create() {
let endpoint = BaseApiV2::RoleCreate("app_token_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/base/v2/apps/app_token_123/roles"
);
assert_eq!(endpoint.method(), HttpMethod::Post);
let req: ApiRequest<()> = endpoint.to_request();
assert_eq!(req.method(), &HttpMethod::Post);
assert_eq!(
endpoint.supported_access_token_types(),
Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
);
assert_eq!(
req.supported_access_token_types(),
vec![AccessTokenType::User, AccessTokenType::Tenant]
);
}
#[test]
fn test_base_api_v2_role_update() {
let endpoint =
BaseApiV2::RoleUpdate("app_token_123".to_string(), "role_id_456".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/base/v2/apps/app_token_123/roles/role_id_456"
);
assert_eq!(endpoint.method(), HttpMethod::Put);
let req: ApiRequest<()> = endpoint.to_request();
assert_eq!(req.method(), &HttpMethod::Put);
assert_eq!(
endpoint.supported_access_token_types(),
Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
);
assert_eq!(
req.supported_access_token_types(),
vec![AccessTokenType::User, AccessTokenType::Tenant]
);
}
#[test]
fn test_base_api_v2_role_list() {
let endpoint = BaseApiV2::RoleList("app_token_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/base/v2/apps/app_token_123/roles"
);
assert_eq!(endpoint.method(), HttpMethod::Get);
let req: ApiRequest<()> = endpoint.to_request();
assert_eq!(req.method(), &HttpMethod::Get);
assert_eq!(
endpoint.supported_access_token_types(),
Some(vec![AccessTokenType::User, AccessTokenType::Tenant])
);
assert_eq!(
req.supported_access_token_types(),
vec![AccessTokenType::User, AccessTokenType::Tenant]
);
}
#[test]
fn test_base_api_v2_with_special_chars() {
let endpoint = BaseApiV2::RoleCreate("app-token_123".to_string());
assert!(endpoint.to_url().contains("app-token_123"));
}
#[test]
fn test_minutes_api_v1_get() {
let endpoint = MinutesApiV1::Get("minute_token_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/minutes/v1/minutes/minute_token_123"
);
}
#[test]
fn test_minutes_api_v1_media_get() {
let endpoint = MinutesApiV1::MediaGet("minute_token_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/minutes/v1/minutes/minute_token_123/media"
);
}
#[test]
fn test_minutes_api_v1_transcript_get() {
let endpoint = MinutesApiV1::TranscriptGet("minute_token_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/minutes/v1/minutes/minute_token_123/transcript"
);
}
#[test]
fn test_minutes_api_v1_statistics_get() {
let endpoint = MinutesApiV1::StatisticsGet("minute_token_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/minutes/v1/minutes/minute_token_123/statistics"
);
}
#[test]
fn minute_subscription_issue_194_endpoints() {
assert_eq!(
MinutesApiV1::Subscription.to_url(),
"/open-apis/minutes/v1/minutes/subscription"
);
assert_eq!(
MinutesApiV1::Unsubscription.to_url(),
"/open-apis/minutes/v1/minutes/unsubscription"
);
}
#[test]
fn test_wiki_api_v1_node_search() {
let endpoint = WikiApiV1::NodeSearch;
assert_eq!(endpoint.to_url(), "/open-apis/wiki/v1/nodes/search");
}
#[test]
fn test_docs_api_v1_content_get() {
let endpoint = DocsApiV1::ContentGet;
assert_eq!(endpoint.to_url(), "/open-apis/docs/v1/content");
}
#[test]
fn test_docx_api_v1_document_create() {
let endpoint = DocxApiV1::DocumentCreate;
assert_eq!(endpoint.to_url(), "/open-apis/docx/v1/documents");
}
#[test]
fn test_docx_api_v1_document_get() {
let endpoint = DocxApiV1::DocumentGet("doc_id_123".to_string());
assert_eq!(endpoint.to_url(), "/open-apis/docx/v1/documents/doc_id_123");
}
#[test]
fn test_docx_api_v1_document_block_list() {
let endpoint = DocxApiV1::DocumentBlockList("doc_id_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/docx/v1/documents/doc_id_123/blocks"
);
}
#[test]
fn test_docx_api_v1_chat_announcement_get() {
let endpoint = DocxApiV1::ChatAnnouncementGet("chat_id_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/docx/v1/chats/chat_id_123/announcement"
);
}
#[test]
fn test_docx_api_v1_document_convert() {
let endpoint = DocxApiV1::DocumentConvert;
assert_eq!(
endpoint.to_url(),
"/open-apis/docx/documents/blocks/convert"
);
}
#[test]
fn test_docx_api_v1_document_block_children_create() {
let endpoint = DocxApiV1::DocumentBlockChildrenCreate(
"doc_id_123".to_string(),
"block_id_456".to_string(),
);
assert_eq!(
endpoint.to_url(),
"/open-apis/docx/v1/documents/doc_id_123/blocks/block_id_456/children"
);
}
#[test]
fn test_wiki_api_v2_space_list() {
let endpoint = WikiApiV2::SpaceList;
assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/spaces");
}
#[test]
fn test_wiki_api_v2_space_get() {
let endpoint = WikiApiV2::SpaceGet("space_id_123".to_string());
assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/spaces/space_id_123");
}
#[test]
fn test_wiki_api_v2_space_create() {
let endpoint = WikiApiV2::SpaceCreate;
assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/spaces");
}
#[test]
fn test_wiki_api_v2_space_node_list() {
let endpoint = WikiApiV2::SpaceNodeList("space_id_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/wiki/v2/spaces/space_id_123/nodes"
);
}
#[test]
fn test_wiki_api_v2_space_member_delete() {
let endpoint =
WikiApiV2::SpaceMemberDelete("space_id_123".to_string(), "member_id_456".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/wiki/v2/spaces/space_id_123/members/member_id_456"
);
}
#[test]
fn test_wiki_api_v2_task_get() {
let endpoint = WikiApiV2::TaskGet("task_id_123".to_string());
assert_eq!(endpoint.to_url(), "/open-apis/wiki/v2/tasks/task_id_123");
}
#[test]
fn test_ccm_doc_api_old_create() {
let endpoint = CcmDocApiOld::Create;
assert_eq!(endpoint.to_url(), "/open-apis/doc/v2/create");
}
#[test]
fn test_ccm_doc_api_old_meta() {
let endpoint = CcmDocApiOld::Meta("doc_token_123".to_string());
assert_eq!(endpoint.to_url(), "/open-apis/doc/v2/meta/doc_token_123");
}
#[test]
fn test_ccm_doc_api_old_raw_content() {
let endpoint = CcmDocApiOld::RawContent("doc_token_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/doc/v2/doc_token_123/raw_content"
);
}
#[test]
fn test_ccm_doc_api_old_batch_update() {
let endpoint = CcmDocApiOld::BatchUpdate("doc_token_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/doc/v2/doc_token_123/batch_update"
);
}
#[test]
fn test_ccm_docs_api_old_search_object() {
let endpoint = CcmDocsApiOld::SearchObject;
assert_eq!(endpoint.to_url(), "/open-apis/suite/docs-api/search/object");
}
#[test]
fn test_ccm_docs_api_old_meta() {
let endpoint = CcmDocsApiOld::Meta;
assert_eq!(endpoint.to_url(), "/open-apis/suite/docs-api/meta");
}
#[test]
fn test_ccm_drive_explorer_api_old_root_folder_meta() {
let endpoint = CcmDriveExplorerApiOld::RootFolderMeta;
assert_eq!(
endpoint.to_url(),
"/open-apis/drive/explorer/v2/root_folder/meta"
);
}
#[test]
fn test_ccm_drive_explorer_api_old_folder_meta() {
let endpoint = CcmDriveExplorerApiOld::FolderMeta("folder_token_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/drive/explorer/v2/folder/folder_token_123/meta"
);
}
#[test]
fn test_ccm_drive_explorer_api_old_file_copy() {
let endpoint = CcmDriveExplorerApiOld::FileCopy("file_token_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/drive/explorer/v2/file/copy/files/file_token_123"
);
}
#[test]
fn test_ccm_drive_explorer_api_root_folder_meta() {
let endpoint = CcmDriveExplorerApi::RootFolderMeta;
assert_eq!(
endpoint.to_url(),
"/open-apis/drive/v1/explorer/root_folder/meta"
);
}
#[test]
fn test_ccm_drive_explorer_api_folder_meta() {
let endpoint = CcmDriveExplorerApi::FolderMeta("folder_token_123".to_string());
assert_eq!(
endpoint.to_url(),
"/open-apis/drive/v1/explorer/folder/folder_token_123/meta"
);
}
#[test]
fn test_ccm_drive_explorer_api_folder() {
let endpoint = CcmDriveExplorerApi::Folder;
assert_eq!(endpoint.to_url(), "/open-apis/drive/v1/explorer/folder");
}
#[test]
fn test_ccm_drive_explorer_api_to_url_with_params() {
let endpoint = CcmDriveExplorerApi::RootFolderMeta;
let params = vec![("key", "value".to_string())];
let url = endpoint.to_url_with_params(¶ms);
assert!(url.contains("?"));
assert!(url.contains("key=value"));
}
#[test]
fn test_ccm_drive_explorer_api_to_url_with_empty_params() {
let endpoint = CcmDriveExplorerApi::RootFolderMeta;
let params: Vec<(&str, String)> = vec![];
let url = endpoint.to_url_with_params(¶ms);
assert!(!url.contains("?"));
}
#[test]
fn test_ccm_drive_explorer_api_to_url_with_special_chars() {
let endpoint = CcmDriveExplorerApi::RootFolderMeta;
let params = vec![("query", "hello world".to_string())];
let url = endpoint.to_url_with_params(¶ms);
assert!(url.contains("%20"));
}
#[test]
fn test_permission_api_member_permitted() {
let endpoint = PermissionApi::MemberPermitted;
assert_eq!(
endpoint.to_url(),
"/open-apis/drive/v1/permission/member/permitted"
);
}
#[test]
fn test_permission_api_member_transfer() {
let endpoint = PermissionApi::MemberTransfer;
assert_eq!(
endpoint.to_url(),
"/open-apis/drive/v1/permission/member/transfer"
);
}
#[test]
fn test_permission_api_public() {
let endpoint = PermissionApi::Public;
assert_eq!(
endpoint.to_url(),
"/open-apis/drive/v1/permission/v2/public/"
);
}
#[test]
fn test_permission_api_old_member_permitted() {
let endpoint = PermissionApiOld::MemberPermitted;
assert_eq!(
endpoint.to_url(),
"/open-apis/drive/v1/permission/member/permitted"
);
}
#[test]
fn test_permission_api_old_public() {
let endpoint = PermissionApiOld::Public;
assert_eq!(
endpoint.to_url(),
"/open-apis/drive/v1/permission/v2/public/"
);
}
}