Line data Source code
1 : //! Errors.
2 :
3 : use std::error::{self, Error as _Error};
4 : use std::{fmt, io};
5 :
6 : use fallible_iterator::FallibleIterator;
7 : use postgres_protocol2::message::backend::{ErrorFields, ErrorResponseBody};
8 :
9 : pub use self::sqlstate::*;
10 :
11 : #[allow(clippy::unreadable_literal)]
12 : mod sqlstate;
13 :
14 : /// The severity of a Postgres error or notice.
15 : #[derive(Debug, Copy, Clone, PartialEq, Eq)]
16 : pub enum Severity {
17 : /// PANIC
18 : Panic,
19 : /// FATAL
20 : Fatal,
21 : /// ERROR
22 : Error,
23 : /// WARNING
24 : Warning,
25 : /// NOTICE
26 : Notice,
27 : /// DEBUG
28 : Debug,
29 : /// INFO
30 : Info,
31 : /// LOG
32 : Log,
33 : }
34 :
35 : impl fmt::Display for Severity {
36 0 : fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
37 0 : let s = match *self {
38 0 : Severity::Panic => "PANIC",
39 0 : Severity::Fatal => "FATAL",
40 0 : Severity::Error => "ERROR",
41 0 : Severity::Warning => "WARNING",
42 0 : Severity::Notice => "NOTICE",
43 0 : Severity::Debug => "DEBUG",
44 0 : Severity::Info => "INFO",
45 0 : Severity::Log => "LOG",
46 : };
47 0 : fmt.write_str(s)
48 0 : }
49 : }
50 :
51 : impl Severity {
52 0 : fn from_str(s: &str) -> Option<Severity> {
53 0 : match s {
54 0 : "PANIC" => Some(Severity::Panic),
55 0 : "FATAL" => Some(Severity::Fatal),
56 0 : "ERROR" => Some(Severity::Error),
57 0 : "WARNING" => Some(Severity::Warning),
58 0 : "NOTICE" => Some(Severity::Notice),
59 0 : "DEBUG" => Some(Severity::Debug),
60 0 : "INFO" => Some(Severity::Info),
61 0 : "LOG" => Some(Severity::Log),
62 0 : _ => None,
63 : }
64 0 : }
65 : }
66 :
67 : /// A Postgres error or notice.
68 : #[derive(Debug, Clone, PartialEq, Eq)]
69 : pub 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 :
88 : impl DbError {
89 12 : pub fn new_test_error(code: SqlState, message: String) -> Self {
90 12 : DbError {
91 12 : severity: "ERROR".to_string(),
92 12 : parsed_severity: Some(Severity::Error),
93 12 : code,
94 12 : message,
95 12 : detail: None,
96 12 : hint: None,
97 12 : position: None,
98 12 : where_: None,
99 12 : schema: None,
100 12 : table: None,
101 12 : column: None,
102 12 : datatype: None,
103 12 : constraint: None,
104 12 : file: None,
105 12 : line: None,
106 12 : routine: None,
107 12 : }
108 12 : }
109 :
110 1 : pub(crate) fn parse(fields: &mut ErrorFields<'_>) -> io::Result<DbError> {
111 1 : let mut severity = None;
112 1 : let mut parsed_severity = None;
113 1 : let mut code = None;
114 1 : let mut message = None;
115 1 : let mut detail = None;
116 1 : let mut hint = None;
117 1 : let mut normal_position = None;
118 1 : let mut internal_position = None;
119 1 : let mut internal_query = None;
120 1 : let mut where_ = None;
121 1 : let mut schema = None;
122 1 : let mut table = None;
123 1 : let mut column = None;
124 1 : let mut datatype = None;
125 1 : let mut constraint = None;
126 1 : let mut file = None;
127 1 : let mut line = None;
128 1 : let mut routine = None;
129 :
130 4 : while let Some(field) = fields.next()? {
131 3 : match field.type_() {
132 1 : b'S' => severity = Some(field.value().to_owned()),
133 1 : b'C' => code = Some(SqlState::from_code(field.value())),
134 1 : b'M' => message = Some(field.value().to_owned()),
135 0 : b'D' => detail = Some(field.value().to_owned()),
136 0 : b'H' => hint = Some(field.value().to_owned()),
137 : b'P' => {
138 0 : normal_position = Some(field.value().parse::<u32>().map_err(|_| {
139 0 : io::Error::new(
140 0 : io::ErrorKind::InvalidInput,
141 : "`P` field did not contain an integer",
142 : )
143 0 : })?);
144 : }
145 : b'p' => {
146 0 : internal_position = Some(field.value().parse::<u32>().map_err(|_| {
147 0 : io::Error::new(
148 0 : io::ErrorKind::InvalidInput,
149 : "`p` field did not contain an integer",
150 : )
151 0 : })?);
152 : }
153 0 : b'q' => internal_query = Some(field.value().to_owned()),
154 0 : b'W' => where_ = Some(field.value().to_owned()),
155 0 : b's' => schema = Some(field.value().to_owned()),
156 0 : b't' => table = Some(field.value().to_owned()),
157 0 : b'c' => column = Some(field.value().to_owned()),
158 0 : b'd' => datatype = Some(field.value().to_owned()),
159 0 : b'n' => constraint = Some(field.value().to_owned()),
160 0 : b'F' => file = Some(field.value().to_owned()),
161 : b'L' => {
162 0 : line = Some(field.value().parse::<u32>().map_err(|_| {
163 0 : io::Error::new(
164 0 : io::ErrorKind::InvalidInput,
165 : "`L` field did not contain an integer",
166 : )
167 0 : })?);
168 : }
169 0 : b'R' => routine = Some(field.value().to_owned()),
170 : b'V' => {
171 0 : parsed_severity = Some(Severity::from_str(field.value()).ok_or_else(|| {
172 0 : io::Error::new(
173 0 : io::ErrorKind::InvalidInput,
174 : "`V` field contained an invalid value",
175 : )
176 0 : })?);
177 : }
178 0 : _ => {}
179 : }
180 : }
181 :
182 : Ok(DbError {
183 1 : severity: severity
184 1 : .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`S` field missing"))?,
185 1 : parsed_severity,
186 1 : code: code
187 1 : .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`C` field missing"))?,
188 1 : message: message
189 1 : .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "`M` field missing"))?,
190 1 : detail,
191 1 : hint,
192 1 : position: match normal_position {
193 0 : Some(position) => Some(ErrorPosition::Original(position)),
194 1 : None => match internal_position {
195 0 : Some(position) => Some(ErrorPosition::Internal {
196 0 : position,
197 0 : query: internal_query.ok_or_else(|| {
198 0 : io::Error::new(
199 0 : io::ErrorKind::InvalidInput,
200 : "`q` field missing but `p` field present",
201 : )
202 0 : })?,
203 : }),
204 1 : None => None,
205 : },
206 : },
207 1 : where_,
208 1 : schema,
209 1 : table,
210 1 : column,
211 1 : datatype,
212 1 : constraint,
213 1 : file,
214 1 : line,
215 1 : routine,
216 : })
217 1 : }
218 :
219 : /// The field contents are ERROR, FATAL, or PANIC (in an error message),
220 : /// or WARNING, NOTICE, DEBUG, INFO, or LOG (in a notice message), or a
221 : /// localized translation of one of these.
222 0 : pub fn severity(&self) -> &str {
223 0 : &self.severity
224 0 : }
225 :
226 : /// A parsed, nonlocalized version of `severity`. (PostgreSQL 9.6+)
227 0 : pub fn parsed_severity(&self) -> Option<Severity> {
228 0 : self.parsed_severity
229 0 : }
230 :
231 : /// The SQLSTATE code for the error.
232 17 : pub fn code(&self) -> &SqlState {
233 17 : &self.code
234 17 : }
235 :
236 : /// The primary human-readable error message.
237 : ///
238 : /// This should be accurate but terse (typically one line).
239 1 : pub fn message(&self) -> &str {
240 1 : &self.message
241 1 : }
242 :
243 : /// An optional secondary error message carrying more detail about the
244 : /// problem.
245 : ///
246 : /// Might run to multiple lines.
247 0 : pub fn detail(&self) -> Option<&str> {
248 0 : self.detail.as_deref()
249 0 : }
250 :
251 : /// An optional suggestion what to do about the problem.
252 : ///
253 : /// This is intended to differ from `detail` in that it offers advice
254 : /// (potentially inappropriate) rather than hard facts. Might run to
255 : /// multiple lines.
256 0 : pub fn hint(&self) -> Option<&str> {
257 0 : self.hint.as_deref()
258 0 : }
259 :
260 : /// An optional error cursor position into either the original query string
261 : /// or an internally generated query.
262 0 : pub fn position(&self) -> Option<&ErrorPosition> {
263 0 : self.position.as_ref()
264 0 : }
265 :
266 : /// An indication of the context in which the error occurred.
267 : ///
268 : /// Presently this includes a call stack traceback of active procedural
269 : /// language functions and internally-generated queries. The trace is one
270 : /// entry per line, most recent first.
271 0 : pub fn where_(&self) -> Option<&str> {
272 0 : self.where_.as_deref()
273 0 : }
274 :
275 : /// If the error was associated with a specific database object, the name
276 : /// of the schema containing that object, if any. (PostgreSQL 9.3+)
277 0 : pub fn schema(&self) -> Option<&str> {
278 0 : self.schema.as_deref()
279 0 : }
280 :
281 : /// If the error was associated with a specific table, the name of the
282 : /// table. (Refer to the schema name field for the name of the table's
283 : /// schema.) (PostgreSQL 9.3+)
284 0 : pub fn table(&self) -> Option<&str> {
285 0 : self.table.as_deref()
286 0 : }
287 :
288 : /// If the error was associated with a specific table column, the name of
289 : /// the column.
290 : ///
291 : /// (Refer to the schema and table name fields to identify the table.)
292 : /// (PostgreSQL 9.3+)
293 0 : pub fn column(&self) -> Option<&str> {
294 0 : self.column.as_deref()
295 0 : }
296 :
297 : /// If the error was associated with a specific data type, the name of the
298 : /// data type. (Refer to the schema name field for the name of the data
299 : /// type's schema.) (PostgreSQL 9.3+)
300 0 : pub fn datatype(&self) -> Option<&str> {
301 0 : self.datatype.as_deref()
302 0 : }
303 :
304 : /// If the error was associated with a specific constraint, the name of the
305 : /// constraint.
306 : ///
307 : /// Refer to fields listed above for the associated table or domain.
308 : /// (For this purpose, indexes are treated as constraints, even if they
309 : /// weren't created with constraint syntax.) (PostgreSQL 9.3+)
310 0 : pub fn constraint(&self) -> Option<&str> {
311 0 : self.constraint.as_deref()
312 0 : }
313 :
314 : /// The file name of the source-code location where the error was reported.
315 0 : pub fn file(&self) -> Option<&str> {
316 0 : self.file.as_deref()
317 0 : }
318 :
319 : /// The line number of the source-code location where the error was
320 : /// reported.
321 0 : pub fn line(&self) -> Option<u32> {
322 0 : self.line
323 0 : }
324 :
325 : /// The name of the source-code routine reporting the error.
326 0 : pub fn routine(&self) -> Option<&str> {
327 0 : self.routine.as_deref()
328 0 : }
329 : }
330 :
331 : impl fmt::Display for DbError {
332 2 : fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
333 2 : write!(fmt, "{}: {}", self.severity, self.message)?;
334 2 : if let Some(detail) = &self.detail {
335 0 : write!(fmt, "\nDETAIL: {detail}")?;
336 2 : }
337 2 : if let Some(hint) = &self.hint {
338 0 : write!(fmt, "\nHINT: {hint}")?;
339 2 : }
340 2 : Ok(())
341 2 : }
342 : }
343 :
344 : impl error::Error for DbError {}
345 :
346 : /// Represents the position of an error in a query.
347 : #[derive(Clone, PartialEq, Eq, Debug)]
348 : pub enum ErrorPosition {
349 : /// A position in the original query.
350 : Original(u32),
351 : /// A position in an internally generated query.
352 : Internal {
353 : /// The byte position.
354 : position: u32,
355 : /// A query generated by the Postgres server.
356 : query: String,
357 : },
358 : }
359 :
360 : #[derive(Debug, PartialEq)]
361 : enum Kind {
362 : Io,
363 : UnexpectedMessage,
364 : Tls,
365 : ToSql(usize),
366 : FromSql(usize),
367 : Column(String),
368 : Closed,
369 : Db,
370 : Parse,
371 : Encode,
372 : Authentication,
373 : Config,
374 : Connect,
375 : Timeout,
376 : }
377 :
378 : struct ErrorInner {
379 : kind: Kind,
380 : cause: Option<Box<dyn error::Error + Sync + Send>>,
381 : }
382 :
383 : /// An error communicating with the Postgres server.
384 : pub struct Error(Box<ErrorInner>);
385 :
386 : impl fmt::Debug for Error {
387 0 : fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
388 0 : fmt.debug_struct("Error")
389 0 : .field("kind", &self.0.kind)
390 0 : .field("cause", &self.0.cause)
391 0 : .finish()
392 0 : }
393 : }
394 :
395 : impl fmt::Display for Error {
396 2 : fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
397 2 : match &self.0.kind {
398 0 : Kind::Io => fmt.write_str("error communicating with the server")?,
399 0 : Kind::UnexpectedMessage => fmt.write_str("unexpected message from server")?,
400 0 : Kind::Tls => fmt.write_str("error performing TLS handshake")?,
401 0 : Kind::ToSql(idx) => write!(fmt, "error serializing parameter {idx}")?,
402 0 : Kind::FromSql(idx) => write!(fmt, "error deserializing column {idx}")?,
403 0 : Kind::Column(column) => write!(fmt, "invalid column `{column}`")?,
404 0 : Kind::Closed => fmt.write_str("connection closed")?,
405 2 : Kind::Db => fmt.write_str("db error")?,
406 0 : Kind::Parse => fmt.write_str("error parsing response from server")?,
407 0 : Kind::Encode => fmt.write_str("error encoding message to server")?,
408 0 : Kind::Authentication => fmt.write_str("authentication error")?,
409 0 : Kind::Config => fmt.write_str("invalid configuration")?,
410 0 : Kind::Connect => fmt.write_str("error connecting to server")?,
411 0 : Kind::Timeout => fmt.write_str("timeout waiting for server")?,
412 : };
413 2 : if let Some(ref cause) = self.0.cause {
414 2 : write!(fmt, ": {cause}")?;
415 0 : }
416 2 : Ok(())
417 2 : }
418 : }
419 :
420 : impl error::Error for Error {
421 0 : fn source(&self) -> Option<&(dyn error::Error + 'static)> {
422 0 : self.0.cause.as_ref().map(|e| &**e as _)
423 0 : }
424 : }
425 :
426 : impl Error {
427 : /// Consumes the error, returning its cause.
428 0 : pub fn into_source(self) -> Option<Box<dyn error::Error + Sync + Send>> {
429 0 : self.0.cause
430 0 : }
431 :
432 : /// Returns the source of this error if it was a `DbError`.
433 : ///
434 : /// This is a simple convenience method.
435 0 : pub fn as_db_error(&self) -> Option<&DbError> {
436 0 : self.source().and_then(|e| e.downcast_ref::<DbError>())
437 0 : }
438 :
439 : /// Determines if the error was associated with closed connection.
440 0 : pub fn is_closed(&self) -> bool {
441 0 : self.0.kind == Kind::Closed
442 0 : }
443 :
444 : /// Returns the SQLSTATE error code associated with the error.
445 : ///
446 : /// This is a convenience method that downcasts the cause to a `DbError` and returns its code.
447 0 : pub fn code(&self) -> Option<&SqlState> {
448 0 : self.as_db_error().map(DbError::code)
449 0 : }
450 :
451 8 : fn new(kind: Kind, cause: Option<Box<dyn error::Error + Sync + Send>>) -> Error {
452 8 : Error(Box::new(ErrorInner { kind, cause }))
453 8 : }
454 :
455 0 : pub(crate) fn closed() -> Error {
456 0 : Error::new(Kind::Closed, None)
457 0 : }
458 :
459 0 : pub(crate) fn unexpected_message() -> Error {
460 0 : Error::new(Kind::UnexpectedMessage, None)
461 0 : }
462 :
463 : #[allow(clippy::needless_pass_by_value)]
464 1 : pub(crate) fn db(error: ErrorResponseBody) -> Error {
465 1 : match DbError::parse(&mut error.fields()) {
466 1 : Ok(e) => Error::new(Kind::Db, Some(Box::new(e))),
467 0 : Err(e) => Error::new(Kind::Parse, Some(Box::new(e))),
468 : }
469 1 : }
470 :
471 0 : pub(crate) fn parse(e: io::Error) -> Error {
472 0 : Error::new(Kind::Parse, Some(Box::new(e)))
473 0 : }
474 :
475 0 : pub(crate) fn encode(e: io::Error) -> Error {
476 0 : Error::new(Kind::Encode, Some(Box::new(e)))
477 0 : }
478 :
479 : #[allow(clippy::wrong_self_convention)]
480 0 : pub(crate) fn to_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
481 0 : Error::new(Kind::ToSql(idx), Some(e))
482 0 : }
483 :
484 0 : pub(crate) fn from_sql(e: Box<dyn error::Error + Sync + Send>, idx: usize) -> Error {
485 0 : Error::new(Kind::FromSql(idx), Some(e))
486 0 : }
487 :
488 0 : pub(crate) fn column(column: String) -> Error {
489 0 : Error::new(Kind::Column(column), None)
490 0 : }
491 :
492 0 : pub(crate) fn tls(e: Box<dyn error::Error + Sync + Send>) -> Error {
493 0 : Error::new(Kind::Tls, Some(e))
494 0 : }
495 :
496 6 : pub(crate) fn io(e: io::Error) -> Error {
497 6 : Error::new(Kind::Io, Some(Box::new(e)))
498 6 : }
499 :
500 1 : pub(crate) fn authentication(e: Box<dyn error::Error + Sync + Send>) -> Error {
501 1 : Error::new(Kind::Authentication, Some(e))
502 1 : }
503 :
504 0 : pub(crate) fn config(e: Box<dyn error::Error + Sync + Send>) -> Error {
505 0 : Error::new(Kind::Config, Some(e))
506 0 : }
507 :
508 0 : pub(crate) fn connect(e: io::Error) -> Error {
509 0 : Error::new(Kind::Connect, Some(Box::new(e)))
510 0 : }
511 :
512 : #[doc(hidden)]
513 0 : pub fn __private_api_timeout() -> Error {
514 0 : Error::new(Kind::Timeout, None)
515 0 : }
516 : }
|