typedb_driver/common/
error.rs1use std::{collections::HashSet, error::Error as StdError, fmt};
21
22use tonic::{Code, Status};
23use typeql::error_messages;
24
25use super::{address::Address, RequestID};
26
27error_messages! { ConnectionError
28 code: "CXN", type: "Connection Error",
29 RPCMethodUnavailable { message: String } =
30 1: "The server does not support this method, please check the driver-server compatibility:\n'{message}'.",
31 ConnectionIsClosed =
32 2: "The connection has been closed and no further operation is allowed.",
33 SessionIsClosed =
34 3: "The session is closed and no further operation is allowed.",
35 TransactionIsClosed =
36 4: "The transaction is closed and no further operation is allowed.",
37 TransactionIsClosedWithErrors { errors: String } =
38 5: "The transaction is closed because of the error(s):\n{errors}",
39 DatabaseDoesNotExist { name: String } =
40 6: "The database '{name}' does not exist.",
41 MissingResponseField { field: &'static str } =
42 7: "Missing field in message received from server: '{field}'.",
43 UnknownRequestId { request_id: RequestID } =
44 8: "Received a response with unknown request id '{request_id}'",
45 InvalidResponseField { name: &'static str } =
46 9: "Invalid field in message received from server: '{name}'.",
47 UnexpectedResponse { response: String } =
48 10: "Received unexpected response from server: '{response}'.",
49 ServerConnectionFailed { addresses: Vec<Address> } =
50 11: "Unable to connect to TypeDB server(s) at: \n{addresses:?}",
51 ServerConnectionFailedWithError { error: String } =
52 12: "Unable to connect to TypeDB server(s), received errors: \n{error}",
53 ServerConnectionFailedStatusError { error: String } =
54 13: "Unable to connect to TypeDB server(s), received network error: \n{error}",
55 UserManagementCloudOnly =
56 14: "User management is only available in TypeDB Cloud servers.",
57 CloudReplicaNotPrimary =
58 15: "The replica is not the primary replica.",
59 CloudAllNodesFailed { errors: String } =
60 16: "Attempted connecting to all TypeDB Cloud servers, but the following errors occurred: \n{errors}.",
61 CloudTokenCredentialInvalid =
62 17: "Invalid token credential.",
63 SessionCloseFailed =
64 18: "Failed to close session. It may still be open on the server: or it may already have been closed previously.",
65 CloudEncryptionSettingsMismatch =
66 19: "Unable to connect to TypeDB Cloud: possible encryption settings mismatch.",
67 CloudSSLCertificateNotValidated =
68 20: "SSL handshake with TypeDB Cloud failed: the server's identity could not be verified. Possible CA mismatch.",
69 BrokenPipe =
70 21: "Stream closed because of a broken pipe. This could happen if you are attempting to connect to an unencrypted cloud instance using a TLS-enabled credential.",
71 ConnectionFailed =
72 22: "Connection failed. Please check the server is running and the address is accessible. Encrypted Cloud endpoints may also have misconfigured SSL certificates.",
73 MissingPort { address: String } =
74 23: "Invalid URL '{address}': missing port.",
75 AddressTranslationMismatch { unknown: HashSet<Address>, unmapped: HashSet<Address> } =
76 24: "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:?}.",
77}
78
79error_messages! { InternalError
80 code: "INT", type: "Internal Error",
81 RecvError =
82 1: "Channel is closed.",
83 SendError =
84 2: "Unable to send response over callback channel (receiver dropped).",
85 UnexpectedRequestType { request_type: String } =
86 3: "Unexpected request type for remote procedure call: {request_type}.",
87 UnexpectedResponseType { response_type: String } =
88 4: "Unexpected response type for remote procedure call: {response_type}.",
89 UnknownServer { server: Address } =
90 5: "Received replica at unrecognized server: {server}.",
91 EnumOutOfBounds { value: i32, enum_name: &'static str } =
92 6: "Value '{value}' is out of bounds for enum '{enum_name}'.",
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
97pub enum Error {
98 Connection(ConnectionError),
99 Internal(InternalError),
100 TypeQL(typeql::common::Error),
101 Other(String),
102}
103
104impl Error {
105 pub fn code(&self) -> String {
106 match self {
107 Self::Connection(error) => error.format_code(),
108 Self::Internal(error) => error.format_code(),
109 Self::TypeQL(_error) => String::new(),
110 Self::Other(_error) => String::new(),
111 }
112 }
113
114 pub fn message(&self) -> String {
115 match self {
116 Self::Connection(error) => error.message(),
117 Self::Internal(error) => error.message(),
118 Self::TypeQL(error) => error.to_string(),
119 Self::Other(error) => error.clone(),
120 }
121 }
122
123 fn from_message(message: &str) -> Self {
124 match message.split_ascii_whitespace().next() {
125 Some("[RPL01]") => Self::Connection(ConnectionError::CloudReplicaNotPrimary),
126 Some("[CLS08]") => Self::Connection(ConnectionError::CloudTokenCredentialInvalid),
128 Some("[ENT08]") => Self::Connection(ConnectionError::CloudTokenCredentialInvalid),
129 Some("[DBS06]") => Self::Connection(ConnectionError::DatabaseDoesNotExist {
130 name: message.split('\'').nth(1).unwrap_or("{unknown}").to_owned(),
131 }),
132 _ => Self::Other(message.to_owned()),
133 }
134 }
135
136 fn parse_unavailable(status_message: &str) -> Error {
137 if status_message == "broken pipe" {
138 Error::Connection(ConnectionError::BrokenPipe)
139 } else if status_message.contains("received corrupt message") {
140 Error::Connection(ConnectionError::CloudEncryptionSettingsMismatch)
141 } else if status_message.contains("UnknownIssuer") {
142 Error::Connection(ConnectionError::CloudSSLCertificateNotValidated)
143 } else if status_message.contains("Connection refused") {
144 Error::Connection(ConnectionError::ConnectionFailed)
145 } else {
146 Error::Connection(ConnectionError::ServerConnectionFailedStatusError { error: status_message.to_owned() })
147 }
148 }
149}
150
151impl fmt::Display for Error {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 match self {
154 Self::Connection(error) => write!(f, "{error}"),
155 Self::Internal(error) => write!(f, "{error}"),
156 Self::TypeQL(error) => write!(f, "{error}"),
157 Self::Other(message) => write!(f, "{message}"),
158 }
159 }
160}
161
162impl StdError for Error {
163 fn source(&self) -> Option<&(dyn StdError + 'static)> {
164 match self {
165 Self::Connection(error) => Some(error),
166 Self::Internal(error) => Some(error),
167 Self::TypeQL(error) => Some(error),
168 Self::Other(_) => None,
169 }
170 }
171}
172
173impl From<ConnectionError> for Error {
174 fn from(error: ConnectionError) -> Self {
175 Self::Connection(error)
176 }
177}
178
179impl From<InternalError> for Error {
180 fn from(error: InternalError) -> Self {
181 Self::Internal(error)
182 }
183}
184
185impl From<typeql::common::Error> for Error {
186 fn from(err: typeql::common::Error) -> Self {
187 Self::TypeQL(err)
188 }
189}
190
191impl From<Status> for Error {
192 fn from(status: Status) -> Self {
193 if status.code() == Code::Unavailable {
194 Self::parse_unavailable(status.message())
195 } else if status.code() == Code::Unknown || is_rst_stream(&status) {
196 Self::Connection(ConnectionError::ServerConnectionFailedStatusError { error: status.message().to_owned() })
197 } else if status.code() == Code::Unimplemented {
198 Self::Connection(ConnectionError::RPCMethodUnavailable { message: status.message().to_owned() })
199 } else {
200 Self::from_message(status.message())
201 }
202 }
203}
204
205fn is_rst_stream(status: &Status) -> bool {
206 status.message().contains("Received Rst Stream")
208}
209
210impl From<http::uri::InvalidUri> for Error {
211 fn from(err: http::uri::InvalidUri) -> Self {
212 Self::Other(err.to_string())
213 }
214}
215
216impl From<tonic::transport::Error> for Error {
217 fn from(err: tonic::transport::Error) -> Self {
218 Self::Other(err.to_string())
219 }
220}
221
222impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error {
223 fn from(_err: tokio::sync::mpsc::error::SendError<T>) -> Self {
224 Self::Internal(InternalError::SendError)
225 }
226}
227
228impl From<tokio::sync::oneshot::error::RecvError> for Error {
229 fn from(_err: tokio::sync::oneshot::error::RecvError) -> Self {
230 Self::Internal(InternalError::RecvError)
231 }
232}
233
234impl From<crossbeam::channel::RecvError> for Error {
235 fn from(_err: crossbeam::channel::RecvError) -> Self {
236 Self::Internal(InternalError::RecvError)
237 }
238}
239
240impl<T> From<crossbeam::channel::SendError<T>> for Error {
241 fn from(_err: crossbeam::channel::SendError<T>) -> Self {
242 Self::Internal(InternalError::SendError)
243 }
244}
245
246impl From<String> for Error {
247 fn from(err: String) -> Self {
248 Self::Other(err)
249 }
250}
251
252impl From<std::io::Error> for Error {
253 fn from(err: std::io::Error) -> Self {
254 Self::Other(err.to_string())
255 }
256}