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    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
367/// An error communicating with the Postgres server.
368pub 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    /// Consumes the error, returning its cause.
418    pub fn into_source(self) -> Option<Box<dyn error::Error + Sync + Send>> {
419        self.0.cause
420    }
421
422    /// Returns the source of this error if it was a `DbError`.
423    ///
424    /// This is a simple convenience method.
425    pub fn as_db_error(&self) -> Option<&DbError> {
426        self.source().and_then(|e| e.downcast_ref::<DbError>())
427    }
428
429    /// Determines if the error was associated with closed connection.
430    pub fn is_closed(&self) -> bool {
431        self.0.kind == Kind::Closed
432    }
433
434    /// Returns the SQLSTATE error code associated with the error.
435    ///
436    /// This is a convenience method that downcasts the cause to a `DbError` and returns its code.
437    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}