Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Breaking

- **meeting_room 17 叶 `execute()` 返回类型 `Value` → typed Response**(#349):
`meeting_room/{building,room,country,district,freebusy,instance,summary}` 全部
`execute()` / `execute_with_options()` 从 `SDKResult<serde_json::Value>` 改为 typed
Response(如 `ListBuildingResponse` / `BatchGetFreebusyResponse` / `DeleteBuildingResponse`)。
字段对齐飞书历史版文档 Response body example(该类文档为 GuideDocumentType,无结构化
apiSchema);无 `data` 的写操作(update/delete/instance reply)在缺省时返回
`Default` 空响应。**v0.18 breaking**:调用方需按新 typed Response 取值,不可再按
`resp["field"]` 索引。请求 body 仍为 `serde_json::Value`(后续可独立 typed)。

- **删除 `openlark-user` 幻影 `SystemStatusResource::get()` / `SystemStatusGetRequest`(#377)**:
飞书 `personal_settings/v1/system_status` 仅有 6 个 API(batch_close / batch_open /
create / delete / list / patch),无 `get`("获取系统状态"对应 `list`)。既有
Expand Down
23 changes: 17 additions & 6 deletions crates/openlark-meeting/src/meeting_room/building/batch_get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use openlark_core::{

use crate::common::api_utils::extract_response_data;
use crate::endpoints::MEETING_ROOM;
use crate::meeting_room::responses::BatchGetBuildingResponse;

/// 查询建筑物详情请求
pub struct BatchGetBuildingRequest {
Expand All @@ -33,14 +34,17 @@ impl BatchGetBuildingRequest {
/// 执行请求
///
/// docPath: <https://open.feishu.cn/document/server-docs/historic-version/meeting_room-v1/api-reference/query-building-details>
pub async fn execute(self) -> SDKResult<serde_json::Value> {
pub async fn execute(self) -> SDKResult<BatchGetBuildingResponse> {
self.execute_with_options(RequestOption::default()).await
}

/// 执行请求(带选项)
pub async fn execute_with_options(self, option: RequestOption) -> SDKResult<serde_json::Value> {
pub async fn execute_with_options(
self,
option: RequestOption,
) -> SDKResult<BatchGetBuildingResponse> {
// url: GET:/open-apis/meeting_room/building/batch_get
let mut req: ApiRequest<serde_json::Value> =
let mut req: ApiRequest<BatchGetBuildingResponse> =
ApiRequest::get(format!("{MEETING_ROOM}/building/batch_get"));
for (k, v) in self.query_params {
req = req.query(k, v);
Expand All @@ -55,7 +59,7 @@ impl BatchGetBuildingRequest {
mod tests {
use super::*;

/// 端到端:GET .../meeting_room/building/batch_get → 裸 Value(单层 resp["field"])
/// 端到端:GET .../meeting_room/building/batch_get → BatchGetBuildingResponse
#[tokio::test]
async fn test_batch_get_building_returns_data_on_success() {
use serde_json::json;
Expand All @@ -71,7 +75,13 @@ mod tests {
"msg": "success",
"data": {
"buildings": [
{ "building_id": "bldg_001", "name": "1号楼" }
{
"building_id": "bldg_001",
"name": "1号楼",
"floors": ["F1"],
"country_id": "1814991",
"district_id": "2034437"
}
]
}
})))
Expand All @@ -90,7 +100,8 @@ mod tests {
.execute()
.await
.expect("查询建筑物详情应成功");
assert_eq!(resp["buildings"][0]["building_id"], json!("bldg_001"));
assert_eq!(resp.buildings[0].building_id, "bldg_001");
assert_eq!(resp.buildings[0].name.as_deref(), Some("1号楼"));

let received = server.received_requests().await.unwrap_or_default();
assert_eq!(received.len(), 1);
Expand Down
31 changes: 25 additions & 6 deletions crates/openlark-meeting/src/meeting_room/building/batch_get_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use openlark_core::{
SDKResult, api::ApiRequest, config::Config, http::Transport, req_option::RequestOption,
};

use crate::meeting_room::responses::BatchGetBuildingIdResponse;
use crate::{common::api_utils::extract_response_data, endpoints::MEETING_ROOM};

/// 查询建筑物ID请求
Expand All @@ -32,14 +33,17 @@ impl BatchGetBuildingIdRequest {
/// 执行请求
///
/// docPath: <https://open.feishu.cn/document/server-docs/historic-version/meeting_room-v1/api-reference/obtain-building-id>
pub async fn execute(self) -> SDKResult<serde_json::Value> {
pub async fn execute(self) -> SDKResult<BatchGetBuildingIdResponse> {
self.execute_with_options(RequestOption::default()).await
}

/// 执行请求(带选项)
pub async fn execute_with_options(self, option: RequestOption) -> SDKResult<serde_json::Value> {
pub async fn execute_with_options(
self,
option: RequestOption,
) -> SDKResult<BatchGetBuildingIdResponse> {
// url: GET:/open-apis/meeting_room/building/batch_get_id
let mut req: ApiRequest<serde_json::Value> =
let mut req: ApiRequest<BatchGetBuildingIdResponse> =
ApiRequest::get(format!("{MEETING_ROOM}/building/batch_get_id"));
for (k, v) in self.query_params {
req = req.query(k, v);
Expand All @@ -53,7 +57,7 @@ impl BatchGetBuildingIdRequest {
mod tests {
use super::*;

/// 端到端:GET .../meeting_room/building/batch_get_id → 裸 Value(单层 resp["field"])
/// 端到端:GET .../meeting_room/building/batch_get_id → BatchGetBuildingIdResponse
#[tokio::test]
async fn test_batch_get_id_building_returns_data_on_success() {
use serde_json::json;
Expand All @@ -67,7 +71,18 @@ mod tests {
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"code": 0,
"msg": "success",
"data": { "building_ids": ["bldg_001", "bldg_002"] }
"data": {
"buildings": [
{
"building_id": "bldg_001",
"custom_bulding_id": "test01"
},
{
"building_id": "bldg_002",
"custom_bulding_id": "test02"
}
]
}
})))
.mount(&server)
.await;
Expand All @@ -84,7 +99,11 @@ mod tests {
.execute()
.await
.expect("查询建筑物ID应成功");
assert_eq!(resp["building_ids"][0], json!("bldg_001"));
assert_eq!(resp.buildings[0].building_id, "bldg_001");
assert_eq!(
resp.buildings[0].custom_bulding_id.as_deref(),
Some("test01")
);

let received = server.received_requests().await.unwrap_or_default();
assert_eq!(received.len(), 1);
Expand Down
40 changes: 37 additions & 3 deletions crates/openlark-meeting/src/meeting_room/building/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use openlark_core::{

use crate::common::api_endpoints::MeetingRoomApi;
use crate::common::api_utils::{extract_response_data, serialize_params};
use crate::meeting_room::responses::CreateBuildingResponse;

/// 创建建筑物请求
pub struct CreateBuildingRequest {
Expand All @@ -25,7 +26,7 @@ impl CreateBuildingRequest {
/// 说明:该接口请求体字段较多,建议直接按文档构造 JSON 传入。
///
/// docPath: <https://open.feishu.cn/document/server-docs/historic-version/meeting_room-v1/api-reference/create-building>
pub async fn execute(self, body: serde_json::Value) -> SDKResult<serde_json::Value> {
pub async fn execute(self, body: serde_json::Value) -> SDKResult<CreateBuildingResponse> {
self.execute_with_options(body, RequestOption::default())
.await
}
Expand All @@ -35,10 +36,10 @@ impl CreateBuildingRequest {
self,
body: serde_json::Value,
option: RequestOption,
) -> SDKResult<serde_json::Value> {
) -> SDKResult<CreateBuildingResponse> {
// url: POST:/open-apis/meeting_room/buildings
let api_endpoint = MeetingRoomApi::BuildingCreate;
let req: ApiRequest<serde_json::Value> =
let req: ApiRequest<CreateBuildingResponse> =
ApiRequest::post(api_endpoint.to_url()).body(serialize_params(&body, "创建建筑物")?);

let resp = Transport::request(req, &self.config, Some(option)).await?;
Expand Down Expand Up @@ -74,4 +75,37 @@ mod tests {
assert_eq!(request.config.app_id(), "test_app");
assert_eq!(request.config.app_secret(), "test_secret");
}

/// 端到端:POST .../meeting_room/buildings → CreateBuildingResponse。
#[tokio::test]
async fn test_create_building_returns_typed_on_success() {
use serde_json::json;
use wiremock::MockServer;
use wiremock::matchers::{method, path};
use wiremock::{Mock, ResponseTemplate};

let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/open-apis/meeting_room/buildings"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"code": 0,
"msg": "success",
"data": { "building_id": "omb_8ec170b937536a5d87c23b418b83f9bb" }
})))
.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();

let resp = CreateBuildingRequest::new(config)
.execute(json!({ "name": "测试建筑" }))
.await
.expect("创建建筑物应成功");
assert_eq!(resp.building_id, "omb_8ec170b937536a5d87c23b418b83f9bb");
}
}
31 changes: 21 additions & 10 deletions crates/openlark-meeting/src/meeting_room/building/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use openlark_core::{
};

use crate::common::api_endpoints::MeetingRoomApi;
use crate::common::api_utils::extract_response_data;
use crate::meeting_room::responses::DeleteBuildingResponse;

/// 删除建筑物请求
pub struct DeleteBuildingRequest {
Expand All @@ -34,31 +34,43 @@ impl DeleteBuildingRequest {
/// 执行请求
///
/// docPath: <https://open.feishu.cn/document/server-docs/historic-version/meeting_room-v1/api-reference/delete-building>
pub async fn execute(self) -> SDKResult<serde_json::Value> {
pub async fn execute(self) -> SDKResult<DeleteBuildingResponse> {
self.execute_with_options(RequestOption::default()).await
}

/// 执行请求(带选项)
pub async fn execute_with_options(self, option: RequestOption) -> SDKResult<serde_json::Value> {
pub async fn execute_with_options(
self,
option: RequestOption,
) -> SDKResult<DeleteBuildingResponse> {
validate_required!(self.building_id, "building_id 不能为空");

// url: DELETE:/open-apis/meeting_room/buildings/:building_id
let api_endpoint = MeetingRoomApi::BuildingDelete(self.building_id.clone());
let req: ApiRequest<serde_json::Value> =
ApiRequest::delete(api_endpoint.to_url()).body(serde_json::json!({
let req: ApiRequest<DeleteBuildingResponse> = ApiRequest::delete(api_endpoint.to_url())
.body(serde_json::json!({
"building_id": self.building_id
}));

let resp = Transport::request(req, &self.config, Some(option)).await?;
extract_response_data(resp, "删除建筑物")
// 官方示例无 data 字段;成功且缺省时返回空响应。
if !resp.is_success() {
return Err(openlark_core::error::api_error(
resp.code() as u16,
"删除建筑物",
resp.message().to_string(),
resp.raw().request_id.clone(),
));
}
Ok(resp.data.unwrap_or_default())
}
}

#[cfg(test)]
mod tests {
use super::*;

/// 端到端:DELETE .../meeting_room/buildings/{building_id} → 裸 Value(单层 resp["field"])
/// 端到端:DELETE .../meeting_room/buildings/{building_id} → DeleteBuildingResponse
#[tokio::test]
async fn test_delete_building_returns_data_on_success() {
use serde_json::json;
Expand All @@ -71,8 +83,7 @@ mod tests {
.and(path("/open-apis/meeting_room/buildings/bldg_001"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"code": 0,
"msg": "success",
"data": { "success": true }
"msg": "success"
})))
.mount(&server)
.await;
Expand All @@ -89,7 +100,7 @@ mod tests {
.execute()
.await
.expect("删除建筑物应成功");
assert_eq!(resp["success"], json!(true));
assert_eq!(resp, DeleteBuildingResponse {});

let received = server.received_requests().await.unwrap_or_default();
assert_eq!(received.len(), 1);
Expand Down
60 changes: 57 additions & 3 deletions crates/openlark-meeting/src/meeting_room/building/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use openlark_core::{

use crate::common::api_utils::extract_response_data;
use crate::endpoints::MEETING_ROOM;
use crate::meeting_room::responses::ListBuildingResponse;

/// 获取建筑物列表请求
pub struct ListBuildingRequest {
Expand All @@ -33,14 +34,17 @@ impl ListBuildingRequest {
/// 执行请求
///
/// docPath: <https://open.feishu.cn/document/server-docs/historic-version/meeting_room-v1/api-reference/obtain-building-list>
pub async fn execute(self) -> SDKResult<serde_json::Value> {
pub async fn execute(self) -> SDKResult<ListBuildingResponse> {
self.execute_with_options(RequestOption::default()).await
}

/// 执行请求(带选项)
pub async fn execute_with_options(self, option: RequestOption) -> SDKResult<serde_json::Value> {
pub async fn execute_with_options(
self,
option: RequestOption,
) -> SDKResult<ListBuildingResponse> {
// url: GET:/open-apis/meeting_room/building/list
let mut req: ApiRequest<serde_json::Value> =
let mut req: ApiRequest<ListBuildingResponse> =
ApiRequest::get(format!("{MEETING_ROOM}/building/list"));
for (k, v) in self.query_params {
req = req.query(k, v);
Expand All @@ -65,4 +69,54 @@ mod tests {
.query_param("key1".to_string(), "value1".to_string());
let _ = request;
}

/// 端到端:GET .../meeting_room/building/list → ListBuildingResponse。
#[tokio::test]
async fn test_list_building_returns_typed_on_success() {
use serde_json::json;
use wiremock::MockServer;
use wiremock::matchers::{method, path};
use wiremock::{Mock, ResponseTemplate};

let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/open-apis/meeting_room/building/list"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"code": 0,
"msg": "success",
"data": {
"page_token": "1",
"has_more": true,
"buildings": [{
"building_id": "omb_8ec170b937536a5d87c23b418b83f9bb",
"name": "Building name",
"description": "Some description",
"floors": ["F1"],
"country_id": "country id",
"district_id": "district id"
}]
}
})))
.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();

let resp = ListBuildingRequest::new(config)
.query_param("page_size", "10")
.execute()
.await
.expect("获取建筑物列表应成功");
assert_eq!(resp.has_more, Some(true));
assert_eq!(
resp.buildings[0].building_id,
"omb_8ec170b937536a5d87c23b418b83f9bb"
);
assert_eq!(resp.buildings[0].name.as_deref(), Some("Building name"));
}
}
Loading
Loading