Skip to content
Merged
552 changes: 268 additions & 284 deletions .github/msrv/Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion crates/openlark-docs/src/base/base/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ pub mod v2;
// 使用通配符导出所有子模块,避免维护大量重复的导出列表
// v2 模块显式导出
pub use v2::{
AppRole, Create, CreateReq, CreateResp, List, ListReq, ListResp, Update, UpdateReq, UpdateResp,
AppRole, Create, CreateReq, CreateResp, Delete, DeleteResp, List, ListReq, ListResp, Update,
UpdateReq, UpdateResp,
};
13 changes: 11 additions & 2 deletions crates/openlark-docs/src/base/base/v2/app/role/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use openlark_core::{
};
use serde::{Deserialize, Serialize};

use crate::common::api_endpoints::{BaseApiV2, CatalogEndpoint};
use crate::common::api_utils::*;

/// 新增自定义角色
Expand Down Expand Up @@ -123,10 +124,11 @@ impl Create {
}

// 使用类型安全的端点枚举生成路径
use crate::common::api_endpoints::BaseApiV2;
let api_endpoint = BaseApiV2::RoleCreate(self.app_token);

let api_request: ApiRequest<CreateResp> = ApiRequest::post(&api_endpoint.to_url())
// #438: method 来自 catalog
let api_request: ApiRequest<CreateResp> = api_endpoint
.to_request()
.body(serialize_params(&self.req, "新增自定义角色")?);

let response = Transport::request(api_request, &self.config, Some(option)).await?;
Expand Down Expand Up @@ -180,4 +182,11 @@ mod tests {
"/open-apis/base/v2/apps/app001/roles"
);
}

#[test]
fn test_create_role_uses_post_from_catalog_438() {
let ep = BaseApiV2::RoleCreate("app".into());
let req: openlark_core::api::ApiRequest<CreateResp> = ep.to_request();
assert_eq!(req.method(), &openlark_core::api::HttpMethod::Post);
}
}
125 changes: 125 additions & 0 deletions crates/openlark-docs/src/base/base/v2/app/role/delete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//! 删除自定义角色
//!
//! docPath: <https://open.feishu.cn/document/docs/bitable-v1/advanced-permission/app-role/delete-2>

use openlark_core::{
api::{ApiRequest, ApiResponseTrait, ResponseFormat},
config::Config,
error::SDKResult,
http::Transport,
validate_required,
};
use serde::{Deserialize, Serialize};

use crate::common::api_endpoints::{BaseApiV2, CatalogEndpoint};
use crate::common::api_utils::*;

/// 删除自定义角色请求。
#[derive(Debug, Clone)]
pub struct Delete {
config: Config,
app_token: String,
role_id: String,
}

impl Delete {
/// 创建新的删除请求。
pub fn new(config: Config) -> Self {
Self {
config,
app_token: String::new(),
role_id: String::new(),
}
}

/// 设置应用 token。
pub fn app_token(mut self, app_token: impl Into<String>) -> Self {
self.app_token = app_token.into();
self
}

/// 设置角色 ID。
pub fn role_id(mut self, role_id: impl Into<String>) -> Self {
self.role_id = role_id.into();
self
}

/// 执行请求。
pub async fn execute(self) -> SDKResult<DeleteResp> {
self.execute_with_options(openlark_core::req_option::RequestOption::default())
.await
}

/// 使用指定请求选项执行请求。
pub async fn execute_with_options(
self,
option: openlark_core::req_option::RequestOption,
) -> SDKResult<DeleteResp> {
validate_required!(self.app_token.trim(), "app_token 不能为空");
validate_required!(self.role_id.trim(), "role_id 不能为空");

let api_endpoint = BaseApiV2::RoleDelete(self.app_token, self.role_id);

let api_request: ApiRequest<DeleteResp> = api_endpoint.to_request();

let response = Transport::request(api_request, &self.config, Some(option)).await?;
extract_response_data(response, "删除自定义角色")
}
}

/// 删除自定义角色响应(data 为空对象)。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct DeleteResp {}

impl ApiResponseTrait for DeleteResp {
fn data_format() -> ResponseFormat {
ResponseFormat::Data
}
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use wiremock::MockServer;
use wiremock::matchers::{method, path};
use wiremock::{Mock, ResponseTemplate};

/// 端到端:DELETE .../roles/{role_id} → DeleteResp。
#[tokio::test]
async fn test_delete_role_returns_data_on_success() {
let server = MockServer::start().await;
Mock::given(method("DELETE"))
.and(path("/open-apis/base/v2/apps/app001/roles/role001"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"code": 0, "msg": "success", "data": {}
})))
.mount(&server)
.await;
let config = Config::builder()
.app_id("ci_app_id")
.app_secret("ci_app_secret")
.base_url(server.uri())
.enable_token_cache(false)
.build();
Delete::new(config)
.app_token("app001")
.role_id("role001")
.execute()
.await
.expect("删除角色应成功");
let received = server.received_requests().await.unwrap_or_default();
assert_eq!(received.len(), 1);
assert_eq!(
received[0].url.path(),
"/open-apis/base/v2/apps/app001/roles/role001"
);
}

