Skip to main content

openlark_docs/common/
api_utils.rs

1/// API通用工具函数
2///
3/// 提供API实现的通用工具和辅助函数,减少重复代码,提高一致性
4use openlark_core::{SDKResult, error};
5
6const ERROR_COMPONENT: &str = "openlark-docs";
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/// 标准化API响应数据提取
49///
50/// # 参数
51/// - `response`: API响应对象
52/// - `context`: 错误上下文描述
53///
54/// # 返回
55/// - `Ok(T)`: 成功提取的数据
56/// - `Err(SDKError)`: 包含上下文信息的错误
57pub fn extract_response_data<T>(
58    response: openlark_core::api::ApiResponse<T>,
59    context: &str,
60) -> SDKResult<T> {
61    response.data.ok_or_else(|| {
62        missing_response_data_error(context, response.raw_response.request_id.clone())
63    })
64}
65
66/// 标准化参数验证错误处理
67///
68/// # 参数
69/// - `field_name`: 字段名称
70/// - `field_value`: 字段值
71/// - `error_message`: 错误消息
72///
73/// # 返回
74/// - `Ok(())`: 验证通过
75/// - `Err(SDKError)`: 验证失败错误
76pub fn validate_required_field<T: AsRef<str>>(
77    field_name: &str,
78    field_value: Option<T>,
79    error_message: &str,
80) -> SDKResult<()> {
81    match field_value {
82        Some(value) if !value.as_ref().trim().is_empty() => Ok(()),
83        _ => Err(attach_standard_error_context(
84            error::validation_error(field_name, error_message),
85            "validate_required_field",
86            field_name,
87            None,
88        )),
89    }
90}
91
92/// 标准化API端点URL生成辅助宏
93///
94/// # 使用示例
95/// ```rust
96/// use openlark_docs::api_url;
97///
98/// let doc_token = "doc_token";
99/// let spreadsheet_token = "spreadsheet_token";
100///
101/// let doc_url = api_url!("/open-apis/doc/v2/{}", doc_token);
102/// let sheet_url = api_url!("/open-apis/sheets/v2/spreadsheets/{}/values", spreadsheet_token);
103///
104/// assert!(doc_url.contains(doc_token));
105/// assert!(sheet_url.contains(spreadsheet_token));
106/// ```
107#[macro_export]
108macro_rules! api_url {
109    ($base_url:expr) => {
110        $base_url.to_string()
111    };
112    ($base_url:expr, $($arg:expr),+) => {
113        format!($base_url, $($arg),+)
114    };
115}
116
117/// 标准化参数序列化错误处理
118///
119/// # 参数
120/// - `params`: 要序列化的参数
121/// - `context`: 序列化上下文
122///
123/// # 返回
124/// - `Ok(serde_json::Value)`: 序列化成功
125/// - `Err(SDKError)`: 包含详细信息的序列化错误
126pub fn serialize_params<T: serde::Serialize>(
127    params: &T,
128    context: &str,
129) -> SDKResult<serde_json::Value> {
130    serde_json::to_value(params).map_err(|e| request_serialization_error(context, e))
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use openlark_core::error::ErrorTrait;
137    use serde::Serialize;
138
139    #[derive(Serialize)]
140    struct TestParams {
141        name: String,
142        value: i32,
143    }
144
145    struct FailingParams;
146
147    impl Serialize for FailingParams {
148        fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
149        where
150            S: serde::Serializer,
151        {
152            Err(serde::ser::Error::custom("boom"))
153        }
154    }
155
156    #[test]
157    fn test_serialize_params_success() {
158        let params = TestParams {
159            name: "test".to_string(),
160            value: 42,
161        };
162        let result = serialize_params(&params, "test");
163        assert!(result.is_ok());
164        let json = result.unwrap();
165        assert_eq!(json["name"], "test");
166        assert_eq!(json["value"], 42);
167    }
168
169    #[test]
170    fn test_serialize_params_adds_standard_error_context() {
171        let err = serialize_params(&FailingParams, "查询记录").unwrap_err();
172        assert_eq!(err.context().operation(), Some("serialize_params"));
173        assert_eq!(err.context().component(), Some(ERROR_COMPONENT));
174        assert_eq!(err.context().get_context("resource"), Some("查询记录"));
175    }
176
177    #[test]
178    fn test_extract_response_data_adds_request_id_and_resource_context() {
179        let response = openlark_core::api::ApiResponse::new(
180            None::<serde_json::Value>,
181            openlark_core::api::RawResponse {
182                request_id: Some("req-docs-123".to_string()),
183                ..Default::default()
184            },
185        );
186
187        let err = extract_response_data(response, "查询记录").unwrap_err();
188        assert_eq!(err.context().operation(), Some("extract_response_data"));
189        assert_eq!(err.context().component(), Some(ERROR_COMPONENT));
190        assert_eq!(err.context().get_context("resource"), Some("查询记录"));
191        assert_eq!(err.context().request_id(), Some("req-docs-123"));
192    }
193
194    #[test]
195    fn test_missing_response_data_error_reuses_standard_shape() {
196        let err = missing_response_data_error("获取多维表格", Some("req-docs-456".to_string()));
197        assert_eq!(err.context().operation(), Some("extract_response_data"));
198        assert_eq!(err.context().component(), Some(ERROR_COMPONENT));
199        assert_eq!(err.context().get_context("resource"), Some("获取多维表格"));
200        assert_eq!(err.context().request_id(), Some("req-docs-456"));
201    }
202
203    #[test]
204    fn test_validate_required_field_adds_standard_context() {
205        let err =
206            validate_required_field("sheet_id", None::<String>, "sheet_id 不能为空").unwrap_err();
207        assert_eq!(err.context().operation(), Some("validate_required_field"));
208        assert_eq!(err.context().component(), Some(ERROR_COMPONENT));
209        assert_eq!(err.context().get_context("resource"), Some("sheet_id"));
210    }
211}