1use std::fmt::Display;
2use std::io::Error as IOError;
3use thiserror::Error;
4
5use crate::messages::response::{ErrorResponse, NoticeResponse};
6
7#[derive(Error, Debug)]
9pub enum PgWireError {
10 #[error("Unsupported protocol version, received {0}.{1}")]
11 UnsupportedProtocolVersion(u16, u16),
12 #[error("Invalid CancelRequest message, code mismatch")]
13 InvalidCancelRequest,
14 #[error("Secret key length must be in [4, 256]")]
15 InvalidSecretKey,
16 #[error("Invalid message recevied, received {0}")]
17 InvalidMessageType(u8),
18 #[error("Invalid message length, expected max {0}, actual: {1}")]
19 MessageTooLarge(usize, usize),
20 #[error("Invalid target type, received {0}")]
21 InvalidTargetType(u8),
22 #[error("Invalid transaction status, received {0}")]
23 InvalidTransactionStatus(u8),
24 #[error("Invalid ssl request message")]
25 InvalidSSLRequestMessage,
26 #[error("Invalid gss encrypt request message")]
27 InvalidGssEncRequestMessage,
28 #[error("Invalid startup message")]
29 InvalidStartupMessage,
30 #[error("Invalid authentication message code: {0}")]
31 InvalidAuthenticationMessageCode(i32),
32 #[error("Invalid password message type, failed to coerce")]
33 FailedToCoercePasswordMessage,
34 #[error("Invalid SASL state")]
35 InvalidSASLState,
36 #[error("Unsupported SASL authentication method {0}")]
37 UnsupportedSASLAuthMethod(String),
38 #[error(transparent)]
39 IoError(#[from] std::io::Error),
40 #[error("Portal not found for name: {0}")]
41 PortalNotFound(String),
42 #[error("Cannot fetch portal in its Initial state, call start() first")]
43 PortalNotStarted,
44 #[error("Statement not found for name: {0}")]
45 StatementNotFound(String),
46 #[error("Parameter index out of bound: {0}")]
47 ParameterIndexOutOfBound(usize),
48 #[error("Cannot convert postgres type {0} to given rust type")]
49 InvalidRustTypeForParameter(String),
50 #[error("Failed to parse parameter: {0}")]
51 FailedToParseParameter(Box<dyn std::error::Error + Send + Sync>),
52 #[error("Failed to parse scram message: {0}")]
53 InvalidScramMessage(String),
54 #[error("Password authentication failed for user \"{0}\"")]
55 InvalidPassword(String),
56 #[error("Certificate algorithm is not supported")]
57 UnsupportedCertificateSignatureAlgorithm,
58 #[error("Username is required")]
59 UserNameRequired,
60 #[error("Connection is not ready for query")]
61 NotReadyForQuery,
62 #[error("canceling statement due to user request")]
63 QueryCanceled,
64 #[error("Invalid option value {0}")]
65 InvalidOptionValue(String),
66 #[error("{0}")]
67 InvalidOauthMessage(String),
68 #[error("{0}")]
69 OAuthAuthenticationFailed(String),
70 #[error("{0}")]
71 OAuthValidationError(String),
72 #[error("{0}")]
73 OauthAuthzIdError(String),
74 #[error(transparent)]
75 ApiError(#[from] Box<dyn std::error::Error + Send + Sync>),
76
77 #[error("User provided error: {0}")]
78 UserError(Box<ErrorInfo>),
79}
80
81impl From<PgWireError> for IOError {
82 fn from(e: PgWireError) -> Self {
83 IOError::other(e)
84 }
85}
86
87pub type PgWireResult<T> = Result<T, PgWireError>;
89
90#[non_exhaustive]
95#[derive(new, Debug, Default)]
96pub struct ErrorInfo {
97 pub severity: String,
101 pub code: String,
104 pub message: String,
106 #[new(default)]
108 pub detail: Option<String>,
109 #[new(default)]
111 pub hint: Option<String>,
112 #[new(default)]
115 pub position: Option<String>,
116 #[new(default)]
120 pub internal_position: Option<String>,
121 #[new(default)]
123 pub internal_query: Option<String>,
124 #[new(default)]
126 pub where_context: Option<String>,
127 #[new(default)]
129 pub file_name: Option<String>,
130 #[new(default)]
132 pub line: Option<usize>,
133 #[new(default)]
135 pub routine: Option<String>,
136 #[new(default)]
139 pub severity_nonlocalized: Option<String>,
140 #[new(default)]
143 pub schema: Option<String>,
144 #[new(default)]
146 pub table: Option<String>,
147 #[new(default)]
150 pub column: Option<String>,
151 #[new(default)]
154 pub datatype: Option<String>,
155 #[new(default)]
158 pub constraint: Option<String>,
159}
160
161impl Display for ErrorInfo {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 write!(
164 f,
165 "ErrorInfo: {}, {}, {}",
166 self.severity, self.code, self.message
167 )
168 }
169}
170
171impl ErrorInfo {
172 fn into_fields(self) -> Vec<(u8, String)> {
173 let mut fields = Vec::with_capacity(18);
174
175 fields.push((b'S', self.severity));
176 if let Some(value) = self.severity_nonlocalized {
177 fields.push((b'V', value));
178 }
179 fields.push((b'C', self.code));
180 fields.push((b'M', self.message));
181 if let Some(value) = self.detail {
182 fields.push((b'D', value));
183 }
184 if let Some(value) = self.hint {
185 fields.push((b'H', value));
186 }
187 if let Some(value) = self.position {
188 fields.push((b'P', value));
189 }
190 if let Some(value) = self.internal_position {
191 fields.push((b'p', value));
192 }
193 if let Some(value) = self.internal_query {
194 fields.push((b'q', value));
195 }
196 if let Some(value) = self.where_context {
197 fields.push((b'W', value));
198 }
199 if let Some(value) = self.file_name {
200 fields.push((b'F', value));
201 }
202 if let Some(value) = self.line {
203 fields.push((b'L', value.to_string()));
204 }
205 if let Some(value) = self.routine {
206 fields.push((b'R', value));
207 }
208 if let Some(value) = self.schema {
209 fields.push((b's', value));
210 }
211 if let Some(value) = self.table {
212 fields.push((b't', value));
213 }
214 if let Some(value) = self.column {
215 fields.push((b'c', value));
216 }
217 if let Some(value) = self.datatype {
218 fields.push((b'd', value));
219 }
220 if let Some(value) = self.constraint {
221 fields.push((b'n', value));
222 }
223
224 fields
225 }
226
227 pub fn is_fatal(&self) -> bool {
229 self.severity == "FATAL"
230 }
231}
232
233impl From<ErrorInfo> for ErrorResponse {
234 fn from(ei: ErrorInfo) -> ErrorResponse {
235 ErrorResponse::new(ei.into_fields())
236 }
237}
238
239impl From<ErrorResponse> for ErrorInfo {
240 fn from(value: ErrorResponse) -> Self {
241 let mut error_info = ErrorInfo::default();
242 for field in value.fields {
243 let (key, value) = field;
244 match key {
245 b'S' => {
246 error_info.severity = value;
247 }
248 b'C' => {
249 error_info.code = value;
250 }
251 b'M' => {
252 error_info.message = value;
253 }
254 b'D' => {
255 error_info.detail = Some(value);
256 }
257 b'H' => {
258 error_info.hint = Some(value);
259 }
260 b'P' => {
261 error_info.position = Some(value);
262 }
263 b'p' => {
264 error_info.internal_position = Some(value);
265 }
266 b'q' => {
267 error_info.internal_query = Some(value);
268 }
269 b'W' => {
270 error_info.where_context = Some(value);
271 }
272 b'F' => {
273 error_info.file_name = Some(value);
274 }
275 b'L' => {
276 error_info.line = Some(value.parse().unwrap_or(0));
277 }
278 b'R' => {
279 error_info.routine = Some(value);
280 }
281 b'V' => {
282 error_info.severity_nonlocalized = Some(value);
283 }
284 b's' => {
285 error_info.schema = Some(value);
286 }
287 b't' => {
288 error_info.table = Some(value);
289 }
290 b'c' => {
291 error_info.column = Some(value);
292 }
293 b'd' => {
294 error_info.datatype = Some(value);
295 }
296 b'n' => {
297 error_info.constraint = Some(value);
298 }
299 _ => {}
300 }
301 }
302 error_info
303 }
304}
305
306impl From<ErrorInfo> for NoticeResponse {
307 fn from(ei: ErrorInfo) -> NoticeResponse {
308 NoticeResponse::new(ei.into_fields())
309 }
310}
311
312impl From<PgWireError> for ErrorInfo {
313 fn from(error: PgWireError) -> Self {
314 match error {
315 PgWireError::UnsupportedProtocolVersion(_, _) => {
316 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
317 }
318 PgWireError::InvalidCancelRequest => {
319 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
320 }
321 PgWireError::InvalidMessageType(_) => {
322 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
323 }
324 PgWireError::InvalidTargetType(_) => {
325 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
326 }
327 PgWireError::MessageTooLarge(..) => {
328 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
329 }
330 PgWireError::InvalidTransactionStatus(_) => {
331 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
332 }
333 PgWireError::InvalidSSLRequestMessage => {
334 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
335 }
336 PgWireError::InvalidGssEncRequestMessage => {
337 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
338 }
339 PgWireError::InvalidStartupMessage => {
340 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
341 }
342 PgWireError::InvalidAuthenticationMessageCode(_) => {
343 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
344 }
345 PgWireError::FailedToCoercePasswordMessage => {
346 ErrorInfo::new("FATAL".to_owned(), "XX000".to_owned(), error.to_string())
347 }
348 PgWireError::InvalidSASLState => {
349 ErrorInfo::new("FATAL".to_owned(), "XX000".to_owned(), error.to_string())
350 }
351 PgWireError::UnsupportedSASLAuthMethod(_) => {
352 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
353 }
354 PgWireError::IoError(_) => {
355 ErrorInfo::new("FATAL".to_owned(), "58030".to_owned(), error.to_string())
356 }
357 PgWireError::PortalNotFound(_) => {
358 ErrorInfo::new("ERROR".to_owned(), "26000".to_owned(), error.to_string())
359 }
360 PgWireError::PortalNotStarted => {
361 ErrorInfo::new("ERROR".to_owned(), "XX000".to_owned(), error.to_string())
362 }
363 PgWireError::StatementNotFound(_) => {
364 ErrorInfo::new("ERROR".to_owned(), "26000".to_owned(), error.to_string())
365 }
366 PgWireError::ParameterIndexOutOfBound(_) => {
367 ErrorInfo::new("ERROR".to_owned(), "22023".to_owned(), error.to_string())
368 }
369 PgWireError::InvalidRustTypeForParameter(_) => {
370 ErrorInfo::new("ERROR".to_owned(), "22023".to_owned(), error.to_string())
371 }
372 PgWireError::FailedToParseParameter(_) => {
373 ErrorInfo::new("ERROR".to_owned(), "22P02".to_owned(), error.to_string())
374 }
375 PgWireError::InvalidScramMessage(_) => {
376 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
377 }
378 PgWireError::InvalidPassword(_) => {
379 ErrorInfo::new("FATAL".to_owned(), "28P01".to_owned(), error.to_string())
380 }
381 PgWireError::UnsupportedCertificateSignatureAlgorithm => {
382 ErrorInfo::new("FATAL".to_owned(), "0A000".to_owned(), error.to_string())
383 }
384 PgWireError::UserNameRequired => {
385 ErrorInfo::new("FATAL".to_owned(), "28000".to_owned(), error.to_string())
386 }
387 PgWireError::NotReadyForQuery => {
388 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
389 }
390 PgWireError::QueryCanceled => {
391 ErrorInfo::new("ERROR".to_owned(), "57014".to_owned(), error.to_string())
392 }
393 PgWireError::InvalidSecretKey => {
394 ErrorInfo::new("FATAL".to_owned(), "08P01".to_owned(), error.to_string())
395 }
396 PgWireError::ApiError(_) => {
397 ErrorInfo::new("ERROR".to_owned(), "XX000".to_owned(), error.to_string())
398 }
399 PgWireError::UserError(info) => *info,
400 PgWireError::InvalidOptionValue(_) => {
401 ErrorInfo::new("ERROR".to_owned(), "22023".to_owned(), error.to_string())
402 }
403 PgWireError::InvalidOauthMessage(_) => {
404 ErrorInfo::new("FATAL".to_string(), "08P01".to_string(), error.to_string())
405 }
406 PgWireError::OAuthAuthenticationFailed(_) => {
407 ErrorInfo::new("FATAL".to_string(), "08P01".to_string(), error.to_string())
408 }
409 PgWireError::OAuthValidationError(_) => {
410 ErrorInfo::new("FATAL".to_string(), "08P01".to_string(), error.to_string())
411 }
412 PgWireError::OauthAuthzIdError(_) => {
413 ErrorInfo::new("FATAL".to_string(), "0A000".to_string(), error.to_string())
414 }
415 }
416 }
417}
418
419#[cfg(feature = "client-api")]
420#[derive(Error, Debug)]
422pub enum PgWireClientError {
423 #[error("Failed to parse connection config, invalid value for: {0}")]
424 InvalidConfig(String),
425
426 #[error("Failed to parse connection config, unknown config: {0}")]
427 UnknownConfig(String),
428
429 #[error("Failed to parse utf8 value")]
430 InvalidUtf8ConfigValue(#[source] std::str::Utf8Error),
431
432 #[error("Unexpected EOF")]
433 UnexpectedEOF,
434
435 #[error("Unexpected remote message")]
436 UnexpectedMessage(Box<crate::messages::PgWireBackendMessage>),
437
438 #[error(transparent)]
439 IoError(#[from] std::io::Error),
440
441 #[error(transparent)]
442 PgWireError(#[from] PgWireError),
443
444 #[error("Error received from remote server: {0}")]
445 RemoteError(Box<ErrorInfo>),
446
447 #[error("Error parse command tag: {0}")]
448 InvalidTag(Box<dyn std::error::Error + Send + Sync>),
449
450 #[error("ALPN postgresql is required for direct connect.")]
451 AlpnRequired,
452
453 #[error("Failed to parse data: {0}")]
454 FromSqlError(Box<dyn std::error::Error + Send + Sync>),
455
456 #[error("Index out of bounds")]
457 DataRowIndexOutOfBounds,
458
459 #[error("Error from SCRAM authentication server: {0}")]
460 ScramError(String),
461
462 #[error("None of the server SASL auth methods is not supported by the client: {0:?}")]
463 UnsupportedSASLAuthMethods(Vec<String>),
464}
465
466#[cfg(feature = "client-api")]
467impl From<ErrorInfo> for PgWireClientError {
468 fn from(ei: ErrorInfo) -> PgWireClientError {
469 PgWireClientError::RemoteError(Box::new(ei))
470 }
471}
472
473#[cfg(feature = "client-api")]
474pub type PgWireClientResult<T> = Result<T, PgWireClientError>;
476
477#[cfg(test)]
478mod test {
479 use super::*;
480 use std::error::Error;
481
482 #[test]
483 fn test_error_notice_info() {
484 let error_info = ErrorInfo::new(
485 "FATAL".to_owned(),
486 "28P01".to_owned(),
487 "Password authentication failed".to_owned(),
488 );
489 assert_eq!("FATAL", error_info.severity);
490 assert_eq!("28P01", error_info.code);
491 assert_eq!("Password authentication failed", error_info.message);
492 assert!(error_info.file_name.is_none());
493 }
494
495 #[test]
496 fn test_error_send_and_sync() {
497 fn is_error_send_and_sync<T: Error + Send + Sync + 'static>() {}
498 is_error_send_and_sync::<PgWireError>();
499 #[cfg(feature = "client-api")]
500 is_error_send_and_sync::<PgWireClientError>();
501 }
502}