1use core::ffi::c_int;
4
5use crate::sys;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ErrorKind {
10 Io,
12 Eof,
14 Protocol,
16 Type,
18 Oom,
20 Cancelled,
22 Server,
26 Usage,
28 WouldBlock,
31 Other(c_int),
33}
34
35impl ErrorKind {
36 pub(crate) fn from_code(code: c_int) -> Self {
37 match code {
38 sys::CHC_ERR_IO => Self::Io,
39 sys::CHC_ERR_EOF => Self::Eof,
40 sys::CHC_ERR_PROTOCOL => Self::Protocol,
41 sys::CHC_ERR_TYPE => Self::Type,
42 sys::CHC_ERR_OOM => Self::Oom,
43 sys::CHC_ERR_CANCELLED => Self::Cancelled,
44 sys::CHC_ERR_SERVER => Self::Server,
45 sys::CHC_ERR_USAGE => Self::Usage,
46 sys::CHC_WOULD_BLOCK => Self::WouldBlock,
47 other => Self::Other(other),
48 }
49 }
50}
51
52#[derive(Debug, Clone, thiserror::Error)]
54#[error("clickhouse-c: {kind:?}: {message}")]
55pub struct Error {
56 pub kind: ErrorKind,
57 pub server_code: i32,
59 pub message: String,
61 pub server_name: String,
63}
64
65impl Error {
66 pub(crate) fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
67 Self {
68 kind,
69 server_code: 0,
70 message: message.into(),
71 server_name: String::new(),
72 }
73 }
74
75 pub(crate) fn from_raw(code: c_int, e: &sys::chc_err) -> Self {
76 Self::new(ErrorKind::from_code(code), cstr_array_to_string(&e.msg))
77 }
78}
79
80impl From<std::io::Error> for Error {
81 fn from(e: std::io::Error) -> Self {
82 Self::new(ErrorKind::Io, e.to_string())
83 }
84}
85
86pub type Result<T> = core::result::Result<T, Error>;
88
89fn cstr_array_to_string(buf: &[core::ffi::c_char]) -> String {
90 let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
91 let bytes: &[u8] = unsafe { core::slice::from_raw_parts(buf.as_ptr().cast::<u8>(), end) };
92 String::from_utf8_lossy(bytes).into_owned()
93}
94
95#[inline]
96pub(crate) fn check(rc: c_int, err: &sys::chc_err) -> Result<()> {
97 if rc == sys::CHC_OK {
98 Ok(())
99 } else {
100 Err(Error::from_raw(rc, err))
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 fn err_with(msg: &str) -> sys::chc_err {
109 let mut e = sys::chc_err::zeroed();
110 for (slot, b) in e.msg.iter_mut().zip(msg.bytes()) {
111 *slot = b as core::ffi::c_char;
112 }
113 e
114 }
115
116 #[test]
118 fn ok_rc_ignores_stale_err() {
119 let stale = err_with("ioless buffer drained");
120 assert!(check(sys::CHC_OK, &stale).is_ok());
121 }
122
123 #[test]
124 fn error_rc_surfaces_kind() {
125 let e = err_with("bad handshake");
126 let err = check(sys::CHC_ERR_PROTOCOL, &e).unwrap_err();
127 assert_eq!(err.kind, ErrorKind::Protocol);
128 assert_eq!(err.message, "bad handshake");
129 }
130
131 #[test]
132 fn every_c_code_maps_to_its_kind() {
133 for (code, kind) in [
134 (sys::CHC_ERR_IO, ErrorKind::Io),
135 (sys::CHC_ERR_EOF, ErrorKind::Eof),
136 (sys::CHC_ERR_PROTOCOL, ErrorKind::Protocol),
137 (sys::CHC_ERR_TYPE, ErrorKind::Type),
138 (sys::CHC_ERR_OOM, ErrorKind::Oom),
139 (sys::CHC_ERR_CANCELLED, ErrorKind::Cancelled),
140 (sys::CHC_ERR_SERVER, ErrorKind::Server),
141 (sys::CHC_ERR_USAGE, ErrorKind::Usage),
142 (sys::CHC_WOULD_BLOCK, ErrorKind::WouldBlock),
143 ] {
144 assert_eq!(ErrorKind::from_code(code), kind, "code {code}");
145 }
146 }
147
148 #[test]
150 fn unknown_code_keeps_its_number() {
151 assert_eq!(ErrorKind::from_code(-4242), ErrorKind::Other(-4242));
152 }
153
154 #[test]
155 fn transport_errors_become_io_kind() {
156 let err: Error = std::io::Error::other("socket went away").into();
157 assert_eq!(err.kind, ErrorKind::Io);
158 assert!(err.message.contains("socket went away"), "{err}");
159 assert_eq!(err.server_code, 0);
160 assert!(err.server_name.is_empty());
161 }
162
163 #[test]
165 fn unterminated_message_reads_to_capacity() {
166 let mut e = sys::chc_err::zeroed();
167 e.msg.fill(b'x' as core::ffi::c_char);
168 let err = check(sys::CHC_ERR_IO, &e).unwrap_err();
169 assert_eq!(err.message.len(), e.msg.len());
170 }
171}