use std::any::type_name; use std::borrow::Cow; use std::error::Error as StdError; use std::fmt::Display; use std::io; use std::result::Result as StdResult; use crate::database::Database; /// A specialized `Result` type for SQLx. pub type Result = StdResult; // Convenience type alias for usage within SQLx. pub(crate) type BoxDynError = Box; /// 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(thiserror::Error, Debug)] #[error("unexpected null; try decoding as an `Option`")] pub struct UnexpectedNullError; /// Represents all the ways a method can fail within SQLx. #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum Error { /// Error occurred while parsing a connection string. #[error("error occurred while parsing a connection string: {0}")] ParseConnectOptions(#[source] BoxDynError), /// Error returned from the database. #[error("error returned from database: {0}")] Database(Box), /// Error communicating with the database backend. #[error("error communicating with the server: {0}")] Io(#[from] io::Error), /// Error occurred while attempting to establish a TLS connection. #[error("error occurred while attempting to establish a TLS connection: {0}")] Tls(#[source] BoxDynError), /// Unexpected or invalid data encountered while communicating with the database. /// /// This should indicate there is a programming error in a SQLx driver or there /// is something corrupted with the connection to the database itself. #[error("encountered unexpected or invalid data: {0}")] Protocol(String), /// No rows returned by a query that expected to return at least one row. #[error("no rows returned by a query that expected to return at least one row")] RowNotFound, /// Column index was out of bounds. #[error("column index out of bounds: the len is {len}, but the index is {index}")] ColumnIndexOutOfBounds { index: usize, len: usize }, /// No column found for the given name. #[error("no column found for name: {0}")] ColumnNotFound(String), /// Error occurred while decoding a value from a specific column. #[error("error occurred while decoding column {index}: {source}")] ColumnDecode { index: String, #[source] source: BoxDynError, }, /// Error occurred while decoding a value. #[error("error occurred while decoding: {0}")] Decode(#[source] BoxDynError), /// 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 #[error("pool timed out while waiting for an open connection")] PoolTimedOut, /// [`Pool::close`] was called while we were waiting in [`Pool::acquire`]. /// /// [`Pool::acquire`]: crate::pool::Pool::acquire /// [`Pool::close`]: crate::pool::Pool::close #[error("attempted to acquire a connection on a closed pool")] PoolClosed, } impl Error { pub fn into_database_error(self) -> Option> { match self { Error::Database(err) => Some(err), _ => None, } } pub fn as_database_error(&self) -> Option<&(dyn DatabaseError + 'static)> { match self { Error::Database(err) => Some(&**err), _ => None, } } #[allow(dead_code)] #[inline] pub(crate) fn protocol(err: impl Display) -> Self { Error::Protocol(err.to_string()) } #[allow(dead_code)] #[inline] pub(crate) fn tls(err: impl StdError + Send + Sync + 'static) -> Self { Error::Tls(err.into()) } } pub(crate) fn mismatched_types( actual: &DB::TypeInfo, expected: &DB::TypeInfo, ) -> BoxDynError { let ty_name = type_name::(); format!( "mismatched types; Rust type `{}` (as SQL type `{}`) is not compatible with SQL type `{}`", ty_name, actual, expected ) .into() } /// An error that was returned from the database. pub trait DatabaseError: 'static + Send + Sync + StdError { /// The primary, human-readable error message. fn message(&self) -> &str; /// The (SQLSTATE) code for the error. fn code(&self) -> Option> { None } #[doc(hidden)] fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static); #[doc(hidden)] fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static); #[doc(hidden)] fn into_error(self: Box) -> Box; } impl dyn DatabaseError { /// Downcast a reference to this generic database error to a specific /// database error type. /// /// # Panics /// /// Panics if the database error type is not `E`. This is a deliberate contrast from /// `Error::downcast_ref` which returns `Option<&E>`. In normal usage, you should know the /// specific error type. In other cases, use `try_downcast_ref`. /// pub fn downcast_ref(&self) -> &E { self.try_downcast_ref().unwrap_or_else(|| { panic!( "downcast to wrong DatabaseError type; original error: {}", self ) }) } /// Downcast this generic database error to a specific database error type. /// /// # Panics /// /// Panics if the database error type is not `E`. This is a deliberate contrast from /// `Error::downcast` which returns `Option`. In normal usage, you should know the /// specific error type. In other cases, use `try_downcast`. /// pub fn downcast(self: Box) -> Box { self.try_downcast().unwrap_or_else(|e| { panic!( "downcast to wrong DatabaseError type; original error: {}", e ) }) } /// Downcast a reference to this generic database error to a specific /// database error type. #[inline] pub fn try_downcast_ref(&self) -> Option<&E> { self.as_error().downcast_ref() } /// Downcast this generic database error to a specific database error type. #[inline] pub fn try_downcast(self: Box) -> StdResult, Box> { if self.as_error().is::() { Ok(self.into_error().downcast().unwrap()) } else { Err(self) } } } impl From for Error where E: DatabaseError, { #[inline] fn from(error: E) -> Self { Error::Database(Box::new(error)) } } // Format an error message as a `Protocol` error macro_rules! err_protocol { ($expr:expr) => { $crate::error::Error::Protocol($expr.into()) }; ($fmt:expr, $($arg:tt)*) => { $crate::error::Error::Protocol(format!($fmt, $($arg)*)) }; }