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