Restructure errors

WasNull and BadResponse are gone, and Conversion was added. IoError
covers the BadResponse case and Conversion is a more general version of
WasNull.
This commit is contained in:
Steven Fackler 2015-05-22 21:20:56 -07:00
parent 81fc578ca6
commit 35197960b2
10 changed files with 60 additions and 41 deletions

View File

@ -66,14 +66,14 @@ impl DbErrorNew for DbError {
fn new_connect<T>(fields: Vec<(u8, String)>) -> result::Result<T, ConnectError> { fn new_connect<T>(fields: Vec<(u8, String)>) -> result::Result<T, ConnectError> {
match DbError::new_raw(fields) { match DbError::new_raw(fields) {
Ok(err) => Err(ConnectError::DbError(err)), Ok(err) => Err(ConnectError::DbError(err)),
Err(()) => Err(ConnectError::BadResponse), Err(()) => Err(ConnectError::IoError(::bad_response())),
} }
} }
fn new<T>(fields: Vec<(u8, String)>) -> Result<T> { fn new<T>(fields: Vec<(u8, String)>) -> Result<T> {
match DbError::new_raw(fields) { match DbError::new_raw(fields) {
Ok(err) => Err(Error::DbError(err)), Ok(err) => Err(Error::DbError(err)),
Err(()) => Err(Error::BadResponse), Err(()) => Err(Error::IoError(::bad_response())),
} }
} }
} }
@ -205,11 +205,9 @@ pub enum ConnectError {
/// The Postgres server does not support SSL encryption. /// The Postgres server does not support SSL encryption.
NoSslSupport, NoSslSupport,
/// There was an error initializing the SSL session /// There was an error initializing the SSL session
SslError(Box<error::Error>), SslError(Box<error::Error+Sync+Send>),
/// There was an error communicating with the server. /// There was an error communicating with the server.
IoError(io::Error), IoError(io::Error),
/// The server sent an unexpected response.
BadResponse,
} }
impl fmt::Display for ConnectError { impl fmt::Display for ConnectError {
@ -235,7 +233,6 @@ impl error::Error for ConnectError {
ConnectError::NoSslSupport => "The server does not support SSL", ConnectError::NoSslSupport => "The server does not support SSL",
ConnectError::SslError(_) => "Error initiating SSL session", ConnectError::SslError(_) => "Error initiating SSL session",
ConnectError::IoError(_) => "Error communicating with server", ConnectError::IoError(_) => "Error communicating with server",
ConnectError::BadResponse => "The server returned an unexpected response",
} }
} }
@ -296,10 +293,8 @@ pub enum Error {
WrongType(Type), WrongType(Type),
/// An attempt was made to read from a column that does not exist. /// An attempt was made to read from a column that does not exist.
InvalidColumn, InvalidColumn,
/// A value was NULL but converted to a non-nullable Rust type. /// An error converting between Postgres and Rust types.
WasNull, Conversion(Box<error::Error+Sync+Send>),
/// The server returned an unexpected response.
BadResponse,
} }
impl fmt::Display for Error { impl fmt::Display for Error {
@ -322,8 +317,7 @@ impl error::Error for Error {
} }
Error::WrongType(_) => "Unexpected type", Error::WrongType(_) => "Unexpected type",
Error::InvalidColumn => "Invalid column", Error::InvalidColumn => "Invalid column",
Error::WasNull => "The value was NULL", Error::Conversion(_) => "An error converting between Postgres and Rust types",
Error::BadResponse => "The server returned an unexpected response",
} }
} }
@ -331,6 +325,7 @@ impl error::Error for Error {
match *self { match *self {
Error::DbError(ref err) => Some(err), Error::DbError(ref err) => Some(err),
Error::IoError(ref err) => Some(err), Error::IoError(ref err) => Some(err),
Error::Conversion(ref err) => Some(&**err),
_ => None _ => None
} }
} }

View File

@ -26,5 +26,6 @@ pub trait NegotiateSsl {
/// ///
/// The host portion of the connection parameters is provided for hostname /// The host portion of the connection parameters is provided for hostname
/// verification. /// verification.
fn negotiate_ssl(&self, host: &str, stream: Stream) -> Result<Box<StreamWrapper>, Box<Error>>; fn negotiate_ssl(&self, host: &str, stream: Stream)
-> Result<Box<StreamWrapper>, Box<Error+Sync+Send>>;
} }

