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#[allow(missing_docs)]
342#[derive(Debug, PartialEq)]
343pub enum ErrorKind {
344 Io,
345 UnexpectedMessage,
346 Tls,
347 ToSql(usize),
348 FromSql(usize),
349 Column(String),
350 ColumnIndexOutOfBound(usize),
351 Parameters(usize, usize),
352 Closed,
353 Db,
354 Parse,
355 Encode,
356 Authentication,
357 ConfigParse,
358 Config,
359 RowCount,
360 #[cfg(feature = "runtime")]
361 Connect,
362 Timeout,
363 Custom,
364}
365
366struct ErrorInner {
367 kind: ErrorKind,
368 cause: Option<Box<dyn error::Error + Sync + Send>>,
369}
370
371pub struct Error(Box<ErrorInner>);
373
374impl fmt::Debug for Error {
375 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
376 fmt.debug_struct("Error")
377 .field("kind", &self.0.kind)
378 .field("cause", &self.0.cause)
379 .finish()
380 }
381}
382
383impl fmt::Display for Error {
384 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
385 match &self.0.kind {
386 ErrorKind::Io => fmt.write_str("error communicating with the server")?,
387 ErrorKind::UnexpectedMessage => fmt.write_str("unexpected message from server")?,
388 ErrorKind::Tls => fmt.write_str("error performing TLS handshake")?,
389 ErrorKind::ToSql(idx) => write!(fmt, "error serializing parameter {}", idx)?,
390 ErrorKind::FromSql(idx) => write!(fmt, "error deserializing column {}", idx)?,
391 ErrorKind::Column(column) => write!(fmt, "invalid column `{}`", column)?,
392 ErrorKind::Parameters(real, expected) => {
393 write!(fmt, "expected {expected} parameters but got {real}")?
394 }
395 ErrorKind::Closed => fmt.write_str("connection closed")?,
396 ErrorKind::ColumnIndexOutOfBound(index) => {
397 write!(fmt, "column index out of bound {index}")?
398 }
399 ErrorKind::Db => fmt.write_str("db error")?,
400 ErrorKind::Parse => fmt.write_str("error parsing response from server")?,
401 ErrorKind::Encode => fmt.write_str("error encoding message to server")?,
402 ErrorKind::Authentication => fmt.write_str("authentication error")?,
403 ErrorKind::ConfigParse => fmt.write_str("invalid connection string")?,
404 ErrorKind::Config => fmt.write_str("invalid configuration")?,
405 ErrorKind::RowCount => fmt.write_str("query returned an unexpected number of rows")?,
406 #[cfg(feature = "runtime")]
407 ErrorKind::Connect => fmt.write_str("error connecting to server")?,
408 ErrorKind::Timeout => fmt.write_str("timeout waiting for server")?,
409 ErrorKind::Custom => write!(fmt, "error: {:?}", self.0.cause)?,
410 };
411 if let Some(ref cause) = self.0.cause {
412 write!(fmt, ": {}", cause)?;
413 }
414 Ok(())
415 }
416}
417
418impl error::Error for Error {
419 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
420 self.0.cause.as_ref().map(|e| &**e as _)
421 }
422}
423
424impl Error {
425 pub fn kind(&self) -> &ErrorKind {
427 &self.0.kind
428 }
429
430 pub fn into_source(self) -> Option<Box<dyn error::Error + Sync + Send>> {
432 self.0.cause
433 }
434
435 pub fn as_db_error(&self) -> Option<&DbError> {
439 self.source().and_then(|e| e.downcast_ref::<DbError>())
440 }
441
442 pub fn is_closed(&self) -> bool {
444 self.0.kind == ErrorKind::Closed
445 }
446
447 pub fn code(&self) -> Option<&SqlState> {
451 self.as_db_error().map(DbError::code)
452 }
453
454 pub fn custom(cause: Box<dyn error::Error + Sync + Send>) -> Error {
456 Error(Box::new(ErrorInner {
457 kind: ErrorKind::Custom,
458 cause: Some(cause),
459 }))
460 }
461
462 pub fn new(kind: ErrorKind, cause: Option<Box<dyn error::Error + Sync + Send>>) -> Error {
464 Error(Box::new(ErrorInner { kind, cause }))
465 }
466
467 pub(crate) fn closed() -> Error {
468 Error::new(ErrorKind::Closed, None)
469 }
470
471 pub(crate) fn unexpected_message() -> Error {
472 Error::new(ErrorKind::UnexpectedMessage, None)
473 }
474
475 #[allow(clippy::needless_pass_by_value)]
476 pub(crate) fn db(error: ErrorResponseBody) -> Error {
477 match DbError::parse(&mut error.fields()) {
478 Ok(e) => Error::new(ErrorKind::Db, Some(Box::new(e))),
479 Err(e) => Error::new(ErrorKind::Parse, Some(Box::new(e))),
480 }
481 }
482
483 pub(crate) fn parse(e: io::Error) -> Error {
484 Error::new(ErrorKind::Parse, Some(Box::new(e)))
485 }
486
487 pub(crate) fn encode(e: io::Error) -> Error {
488 Error::new(ErrorKind::Encode, Some(Box::new(e)))
489 }
490
491 #[allow(clippy::wrong_self_convention)]
492 pub(crate) fn to_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
493 Error::new(ErrorKind::ToSql(idx), Some(e))
494 }
495
496 pub(crate) fn from_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
497 Error::new(ErrorKind::FromSql(idx), Some(e))
498 }
499
500 pub(crate) fn column_index(index: usize) -> Error {
501 Error::new(ErrorKind::ColumnIndexOutOfBound(index), None)
502 }
503 pub(crate) fn column(column: String) -> Error {
504 Error::new(ErrorKind::Column(column), None)
505 }
506
507 pub(crate) fn parameters(real: usize, expected: usize) -> Error {
508 Error::new(ErrorKind::Parameters(real, expected), None)
509 }
510
511 pub(crate) fn tls(e: Box<dyn error::Error + Sync + Send>) -> Error {
512 Error::new(ErrorKind::Tls, Some(e))
513 }
514
515 pub(crate) fn io(e: io::Error) -> Error {
516 Error::new(ErrorKind::Io, Some(Box::new(e)))
517 }
518
519 pub(crate) fn authentication(e: Box<dyn error::Error + Sync + Send>) -> Error {
520 Error::new(ErrorKind::Authentication, Some(e))
521 }
522
523 pub(crate) fn config_parse(e: Box<dyn error::Error + Sync + Send>) -> Error {
524 Error::new(ErrorKind::ConfigParse, Some(e))
525 }
526
527 pub(crate) fn config(e: Box<dyn error::Error + Sync + Send>) -> Error {
528 Error::new(ErrorKind::Config, Some(e))
529 }
530
531 pub(crate) fn row_count() -> Error {
532 Error::new(ErrorKind::RowCount, None)
533 }
534
535 #[cfg(feature = "runtime")]
536 pub(crate) fn connect(e: io::Error) -> Error {
537 Error::new(ErrorKind::Connect, Some(Box::new(e)))
538 }
539
540 #[doc(hidden)]
541 pub fn __private_api_timeout() -> Error {
542 Error::new(ErrorKind::Timeout, None)
543 }
544}