1use serde::Deserialize;
8
9#[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#[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 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}