feat(core): expand variants and documentation of sqlx::Error

This commit is contained in:
Ryan Leckey 2021-02-22 18:57:06 -08:00
parent 301665360c
commit 2b99b1aeaf
No known key found for this signature in database
GPG Key ID: F8AA68C235AB08C9
9 changed files with 106 additions and 54 deletions

View File

@ -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<T> = std::result::Result<T, Error>;
/// Error type returned for all methods in SQLX.
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Configuration {
message: Cow<'static, str>,
source: Option<Box<dyn StdError + Send + Sync>>,
},
/// 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<Box<dyn StdError + Send + Sync>> },
Connect(Box<dyn DatabaseError>),
/// The database returned an error.
Database(Box<dyn DatabaseError>),
/// 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<str> },
/// An error occurred decoding a SQL value of a specific column
/// from the database.
ColumnDecode { column_index: usize, column_name: Box<str>, source: DecodeError },
/// An error occurred encoding a value for a specific parameter to
/// be sent to the database.
ParameterEncode { parameter: Either<usize, Box<str>>, source: EncodeError },
}
impl Error {
#[doc(hidden)]
pub fn connect<E>(error: E) -> Self
where
E: DatabaseError,
{
Self::Connect(Box::new(error))
}
#[doc(hidden)]
pub fn configuration(
pub fn opt(
message: impl Into<Cow<'static, str>>,
source: impl Into<Box<dyn StdError + Send + Sync>>,
) -> 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<Cow<'static, str>>) -> Self {
Self::Configuration { message: message.into(), source: None }
pub fn opt_msg(message: impl Into<Cow<'static, str>>) -> 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<E: DatabaseError> From<E> for Error {
fn from(error: E) -> Self {
Self::Database(Box::new(error))
}
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Self::Network(error)

View File

@ -3,6 +3,7 @@
///
/// See <https://en.wikipedia.org/wiki/Isolation_(database_systems)#Isolation_levels>.
///
#[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.

View File

@ -11,11 +11,10 @@ impl FromStr for MySqlConnectOptions {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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()
)));

View File

@ -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<E>(plugin: &'static str, error: &E) -> Error

View File

@ -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()),
}
}
}

View File

@ -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()),
}
}
}

View File

@ -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()),
}
}
}

View File

@ -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<T> {
match self {
Self::Ok(ok) => Ok(ok),
Self::Err(err) => Err(Error::connect(MySqlDatabaseError(err))),
Self::Err(err) => Err(MySqlDatabaseError(err).into()),
}
}
}

View File

@ -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<Rt: Runtime> MySqlStream<Rt> {
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)