1use serde::Serialize;
2use thiserror::Error;
3
4pub type Result<T> = std::result::Result<T, EnigmaRelayError>;
5
6#[derive(Debug, Serialize, Clone)]
7pub struct ErrorBody {
8 pub code: &'static str,
9 pub message: String,
10 #[serde(skip_serializing_if = "Option::is_none")]
11 pub details: Option<serde_json::Value>,
12}
13
14#[derive(Debug, Serialize, Clone)]
15pub struct ErrorResponse {
16 pub error: ErrorBody,
17}
18
19#[derive(Debug, Error, Clone)]
20pub enum EnigmaRelayError {
21 #[error("invalid input: {0}")]
22 InvalidInput(String),
23 #[error("rate limited")]
24 RateLimited,
25 #[error("duplicate")]
26 Duplicate,
27 #[error("quota exceeded")]
28 QuotaExceeded,
29 #[error("not found")]
30 NotFound,
31 #[error("storage error: {0}")]
32 StorageError(String),
33 #[error("config error: {0}")]
34 Config(String),
35 #[error("tls error: {0}")]
36 Tls(String),
37 #[error("feature disabled: {0}")]
38 Disabled(String),
39 #[error("internal error: {0}")]
40 Internal(String),
41}
42
43impl EnigmaRelayError {
44 pub fn into_response(self) -> ErrorResponse {
45 match self.clone() {
46 EnigmaRelayError::InvalidInput(msg) => ErrorResponse {
47 error: ErrorBody {
48 code: "invalid_input",
49 message: msg,
50 details: None,
51 },
52 },
53 EnigmaRelayError::RateLimited => ErrorResponse {
54 error: ErrorBody {
55 code: "rate_limited",
56 message: "rate limit exceeded".to_string(),
57 details: None,
58 },
59 },
60 EnigmaRelayError::Duplicate => ErrorResponse {
61 error: ErrorBody {
62 code: "duplicate",
63 message: "duplicate message".to_string(),
64 details: None,
65 },
66 },
67 EnigmaRelayError::QuotaExceeded => ErrorResponse {
68 error: ErrorBody {
69 code: "quota_exceeded",
70 message: "recipient quota exceeded".to_string(),
71 details: None,
72 },
73 },
74 EnigmaRelayError::NotFound => ErrorResponse {
75 error: ErrorBody {
76 code: "not_found",
77 message: "not found".to_string(),
78 details: None,
79 },
80 },
81 EnigmaRelayError::StorageError(msg) => ErrorResponse {
82 error: ErrorBody {
83 code: "storage_error",
84 message: msg,
85 details: None,
86 },
87 },
88 EnigmaRelayError::Config(msg) => ErrorResponse {
89 error: ErrorBody {
90 code: "config_error",
91 message: msg,
92 details: None,
93 },
94 },
95 EnigmaRelayError::Tls(msg) => ErrorResponse {
96 error: ErrorBody {
97 code: "tls_error",
98 message: msg,
99 details: None,
100 },
101 },
102 EnigmaRelayError::Disabled(msg) => ErrorResponse {
103 error: ErrorBody {
104 code: "feature_disabled",
105 message: msg,
106 details: None,
107 },
108 },
109 EnigmaRelayError::Internal(msg) => ErrorResponse {
110 error: ErrorBody {
111 code: "internal_error",
112 message: msg,
113 details: None,
114 },
115 },
116 }
117 }
118}
119
120impl From<serde_json::Error> for EnigmaRelayError {
121 fn from(err: serde_json::Error) -> Self {
122 EnigmaRelayError::Internal(err.to_string())
123 }
124}
125
126impl From<std::io::Error> for EnigmaRelayError {
127 fn from(err: std::io::Error) -> Self {
128 EnigmaRelayError::Internal(err.to_string())
129 }
130}
131
132#[cfg(feature = "persistence")]
133impl From<sled::Error> for EnigmaRelayError {
134 fn from(err: sled::Error) -> Self {
135 EnigmaRelayError::StorageError(err.to_string())
136 }
137}
138
139#[cfg(feature = "http")]
140impl actix_web::ResponseError for EnigmaRelayError {
141 fn status_code(&self) -> actix_web::http::StatusCode {
142 match self {
143 EnigmaRelayError::InvalidInput(_) => actix_web::http::StatusCode::BAD_REQUEST,
144 EnigmaRelayError::RateLimited => actix_web::http::StatusCode::TOO_MANY_REQUESTS,
145 EnigmaRelayError::Duplicate => actix_web::http::StatusCode::OK,
146 EnigmaRelayError::QuotaExceeded => actix_web::http::StatusCode::CONFLICT,
147 EnigmaRelayError::NotFound => actix_web::http::StatusCode::NOT_FOUND,
148 EnigmaRelayError::StorageError(_) => actix_web::http::StatusCode::SERVICE_UNAVAILABLE,
149 EnigmaRelayError::Config(_) => actix_web::http::StatusCode::BAD_REQUEST,
150 EnigmaRelayError::Tls(_) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
151 EnigmaRelayError::Disabled(_) => actix_web::http::StatusCode::SERVICE_UNAVAILABLE,
152 EnigmaRelayError::Internal(_) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
153 }
154 }
155
156 fn error_response(&self) -> actix_web::HttpResponse {
157 let body = self.clone().into_response();
158 actix_web::HttpResponse::build(self.status_code()).json(body)
159 }
160}