decode: add lifetime to Decode; impl Decode for &str and &[u8]; remove DecodeError

This commit is contained in:
Ryan Leckey 2020-02-29 19:30:21 -08:00
parent d257c32946
commit dd5f250e5e
12 changed files with 98 additions and 115 deletions

View File

@ -47,7 +47,7 @@ rand = { version = "0.7.3", default-features = false, optional = true, features
sha-1 = { version = "0.8.2", default-features = false, optional = true }
sha2 = { version = "0.8.1", default-features = false, optional = true }
url = { version = "2.1.1", default-features = false }
uuid = { version = "0.8.1", default-features = false, optional = true }
uuid = { version = "0.8.1", default-features = false, optional = true, features = [ "std" ] }
hmac = { version = "0.7.1", default-features = false, optional = true }
[dev-dependencies]

View File

@ -6,30 +6,22 @@ use std::fmt::{self, Display};
use crate::database::Database;
use crate::types::Type;
pub enum DecodeError {
/// An unexpected `NULL` was encountered while decoding.
UnexpectedNull,
Message(Box<dyn Display + Send + Sync>),
Other(Box<dyn StdError + Send + Sync>),
}
/// Decode a single value from the database.
pub trait Decode<DB>: Sized
pub trait Decode<'de, DB>
where
DB: Database + ?Sized,
Self: Sized,
DB: Database,
{
fn decode(raw: &[u8]) -> Result<Self, DecodeError>;
fn decode(raw: &'de [u8]) -> crate::Result<Self>;
/// Creates a new value of this type from a `NULL` SQL value.
///
/// The default implementation returns [DecodeError::UnexpectedNull].
fn decode_null() -> Result<Self, DecodeError> {
Err(DecodeError::UnexpectedNull)
fn decode_null() -> crate::Result<Self> {
Err(crate::Error::Decode(UnexpectedNullError.into()))
}
fn decode_nullable(raw: Option<&[u8]>) -> Result<Self, DecodeError> {
fn decode_nullable(raw: Option<&'de [u8]>) -> crate::Result<Self> {
if let Some(raw) = raw {
Self::decode(raw)
} else {
@ -38,50 +30,32 @@ where
}
}
impl<T, DB> Decode<DB> for Option<T>
impl<'de, T, DB> Decode<'de, DB> for Option<T>
where
DB: Database,
T: Type<DB>,
T: Decode<DB>,
T: Decode<'de, DB>,
{
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
fn decode(buf: &'de [u8]) -> crate::Result<Self> {
T::decode(buf).map(Some)
}
fn decode_null() -> Result<Self, DecodeError> {
fn decode_null() -> crate::Result<Self> {
Ok(None)
}
}
impl fmt::Debug for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("DecodeError(")?;
/// An unexpected `NULL` was encountered during decoding.
///
/// Returned from `Row::try_get` if the value from the database is `NULL`
/// and you are not decoding into an `Option`.
#[derive(Debug, Clone, Copy)]
pub struct UnexpectedNullError;
match self {
DecodeError::UnexpectedNull => write!(f, "unexpected null for non-null column")?,
DecodeError::Message(err) => write!(f, "{}", err)?,
DecodeError::Other(err) => write!(f, "{:?}", err)?,
}
f.write_str(")")
impl Display for UnexpectedNullError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("unexpected null; try decoding as an `Option`")
}
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DecodeError::UnexpectedNull => f.write_str("unexpected null for non-null column"),
DecodeError::Message(err) => write!(f, "{}", err),
DecodeError::Other(err) => write!(f, "{}", err),
}
}
}
impl<E> From<E> for DecodeError
where
E: StdError + Send + Sync + 'static,
{
fn from(err: E) -> DecodeError {
DecodeError::Other(Box::new(err))
}
}
impl StdError for UnexpectedNullError {}

View File

@ -1,6 +1,5 @@
//! Error and Result types.
use crate::decode::DecodeError;
use std::error::Error as StdError;
use std::fmt::{self, Debug, Display};
use std::io;
@ -28,10 +27,7 @@ pub enum Error {
ColumnNotFound(Box<str>),
/// Column index was out of bounds (e.g., asking for column 4 in a 2-column row).
ColumnIndexOutOfBounds {
index: usize,
len: usize,
},
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
@ -51,7 +47,8 @@ pub enum Error {
/// An error occurred during a TLS upgrade.
TlsUpgrade(Box<dyn StdError + Send + Sync>),
Decode(DecodeError),
/// An error occurred decoding data received from the database.
Decode(Box<dyn StdError + Send + Sync>),
}
impl StdError for Error {
@ -60,7 +57,7 @@ impl StdError for Error {
Error::Io(error) => Some(error),
Error::UrlParse(error) => Some(error),
Error::PoolTimedOut(Some(error)) => Some(&**error),
Error::Decode(DecodeError::Other(error)) => Some(&**error),
Error::Decode(error) => Some(&**error),
Error::TlsUpgrade(error) => Some(&**error),
_ => None,
@ -124,13 +121,6 @@ impl From<io::ErrorKind> for Error {
}
}
impl From<DecodeError> for Error {
#[inline]
fn from(err: DecodeError) -> Self {
Error::Decode(err)
}
}
impl From<url::ParseError> for Error {
#[inline]
fn from(err: url::ParseError) -> Self {

View File

@ -1,4 +1,4 @@
use crate::decode::{Decode, DecodeError};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
@ -23,10 +23,8 @@ impl Encode<Postgres> for bool {
}
}
impl Decode<Postgres> for bool {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
buf.get(0).map(|&b| b != 0).ok_or_else(|| {
DecodeError::Message(Box::new("Expected minimum 1 byte but received none."))
})
impl<'de> Decode<'de, Postgres> for bool {
fn decode(buf: &'de [u8]) -> crate::Result<Self> {
Ok(buf.get(0).map(|&b| b != 0).unwrap_or_default())
}
}

View File

@ -1,4 +1,4 @@
use crate::decode::{Decode, DecodeError};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
@ -17,7 +17,6 @@ impl Type<Postgres> for [&'_ [u8]] {
}
}
// TODO: Do we need the [HasSqlType] here on the Vec?
impl Type<Postgres> for Vec<u8> {
fn type_info() -> PgTypeInfo {
<[u8] as Type<Postgres>>::type_info()
@ -36,8 +35,14 @@ impl Encode<Postgres> for Vec<u8> {
}
}
impl Decode<Postgres> for Vec<u8> {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
impl<'de> Decode<'de, Postgres> for Vec<u8> {
fn decode(buf: &'de [u8]) -> crate::Result<Self> {
Ok(buf.to_vec())
}
}
impl<'de> Decode<'de, Postgres> for &'de [u8] {
fn decode(buf: &'de [u8]) -> crate::Result<&'de [u8]> {
Ok(buf)
}
}

View File

@ -3,7 +3,7 @@ use std::mem;
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use crate::decode::{Decode, DecodeError};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
@ -28,7 +28,7 @@ impl Type<Postgres> for NaiveDateTime {
}
}
impl<Tz> Type<DateTime<Tz>> for Postgres
impl<Tz> Type<Postgres> for DateTime<Tz>
where
Tz: TimeZone,
{
@ -55,7 +55,7 @@ impl Type<Postgres> for [NaiveDateTime] {
}
}
impl<Tz> Type<[DateTime<Tz>]> for Postgres
impl<Tz> Type<Postgres> for [DateTime<Tz>]
where
Tz: TimeZone,
{
@ -64,8 +64,8 @@ where
}
}
impl Decode<Postgres> for NaiveTime {
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
impl<'de> Decode<'de, Postgres> for NaiveTime {
fn decode(raw: &'de [u8]) -> crate::Result<Self> {
let micros: i64 = Decode::<Postgres>::decode(raw)?;
Ok(NaiveTime::from_hms(0, 0, 0) + Duration::microseconds(micros))
@ -86,8 +86,8 @@ impl Encode<Postgres> for NaiveTime {
}
}
impl Decode<Postgres> for NaiveDate {
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
impl<'de> Decode<'de, Postgres> for NaiveDate {
fn decode(raw: &'de [u8]) -> crate::Result<Self> {
let days: i32 = Decode::<Postgres>::decode(raw)?;
Ok(NaiveDate::from_ymd(2000, 1, 1) + Duration::days(days as i64))
@ -111,18 +111,21 @@ impl Encode<Postgres> for NaiveDate {
}
}
impl Decode<Postgres> for NaiveDateTime {
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
impl<'de> Decode<'de, Postgres> for NaiveDateTime {
fn decode(raw: &'de [u8]) -> crate::Result<Self> {
let micros: i64 = Decode::<Postgres>::decode(raw)?;
postgres_epoch()
.naive_utc()
.checked_add_signed(Duration::microseconds(micros))
.ok_or_else(|| {
DecodeError::Message(Box::new(format!(
"Postgres timestamp out of range for NaiveDateTime: {:?}",
micros
)))
crate::Error::Decode(
format!(
"Postgres timestamp out of range for NaiveDateTime: {:?}",
micros
)
.into(),
)
})
}
}
@ -142,15 +145,15 @@ impl Encode<Postgres> for NaiveDateTime {
}
}
impl Decode<Postgres> for DateTime<Utc> {
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
impl<'de> Decode<'de, Postgres> for DateTime<Utc> {
fn decode(raw: &'de [u8]) -> crate::Result<Self> {
let date_time = Decode::<Postgres>::decode(raw)?;
Ok(DateTime::from_utc(date_time, Utc))
}
}
impl Decode<Postgres> for DateTime<Local> {
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
impl<'de> Decode<'de, Postgres> for DateTime<Local> {
fn decode(raw: &'de [u8]) -> crate::Result<Self> {
let date_time = Decode::<Postgres>::decode(raw)?;
Ok(Local.from_utc_datetime(&date_time))
}

View File

@ -1,4 +1,4 @@
use crate::decode::{Decode, DecodeError};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
@ -23,8 +23,8 @@ impl Encode<Postgres> for f32 {
}
}
impl Decode<Postgres> for f32 {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
impl<'de> Decode<'de, Postgres> for f32 {
fn decode(buf: &'de [u8]) -> crate::Result<Self> {
Ok(f32::from_bits(
<i32 as Decode<Postgres>>::decode(buf)? as u32
))
@ -49,8 +49,8 @@ impl Encode<Postgres> for f64 {
}
}
impl Decode<Postgres> for f64 {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
impl<'de> Decode<'de, Postgres> for f64 {
fn decode(buf: &'de [u8]) -> crate::Result<Self> {
Ok(f64::from_bits(
<i64 as Decode<Postgres>>::decode(buf)? as u64
))

View File

@ -1,6 +1,6 @@
use byteorder::{ByteOrder, NetworkEndian};
use crate::decode::{Decode, DecodeError};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
@ -25,8 +25,8 @@ impl Encode<Postgres> for i16 {
}
}
impl Decode<Postgres> for i16 {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
impl<'de> Decode<'de, Postgres> for i16 {
fn decode(buf: &'de [u8]) -> crate::Result<Self> {
Ok(NetworkEndian::read_i16(buf))
}
}
@ -49,8 +49,8 @@ impl Encode<Postgres> for i32 {
}
}
impl Decode<Postgres> for i32 {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
impl<'de> Decode<'de, Postgres> for i32 {
fn decode(buf: &'de [u8]) -> crate::Result<Self> {
Ok(NetworkEndian::read_i32(buf))
}
}
@ -73,8 +73,8 @@ impl Encode<Postgres> for i64 {
}
}
impl Decode<Postgres> for i64 {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
impl<'de> Decode<'de, Postgres> for i64 {
fn decode(buf: &'de [u8]) -> crate::Result<Self> {
Ok(NetworkEndian::read_i64(buf))
}
}

View File

@ -1,6 +1,6 @@
use std::str;
use crate::decode::{Decode, DecodeError};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
@ -19,7 +19,7 @@ impl Type<Postgres> for [&'_ str] {
}
}
// TODO: Do we need [HasSqlType] on String here?
// TODO: Do we need [Type] on String here?
impl Type<Postgres> for String {
fn type_info() -> PgTypeInfo {
<str as Type<Postgres>>::type_info()
@ -46,8 +46,14 @@ impl Encode<Postgres> for String {
}
}
impl Decode<Postgres> for String {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
Ok(str::from_utf8(buf)?.to_owned())
impl<'de> Decode<'de, Postgres> for String {
fn decode(buf: &'de [u8]) -> crate::Result<Self> {
<&'de str>::decode(buf).map(ToOwned::to_owned)
}
}
impl<'de> Decode<'de, Postgres> for &'de str {
fn decode(buf: &'de [u8]) -> crate::Result<Self> {
str::from_utf8(buf).map_err(|err| crate::Error::Decode(Box::new(err)))
}
}

View File

@ -1,6 +1,6 @@
use uuid::Uuid;
use crate::decode::{Decode, DecodeError};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::types::PgTypeInfo;
@ -25,8 +25,8 @@ impl Encode<Postgres> for Uuid {
}
}
impl Decode<Postgres> for Uuid {
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
Uuid::from_slice(buf).map_err(|err| DecodeError::Message(Box::new(err)))
impl<'de> Decode<'de, Postgres> for Uuid {
fn decode(buf: &'de [u8]) -> crate::Result<Self> {
Uuid::from_slice(buf).map_err(|err| crate::Error::Decode(Box::new(err)))
}
}

View File

@ -27,9 +27,8 @@ pub trait Row<'c>: Unpin + Send {
where
T: Type<Self::Database>,
I: ColumnIndex<'c, Self>,
T: Decode<Self::Database>,
T: Decode<'c, Self::Database>,
{
// todo: use expect with a proper message
self.try_get(index).unwrap()
}
@ -37,7 +36,7 @@ pub trait Row<'c>: Unpin + Send {
where
T: Type<Self::Database>,
I: ColumnIndex<'c, Self>,
T: Decode<Self::Database>,
T: Decode<'c, Self::Database>,
{
Ok(Decode::decode_nullable(self.try_get_raw(index)?)?)
}

View File

@ -1,4 +1,4 @@
use crate::decode::DecodeError;
use crate::decode::UnexpectedNullError;
use crate::Error;
pub trait ResultExt<T>: Sized {
@ -15,7 +15,15 @@ impl<T> ResultExt<Option<T>> for crate::Result<T> {
fn try_unwrap_optional(self) -> crate::Result<Option<T>> {
match self {
Ok(val) => Ok(Some(val)),
Err(Error::Decode(DecodeError::UnexpectedNull)) => Ok(None),
Err(Error::Decode(error)) => {
if let Some(UnexpectedNullError) = error.downcast_ref() {
Ok(None)
} else {
Err(Error::Decode(error))
}
}
Err(e) => Err(e),
}
}