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