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}