Skip to main content

clickhouse/
error.rs

1//! Contains [`Error`] and corresponding [`Result`].
2
3use serde::{de, ser};
4use std::{error::Error as StdError, fmt, io, result, str::Utf8Error};
5
6/// A result with a specified [`Error`] type.
7pub type Result<T, E = Error> = result::Result<T, E>;
8
9type BoxedError = Box<dyn StdError + Send + Sync>;
10
11/// Represents all possible errors.
12#[derive(Debug, thiserror::Error)]
13#[non_exhaustive]
14#[allow(missing_docs)]
15pub enum Error {
16    #[error("invalid params: {0}")]
17    InvalidParams(#[source] BoxedError),
18    #[error("network error: {0}")]
19    Network(#[source] BoxedError),
20    #[error("compression error: {0}")]
21    Compression(#[source] BoxedError),
22    #[error("decompression error: {0}")]
23    Decompression(#[source] BoxedError),
24    #[error("no rows returned by a query that expected to return at least one row")]
25    RowNotFound,
26    #[error("sequences must have a known size ahead of time")]
27    SequenceMustHaveLength,
28    #[error("`deserialize_any` is not supported")]
29    DeserializeAnyNotSupported,
30    #[error("not enough data, probably a row type mismatches a database schema")]
31    NotEnoughData,
32    #[error("string is not valid utf8")]
33    InvalidUtf8Encoding(#[from] Utf8Error),
34    #[error("tag for enum is not valid")]
35    InvalidTagEncoding(usize),
36    #[error("max number of types in the Variant data type is 255, got {0}")]
37    VariantDiscriminatorIsOutOfBound(usize),
38    #[error("a custom error message from serde: {0}")]
39    Custom(String),
40    #[error("bad response: {0}")]
41    BadResponse(String),
42    #[error("timeout expired")]
43    TimedOut,
44    #[error("error while parsing columns header from the response: {0}")]
45    InvalidColumnsHeader(#[source] BoxedError),
46    #[error("schema mismatch: {0}")]
47    SchemaMismatch(String),
48    #[error("unsupported: {0}")]
49    Unsupported(String),
50    #[cfg(feature = "sea-ql")]
51    #[error("type error: {0}")]
52    TypeError(#[from] crate::TypeError),
53    #[error("{0}")]
54    Other(BoxedError),
55}
56
57impl From<clickhouse_types::error::TypesError> for Error {
58    fn from(err: clickhouse_types::error::TypesError) -> Self {
59        Self::InvalidColumnsHeader(Box::new(err))
60    }
61}
62
63impl From<hyper::Error> for Error {
64    fn from(error: hyper::Error) -> Self {
65        Self::Network(Box::new(error))
66    }
67}
68
69impl From<hyper_util::client::legacy::Error> for Error {
70    fn from(error: hyper_util::client::legacy::Error) -> Self {
71        #[cfg(not(any(feature = "rustls-tls", feature = "native-tls")))]
72        if error.is_connect() {
73            static SCHEME_IS_NOT_HTTP: &str = "invalid URL, scheme is not http";
74
75            let src = error.source().unwrap();
76            // Unfortunately, this seems to be the only way, as `INVALID_NOT_HTTP` is not public.
77            // See https://github.com/hyperium/hyper-util/blob/v0.1.14/src/client/legacy/connect/http.rs#L491-L495
78            if src.to_string() == SCHEME_IS_NOT_HTTP {
79                return Self::Unsupported(format!(
80                    "{SCHEME_IS_NOT_HTTP}; if you are trying to connect via HTTPS, \
81                    consider enabling `native-tls` or `rustls-tls` feature"
82                ));
83            }
84        }
85        Self::Network(Box::new(error))
86    }
87}
88
89impl ser::Error for Error {
90    fn custom<T: fmt::Display>(msg: T) -> Self {
91        Self::Custom(msg.to_string())
92    }
93}
94
95impl de::Error for Error {
96    fn custom<T: fmt::Display>(msg: T) -> Self {
97        Self::Custom(msg.to_string())
98    }
99}
100
101impl From<Error> for io::Error {
102    fn from(error: Error) -> Self {
103        io::Error::other(error)
104    }
105}
106
107impl From<io::Error> for Error {
108    fn from(error: io::Error) -> Self {
109        // TODO: after MSRV 1.79 replace with `io::Error::downcast`.
110        if error.get_ref().is_some_and(|r| r.is::<Error>()) {
111            *error.into_inner().unwrap().downcast::<Error>().unwrap()
112        } else {
113            Self::Other(error.into())
114        }
115    }
116}
117
118#[cfg(tests)]
119mod tests {
120    use crate::error::Error;
121    use std::io;
122
123    #[test]
124    fn roundtrip_io_error() {
125        let orig = Error::NotEnoughData;
126
127        // Error -> io::Error
128        let orig_str = orig.to_string();
129        let io = io::Error::from(orig);
130        assert_eq!(io.kind(), io::ErrorKind::Other);
131        assert_eq!(io.to_string(), orig_str);
132
133        // io::Error -> Error
134        let orig = Error::from(io);
135        assert!(matches!(orig, Error::NotEnoughData));
136    }
137
138    #[test]
139    fn error_traits() {
140        fn assert_traits<T: std::error::Error + Send + Sync>() {}
141
142        assert_traits::<Error>();
143    }
144}