1use fallible_iterator::FallibleIterator;
4use postgres_protocol::message::backend::{ErrorFields, ErrorResponseBody};
5use std::error::{self, Error as _Error};
6use std::fmt;
7use std::io;
8
9pub use self::sqlstate::*;
10
11#[allow(clippy::unreadable_literal)]
12mod sqlstate;
13
14#[derive(Debug, Copy, Clone, PartialEq, Eq)]
16pub enum Severity {
17 Panic,
19 Fatal,
21 Error,
23 Warning,
25 Notice,
27 Debug,
29 Info,
31 Log,
33}
34
35impl fmt::Display for Severity {
36 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
37 let s = match *self {
38 Severity::Panic => "PANIC",
39 Severity::Fatal => "FATAL",
40 Severity::Error => "ERROR",
41 Severity::Warning => "WARNING",
42 Severity::Notice => "NOTICE",
43 Severity::Debug => "DEBUG",
44 Severity::Info => "INFO",
45 Severity::Log => "LOG",
46 };
47 fmt.write_str(s)
48 }
49}
50
51impl Severity {
52 fn from_str(s: &str) -> Option<Severity> {
53 match s {
54 "PANIC" => Some(Severity::Panic),
55 "FATAL" => Some(Severity::Fatal),
56 "ERROR" => Some(Severity::Error),
57 "WARNING" => Some(Severity::Warning),
58 "NOTICE" => Some(Severity::Notice),
59 "DEBUG" => Some(Severity::Debug),
60 "INFO" => Some(Severity::Info),
61 "LOG" => Some(Severity::Log),
62 _ => None,
63 }
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct DbError {
70 severity: String,
71 parsed_severity: Option<Severity>,
72 code: SqlState,
73 message: String,
74 detail: Option<String>,
75 hint: Option<String>,
76 position: Option<ErrorPosition>,
77 where_: Option<String>,
78 schema: Option<String>,
79 table: Option<String>,
80 column: Option<String>,
81 datatype: Option<String>,
82 constraint: Option<String>,
83 file: Option<String>,
84 line: Option<u32>,
85 routine: Option<String>,
86}
87
88impl DbError {
89 pub fn parse(fields: &mut ErrorFields<'_>) -> io::Result<DbError> {
91 let mut severity = None;
92 let mut parsed_severity = None;
93 let mut code = None;
94 let mut message = None;
95 let mut detail = None;
96 let mut hint = None;
97 let mut normal_position = None;
98 let mut internal_position = None;
99 let mut internal_query = None;
100 let mut where_ = None;
101 let mut schema = None;
102 let mut table = None;
103 let mut column = None;
104 let mut datatype = None;
105 let mut constraint = None;
106 let mut file = None;
107 let mut line = None;
108 let mut routine = None;
109
110 while let Some(field) = fields.next()? {
111 let value = String::from_utf8_lossy(field.value_bytes());
112 match field.type_() {
113 b'S' => severity = Some(value.into_owned()),
114 b'C' => code = Some(SqlState::from_code(&value)),
115 b'M' => message = Some(value.into_owned()),
116 b'D' => detail = Some(value.into_owned()),
117 b'H' => hint = Some(value.into_owned()),
118 b'P' => {
119 normal_position = Some(value.parse::<u32>().map_err(|_| {
120 io::Error::new(
121 io::ErrorKind::InvalidInput,
122 "`P` field did not contain an integer",
123 )
124 })?);
125 }
126 b'p' => {
127 internal_position = Some(value.parse::<u32>().map_err(|_| {
128 io::Error::new(
129 io::ErrorKind::InvalidInput,
130 "`p` field did not contain an integer",
131 )
132 })?);
133 }
134 b'q' => internal_query = Some(value.into_owned()),
135 b'W' => where_ = Some(value.into_owned()),
136 b's' => schema = Some(value.into_owned()),
137 b't' => table = Some(value.into_owned()),
138 b'c' => column = Some(value.into_owned()),
139 b'd' => datatype = Some(value.into_owned()),
140 b'n' => constraint = Some(value.into_owned()),
141 b'F' => file = Some(value.into_owned()),
142 b'L' => {
143 line = Some(value.parse::<u32>().map_err(|_| {
144 io::Error::new(
145 io::ErrorKind::InvalidInput,
146 "`L` field did not contain an integer",
147 )
148 })?);
149 }
150 b'R' => routine = Some(value.into_owned()),
151 b'V' => {
152 parsed_severity = Some(Severity::from_str(&value).ok_or_else(|| {
153 io::Error::new(
154 io::ErrorKind::InvalidInput,
155 "`V` field contained an invalid value",
156 )
157 })?);
158 }
159 _ => {}
160 }
161 }
162
163 Ok(DbError {
164 severity: severity
165 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`S` field missing"))?,
166 parsed_severity,
167 code: code
168 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`C` field missing"))?,
169 message: message
170 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`M` field missing"))?,
171 detail,
172 hint,
173 position: match normal_position {
174 Some(position) => Some(ErrorPosition::Original(position)),
175 None => match internal_position {
176 Some(position) => Some(ErrorPosition::Internal {
177 position,
178 query: internal_query.ok_or_else(|| {
179 io::Error::new(
180 io::ErrorKind::InvalidInput,
181 "`q` field missing but `p` field present",
182 )
183 })?,
184 }),
185 None => None,
186 },
187 },
188 where_,
189 schema,
190 table,
191 column,
192 datatype,
193 constraint,
194 file,
195 line,
196 routine,
197 })
198 }
199
200 pub fn severity(&self) -> &str {
204 &self.severity
205 }
206
207 pub fn parsed_severity(&self) -> Option<Severity> {
209 self.parsed_severity
210 }
211
212 pub fn code(&self) -> &SqlState {
214 &self.code
215 }
216
217 pub fn message(&self) -> &str {
221 &self.message
222 }
223
224 pub fn detail(&self) -> Option<&str> {
229 self.detail.as_deref()
230 }
231
232 pub fn hint(&self) -> Option<&str> {
238 self.hint.as_deref()
239 }
240
241 pub fn position(&self) -> Option<&ErrorPosition> {
244 self.position.as_ref()
245 }
246
247 pub fn where_(&self) -> Option<&str> {
253 self.where_.as_deref()
254 }
255
256 pub fn schema(&self) -> Option<&str> {
259 self.schema.as_deref()
260 }
261
262 pub fn table(&self) -> Option<&str> {
266 self.table.as_deref()
267 }
268
269 pub fn column(&self) -> Option<&str> {
275 self.column.as_deref()
276 }
277
278 pub fn datatype(&self) -> Option<&str> {
282 self.datatype.as_deref()
283 }
284
285 pub fn constraint(&self) -> Option<&str> {
292 self.constraint.as_deref()
293 }
294
295 pub fn file(&self) -> Option<&str> {
297 self.file.as_deref()
298 }
299
300 pub fn line(&self) -> Option<u32> {
303 self.line
304 }
305
306 pub fn routine(&self) -> Option<&str> {
308 self.routine.as_deref()
309 }
310}
311
312impl fmt::Display for DbError {
313 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
314 write!(fmt, "{}: {}", self.severity, self.message)?;
315 if let Some(detail) = &self.detail {
316 write!(fmt, "\nDETAIL: {}", detail)?;
317 }
318 if let Some(hint) = &self.hint {
319 write!(fmt, "\nHINT: {}", hint)?;
320 }
321 Ok(())
322 }
323}
324
325impl error::Error for DbError {}
326
327#[derive(Clone, PartialEq, Eq, Debug)]
329pub enum ErrorPosition {
330 Original(u32),
332 Internal {
334 position: u32,
336 query: String,
338 },
339}
340
341#[derive(Debug, PartialEq)]
342enum Kind {
343 Io,
344 UnexpectedMessage,
345 Tls,
346 ToSql(usize),
347 FromSql(usize),
348 Column(String),
349 Parameters(usize, usize),
350 Closed,
351 Db,
352 Parse,
353 Encode,
354 Authentication,
355 ConfigParse,
356 Config,
357 RowCount,
358 #[cfg(feature = "runtime")]
359 Connect,
360 Timeout,
361}
362
363struct ErrorInner {
364 kind: Kind,
365 cause: Option<Box<dyn error::Error + Sync + Send>>,
366}
367
368pub struct Error(Box<ErrorInner>);
370
371impl fmt::Debug for Error {
372 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
373 fmt.debug_struct("Error")
374 .field("kind", &self.0.kind)
375 .field("cause", &self.0.cause)
376 .finish()
377 }
378}
379
380impl fmt::Display for Error {
381 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
382 match &self.0.kind {
383 Kind::Io => fmt.write_str("error communicating with the server")?,
384 Kind::UnexpectedMessage => fmt.write_str("unexpected message from server")?,
385 Kind::Tls => fmt.write_str("error performing TLS handshake")?,
386 Kind::ToSql(idx) => write!(fmt, "error serializing parameter {}", idx)?,
387 Kind::FromSql(idx) => write!(fmt, "error deserializing column {}", idx)?,
388 Kind::Column(column) => write!(fmt, "invalid column `{}`", column)?,
389 Kind::Parameters(real, expected) => {
390 write!(fmt, "expected {expected} parameters but got {real}")?
391 }
392 Kind::Closed => fmt.write_str("connection closed")?,
393 Kind::Db => fmt.write_str("db error")?,
394 Kind::Parse => fmt.write_str("error parsing response from server")?,
395 Kind::Encode => fmt.write_str("error encoding message to server")?,
396 Kind::Authentication => fmt.write_str("authentication error")?,
397 Kind::ConfigParse => fmt.write_str("invalid connection string")?,
398 Kind::Config => fmt.write_str("invalid configuration")?,
399 Kind::RowCount => fmt.write_str("query returned an unexpected number of rows")?,
400 #[cfg(feature = "runtime")]
401 Kind::Connect => fmt.write_str("error connecting to server")?,
402 Kind::Timeout => fmt.write_str("timeout waiting for server")?,
403 };
404 if let Some(ref cause) = self.0.cause {
405 write!(fmt, ": {}", cause)?;
406 }
407 Ok(())
408 }
409}
410
411impl error::Error for Error {
412 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
413 self.0.cause.as_ref().map(|e| &**e as _)
414 }
415}
416
417impl Error {
418 pub fn into_source(self) -> Option<Box<dyn error::Error + Sync + Send>> {
420 self.0.cause
421 }
422
423 pub fn as_db_error(&self) -> Option<&DbError> {
427 self.source().and_then(|e| e.downcast_ref::<DbError>())
428 }
429
430 pub fn is_closed(&self) -> bool {
432 self.0.kind == Kind::Closed
433 }
434
435 pub fn code(&self) -> Option<&SqlState> {
439 self.as_db_error().map(DbError::code)
440 }
441
442 fn new(kind: Kind, cause: Option<Box<dyn error::Error + Sync + Send>>) -> Error {
443 Error(Box::new(ErrorInner { kind, cause }))
444 }
445
446 pub(crate) fn closed() -> Error {
447 Error::new(Kind::Closed, None)
448 }
449
450 pub fn unexpected_message() -> Error {
452 Error::new(Kind::UnexpectedMessage, None)
453 }
454
455 #[allow(clippy::needless_pass_by_value)]
456 pub(crate) fn db(error: ErrorResponseBody) -> Error {
457 match DbError::parse(&mut error.fields()) {
458 Ok(e) => Error::new(Kind::Db, Some(Box::new(e))),
459 Err(e) => Error::new(Kind::Parse, Some(Box::new(e))),
460 }
461 }
462
463 pub fn parse(e: io::Error) -> Error {
465 Error::new(Kind::Parse, Some(Box::new(e)))
466 }
467
468 pub(crate) fn encode(e: io::Error) -> Error {
469 Error::new(Kind::Encode, Some(Box::new(e)))
470 }
471
472 #[allow(clippy::wrong_self_convention)]
473 pub(crate) fn to_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
474 Error::new(Kind::ToSql(idx), Some(e))
475 }
476
477 pub(crate) fn from_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
478 Error::new(Kind::FromSql(idx), Some(e))
479 }
480
481 pub(crate) fn column(column: String) -> Error {
482 Error::new(Kind::Column(column), None)
483 }
484
485 pub(crate) fn parameters(real: usize, expected: usize) -> Error {
486 Error::new(Kind::Parameters(real, expected), None)
487 }
488
489 pub(crate) fn tls(e: Box<dyn error::Error + Sync + Send>) -> Error {
490 Error::new(Kind::Tls, Some(e))
491 }
492
493 pub(crate) fn io(e: io::Error) -> Error {
494 Error::new(Kind::Io, Some(Box::new(e)))
495 }
496
497 pub(crate) fn authentication(e: Box<dyn error::Error + Sync + Send>) -> Error {
498 Error::new(Kind::Authentication, Some(e))
499 }
500
501 pub(crate) fn config_parse(e: Box<dyn error::Error + Sync + Send>) -> Error {
502 Error::new(Kind::ConfigParse, Some(e))
503 }
504
505 pub(crate) fn config(e: Box<dyn error::Error + Sync + Send>) -> Error {
506 Error::new(Kind::Config, Some(e))
507 }
508
509 pub(crate) fn row_count() -> Error {
510 Error::new(Kind::RowCount, None)
511 }
512
513 #[cfg(feature = "runtime")]
514 pub(crate) fn connect(e: io::Error) -> Error {
515 Error::new(Kind::Connect, Some(Box::new(e)))
516 }
517
518 #[doc(hidden)]
519 pub fn __private_api_timeout() -> Error {
520 Error::new(Kind::Timeout, None)
521 }
522}