rust-postgres/postgres-shared/src/error/mod.rs
Steven Fackler 6a86f8dd85 Rustfmt
2017-06-30 17:35:17 -10:00

366 lines
12 KiB
Rust

use fallible_iterator::FallibleIterator;
use postgres_protocol::message::backend::ErrorFields;
use std::error;
use std::convert::From;
use std::fmt;
use std::io;
pub use self::sqlstate::SqlState;
mod sqlstate;
/// The severity of a Postgres error or notice.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Severity {
/// PANIC
Panic,
/// FATAL
Fatal,
/// ERROR
Error,
/// WARNING
Warning,
/// NOTICE
Notice,
/// DEBUG
Debug,
/// INFO
Info,
/// LOG
Log,
}
impl fmt::Display for Severity {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let s = match *self {
Severity::Panic => "PANIC",
Severity::Fatal => "FATAL",
Severity::Error => "ERROR",
Severity::Warning => "WARNING",
Severity::Notice => "NOTICE",
Severity::Debug => "DEBUG",
Severity::Info => "INFO",
Severity::Log => "LOG",
};
fmt.write_str(s)
}
}
impl Severity {
fn from_str(s: &str) -> Option<Severity> {
match s {
"PANIC" => Some(Severity::Panic),
"FATAL" => Some(Severity::Fatal),
"ERROR" => Some(Severity::Error),
"WARNING" => Some(Severity::Warning),
"NOTICE" => Some(Severity::Notice),
"DEBUG" => Some(Severity::Debug),
"INFO" => Some(Severity::Info),
"LOG" => Some(Severity::Log),
_ => None,
}
}
}
/// A Postgres error or notice.
#[derive(Clone, PartialEq, Eq)]
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,
/// A parsed, nonlocalized version of `severity`. (PostgreSQL 9.6+)
pub parsed_severity: Option<Severity>,
/// 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<String>,
/// 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<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: Option<String>,
/// The line number of the source-code location where the error was
/// reported.
pub line: Option<u32>,
/// The name of the source-code routine reporting the error.
pub routine: Option<String>,
_p: (),
}
impl DbError {
#[doc(hidden)]
pub fn new(fields: &mut ErrorFields) -> io::Result<DbError> {
let mut severity = None;
let mut parsed_severity = None;
let mut code = None;
let mut message = None;
let mut detail = None;
let mut hint = None;
let mut normal_position = None;
let mut internal_position = None;
let mut internal_query = None;
let mut where_ = None;
let mut schema = None;
let mut table = None;
let mut column = None;
let mut datatype = None;
let mut constraint = None;
let mut file = None;
let mut line = None;
let mut routine = None;
while let Some(field) = fields.next()? {
match field.type_() {
b'S' => severity = Some(field.value().to_owned()),
b'C' => code = Some(SqlState::from_code(field.value())),
b'M' => message = Some(field.value().to_owned()),
b'D' => detail = Some(field.value().to_owned()),
b'H' => hint = Some(field.value().to_owned()),
b'P' => {
normal_position = Some(field.value().parse::<u32>().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"`P` field did not contain an integer",
)
})?);
}
b'p' => {
internal_position = Some(field.value().parse::<u32>().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"`p` field did not contain an integer",
)
})?);
}
b'q' => internal_query = Some(field.value().to_owned()),
b'W' => where_ = Some(field.value().to_owned()),
b's' => schema = Some(field.value().to_owned()),
b't' => table = Some(field.value().to_owned()),
b'c' => column = Some(field.value().to_owned()),
b'd' => datatype = Some(field.value().to_owned()),
b'n' => constraint = Some(field.value().to_owned()),
b'F' => file = Some(field.value().to_owned()),
b'L' => {
line = Some(field.value().parse::<u32>().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"`L` field did not contain an integer",
)
})?);
}
b'R' => routine = Some(field.value().to_owned()),
b'V' => {
parsed_severity = Some(Severity::from_str(field.value()).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"`V` field contained an invalid value",
)
})?);
}
_ => {}
}
}
Ok(DbError {
severity: severity.ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "`S` field missing")
})?,
parsed_severity: parsed_severity,
code: code.ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "`C` field missing")
})?,
message: message.ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "`M` field missing")
})?,
detail: detail,
hint: hint,
position: match normal_position {
Some(position) => Some(ErrorPosition::Normal(position)),
None => {
match internal_position {
Some(position) => {
Some(ErrorPosition::Internal {
position: position,
query: internal_query.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"`q` field missing but `p` field present",
)
})?,
})
}
None => None,
}
}
},
where_: where_,
schema: schema,
table: table,
column: column,
datatype: datatype,
constraint: constraint,
file: file,
line: line,
routine: routine,
_p: (),
})
}
}
// manual impl to leave out _p
impl fmt::Debug for DbError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("DbError")
.field("severity", &self.severity)
.field("parsed_severity", &self.parsed_severity)
.field("code", &self.code)
.field("message", &self.message)
.field("detail", &self.detail)
.field("hint", &self.hint)
.field("position", &self.position)
.field("where_", &self.where_)
.field("schema", &self.schema)
.field("table", &self.table)
.field("column", &self.column)
.field("datatype", &self.datatype)
.field("constraint", &self.constraint)
.field("file", &self.file)
.field("line", &self.line)
.field("routine", &self.routine)
.finish()
}
}
impl fmt::Display for DbError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}: {}", self.severity, self.message)
}
}
impl error::Error for DbError {
fn description(&self) -> &str {
&self.message
}
}
/// Represents the position of an error in a query.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ErrorPosition {
/// A position in the original query.
Normal(u32),
/// A position in an internally generated query.
Internal {
/// The byte position.
position: u32,
/// A query generated by the Postgres server.
query: String,
},
}
/// Reasons a new Postgres connection could fail.
#[derive(Debug)]
pub enum ConnectError {
/// An error relating to connection parameters.
ConnectParams(Box<error::Error + Sync + Send>),
/// An error from the Postgres server itself.
Db(Box<DbError>),
/// An error initializing the TLS session.
Tls(Box<error::Error + Sync + Send>),
/// An error communicating with the server.
Io(io::Error),
}
impl fmt::Display for ConnectError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(error::Error::description(self))?;
match *self {
ConnectError::ConnectParams(ref msg) => write!(fmt, ": {}", msg),
ConnectError::Db(ref err) => write!(fmt, ": {}", err),
ConnectError::Tls(ref err) => write!(fmt, ": {}", err),
ConnectError::Io(ref err) => write!(fmt, ": {}", err),
}
}
}
impl error::Error for ConnectError {
fn description(&self) -> &str {
match *self {
ConnectError::ConnectParams(_) => "Invalid connection parameters",
ConnectError::Db(_) => "Error reported by Postgres",
ConnectError::Tls(_) => "Error initiating SSL session",
ConnectError::Io(_) => "Error communicating with the server",
}
}
fn cause(&self) -> Option<&error::Error> {
match *self {
ConnectError::ConnectParams(ref err) |
ConnectError::Tls(ref err) => Some(&**err),
ConnectError::Db(ref err) => Some(&**err),
ConnectError::Io(ref err) => Some(err),
}
}
}
impl From<io::Error> for ConnectError {
fn from(err: io::Error) -> ConnectError {
ConnectError::Io(err)
}
}
impl From<DbError> for ConnectError {
fn from(err: DbError) -> ConnectError {
ConnectError::Db(Box::new(err))
}
}