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