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(crate) fn parse(fields: &mut ErrorFields<'_>) -> io::Result<DbError> {
90 let mut severity = None;
91 let mut parsed_severity = None;
92 let mut code = None;
93 let mut message = None;
94 let mut detail = None;
95 let mut hint = None;
96 let mut normal_position = None;
97 let mut internal_position = None;
98 let mut internal_query = None;
99 let mut where_ = None;
100 let mut schema = None;
101 let mut table = None;
102 let mut column = None;
103 let mut datatype = None;
104 let mut constraint = None;
105 let mut file = None;
106 let mut line = None;
107 let mut routine = None;
108
109 while let Some(field) = fields.next()? {
110 match field.type_() {
111 b'S' => severity = Some(field.value().to_owned()),
112 b'C' => code = Some(SqlState::from_code(field.value())),
113 b'M' => message = Some(field.value().to_owned()),
114 b'D' => detail = Some(field.value().to_owned()),
115 b'H' => hint = Some(field.value().to_owned()),
116 b'P' => {
117 normal_position = Some(field.value().parse::<u32>().map_err(|_| {
118 io::Error::new(
119 io::ErrorKind::InvalidInput,
120 "`P` field did not contain an integer",
121 )
122 })?);
123 }
124 b'p' => {
125 internal_position = Some(field.value().parse::<u32>().map_err(|_| {
126 io::Error::new(
127 io::ErrorKind::InvalidInput,
128 "`p` field did not contain an integer",
129 )
130 })?);
131 }
132 b'q' => internal_query = Some(field.value().to_owned()),
133 b'W' => where_ = Some(field.value().to_owned()),
134 b's' => schema = Some(field.value().to_owned()),
135 b't' => table = Some(field.value().to_owned()),
136 b'c' => column = Some(field.value().to_owned()),
137 b'd' => datatype = Some(field.value().to_owned()),
138 b'n' => constraint = Some(field.value().to_owned()),
139 b'F' => file = Some(field.value().to_owned()),
140 b'L' => {
141 line = Some(field.value().parse::<u32>().map_err(|_| {
142 io::Error::new(
143 io::ErrorKind::InvalidInput,
144 "`L` field did not contain an integer",
145 )
146 })?);
147 }
148 b'R' => routine = Some(field.value().to_owned()),
149 b'V' => {
150 parsed_severity = Some(Severity::from_str(field.value()).ok_or_else(|| {
151 io::Error::new(
152 io::ErrorKind::InvalidInput,
153 "`V` field contained an invalid value",
154 )
155 })?);
156 }
157 _ => {}
158 }
159 }
160
161 Ok(DbError {
162 severity: severity
163 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`S` field missing"))?,
164 parsed_severity,
165 code: code
166 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`C` field missing"))?,
167 message: message
168 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`M` field missing"))?,
169 detail,
170 hint,
171 position: match normal_position {
172 Some(position) => Some(ErrorPosition::Original(position)),
173 None => match internal_position {
174 Some(position) => Some(ErrorPosition::Internal {
175 position,
176 query: internal_query.ok_or_else(|| {
177 io::Error::new(
178 io::ErrorKind::InvalidInput,
179 "`q` field missing but `p` field present",
180 )
181 })?,
182 }),
183 None => None,
184 },
185 },
186 where_,
187 schema,
188 table,
189 column,
190 datatype,
191 constraint,
192 file,
193 line,
194 routine,
195 })
196 }
197
198 pub fn severity(&self) -> &str {
202 &self.severity
203 }
204
205 pub fn parsed_severity(&self) -> Option<Severity> {
207 self.parsed_severity
208 }
209
210 pub fn code(&self) -> &SqlState {
212 &self.code
213 }
214
215 pub fn message(&self) -> &str {
219 &self.message
220 }
221
222 pub fn detail(&self) -> Option<&str> {
227 self.detail.as_deref()
228 }
229
230 pub fn hint(&self) -> Option<&str> {
236 self.hint.as_deref()
237 }
238
239 pub fn position(&self) -> Option<&ErrorPosition> {
242 self.position.as_ref()
243 }
244
245 pub fn where_(&self) -> Option<&str> {
251 self.where_.as_deref()
252 }
253
254 pub fn schema(&self) -> Option<&str> {
257 self.schema.as_deref()
258 }
259
260 pub fn table(&self) -> Option<&str> {
264 self.table.as_deref()
265 }
266
267 pub fn column(&self) -> Option<&str> {
273 self.column.as_deref()
274 }
275
276 pub fn datatype(&self) -> Option<&str> {
280 self.datatype.as_deref()
281 }
282
283 pub fn constraint(&self) -> Option<&str> {
290 self.constraint.as_deref()
291 }
292
293 pub fn file(&self) -> Option<&str> {
295 self.file.as_deref()
296 }
297
298 pub fn line(&self) -> Option<u32> {
301 self.line
302 }
303
304 pub fn routine(&self) -> Option<&str> {
306 self.routine.as_deref()
307 }
308}
309
310impl fmt::Display for DbError {
311 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
312 write!(fmt, "{}: {}", self.severity, self.message)?;
313 if let Some(detail) = &self.detail {
314 write!(fmt, "\nDETAIL: {}", detail)?;
315 }
316 if let Some(hint) = &self.hint {
317 write!(fmt, "\nHINT: {}", hint)?;
318 }
319 Ok(())
320 }
321}
322
323impl error::Error for DbError {}
324
325#[derive(Clone, PartialEq, Eq, Debug)]
327pub enum ErrorPosition {
328 Original(u32),
330 Internal {
332 position: u32,
334 query: String,
336 },
337}
338
339#[derive(Debug, PartialEq)]
340enum Kind {
341 Io,
342 UnexpectedMessage,
343 Tls,
344 ToSql(usize),
345 FromSql(usize),
346 Column(String),
347 Parameters(usize, usize),
348 Closed,
349 Db,
350 Parse,
351 Encode,
352 Authentication,
353 ConfigParse,
354 Config,
355 RowCount,
356 #[cfg(feature = "runtime")]
357 Connect,
358 Timeout,
359}
360
361struct ErrorInner {
362 kind: Kind,
363 cause: Option<Box<dyn error::Error + Sync + Send>>,
364}
365
366pub struct Error(Box<ErrorInner>);
368
369impl fmt::Debug for Error {
370 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
371 fmt.debug_struct("Error")
372 .field("kind", &self.0.kind)
373 .field("cause", &self.0.cause)
374 .finish()
375 }
376}
377
378impl fmt::Display for Error {
379 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
380 match &self.0.kind {
381 Kind::Io => fmt.write_str("error communicating with the server")?,
382 Kind::UnexpectedMessage => fmt.write_str("unexpected message from server")?,
383 Kind::Tls => fmt.write_str("error performing TLS handshake")?,
384 Kind::ToSql(idx) => write!(fmt, "error serializing parameter {}", idx)?,
385 Kind::FromSql(idx) => write!(fmt, "error deserializing column {}", idx)?,
386 Kind::Column(column) => write!(fmt, "invalid column `{}`", column)?,
387 Kind::Parameters(real, expected) => {
388 write!(fmt, "expected {expected} parameters but got {real}")?
389 }
390 Kind::Closed => fmt.write_str("connection closed")?,
391 Kind::Db => fmt.write_str("db error")?,
392 Kind::Parse => fmt.write_str("error parsing response from server")?,
393 Kind::Encode => fmt.write_str("error encoding message to server")?,
394 Kind::Authentication => fmt.write_str("authentication error")?,
395 Kind::ConfigParse => fmt.write_str("invalid connection string")?,
396 Kind::Config => fmt.write_str("invalid configuration")?,
397 Kind::RowCount => fmt.write_str("query returned an unexpected number of rows")?,
398 #[cfg(feature = "runtime")]
399 Kind::Connect => fmt.write_str("error connecting to server")?,
400 Kind::Timeout => fmt.write_str("timeout waiting for server")?,
401 };
402 if let Some(ref cause) = self.0.cause {
403 write!(fmt, ": {}", cause)?;
404 }
405 Ok(())
406 }
407}
408
409impl error::Error for Error {
410 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
411 self.0.cause.as_ref().map(|e| &**e as _)
412 }
413}
414
415impl Error {
416 pub fn into_source(self) -> Option<Box<dyn error::Error + Sync + Send>> {
418 self.0.cause
419 }
420
421 pub fn as_db_error(&self) -> Option<&DbError> {
425 self.source().and_then(|e| e.downcast_ref::<DbError>())
426 }
427
428 pub fn is_closed(&self) -> bool {
430 self.0.kind == Kind::Closed
431 }
432
433 pub fn code(&self) -> Option<&SqlState> {
437 self.as_db_error().map(DbError::code)
438 }
439
440 fn new(kind: Kind, cause: Option<Box<dyn error::Error + Sync + Send>>) -> Error {
441 Error(Box::new(ErrorInner { kind, cause }))
442 }
443
444 pub(crate) fn closed() -> Error {
445 Error::new(Kind::Closed, None)
446 }
447
448 pub(crate) fn unexpected_message() -> Error {
449 Error::new(Kind::UnexpectedMessage, None)
450 }
451
452 #[allow(clippy::needless_pass_by_value)]
453 pub(crate) fn db(error: ErrorResponseBody) -> Error {
454 match DbError::parse(&mut error.fields()) {
455 Ok(e) => Error::new(Kind::Db, Some(Box::new(e))),
456 Err(e) => Error::new(Kind::Parse, Some(Box::new(e))),
457 }
458 }
459
460 pub(crate) fn parse(e: io::Error) -> Error {
461 Error::new(Kind::Parse, Some(Box::new(e)))
462 }
463
464 pub(crate) fn encode(e: io::Error) -> Error {
465 Error::new(Kind::Encode, Some(Box::new(e)))
466 }
467
468 #[allow(clippy::wrong_self_convention)]
469 pub(crate) fn to_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
470 Error::new(Kind::ToSql(idx), Some(e))
471 }
472
473 pub(crate) fn from_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
474 Error::new(Kind::FromSql(idx), Some(e))
475 }
476
477 pub(crate) fn column(column: String) -> Error {
478 Error::new(Kind::Column(column), None)
479 }
480
481 pub(crate) fn parameters(real: usize, expected: usize) -> Error {
482 Error::new(Kind::Parameters(real, expected), None)
483 }
484
485 pub(crate) fn tls(e: Box<dyn error::Error + Sync + Send>) -> Error {
486 Error::new(Kind::Tls, Some(e))
487 }
488
489 pub(crate) fn io(e: io::Error) -> Error {
490 Error::new(Kind::Io, Some(Box::new(e)))
491 }
492
493 pub(crate) fn authentication(e: Box<dyn error::Error + Sync + Send>) -> Error {
494 Error::new(Kind::Authentication, Some(e))
495 }
496
497 pub(crate) fn config_parse(e: Box<dyn error::Error + Sync + Send>) -> Error {
498 Error::new(Kind::ConfigParse, Some(e))
499 }
500
501 pub(crate) fn config(e: Box<dyn error::Error + Sync + Send>) -> Error {
502 Error::new(Kind::Config, Some(e))
503 }
504
505 pub(crate) fn row_count() -> Error {
506 Error::new(Kind::RowCount, None)
507 }
508
509 #[cfg(feature = "runtime")]
510 pub(crate) fn connect(e: io::Error) -> Error {
511 Error::new(Kind::Connect, Some(Box::new(e)))
512 }
513
514 #[doc(hidden)]
515 pub fn __private_api_timeout() -> Error {
516 Error::new(Kind::Timeout, None)
517 }
518}