LCOV - code coverage report
Current view: top level - libs/proxy/tokio-postgres2/src/error - mod.rs (source / functions) Coverage Total Hit
Test: 1e20c4f2b28aa592527961bb32170ebbd2c9172f.info Lines: 36.8 % 272 100
Test Date: 2025-07-16 12:29:03 Functions: 18.9 % 53 10

            Line data    Source code
       1              : //! Errors.
       2              : 
       3              : use std::error::{self, Error as _Error};
       4              : use std::{fmt, io};
       5              : 
       6              : use fallible_iterator::FallibleIterator;
       7              : use postgres_protocol2::message::backend::{ErrorFields, ErrorResponseBody};
       8              : 
       9              : pub use self::sqlstate::*;
      10              : 
      11              : #[allow(clippy::unreadable_literal)]
      12              : mod sqlstate;
      13              : 
      14              : /// The severity of a Postgres error or notice.
      15              : #[derive(Debug, Copy, Clone, PartialEq, Eq)]
      16              : pub 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              : 
      35              : impl fmt::Display for Severity {
      36            0 :     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
      37            0 :         let s = match *self {
      38            0 :             Severity::Panic => "PANIC",
      39            0 :             Severity::Fatal => "FATAL",
      40            0 :             Severity::Error => "ERROR",
      41            0 :             Severity::Warning => "WARNING",
      42            0 :             Severity::Notice => "NOTICE",
      43            0 :             Severity::Debug => "DEBUG",
      44            0 :             Severity::Info => "INFO",
      45            0 :             Severity::Log => "LOG",
      46              :         };
      47            0 :         fmt.write_str(s)
      48            0 :     }
      49              : }
      50              : 
      51              : impl Severity {
      52            0 :     fn from_str(s: &str) -> Option<Severity> {
      53            0 :         match s {
      54            0 :             "PANIC" => Some(Severity::Panic),
      55            0 :             "FATAL" => Some(Severity::Fatal),
      56            0 :             "ERROR" => Some(Severity::Error),
      57            0 :             "WARNING" => Some(Severity::Warning),
      58            0 :             "NOTICE" => Some(Severity::Notice),
      59            0 :             "DEBUG" => Some(Severity::Debug),
      60            0 :             "INFO" => Some(Severity::Info),
      61            0 :             "LOG" => Some(Severity::Log),
      62            0 :             _ => None,
      63              :         }
      64            0 :     }
      65              : }
      66              : 
      67              : /// A Postgres error or notice.
      68              : #[derive(Debug, Clone, PartialEq, Eq)]
      69              : pub 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              : 
      88              : impl DbError {
      89           12 :     pub fn new_test_error(code: SqlState, message: String) -> Self {
      90           12 :         DbError {
      91           12 :             severity: "ERROR".to_string(),
      92           12 :             parsed_severity: Some(Severity::Error),
      93           12 :             code,
      94           12 :             message,
      95           12 :             detail: None,
      96           12 :             hint: None,
      97           12 :             position: None,
      98           12 :             where_: None,
      99           12 :             schema: None,
     100           12 :             table: None,
     101           12 :             column: None,
     102           12 :             datatype: None,
     103           12 :             constraint: None,
     104           12 :             file: None,
     105           12 :             line: None,
     106           12 :             routine: None,
     107           12 :         }
     108           12 :     }
     109              : 
     110            1 :     pub(crate) fn parse(fields: &mut ErrorFields<'_>) -> io::Result<DbError> {
     111            1 :         let mut severity = None;
     112            1 :         let mut parsed_severity = None;
     113            1 :         let mut code = None;
     114            1 :         let mut message = None;
     115            1 :         let mut detail = None;
     116            1 :         let mut hint = None;
     117            1 :         let mut normal_position = None;
     118            1 :         let mut internal_position = None;
     119            1 :         let mut internal_query = None;
     120            1 :         let mut where_ = None;
     121            1 :         let mut schema = None;
     122            1 :         let mut table = None;
     123            1 :         let mut column = None;
     124            1 :         let mut datatype = None;
     125            1 :         let mut constraint = None;
     126            1 :         let mut file = None;
     127            1 :         let mut line = None;
     128            1 :         let mut routine = None;
     129              : 
     130            4 :         while let Some(field) = fields.next()? {
     131            3 :             match field.type_() {
     132            1 :                 b'S' => severity = Some(field.value().to_owned()),
     133            1 :                 b'C' => code = Some(SqlState::from_code(field.value())),
     134            1 :                 b'M' => message = Some(field.value().to_owned()),
     135            0 :                 b'D' => detail = Some(field.value().to_owned()),
     136            0 :                 b'H' => hint = Some(field.value().to_owned()),
     137              :                 b'P' => {
     138            0 :                     normal_position = Some(field.value().parse::<u32>().map_err(|_| {
     139            0 :                         io::Error::new(
     140            0 :                             io::ErrorKind::InvalidInput,
     141              :                             "`P` field did not contain an integer",
     142              :                         )
     143            0 :                     })?);
     144              :                 }
     145              :                 b'p' => {
     146            0 :                     internal_position = Some(field.value().parse::<u32>().map_err(|_| {
     147            0 :                         io::Error::new(
     148            0 :                             io::ErrorKind::InvalidInput,
     149              :                             "`p` field did not contain an integer",
     150              :                         )
     151            0 :                     })?);
     152              :                 }
     153            0 :                 b'q' => internal_query = Some(field.value().to_owned()),
     154            0 :                 b'W' => where_ = Some(field.value().to_owned()),
     155            0 :                 b's' => schema = Some(field.value().to_owned()),
     156            0 :                 b't' => table = Some(field.value().to_owned()),
     157            0 :                 b'c' => column = Some(field.value().to_owned()),
     158            0 :                 b'd' => datatype = Some(field.value().to_owned()),
     159            0 :                 b'n' => constraint = Some(field.value().to_owned()),
     160            0 :                 b'F' => file = Some(field.value().to_owned()),
     161              :                 b'L' => {
     162            0 :                     line = Some(field.value().parse::<u32>().map_err(|_| {
     163            0 :                         io::Error::new(
     164            0 :                             io::ErrorKind::InvalidInput,
     165              :                             "`L` field did not contain an integer",
     166              :                         )
     167            0 :                     })?);
     168              :                 }
     169            0 :                 b'R' => routine = Some(field.value().to_owned()),
     170              :                 b'V' => {
     171            0 :                     parsed_severity = Some(Severity::from_str(field.value()).ok_or_else(|| {
     172            0 :                         io::Error::new(
     173            0 :                             io::ErrorKind::InvalidInput,
     174              :                             "`V` field contained an invalid value",
     175              :                         )
     176            0 :                     })?);
     177              :                 }
     178            0 :                 _ => {}
     179              :             }
     180              :         }
     181              : 
     182              :         Ok(DbError {
     183            1 :             severity: severity
     184            1 :                 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`S` field missing"))?,
     185            1 :             parsed_severity,
     186            1 :             code: code
     187            1 :                 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`C` field missing"))?,
     188            1 :             message: message
     189            1 :                 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`M` field missing"))?,
     190            1 :             detail,
     191            1 :             hint,
     192            1 :             position: match normal_position {
     193            0 :                 Some(position) => Some(ErrorPosition::Original(position)),
     194            1 :                 None => match internal_position {
     195            0 :                     Some(position) => Some(ErrorPosition::Internal {
     196            0 :                         position,
     197            0 :                         query: internal_query.ok_or_else(|| {
     198            0 :                             io::Error::new(
     199            0 :                                 io::ErrorKind::InvalidInput,
     200              :                                 "`q` field missing but `p` field present",
     201              :                             )
     202            0 :                         })?,
     203              :                     }),
     204            1 :                     None => None,
     205              :                 },
     206              :             },
     207            1 :             where_,
     208            1 :             schema,
     209            1 :             table,
     210            1 :             column,
     211            1 :             datatype,
     212            1 :             constraint,
     213            1 :             file,
     214            1 :             line,
     215            1 :             routine,
     216              :         })
     217            1 :     }
     218              : 
     219              :     /// The field contents are ERROR, FATAL, or PANIC (in an error message),
     220              :     /// or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message), or a
     221              :     /// localized translation of one of these.
     222            0 :     pub fn severity(&self) -> &str {
     223            0 :         &self.severity
     224            0 :     }
     225              : 
     226              :     /// A parsed, nonlocalized version of `severity`. (PostgreSQL 9.6+)
     227            0 :     pub fn parsed_severity(&self) -> Option<Severity> {
     228            0 :         self.parsed_severity
     229            0 :     }
     230              : 
     231              :     /// The SQLSTATE code for the error.
     232           17 :     pub fn code(&self) -> &SqlState {
     233           17 :         &self.code
     234           17 :     }
     235              : 
     236              :     /// The primary human-readable error message.
     237              :     ///
     238              :     /// This should be accurate but terse (typically one line).
     239            1 :     pub fn message(&self) -> &str {
     240            1 :         &self.message
     241            1 :     }
     242              : 
     243              :     /// An optional secondary error message carrying more detail about the
     244              :     /// problem.
     245              :     ///
     246              :     /// Might run to multiple lines.
     247            0 :     pub fn detail(&self) -> Option<&str> {
     248            0 :         self.detail.as_deref()
     249            0 :     }
     250              : 
     251              :     /// An optional suggestion what to do about the problem.
     252              :     ///
     253              :     /// This is intended to differ from `detail` in that it offers advice
     254              :     /// (potentially inappropriate) rather than hard facts. Might run to
     255              :     /// multiple lines.
     256            0 :     pub fn hint(&self) -> Option<&str> {
     257            0 :         self.hint.as_deref()
     258            0 :     }
     259              : 
     260              :     /// An optional error cursor position into either the original query string
     261              :     /// or an internally generated query.
     262            0 :     pub fn position(&self) -> Option<&ErrorPosition> {
     263            0 :         self.position.as_ref()
     264            0 :     }
     265              : 
     266              :     /// An indication of the context in which the error occurred.
     267              :     ///
     268              :     /// Presently this includes a call stack traceback of active procedural
     269              :     /// language functions and internally-generated queries. The trace is one
     270              :     /// entry per line, most recent first.
     271            0 :     pub fn where_(&self) -> Option<&str> {
     272            0 :         self.where_.as_deref()
     273            0 :     }
     274              : 
     275              :     /// If the error was associated with a specific database object, the name
     276              :     /// of the schema containing that object, if any. (PostgreSQL 9.3+)
     277            0 :     pub fn schema(&self) -> Option<&str> {
     278            0 :         self.schema.as_deref()
     279            0 :     }
     280              : 
     281              :     /// If the error was associated with a specific table, the name of the
     282              :     /// table. (Refer to the schema name field for the name of the table's
     283              :     /// schema.) (PostgreSQL 9.3+)
     284            0 :     pub fn table(&self) -> Option<&str> {
     285            0 :         self.table.as_deref()
     286            0 :     }
     287              : 
     288              :     /// If the error was associated with a specific table column, the name of
     289              :     /// the column.
     290              :     ///
     291              :     /// (Refer to the schema and table name fields to identify the table.)
     292              :     /// (PostgreSQL 9.3+)
     293            0 :     pub fn column(&self) -> Option<&str> {
     294            0 :         self.column.as_deref()
     295            0 :     }
     296              : 
     297              :     /// If the error was associated with a specific data type, the name of the
     298              :     /// data type. (Refer to the schema name field for the name of the data
     299              :     /// type's schema.) (PostgreSQL 9.3+)
     300            0 :     pub fn datatype(&self) -> Option<&str> {
     301            0 :         self.datatype.as_deref()
     302            0 :     }
     303              : 
     304              :     /// If the error was associated with a specific constraint, the name of the
     305              :     /// constraint.
     306              :     ///
     307              :     /// Refer to fields listed above for the associated table or domain.
     308              :     /// (For this purpose, indexes are treated as constraints, even if they
     309              :     /// weren't created with constraint syntax.) (PostgreSQL 9.3+)
     310            0 :     pub fn constraint(&self) -> Option<&str> {
     311            0 :         self.constraint.as_deref()
     312            0 :     }
     313              : 
     314              :     /// The file name of the source-code location where the error was reported.
     315            0 :     pub fn file(&self) -> Option<&str> {
     316            0 :         self.file.as_deref()
     317            0 :     }
     318              : 
     319              :     /// The line number of the source-code location where the error was
     320              :     /// reported.
     321            0 :     pub fn line(&self) -> Option<u32> {
     322            0 :         self.line
     323            0 :     }
     324              : 
     325              :     /// The name of the source-code routine reporting the error.
     326            0 :     pub fn routine(&self) -> Option<&str> {
     327            0 :         self.routine.as_deref()
     328            0 :     }
     329              : }
     330              : 
     331              : impl fmt::Display for DbError {
     332            2 :     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
     333            2 :         write!(fmt, "{}: {}", self.severity, self.message)?;
     334            2 :         if let Some(detail) = &self.detail {
     335            0 :             write!(fmt, "\nDETAIL: {detail}")?;
     336            2 :         }
     337            2 :         if let Some(hint) = &self.hint {
     338            0 :             write!(fmt, "\nHINT: {hint}")?;
     339            2 :         }
     340            2 :         Ok(())
     341            2 :     }
     342              : }
     343              : 
     344              : impl error::Error for DbError {}
     345              : 
     346              : /// Represents the position of an error in a query.
     347              : #[derive(Clone, PartialEq, Eq, Debug)]
     348              : pub enum ErrorPosition {
     349              :     /// A position in the original query.
     350              :     Original(u32),
     351              :     /// A position in an internally generated query.
     352              :     Internal {
     353              :         /// The byte position.
     354              :         position: u32,
     355              :         /// A query generated by the Postgres server.
     356              :         query: String,
     357              :     },
     358              : }
     359              : 
     360              : #[derive(Debug, PartialEq)]
     361              : enum Kind {
     362              :     Io,
     363              :     UnexpectedMessage,
     364              :     Tls,
     365              :     ToSql(usize),
     366              :     FromSql(usize),
     367              :     Column(String),
     368              :     Closed,
     369              :     Db,
     370              :     Parse,
     371              :     Encode,
     372              :     Authentication,
     373              :     Config,
     374              :     Connect,
     375              :     Timeout,
     376              : }
     377              : 
     378              : struct ErrorInner {
     379              :     kind: Kind,
     380              :     cause: Option<Box<dyn error::Error + Sync + Send>>,
     381              : }
     382              : 
     383              : /// An error communicating with the Postgres server.
     384              : pub struct Error(Box<ErrorInner>);
     385              : 
     386              : impl fmt::Debug for Error {
     387            0 :     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
     388            0 :         fmt.debug_struct("Error")
     389            0 :             .field("kind", &self.0.kind)
     390            0 :             .field("cause", &self.0.cause)
     391            0 :             .finish()
     392            0 :     }
     393              : }
     394              : 
     395              : impl fmt::Display for Error {
     396            2 :     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
     397            2 :         match &self.0.kind {
     398            0 :             Kind::Io => fmt.write_str("error communicating with the server")?,
     399            0 :             Kind::UnexpectedMessage => fmt.write_str("unexpected message from server")?,
     400            0 :             Kind::Tls => fmt.write_str("error performing TLS handshake")?,
     401            0 :             Kind::ToSql(idx) => write!(fmt, "error serializing parameter {idx}")?,
     402            0 :             Kind::FromSql(idx) => write!(fmt, "error deserializing column {idx}")?,
     403            0 :             Kind::Column(column) => write!(fmt, "invalid column `{column}`")?,
     404            0 :             Kind::Closed => fmt.write_str("connection closed")?,
     405            2 :             Kind::Db => fmt.write_str("db error")?,
     406            0 :             Kind::Parse => fmt.write_str("error parsing response from server")?,
     407            0 :             Kind::Encode => fmt.write_str("error encoding message to server")?,
     408            0 :             Kind::Authentication => fmt.write_str("authentication error")?,
     409            0 :             Kind::Config => fmt.write_str("invalid configuration")?,
     410            0 :             Kind::Connect => fmt.write_str("error connecting to server")?,
     411            0 :             Kind::Timeout => fmt.write_str("timeout waiting for server")?,
     412              :         };
     413            2 :         if let Some(ref cause) = self.0.cause {
     414            2 :             write!(fmt, ": {cause}")?;
     415            0 :         }
     416            2 :         Ok(())
     417            2 :     }
     418              : }
     419              : 
     420              : impl error::Error for Error {
     421            0 :     fn source(&self) -> Option<&(dyn error::Error + 'static)> {
     422            0 :         self.0.cause.as_ref().map(|e| &**e as _)
     423            0 :     }
     424              : }
     425              : 
     426              : impl Error {
     427              :     /// Consumes the error, returning its cause.
     428            0 :     pub fn into_source(self) -> Option<Box<dyn error::Error + Sync + Send>> {
     429            0 :         self.0.cause
     430            0 :     }
     431              : 
     432              :     /// Returns the source of this error if it was a `DbError`.
     433              :     ///
     434              :     /// This is a simple convenience method.
     435            0 :     pub fn as_db_error(&self) -> Option<&DbError> {
     436            0 :         self.source().and_then(|e| e.downcast_ref::<DbError>())
     437            0 :     }
     438              : 
     439              :     /// Determines if the error was associated with closed connection.
     440            0 :     pub fn is_closed(&self) -> bool {
     441            0 :         self.0.kind == Kind::Closed
     442            0 :     }
     443              : 
     444              :     /// Returns the SQLSTATE error code associated with the error.
     445              :     ///
     446              :     /// This is a convenience method that downcasts the cause to a `DbError` and returns its code.
     447            0 :     pub fn code(&self) -> Option<&SqlState> {
     448            0 :         self.as_db_error().map(DbError::code)
     449            0 :     }
     450              : 
     451            8 :     fn new(kind: Kind, cause: Option<Box<dyn error::Error + Sync + Send>>) -> Error {
     452            8 :         Error(Box::new(ErrorInner { kind, cause }))
     453            8 :     }
     454              : 
     455            0 :     pub(crate) fn closed() -> Error {
     456            0 :         Error::new(Kind::Closed, None)
     457            0 :     }
     458              : 
     459            0 :     pub(crate) fn unexpected_message() -> Error {
     460            0 :         Error::new(Kind::UnexpectedMessage, None)
     461            0 :     }
     462              : 
     463              :     #[allow(clippy::needless_pass_by_value)]
     464            1 :     pub(crate) fn db(error: ErrorResponseBody) -> Error {
     465            1 :         match DbError::parse(&mut error.fields()) {
     466            1 :             Ok(e) => Error::new(Kind::Db, Some(Box::new(e))),
     467            0 :             Err(e) => Error::new(Kind::Parse, Some(Box::new(e))),
     468              :         }
     469            1 :     }
     470              : 
     471            0 :     pub(crate) fn parse(e: io::Error) -> Error {
     472            0 :         Error::new(Kind::Parse, Some(Box::new(e)))
     473            0 :     }
     474              : 
     475            0 :     pub(crate) fn encode(e: io::Error) -> Error {
     476            0 :         Error::new(Kind::Encode, Some(Box::new(e)))
     477            0 :     }
     478              : 
     479              :     #[allow(clippy::wrong_self_convention)]
     480            0 :     pub(crate) fn to_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
     481            0 :         Error::new(Kind::ToSql(idx), Some(e))
     482            0 :     }
     483              : 
     484            0 :     pub(crate) fn from_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
     485            0 :         Error::new(Kind::FromSql(idx), Some(e))
     486            0 :     }
     487              : 
     488            0 :     pub(crate) fn column(column: String) -> Error {
     489            0 :         Error::new(Kind::Column(column), None)
     490            0 :     }
     491              : 
     492            0 :     pub(crate) fn tls(e: Box<dyn error::Error + Sync + Send>) -> Error {
     493            0 :         Error::new(Kind::Tls, Some(e))
     494            0 :     }
     495              : 
     496            6 :     pub(crate) fn io(e: io::Error) -> Error {
     497            6 :         Error::new(Kind::Io, Some(Box::new(e)))
     498            6 :     }
     499              : 
     500            1 :     pub(crate) fn authentication(e: Box<dyn error::Error + Sync + Send>) -> Error {
     501            1 :         Error::new(Kind::Authentication, Some(e))
     502            1 :     }
     503              : 
     504            0 :     pub(crate) fn config(e: Box<dyn error::Error + Sync + Send>) -> Error {
     505            0 :         Error::new(Kind::Config, Some(e))
     506            0 :     }
     507              : 
     508            0 :     pub(crate) fn connect(e: io::Error) -> Error {
     509            0 :         Error::new(Kind::Connect, Some(Box::new(e)))
     510            0 :     }
     511              : 
     512              :     #[doc(hidden)]
     513            0 :     pub fn __private_api_timeout() -> Error {
     514            0 :         Error::new(Kind::Timeout, None)
     515            0 :     }
     516              : }
        

Generated by: LCOV version 2.1-beta