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
110
111
112
113
114
use crate::couch::CouchError;
use oas_common::{DecodingError, EncodingError, ValidationError};
use okapi::openapi3::Responses;
use rocket::http::Status;
use rocket::response::Responder;
use rocket::{response, response::content, Request};
use rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::response::OpenApiResponderInner;
use rocket_okapi::util::add_schema_response;
use schemars::JsonSchema;
use serde::Serialize;
use thiserror::Error;
pub type Result<T> = std::result::Result<rocket::serde::json::Json<T>, AppError>;
#[derive(Error, Debug)]
pub enum AppError {
#[error("{0}")]
DecodingError(#[from] DecodingError),
#[error("{0}")]
EncodingError(#[from] EncodingError),
#[error("{0}")]
Couch(#[from] CouchError),
#[error("{0}")]
Serde(#[from] serde_json::Error),
#[error("{0}")]
Other(String),
#[error("{0}")]
Elastic(#[from] elasticsearch::Error),
#[error("HTTP error: {0} {1}")]
Http(Status, String),
#[error("Validation error: {0}")]
ValidationError(ValidationError),
#[error("Unauthorized")]
Unauthorized,
}
impl From<anyhow::Error> for AppError {
fn from(err: anyhow::Error) -> Self {
Self::Other(format!("{}", err))
}
}
impl<'r> Responder<'r, 'static> for AppError {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
log::debug!("{:?}", self);
let code = match &self {
AppError::Couch(err) => map_u16_status(err.status_code()),
AppError::Http(code, _) => *code,
AppError::EncodingError(_) => Status::BadRequest,
AppError::ValidationError(_) => Status::UnprocessableEntity,
AppError::Elastic(err) => map_u16_status(err.status_code().map(|code| code.as_u16())),
AppError::Unauthorized => Status::Unauthorized,
_ => Status::InternalServerError,
};
let message = match &self {
AppError::Http(_code, message) => message.clone(),
_ => format!("{}", self),
};
let response = ErrorResponse { error: message };
let json_string = serde_json::to_string(&response).unwrap();
let res = content::Json(json_string).respond_to(req);
match res {
Err(res) => Err(res),
Ok(mut res) => {
res.set_status(code);
if let Self::Unauthorized = self {
let header_value = format!(
r#"Basic realm="{}", charset="UTF-8""#,
"Please enter user username and password"
);
let header = rocket::http::Header::new(
http::header::WWW_AUTHENTICATE.as_str(),
header_value,
);
res.set_header(header);
}
Ok(res)
}
}
}
}
fn map_u16_status(status: Option<u16>) -> Status {
status
.map(|code| Status::from_code(code).unwrap())
.unwrap_or(Status::InternalServerError)
}
#[derive(Serialize, JsonSchema, Debug, Default)]
struct ErrorResponse {
error: String,
}
impl OpenApiResponderInner for AppError {
fn responses(gen: &mut OpenApiGenerator) -> rocket_okapi::Result<Responses> {
let mut responses = Responses::default();
let schema = gen.json_schema::<ErrorResponse>();
add_schema_response(&mut responses, 500, "application/json", schema)?;
Ok(responses)
}
}