use fallible_iterator::FallibleIterator;
use postgres_protocol::message::backend::ErrorResponseBody;
use crate::PgToPlError;
#[derive(Debug, Clone)]
pub struct PostgresError {
pub severity: Option<String>,
pub code: Option<String>,
pub message: String,
pub detail: Option<String>,
pub hint: Option<String>,
pub position: Option<String>,
pub internal_position: Option<String>,
pub internal_query: Option<String>,
pub where_: Option<String>,
pub schema: Option<String>,
pub table: Option<String>,
pub column: Option<String>,
pub datatype: Option<String>,
pub constraint: Option<String>,
pub file: Option<String>,
pub line: Option<String>,
pub routine: Option<String>,
}
impl PostgresError {
pub fn from_error_response(err: &ErrorResponseBody) -> Self {
let fields = err.fields().iterator();
let mut severity = None;
let mut code = None;
let mut message = None;
let mut detail = None;
let mut hint = None;
let mut 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;
let mut aggregate = "".to_string();
for field in fields {
if let Ok(f) = field {
let field_type = f.type_();
let value = String::from_utf8_lossy(f.value_bytes()).to_string();
aggregate.push_str("\n");
aggregate.push_str(&value);
match field_type {
b'S' => severity = Some(value), b'V' => severity = Some(value), b'C' => code = Some(value), b'M' => message = Some(value), b'D' => detail = Some(value), b'H' => hint = Some(value), b'P' => position = Some(value), b'p' => internal_position = Some(value), b'q' => internal_query = Some(value), b'W' => where_ = Some(value), b's' => schema = Some(value), b't' => table = Some(value), b'c' => column = Some(value), b'd' => datatype = Some(value), b'n' => constraint = Some(value), b'F' => file = Some(value), b'L' => line = Some(value), b'R' => routine = Some(value), _ => {}
}
}
}
PostgresError {
severity,
code,
message: message.unwrap_or(aggregate),
detail,
hint,
position,
internal_position,
internal_query,
where_,
schema,
table,
column,
datatype,
constraint,
file,
line,
routine,
}
}
pub fn to_compact_string(&self) -> String {
match (&self.code, &self.message) {
(Some(code), msg) => format!("[{}] {}", code, msg),
(None, msg) => format!("Unknown PostgreSQL error: {}", msg),
}
}
pub fn to_detailed_string(&self) -> String {
let mut parts = Vec::new();
if let Some(ref severity) = self.severity {
parts.push(format!("Severity: {}", severity));
}
if let Some(ref code) = self.code {
parts.push(format!("Code: {}", code));
}
parts.push(format!("Message: {}", self.message));
if let Some(ref detail) = self.detail {
parts.push(format!("Detail: {}", detail));
}
if let Some(ref hint) = self.hint {
parts.push(format!("Hint: {}", hint));
}
if let Some(ref position) = self.position {
parts.push(format!("Position: {}", position));
}
if let Some(ref where_) = self.where_ {
parts.push(format!("Where: {}", where_));
}
if let Some(ref schema) = self.schema {
parts.push(format!("Schema: {}", schema));
}
if let Some(ref table) = self.table {
parts.push(format!("Table: {}", table));
}
if let Some(ref column) = self.column {
parts.push(format!("Column: {}", column));
}
if let Some(ref constraint) = self.constraint {
parts.push(format!("Constraint: {}", constraint));
}
parts.join("\n")
}
pub fn is_code(&self, code: &str) -> bool {
self.code.as_deref() == Some(code)
}
pub fn is_duplicate_prepared_statement(&self) -> bool {
self.is_code("42P05")
}
pub fn is_integrity_constraint_violation(&self) -> bool {
self.code.as_ref().map_or(false, |c| c.starts_with("23"))
}
pub fn is_syntax_error(&self) -> bool {
self.is_code("42601")
}
pub fn is_fatal(&self) -> bool {
matches!(self.severity.as_deref(), Some("FATAL") | Some("PANIC"))
}
}
impl std::fmt::Display for PostgresError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_compact_string())
}
}
impl From<ErrorResponseBody> for PostgresError {
fn from(err: ErrorResponseBody) -> Self {
PostgresError::from_error_response(&err)
}
}
impl From<PostgresError> for PgToPlError {
fn from(err: PostgresError) -> Self {
PgToPlError::QueryError(err)
}
}
impl From<ErrorResponseBody> for PgToPlError {
fn from(err: ErrorResponseBody) -> Self {
PgToPlError::QueryError(err.into())
}
}