//! Errorand Result types. use crate::database::Database; use crate::types::Type; use std::any::type_name; use std::error::Error as StdError; use std::fmt::{self, Debug, Display}; use std::io; #[allow(unused_macros)] macro_rules! decode_err { ($s:literal, $($args:tt)*) => { crate::Error::Decode(format!($s, $($args)*).into()) }; ($expr:expr) => { crate::Error::decode($expr) }; } /// A specialized `Result` type for SQLx. pub type Result = std::result::Result; /// A generic error that represents all the ways a method can fail inside of SQLx. #[derive(Debug)] #[non_exhaustive] pub enum Error { /// Error communicating with the database. Io(io::Error), /// Connection URL was malformed. UrlParse(url::ParseError), /// An error was returned by the database. Database(Box), /// No row was returned during [`query::Map::fetch_one`] or `QueryAs::fetch_one`. /// /// [`query::Map::fetch_one`]: crate::query::Map::fetch_one RowNotFound, /// Column was not found by name in a Row (during [`Row::get`]). /// /// [`Row::get`]: crate::row::Row::get ColumnNotFound(Box), /// Column index was out of bounds (e.g., asking for column 4 in a 2-column row). ColumnIndexOutOfBounds { index: usize, len: usize }, /// Unexpected or invalid data was encountered. This would indicate that we received /// data that we were not expecting or it was in a format we did not understand. This /// generally means either there is a programming error in a SQLx driver or /// something with the connection or the database database itself is corrupted. /// /// Context is provided by the included error message. Protocol(Box), /// A [`Pool::acquire`] timed out due to connections not becoming available or /// because another task encountered too many errors while trying to open a new connection. /// /// [`Pool::acquire`]: crate::pool::Pool::acquire PoolTimedOut(Option>), /// [`Pool::close`] was called while we were waiting in [`Pool::acquire`]. /// /// [`Pool::acquire`]: crate::pool::Pool::acquire /// [`Pool::close`]: crate::pool::Pool::close PoolClosed, /// An error occurred while attempting to setup TLS. /// This should only be returned from an explicit ask for TLS. Tls(Box), /// An error occurred decoding data received from the database. Decode(Box), } impl Error { #[allow(dead_code)] pub(crate) fn decode(err: E) -> Self where E: StdError + Send + Sync + 'static, { Error::Decode(err.into()) } #[allow(dead_code)] pub(crate) fn mismatched_types(expected: DB::TypeInfo) -> Self where T: Type, { let ty_name = type_name::(); return decode_err!( "mismatched types; Rust type `{}` (as SQL type {}) is not compatible with SQL type {}", ty_name, T::type_info(), expected ); } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { Error::Io(error) => Some(error), Error::UrlParse(error) => Some(error), Error::PoolTimedOut(Some(error)) => Some(&**error), Error::Decode(error) => Some(&**error), Error::Tls(error) => Some(&**error), Error::Database(error) => Some(error.as_ref_err()), _ => None, } } } impl Display for Error { // IntellijRust does not understand that [non_exhaustive] applies only for downstream crates // noinspection RsMatchCheck fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Io(error) => write!(f, "{}", error), Error::UrlParse(error) => write!(f, "{}", error), Error::Decode(error) => write!(f, "{}", error), Error::Database(error) => Display::fmt(error, f), Error::RowNotFound => f.write_str("found no row when we expected at least one"), Error::ColumnNotFound(ref name) => { write!(f, "no column found with the name {:?}", name) } Error::ColumnIndexOutOfBounds { index, len } => write!( f, "column index out of bounds: there are {} columns but the index is {}", len, index ), Error::Protocol(ref err) => f.write_str(err), Error::PoolTimedOut(Some(ref err)) => { write!(f, "timed out while waiting for an open connection: {}", err) } Error::PoolTimedOut(None) => { write!(f, "timed out while waiting for an open connection") } Error::PoolClosed => f.write_str("attempted to acquire a connection on a closed pool"), Error::Tls(ref err) => write!(f, "error during TLS upgrade: {}", err), } } } impl From for Error { #[inline] fn from(err: io::Error) -> Self { Error::Io(err) } } impl From for Error { #[inline] fn from(err: io::ErrorKind) -> Self { Error::Io(err.into()) } } impl From for Error { #[inline] fn from(err: url::ParseError) -> Self { Error::UrlParse(err) } } impl From> for Error { #[inline] fn from(err: ProtocolError) -> Self { Error::Protocol(err.args.to_string().into_boxed_str()) } } impl From for Error { #[inline] fn from(err: UnexpectedNullError) -> Self { Error::Decode(err.into()) } } #[cfg(feature = "tls")] #[cfg_attr(docsrs, doc(cfg(feature = "tls")))] impl From for Error { #[inline] fn from(err: async_native_tls::Error) -> Self { Error::Tls(err.into()) } } impl From> for Error { #[inline] fn from(err: TlsError<'_>) -> Self { Error::Tls(err.args.to_string().into()) } } /// An error that was returned by the database. pub trait DatabaseError: StdError + Send + Sync + 'static { /// The primary, human-readable error message. fn message(&self) -> &str; /// The (SQLSTATE) code for the error. fn code(&self) -> Option<&str> { None } fn details(&self) -> Option<&str> { None } fn hint(&self) -> Option<&str> { None } fn table_name(&self) -> Option<&str> { None } fn column_name(&self) -> Option<&str> { None } fn constraint_name(&self) -> Option<&str> { None } #[doc(hidden)] fn as_ref_err(&self) -> &(dyn StdError + Send + Sync + 'static); #[doc(hidden)] fn as_mut_err(&mut self) -> &mut (dyn StdError + Send + Sync + 'static); #[doc(hidden)] fn into_box_err(self: Box) -> Box; } impl dyn DatabaseError { /// Downcast this `&dyn DatabaseError` to a specific database error type: /// /// * [PgError][crate::postgres::PgError] (if the `postgres` feature is active) /// * [MySqlError][crate::mysql::MySqlError] (if the `mysql` feature is active) /// * [SqliteError][crate::sqlite::SqliteError] (if the `sqlite` feature is active) /// /// In a generic context you can use the [crate::database::Database::Error] associated type. /// /// ### Panics /// If the type does not match; this is in contrast with [StdError::downcast_ref] /// which returns `Option`. This was a deliberate design decision in favor of brevity as in /// almost all cases you should know which database error type you're expecting. /// /// In any other cases, use [Self::try_downcast_ref] instead. pub fn downcast_ref(&self) -> &T { self.try_downcast_ref::().unwrap_or_else(|| { panic!( "downcasting to wrong DatabaseError type; original error: {:?}", self ) }) } /// Downcast this `&dyn DatabaseError` to a specific database error type: /// /// * [PgError][crate::postgres::PgError] (if the `postgres` feature is active) /// * [MySqlError][crate::mysql::MySqlError] (if the `mysql` feature is active) /// * [SqliteError][crate::sqlite::SqliteError] (if the `sqlite` feature is active) /// /// In a generic context you can use the [crate::database::Database::Error] associated type. /// /// Returns `None` if the downcast fails (the types do not match) pub fn try_downcast_ref(&self) -> Option<&T> { self.as_ref_err().downcast_ref() } /// Only meant for internal use so no `try_` variant is currently provided #[allow(dead_code)] pub(crate) fn downcast_mut(&mut self) -> &mut T { // tried to express this as the following: // // if let Some(e) = self.as_mut_err().downcast_mut() { return e; } // // however it didn't like using `self` again in the panic format if self.as_ref_err().is::() { return self.as_mut_err().downcast_mut().unwrap(); } panic!( "downcasting to wrong DatabaseError type; original error: {:?}", self ) } /// Downcast this `Box` to a specific database error type: /// /// * [PgError][crate::postgres::PgError] (if the `postgres` feature is active) /// * [MySqlError][crate::mysql::MySqlError] (if the `mysql` feature is active) /// * [SqliteError][crate::sqlite::SqliteError] (if the `sqlite` feature is active) /// /// In a generic context you can use the [crate::database::Database::Error] associated type. /// /// ### Panics /// If the type does not match; this is in contrast with [std::error::Error::downcast] /// which returns `Result`. This was a deliberate design decision in favor of /// brevity as in almost all cases you should know which database error type you're expecting. /// /// In any other cases, use [Self::try_downcast] instead. pub fn downcast(self: Box) -> Box { self.try_downcast().unwrap_or_else(|e| { panic!( "downcasting to wrong DatabaseError type; original error: {:?}", e ) }) } /// Downcast this `Box` to a specific database error type: /// /// * [PgError][crate::postgres::PgError] (if the `postgres` feature is active) /// * [MySqlError][crate::mysql::MySqlError] (if the `mysql` feature is active) /// * [SqliteError][crate::sqlite::SqliteError] (if the `sqlite` feature is active) /// /// In a generic context you can use the [crate::database::Database::Error] associated type. /// /// Returns `Err(self)` if the downcast fails (the types do not match). pub fn try_downcast( self: Box, ) -> std::result::Result, Box> { if self.as_ref_err().is::() { Ok(self .into_box_err() .downcast() .expect("type mismatch between DatabaseError::as_ref_err() and into_box_err()")) } else { Err(self) } } } /// Used by the `protocol_error!()` macro for a lazily evaluated conversion to /// `crate::Error::Protocol` so we can use the macro with `.ok_or()` without Clippy complaining. pub(crate) struct ProtocolError<'a> { pub args: fmt::Arguments<'a>, } #[allow(unused_macros)] macro_rules! protocol_err ( ($($args:tt)*) => { $crate::error::ProtocolError { args: format_args!($($args)*) } } ); pub(crate) struct TlsError<'a> { pub args: fmt::Arguments<'a>, } #[allow(unused_macros)] macro_rules! tls_err { ($($args:tt)*) => { crate::error::TlsError { args: format_args!($($args)*)} }; } /// An unexpected `NULL` was encountered during decoding. /// /// Returned from `Row::get` if the value from the database is `NULL` /// and you are not decoding into an `Option`. #[derive(Debug, Clone, Copy)] pub struct UnexpectedNullError; impl Display for UnexpectedNullError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("unexpected null; try decoding as an `Option`") } } impl StdError for UnexpectedNullError {}