Skip to main content

postgresql_protocol/
error.rs

1//! Errors produced while encoding or decoding protocol messages.
2
3use std::fmt;
4
5use small_collections::SmallString;
6
7/// A structured `ErrorResponse` / `NoticeResponse` from the server.
8#[derive(Debug, Clone, Default)]
9pub struct DbError {
10    /// Localized severity (`ERROR`, `FATAL`, …).
11    ///
12    /// Stored inline; only values longer than 8 bytes (unusual localizations)
13    /// spill onto the heap.
14    pub severity: SmallString<8>,
15    /// SQLSTATE code.
16    ///
17    /// The wire protocol bounds this to five bytes, so it is always stored
18    /// inline and decoding it never allocates.
19    pub code: SmallString<5>,
20    /// Primary human-readable message.
21    pub message: String,
22    /// Optional detail.
23    pub detail: Option<String>,
24    /// Optional hint.
25    pub hint: Option<String>,
26    /// Optional 1-based character position in the query.
27    pub position: Option<i32>,
28    /// Optional constraint name.
29    pub constraint: Option<String>,
30    /// Optional table name.
31    pub table: Option<String>,
32    /// Optional column name.
33    pub column: Option<String>,
34    /// Optional schema name.
35    pub schema: Option<String>,
36    /// Optional data type name.
37    pub data_type: Option<String>,
38}
39
40impl DbError {
41    /// Returns the SQLSTATE class (the first two characters), e.g. `23` for
42    /// integrity constraint violations.
43    ///
44    /// Returns `""` when no code was reported. The code is server-supplied and
45    /// may contain arbitrary bytes, so a short or non-ASCII code is truncated
46    /// at a character boundary rather than panicking.
47    pub fn class(&self) -> &str {
48        self.code.get(..2).or_else(|| self.code.get(..1)).unwrap_or("")
49    }
50
51    /// `true` for SQLSTATE class `22` (data exception).
52    pub fn is_data_exception(&self) -> bool {
53        self.class() == "22"
54    }
55
56    /// `true` for SQLSTATE class `23` (integrity constraint violation).
57    pub fn is_integrity_violation(&self) -> bool {
58        self.class() == "23"
59    }
60
61    /// `true` for `23505` (unique violation).
62    pub fn is_unique_violation(&self) -> bool {
63        self.code == "23505"
64    }
65
66    /// `true` for `23503` (foreign key violation).
67    pub fn is_foreign_key_violation(&self) -> bool {
68        self.code == "23503"
69    }
70
71    /// `true` for `40P01` (deadlock detected) or `40001` (serialization failure),
72    /// both of which are safe to retry.
73    pub fn is_retryable(&self) -> bool {
74        self.code == "40001" || self.code == "40P01"
75    }
76}
77
78impl fmt::Display for DbError {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write!(f, "{}: {} (SQLSTATE {})", self.severity, self.message, self.code)?;
81        if let Some(detail) = &self.detail {
82            write!(f, "\nDETAIL: {detail}")?;
83        }
84        if let Some(hint) = &self.hint {
85            write!(f, "\nHINT: {hint}")?;
86        }
87        Ok(())
88    }
89}
90
91impl std::error::Error for DbError {}
92
93/// Errors that can occur while speaking the PostgreSQL wire protocol.
94#[derive(Debug, thiserror::Error)]
95pub enum Error {
96    /// The server sent a message we did not expect in this state.
97    #[error("protocol error: {0}")]
98    Protocol(String),
99    /// A message could not be decoded.
100    #[error("decode error: {0}")]
101    Decode(String),
102    /// A message could not be encoded.
103    #[error("encode error: {0}")]
104    Encode(String),
105    /// The server reported an error.
106    ///
107    /// Boxed to keep [`Error`] (and therefore every `Result<T, Error>`) small;
108    /// [`DbError`] itself is ~250 bytes.
109    #[error("server error: {0}")]
110    Server(Box<DbError>),
111    /// Authentication failed.
112    #[error("authentication error: {0}")]
113    Auth(String),
114}
115
116impl From<DbError> for Error {
117    fn from(err: DbError) -> Self {
118        Error::Server(Box::new(err))
119    }
120}