From 2b99b1aeafc353684eab50f0ad2908aa8b84761b Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Mon, 22 Feb 2021 18:57:06 -0800 Subject: [PATCH] feat(core): expand variants and documentation of sqlx::Error --- sqlx-core/src/error.rs | 110 ++++++++++++++++------ sqlx-core/src/isolation_level.rs | 1 + sqlx-mysql/src/options/parse.rs | 5 +- sqlx-mysql/src/protocol/auth_plugin.rs | 9 +- sqlx-mysql/src/protocol/auth_response.rs | 10 +- sqlx-mysql/src/protocol/query_response.rs | 7 +- sqlx-mysql/src/protocol/query_step.rs | 7 +- sqlx-mysql/src/protocol/result.rs | 4 +- sqlx-mysql/src/stream.rs | 7 +- 9 files changed, 106 insertions(+), 54 deletions(-) diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index 3f5c6664..7ad7a3e5 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -2,6 +2,8 @@ use std::borrow::Cow; use std::error::Error as StdError; use std::fmt::{self, Display, Formatter}; +use either::Either; + use crate::decode::Error as DecodeError; use crate::encode::Error as EncodeError; @@ -9,59 +11,83 @@ mod database; pub use database::DatabaseError; -/// `Result` type returned from methods that can have SQLx errors. +/// Specialized `Result` type returned from fallible methods within SQLx. pub type Result = std::result::Result; +/// Error type returned for all methods in SQLX. #[derive(Debug)] #[non_exhaustive] pub enum Error { - Configuration { - message: Cow<'static, str>, - source: Option>, - }, + /// The database URL is malformed or contains invalid or unsupported + /// values for one or more options; a value of [`ConnectOptions`] failed + /// to be parsed. + ConnectOptions { message: Cow<'static, str>, source: Option> }, - Connect(Box), + /// The database returned an error. + Database(Box), + /// An IO error returned while reading or writing a socket attached + /// to the database server. + /// + /// Only applicable if the database driver connects to a remote database + /// server. + /// Network(std::io::Error), - /// Returned by `fetch_one` when no row was returned from the query. + /// No rows returned by a query required to return at least one row. /// - /// Use `fetch_optional` to return `None` instead of signaling an error. + /// Returned by `fetch_one` when no rows were returned from + /// the query. Use `fetch_optional` to return `None` instead + /// of signaling an error. /// RowNotFound, + /// An attempt to act on a closed connection or pool. + /// + /// A connection will close itself on an unrecoverable error in the + /// connection (implementation bugs, faulty network, etc.). If the error + /// was ignored and the connection is used again, it will + /// return `Error::Closed`. + /// + /// A pool will return `Error::Closed` from `Pool::acquire` if `Pool::close` + /// was called before `acquire` received a connection. + /// Closed, + /// An error occurred decoding a SQL value from the database. Decode(DecodeError), + /// An error occurred encoding a value to be sent to the database. Encode(EncodeError), - ColumnIndexOutOfBounds { - index: usize, - len: usize, - }, + /// An attempt to access a column by index past the end of the row. + ColumnIndexOutOfBounds { index: usize, len: usize }, + + /// An attempt to access a column by name where no such column is + /// present in the row. + ColumnNotFound { name: Box }, + + /// An error occurred decoding a SQL value of a specific column + /// from the database. + ColumnDecode { column_index: usize, column_name: Box, source: DecodeError }, + + /// An error occurred encoding a value for a specific parameter to + /// be sent to the database. + ParameterEncode { parameter: Either>, source: EncodeError }, } impl Error { #[doc(hidden)] - pub fn connect(error: E) -> Self - where - E: DatabaseError, - { - Self::Connect(Box::new(error)) - } - - #[doc(hidden)] - pub fn configuration( + pub fn opt( message: impl Into>, source: impl Into>, ) -> Self { - Self::Configuration { message: message.into(), source: Some(source.into()) } + Self::ConnectOptions { message: message.into(), source: Some(source.into()) } } #[doc(hidden)] - pub fn configuration_msg(message: impl Into>) -> Self { - Self::Configuration { message: message.into(), source: None } + pub fn opt_msg(message: impl Into>) -> Self { + Self::ConnectOptions { message: message.into(), source: None } } } @@ -70,13 +96,13 @@ impl Display for Error { match self { Self::Network(source) => write!(f, "{}", source), - Self::Connect(source) => write!(f, "{}", source), + Self::Database(source) => write!(f, "{}", source), - Self::Configuration { message, source: None } => { + Self::ConnectOptions { message, source: None } => { write!(f, "{}", message) } - Self::Configuration { message, source: Some(source) } => { + Self::ConnectOptions { message, source: Some(source) } => { write!(f, "{}: {}", message, source) } @@ -84,14 +110,14 @@ impl Display for Error { f.write_str("no row returned by a query required to return at least one row") } - Self::Closed => f.write_str("connection or pool was closed"), + Self::Closed => f.write_str("connection or pool is closed"), Self::Decode(error) => { - write!(f, "{}", error) + write!(f, "decode: {}", error) } Self::Encode(error) => { - write!(f, "{}", error) + write!(f, "encode: {}", error) } Self::ColumnIndexOutOfBounds { index, len } => { @@ -101,6 +127,22 @@ impl Display for Error { len, index ) } + + Self::ColumnNotFound { name } => { + write!(f, "no column found for name `{}`", name) + } + + Self::ColumnDecode { column_index, column_name, source } => { + write!(f, "decode column {} `{}`: {}", column_index, column_name, source) + } + + Self::ParameterEncode { parameter: Either::Left(index), source } => { + write!(f, "encode parameter {}: {}", index, source) + } + + Self::ParameterEncode { parameter: Either::Right(name), source } => { + write!(f, "encode parameter `{}`: {}", name, source) + } } } } @@ -108,7 +150,7 @@ impl Display for Error { impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { - Self::Configuration { source: Some(source), .. } => Some(&**source), + Self::ConnectOptions { source: Some(source), .. } => Some(&**source), Self::Network(source) => Some(source), _ => None, @@ -116,6 +158,12 @@ impl StdError for Error { } } +impl From for Error { + fn from(error: E) -> Self { + Self::Database(Box::new(error)) + } +} + impl From for Error { fn from(error: std::io::Error) -> Self { Self::Network(error) diff --git a/sqlx-core/src/isolation_level.rs b/sqlx-core/src/isolation_level.rs index ecdb7b5f..facdf3a6 100644 --- a/sqlx-core/src/isolation_level.rs +++ b/sqlx-core/src/isolation_level.rs @@ -3,6 +3,7 @@ /// /// See . /// +#[derive(Debug)] pub enum IsolationLevel { /// The lowest isolation level. Dirty reads are allowed, so one transaction /// may see **not yet committed** changes made by other transactions. diff --git a/sqlx-mysql/src/options/parse.rs b/sqlx-mysql/src/options/parse.rs index 077aa47b..581d2532 100644 --- a/sqlx-mysql/src/options/parse.rs +++ b/sqlx-mysql/src/options/parse.rs @@ -11,11 +11,10 @@ impl FromStr for MySqlConnectOptions { type Err = Error; fn from_str(s: &str) -> Result { - let url: Url = - s.parse().map_err(|error| Error::configuration("for database URL", error))?; + let url: Url = s.parse().map_err(|error| Error::opt("for database URL", error))?; if !matches!(url.scheme(), "mysql") { - return Err(Error::configuration_msg(format!( + return Err(Error::opt_msg(format!( "unsupported URL scheme {:?} for MySQL", url.scheme() ))); diff --git a/sqlx-mysql/src/protocol/auth_plugin.rs b/sqlx-mysql/src/protocol/auth_plugin.rs index 36e398af..5c4b5364 100644 --- a/sqlx-mysql/src/protocol/auth_plugin.rs +++ b/sqlx-mysql/src/protocol/auth_plugin.rs @@ -40,10 +40,11 @@ impl dyn AuthPlugin { _ if s == Sha256AuthPlugin.name() => Ok(Box::new(Sha256AuthPlugin)), _ if s == NativeAuthPlugin.name() => Ok(Box::new(NativeAuthPlugin)), - _ => Err(Error::connect(MySqlDatabaseError::new( + _ => Err(MySqlDatabaseError::new( 2059, &format!("Authentication plugin '{}' cannot be loaded", s), - ))), + ) + .into()), } } } @@ -59,10 +60,10 @@ fn xor_eq(x: &mut [u8], y: &[u8]) { } fn err_msg(plugin: &'static str, message: &str) -> Error { - Error::connect(MySqlDatabaseError::new( + MySqlDatabaseError::new( 2061, &format!("Authentication plugin '{}' reported error: {}", plugin, message), - )) + ).into() } fn err(plugin: &'static str, error: &E) -> Error diff --git a/sqlx-mysql/src/protocol/auth_response.rs b/sqlx-mysql/src/protocol/auth_response.rs index 4d17def2..c89021c5 100644 --- a/sqlx-mysql/src/protocol/auth_response.rs +++ b/sqlx-mysql/src/protocol/auth_response.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use bytes::Bytes; use sqlx_core::io::Deserialize; -use sqlx_core::{Error, Result}; +use sqlx_core::Result; use crate::protocol::{AuthSwitch, Capabilities, ResultPacket}; use crate::MySqlDatabaseError; @@ -21,14 +21,14 @@ impl Deserialize<'_, Capabilities> for AuthResponse { Some(0x01) => Ok(Self::MoreData(buf.slice(1..))), Some(0xfe) => AuthSwitch::deserialize(buf).map(Self::Switch), - Some(tag) => Err(Error::connect(MySqlDatabaseError::malformed_packet(&format!( + Some(tag) => Err(MySqlDatabaseError::malformed_packet(&format!( "Received 0x{:x} but expected one of: 0x0 (OK), 0x1 (MORE DATA), or 0xfe (SWITCH) for auth response", tag - )))), + )).into()), - None => Err(Error::connect(MySqlDatabaseError::malformed_packet( + None => Err(MySqlDatabaseError::malformed_packet( "Received no bytes for auth response", - ))), + ).into()), } } } diff --git a/sqlx-mysql/src/protocol/query_response.rs b/sqlx-mysql/src/protocol/query_response.rs index 0d11ff03..3558a835 100644 --- a/sqlx-mysql/src/protocol/query_response.rs +++ b/sqlx-mysql/src/protocol/query_response.rs @@ -1,6 +1,6 @@ use bytes::Bytes; use sqlx_core::io::Deserialize; -use sqlx_core::{Error, Result}; +use sqlx_core::Result; use super::{Capabilities, ResultPacket}; use crate::io::MySqlBufExt; @@ -46,9 +46,10 @@ impl Deserialize<'_, Capabilities> for QueryResponse { Ok(Self::ResultSet { columns: columns as u16 }) } - None => Err(Error::connect(MySqlDatabaseError::malformed_packet( + None => Err(MySqlDatabaseError::malformed_packet( "Received no bytes for COM_QUERY response", - ))), + ) + .into()), } } } diff --git a/sqlx-mysql/src/protocol/query_step.rs b/sqlx-mysql/src/protocol/query_step.rs index cc9575b0..47e0d0ff 100644 --- a/sqlx-mysql/src/protocol/query_step.rs +++ b/sqlx-mysql/src/protocol/query_step.rs @@ -1,6 +1,6 @@ use bytes::Bytes; use sqlx_core::io::Deserialize; -use sqlx_core::{Error, Result}; +use sqlx_core::Result; use super::{Capabilities, ResultPacket}; use crate::protocol::Packet; @@ -30,9 +30,10 @@ impl Deserialize<'_, Capabilities> for QueryStep { // If its non-0, then its a Row Some(_) => Ok(Self::Row(Packet { bytes: buf })), - None => Err(Error::connect(MySqlDatabaseError::malformed_packet( + None => Err(MySqlDatabaseError::malformed_packet( "Received no bytes for the next step in a result set", - ))), + ) + .into()), } } } diff --git a/sqlx-mysql/src/protocol/result.rs b/sqlx-mysql/src/protocol/result.rs index 297619ed..f97dadc9 100644 --- a/sqlx-mysql/src/protocol/result.rs +++ b/sqlx-mysql/src/protocol/result.rs @@ -1,6 +1,6 @@ use bytes::Bytes; use sqlx_core::io::Deserialize; -use sqlx_core::{Error, Result}; +use sqlx_core::Result; use super::{Capabilities, ErrPacket, OkPacket}; use crate::MySqlDatabaseError; @@ -22,7 +22,7 @@ where pub(crate) fn into_result(self) -> Result { match self { Self::Ok(ok) => Ok(ok), - Self::Err(err) => Err(Error::connect(MySqlDatabaseError(err))), + Self::Err(err) => Err(MySqlDatabaseError(err).into()), } } } diff --git a/sqlx-mysql/src/stream.rs b/sqlx-mysql/src/stream.rs index e42b75c8..340f03d8 100644 --- a/sqlx-mysql/src/stream.rs +++ b/sqlx-mysql/src/stream.rs @@ -4,7 +4,7 @@ use std::ops::{Deref, DerefMut}; use bytes::{Buf, BufMut}; use sqlx_core::io::{BufStream, Serialize, Stream}; use sqlx_core::net::Stream as NetStream; -use sqlx_core::{Error, Result, Runtime}; +use sqlx_core::{Result, Runtime}; use crate::protocol::{MaybeCommand, Packet, Quit}; use crate::MySqlDatabaseError; @@ -101,11 +101,12 @@ impl MySqlStream { if packet.bytes.len() != len { // BUG: something is very wrong somewhere if this branch is executed // either in the SQLx MySQL driver or in the MySQL server - return Err(Error::connect(MySqlDatabaseError::malformed_packet(&format!( + return Err(MySqlDatabaseError::malformed_packet(&format!( "Received {} bytes for packet but expecting {} bytes", packet.bytes.len(), len - )))); + )) + .into()); } Ok(packet)