hyperdb_api_core/client/
notice.rs1use crate::protocol::message::backend::NoticeResponseBody;
7use tracing::trace;
8
9#[derive(Debug, Clone)]
15pub struct Notice {
16 severity: Option<String>,
17 code: Option<String>,
18 message: String,
19 detail: Option<String>,
20 hint: Option<String>,
21 position: Option<i32>,
22}
23
24impl Notice {
25 #[inline]
27 #[must_use]
28 pub fn severity(&self) -> Option<&str> {
29 self.severity.as_deref()
30 }
31
32 #[inline]
34 #[must_use]
35 pub fn code(&self) -> Option<&str> {
36 self.code.as_deref()
37 }
38
39 #[inline]
41 #[must_use]
42 pub fn message(&self) -> &str {
43 &self.message
44 }
45
46 #[inline]
48 #[must_use]
49 pub fn detail(&self) -> Option<&str> {
50 self.detail.as_deref()
51 }
52
53 #[inline]
55 #[must_use]
56 pub fn hint(&self) -> Option<&str> {
57 self.hint.as_deref()
58 }
59
60 #[inline]
62 #[must_use]
63 pub fn position(&self) -> Option<i32> {
64 self.position
65 }
66}
67
68impl Notice {
69 pub(crate) fn from_response_body(body: &NoticeResponseBody) -> Self {
71 let mut severity = None;
72 let mut code = None;
73 let mut message = String::new();
74 let mut detail = None;
75 let mut hint = None;
76 let mut position = None;
77
78 for field in body.fields().filter_map(|r| {
79 r.map_err(|e| trace!(target: "hyperdb_api_core::client", error = %e, "dropped error parsing notice field")).ok()
80 }) {
81 match (field.type_(), field.value()) {
82 (b'S', Ok(v)) => severity = Some(v.to_string()),
83 (b'V', Ok(v)) if severity.is_none() => severity = Some(v.to_string()),
84 (b'C', Ok(v)) => code = Some(v.to_string()),
85 (b'M', Ok(v)) => message = v.to_string(),
86 (b'D', Ok(v)) => detail = Some(v.to_string()),
87 (b'H', Ok(v)) => hint = Some(v.to_string()),
88 (b'P', Ok(v)) => position = v.parse().ok(),
89 _ => {}
90 }
91 }
92
93 Notice {
94 severity,
95 code,
96 message,
97 detail,
98 hint,
99 position,
100 }
101 }
102
103 #[inline]
105 #[must_use]
106 pub fn is_warning(&self) -> bool {
107 matches!(self.severity(), Some("WARNING" | "WARN"))
108 }
109
110 #[inline]
112 #[must_use]
113 pub fn is_info(&self) -> bool {
114 matches!(self.severity(), Some("NOTICE" | "INFO" | "LOG" | "DEBUG"))
115 }
116}
117
118impl std::fmt::Display for Notice {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 if let Some(sev) = self.severity() {
121 write!(f, "{sev}: ")?;
122 }
123 write!(f, "{}", self.message())?;
124 if let Some(code) = self.code() {
125 write!(f, " ({code})")?;
126 }
127 Ok(())
128 }
129}
130
131pub type NoticeReceiver = Box<dyn Fn(Notice) + Send + Sync>;
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 fn make_notice(severity: Option<&str>, code: Option<&str>, message: &str) -> Notice {
147 Notice {
148 severity: severity.map(String::from),
149 code: code.map(String::from),
150 message: message.to_string(),
151 detail: None,
152 hint: None,
153 position: None,
154 }
155 }
156
157 #[test]
158 fn test_notice_display() {
159 let notice = make_notice(Some("WARNING"), Some("01000"), "test warning");
160 assert_eq!(format!("{notice}"), "WARNING: test warning (01000)");
161 }
162
163 #[test]
164 fn test_notice_accessors() {
165 let notice = make_notice(Some("WARNING"), Some("01000"), "test");
166 assert_eq!(notice.severity(), Some("WARNING"));
167 assert_eq!(notice.code(), Some("01000"));
168 assert_eq!(notice.message(), "test");
169 }
170
171 #[test]
172 fn test_notice_is_warning() {
173 let warning = make_notice(Some("WARNING"), None, "");
174 assert!(warning.is_warning());
175
176 let info = make_notice(Some("INFO"), None, "");
177 assert!(!info.is_warning());
178 assert!(info.is_info());
179 }
180}