tokio_postgres/error/
mod.rs

1//! Errors.
2
3use 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/// The severity of a Postgres error or notice.
15#[derive(Debug, Copy, Clone, PartialEq, Eq)]
16pub enum Severity {
17    /// PANIC
18    Panic,
19    /// FATAL
20    Fatal,
21    /// ERROR
22    Error,
23    /// WARNING
24    Warning,
25    /// NOTICE
26    Notice,
27    /// DEBUG
28    Debug,
29    /// INFO
30    Info,
31    /// LOG
32    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/// A Postgres error or notice.
68#[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    /// Parses the error fields obtained from Postgres into a `DBError`.
90    pub fn parse(fields: &mut ErrorFields<'_>) -> io::Result<DbError> {
91        let mut severity = None;
92        let mut parsed_severity = None;
93        let mut code = None;
94        let mut message = None;
95        let mut detail = None;
96        let mut hint = None;
97        let mut normal_position = None;
98        let mut internal_position = None;
99        let mut internal_query = None;
100        let mut where_ = None;
101        let mut schema = None;
102        let mut table = None;
103        let mut column = None;
104        let mut datatype = None;
105        let mut constraint = None;
106        let mut file = None;
107        let mut line = None;
108        let mut routine = None;
109
110        while let Some(field) = fields.next()? {
111            let value = String::from_utf8_lossy(field.value_bytes());
112            match field.type_() {
113                b'S' => severity = Some(value.into_owned()),
114                b'C' => code = Some(SqlState::from_code(&value)),
115                b'M' => message = Some(value.into_owned()),
116                b'D' => detail = Some(value.into_owned()),
117                b'H' => hint = Some(value.into_owned()),
118                b'P' => {
119                    normal_position = Some(value.parse::<u32>().map_err(|_| {
120                        io::Error::new(
121                            io::ErrorKind::InvalidInput,
122                            "`P` field did not contain an integer",
123                        )
124                    })?);
125                }
126                b'p' => {
127                    internal_position = Some(value.parse::<u32>().map_err(|_| {
128                        io::Error::new(
129                            io::ErrorKind::InvalidInput,
130                            "`p` field did not contain an integer",
131                        )
132                    })?);
133                }
134                b'q' => internal_query = Some(value.into_owned()),
135                b'W' => where_ = Some(value.into_owned()),
136                b's' => schema = Some(value.into_owned()),
137                b't' => table = Some(value.into_owned()),
138                b'c' => column = Some(value.into_owned()),
139                b'd' => datatype = Some(value.into_owned()),
140                b'n' => constraint = Some(value.into_owned()),
141                b'F' => file = Some(value.into_owned()),
142                b'L' => {
143                    line = Some(value.parse::<u32>().map_err(|_| {
144                        io::Error::new(
145                            io::ErrorKind::InvalidInput,
146                            "`L` field did not contain an integer",
147                        )
148                    })?);
149                }
150                b'R' => routine = Some(value.into_owned()),
151                b'V' => {
152                    parsed_severity = Some(Severity::from_str(&value).ok_or_else(|| {
153                        io::Error::new(
154                            io::ErrorKind::InvalidInput,
155                            "`V` field contained an invalid value",
156                        )
157                    })?);
158                }
159                _ => {}
160            }
161        }
162
163        Ok(DbError {
164            severity: severity
165                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`S` field missing"))?,
166            parsed_severity,
167            code: code
168                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`C` field missing"))?,
169            message: message
170                .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`M` field missing"))?,
171            detail,
172            hint,
173            position: match normal_position {
174                Some(position) => Some(ErrorPosition::Original(position)),
175                None => match internal_position {
176                    Some(position) => Some(ErrorPosition::Internal {
177                        position,
178                        query: internal_query.ok_or_else(|| {
179                            io::Error::new(
180                                io::ErrorKind::InvalidInput,
181                                "`q` field missing but `p` field present",
182                            )
183                        })?,
184                    }),
185                    None => None,
186                },
187            },
188            where_,
189            schema,
190            table,
191            column,
192            datatype,
193            constraint,
194            file,
195            line,
196            routine,
197        })
198    }
199
200    /// The field contents are ERROR, FATAL, or PANIC (in an error message),
201    /// or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message), or a
202    /// localized translation of one of these.
203    pub fn severity(&self) -> &str {
204        &self.severity
205    }
206
207    /// A parsed, nonlocalized version of `severity`. (PostgreSQL 9.6+)
208    pub fn parsed_severity(&self) -> Option<Severity> {
209        self.parsed_severity
210    }
211
212    /// The SQLSTATE code for the error.
213    pub fn code(&self) -> &SqlState {
214        &self.code
215    }
216
217    /// The primary human-readable error message.
218    ///
219    /// This should be accurate but terse (typically one line).
220    pub fn message(&self) -> &str {
221        &self.message
222    }
223
224    /// An optional secondary error message carrying more detail about the
225    /// problem.
226    ///
227    /// Might run to multiple lines.
228    pub fn detail(&self) -> Option<&str> {
229        self.detail.as_deref()
230    }
231
232    /// An optional suggestion what to do about the problem.
233    ///
234    /// This is intended to differ from `detail` in that it offers advice
235    /// (potentially inappropriate) rather than hard facts. Might run to
236    /// multiple lines.
237    pub fn hint(&self) -> Option<&str> {
238        self.hint.as_deref()
239    }
240
241    /// An optional error cursor position into either the original query string
242    /// or an internally generated query.
243    pub fn position(&self) -> Option<&ErrorPosition> {
244        self.position.as_ref()
245    }
246
247    /// An indication of the context in which the error occurred.
248    ///
249    /// Presently this includes a call stack traceback of active procedural
250    /// language functions and internally-generated queries. The trace is one
251    /// entry per line, most recent first.
252    pub fn where_(&self) -> Option<&str> {
253        self.where_.as_deref()
254    }
255
256    /// If the error was associated with a specific database object, the name
257    /// of the schema containing that object, if any. (PostgreSQL 9.3+)
258    pub fn schema(&self) -> Option<&str> {
259        self.schema.as_deref()
260    }
261
262    /// If the error was associated with a specific table, the name of the
263    /// table. (Refer to the schema name field for the name of the table's
264    /// schema.) (PostgreSQL 9.3+)
265    pub fn table(&self) -> Option<&str> {
266        self.table.as_deref()
267    }
268
269    /// If the error was associated with a specific table column, the name of
270    /// the column.
271    ///
272    /// (Refer to the schema and table name fields to identify the table.)
273    /// (PostgreSQL 9.3+)
274    pub fn column(&self) -> Option<&str> {
275        self.column.as_deref()
276    }
277
278    /// If the error was associated with a specific data type, the name of the
279    /// data type. (Refer to the schema name field for the name of the data
280    /// type's schema.) (PostgreSQL 9.3+)
281    pub fn datatype(&self) -> Option<&str> {
282        self.datatype.as_deref()
283    }
284
285    /// If the error was associated with a specific constraint, the name of the
286    /// constraint.
287    ///
288    /// Refer to fields listed above for the associated table or domain.
289    /// (For this purpose, indexes are treated as constraints, even if they
290    /// weren't created with constraint syntax.) (PostgreSQL 9.3+)
291    pub fn constraint(&self) -> Option<&str> {
292        self.constraint.as_deref()
293    }
294
295    /// The file name of the source-code location where the error was reported.
296    pub fn file(&self) -> Option<&str> {
297        self.file.as_deref()
298    }
299
300    /// The line number of the source-code location where the error was
301    /// reported.
302    pub fn line(&self) -> Option<u32> {
303        self.line
304    }
305
306    /// The name of the source-code routine reporting the error.
307    pub fn routine(&self) -> Option<&str> {
308        self.routine.as_deref()
309    }
310}
311
312impl fmt::Display for DbError {
313    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
314        write!(fmt, "{}: {}", self.severity, self.message)?;
315        if let Some(detail) = &self.detail {
316            write!(fmt, "\nDETAIL: {}", detail)?;
317        }
318        if let Some(hint) = &self.hint {
319            write!(fmt, "\nHINT: {}", hint)?;
320        }
321        Ok(())
322    }
323}
324
325impl error::Error for DbError {}
326
327/// Represents the position of an error in a query.
328#[derive(Clone, PartialEq, Eq, Debug)]
329pub enum ErrorPosition {
330    /// A position in the original query.
331    Original(u32),
332    /// A position in an internally generated query.
333    Internal {
334        /// The byte position.
335        position: u32,
336        /// A query generated by the Postgres server.
337        query: String,
338    },
339}
340
341#[derive(Debug, PartialEq)]
342enum Kind {
343    Io,
344    UnexpectedMessage,
345    Tls,
346    ToSql(usize),
347    FromSql(usize),
348    Column(String),
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
368/// An error communicating with the Postgres server.
369pub 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::Parameters(real, expected) => {
390                write!(fmt, "expected {expected} parameters but got {real}")?
391            }
392            Kind::Closed => fmt.write_str("connection closed")?,
393            Kind::Db => fmt.write_str("db error")?,
394            Kind::Parse => fmt.write_str("error parsing response from server")?,
395            Kind::Encode => fmt.write_str("error encoding message to server")?,
396            Kind::Authentication => fmt.write_str("authentication error")?,
397            Kind::ConfigParse => fmt.write_str("invalid connection string")?,
398            Kind::Config => fmt.write_str("invalid configuration")?,
399            Kind::RowCount => fmt.write_str("query returned an unexpected number of rows")?,
400            #[cfg(feature = "runtime")]
401            Kind::Connect => fmt.write_str("error connecting to server")?,
402            Kind::Timeout => fmt.write_str("timeout waiting for server")?,
403        };
404        if let Some(ref cause) = self.0.cause {
405            write!(fmt, ": {}", cause)?;
406        }
407        Ok(())
408    }
409}
410
411impl error::Error for Error {
412    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
413        self.0.cause.as_ref().map(|e| &**e as _)
414    }
415}
416
417impl Error {
418    /// Consumes the error, returning its cause.
419    pub fn into_source(self) -> Option<Box<dyn error::Error + Sync + Send>> {
420        self.0.cause
421    }
422
423    /// Returns the source of this error if it was a `DbError`.
424    ///
425    /// This is a simple convenience method.
426    pub fn as_db_error(&self) -> Option<&DbError> {
427        self.source().and_then(|e| e.downcast_ref::<DbError>())
428    }
429
430    /// Determines if the error was associated with closed connection.
431    pub fn is_closed(&self) -> bool {
432        self.0.kind == Kind::Closed
433    }
434
435    /// Returns the SQLSTATE error code associated with the error.
436    ///
437    /// This is a convenience method that downcasts the cause to a `DbError` and returns its code.
438    pub fn code(&self) -> Option<&SqlState> {
439        self.as_db_error().map(DbError::code)
440    }
441
442    fn new(kind: Kind, cause: Option<Box<dyn error::Error + Sync + Send>>) -> Error {
443        Error(Box::new(ErrorInner { kind, cause }))
444    }
445
446    pub(crate) fn closed() -> Error {
447        Error::new(Kind::Closed, None)
448    }
449
450    /// Constructs an `UnexpectedMessage` error.
451    pub fn unexpected_message() -> Error {
452        Error::new(Kind::UnexpectedMessage, None)
453    }
454
455    #[allow(clippy::needless_pass_by_value)]
456    pub(crate) fn db(error: ErrorResponseBody) -> Error {
457        match DbError::parse(&mut error.fields()) {
458            Ok(e) => Error::new(Kind::Db, Some(Box::new(e))),
459            Err(e) => Error::new(Kind::Parse, Some(Box::new(e))),
460        }
461    }
462
463    /// Constructs a `Parse` error wrapping the provided one.
464    pub fn parse(e: io::Error) -> Error {
465        Error::new(Kind::Parse, Some(Box::new(e)))
466    }
467
468    pub(crate) fn encode(e: io::Error) -> Error {
469        Error::new(Kind::Encode, Some(Box::new(e)))
470    }
471
472    #[allow(clippy::wrong_self_convention)]
473    pub(crate) fn to_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
474        Error::new(Kind::ToSql(idx), Some(e))
475    }
476
477    pub(crate) fn from_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
478        Error::new(Kind::FromSql(idx), Some(e))
479    }
480
481    pub(crate) fn column(column: String) -> Error {
482        Error::new(Kind::Column(column), None)
483    }
484
485    pub(crate) fn parameters(real: usize, expected: usize) -> Error {
486        Error::new(Kind::Parameters(real, expected), None)
487    }
488
489    pub(crate) fn tls(e: Box<dyn error::Error + Sync + Send>) -> Error {
490        Error::new(Kind::Tls, Some(e))
491    }
492
493    pub(crate) fn io(e: io::Error) -> Error {
494        Error::new(Kind::Io, Some(Box::new(e)))
495    }
496
497    pub(crate) fn authentication(e: Box<dyn error::Error + Sync + Send>) -> Error {
498        Error::new(Kind::Authentication, Some(e))
499    }
500
501    pub(crate) fn config_parse(e: Box<dyn error::Error + Sync + Send>) -> Error {
502        Error::new(Kind::ConfigParse, Some(e))
503    }
504
505    pub(crate) fn config(e: Box<dyn error::Error + Sync + Send>) -> Error {
506        Error::new(Kind::Config, Some(e))
507    }
508
509    pub(crate) fn row_count() -> Error {
510        Error::new(Kind::RowCount, None)
511    }
512
513    #[cfg(feature = "runtime")]
514    pub(crate) fn connect(e: io::Error) -> Error {
515        Error::new(Kind::Connect, Some(Box::new(e)))
516    }
517
518    #[doc(hidden)]
519    pub fn __private_api_timeout() -> Error {
520        Error::new(Kind::Timeout, None)
521    }
522}