1use fallible_iterator::FallibleIterator;
4use postgres_protocol::message::backend::{ErrorFields, ErrorResponseBody};
5use std::error::{self, Error as _Error};
6use std::fmt;
7use std::io;
8
9pub use self::sqlstate::*;
10
11#[allow(clippy::unreadable_literal)]
12mod sqlstate;
13
14#[derive(Debug, Copy, Clone, PartialEq, Eq)]
16pub enum Severity {
17 Panic,
19 Fatal,
21 Error,
23 Warning,
25 Notice,
27 Debug,
29 Info,
31 Log,
33}
34
35impl fmt::Display for Severity {
36 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
37 let s = match *self {
38 Severity::Panic => "PANIC",
39 Severity::Fatal => "FATAL",
40 Severity::Error => "ERROR",
41 Severity::Warning => "WARNING",
42 Severity::Notice => "NOTICE",
43 Severity::Debug => "DEBUG",
44 Severity::Info => "INFO",
45 Severity::Log => "LOG",
46 };
47 fmt.write_str(s)
48 }
49}
50
51impl Severity {
52 fn from_str(s: &str) -> Option<Severity> {
53 match s {
54 "PANIC" => Some(Severity::Panic),
55 "FATAL" => Some(Severity::Fatal),
56 "ERROR" => Some(Severity::Error),
57 "WARNING" => Some(Severity::Warning),
58 "NOTICE" => Some(Severity::Notice),
59 "DEBUG" => Some(Severity::Debug),
60 "INFO" => Some(Severity::Info),
61 "LOG" => Some(Severity::Log),
62 _ => None,
63 }
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct DbError {
70 severity: String,
71 parsed_severity: Option<Severity>,
72 code: SqlState,
73 message: String,
74 detail: Option<String>,
75 hint: Option<String>,
76 position: Option<ErrorPosition>,
77 where_: Option<String>,
78 schema: Option<String>,
79 table: Option<String>,
80 column: Option<String>,
81 datatype: Option<String>,
82 constraint: Option<String>,
83 file: Option<String>,
84 line: Option<u32>,
85 routine: Option<String>,
86}
87
88impl DbError {
89 pub(crate) fn parse(fields: &mut ErrorFields<'_>) -> io::Result<DbError> {
90 let mut severity = None;
91 let mut parsed_severity = None;
92 let mut code = None;
93 let mut message = None;
94 let mut detail = None;
95 let mut hint = None;
96 let mut normal_position = None;
97 let mut internal_position = None;
98 let mut internal_query = None;
99 let mut where_ = None;
100 let mut schema = None;
101 let mut table = None;
102 let mut column = None;
103 let mut datatype = None;
104 let mut constraint = None;
105 let mut file = None;
106 let mut line = None;
107 let mut routine = None;
108
109 while let Some(field) = fields.next()? {
110 let value = String::from_utf8_lossy(field.value_bytes());
111 match field.type_() {
112 b'S' => severity = Some(value.into_owned()),
113 b'C' => code = Some(SqlState::from_code(&value)),
114 b'M' => message = Some(value.into_owned()),
115 b'D' => detail = Some(value.into_owned()),
116 b'H' => hint = Some(value.into_owned()),
117 b'P' => {
118 normal_position = Some(value.parse::<u32>().map_err(|_| {
119 io::Error::new(
120 io::ErrorKind::InvalidInput,
121 "`P` field did not contain an integer",
122 )
123 })?);
124 }
125 b'p' => {
126 internal_position = Some(value.parse::<u32>().map_err(|_| {
127 io::Error::new(
128 io::ErrorKind::InvalidInput,
129 "`p` field did not contain an integer",
130 )
131 })?);
132 }
133 b'q' => internal_query = Some(value.into_owned()),
134 b'W' => where_ = Some(value.into_owned()),
135 b's' => schema = Some(value.into_owned()),
136 b't' => table = Some(value.into_owned()),
137 b'c' => column = Some(value.into_owned()),
138 b'd' => datatype = Some(value.into_owned()),
139 b'n' => constraint = Some(value.into_owned()),
140 b'F' => file = Some(value.into_owned()),
141 b'L' => {
142 line = Some(value.parse::<u32>().map_err(|_| {
143 io::Error::new(
144 io::ErrorKind::InvalidInput,
145 "`L` field did not contain an integer",
146 )
147 })?);
148 }
149 b'R' => routine = Some(value.into_owned()),
150 b'V' => {
151 parsed_severity = Some(Severity::from_str(&value).ok_or_else(|| {
152 io::Error::new(
153 io::ErrorKind::InvalidInput,
154 "`V` field contained an invalid value",
155 )
156 })?);
157 }
158 _ => {}
159 }
160 }
161
162 Ok(DbError {
163 severity: severity
164 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`S` field missing"))?,
165 parsed_severity,
166 code: code
167 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`C` field missing"))?,
168 message: message
169 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`M` field missing"))?,
170 detail,
171 hint,
172 position: match normal_position {
173 Some(position) => Some(ErrorPosition::Original(position)),
174 None => match internal_position {
175 Some(position) => Some(ErrorPosition::Internal {
176 position,
177 query: internal_query.ok_or_else(|| {
178 io::Error::new(
179 io::ErrorKind::InvalidInput,
180 "`q` field missing but `p` field present",
181 )
182 })?,
183 }),
184 None => None,
185 },
186 },
187 where_,
188 schema,
189 table,
190 column,
191 datatype,
192 constraint,
193 file,
194 line,
195 routine,
196 })
197 }
198
199 pub fn severity(&self) -> &str {
203 &self.severity
204 }
205
206 pub fn parsed_severity(&self) -> Option<Severity> {
208 self.parsed_severity
209 }
210
211 pub fn code(&self) -> &SqlState {
213 &self.code
214 }
215
216 pub fn message(&self) -> &str {
220 &self.message
221 }
222
223 pub fn detail(&self) -> Option<&str> {
228 self.detail.as_deref()
229 }
230
231 pub fn hint(&self) -> Option<&str> {
237 self.hint.as_deref()
238 }
239
240 pub fn position(&self) -> Option<&ErrorPosition> {
243 self.position.as_ref()
244 }
245
246 pub fn where_(&self) -> Option<&str> {
252 self.where_.as_deref()
253 }
254
255 pub fn schema(&self) -> Option<&str> {
258 self.schema.as_deref()
259 }
260
261 pub fn table(&self) -> Option<&str> {
265 self.table.as_deref()
266 }
267
268 pub fn column(&self) -> Option<&str> {
274 self.column.as_deref()
275 }
276
277 pub fn datatype(&self) -> Option<&str> {
281 self.datatype.as_deref()
282 }
283
284 pub fn constraint(&self) -> Option<&str> {
291 self.constraint.as_deref()
292 }
293
294 pub fn file(&self) -> Option<&str> {
296 self.file.as_deref()
297 }
298
299 pub fn line(&self) -> Option<u32> {
302 self.line
303 }
304
305 pub fn routine(&self) -> Option<&str> {
307 self.routine.as_deref()
308 }
309}
310
311impl fmt::Display for DbError {
312 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
313 write!(fmt, "{}: {}", self.severity, self.message)?;
314 if let Some(detail) = &self.detail {
315 write!(fmt, "\nDETAIL: {detail}")?;
316 }
317 if let Some(hint) = &self.hint {
318 write!(fmt, "\nHINT: {hint}")?;
319 }
320 Ok(())
321 }
322}
323
324impl error::Error for DbError {}
325
326#[derive(Clone, PartialEq, Eq, Debug)]
328pub enum ErrorPosition {
329 Original(u32),
331 Internal {
333 position: u32,
335 query: String,
337 },
338}
339
340#[derive(Debug, PartialEq)]
341enum Kind {
342 Io,
343 UnexpectedMessage,
344 Tls,
345 ToSql(usize),
346 FromSql(usize),
347 Column(String),
348 ColumnCount,
349 Parameters(usize, usize),
350 Closed,
351 Db,
352 Parse,
353 Encode,
354 Authentication,
355 ConfigParse,
356 Config,
357 RowCount,
358 #[cfg(feature = "runtime")]
359 Connect,
360 Timeout,
361}
362
363struct ErrorInner {
364 kind: Kind,
365 cause: Option<Box<dyn error::Error + Sync + Send>>,
366}
367
368pub struct Error(Box<ErrorInner>);
370
371impl fmt::Debug for Error {
372 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
373 fmt.debug_struct("Error")
374 .field("kind", &self.0.kind)
375 .field("cause", &self.0.cause)
376 .finish()
377 }
378}
379
380impl fmt::Display for Error {
381 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
382 match &self.0.kind {
383 Kind::Io => fmt.write_str("error communicating with the server"),
384 Kind::UnexpectedMessage => fmt.write_str("unexpected message from server"),
385 Kind::Tls => fmt.write_str("error performing TLS handshake"),
386 Kind::ToSql(idx) => write!(fmt, "error serializing parameter {idx}"),
387 Kind::FromSql(idx) => write!(fmt, "error deserializing column {idx}"),
388 Kind::Column(column) => write!(fmt, "invalid column `{column}`"),
389 Kind::ColumnCount => write!(fmt, "query returned an unexpected number of columns"),
390 Kind::Parameters(real, expected) => {
391 write!(fmt, "expected {expected} parameters but got {real}")
392 }
393 Kind::Closed => fmt.write_str("connection closed"),
394 Kind::Db => fmt.write_str("db error"),
395 Kind::Parse => fmt.write_str("error parsing response from server"),
396 Kind::Encode => fmt.write_str("error encoding message to server"),
397 Kind::Authentication => fmt.write_str("authentication error"),
398 Kind::ConfigParse => fmt.write_str("invalid connection string"),
399 Kind::Config => fmt.write_str("invalid configuration"),
400 Kind::RowCount => fmt.write_str("query returned an unexpected number of rows"),
401 #[cfg(feature = "runtime")]
402 Kind::Connect => fmt.write_str("error connecting to server"),
403 Kind::Timeout => fmt.write_str("timeout waiting for server"),
404 }
405 }
406}
407
408impl error::Error for Error {
409 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
410 self.0.cause.as_ref().map(|e| &**e as _)
411 }
412}
413
414impl Error {
415 pub fn into_source(self) -> Option<Box<dyn error::Error + Sync + Send>> {
417 self.0.cause
418 }
419
420 pub fn as_db_error(&self) -> Option<&DbError> {
424 self.source().and_then(|e| e.downcast_ref::<DbError>())
425 }
426
427 pub fn is_closed(&self) -> bool {
429 self.0.kind == Kind::Closed
430 }
431
432 pub fn code(&self) -> Option<&SqlState> {
436 self.as_db_error().map(DbError::code)
437 }
438
439 fn new(kind: Kind, cause: Option<Box<dyn error::Error + Sync + Send>>) -> Error {
440 Error(Box::new(ErrorInner { kind, cause }))
441 }
442
443 pub(crate) fn closed() -> Error {
444 Error::new(Kind::Closed, None)
445 }
446
447 pub(crate) fn unexpected_message() -> Error {
448 Error::new(Kind::UnexpectedMessage, None)
449 }
450
451 #[allow(clippy::needless_pass_by_value)]
452 pub(crate) fn db(error: ErrorResponseBody) -> Error {
453 match DbError::parse(&mut error.fields()) {
454 Ok(e) => Error::new(Kind::Db, Some(Box::new(e))),
455 Err(e) => Error::new(Kind::Parse, Some(Box::new(e))),
456 }
457 }
458
459 pub(crate) fn parse(e: io::Error) -> Error {
460 Error::new(Kind::Parse, Some(Box::new(e)))
461 }
462
463 pub(crate) fn encode(e: io::Error) -> Error {
464 Error::new(Kind::Encode, Some(Box::new(e)))
465 }
466
467 #[allow(clippy::wrong_self_convention)]
468 pub(crate) fn to_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
469 Error::new(Kind::ToSql(idx), Some(e))
470 }
471
472 pub(crate) fn from_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
473 Error::new(Kind::FromSql(idx), Some(e))
474 }
475
476 pub(crate) fn column(column: String) -> Error {
477 Error::new(Kind::Column(column), None)
478 }
479
480 pub(crate) fn column_count() -> Error {
481 Error::new(Kind::ColumnCount, None)
482 }
483
484 pub(crate) fn parameters(real: usize, expected: usize) -> Error {
485 Error::new(Kind::Parameters(real, expected), None)
486 }
487
488 pub(crate) fn tls(e: Box<dyn error::Error + Sync + Send>) -> Error {
489 Error::new(Kind::Tls, Some(e))
490 }
491
492 pub(crate) fn io(e: io::Error) -> Error {
493 Error::new(Kind::Io, Some(Box::new(e)))
494 }
495
496 pub(crate) fn authentication(e: Box<dyn error::Error + Sync + Send>) -> Error {
497 Error::new(Kind::Authentication, Some(e))
498 }
499
500 pub(crate) fn config_parse(e: Box<dyn error::Error + Sync + Send>) -> Error {
501 Error::new(Kind::ConfigParse, Some(e))
502 }
503
504 pub(crate) fn config(e: Box<dyn error::Error + Sync + Send>) -> Error {
505 Error::new(Kind::Config, Some(e))
506 }
507
508 pub(crate) fn row_count() -> Error {
509 Error::new(Kind::RowCount, None)
510 }
511
512 #[cfg(feature = "runtime")]
513 pub(crate) fn connect(e: io::Error) -> Error {
514 Error::new(Kind::Connect, Some(Box::new(e)))
515 }
516
517 #[doc(hidden)]
518 pub fn __private_api_timeout() -> Error {
519 Error::new(Kind::Timeout, None)
520 }
521}