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 match field.type_() {
111 b'S' => severity = Some(field.value().to_owned()),
112 b'C' => code = Some(SqlState::from_code(field.value())),
113 b'M' => message = Some(field.value().to_owned()),
114 b'D' => detail = Some(field.value().to_owned()),
115 b'H' => hint = Some(field.value().to_owned()),
116 b'P' => {
117 normal_position = Some(field.value().parse::<u32>().map_err(|_| {
118 io::Error::new(
119 io::ErrorKind::InvalidInput,
120 "`P` field did not contain an integer",
121 )
122 })?);
123 }
124 b'p' => {
125 internal_position = Some(field.value().parse::<u32>().map_err(|_| {
126 io::Error::new(
127 io::ErrorKind::InvalidInput,
128 "`p` field did not contain an integer",
129 )
130 })?);
131 }
132 b'q' => internal_query = Some(field.value().to_owned()),
133 b'W' => where_ = Some(field.value().to_owned()),
134 b's' => schema = Some(field.value().to_owned()),
135 b't' => table = Some(field.value().to_owned()),
136 b'c' => column = Some(field.value().to_owned()),
137 b'd' => datatype = Some(field.value().to_owned()),
138 b'n' => constraint = Some(field.value().to_owned()),
139 b'F' => file = Some(field.value().to_owned()),
140 b'L' => {
141 line = Some(field.value().parse::<u32>().map_err(|_| {
142 io::Error::new(
143 io::ErrorKind::InvalidInput,
144 "`L` field did not contain an integer",
145 )
146 })?);
147 }
148 b'R' => routine = Some(field.value().to_owned()),
149 b'V' => {
150 parsed_severity = Some(Severity::from_str(field.value()).ok_or_else(|| {
151 io::Error::new(
152 io::ErrorKind::InvalidInput,
153 "`V` field contained an invalid value",
154 )
155 })?);
156 }
157 _ => {}
158 }
159 }
160
161 Ok(DbError {
162 severity: severity
163 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`S` field missing"))?,
164 parsed_severity,
165 code: code
166 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`C` field missing"))?,
167 message: message
168 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`M` field missing"))?,
169 detail,
170 hint,
171 position: match normal_position {
172 Some(position) => Some(ErrorPosition::Original(position)),
173 None => match internal_position {
174 Some(position) => Some(ErrorPosition::Internal {
175 position,
176 query: internal_query.ok_or_else(|| {
177 io::Error::new(
178 io::ErrorKind::InvalidInput,
179 "`q` field missing but `p` field present",
180 )
181 })?,
182 }),
183 None => None,
184 },
185 },
186 where_,
187 schema,
188 table,
189 column,
190 datatype,
191 constraint,
192 file,
193 line,
194 routine,
195 })
196 }
197
198 pub fn severity(&self) -> &str {
202 &self.severity
203 }
204
205 pub fn parsed_severity(&self) -> Option<Severity> {
207 self.parsed_severity
208 }
209
210 pub fn code(&self) -> &SqlState {
212 &self.code
213 }
214
215 pub fn message(&self) -> &str {
219 &self.message
220 }
221
222 pub fn detail(&self) -> Option<&str> {
227 self.detail.as_deref()
228 }
229
230 pub fn hint(&self) -> Option<&str> {
236 self.hint.as_deref()
237 }
238
239 pub fn position(&self) -> Option<&ErrorPosition> {
242 self.position.as_ref()
243 }
244
245 pub fn where_(&self) -> Option<&str> {
251 self.where_.as_deref()
252 }
253
254 pub fn schema(&self) -> Option<&str> {
257 self.schema.as_deref()
258 }
259
260 pub fn table(&self) -> Option<&str> {
264 self.table.as_deref()
265 }
266
267 pub fn column(&self) -> Option<&str> {
273 self.column.as_deref()
274 }
275
276 pub fn datatype(&self) -> Option<&str> {
280 self.datatype.as_deref()
281 }
282
283 pub fn constraint(&self) -> Option<&str> {
290 self.constraint.as_deref()
291 }
292
293 pub fn file(&self) -> Option<&str> {
295 self.file.as_deref()
296 }
297
298 pub fn line(&self) -> Option<u32> {
301 self.line
302 }
303
304 pub fn routine(&self) -> Option<&str> {
306 self.routine.as_deref()
307 }
308}
309
310impl fmt::Display for DbError {
311 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
312 write!(fmt, "{}: {}", self.severity, self.message)?;
313 if let Some(detail) = &self.detail {
314 write!(fmt, "\nDETAIL: {}", detail)?;
315 }
316 if let Some(hint) = &self.hint {
317 write!(fmt, "\nHINT: {}", hint)?;
318 }
319 Ok(())
320 }
321}
322
323impl error::Error for DbError {}
324
325#[derive(Clone, PartialEq, Eq, Debug)]
327pub enum ErrorPosition {
328 Original(u32),
330 Internal {
332 position: u32,
334 query: String,
336 },
337}
338
339#[derive(Debug, PartialEq)]
340enum Kind {
341 Io,
342 UnexpectedMessage,
343 Tls,
344 ToSql(usize),
345 FromSql(usize),
346 Column(String),
347 Closed,
348 Db,
349 Parse,
350 Encode,
351 Authentication,
352 ConfigParse,
353 Config,
354 RowCount,
355 #[cfg(feature = "runtime")]
356 Connect,
357 Timeout,
358}
359
360struct ErrorInner {
361 kind: Kind,
362 cause: Option<Box<dyn error::Error + Sync + Send>>,
363}
364
365pub struct Error(Box<ErrorInner>);
367
368impl fmt::Debug for Error {
369 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
370 fmt.debug_struct("Error")
371 .field("kind", &self.0.kind)
372 .field("cause", &self.0.cause)
373 .finish()
374 }
375}
376
377impl fmt::Display for Error {
378 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
379 match &self.0.kind {
380 Kind::Io => fmt.write_str("error communicating with the server")?,
381 Kind::UnexpectedMessage => fmt.write_str("unexpected message from server")?,
382 Kind::Tls => fmt.write_str("error performing TLS handshake")?,
383 Kind::ToSql(idx) => write!(fmt, "error serializing parameter {}", idx)?,
384 Kind::FromSql(idx) => write!(fmt, "error deserializing column {}", idx)?,
385 Kind::Column(column) => write!(fmt, "invalid column `{}`", column)?,
386 Kind::Closed => fmt.write_str("connection closed")?,
387 Kind::Db => fmt.write_str("db error")?,
388 Kind::Parse => fmt.write_str("error parsing response from server")?,
389 Kind::Encode => fmt.write_str("error encoding message to server")?,
390 Kind::Authentication => fmt.write_str("authentication error")?,
391 Kind::ConfigParse => fmt.write_str("invalid connection string")?,
392 Kind::Config => fmt.write_str("invalid configuration")?,
393 Kind::RowCount => fmt.write_str("query returned an unexpected number of rows")?,
394 #[cfg(feature = "runtime")]
395 Kind::Connect => fmt.write_str("error connecting to server")?,
396 Kind::Timeout => fmt.write_str("timeout waiting for server")?,
397 };
398 if let Some(ref cause) = self.0.cause {
399 write!(fmt, ": {}", cause)?;
400 }
401 Ok(())
402 }
403}
404
405impl error::Error for Error {
406 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
407 self.0.cause.as_ref().map(|e| &**e as _)
408 }
409}
410
411impl Error {
412 pub fn into_source(self) -> Option<Box<dyn error::Error + Sync + Send>> {
414 self.0.cause
415 }
416
417 pub fn as_db_error(&self) -> Option<&DbError> {
421 self.source().and_then(|e| e.downcast_ref::<DbError>())
422 }
423
424 pub fn is_closed(&self) -> bool {
426 self.0.kind == Kind::Closed
427 }
428
429 pub fn code(&self) -> Option<&SqlState> {
433 self.as_db_error().map(DbError::code)
434 }
435
436 fn new(kind: Kind, cause: Option<Box<dyn error::Error + Sync + Send>>) -> Error {
437 Error(Box::new(ErrorInner { kind, cause }))
438 }
439
440 pub(crate) fn closed() -> Error {
441 Error::new(Kind::Closed, None)
442 }
443
444 pub(crate) fn unexpected_message() -> Error {
445 Error::new(Kind::UnexpectedMessage, None)
446 }
447
448 #[allow(clippy::needless_pass_by_value)]
449 pub(crate) fn db(error: ErrorResponseBody) -> Error {
450 match DbError::parse(&mut error.fields()) {
451 Ok(e) => Error::new(Kind::Db, Some(Box::new(e))),
452 Err(e) => Error::new(Kind::Parse, Some(Box::new(e))),
453 }
454 }
455
456 pub(crate) fn parse(e: io::Error) -> Error {
457 Error::new(Kind::Parse, Some(Box::new(e)))
458 }
459
460 pub(crate) fn encode(e: io::Error) -> Error {
461 Error::new(Kind::Encode, Some(Box::new(e)))
462 }
463
464 #[allow(clippy::wrong_self_convention)]
465 pub(crate) fn to_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
466 Error::new(Kind::ToSql(idx), Some(e))
467 }
468
469 pub(crate) fn from_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
470 Error::new(Kind::FromSql(idx), Some(e))
471 }
472
473 pub(crate) fn column(column: String) -> Error {
474 Error::new(Kind::Column(column), None)
475 }
476
477 pub(crate) fn tls(e: Box<dyn error::Error + Sync + Send>) -> Error {
478 Error::new(Kind::Tls, Some(e))
479 }
480
481 pub(crate) fn io(e: io::Error) -> Error {
482 Error::new(Kind::Io, Some(Box::new(e)))
483 }
484
485 pub(crate) fn authentication(e: Box<dyn error::Error + Sync + Send>) -> Error {
486 Error::new(Kind::Authentication, Some(e))
487 }
488
489 pub(crate) fn config_parse(e: Box<dyn error::Error + Sync + Send>) -> Error {
490 Error::new(Kind::ConfigParse, Some(e))
491 }
492
493 pub(crate) fn config(e: Box<dyn error::Error + Sync + Send>) -> Error {
494 Error::new(Kind::Config, Some(e))
495 }
496
497 pub(crate) fn row_count() -> Error {
498 Error::new(Kind::RowCount, None)
499 }
500
501 #[cfg(feature = "runtime")]
502 pub(crate) fn connect(e: io::Error) -> Error {
503 Error::new(Kind::Connect, Some(Box::new(e)))
504 }
505
506 #[doc(hidden)]
507 pub fn __private_api_timeout() -> Error {
508 Error::new(Kind::Timeout, None)
509 }
510}