yb_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    Parameters(usize, usize),
348    Closed,
349    Db,
350    Parse,
351    Encode,
352    Authentication,
353    ConfigParse,
354    Config,
355    RowCount,
356    #[cfg(feature = "runtime")]
357    Connect,
358    Timeout,
359}
360
361struct ErrorInner {
362    kind: Kind,
363    cause: Option<Box<dyn error::Error + Sync + Send>>,
364}
365
366/// An error communicating with the Postgres server.
367pub struct Error(Box<ErrorInner>);
368
369impl fmt::Debug for Error {
370    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
371        fmt.debug_struct("Error")
372            .field("kind", &self.0.kind)
373            .field("cause", &self.0.cause)
374            .finish()
375    }
376}
377
378impl fmt::Display for Error {
379    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
380        match &self.0.kind {
381            Kind::Io => fmt.write_str("error communicating with the server")?,
382            Kind::UnexpectedMessage => fmt.write_str("unexpected message from server")?,
383            Kind::Tls => fmt.write_str("error performing TLS handshake")?,
384            Kind::ToSql(idx) => write!(fmt, "error serializing parameter {}", idx)?,
385            Kind::FromSql(idx) => write!(fmt, "error deserializing column {}", idx)?,
386            Kind::Column(column) => write!(fmt, "invalid column `{}`", column)?,
387            Kind::Parameters(real, expected) => {
388                write!(fmt, "expected {expected} parameters but got {real}")?
389            }
390            Kind::Closed => fmt.write_str("connection closed")?,
391            Kind::Db => fmt.write_str("db error")?,
392            Kind::Parse => fmt.write_str("error parsing response from server")?,
393            Kind::Encode => fmt.write_str("error encoding message to server")?,
394            Kind::Authentication => fmt.write_str("authentication error")?,
395            Kind::ConfigParse => fmt.write_str("invalid connection string")?,
396            Kind::Config => fmt.write_str("invalid configuration")?,
397            Kind::RowCount => fmt.write_str("query returned an unexpected number of rows")?,
398            #[cfg(feature = "runtime")]
399            Kind::Connect => fmt.write_str("error connecting to server")?,
400            Kind::Timeout => fmt.write_str("timeout waiting for server")?,
401        };
402        if let Some(ref cause) = self.0.cause {
403            write!(fmt, ": {}", cause)?;
404        }
405        Ok(())
406    }
407}
408
409impl error::Error for Error {
410    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
411        self.0.cause.as_ref().map(|e| &**e as _)
412    }
413}
414
415impl Error {
416    /// Consumes the error, returning its cause.
417    pub fn into_source(self) -> Option<Box<dyn error::Error + Sync + Send>> {
418        self.0.cause
419    }
420
421    /// Returns the source of this error if it was a `DbError`.
422    ///
423    /// This is a simple convenience method.
424    pub fn as_db_error(&self) -> Option<&DbError> {
425        self.source().and_then(|e| e.downcast_ref::<DbError>())
426    }
427
428    /// Determines if the error was associated with closed connection.
429    pub fn is_closed(&self) -> bool {
430        self.0.kind == Kind::Closed
431    }
432
433    /// Returns the SQLSTATE error code associated with the error.
434    ///
435    /// This is a convenience method that downcasts the cause to a `DbError` and returns its code.
436    pub fn code(&self) -> Option<&SqlState> {
437        self.as_db_error().map(DbError::code)
438    }
439
440    fn new(kind: Kind, cause: Option<Box<dyn error::Error + Sync + Send>>) -> Error {
441        Error(Box::new(ErrorInner { kind, cause }))
442    }
443
444    pub(crate) fn closed() -> Error {
445        Error::new(Kind::Closed, None)
446    }
447
448    pub(crate) fn unexpected_message() -> Error {
449        Error::new(Kind::UnexpectedMessage, None)
450    }
451
452    #[allow(clippy::needless_pass_by_value)]
453    pub(crate) fn db(error: ErrorResponseBody) -> Error {
454        match DbError::parse(&mut error.fields()) {
455            Ok(e) => Error::new(Kind::Db, Some(Box::new(e))),
456            Err(e) => Error::new(Kind::Parse, Some(Box::new(e))),
457        }
458    }
459
460    pub(crate) fn parse(e: io::Error) -> Error {
461        Error::new(Kind::Parse, Some(Box::new(e)))
462    }
463
464    pub(crate) fn encode(e: io::Error) -> Error {
465        Error::new(Kind::Encode, Some(Box::new(e)))
466    }
467
468    #[allow(clippy::wrong_self_convention)]
469    pub(crate) fn to_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
470        Error::new(Kind::ToSql(idx), Some(e))
471    }
472
473    pub(crate) fn from_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
474        Error::new(Kind::FromSql(idx), Some(e))
475    }
476
477    pub(crate) fn column(column: String) -> Error {
478        Error::new(Kind::Column(column), None)
479    }
480
481    pub(crate) fn parameters(real: usize, expected: usize) -> Error {
482        Error::new(Kind::Parameters(real, expected), None)
483    }
484
485    pub(crate) fn tls(e: Box<dyn error::Error + Sync + Send>) -> Error {
486        Error::new(Kind::Tls, Some(e))
487    }
488
489    pub(crate) fn io(e: io::Error) -> Error {
490        Error::new(Kind::Io, Some(Box::new(e)))
491    }
492
493    pub(crate) fn authentication(e: Box<dyn error::Error + Sync + Send>) -> Error {
494        Error::new(Kind::Authentication, Some(e))
495    }
496
497    pub(crate) fn config_parse(e: Box<dyn error::Error + Sync + Send>) -> Error {
498        Error::new(Kind::ConfigParse, Some(e))
499    }
500
501    pub(crate) fn config(e: Box<dyn error::Error + Sync + Send>) -> Error {
502        Error::new(Kind::Config, Some(e))
503    }
504
505    pub(crate) fn row_count() -> Error {
506        Error::new(Kind::RowCount, None)
507    }
508
509    #[cfg(feature = "runtime")]
510    pub(crate) fn connect(e: io::Error) -> Error {
511        Error::new(Kind::Connect, Some(Box::new(e)))
512    }
513
514    #[doc(hidden)]
515    pub fn __private_api_timeout() -> Error {
516        Error::new(Kind::Timeout, None)
517    }
518}