diff --git a/sqlx-core/src/decode.rs b/sqlx-core/src/decode.rs index f1551a23..292df0cd 100644 --- a/sqlx-core/src/decode.rs +++ b/sqlx-core/src/decode.rs @@ -3,13 +3,12 @@ use std::fmt::{self, Display, Formatter}; use std::str::Utf8Error; use crate::database::HasRawValue; -use crate::Database; +use crate::{Database, Type, TypeInfo}; /// A type that can be decoded from a SQL value. -pub trait Decode<'r, Db: Database>: Send + Sync { - fn decode(value: >::RawValue) -> Result - where - Self: Sized; +pub trait Decode<'r, Db: Database>: Sized + Send + Sync { + /// Decode the SQL value into the target type. + fn decode(value: >::RawValue) -> Result; } /// A type that can be decoded from a SQL value, without borrowing any data @@ -23,6 +22,11 @@ impl DecodeOwned for T where T: for<'r> Decode<'r, Db> {} #[derive(Debug)] #[non_exhaustive] pub enum Error { + TypeNotCompatible { + rust_type_name: &'static str, + sql_type_name: &'static str, + }, + /// An unexpected SQL `NULL` was encountered during decoding. /// /// To decode potentially `NULL` values, wrap the target type in `Option`. @@ -48,6 +52,14 @@ impl Display for Error { match self { Self::UnexpectedNull => f.write_str("unexpected null; try decoding as an `Option`"), + Self::TypeNotCompatible { rust_type_name, sql_type_name } => { + write!( + f, + "Rust type `{}` is not compatible with SQL type `{}`", + rust_type_name, sql_type_name + ) + } + Self::NotUtf8(error) => { write!(f, "{}", error) } diff --git a/sqlx-core/src/encode.rs b/sqlx-core/src/encode.rs index a071a608..6b9a3743 100644 --- a/sqlx-core/src/encode.rs +++ b/sqlx-core/src/encode.rs @@ -2,7 +2,7 @@ use std::error::Error as StdError; use std::fmt::{self, Display, Formatter}; use crate::database::HasOutput; -use crate::Database; +use crate::{Database, Type, TypeInfo}; /// A type that can be encoded into a SQL value. pub trait Encode: Send + Sync { @@ -21,6 +21,11 @@ impl, Db: Database> Encode for &T { #[derive(Debug)] #[non_exhaustive] pub enum Error { + TypeNotCompatible { + rust_type_name: &'static str, + sql_type_name: &'static str, + }, + /// A general error raised while encoding a value. Custom(Box), } @@ -35,6 +40,14 @@ impl Error { impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { + Self::TypeNotCompatible { rust_type_name, sql_type_name } => { + write!( + f, + "Rust type `{}` is not compatible with SQL type `{}`", + rust_type_name, sql_type_name + ) + } + Self::Custom(error) => { write!(f, "{}", error) } diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index 7ad7a3e5..b731f4c9 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -11,6 +11,8 @@ mod database; pub use database::DatabaseError; +use crate::Column; + /// Specialized `Result` type returned from fallible methods within SQLx. pub type Result = std::result::Result; @@ -89,6 +91,15 @@ impl Error { pub fn opt_msg(message: impl Into>) -> Self { Self::ConnectOptions { message: message.into(), source: None } } + + #[doc(hidden)] + pub fn column_decode(column: &impl Column, source: DecodeError) -> Self { + crate::Error::ColumnDecode { + source, + column_index: column.index(), + column_name: column.name().to_owned().into_boxed_str(), + } + } } impl Display for Error { @@ -107,41 +118,45 @@ impl Display for Error { } Self::RowNotFound => { - f.write_str("no row returned by a query required to return at least one row") + f.write_str("No row returned by a query required to return at least one row") } - Self::Closed => f.write_str("connection or pool is closed"), + Self::Closed => f.write_str("Connection or pool is closed"), Self::Decode(error) => { - write!(f, "decode: {}", error) + write!(f, "Decode: {}", error) } Self::Encode(error) => { - write!(f, "encode: {}", error) + write!(f, "Encode: {}", error) } Self::ColumnIndexOutOfBounds { index, len } => { write!( f, - "column index out of bounds: the len is {}, but the index is {}", + "Column index out of bounds: the len is {}, but the index is {}", len, index ) } Self::ColumnNotFound { name } => { - write!(f, "no column found for name `{}`", 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) + if column_name.is_empty() { + write!(f, "Decode column {}: {}", column_index, source) + } else { + write!(f, "Decode column {} `{}`: {}", column_index, column_name, source) + } } Self::ParameterEncode { parameter: Either::Left(index), source } => { - write!(f, "encode parameter {}: {}", index, source) + write!(f, "Encode parameter {}: {}", index, source) } Self::ParameterEncode { parameter: Either::Right(name), source } => { - write!(f, "encode parameter `{}`: {}", name, source) + write!(f, "Encode parameter `{}`: {}", name, source) } } } diff --git a/sqlx-core/src/type_info.rs b/sqlx-core/src/type_info.rs index 594f13d7..72584d53 100644 --- a/sqlx-core/src/type_info.rs +++ b/sqlx-core/src/type_info.rs @@ -34,5 +34,5 @@ pub trait TypeInfo { /// /// Common type names include `VARCHAR`, `INTEGER`, and `BIGINT`. /// - fn name(&self) -> &str; + fn name(&self) -> &'static str; }