View File

@ -402,6 +402,11 @@ pub fn cancel_query<T>(params: T, ssl: &SslMode, data: CancelData)
Ok(()) Ok(())
} }
fn bad_response() -> std_io::Error {
std_io::Error::new(std_io::ErrorKind::InvalidInput,
"the server returned an unexpected response")
}
/// An enumeration of transaction isolation levels. /// An enumeration of transaction isolation levels.
/// ///
/// See the [Postgres documentation](http://www.postgresql.org/docs/9.4/static/transaction-iso.html) /// See the [Postgres documentation](http://www.postgresql.org/docs/9.4/static/transaction-iso.html)
@ -451,7 +456,7 @@ impl IsolationLevel {
} else if raw.eq_ignore_ascii_case("SERIALIZABLE") { } else if raw.eq_ignore_ascii_case("SERIALIZABLE") {
Ok(IsolationLevel::Serializable) Ok(IsolationLevel::Serializable)
} else { } else {
Err(Error::BadResponse) Err(Error::IoError(bad_response()))
} }
} }
} }
@ -548,7 +553,7 @@ impl InnerConnection {
} }
ReadyForQuery { .. } => break, ReadyForQuery { .. } => break,
ErrorResponse { fields } => return DbError::new_connect(fields), ErrorResponse { fields } => return DbError::new_connect(fields),
_ => return Err(ConnectError::BadResponse), _ => return Err(ConnectError::IoError(bad_response())),
} }
} }
@ -659,13 +664,13 @@ impl InnerConnection {
| AuthenticationGSS | AuthenticationGSS
| AuthenticationSSPI => return Err(ConnectError::UnsupportedAuthentication), | AuthenticationSSPI => return Err(ConnectError::UnsupportedAuthentication),
ErrorResponse { fields } => return DbError::new_connect(fields), ErrorResponse { fields } => return DbError::new_connect(fields),
_ => return Err(ConnectError::BadResponse) _ => return Err(ConnectError::IoError(bad_response()))
} }
match try!(self.read_message()) { match try!(self.read_message()) {
AuthenticationOk => Ok(()), AuthenticationOk => Ok(()),
ErrorResponse { fields } => return DbError::new_connect(fields), ErrorResponse { fields } => return DbError::new_connect(fields),
_ => return Err(ConnectError::BadResponse) _ => return Err(ConnectError::IoError(bad_response()))
} }
} }
@ -1469,7 +1474,7 @@ impl<'conn> Statement<'conn> {
} }
_ => { _ => {
conn.desynchronized = true; conn.desynchronized = true;
Err(Error::BadResponse) Err(Error::IoError(bad_response()))
} }
} }
} }
@ -1545,7 +1550,7 @@ impl<'conn> Statement<'conn> {
} }
_ => { _ => {
conn.desynchronized = true; conn.desynchronized = true;
return Err(Error::BadResponse); return Err(Error::IoError(bad_response()));
} }
} }
} }
@ -1693,7 +1698,7 @@ fn read_rows(conn: &mut InnerConnection, buf: &mut VecDeque<Vec<Option<Vec<u8>>>
} }
_ => { _ => {
conn.desynchronized = true; conn.desynchronized = true;
return Err(Error::BadResponse); return Err(Error::IoError(bad_response()));
} }
} }
} }
@ -2136,7 +2141,7 @@ impl<'a> CopyInStatement<'a> {
} }
_ => { _ => {
conn.desynchronized = true; conn.desynchronized = true;
return Err(Error::BadResponse); return Err(Error::IoError(bad_response()));
} }
} }
@ -2144,7 +2149,7 @@ impl<'a> CopyInStatement<'a> {
CopyInResponse { .. } => {} CopyInResponse { .. } => {}
_ => { _ => {
conn.desynchronized = true; conn.desynchronized = true;
return Err(Error::BadResponse); return Err(Error::IoError(bad_response()));
} }
} }
@ -2213,7 +2218,7 @@ impl<'a> CopyInStatement<'a> {
} }
_ => { _ => {
conn.desynchronized = true; conn.desynchronized = true;
return Err(Error::BadResponse); return Err(Error::IoError(bad_response()));
} }
}; };

