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