1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use std::{error::Error, fmt};
use reqwest::{blocking::Response, StatusCode};
use serde::Deserialize;
use crate::error::Result;
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct ApiResponseError {
status: usize,
title: String,
detail: String,
}
pub fn map_api_error(resp: Response) -> Result<Response> {
if let Err(source) = resp.error_for_status_ref() {
let kind = source.status().into();
return Err(
ApiError { message: resp.json::<ApiResponseError>()?.detail, source, kind }.into()
);
}
Ok(resp)
}
#[derive(Debug)]
#[non_exhaustive]
pub struct ApiError {
pub message: String,
pub source: reqwest::Error,
pub kind: ApiErrorKind,
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.message.is_empty() {
write!(f, "{}", self.kind)
} else {
write!(f, "{}", self.message)
}
}
}
impl Error for ApiError {
fn source(&self) -> Option<&(dyn Error + 'static)> { Some(&self.source) }
}
impl PartialEq for ApiError {
fn eq(&self, other: &Self) -> bool { self.kind == other.kind }
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum ApiErrorKind {
UnimplementedHttpStatus(StatusCode),
BadRequest,
Unauthorized,
Forbidden,
NotFound,
Conflict,
InternalServerError,
ServiceUnavailable,
Unknown,
}
impl fmt::Display for ApiErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ApiErrorKind::UnimplementedHttpStatus(code) => write!(f, "HTTP {code}"),
ApiErrorKind::BadRequest => write!(f, "bad HTTP request"),
ApiErrorKind::Unauthorized => write!(f, "unauthorized"),
ApiErrorKind::Forbidden => write!(f, "permission denied"),
ApiErrorKind::NotFound => write!(f, "resource does not exist"),
ApiErrorKind::Conflict => write!(f, "HTTP conflict"),
ApiErrorKind::InternalServerError => write!(f, "internal error"),
ApiErrorKind::ServiceUnavailable => write!(f, "service is unavailable"),
ApiErrorKind::Unknown => write!(f, "unknown fatal error"),
}
}
}
impl From<Option<StatusCode>> for ApiErrorKind {
fn from(code: Option<StatusCode>) -> Self {
use ApiErrorKind::*;
match code {
Some(StatusCode::BAD_REQUEST) => BadRequest,
Some(StatusCode::UNAUTHORIZED) => Unauthorized,
Some(StatusCode::FORBIDDEN) => Forbidden,
Some(StatusCode::NOT_FOUND) => NotFound,
Some(StatusCode::CONFLICT) => Conflict,
Some(StatusCode::INTERNAL_SERVER_ERROR) => InternalServerError,
Some(StatusCode::SERVICE_UNAVAILABLE) => ServiceUnavailable,
Some(code) => UnimplementedHttpStatus(code),
None => Unknown,
}
}
}