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