autumn_plugin_clickhouse/
error.rs1use std::time::Duration;
16
17use autumn_web::AutumnError;
18use http::StatusCode;
19
20use crate::config::ConfigError;
21
22#[derive(Clone, PartialEq, Eq, thiserror::Error)]
26#[non_exhaustive]
27pub enum ClickHouseError {
28 #[error(transparent)]
30 Config(#[from] ConfigError),
31 #[error("ClickHouse refused the call: {kind}")]
35 #[non_exhaustive]
36 Database {
37 kind: ErrorKind,
39 detail: String,
41 },
42 #[error("the call did not complete in {timeout:?}")]
44 #[non_exhaustive]
45 Timeout {
46 timeout: Duration,
48 },
49 #[error("the migration `{name}` failed")]
53 #[non_exhaustive]
54 Migration {
55 name: String,
57 detail: String,
59 },
60 #[error("the ClickHouse plugin is not installed: add `ClickHousePlugin` to the app")]
62 NotInstalled,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67#[non_exhaustive]
68pub enum ErrorKind {
69 InvalidParams,
71 Network,
73 TimedOut,
75 BadResponse,
77 NotFound,
79 SchemaMismatch,
81 Decode,
83 Unsupported,
85 Custom,
87 Unknown,
89}
90
91impl std::fmt::Display for ErrorKind {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 f.write_str(match self {
94 Self::InvalidParams => "invalid parameters",
95 Self::Network => "network error",
96 Self::TimedOut => "timed out",
97 Self::BadResponse => "bad response",
98 Self::NotFound => "row not found",
99 Self::SchemaMismatch => "schema mismatch",
100 Self::Decode => "decode error",
101 Self::Unsupported => "unsupported",
102 Self::Custom => "client error",
103 Self::Unknown => "unknown error",
104 })
105 }
106}
107
108impl std::fmt::Debug for ClickHouseError {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.debug_tuple("ClickHouseError")
112 .field(&self.to_string())
113 .finish()
114 }
115}
116
117impl From<clickhouse::error::Error> for ClickHouseError {
118 fn from(err: clickhouse::error::Error) -> Self {
119 use clickhouse::error::Error as Ch;
120 let kind = match &err {
121 Ch::InvalidParams(_) => ErrorKind::InvalidParams,
122 Ch::Network(_) => ErrorKind::Network,
123 Ch::TimedOut => ErrorKind::TimedOut,
124 Ch::BadResponse(_) => ErrorKind::BadResponse,
125 Ch::RowNotFound => ErrorKind::NotFound,
126 Ch::SchemaMismatch(_) => ErrorKind::SchemaMismatch,
127 Ch::Compression(_)
128 | Ch::Decompression(_)
129 | Ch::InvalidUtf8Encoding(_)
130 | Ch::InvalidTagEncoding(_)
131 | Ch::VariantDiscriminatorIsOutOfBound(_)
132 | Ch::NotEnoughData
133 | Ch::SequenceMustHaveLength
134 | Ch::DeserializeAnyNotSupported
135 | Ch::InvalidColumnsHeader(_) => ErrorKind::Decode,
136 Ch::Unsupported(_) => ErrorKind::Unsupported,
137 Ch::Custom(_) => ErrorKind::Custom,
138 _ => ErrorKind::Unknown,
139 };
140 Self::Database {
141 kind,
142 detail: err.to_string(),
143 }
144 }
145}
146
147impl ClickHouseError {
148 #[must_use]
152 pub fn detail(&self) -> Option<&str> {
153 match self {
154 Self::Database { detail, .. } | Self::Migration { detail, .. } => Some(detail),
155 _ => None,
156 }
157 }
158
159 #[must_use]
161 pub const fn kind(&self) -> Option<ErrorKind> {
162 match self {
163 Self::Database { kind, .. } => Some(*kind),
164 _ => None,
165 }
166 }
167
168 #[must_use]
170 pub const fn is_retryable(&self) -> bool {
171 matches!(
172 self,
173 Self::Timeout { .. }
174 | Self::Database {
175 kind: ErrorKind::Network | ErrorKind::TimedOut,
176 ..
177 }
178 )
179 }
180
181 #[must_use]
183 pub const fn status(&self) -> StatusCode {
184 match self {
185 Self::Timeout { .. }
186 | Self::Database {
187 kind: ErrorKind::TimedOut,
188 ..
189 } => StatusCode::GATEWAY_TIMEOUT,
190 Self::Database {
191 kind: ErrorKind::Network,
192 ..
193 } => StatusCode::SERVICE_UNAVAILABLE,
194 Self::Database {
195 kind: ErrorKind::BadResponse,
196 ..
197 } => StatusCode::BAD_GATEWAY,
198 Self::Database {
199 kind: ErrorKind::NotFound,
200 ..
201 } => StatusCode::NOT_FOUND,
202 Self::Database {
203 kind: ErrorKind::InvalidParams,
204 ..
205 } => StatusCode::BAD_REQUEST,
206 _ => StatusCode::INTERNAL_SERVER_ERROR,
207 }
208 }
209
210 #[must_use]
216 pub fn into_autumn(self) -> AutumnError {
217 match self.status() {
218 StatusCode::NOT_FOUND => AutumnError::not_found(self),
219 StatusCode::BAD_REQUEST => AutumnError::bad_request(self),
220 StatusCode::SERVICE_UNAVAILABLE => AutumnError::service_unavailable(self),
221 StatusCode::GATEWAY_TIMEOUT => AutumnError::query_timeout(self.to_string()),
222 _ => AutumnError::internal_server_error(self),
223 }
224 }
225}
226
227pub trait ClickHouseResultExt<T> {
229 fn or_http(self) -> Result<T, AutumnError>;
235}
236
237impl<T> ClickHouseResultExt<T> for Result<T, ClickHouseError> {
238 fn or_http(self) -> Result<T, AutumnError> {
239 self.map_err(ClickHouseError::into_autumn)
240 }
241}
242
243#[cfg(test)]
244mod tests;