1use std::{collections::HashSet, error::Error as StdError, fmt};
21
22use itertools::Itertools;
23use tonic::{Code, Status};
24use tonic_types::{ErrorDetails, ErrorInfo, StatusExt};
25
26use super::{address::Address, RequestID};
27
28macro_rules! error_messages {
29 {
30 $name:ident code: $code_pfx:literal, type: $message_pfx:literal,
31 $($error_name:ident $({ $($field:ident : $inner:ty),+ $(,)? })? = $code:literal: $body:literal),+ $(,)?
32 } => {
33 #[derive(Clone, Eq, PartialEq)]
34 pub enum $name {$(
35 $error_name$( { $($field: $inner),+ })?,
36 )*}
37
38 impl $name {
39 pub const PREFIX: &'static str = $code_pfx;
40
41 pub const fn code(&self) -> usize {
42 match self {$(
43 Self::$error_name $({ $($field: _),+ })? => $code,
44 )*}
45 }
46
47 pub fn format_code(&self) -> String {
48 format!(concat!("[", $code_pfx, "{}{}]"), self.padding(), self.code())
49 }
50
51 pub fn message(&self) -> String {
52 match self {$(
53 Self::$error_name $({$($field),+})? => format!($body $($(, $field = $field)+)?),
54 )*}
55 }
56
57 const fn max_code() -> usize {
58 let mut max = usize::MIN;
59 $(max = if $code > max { $code } else { max };)*
60 max
61 }
62
63 const fn num_digits(x: usize) -> usize {
64 if (x < 10) { 1 } else { 1 + Self::num_digits(x/10) }
65 }
66
67 const fn padding(&self) -> &'static str {
68 match Self::num_digits(Self::max_code()) - Self::num_digits(self.code()) {
69 0 => "",
70 1 => "0",
71 2 => "00",
72 3 => "000",
73 _ => unreachable!(),
74 }
75 }
76
77 const fn name(&self) -> &'static str {
78 match self {$(
79 Self::$error_name $({ $($field: _),+ })? => concat!(stringify!($name), "::", stringify!($error_name)),
80 )*}
81 }
82 }
83
84 impl std::fmt::Display for $name {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 write!(
87 f,
88 concat!("[", $code_pfx, "{}{}] ", $message_pfx, ": {}"),
89 self.padding(),
90 self.code(),
91 self.message()
92 )
93 }
94 }
95
96 impl std::fmt::Debug for $name {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 let mut debug_struct = f.debug_struct(self.name());
99 debug_struct.field("message", &format!("{}", self));
100 $(
101 $(
102 if let Self::$error_name { $($field),+ } = &self {
103 $(debug_struct.field(stringify!($field), &$field);)+
104 }
105 )?
106 )*
107 debug_struct.finish()
108 }
109 }
110
111 impl std::error::Error for $name {
112 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
113 None
114 }
115 }
116 };
117}
118
119error_messages! { ConnectionError
120 code: "CXN", type: "Connection Error",
121 RPCMethodUnavailable { message: String } =
122 1: "The server does not support this method, please check the driver-server compatibility:\n'{message}'.",
123 ServerConnectionFailed { addresses: Vec<Address> } =
124 2: "Unable to connect to TypeDB server(s) at: \n{addresses:?}",
125 ServerConnectionFailedWithError { error: String } =
126 3: "Unable to connect to TypeDB server(s), received errors: \n{error}",
127 ServerConnectionFailedStatusError { error: String } =
128 4: "Unable to connect to TypeDB server(s), received network or protocol error: \n{error}",
129 ServerConnectionIsClosed =
130 5: "The connection has been closed and no further operation is allowed.",
131 TransactionIsClosed =
132 6: "The transaction is closed and no further operation is allowed.",
133 TransactionIsClosedWithErrors { errors: String } =
134 7: "The transaction is closed because of the error(s):\n{errors}",
135 DatabaseNotFound { name: String } =
136 8: "Database '{name}' not found.",
137 MissingResponseField { field: &'static str } =
138 9: "Missing field in message received from server: '{field}'. This is either a version compatibility issue or a bug.",
139 UnknownRequestId { request_id: RequestID } =
140 10: "Received a response with unknown request id '{request_id}'",
141 UnexpectedResponse { response: String } =
142 11: "Received unexpected response from server: '{response}'. This is either a version compatibility issue or a bug.",
143 InvalidResponseField { name: &'static str } =
144 12: "Invalid field in message received from server: '{name}'. This is either a version compatibility issue or a bug.",
145 QueryStreamNoResponse =
146 13: "Didn't receive any server responses for the query.",
147 UnexpectedQueryType { query_type: i32 } =
148 14: "Unexpected query type in message received from server: {query_type}. This is either a version compatibility issue or a bug.",
149 ClusterReplicaNotPrimary =
150 15: "The replica is not the primary replica.",
151 ClusterAllNodesFailed { errors: String } =
152 16: "Attempted connecting to all TypeDB Cluster servers, but the following errors occurred: \n{errors}.",
153 TokenCredentialInvalid =
154 17: "Invalid token credentials.",
155 EncryptionSettingsMismatch =
156 18: "Unable to connect to TypeDB: possible encryption settings mismatch.",
157 SSLCertificateNotValidated =
158 19: "SSL handshake with TypeDB failed: the server's identity could not be verified. Possible CA mismatch.",
159 BrokenPipe =
160 20: "Stream closed because of a broken pipe. This could happen if you are attempting to connect to an unencrypted TypeDB server using a TLS-enabled credentials.",
161 ConnectionFailed =
162 21: "Connection failed. Please check the server is running and the address is accessible. Encrypted TypeDB endpoints may also have misconfigured SSL certificates.",
163 MissingPort { address: String } =
164 22: "Invalid URL '{address}': missing port.",
165 AddressTranslationMismatch { unknown: HashSet<Address>, unmapped: HashSet<Address> } =
166 23: "Address translation map does not match the server's advertised address list. User-provided servers not in the advertised list: {unknown:?}. Advertised servers not mapped by user: {unmapped:?}.",
167 ValueTimeZoneNameNotRecognised { time_zone: String } =
168 24: "Time zone provided by the server has name '{time_zone}', which is not an officially recognized timezone.",
169 ValueTimeZoneOffsetNotRecognised { offset: i32 } =
170 25: "Time zone provided by the server has numerical offset '{offset}', which is not recognised as a valid value for offset in seconds.",
171 ValueStructNotImplemented =
172 26: "Struct valued responses are not yet supported by the driver.",
173 ListsNotImplemented =
174 27: "Lists are not yet supported by the driver.",
175 UnexpectedKind { kind: i32 } =
176 28: "Unexpected kind in message received from server: {kind}. This is either a version compatibility issue or a bug.",
177 UnexpectedConnectionClose =
178 29: "Connection closed unexpectedly.",
179}
180
181error_messages! { ConceptError
182 code: "CPT", type: "Concept Error",
183 UnavailableRowVariable { variable: String } =
184 1: "Cannot get concept from a concept row by variable '{variable}'.",
185 UnavailableRowIndex { index: usize } =
186 2: "Cannot get concept from a concept row by index '{index}'.",
187}
188
189error_messages! { InternalError
190 code: "INT", type: "Internal Error",
191 RecvError =
192 1: "Channel is closed.",
193 SendError =
194 2: "Unable to send response over callback channel (receiver dropped).",
195 UnexpectedRequestType { request_type: String } =
196 3: "Unexpected request type for remote procedure call: {request_type}. This is either a version compatibility issue or a bug.",
197 UnexpectedResponseType { response_type: String } =
198 4: "Unexpected response type for remote procedure call: {response_type}. This is either a version compatibility issue or a bug.",
199 UnknownServer { server: Address } =
200 5: "Received replica at unrecognized server: {server}.",
201 EnumOutOfBounds { value: i32, enum_name: &'static str } =
202 6: "Value '{value}' is out of bounds for enum '{enum_name}'.",
203}
204
205#[derive(Clone, PartialEq, Eq)]
206pub struct ServerError {
207 error_code: String,
208 error_domain: String,
209 message: String,
210 stack_trace: Vec<String>,
211}
212
213impl ServerError {
214 pub(crate) fn new(error_code: String, error_domain: String, message: String, stack_trace: Vec<String>) -> Self {
215 Self { error_code, error_domain, message, stack_trace }
216 }
217
218 pub(crate) fn format_code(&self) -> &str {
219 &self.error_code
220 }
221
222 pub(crate) fn message(&self) -> String {
223 self.to_string()
224 }
225
226 fn to_string(&self) -> String {
227 if self.stack_trace.is_empty() {
228 format!("[{}] {}. {}", self.error_code, self.error_domain, self.message)
229 } else {
230 format!("\n{}", self.stack_trace.join("\nCaused: "))
231 }
232 }
233}
234
235impl fmt::Display for ServerError {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 write!(f, "{}", self.to_string())
238 }
239}
240
241impl fmt::Debug for ServerError {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 fmt::Display::fmt(self, f)
244 }
245}
246
247#[derive(Clone, Debug, PartialEq, Eq)]
249pub enum Error {
250 Connection(ConnectionError),
251 Concept(ConceptError),
252 Internal(InternalError),
253 Server(ServerError),
254 Other(String),
255}
256
257impl Error {
258 pub fn code(&self) -> String {
259 match self {
260 Self::Connection(error) => error.format_code(),
261 Self::Concept(error) => error.format_code(),
262 Self::Internal(error) => error.format_code(),
263 Self::Server(error) => error.format_code().to_owned(),
264 Self::Other(_error) => String::new(),
265 }
266 }
267
268 pub fn message(&self) -> String {
269 match self {
270 Self::Connection(error) => error.message(),
271 Self::Concept(error) => error.message(),
272 Self::Internal(error) => error.message(),
273 Self::Server(error) => error.message(),
274 Self::Other(error) => error.clone(),
275 }
276 }
277
278 fn try_extracting_connection_error(status: &Status, code: &str) -> Option<ConnectionError> {
279 match code {
283 "AUT2" | "AUT3" => Some(ConnectionError::TokenCredentialInvalid {}),
284 _ => None,
285 }
286 }
287
288 fn from_message(message: &str) -> Self {
289 Self::Other(message.to_owned())
291 }
292
293 fn parse_unavailable(status_message: &str) -> Error {
294 if status_message == "broken pipe" {
295 Error::Connection(ConnectionError::BrokenPipe)
296 } else if status_message.contains("received corrupt message") {
297 Error::Connection(ConnectionError::EncryptionSettingsMismatch)
298 } else if status_message.contains("UnknownIssuer") {
299 Error::Connection(ConnectionError::SSLCertificateNotValidated)
300 } else if status_message.contains("Connection refused") {
301 Error::Connection(ConnectionError::ConnectionFailed)
302 } else {
303 Error::Connection(ConnectionError::ServerConnectionFailedStatusError { error: status_message.to_owned() })
304 }
305 }
306}
307
308impl fmt::Display for Error {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 match self {
311 Self::Connection(error) => write!(f, "{error}"),
312 Self::Concept(error) => write!(f, "{error}"),
313 Self::Internal(error) => write!(f, "{error}"),
314 Self::Server(error) => write!(f, "{error}"),
315 Self::Other(message) => write!(f, "{message}"),
316 }
317 }
318}
319
320impl StdError for Error {
321 fn source(&self) -> Option<&(dyn StdError + 'static)> {
322 match self {
323 Self::Connection(error) => Some(error),
324 Self::Concept(error) => Some(error),
325 Self::Internal(error) => Some(error),
326 Self::Server(_) => None,
327 Self::Other(_) => None,
328 }
329 }
330}
331
332impl From<ConnectionError> for Error {
333 fn from(error: ConnectionError) -> Self {
334 Self::Connection(error)
335 }
336}
337
338impl From<ConceptError> for Error {
339 fn from(error: ConceptError) -> Self {
340 Self::Concept(error)
341 }
342}
343
344impl From<InternalError> for Error {
345 fn from(error: InternalError) -> Self {
346 Self::Internal(error)
347 }
348}
349
350impl From<ServerError> for Error {
351 fn from(error: ServerError) -> Self {
352 Self::Server(error)
353 }
354}
355
356impl From<Status> for Error {
357 fn from(status: Status) -> Self {
358 if let Ok(details) = status.check_error_details() {
359 if let Some(bad_request) = details.bad_request() {
360 Self::Connection(ConnectionError::ServerConnectionFailedWithError {
361 error: format!("{:?}", bad_request),
362 })
363 } else if let Some(error_info) = details.error_info() {
364 let code = error_info.reason.clone();
365 if let Some(connection_error) = Self::try_extracting_connection_error(&status, &code) {
366 return Self::Connection(connection_error);
367 }
368 let domain = error_info.domain.clone();
369 let stack_trace =
370 if let Some(debug_info) = details.debug_info() { debug_info.stack_entries.clone() } else { vec![] };
371
372 Self::Server(ServerError::new(code, domain, status.message().to_owned(), stack_trace))
373 } else {
374 Self::from_message(status.message())
375 }
376 } else {
377 if status.code() == Code::Unavailable {
378 Self::parse_unavailable(status.message())
379 } else if status.code() == Code::Unknown
380 || is_rst_stream(&status)
381 || status.code() == Code::FailedPrecondition
382 || status.code() == Code::AlreadyExists
383 {
384 Self::Connection(ConnectionError::ServerConnectionFailedStatusError {
385 error: status.message().to_owned(),
386 })
387 } else if status.code() == Code::Unimplemented {
388 Self::Connection(ConnectionError::RPCMethodUnavailable { message: status.message().to_owned() })
389 } else {
390 Self::from_message(status.message())
391 }
392 }
393 }
394}
395
396fn is_rst_stream(status: &Status) -> bool {
397 status.message().contains("Received Rst Stream")
399}
400
401impl From<http::uri::InvalidUri> for Error {
402 fn from(err: http::uri::InvalidUri) -> Self {
403 Self::Other(err.to_string())
404 }
405}
406
407impl From<tonic::transport::Error> for Error {
408 fn from(err: tonic::transport::Error) -> Self {
409 Self::Other(err.to_string())
410 }
411}
412
413impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error {
414 fn from(_err: tokio::sync::mpsc::error::SendError<T>) -> Self {
415 Self::Internal(InternalError::SendError)
416 }
417}
418
419impl From<tokio::sync::oneshot::error::RecvError> for Error {
420 fn from(_err: tokio::sync::oneshot::error::RecvError) -> Self {
421 Self::Internal(InternalError::RecvError)
422 }
423}
424
425impl From<crossbeam::channel::RecvError> for Error {
426 fn from(_err: crossbeam::channel::RecvError) -> Self {
427 Self::Internal(InternalError::RecvError)
428 }
429}
430
431impl<T> From<crossbeam::channel::SendError<T>> for Error {
432 fn from(_err: crossbeam::channel::SendError<T>) -> Self {
433 Self::Internal(InternalError::SendError)
434 }
435}
436
437impl From<String> for Error {
438 fn from(err: String) -> Self {
439 Self::Other(err)
440 }
441}
442
443impl From<std::io::Error> for Error {
444 fn from(err: std::io::Error) -> Self {
445 Self::Other(err.to_string())
446 }
447}