diff --git a/src/error.rs b/src/error.rs index 35ed5f3d..55992f11 100644 --- a/src/error.rs +++ b/src/error.rs @@ -466,60 +466,24 @@ pub enum ErrorPosition { } } -/// Encapsulates a Postgres error or notice. +/// A Postgres error or notice. #[derive(Clone, PartialEq, Eq, Debug)] pub struct DbError { - /// 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 severity: String, - /// The SQLSTATE code for the error. - pub code: SqlState, - /// The primary human-readable error message. This should be accurate but - /// terse (typically one line). - pub message: String, - /// An optional secondary error message carrying more detail about the - /// problem. Might run to multiple lines. - pub detail: Option, - /// 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 hint: Option, - /// An optional error cursor position into either the original query string - /// or an internally generated query. - pub position: Option, - /// 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, - /// 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, - /// 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, - /// 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, - /// 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, - /// 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, - /// 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 + severity: String, + code: SqlState, + message: String, + detail: Option, + hint: Option, + position: Option, + where_: Option, + schema: Option, + table: Option, + column: Option, + datatype: Option, + constraint: Option, + file: String, + line: u32, + routine: String } impl DbError { @@ -569,6 +533,102 @@ impl DbError { 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 { diff --git a/src/lib.rs b/src/lib.rs index 41bf336e..0a8133b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -211,7 +211,7 @@ pub struct DefaultNoticeHandler; impl NoticeHandler for DefaultNoticeHandler { 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(()), Err(Error::IoError(e)) => return Err(ConnectError::IoError(e)), // 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)), _ => unreachable!() } diff --git a/tests/test.rs b/tests/test.rs index 2da78113..4688271c 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -56,7 +56,7 @@ fn test_url_terminating_slash() { fn test_prepare_err() { let conn = or_panic!(Connection::connect("postgres://postgres@localhost", &SslMode::None)); 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), _ => panic!("Unexpected result"), } @@ -65,7 +65,7 @@ fn test_prepare_err() { #[test] fn test_unknown_database() { 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), _ => panic!("Unexpected result"), } @@ -336,7 +336,7 @@ fn test_batch_execute_error() { conn.batch_execute(query).err().unwrap(); 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), _ => panic!("unexpected success"), } @@ -379,7 +379,7 @@ FROM (SELECT gs.i ORDER BY gs.i LIMIT 2) ss")); match stmt.query(&[]) { - Err(Error::DbError(DbError { code: CardinalityViolation, .. })) => {} + Err(Error::DbError(ref e)) if e.code() == &CardinalityViolation => {} Err(err) => panic!("Unexpected error {:?}", err), Ok(_) => panic!("Expected failure"), } @@ -526,7 +526,7 @@ fn test_custom_notice_handler() { impl NoticeHandler for Handler { fn handle(&mut self, notice: DbError) { - assert_eq!("note", &*notice.message); + assert_eq!("note", notice.message()); unsafe { count += 1; } } } @@ -657,7 +657,7 @@ fn test_cancel_query() { }); 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), _ => panic!("Unexpected result"), } @@ -698,7 +698,7 @@ fn test_plaintext_pass_no_pass() { fn test_plaintext_pass_wrong_pass() { let ret = Connection::connect("postgres://pass_user:asdf@localhost/postgres", &SslMode::None); match ret { - Err(ConnectError::DbError(DbError { code: InvalidPassword, .. })) => (), + Err(ConnectError::DbError(ref e)) if e.code() == &InvalidPassword => {} Err(err) => panic!("Unexpected error {:?}", err), _ => panic!("Expected error") } @@ -723,7 +723,7 @@ fn test_md5_pass_no_pass() { fn test_md5_pass_wrong_pass() { let ret = Connection::connect("postgres://md5_user:asdf@localhost/postgres", &SslMode::None); match ret { - Err(ConnectError::DbError(DbError { code: InvalidPassword, .. })) => (), + Err(ConnectError::DbError(ref e)) if e.code() == &InvalidPassword => {} Err(err) => panic!("Unexpected error {:?}", err), _ => panic!("Expected error") } @@ -735,12 +735,12 @@ fn test_execute_copy_from_err() { or_panic!(conn.execute("CREATE TEMPORARY TABLE foo (id INT)", &[])); let stmt = or_panic!(conn.prepare("COPY foo (id) FROM STDIN")); 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), _ => panic!("Expected error"), } 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), _ => panic!("Expected error"), } @@ -779,7 +779,7 @@ fn test_copy_in_bad_column_count() { let res = stmt.execute(data); 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), _ => panic!("Expected error"), } @@ -794,7 +794,7 @@ fn test_copy_in_bad_column_count() { let res = stmt.execute(data); 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), _ => panic!("Expected error"), } @@ -818,7 +818,7 @@ fn test_copy_in_bad_type() { let res = stmt.execute(data); 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), _ => 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)); or_panic!(conn.execute("CREATE TEMPORARY TABLE foo (id INT)", &[])); 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), _ => panic!("Expected error"), }