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