1use std::collections::HashMap;
4use thiserror::Error;
5
6#[cfg(all(
8 feature = "compio-tls",
9 not(any(feature = "sync-tls", feature = "tokio-tls"))
10))]
11use compio::native_tls;
12
13pub type Result<T> = core::result::Result<T, Error>;
15
16pub mod field_type {
18 pub const SEVERITY: u8 = b'S';
19 pub const SEVERITY_V: u8 = b'V';
20 pub const CODE: u8 = b'C';
21 pub const MESSAGE: u8 = b'M';
22 pub const DETAIL: u8 = b'D';
23 pub const HINT: u8 = b'H';
24 pub const POSITION: u8 = b'P';
25 pub const INTERNAL_POSITION: u8 = b'p';
26 pub const INTERNAL_QUERY: u8 = b'q';
27 pub const WHERE: u8 = b'W';
28 pub const SCHEMA: u8 = b's';
29 pub const TABLE: u8 = b't';
30 pub const COLUMN: u8 = b'c';
31 pub const DATA_TYPE: u8 = b'd';
32 pub const CONSTRAINT: u8 = b'n';
33 pub const FILE: u8 = b'F';
34 pub const LINE: u8 = b'L';
35 pub const ROUTINE: u8 = b'R';
36}
37
38#[derive(Debug, Clone)]
40pub struct ServerError(pub(crate) HashMap<u8, String>);
41
42impl ServerError {
43 pub fn new(fields: HashMap<u8, String>) -> Self {
45 Self(fields)
46 }
47
48 pub fn severity_localized(&self) -> &str {
52 self.0
53 .get(&field_type::SEVERITY)
54 .map(|s| s.as_str())
55 .unwrap_or_default()
56 }
57
58 pub fn severity_english(&self) -> &str {
60 self.0
61 .get(&field_type::SEVERITY_V)
62 .map(|s| s.as_str())
63 .unwrap_or_default()
64 }
65
66 pub fn code(&self) -> &str {
68 self.0
69 .get(&field_type::CODE)
70 .map(|s| s.as_str())
71 .unwrap_or_default()
72 }
73
74 pub fn message(&self) -> &str {
76 self.0
77 .get(&field_type::MESSAGE)
78 .map(|s| s.as_str())
79 .unwrap_or_default()
80 }
81
82 pub fn detail(&self) -> Option<&str> {
86 self.0.get(&field_type::DETAIL).map(|s| s.as_str())
87 }
88
89 pub fn hint(&self) -> Option<&str> {
91 self.0.get(&field_type::HINT).map(|s| s.as_str())
92 }
93
94 pub fn position(&self) -> Option<u32> {
96 self.0.get(&field_type::POSITION)?.parse().ok()
97 }
98
99 pub fn internal_position(&self) -> Option<u32> {
101 self.0.get(&field_type::INTERNAL_POSITION)?.parse().ok()
102 }
103
104 pub fn internal_query(&self) -> Option<&str> {
106 self.0.get(&field_type::INTERNAL_QUERY).map(|s| s.as_str())
107 }
108
109 pub fn where_(&self) -> Option<&str> {
111 self.0.get(&field_type::WHERE).map(|s| s.as_str())
112 }
113
114 pub fn schema(&self) -> Option<&str> {
116 self.0.get(&field_type::SCHEMA).map(|s| s.as_str())
117 }
118
119 pub fn table(&self) -> Option<&str> {
121 self.0.get(&field_type::TABLE).map(|s| s.as_str())
122 }
123
124 pub fn column(&self) -> Option<&str> {
126 self.0.get(&field_type::COLUMN).map(|s| s.as_str())
127 }
128
129 pub fn data_type(&self) -> Option<&str> {
131 self.0.get(&field_type::DATA_TYPE).map(|s| s.as_str())
132 }
133
134 pub fn constraint(&self) -> Option<&str> {
136 self.0.get(&field_type::CONSTRAINT).map(|s| s.as_str())
137 }
138
139 pub fn file(&self) -> Option<&str> {
141 self.0.get(&field_type::FILE).map(|s| s.as_str())
142 }
143
144 pub fn line(&self) -> Option<u32> {
146 self.0.get(&field_type::LINE)?.parse().ok()
147 }
148
149 pub fn routine(&self) -> Option<&str> {
151 self.0.get(&field_type::ROUTINE).map(|s| s.as_str())
152 }
153
154 pub fn get(&self, field_type: u8) -> Option<&str> {
156 self.0.get(&field_type).map(|s| s.as_str())
157 }
158}
159
160impl std::fmt::Display for ServerError {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 write!(
163 f,
164 "{}: {} (SQLSTATE {})",
165 self.severity_localized(),
166 self.message(),
167 self.code()
168 )?;
169 if let Some(detail) = self.detail() {
170 write!(f, "\nDETAIL: {}", detail)?;
171 }
172 if let Some(hint) = self.hint() {
173 write!(f, "\nHINT: {}", hint)?;
174 }
175 Ok(())
176 }
177}
178
179#[derive(Debug, Error)]
181pub enum Error {
182 #[error("PostgreSQL error: {0}")]
184 Server(ServerError),
185
186 #[error("Library bug: {0}")]
188 LibraryBug(String),
189
190 #[error("I/O error: {0}")]
192 Io(#[from] std::io::Error),
193
194 #[error("Authentication failed: {0}")]
196 Auth(String),
197
198 #[cfg(any(feature = "sync-tls", feature = "tokio-tls", feature = "compio-tls"))]
200 #[error("TLS error: {0}")]
201 Tls(#[from] native_tls::Error),
202
203 #[error("Connection is broken")]
205 ConnectionBroken,
206
207 #[error("Invalid usage: {0}")]
209 InvalidUsage(String),
210
211 #[error("Unsupported: {0}")]
213 Unsupported(String),
214
215 #[error("Decode error: {0}")]
217 Decode(String),
218
219 #[error("Encode error: {0}")]
221 Encode(String),
222}
223
224impl Error {
225 pub fn overflow(from: &str, to: &str) -> Self {
227 Error::Encode(format!("value overflow: cannot convert {} to {}", from, to))
228 }
229
230 pub fn type_mismatch(value_oid: u32, target_oid: u32) -> Self {
232 Error::Encode(format!(
233 "type mismatch: value has OID {} but target expects OID {}",
234 value_oid, target_oid
235 ))
236 }
237
238 pub fn is_connection_broken(&self) -> bool {
242 match self {
243 Error::Server(err) => matches!(err.severity_english(), "FATAL" | "PANIC"),
244 Error::Decode(_) | Error::Encode(_) | Error::InvalidUsage(_) => false,
245 _ => true,
246 }
247 }
248
249 pub fn sqlstate(&self) -> Option<&str> {
251 match self {
252 Error::Server(err) => Some(err.code()),
253 _ => None,
254 }
255 }
256}
257
258impl<Src: std::fmt::Debug, Dst: std::fmt::Debug + ?Sized> From<zerocopy::error::CastError<Src, Dst>>
259 for Error
260{
261 fn from(err: zerocopy::error::CastError<Src, Dst>) -> Self {
262 Error::LibraryBug(format!("zerocopy cast error: {err:?}"))
263 }
264}
265
266impl From<std::convert::Infallible> for Error {
267 fn from(err: std::convert::Infallible) -> Self {
268 match err {}
269 }
270}