View File

@ -22,6 +22,6 @@ macro_rules! bad_response {
($s:expr) => ({ ($s:expr) => ({
debug!("Bad response at {}:{}", file!(), line!()); debug!("Bad response at {}:{}", file!(), line!());
$s.desynchronized = true; $s.desynchronized = true;
return Err(::Error::BadResponse); return Err(::Error::IoError(::bad_response()));
}) })
} }

View File

@ -143,7 +143,7 @@ pub fn initialize_stream(params: &ConnectParams, ssl: &SslMode)
let host = match params.target { let host = match params.target {
ConnectTarget::Tcp(ref host) => host, ConnectTarget::Tcp(ref host) => host,
#[cfg(feature = "unix_socket")] #[cfg(feature = "unix_socket")]
ConnectTarget::Unix(_) => return Err(ConnectError::BadResponse) ConnectTarget::Unix(_) => return Err(ConnectError::IoError(::bad_response()))
}; };
match negotiator.negotiate_ssl(host, socket) { match negotiator.negotiate_ssl(host, socket) {

View File

@ -2,6 +2,7 @@
pub use self::slice::Slice; pub use self::slice::Slice;
use std::collections::HashMap; use std::collections::HashMap;
use std::error;
use std::fmt; use std::fmt;
use std::io::prelude::*; use std::io::prelude::*;
use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian}; use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian};
@ -514,6 +515,23 @@ impl Other {
} }
} }
/// An error indicating that a `NULL` Postgres value was passed to a `FromSql`
/// implementation that does not support `NULL` values.
#[derive(Debug, Clone, Copy)]
pub struct WasNull;
impl fmt::Display for WasNull {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(error::Error::description(self))
}
}
impl error::Error for WasNull {
fn description(&self) -> &str {
"a Postgres value was `NULL`"
}
}
/// A trait for types that can be created from a Postgres value. /// A trait for types that can be created from a Postgres value.
/// ///
/// # Types /// # Types
@ -566,13 +584,13 @@ pub trait FromSql: Sized {
/// is compatible with the Postgres `Type`. /// is compatible with the Postgres `Type`.
/// ///
/// The default implementation calls `FromSql::from_sql` when `raw` is /// The default implementation calls `FromSql::from_sql` when `raw` is
/// `Some` and returns `Err(Error::WasNull)` when `raw` is `None`. It does /// `Some` and returns `Err(Error::Conversion(Box::new(WasNull))` when
/// not typically need to be overridden. /// `raw` is `None`. It does not typically need to be overridden.
fn from_sql_nullable<R: Read>(ty: &Type, raw: Option<&mut R>, ctx: &SessionInfo) fn from_sql_nullable<R: Read>(ty: &Type, raw: Option<&mut R>, ctx: &SessionInfo)
-> Result<Self> { -> Result<Self> {
match raw { match raw {
Some(raw) => FromSql::from_sql(ty, raw, ctx), Some(raw) => FromSql::from_sql(ty, raw, ctx),
None => Err(Error::WasNull), None => Err(Error::Conversion(Box::new(WasNull))),
} }
} }
@ -628,7 +646,7 @@ impl FromSql for String {
fn from_sql<R: Read>(_: &Type, raw: &mut R, _: &SessionInfo) -> Result<String> { fn from_sql<R: Read>(_: &Type, raw: &mut R, _: &SessionInfo) -> Result<String> {
let mut buf = vec![]; let mut buf = vec![];
try!(raw.read_to_end(&mut buf)); try!(raw.read_to_end(&mut buf));
String::from_utf8(buf).map_err(|_| Error::BadResponse) String::from_utf8(buf).map_err(|err| Error::Conversion(Box::new(err)))
} }
fn accepts(ty: &Type) -> bool { fn accepts(ty: &Type) -> bool {
@ -680,7 +698,7 @@ impl FromSql for HashMap<String, Option<String>> {
try!(util::read_all(raw, &mut key)); try!(util::read_all(raw, &mut key));
let key = match String::from_utf8(key) { let key = match String::from_utf8(key) {
Ok(key) => key, Ok(key) => key,
Err(_) => return Err(Error::BadResponse), Err(err) => return Err(Error::Conversion(Box::new(err))),
}; };
let val_len = try!(raw.read_i32::<BigEndian>()); let val_len = try!(raw.read_i32::<BigEndian>());
@ -691,7 +709,7 @@ impl FromSql for HashMap<String, Option<String>> {
try!(util::read_all(raw, &mut val)); try!(util::read_all(raw, &mut val));
match String::from_utf8(val) { match String::from_utf8(val) {
Ok(val) => Some(val), Ok(val) => Some(val),
Err(_) => return Err(Error::BadResponse), Err(err) => return Err(Error::Conversion(Box::new(err))),
} }
}; };

View File

@ -1,4 +1,5 @@
use serialize::json; use serialize::json;
use std::error;
use std::io::prelude::*; use std::io::prelude::*;
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
@ -10,10 +11,11 @@ impl FromSql for json::Json {
if let Type::Jsonb = *ty { if let Type::Jsonb = *ty {
// We only support version 1 of the jsonb binary format // We only support version 1 of the jsonb binary format
if try!(raw.read_u8()) != 1 { if try!(raw.read_u8()) != 1 {
return Err(Error::BadResponse); let err: Box<error::Error+Sync+Send> = "unsupported JSONB encoding version".into();
return Err(Error::Conversion(err));
} }
} }
json::Json::from_reader(raw).map_err(|_| Error::BadResponse) json::Json::from_reader(raw).map_err(|err| Error::Conversion(Box::new(err)))
} }
accepts!(Type::Json, Type::Jsonb); accepts!(Type::Json, Type::Jsonb);

View File

@ -1,5 +1,6 @@
extern crate serde; extern crate serde;
use std::error;
use std::io::prelude::*; use std::io::prelude::*;
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use self::serde::json::{self, Value}; use self::serde::json::{self, Value};
@ -12,10 +13,11 @@ impl FromSql for Value {
if let Type::Jsonb = *ty { if let Type::Jsonb = *ty {
// We only support version 1 of the jsonb binary format // We only support version 1 of the jsonb binary format
if try!(raw.read_u8()) != 1 { if try!(raw.read_u8()) != 1 {
return Err(Error::BadResponse); let err: Box<error::Error+Sync+Send> = "unsupported JSONB encoding version".into();
return Err(Error::Conversion(err));
} }
} }
json::de::from_reader(raw).map_err(|_| Error::BadResponse) json::de::from_reader(raw).map_err(|err| Error::Conversion(Box::new(err)))
} }
accepts!(Type::Json, Type::Jsonb); accepts!(Type::Json, Type::Jsonb);

View File

@ -4,7 +4,6 @@ use std::io::prelude::*;
use self::uuid::Uuid; use self::uuid::Uuid;
use types::{FromSql, ToSql, Type, IsNull, SessionInfo}; use types::{FromSql, ToSql, Type, IsNull, SessionInfo};
use Error;
use Result; use Result;
use util; use util;
@ -12,10 +11,7 @@ impl FromSql for Uuid {
fn from_sql<R: Read>(_: &Type, raw: &mut R, _: &SessionInfo) -> Result<Uuid> { fn from_sql<R: Read>(_: &Type, raw: &mut R, _: &SessionInfo) -> Result<Uuid> {
let mut bytes = [0; 16]; let mut bytes = [0; 16];
try!(util::read_all(raw, &mut bytes)); try!(util::read_all(raw, &mut bytes));
match Uuid::from_bytes(&bytes) { Ok(Uuid::from_bytes(&bytes).unwrap())
Some(u) => Ok(u),
None => Err(Error::BadResponse),
}
} }
accepts!(Type::Uuid); accepts!(Type::Uuid);

View File

@ -507,7 +507,7 @@ fn test_get_was_null() {
let result = or_panic!(stmt.query(&[])); let result = or_panic!(stmt.query(&[]));
match result.iter().next().unwrap().get_opt::<usize, i32>(0) { match result.iter().next().unwrap().get_opt::<usize, i32>(0) {
Err(Error::WasNull) => {} Err(Error::Conversion(..)) => {}
res => panic!("unexpected result {:?}", res), res => panic!("unexpected result {:?}", res),
}; };
} }