use serde::Deserialize;
#[derive(Debug, Clone, thiserror::Error)]
#[error("status={status}, body={body:?}")]
pub struct HttpError {
pub status: u16,
pub body: String,
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("status={status:?}, code={code:?}, id={id:?}, message={message:?}")]
pub struct KintoneError {
pub status: u16,
pub code: String,
pub id: String,
pub message: String,
}
#[derive(Deserialize)]
struct KintoneErrorJson {
pub code: String,
pub id: String,
pub message: String,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ApiError {
#[error("i/o error: {0}")]
Io(#[from] std::io::Error),
#[error("http error: {0}")]
Http(#[from] HttpError),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("kintone error: {0}")]
Kintone(#[from] KintoneError),
}
impl From<ureq::Error> for ApiError {
fn from(err: ureq::Error) -> Self {
Self::Io(err.into_io())
}
}
impl From<http::Error> for ApiError {
fn from(err: http::Error) -> Self {
Self::Io(ureq::Error::from(err).into_io())
}
}
fn is_json_response<T>(response: &http::Response<T>) -> bool {
let Some(content_type) = response.headers().get(http::header::CONTENT_TYPE) else {
return false;
};
let Ok(content_type) = content_type.to_str() else {
return false;
};
let Ok(content_type) = content_type.parse::<mime::Mime>() else {
return false;
};
content_type.essence_str() == "application/json"
}
impl From<http::Response<ureq::Body>> for ApiError {
fn from(mut response: http::Response<ureq::Body>) -> ApiError {
const MAX_JSON_SIZE: u64 = 10 * 1024 * 1024;
if !is_json_response(&response) {
let status = response.status().as_u16();
return match response.body_mut().read_to_string() {
Ok(body) => ApiError::Http(HttpError { status, body }),
Err(e) => ApiError::Io(e.into_io()),
};
};
let body = match response.body_mut().with_config().limit(MAX_JSON_SIZE).read_to_vec() {
Ok(body) => body,
Err(e) => return e.into(),
};
match serde_json::from_slice::<KintoneErrorJson>(&body) {
Ok(error_json) => KintoneError {
status: response.status().as_u16(),
code: error_json.code,
id: error_json.id,
message: error_json.message,
}
.into(),
Err(e) => e.into(),
}
}
}