Skip to main content

openlark_workflow/common/
api_utils.rs

1/// API通用工具函数
2///
3/// 提供API实现的通用工具和辅助函数,减少重复代码,提高一致性
4use openlark_core::{SDKResult, error};
5
6const ERROR_COMPONENT: &str = "openlark-workflow";
7
8fn attach_standard_error_context(
9    err: openlark_core::error::CoreError,
10    operation: &str,
11    resource: &str,
12    request_id: Option<String>,
13) -> openlark_core::error::CoreError {
14    err.with_operation(operation, ERROR_COMPONENT)
15        .map_context(|ctx| {
16            ctx.add_context("resource", resource);
17            if let Some(request_id) = request_id.filter(|value| !value.trim().is_empty()) {
18                ctx.set_request_id(request_id);
19            }
20        })
21}
22
23/// 创建“响应 data 为空”的标准错误。
24pub fn missing_response_data_error(
25    resource: &str,
26    request_id: Option<String>,
27) -> openlark_core::error::CoreError {
28    attach_standard_error_context(
29        error::validation_error("response.data", "服务器没有返回有效的数据"),
30        "extract_response_data",
31        resource,
32        request_id,
33    )
34}
35
36/// 创建“请求参数序列化失败”的标准错误。
37pub fn request_serialization_error(
38    resource: &str,
39    source: impl std::fmt::Display,
40) -> openlark_core::error::CoreError {
41    attach_standard_error_context(
42        error::validation_error("request.params", format!("无法序列化请求参数: {source}")),
43        "serialize_params",
44        resource,
45        None,
46    )
47}
48
49/// 标准化API响应数据提取
50///
51/// # 参数
52/// - `response`: API响应对象
53/// - `context`: 错误上下文描述
54///
55/// # 返回
56/// - `Ok(T)`: 成功提取的数据
57/// - `Err(SDKError)`: 包含上下文信息的错误
58pub fn extract_response_data<T>(
59    response: openlark_core::api::Response<T>,
60    context: &str,
61) -> SDKResult<T> {
62    response.data.ok_or_else(|| {
63        missing_response_data_error(context, response.raw_response.request_id.clone())
64    })
65}
66
67/// 标准化参数序列化错误处理
68///
69/// # 参数
70/// - `params`: 要序列化的参数
71/// - `context`: 序列化上下文
72///
73/// # 返回
74/// - `Ok(serde_json::Value)`: 序列化成功
75/// - `Err(SDKError)`: 包含详细信息的序列化错误
76pub fn serialize_params<T: serde::Serialize>(
77    params: &T,
78    context: &str,
79) -> SDKResult<serde_json::Value> {
80    serde_json::to_value(params).map_err(|e| request_serialization_error(context, e))
81}
82
83/// 标准化API端点URL生成辅助宏
84///
85/// # 使用示例
86/// ```rust
87/// # fn main() {
88/// // openlark_task 模块暂未实现
89/// // use openlark_task::api_url;
90///
91/// // let task_guid = "task_guid";
92/// // let url = api_url!("/open-apis/task/v2/tasks/{}", task_guid);
93/// // assert!(url.contains(task_guid));
94/// # }
95/// ```
96#[macro_export]
97macro_rules! api_url {
98    ($base_url:expr) => {
99        $base_url.to_string()
100    };
101    ($base_url:expr, $($arg:expr),+) => {
102        format!($base_url, $($arg),+)
103    };
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use openlark_core::error::ErrorTrait;
110    use serde::Serialize;
111
112    #[derive(Serialize)]
113    struct TestParams {
114        name: String,
115        value: i32,
116    }
117
118    struct FailingParams;
119
120    impl Serialize for FailingParams {
121        fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
122        where
123            S: serde::Serializer,
124        {
125            Err(serde::ser::Error::custom("boom"))
126        }
127    }
128
129    #[test]
130    fn test_serialize_params_success() {
131        let params = TestParams {
132            name: "test".to_string(),
133            value: 42,
134        };
135        let result = serialize_params(&params, "查询任务");
136        assert!(result.is_ok());
137        let json = result.expect("序列化参数应成功");
138        assert_eq!(json["name"], "test");
139        assert_eq!(json["value"], 42);
140    }
141
142    #[test]
143    fn test_serialize_params_adds_standard_error_context() {
144        let err = serialize_params(&FailingParams, "查询任务").unwrap_err();
145        assert_eq!(err.context().operation(), Some("serialize_params"));
146        assert_eq!(err.context().component(), Some(ERROR_COMPONENT));
147        assert_eq!(err.context().get_context("resource"), Some("查询任务"));
148    }
149
150    #[test]
151    fn test_extract_response_data_adds_request_id_and_resource_context() {
152        let response = openlark_core::api::Response::new(
153            None::<serde_json::Value>,
154            openlark_core::api::RawResponse {
155                request_id: Some("req-wf-123".to_string()),
156                ..Default::default()
157            },
158        );
159
160        let err = extract_response_data(response, "查询任务").unwrap_err();
161        assert_eq!(err.context().operation(), Some("extract_response_data"));
162        assert_eq!(err.context().component(), Some(ERROR_COMPONENT));
163        assert_eq!(err.context().get_context("resource"), Some("查询任务"));
164        assert_eq!(err.context().request_id(), Some("req-wf-123"));
165    }
166
167    #[test]
168    fn test_missing_response_data_error_reuses_standard_shape() {
169        let err = missing_response_data_error("审批任务查询", Some("req-wf-456".to_string()));
170        assert_eq!(err.context().operation(), Some("extract_response_data"));
171        assert_eq!(err.context().component(), Some(ERROR_COMPONENT));
172        assert_eq!(err.context().get_context("resource"), Some("审批任务查询"));
173        assert_eq!(err.context().request_id(), Some("req-wf-456"));
174    }
175}