-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy patherror.rs
157 lines (136 loc) · 4.41 KB
/
error.rs
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Error types and transformations
use actix_web::{
dev::{HttpResponseBuilder, ServiceResponse},
error::ResponseError,
http::StatusCode,
middleware::errhandlers::ErrorHandlerResponse,
HttpResponse, Result,
};
use backtrace::Backtrace;
use serde::ser::SerializeMap;
use serde::{Serialize, Serializer};
use std::error::Error;
use std::fmt::{self, Display};
use thiserror::Error;
/// Common `Result` type.
pub type ApiResult<T> = Result<T, ApiError>;
/// How long the client should wait before retrying a conflicting write.
pub const RETRY_AFTER: u8 = 10;
/// The main error type.
#[derive(Debug)]
pub struct ApiError {
pub kind: ApiErrorKind,
pub backtrace: Backtrace,
}
impl ApiError {
/// Render a 404 response
pub fn render_404<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
// Replace the outbound error message with our own.
let resp = HttpResponseBuilder::new(StatusCode::NOT_FOUND).finish();
Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
res.request().clone(),
resp.into_body(),
)))
}
}
/// The possible errors this application could encounter
#[derive(Debug, Error)]
pub enum ApiErrorKind {
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
Metrics(#[from] cadence::MetricError),
#[error("{0}")]
Internal(String),
}
impl ApiErrorKind {
/// Get the associated HTTP status code
pub fn status(&self) -> StatusCode {
match self {
ApiErrorKind::Io(_) | ApiErrorKind::Metrics(_) | ApiErrorKind::Internal(_) => {
StatusCode::INTERNAL_SERVER_ERROR
}
}
}
}
// Print out the error and backtrace, including source errors
impl Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error: {}\nBacktrace: {:?}", self.kind, self.backtrace)?;
// Go down the chain of errors
let mut error: &dyn Error = &self.kind;
while let Some(source) = error.source() {
write!(f, "\n\nCaused by: {}", source)?;
error = source;
}
Ok(())
}
}
impl Error for ApiError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.kind.source()
}
}
// Forward From impls to ApiError from ApiErrorKind. Because From is reflexive,
// this impl also takes care of From<ApiErrorKind>.
impl<T> From<T> for ApiError
where
ApiErrorKind: From<T>,
{
fn from(item: T) -> Self {
ApiError {
kind: ApiErrorKind::from(item),
backtrace: Backtrace::new(),
}
}
}
impl From<actix_web::error::BlockingError<ApiError>> for ApiError {
fn from(inner: actix_web::error::BlockingError<ApiError>) -> Self {
match inner {
actix_web::error::BlockingError::Error(e) => e,
actix_web::error::BlockingError::Canceled => {
ApiErrorKind::Internal("Db threadpool operation canceled".to_owned()).into()
}
}
}
}
impl From<ApiError> for HttpResponse {
fn from(inner: ApiError) -> Self {
ResponseError::error_response(&inner)
}
}
impl ResponseError for ApiError {
fn error_response(&self) -> HttpResponse {
// To return a descriptive error response, this would work. We do not
// unfortunately do that so that we can retain Sync 1.1 backwards compatibility
// as the Python one does.
// HttpResponse::build(self.status).json(self)
//
// So instead we translate our error to a backwards compatible one
HttpResponse::build(self.kind.status())
.header("Retry-After", RETRY_AFTER.to_string())
.finish()
}
}
// TODO: Use the same schema as documented here?
// https://autopush.readthedocs.io/en/latest/http.html#response
impl Serialize for ApiError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let status = self.kind.status();
let size = if status == StatusCode::UNAUTHORIZED {
2
} else {
3
};
let mut map = serializer.serialize_map(Some(size))?;
map.serialize_entry("status", &status.as_u16())?;
map.serialize_entry("reason", status.canonical_reason().unwrap_or(""))?;
if status != StatusCode::UNAUTHORIZED {
map.serialize_entry("errors", &self.kind.to_string())?;
}
map.end()
}
}