use crate::{
codec::FromRadix10,
database::client::postgres::{PostgresError, SqlState},
misc::{Usize, into_rslt, str_split1, usize_range_from_u32_range},
};
use alloc::boxed::Box;
use core::{
fmt::{Debug, Formatter},
ops::Range,
};
#[derive(Debug, Eq, PartialEq)]
pub enum ErrorPosition {
Internal {
position: u32,
query: Range<u32>,
},
Original(u32),
}
create_enum! {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Severity<u8> {
Debug = (0, "DEBUG"),
Error = (1, "ERROR"),
Fatal = (2, "FATAL"),
Info = (3, "INFO"),
Log = (4, "LOG"),
Notice = (5, "NOTICE"),
Panic = (6, "PANIC"),
Warning = (7, "WARNING"),
}
}
#[derive(Eq, PartialEq)]
pub struct DbError {
buffer: Box<str>,
code: SqlState,
column: Option<Range<u32>>,
constraint: Option<Range<u32>>,
datatype: Option<Range<u32>>,
detail: Option<Range<u32>>,
file: Option<Range<u32>>,
hint: Option<Range<u32>>,
line: Option<u32>,
message: Range<u32>,
position: Option<ErrorPosition>,
routine: Option<Range<u32>>,
scheme: Option<Range<u32>>,
severity_localized: Range<u32>,
severity_nonlocalized: Option<Severity>,
table: Option<Range<u32>>,
r#where: Option<Range<u32>>,
}
impl DbError {
pub const fn code(&self) -> &SqlState {
&self.code
}
pub fn column(&self) -> Option<&str> {
self
.column
.as_ref()
.and_then(|range| self.buffer.get(usize_range_from_u32_range(range.clone())))
}
pub fn constraint(&self) -> Option<&str> {
self
.constraint
.as_ref()
.and_then(|range| self.buffer.get(usize_range_from_u32_range(range.clone())))
}
pub fn datatype(&self) -> Option<&str> {
self
.datatype
.as_ref()
.and_then(|range| self.buffer.get(usize_range_from_u32_range(range.clone())))
}
pub fn detail(&self) -> Option<&str> {
self
.detail
.as_ref()
.and_then(|range| self.buffer.get(usize_range_from_u32_range(range.clone())))
}
pub fn file(&self) -> Option<&str> {
self.file.as_ref().and_then(|range| self.buffer.get(usize_range_from_u32_range(range.clone())))
}
pub fn hint(&self) -> Option<&str> {
self.hint.as_ref().and_then(|range| self.buffer.get(usize_range_from_u32_range(range.clone())))
}
pub const fn line(&self) -> Option<u32> {
self.line
}
pub fn message(&self) -> &str {
self.buffer.get(usize_range_from_u32_range(self.message.clone())).unwrap_or_default()
}
pub const fn position(&self) -> Option<&ErrorPosition> {
self.position.as_ref()
}
pub fn routine(&self) -> Option<&str> {
self
.routine
.as_ref()
.and_then(|range| self.buffer.get(usize_range_from_u32_range(range.clone())))
}
pub fn scheme(&self) -> Option<&str> {
self
.scheme
.as_ref()
.and_then(|range| self.buffer.get(usize_range_from_u32_range(range.clone())))
}
pub fn severity_localized(&self) -> &str {
self.buffer.get(usize_range_from_u32_range(self.severity_localized.clone())).unwrap_or_default()
}
pub const fn severity_nonlocalized(&self) -> Option<Severity> {
self.severity_nonlocalized
}
pub fn table(&self) -> Option<&str> {
self.table.as_ref().and_then(|range| self.buffer.get(usize_range_from_u32_range(range.clone())))
}
pub fn r#where(&self) -> Option<&str> {
self
.r#where
.as_ref()
.and_then(|range| self.buffer.get(usize_range_from_u32_range(range.clone())))
}
}
impl Debug for DbError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DbError")
.field("code", &self.code())
.field("column", &self.column())
.field("constraint", &self.constraint())
.field("datatype", &self.datatype())
.field("detail", &self.detail())
.field("file", &self.file())
.field("hint", &self.hint())
.field("line", &self.line())
.field("message", &self.message())
.field("position", &self.position())
.field("routine", &self.routine())
.field("schema", &self.scheme())
.field("severity_localized", &self.severity_localized())
.field("severity_nonlocalized", &self.severity_nonlocalized())
.field("table", &self.table())
.field("where", &self.r#where())
.finish()
}
}
impl TryFrom<&str> for DbError {
type Error = crate::Error;
#[inline]
fn try_from(from: &str) -> Result<Self, Self::Error> {
let mut code = None;
let mut column = None;
let mut constraint = None;
let mut datatype = None;
let mut detail = None;
let mut file = None;
let mut hint = None;
let mut internal_position = None;
let mut internal_query = None;
let mut line = None;
let mut message = None;
let mut normal_position = None;
let mut routine = None;
let mut schema = None;
let mut severity_localized = None;
let mut severity_nonlocalized = None;
let mut table = None;
let mut r#where = None;
let mut idx: u32 = 0;
while let Some(curr) = from.get(*Usize::from(idx)..) {
let Some((ty, rest)) = curr.split_at_checked(1) else {
break;
};
idx = idx.wrapping_add(1);
if ty == "\0" {
if rest.is_empty() {
break;
}
return Err(crate::Error::UnexpectedString { length: rest.len() });
}
let Some(data) = str_split1(rest, b'\0').next() else {
return Err(PostgresError::InsufficientDbErrorBytes.into());
};
let begin = idx;
let (end, new_idx) = u32::try_from(data.len())
.ok()
.and_then(|data_len_u32| {
let end = idx.checked_add(data_len_u32)?;
let new_idx = end.checked_add(1)?;
Some((end, new_idx))
})
.unwrap_or((u32::MAX, u32::MAX));
let range = begin..end;
idx = new_idx;
match ty {
"C" => code = Some(SqlState::try_from(data)?),
"D" => detail = Some(range),
"H" => hint = Some(range),
"L" => line = Some(u32::from_radix_10(data.as_bytes())?),
"M" => message = Some(range),
"P" => normal_position = Some(u32::from_radix_10(data.as_bytes())?),
"R" => routine = Some(range),
"S" => severity_localized = Some(range),
"V" => severity_nonlocalized = Some(Severity::try_from(data)?),
"W" => r#where = Some(range),
"c" => column = Some(range),
"d" => datatype = Some(range),
"F" => file = Some(range),
"n" => constraint = Some(range),
"p" => internal_position = Some(u32::from_radix_10(data.as_bytes())?),
"q" => internal_query = Some(range),
"s" => schema = Some(range),
"t" => table = Some(range),
_ => {
return Err(crate::Error::UnexpectedUint {
received: u64::from_radix_10(ty.as_bytes())?,
});
}
}
}
Ok(Self {
buffer: from.get(..*Usize::from(idx)).unwrap_or_default().into(),
code: into_rslt(code)?,
column,
constraint,
datatype,
detail,
file,
hint,
line,
message: into_rslt(message)?,
severity_localized: into_rslt(severity_localized)?,
severity_nonlocalized,
position: match normal_position {
None => match internal_position {
Some(position) => {
Some(ErrorPosition::Internal { position, query: into_rslt(internal_query)? })
}
None => None,
},
Some(position) => Some(ErrorPosition::Original(position)),
},
routine,
scheme: schema,
table,
r#where,
})
}
}