#[test]
fn test_delete_role_uses_delete_from_catalog_438() {
let ep = BaseApiV2::RoleDelete("app".into(), "role001".into());
let req: openlark_core::api::ApiRequest<DeleteResp> = ep.to_request();
assert_eq!(req.method(), &openlark_core::api::HttpMethod::Delete);
}
}
12 changes: 10 additions & 2 deletions crates/openlark-docs/src/base/base/v2/app/role/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use openlark_core::{
};
use serde::{Deserialize, Serialize};

use crate::common::api_endpoints::{BaseApiV2, CatalogEndpoint};
use crate::common::api_utils::*;

/// 列出自定义角色
Expand Down Expand Up @@ -101,10 +102,10 @@ impl List {
));
}

use crate::common::api_endpoints::BaseApiV2;
let api_endpoint = BaseApiV2::RoleList(self.app_token);

let mut api_request: ApiRequest<ListResp> = ApiRequest::get(&api_endpoint.to_url());
// #438: method 来自 catalog
let mut api_request: ApiRequest<ListResp> = api_endpoint.to_request();
api_request = api_request.query_opt("page_size", self.req.page_size.map(|v| v.to_string()));
api_request = api_request.query_opt("page_token", self.req.page_token);

Expand Down Expand Up @@ -157,4 +158,11 @@ mod tests {
"/open-apis/base/v2/apps/app001/roles"
);
}

#[test]
fn test_list_roles_uses_get_from_catalog_438() {
let ep = BaseApiV2::RoleList("app".into());
let req: openlark_core::api::ApiRequest<ListResp> = ep.to_request();
assert_eq!(req.method(), &openlark_core::api::HttpMethod::Get);
}
}
2 changes: 2 additions & 0 deletions crates/openlark-docs/src/base/base/v2/app/role/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pub mod create;
pub mod delete;
pub mod list;
pub mod update;

pub use create::{Create, CreateReq, CreateResp};
pub use delete::{Delete, DeleteResp};
pub use list::{List, ListReq, ListResp};
pub use update::{Update, UpdateReq, UpdateResp};
13 changes: 11 additions & 2 deletions crates/openlark-docs/src/base/base/v2/app/role/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use openlark_core::{
};
use serde::{Deserialize, Serialize};

use crate::common::api_endpoints::{BaseApiV2, CatalogEndpoint};
use crate::common::api_utils::*;

/// 更新自定义角色
Expand Down Expand Up @@ -128,10 +129,11 @@ impl Update {
));
}

use crate::common::api_endpoints::BaseApiV2;
let api_endpoint = BaseApiV2::RoleUpdate(self.app_token, self.role_id);

let api_request: ApiRequest<UpdateResp> = ApiRequest::put(&api_endpoint.to_url())
// #438: method 来自 catalog
let api_request: ApiRequest<UpdateResp> = api_endpoint
.to_request()
.body(serialize_params(&self.req, "更新自定义角色")?);

let response = Transport::request(api_request, &self.config, Some(option)).await?;
Expand Down Expand Up @@ -185,4 +187,11 @@ mod tests {
"/open-apis/base/v2/apps/app001/roles/role001"
);
}

