use crate::protocol::message::backend::NoticeResponseBody;
use tracing::trace;
#[derive(Debug, Clone)]
pub struct Notice {
severity: Option<String>,
code: Option<String>,
message: String,
detail: Option<String>,
hint: Option<String>,
position: Option<i32>,
}
impl Notice {
#[inline]
#[must_use]
pub fn severity(&self) -> Option<&str> {
self.severity.as_deref()
}
#[inline]
#[must_use]
pub fn code(&self) -> Option<&str> {
self.code.as_deref()
}
#[inline]
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[inline]
#[must_use]
pub fn detail(&self) -> Option<&str> {
self.detail.as_deref()
}
#[inline]
#[must_use]
pub fn hint(&self) -> Option<&str> {
self.hint.as_deref()
}
#[inline]
#[must_use]
pub fn position(&self) -> Option<i32> {
self.position
}
}
impl Notice {
pub(crate) fn from_response_body(body: &NoticeResponseBody) -> Self {
let mut severity = None;
let mut code = None;
let mut message = String::new();
let mut detail = None;
let mut hint = None;
let mut position = None;
for field in body.fields().filter_map(|r| {
r.map_err(|e| trace!(target: "hyperdb_api_core::client", error = %e, "dropped error parsing notice field")).ok()
}) {
match (field.type_(), field.value()) {
(b'S', Ok(v)) => severity = Some(v.to_string()),
(b'V', Ok(v)) if severity.is_none() => severity = Some(v.to_string()),
(b'C', Ok(v)) => code = Some(v.to_string()),
(b'M', Ok(v)) => message = v.to_string(),
(b'D', Ok(v)) => detail = Some(v.to_string()),
(b'H', Ok(v)) => hint = Some(v.to_string()),
(b'P', Ok(v)) => position = v.parse().ok(),
_ => {}
}
}
Notice {
severity,
code,
message,
detail,
hint,
position,
}
}
#[inline]
#[must_use]
pub fn is_warning(&self) -> bool {
matches!(self.severity(), Some("WARNING" | "WARN"))
}
#[inline]
#[must_use]
pub fn is_info(&self) -> bool {
matches!(self.severity(), Some("NOTICE" | "INFO" | "LOG" | "DEBUG"))
}
}
impl std::fmt::Display for Notice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(sev) = self.severity() {
write!(f, "{sev}: ")?;
}
write!(f, "{}", self.message())?;
if let Some(code) = self.code() {
write!(f, " ({code})")?;
}
Ok(())
}
}
pub type NoticeReceiver = Box<dyn Fn(Notice) + Send + Sync>;
#[cfg(test)]
mod tests {
use super::*;
fn make_notice(severity: Option<&str>, code: Option<&str>, message: &str) -> Notice {
Notice {
severity: severity.map(String::from),
code: code.map(String::from),
message: message.to_string(),
detail: None,
hint: None,
position: None,
}
}
#[test]
fn test_notice_display() {
let notice = make_notice(Some("WARNING"), Some("01000"), "test warning");
assert_eq!(format!("{notice}"), "WARNING: test warning (01000)");
}
#[test]
fn test_notice_accessors() {
let notice = make_notice(Some("WARNING"), Some("01000"), "test");
assert_eq!(notice.severity(), Some("WARNING"));
assert_eq!(notice.code(), Some("01000"));
assert_eq!(notice.message(), "test");
}
#[test]
fn test_notice_is_warning() {
let warning = make_notice(Some("WARNING"), None, "");
assert!(warning.is_warning());
let info = make_notice(Some("INFO"), None, "");
assert!(!info.is_warning());
assert!(info.is_info());
}
}