cdbc_pg/message/
response.rs1use std::str::from_utf8;
2
3use bytes::Bytes;
4use memchr::memchr;
5
6use cdbc::error::Error;
7use cdbc::io::Decode;
8
9#[derive(Debug, Copy, Clone, Eq, PartialEq)]
10#[repr(u8)]
11pub enum PgSeverity {
12 Panic,
13 Fatal,
14 Error,
15 Warning,
16 Notice,
17 Debug,
18 Info,
19 Log,
20}
21
22impl PgSeverity {
23 #[inline]
24 pub fn is_error(self) -> bool {
25 matches!(self, Self::Panic | Self::Fatal | Self::Error)
26 }
27}
28
29impl std::convert::TryFrom<&str> for PgSeverity {
30 type Error = Error;
31
32 fn try_from(s: &str) -> Result<PgSeverity, Error> {
33 let result = match s {
34 "PANIC" => PgSeverity::Panic,
35 "FATAL" => PgSeverity::Fatal,
36 "ERROR" => PgSeverity::Error,
37 "WARNING" => PgSeverity::Warning,
38 "NOTICE" => PgSeverity::Notice,
39 "DEBUG" => PgSeverity::Debug,
40 "INFO" => PgSeverity::Info,
41 "LOG" => PgSeverity::Log,
42
43 severity => {
44 return Err(err_protocol!("unknown severity: {:?}", severity));
45 }
46 };
47
48 Ok(result)
49 }
50}
51
52#[derive(Debug)]
53pub struct Notice {
54 storage: Bytes,
55 severity: PgSeverity,
56 message: (u16, u16),
57 code: (u16, u16),
58}
59
60impl Notice {
61 #[inline]
62 pub fn severity(&self) -> PgSeverity {
63 self.severity
64 }
65
66 #[inline]
67 pub fn code(&self) -> &str {
68 self.get_cached_str(self.code)
69 }
70
71 #[inline]
72 pub fn message(&self) -> &str {
73 self.get_cached_str(self.message)
74 }
75
76 #[inline]
80 pub fn get(&self, ty: u8) -> Option<&str> {
81 self.get_raw(ty).and_then(|v| from_utf8(v).ok())
82 }
83
84 pub fn get_raw(&self, ty: u8) -> Option<&[u8]> {
85 self.fields()
86 .filter(|(field, _)| *field == ty)
87 .map(|(_, (start, end))| &self.storage[start as usize..end as usize])
88 .next()
89 }
90}
91
92impl Notice {
93 #[inline]
94 fn fields(&self) -> Fields<'_> {
95 Fields {
96 storage: &self.storage,
97 offset: 0,
98 }
99 }
100
101 #[inline]
102 fn get_cached_str(&self, cache: (u16, u16)) -> &str {
103 from_utf8(&self.storage[cache.0 as usize..cache.1 as usize]).unwrap()
105 }
106}
107
108impl Decode<'_> for Notice {
109 fn decode_with(buf: Bytes, _: ()) -> Result<Self, Error> {
110 const DEFAULT_SEVERITY: PgSeverity = PgSeverity::Log;
114 let mut severity_v = None;
115 let mut severity_s = None;
116 let mut message = (0, 0);
117 let mut code = (0, 0);
118
119 let fields = Fields {
123 storage: &buf,
124 offset: 0,
125 };
126
127 for (field, v) in fields {
128 if message.0 != 0 && code.0 != 0 {
129 break;
132 }
133
134 use std::convert::TryInto;
135 match field {
136 b'S' => {
137 severity_s = from_utf8(&buf[v.0 as usize..v.1 as usize])
139 .unwrap()
140 .try_into()
141 .ok();
142 }
143
144 b'V' => {
145 severity_v = Some(
148 from_utf8(&buf[v.0 as usize..v.1 as usize])
149 .unwrap()
150 .try_into()?,
151 );
152 }
153
154 b'M' => {
155 message = v;
156 }
157
158 b'C' => {
159 code = v;
160 }
161
162 _ => {}
163 }
164 }
165
166 Ok(Self {
167 severity: severity_v.or(severity_s).unwrap_or(DEFAULT_SEVERITY),
168 message,
169 code,
170 storage: buf,
171 })
172 }
173}
174
175struct Fields<'a> {
177 storage: &'a [u8],
178 offset: u16,
179}
180
181impl<'a> Iterator for Fields<'a> {
182 type Item = (u8, (u16, u16));
183
184 fn next(&mut self) -> Option<Self::Item> {
185 let ty = self.storage[self.offset as usize];
189
190 if ty == 0 {
191 return None;
192 }
193
194 let nul = memchr(b'\0', &self.storage[(self.offset + 1) as usize..])? as u16;
195 let offset = self.offset;
196
197 self.offset += nul + 2;
198
199 Some((ty, (offset + 1, offset + nul + 1)))
200 }
201}
202
203#[test]
204fn test_decode_error_response() {
205 const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
206
207 let m = Notice::decode(Bytes::from_static(DATA)).unwrap();
208
209 assert_eq!(
210 m.message(),
211 "extension \"uuid-ossp\" already exists, skipping"
212 );
213
214 assert!(matches!(m.severity(), PgSeverity::Notice));
215 assert_eq!(m.code(), "42710");
216}
217
218#[cfg(all(test, not(debug_assertions)))]
219#[bench]
220fn bench_error_response_get_message(b: &mut test::Bencher) {
221 const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
222
223 let res = Notice::decode(test::black_box(Bytes::from_static(DATA))).unwrap();
224
225 b.iter(|| {
226 let _ = test::black_box(&res).message();
227 });
228}
229
230#[cfg(all(test, not(debug_assertions)))]
231#[bench]
232fn bench_decode_error_response(b: &mut test::Bencher) {
233 const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
234
235 b.iter(|| {
236 let _ = Notice::decode(test::black_box(Bytes::from_static(DATA)));
237 });
238}