#[test]
fn test_update_role_uses_put_from_catalog_438() {
let ep = BaseApiV2::RoleUpdate("app".into(), "role001".into());
let req: openlark_core::api::ApiRequest<UpdateResp> = ep.to_request();
assert_eq!(req.method(), &openlark_core::api::HttpMethod::Put);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ impl BatchCreateRecordRequest {
let api_endpoint =
BitableApiV1::RecordBatchCreate(self.app_token.clone(), self.table_id.clone());

let mut api_request: ApiRequest<BatchCreateRecordResponse> = ApiRequest::post(
&api_endpoint.to_url(),
)
.body(serde_json::to_vec(&BatchCreateRecordRequestBody {
records: self.records,
})?);
// #424: POST + body from leaf data
let mut api_request: ApiRequest<BatchCreateRecordResponse> = api_endpoint
.to_request()
.body(serde_json::to_vec(&BatchCreateRecordRequestBody {
records: self.records,
})?);

api_request = api_request.query_opt("user_id_type", self.user_id_type);
api_request = api_request.query_opt("client_token", self.client_token);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,13 @@ impl BatchDeleteRecordRequest {
let api_endpoint =
BitableApiV1::RecordBatchDelete(self.app_token.clone(), self.table_id.clone());

let api_request: ApiRequest<BatchDeleteRecordResponse> = ApiRequest::post(
&api_endpoint.to_url(),
)
.body(serde_json::to_vec(&BatchDeleteRecordRequestBody {
record_ids: self.record_ids,
})?);
// #424 catalog POST for batch delete
let api_request: ApiRequest<BatchDeleteRecordResponse> =
api_endpoint
.to_request()
.body(serde_json::to_vec(&BatchDeleteRecordRequestBody {
record_ids: self.record_ids,
})?);

let response = Transport::request(api_request, &self.config, Some(option)).await?;
response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,16 @@ impl BatchGetRecordRequest {
let api_endpoint =
BitableApiV1::RecordBatchGet(self.app_token.clone(), self.table_id.clone());

let api_request: ApiRequest<BatchGetRecordResponse> = ApiRequest::post(
&api_endpoint.to_url(),
)
.body(serde_json::to_vec(&BatchGetRecordRequestBody {
record_ids: self.record_ids,
user_id_type: self.user_id_type,
with_shared_url: self.with_shared_url,
automatic_fields: self.automatic_fields,
})?);
// #424: batch get uses POST in catalog
let api_request: ApiRequest<BatchGetRecordResponse> =
api_endpoint
.to_request()
.body(serde_json::to_vec(&BatchGetRecordRequestBody {
record_ids: self.record_ids,
user_id_type: self.user_id_type,
with_shared_url: self.with_shared_url,
automatic_fields: self.automatic_fields,
})?);

let response = Transport::request(api_request, &self.config, Some(option)).await?;
response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ impl BatchUpdateRecordRequest {
let api_endpoint =
BitableApiV1::RecordBatchUpdate(self.app_token.clone(), self.table_id.clone());

let mut api_request: ApiRequest<BatchUpdateRecordResponse> = ApiRequest::post(
&api_endpoint.to_url(),
)
.body(serde_json::to_vec(&BatchUpdateRecordRequestBody {
records: self.records,
})?);
// #424: POST via catalog for batch update
let mut api_request: ApiRequest<BatchUpdateRecordResponse> = api_endpoint
.to_request()
.body(serde_json::to_vec(&BatchUpdateRecordRequestBody {
records: self.records,
})?);

api_request = api_request.query_opt("user_id_type", self.user_id_type);
api_request = api_request.query_opt(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use openlark_core::{
SDKResult,
api::{ApiRequest, ApiResponseTrait, ResponseFormat},
api::{ApiResponseTrait, ResponseFormat},
config::Config,
http::Transport,
validate_required,
Expand Down Expand Up @@ -102,7 +102,8 @@ impl CreateRecordRequest {

let api_endpoint =
BitableApiV1::RecordCreate(self.app_token.clone(), self.table_id.clone());
let mut request = ApiRequest::<CreateRecordResponse>::post(&api_endpoint.to_url());
// #424:稳定 method 来自目录;叶子只附加 query/body/option
let mut request = api_endpoint.to_request::<CreateRecordResponse>();

if let Some(ref user_id_type) = self.user_id_type {
request = request.query("user_id_type", user_id_type);
Expand Down Expand Up @@ -243,4 +244,13 @@ mod tests {

assert_eq!(request.fields, fields);
}

#[test]
fn test_create_uses_post_from_catalog_424() {
// 叶子现在委托 method 给 catalog
let ep =
crate::common::api_endpoints::BitableApiV1::RecordCreate("app".into(), "tbl".into());
let req: openlark_core::api::ApiRequest<CreateRecordResponse> = ep.to_request();
assert_eq!(req.method(), &openlark_core::api::HttpMethod::Post);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! docPath: <https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table-record/delete>

use openlark_core::{
api::{ApiRequest, ApiResponseTrait, ResponseFormat},
api::{ApiResponseTrait, ResponseFormat},
config::Config,
error::SDKResult,
http::Transport,
Expand Down Expand Up @@ -92,7 +92,8 @@ impl DeleteRecordRequest {

let api_endpoint =
BitableApiV1::RecordDelete(self.app_token, self.table_id, self.record_id);
let request = ApiRequest::<DeleteRecordResponse>::delete(&api_endpoint.to_url());
// #424: method 来自 catalog,保持 path+method+auth locality
let request = api_endpoint.to_request::<DeleteRecordResponse>();

let response = Transport::request(request, &self.config, Some(option)).await?;
extract_response_data(response, "删除记录")
Expand Down Expand Up @@ -206,4 +207,17 @@ mod tests {
fn test_response_trait() {
assert_eq!(DeleteRecordResponse::data_format(), ResponseFormat::Data);
}

#[test]
fn test_delete_uses_delete_method_from_catalog() {
// 验证迁移后叶子使用 catalog 的 method(#424)
let ep = crate::common::api_endpoints::BitableApiV1::RecordDelete(
"app".into(),
"tbl".into(),
"rec".into(),
);
let req: openlark_core::api::ApiRequest<DeleteRecordResponse> = ep.to_request();
assert_eq!(req.method(), &openlark_core::api::HttpMethod::Delete);
assert!(ep.to_url().contains("/records/rec"));
}
}
Loading
Loading