Skip to main content

typedb_driver/common/
error.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20use 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/// Represents errors encountered during operation.
96#[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            // TODO: CLS and ENT are synonyms which we can simplify on protocol change
127            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    // "Received Rst Stream" occurs if the server is in the process of shutting down.
207    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}