Skip to main content

kintone/
error.rs

1//! # Error Types
2//!
3//! This module defines error types used throughout the kintone crate.
4//! All API operations return `Result<T, ApiError>` where errors can be categorized
5//! into I/O errors or HTTP-specific errors.
6
7use serde::Deserialize;
8
9/// HTTP-specific error containing status code and response body.
10///
11/// This error type is used when the HTTP request completes but returns
12/// an error status code (4xx, 5xx). It includes both the status code
13/// and the response body for detailed error analysis.
14///
15/// # Fields
16/// * `status` - The HTTP status code (e.g., 404, 500)
17/// * `body` - The response body as a string, which may contain error details from Kintone
18#[derive(Debug, Clone, thiserror::Error)]
19#[error("status={status}, body={body:?}")]
20pub struct HttpError {
21    pub status: u16,
22    pub body: String,
23}
24
25#[derive(Debug, Clone, thiserror::Error)]
26#[error("status={status:?}, code={code:?}, id={id:?}, message={message:?}")]
27pub struct KintoneError {
28    pub status: u16,
29    pub code: String,
30    pub id: String,
31    pub message: String,
32}
33
34#[derive(Deserialize)]
35struct KintoneErrorJson {
36    pub code: String,
37    pub id: String,
38    pub message: String,
39}
40
41/// The main error type for all Kintone API operations.
42///
43/// This enum represents all possible errors that can occur when interacting
44/// with the Kintone API. It categorizes errors into I/O errors (network issues,
45/// connection problems) and HTTP errors (API-specific error responses).
46///
47/// # Variants
48/// * `Io` - I/O related errors such as network connectivity issues
49/// * `Http` - HTTP-specific errors with status codes and response bodies
50#[derive(Debug, thiserror::Error)]
51#[non_exhaustive]
52pub enum ApiError {
53    #[error("i/o error: {0}")]
54    Io(#[from] std::io::Error),
55
56    #[error("http error: {0}")]
57    Http(#[from] HttpError),
58
59    #[error("JSON error: {0}")]
60    Json(#[from] serde_json::Error),
61
62    #[error("kintone error: {0}")]
63    Kintone(#[from] KintoneError),
64}
65
66impl From<ureq::Error> for ApiError {
67    fn from(err: ureq::Error) -> Self {
68        Self::Io(err.into_io())
69    }
70}
71
72impl From<http::Error> for ApiError {
73    fn from(err: http::Error) -> Self {
74        Self::Io(ureq::Error::from(err).into_io())
75    }
76}
77
78fn is_json_response<T>(response: &http::Response<T>) -> bool {
79    let Some(content_type) = response.headers().get(http::header::CONTENT_TYPE) else {
80        return false;
81    };
82    let Ok(content_type) = content_type.to_str() else {
83        return false;
84    };
85    let Ok(content_type) = content_type.parse::<mime::Mime>() else {
86        return false;
87    };
88    content_type.essence_str() == "application/json"
89}
90
91impl From<http::Response<ureq::Body>> for ApiError {
92    fn from(mut response: http::Response<ureq::Body>) -> ApiError {
93        const MAX_JSON_SIZE: u64 = 10 * 1024 * 1024;
94
95        if !is_json_response(&response) {
96            let status = response.status().as_u16();
97            return match response.body_mut().read_to_string() {
98                Ok(body) => ApiError::Http(HttpError { status, body }),
99                Err(e) => ApiError::Io(e.into_io()),
100            };
101        };
102        // If the response is JSON, attempt to parse it as KintoneError.
103        let body = match response.body_mut().with_config().limit(MAX_JSON_SIZE).read_to_vec() {
104            Ok(body) => body,
105            Err(e) => return e.into(),
106        };
107        match serde_json::from_slice::<KintoneErrorJson>(&body) {
108            Ok(error_json) => KintoneError {
109                status: response.status().as_u16(),
110                code: error_json.code,
111                id: error_json.id,
112                message: error_json.message,
113            }
114            .into(),
115            Err(e) => e.into(),
116        }
117    }
118}