Encapsulate DbError

This commit is contained in:
Steven Fackler 2015-02-05 22:24:57 -08:00
parent 219ce2a82c
commit 7cfde75dce
3 changed files with 128 additions and 68 deletions

View File

@ -466,60 +466,24 @@ pub enum ErrorPosition {
} }
} }
/// Encapsulates a Postgres error or notice. /// A Postgres error or notice.
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub struct DbError { pub struct DbError {
/// The field contents are ERROR, FATAL, or PANIC (in an error message), severity: String,
/// or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message), or a code: SqlState,
/// localized translation of one of these. message: String,
pub severity: String, detail: Option<String>,
/// The SQLSTATE code for the error. hint: Option<String>,
pub code: SqlState, position: Option<ErrorPosition>,
/// The primary human-readable error message. This should be accurate but where_: Option<String>,
/// terse (typically one line). schema: Option<String>,
pub message: String, table: Option<String>,
/// An optional secondary error message carrying more detail about the column: Option<String>,
/// problem. Might run to multiple lines. datatype: Option<String>,
pub detail: Option<String>, constraint: Option<String>,
/// An optional suggestion what to do about the problem. This is intended file: String,
/// to differ from Detail in that it offers advice (potentially line: u32,
/// inappropriate) rather than hard facts. Might run to multiple lines. routine: String
pub hint: Option<String>,
/// An optional error cursor position into either the original query string
/// or an internally generated query.
pub position: Option<ErrorPosition>,
/// An indication of the context in which the error occurred. Presently
/// this includes a call stack traceback of active procedural language
/// functions and internally-generated queries. The trace is one entry per
/// line, most recent first.
pub where_: Option<String>,
/// If the error was associated with a specific database object, the name
/// of the schema containing that object, if any. (PostgreSQL 9.3+)
pub schema: Option<String>,
/// If the error was associated with a specific table, the name of the
/// table. (Refer to the schema name field for the name of the table's
/// schema.) (PostgreSQL 9.3+)
pub table: Option<String>,
/// If the error was associated with a specific table column, the name of
/// the column. (Refer to the schema and table name fields to identify the
/// table.) (PostgreSQL 9.3+)
pub column: Option<String>,
/// If the error was associated with a specific data type, the name of the
/// data type. (Refer to the schema name field for the name of the data
/// type's schema.) (PostgreSQL 9.3+)
pub datatype: Option<String>,
/// If the error was associated with a specific constraint, the name of the
/// constraint. Refer to fields listed above for the associated table or
/// domain. (For this purpose, indexes are treated as constraints, even if
/// they weren't created with constraint syntax.) (PostgreSQL 9.3+)
pub constraint: Option<String>,
/// The file name of the source-code location where the error was reported.
pub file: String,
/// The line number of the source-code location where the error was
/// reported.
pub line: usize,
/// The name of the source-code routine reporting the error.
pub routine: String
} }
impl DbError { impl DbError {
@ -569,6 +533,102 @@ impl DbError {
Err(()) => Err(Error::BadData), Err(()) => Err(Error::BadData),
} }
} }
/// The field contents are ERROR, FATAL, or PANIC (in an error message),
/// or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message), or a
/// localized translation of one of these.
pub fn severity(&self) -> &str {
&self.severity
}
/// The SQLSTATE code for the error.
pub fn code(&self) -> &SqlState {
&self.code
}
/// The primary human-readable error message. This should be accurate but
/// terse (typically one line).
pub fn message(&self) -> &str {
&self.message
}
/// An optional secondary error message carrying more detail about the
/// problem. Might run to multiple lines.
pub fn detail(&self) -> Option<&str> {
self.detail.as_ref().map(|s| &**s)
}
/// An optional suggestion what to do about the problem. This is intended
/// to differ from Detail in that it offers advice (potentially
/// inappropriate) rather than hard facts. Might run to multiple lines.
pub fn hint(&self) -> Option<&str> {
self.hint.as_ref().map(|s| &**s)
}
/// An optional error cursor position into either the original query string
/// or an internally generated query.
pub fn position(&self) -> Option<&ErrorPosition> {
self.position.as_ref()
}
/// An indication of the context in which the error occurred. Presently
/// this includes a call stack traceback of active procedural language
/// functions and internally-generated queries. The trace is one entry per
/// line, most recent first.
pub fn where_(&self) -> Option<&str> {
self.where_.as_ref().map(|s| &**s)
}
/// If the error was associated with a specific database object, the name
/// of the schema containing that object, if any. (PostgreSQL 9.3+)
pub fn schema(&self) -> Option<&str> {
self.schema.as_ref().map(|s| &**s)
}
/// If the error was associated with a specific table, the name of the
/// table. (Refer to the schema name field for the name of the table's
/// schema.) (PostgreSQL 9.3+)
pub fn table(&self) -> Option<&str> {
self.table.as_ref().map(|s| &**s)
}
/// If the error was associated with a specific table column, the name of
/// the column. (Refer to the schema and table name fields to identify the
/// table.) (PostgreSQL 9.3+)
pub fn column(&self) -> Option<&str> {
self.column.as_ref().map(|s| &**s)
}
/// If the error was associated with a specific data type, the name of the
/// data type. (Refer to the schema name field for the name of the data
/// type's schema.) (PostgreSQL 9.3+)
pub fn datatype(&self) -> Option<&str> {
self.datatype.as_ref().map(|s| &**s)
}
/// If the error was associated with a specific constraint, the name of the
/// constraint. Refer to fields listed above for the associated table or
/// domain. (For this purpose, indexes are treated as constraints, even if
/// they weren't created with constraint syntax.) (PostgreSQL 9.3+)
pub fn constraint(&self) -> Option<&str> {
self.constraint.as_ref().map(|s| &**s)
}
/// The file name of the source-code location where the error was reported.
pub fn file(&self) -> &str {
&self.file
}
/// The line number of the source-code location where the error was
/// reported.
pub fn line(&self) -> u32 {
self.line
}
/// The name of the source-code routine reporting the error.
pub fn routine(&self) -> &str {
&self.routine
}
} }
impl fmt::Display for DbError { impl fmt::Display for DbError {

View File

@ -211,7 +211,7 @@ pub struct DefaultNoticeHandler;
impl NoticeHandler for DefaultNoticeHandler { impl NoticeHandler for DefaultNoticeHandler {
fn handle(&mut self, notice: DbError) { fn handle(&mut self, notice: DbError) {
info!("{}: {}", notice.severity, notice.message); info!("{}: {}", notice.severity(), notice.message());
} }
} }
@ -477,7 +477,7 @@ impl InnerConnection {
Ok(..) => return Ok(()), Ok(..) => return Ok(()),
Err(Error::IoError(e)) => return Err(ConnectError::IoError(e)), Err(Error::IoError(e)) => return Err(ConnectError::IoError(e)),
// Range types weren't added until Postgres 9.2, so pg_range may not exist // Range types weren't added until Postgres 9.2, so pg_range may not exist
Err(Error::DbError(DbError { code: SqlState::UndefinedTable, .. })) => {} Err(Error::DbError(ref e)) if e.code() == &SqlState::UndefinedTable => {}
Err(Error::DbError(e)) => return Err(ConnectError::DbError(e)), Err(Error::DbError(e)) => return Err(ConnectError::DbError(e)),
_ => unreachable!() _ => unreachable!()
} }

View File

@ -56,7 +56,7 @@ fn test_url_terminating_slash() {
fn test_prepare_err() { fn test_prepare_err() {
let conn = or_panic!(Connection::connect("postgres://postgres@localhost", &SslMode::None)); let conn = or_panic!(Connection::connect("postgres://postgres@localhost", &SslMode::None));
match conn.prepare("invalid sql statment") { match conn.prepare("invalid sql statment") {
Err(Error::DbError(DbError { code: SyntaxError, position: Some(Normal(1)), .. })) => (), Err(Error::DbError(ref e)) if e.code() == &SyntaxError && e.position() == Some(&Normal(1)) => {}
Err(e) => panic!("Unexpected result {:?}", e), Err(e) => panic!("Unexpected result {:?}", e),
_ => panic!("Unexpected result"), _ => panic!("Unexpected result"),
} }
@ -65,7 +65,7 @@ fn test_prepare_err() {
#[test] #[test]
fn test_unknown_database() { fn test_unknown_database() {
match Connection::connect("postgres://postgres@localhost/asdf", &SslMode::None) { match Connection::connect("postgres://postgres@localhost/asdf", &SslMode::None) {
Err(ConnectError::DbError(DbError { code: InvalidCatalogName, .. })) => {} Err(ConnectError::DbError(ref e)) if e.code() == &InvalidCatalogName => {}
Err(resp) => panic!("Unexpected result {:?}", resp), Err(resp) => panic!("Unexpected result {:?}", resp),
_ => panic!("Unexpected result"), _ => panic!("Unexpected result"),
} }
@ -336,7 +336,7 @@ fn test_batch_execute_error() {
conn.batch_execute(query).err().unwrap(); conn.batch_execute(query).err().unwrap();
match conn.prepare("SELECT * from foo ORDER BY id") { match conn.prepare("SELECT * from foo ORDER BY id") {
Err(Error::DbError(DbError { code: UndefinedTable, .. })) => {}, Err(Error::DbError(ref e)) if e.code() == &UndefinedTable => {}
Err(e) => panic!("unexpected error {:?}", e), Err(e) => panic!("unexpected error {:?}", e),
_ => panic!("unexpected success"), _ => panic!("unexpected success"),
} }
@ -379,7 +379,7 @@ FROM (SELECT gs.i
ORDER BY gs.i ORDER BY gs.i
LIMIT 2) ss")); LIMIT 2) ss"));
match stmt.query(&[]) { match stmt.query(&[]) {
Err(Error::DbError(DbError { code: CardinalityViolation, .. })) => {} Err(Error::DbError(ref e)) if e.code() == &CardinalityViolation => {}
Err(err) => panic!("Unexpected error {:?}", err), Err(err) => panic!("Unexpected error {:?}", err),
Ok(_) => panic!("Expected failure"), Ok(_) => panic!("Expected failure"),
} }
@ -526,7 +526,7 @@ fn test_custom_notice_handler() {
impl NoticeHandler for Handler { impl NoticeHandler for Handler {
fn handle(&mut self, notice: DbError) { fn handle(&mut self, notice: DbError) {
assert_eq!("note", &*notice.message); assert_eq!("note", notice.message());
unsafe { count += 1; } unsafe { count += 1; }
} }
} }
@ -657,7 +657,7 @@ fn test_cancel_query() {
}); });
match conn.execute("SELECT pg_sleep(10)", &[]) { match conn.execute("SELECT pg_sleep(10)", &[]) {
Err(Error::DbError(DbError { code: QueryCanceled, .. })) => {} Err(Error::DbError(ref e)) if e.code() == &QueryCanceled => {}
Err(res) => panic!("Unexpected result {:?}", res), Err(res) => panic!("Unexpected result {:?}", res),
_ => panic!("Unexpected result"), _ => panic!("Unexpected result"),
} }
@ -698,7 +698,7 @@ fn test_plaintext_pass_no_pass() {
fn test_plaintext_pass_wrong_pass() { fn test_plaintext_pass_wrong_pass() {
let ret = Connection::connect("postgres://pass_user:asdf@localhost/postgres", &SslMode::None); let ret = Connection::connect("postgres://pass_user:asdf@localhost/postgres", &SslMode::None);
match ret { match ret {
Err(ConnectError::DbError(DbError { code: InvalidPassword, .. })) => (), Err(ConnectError::DbError(ref e)) if e.code() == &InvalidPassword => {}
Err(err) => panic!("Unexpected error {:?}", err), Err(err) => panic!("Unexpected error {:?}", err),
_ => panic!("Expected error") _ => panic!("Expected error")
} }
@ -723,7 +723,7 @@ fn test_md5_pass_no_pass() {
fn test_md5_pass_wrong_pass() { fn test_md5_pass_wrong_pass() {
let ret = Connection::connect("postgres://md5_user:asdf@localhost/postgres", &SslMode::None); let ret = Connection::connect("postgres://md5_user:asdf@localhost/postgres", &SslMode::None);
match ret { match ret {
Err(ConnectError::DbError(DbError { code: InvalidPassword, .. })) => (), Err(ConnectError::DbError(ref e)) if e.code() == &InvalidPassword => {}
Err(err) => panic!("Unexpected error {:?}", err), Err(err) => panic!("Unexpected error {:?}", err),
_ => panic!("Expected error") _ => panic!("Expected error")
} }
@ -735,12 +735,12 @@ fn test_execute_copy_from_err() {
or_panic!(conn.execute("CREATE TEMPORARY TABLE foo (id INT)", &[])); or_panic!(conn.execute("CREATE TEMPORARY TABLE foo (id INT)", &[]));
let stmt = or_panic!(conn.prepare("COPY foo (id) FROM STDIN")); let stmt = or_panic!(conn.prepare("COPY foo (id) FROM STDIN"));
match stmt.execute(&[]) { match stmt.execute(&[]) {
Err(Error::DbError(ref err)) if err.message[].contains("COPY") => {} Err(Error::DbError(ref err)) if err.message().contains("COPY") => {}
Err(err) => panic!("Unexptected error {:?}", err), Err(err) => panic!("Unexptected error {:?}", err),
_ => panic!("Expected error"), _ => panic!("Expected error"),
} }
match stmt.query(&[]) { match stmt.query(&[]) {
Err(Error::DbError(ref err)) if err.message[].contains("COPY") => {} Err(Error::DbError(ref err)) if err.message().contains("COPY") => {}
Err(err) => panic!("Unexptected error {:?}", err), Err(err) => panic!("Unexptected error {:?}", err),
_ => panic!("Expected error"), _ => panic!("Expected error"),
} }
@ -779,7 +779,7 @@ fn test_copy_in_bad_column_count() {
let res = stmt.execute(data); let res = stmt.execute(data);
match res { match res {
Err(Error::DbError(ref err)) if err.message[].contains("Invalid column count") => {} Err(Error::DbError(ref err)) if err.message().contains("Invalid column count") => {}
Err(err) => panic!("unexpected error {:?}", err), Err(err) => panic!("unexpected error {:?}", err),
_ => panic!("Expected error"), _ => panic!("Expected error"),
} }
@ -794,7 +794,7 @@ fn test_copy_in_bad_column_count() {
let res = stmt.execute(data); let res = stmt.execute(data);
match res { match res {
Err(Error::DbError(ref err)) if err.message[].contains("Invalid column count") => {} Err(Error::DbError(ref err)) if err.message().contains("Invalid column count") => {}
Err(err) => panic!("unexpected error {:?}", err), Err(err) => panic!("unexpected error {:?}", err),
_ => panic!("Expected error"), _ => panic!("Expected error"),
} }
@ -818,7 +818,7 @@ fn test_copy_in_bad_type() {
let res = stmt.execute(data); let res = stmt.execute(data);
match res { match res {
Err(Error::DbError(ref err)) if err.message[].contains("saw type Varchar") => {} Err(Error::DbError(ref err)) if err.message().contains("saw type Varchar") => {}
Err(err) => panic!("unexpected error {:?}", err), Err(err) => panic!("unexpected error {:?}", err),
_ => panic!("Expected error"), _ => panic!("Expected error"),
} }
@ -831,7 +831,7 @@ fn test_batch_execute_copy_from_err() {
let conn = or_panic!(Connection::connect("postgres://postgres@localhost", &SslMode::None)); let conn = or_panic!(Connection::connect("postgres://postgres@localhost", &SslMode::None));
or_panic!(conn.execute("CREATE TEMPORARY TABLE foo (id INT)", &[])); or_panic!(conn.execute("CREATE TEMPORARY TABLE foo (id INT)", &[]));
match conn.batch_execute("COPY foo (id) FROM STDIN") { match conn.batch_execute("COPY foo (id) FROM STDIN") {
Err(Error::DbError(ref err)) if err.message[].contains("COPY") => {} Err(Error::DbError(ref err)) if err.message().contains("COPY") => {}
Err(err) => panic!("Unexptected error {:?}", err), Err(err) => panic!("Unexptected error {:?}", err),
_ => panic!("Expected error"), _ => panic!("Expected error"),
} }