Skip to main content

xitca_postgres/
error.rs

1//! library error types with re-export error from `rust-postgres`
2//!
3//! this crate only exposes a single [Error] type from API where type erase is used to hide complexity
4
5mod sql_state;
6
7pub use postgres_types::{WasNull, WrongType};
8
9use core::{
10    convert::Infallible,
11    fmt,
12    ops::{Deref, DerefMut},
13};
14
15use std::{backtrace::Backtrace, error, io};
16
17use fallible_iterator::FallibleIterator;
18
19use super::{from_sql::FromSqlError, protocol::message::backend::ErrorFields};
20
21pub use self::sql_state::SqlState;
22
23/// public facing error type. providing basic format and display based error handling.
24///
25/// for typed based error handling runtime type cast is needed with the help of other
26/// public error types offered by this module.
27///
28/// # Example
29/// ```rust
30/// use xitca_postgres::error::{DriverDown, Error};
31///
32/// fn is_driver_down(e: Error) -> bool {
33///     // downcast error to DriverDown error type to check if client driver is gone.
34///     e.downcast_ref::<DriverDown>().is_some()
35/// }
36/// ```
37pub struct Error(Box<dyn error::Error + Send + Sync>);
38
39impl Error {
40    pub fn is_driver_down(&self) -> bool {
41        self.0.is::<DriverDown>() || self.0.is::<ClosedByDriver>()
42    }
43
44    pub(crate) fn todo() -> Self {
45        Self(Box::new(ToDo {
46            back_trace: Backtrace::capture(),
47        }))
48    }
49
50    pub(crate) fn driver_io(read: Option<io::Error>, write: Option<io::Error>) -> Self {
51        match (read, write) {
52            (Some(read), Some(write)) => Self::from(DriverIoErrorMulti { read, write }),
53            (Some(read), None) => Self::from(read),
54            (None, Some(write)) => Self::from(write),
55            _ => unreachable!("Driver must not report error when it doesn't produce any"),
56        }
57    }
58
59    #[cold]
60    #[inline(never)]
61    pub(crate) fn db(mut fields: ErrorFields<'_>) -> Error {
62        match DbError::parse(&mut fields) {
63            Ok(e) => Error::from(e),
64            Err(e) => Error::from(e),
65        }
66    }
67
68    pub(crate) fn unexpected() -> Self {
69        Self(Box::new(UnexpectedMessage {
70            back_trace: Backtrace::capture(),
71        }))
72    }
73}
74
75impl Deref for Error {
76    type Target = dyn error::Error + Send + Sync;
77
78    fn deref(&self) -> &Self::Target {
79        &*self.0
80    }
81}
82
83impl DerefMut for Error {
84    fn deref_mut(&mut self) -> &mut Self::Target {
85        &mut *self.0
86    }
87}
88
89impl fmt::Debug for Error {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        fmt::Debug::fmt(&self.0, f)
92    }
93}
94
95impl fmt::Display for Error {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        fmt::Display::fmt(&self.0, f)
98    }
99}
100
101impl error::Error for Error {
102    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
103        self.0.source()
104    }
105}
106
107macro_rules! from_impl {
108    ($i: ty) => {
109        impl From<$i> for Error {
110            fn from(e: $i) -> Self {
111                Self(Box::new(e))
112            }
113        }
114    };
115}
116
117/// work in progress error type with thread backtrace.
118/// use `RUST_BACKTRACE=1` env when starting your program to enable capture and format
119#[derive(Debug)]
120pub struct ToDo {
121    back_trace: Backtrace,
122}
123
124impl fmt::Display for ToDo {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        write!(f, "WIP error type with thread backtrace: {}", self.back_trace)
127    }
128}
129
130impl error::Error for ToDo {}
131
132/// [`Response`] has already finished. Polling it afterwards will cause this error.
133///
134/// [`Response`]: crate::driver::codec::Response
135#[derive(Debug)]
136pub struct Completed;
137
138impl fmt::Display for Completed {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        f.write_str("Response has already finished. No more database response available")
141    }
142}
143
144impl error::Error for Completed {}
145
146from_impl!(Completed);
147
148/// error indicate [`Client`]'s [`Driver`] is dropped and can't be accessed anymore when sending request to driver.
149///
150/// database query related to this error has not been sent to database and it's safe to retry operation if
151/// desired.
152///
153/// # Error source
154/// detailed reason of driver shutdown can be obtained from output of [`Driver`]'s [`AsyncLendingIterator`] or
155/// [`IntoFuture`] trait impl method
156/// ## Examples
157/// ```
158/// # use std::future::IntoFuture;
159/// # use xitca_postgres::{Error, Execute, Postgres};
160/// # async fn driver_error() -> Result<(), Error> {
161/// // start a connection and spawn driver task
162/// let (cli, drv) = Postgres::new("<db_confg>").connect().await?;
163/// // keep the driver task's join handle for later use
164/// let handle = tokio::spawn(drv.into_future());
165///
166/// // when query returns error immediately we check if the driver is gone.
167/// if let Err(e) = "".query(&cli).await {
168///     if e.is_driver_down() {
169///         // driver is gone and we want to know detail reason in this case.
170///         // await on the join handle will return the output of Driver task.
171///         let opt = handle.await.unwrap();
172///         println!("{opt:?}");
173///     }
174/// }
175/// # Ok(())
176/// # }
177/// ```
178///
179/// [`Client`]: crate::client::Client
180/// [`Driver`]: crate::driver::Driver
181/// [`AsyncLendingIterator`]: crate::iter::AsyncLendingIterator
182/// [`IntoFuture`]: core::future::IntoFuture
183#[derive(Default)]
184pub struct DriverDown;
185
186impl fmt::Debug for DriverDown {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        f.debug_struct("DriverDown").finish()
189    }
190}
191
192impl fmt::Display for DriverDown {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        f.write_str("Client's Driver is dropped and unaccessible. Associated query has not been sent to database.")
195    }
196}
197
198impl error::Error for DriverDown {}
199
200from_impl!(DriverDown);
201
202/// error indicate [`Client`]'s [`Driver`] has dropped the response. There are two cases this error can happen:
203///
204/// - database response has finished and further polling it would result in error
205/// - [`Driver`] went into shutdown and all in-flight response are closed with it
206///
207/// [`Client`]: crate::client::Client
208/// [`Driver`]: crate::driver::Driver
209#[derive(Debug)]
210pub struct ClosedByDriver;
211
212impl fmt::Display for ClosedByDriver {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        f.write_str("Reponse closed by Driver. No more data can be collected")
215    }
216}
217
218impl error::Error for ClosedByDriver {}
219
220from_impl!(ClosedByDriver);
221
222/// driver shutdown outcome can contain multiple io error for detailed read/write errors.
223#[derive(Debug)]
224pub struct DriverIoErrorMulti {
225    read: io::Error,
226    write: io::Error,
227}
228
229impl fmt::Display for DriverIoErrorMulti {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        write!(
232            f,
233            "Multiple IO error from driver {{ read error: {}, write error: {} }}",
234            self.read, self.write
235        )
236    }
237}
238
239impl error::Error for DriverIoErrorMulti {}
240
241from_impl!(DriverIoErrorMulti);
242
243pub struct InvalidColumnIndex(pub String);
244
245impl fmt::Debug for InvalidColumnIndex {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        f.debug_struct("InvalidColumnIndex").finish()
248    }
249}
250
251impl fmt::Display for InvalidColumnIndex {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        write!(f, "invalid column index: {}", self.0)
254    }
255}
256
257impl error::Error for InvalidColumnIndex {}
258
259from_impl!(InvalidColumnIndex);
260
261#[derive(Debug)]
262pub struct InvalidParamCount {
263    pub expected: usize,
264    pub params: usize,
265}
266
267impl fmt::Display for InvalidParamCount {
268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269        write!(
270            f,
271            "expected Statement bind to {} parameters but got {}.\r\n",
272            self.expected, self.params
273        )?;
274        f.write_str("note: consider use `Statement::bind` or check the parameter values count if already used")
275    }
276}
277
278impl error::Error for InvalidParamCount {}
279
280from_impl!(InvalidParamCount);
281
282impl From<Infallible> for Error {
283    fn from(e: Infallible) -> Self {
284        match e {}
285    }
286}
287
288from_impl!(io::Error);
289
290impl From<FromSqlError> for Error {
291    fn from(e: FromSqlError) -> Self {
292        Self(e)
293    }
294}
295
296/// error happens when [`Config`] fail to provide necessary information.
297///
298/// [`Config`]: crate::config::Config
299#[derive(Debug)]
300pub enum ConfigError {
301    EmptyHost,
302    EmptyPort,
303    MissingUserName,
304    MissingPassWord,
305    WrongPassWord,
306}
307
308impl fmt::Display for ConfigError {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        f.write_str("Config error: ")?;
311        match self {
312            Self::EmptyHost => f.write_str("no available host name found"),
313            Self::EmptyPort => f.write_str("no available host port found"),
314            Self::MissingUserName => f.write_str("username is missing"),
315            Self::MissingPassWord => f.write_str("password is missing"),
316            Self::WrongPassWord => f.write_str("password is wrong"),
317        }
318    }
319}
320
321impl error::Error for ConfigError {}
322
323from_impl!(ConfigError);
324
325#[non_exhaustive]
326#[derive(Debug)]
327pub enum SystemError {
328    Unix,
329}
330
331impl fmt::Display for SystemError {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        match *self {
334            Self::Unix => f.write_str("unix")?,
335        }
336        f.write_str(" system is not available")
337    }
338}
339
340impl error::Error for SystemError {}
341
342from_impl!(SystemError);
343
344#[non_exhaustive]
345#[derive(Debug)]
346pub enum FeatureError {
347    Tls,
348    Quic,
349}
350
351impl fmt::Display for FeatureError {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        match *self {
354            Self::Tls => f.write_str("tls")?,
355            Self::Quic => f.write_str("quic")?,
356        }
357        f.write_str(" feature is not enabled")
358    }
359}
360
361impl error::Error for FeatureError {}
362
363from_impl!(FeatureError);
364
365#[derive(Debug, PartialEq, Eq)]
366pub enum RuntimeError {
367    RequireNoTokio,
368}
369
370impl fmt::Display for RuntimeError {
371    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372        match *self {
373            Self::RequireNoTokio => f.write_str("Tokio runtime detected. Must be called from outside of tokio"),
374        }
375    }
376}
377
378impl error::Error for RuntimeError {}
379
380from_impl!(RuntimeError);
381
382/// error for database returning backend message type that is not expected.
383/// it indicates there might be protocol error on either side of the connection.
384#[derive(Debug)]
385pub struct UnexpectedMessage {
386    back_trace: Backtrace,
387}
388
389impl fmt::Display for UnexpectedMessage {
390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391        f.write_str("Unexpected message from database with stack trace:\r\n")?;
392        write!(f, "{}", self.back_trace)
393    }
394}
395
396impl error::Error for UnexpectedMessage {}
397
398#[cold]
399#[inline(never)]
400pub(crate) fn unexpected_eof_err() -> io::Error {
401    io::Error::new(
402        io::ErrorKind::UnexpectedEof,
403        "zero byte read. remote close connection unexpectedly",
404    )
405}
406
407from_impl!(WrongType);
408
409/// A Postgres error or notice.
410#[derive(Debug, Clone, PartialEq, Eq)]
411pub struct DbError {
412    severity: String,
413    parsed_severity: Option<Severity>,
414    code: SqlState,
415    message: String,
416    detail: Option<String>,
417    hint: Option<String>,
418    position: Option<ErrorPosition>,
419    where_: Option<String>,
420    schema: Option<String>,
421    table: Option<String>,
422    column: Option<String>,
423    datatype: Option<String>,
424    constraint: Option<String>,
425    file: Option<String>,
426    line: Option<u32>,
427    routine: Option<String>,
428}
429
430impl DbError {
431    fn parse(fields: &mut ErrorFields<'_>) -> io::Result<DbError> {
432        let mut severity = None;
433        let mut parsed_severity = None;
434        let mut code = None;
435        let mut message = None;
436        let mut detail = None;
437        let mut hint = None;
438        let mut normal_position = None;
439        let mut internal_position = None;
440        let mut internal_query = None;
441        let mut where_ = None;
442        let mut schema = None;
443        let mut table = None;
444        let mut column = None;
445        let mut datatype = None;
446        let mut constraint = None;
447        let mut file = None;
448        let mut line = None;
449        let mut routine = None;
450
451        while let Some(field) = fields.next()? {
452            let value = String::from_utf8_lossy(field.value_bytes());
453            match field.type_() {
454                b'S' => severity = Some(value.into_owned()),
455                b'C' => code = Some(SqlState::from_code(&value)),
456                b'M' => message = Some(value.into_owned()),
457                b'D' => detail = Some(value.into_owned()),
458                b'H' => hint = Some(value.into_owned()),
459                b'P' => {
460                    normal_position = Some(value.parse::<u32>().map_err(|_| {
461                        io::Error::new(io::ErrorKind::InvalidInput, "`P` field did not contain an integer")
462                    })?);
463                }
464                b'p' => {
465                    internal_position = Some(value.parse::<u32>().map_err(|_| {
466                        io::Error::new(io::ErrorKind::InvalidInput, "`p` field did not contain an integer")
467                    })?);
468                }
469                b'q' => internal_query = Some(value.into_owned()),
470                b'W' => where_ = Some(value.into_owned()),
471                b's' => schema = Some(value.into_owned()),
472                b't' => table = Some(value.into_owned()),
473                b'c' => column = Some(value.into_owned()),
474                b'd' => datatype = Some(value.into_owned()),
475                b'n' => constraint = Some(value.into_owned()),
476                b'F' => file = Some(value.into_owned()),
477                b'L' => {
478                    line = Some(value.parse::<u32>().map_err(|_| {
479                        io::Error::new(io::ErrorKind::InvalidInput, "`L` field did not contain an integer")
480                    })?);
481                }
482                b'R' => routine = Some(value.into_owned()),
483                b'V' => {
484                    parsed_severity = Some(Severity::from_str(&value).ok_or_else(|| {
485                        io::Error::new(io::ErrorKind::InvalidInput, "`V` field contained an invalid value")
486                    })?);
487                }
488                _ => {}
489            }
490        }
491
492        Ok(DbError {
493            severity: severity.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`S` field missing"))?,
494            parsed_severity,
495            code: code.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`C` field missing"))?,
496            message: message.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`M` field missing"))?,
497            detail,
498            hint,
499            position: match normal_position {
500                Some(position) => Some(ErrorPosition::Original(position)),
501                None => match internal_position {
502                    Some(position) => Some(ErrorPosition::Internal {
503                        position,
504                        query: internal_query.ok_or_else(|| {
505                            io::Error::new(io::ErrorKind::InvalidInput, "`q` field missing but `p` field present")
506                        })?,
507                    }),
508                    None => None,
509                },
510            },
511            where_,
512            schema,
513            table,
514            column,
515            datatype,
516            constraint,
517            file,
518            line,
519            routine,
520        })
521    }
522
523    /// The field contents are ERROR, FATAL, or PANIC (in an error message),
524    /// or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message), or a
525    /// localized translation of one of these.
526    pub fn severity(&self) -> &str {
527        &self.severity
528    }
529
530    /// A parsed, nonlocalized version of `severity`. (PostgreSQL 9.6+)
531    pub fn parsed_severity(&self) -> Option<Severity> {
532        self.parsed_severity
533    }
534
535    /// The SQLSTATE code for the error.
536    pub fn code(&self) -> &SqlState {
537        &self.code
538    }
539
540    /// The primary human-readable error message.
541    ///
542    /// This should be accurate but terse (typically one line).
543    pub fn message(&self) -> &str {
544        &self.message
545    }
546
547    /// An optional secondary error message carrying more detail about the
548    /// problem.
549    ///
550    /// Might run to multiple lines.
551    pub fn detail(&self) -> Option<&str> {
552        self.detail.as_deref()
553    }
554
555    /// An optional suggestion what to do about the problem.
556    ///
557    /// This is intended to differ from `detail` in that it offers advice
558    /// (potentially inappropriate) rather than hard facts. Might run to
559    /// multiple lines.
560    pub fn hint(&self) -> Option<&str> {
561        self.hint.as_deref()
562    }
563
564    /// An optional error cursor position into either the original query string
565    /// or an internally generated query.
566    pub fn position(&self) -> Option<&ErrorPosition> {
567        self.position.as_ref()
568    }
569
570    /// An indication of the context in which the error occurred.
571    ///
572    /// Presently this includes a call stack traceback of active procedural
573    /// language functions and internally-generated queries. The trace is one
574    /// entry per line, most recent first.
575    pub fn where_(&self) -> Option<&str> {
576        self.where_.as_deref()
577    }
578
579    /// If the error was associated with a specific database object, the name
580    /// of the schema containing that object, if any. (PostgreSQL 9.3+)
581    pub fn schema(&self) -> Option<&str> {
582        self.schema.as_deref()
583    }
584
585    /// If the error was associated with a specific table, the name of the
586    /// table. (Refer to the schema name field for the name of the table's
587    /// schema.) (PostgreSQL 9.3+)
588    pub fn table(&self) -> Option<&str> {
589        self.table.as_deref()
590    }
591
592    /// If the error was associated with a specific table column, the name of
593    /// the column.
594    ///
595    /// (Refer to the schema and table name fields to identify the table.)
596    /// (PostgreSQL 9.3+)
597    pub fn column(&self) -> Option<&str> {
598        self.column.as_deref()
599    }
600
601    /// If the error was associated with a specific data type, the name of the
602    /// data type. (Refer to the schema name field for the name of the data
603    /// type's schema.) (PostgreSQL 9.3+)
604    pub fn datatype(&self) -> Option<&str> {
605        self.datatype.as_deref()
606    }
607
608    /// If the error was associated with a specific constraint, the name of the
609    /// constraint.
610    ///
611    /// Refer to fields listed above for the associated table or domain.
612    /// (For this purpose, indexes are treated as constraints, even if they
613    /// weren't created with constraint syntax.) (PostgreSQL 9.3+)
614    pub fn constraint(&self) -> Option<&str> {
615        self.constraint.as_deref()
616    }
617
618    /// The file name of the source-code location where the error was reported.
619    pub fn file(&self) -> Option<&str> {
620        self.file.as_deref()
621    }
622
623    /// The line number of the source-code location where the error was
624    /// reported.
625    pub fn line(&self) -> Option<u32> {
626        self.line
627    }
628
629    /// The name of the source-code routine reporting the error.
630    pub fn routine(&self) -> Option<&str> {
631        self.routine.as_deref()
632    }
633}
634
635impl fmt::Display for DbError {
636    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
637        write!(fmt, "{}: {}", self.severity, self.message)?;
638        if let Some(detail) = &self.detail {
639            write!(fmt, "\nDETAIL: {detail}")?;
640        }
641        if let Some(hint) = &self.hint {
642            write!(fmt, "\nHINT: {hint}")?;
643        }
644        Ok(())
645    }
646}
647
648impl error::Error for DbError {}
649
650from_impl!(DbError);
651
652/// The severity of a Postgres error or notice.
653#[derive(Debug, Copy, Clone, PartialEq, Eq)]
654pub enum Severity {
655    /// PANIC
656    Panic,
657    /// FATAL
658    Fatal,
659    /// ERROR
660    Error,
661    /// WARNING
662    Warning,
663    /// NOTICE
664    Notice,
665    /// DEBUG
666    Debug,
667    /// INFO
668    Info,
669    /// LOG
670    Log,
671}
672
673impl fmt::Display for Severity {
674    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
675        let s = match *self {
676            Severity::Panic => "PANIC",
677            Severity::Fatal => "FATAL",
678            Severity::Error => "ERROR",
679            Severity::Warning => "WARNING",
680            Severity::Notice => "NOTICE",
681            Severity::Debug => "DEBUG",
682            Severity::Info => "INFO",
683            Severity::Log => "LOG",
684        };
685        fmt.write_str(s)
686    }
687}
688
689impl Severity {
690    fn from_str(s: &str) -> Option<Severity> {
691        match s {
692            "PANIC" => Some(Severity::Panic),
693            "FATAL" => Some(Severity::Fatal),
694            "ERROR" => Some(Severity::Error),
695            "WARNING" => Some(Severity::Warning),
696            "NOTICE" => Some(Severity::Notice),
697            "DEBUG" => Some(Severity::Debug),
698            "INFO" => Some(Severity::Info),
699            "LOG" => Some(Severity::Log),
700            _ => None,
701        }
702    }
703}
704
705/// Represents the position of an error in a query.
706#[derive(Clone, PartialEq, Eq, Debug)]
707pub enum ErrorPosition {
708    /// A position in the original query.
709    Original(u32),
710    /// A position in an internally generated query.
711    Internal {
712        /// The byte position.
713        position: u32,
714        /// A query generated by the Postgres server.
715        query: String,
716    },
717}