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    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    /// The field contents are ERROR, FATAL, or PANIC (in an error message),
200    /// or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message), or a
201    /// localized translation of one of these.
202    pub fn severity(&self) -> &str {
203        &self.severity
204    }
205
206    /// A parsed, nonlocalized version of `severity`. (PostgreSQL 9.6+)
207    pub fn parsed_severity(&self) -> Option<Severity> {
208        self.parsed_severity
209    }
210
211    /// The SQLSTATE code for the error.
212    pub fn code(&self) -> &SqlState {
213        &self.code
214    }
215
216    /// The primary human-readable error message.
217    ///
218    /// This should be accurate but terse (typically one line).
219    pub fn message(&self) -> &str {
220        &self.message
221    }
222
223    /// An optional secondary error message carrying more detail about the
224    /// problem.
225    ///
226    /// Might run to multiple lines.
227    pub fn detail(&self) -> Option<&str> {
228        self.detail.as_deref()
229    }
230
231    /// An optional suggestion what to do about the problem.
232    ///
233    /// This is intended to differ from `detail` in that it offers advice
234    /// (potentially inappropriate) rather than hard facts. Might run to
235    /// multiple lines.
236    pub fn hint(&self) -> Option<&str> {
237        self.hint.as_deref()
238    }
239
240    /// An optional error cursor position into either the original query string
241    /// or an internally generated query.
242    pub fn position(&self) -> Option<&ErrorPosition> {
243        self.position.as_ref()
244    }
245
246    /// An indication of the context in which the error occurred.
247    ///
248    /// Presently this includes a call stack traceback of active procedural
249    /// language functions and internally-generated queries. The trace is one
250    /// entry per line, most recent first.
251    pub fn where_(&self) -> Option<&str> {
252        self.where_.as_deref()
253    }
254
255    /// If the error was associated with a specific database object, the name
256    /// of the schema containing that object, if any. (PostgreSQL 9.3+)
257    pub fn schema(&self) -> Option<&str> {
258        self.schema.as_deref()
259    }
260
261    /// If the error was associated with a specific table, the name of the
262    /// table. (Refer to the schema name field for the name of the table's
263    /// schema.) (PostgreSQL 9.3+)
264    pub fn table(&self) -> Option<&str> {
265        self.table.as_deref()
266    }
267
268    /// If the error was associated with a specific table column, the name of
269    /// the column.
270    ///
271    /// (Refer to the schema and table name fields to identify the table.)
272    /// (PostgreSQL 9.3+)
273    pub fn column(&self) -> Option<&str> {
274        self.column.as_deref()
275    }
276
277    /// If the error was associated with a specific data type, the name of the
278    /// data type. (Refer to the schema name field for the name of the data
279    /// type's schema.) (PostgreSQL 9.3+)
280    pub fn datatype(&self) -> Option<&str> {
281        self.datatype.as_deref()
282    }
283
284    /// If the error was associated with a specific constraint, the name of the
285    /// constraint.
286    ///
287    /// Refer to fields listed above for the associated table or domain.
288    /// (For this purpose, indexes are treated as constraints, even if they
289    /// weren't created with constraint syntax.) (PostgreSQL 9.3+)
290    pub fn constraint(&self) -> Option<&str> {
291        self.constraint.as_deref()
292    }
293
294    /// The file name of the source-code location where the error was reported.
295    pub fn file(&self) -> Option<&str> {
296        self.file.as_deref()
297    }
298
299    /// The line number of the source-code location where the error was
300    /// reported.
301    pub fn line(&self) -> Option<u32> {
302        self.line
303    }
304
305    /// The name of the source-code routine reporting the error.
306    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/// Represents the position of an error in a query.
327#[derive(Clone, PartialEq, Eq, Debug)]
328pub enum ErrorPosition {
329    /// A position in the original query.
330    Original(u32),
331    /// A position in an internally generated query.
332    Internal {
333        /// The byte position.
334        position: u32,
335        /// A query generated by the Postgres server.
336        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
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::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    /// Consumes the error, returning its cause.
416    pub fn into_source(self) -> Option<Box<dyn error::Error + Sync + Send>> {
417        self.0.cause
418    }
419
420    /// Returns the source of this error if it was a `DbError`.
421    ///
422    /// This is a simple convenience method.
423    pub fn as_db_error(&self) -> Option<&DbError> {
424        self.source().and_then(|e| e.downcast_ref::<DbError>())
425    }
426
427    /// Determines if the error was associated with closed connection.
428    pub fn is_closed(&self) -> bool {
429        self.0.kind == Kind::Closed
430    }
431
432    /// Returns the SQLSTATE error code associated with the error.
433    ///
434    /// This is a convenience method that downcasts the cause to a `DbError` and returns its code.
435    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}