rp_postgrest_error/
lib.rs

1extern crate alloc;
2
3use alloc::fmt;
4
5use serde::{Deserialize, Serialize};
6
7/// Represents the error response returned by `PostgREST`.
8#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Deserialize, Serialize)]
9pub struct ErrorResponse {
10    #[serde(default)]
11    pub message: String,
12    #[serde(default)]
13    pub code: String,
14    pub details: Option<String>,
15    pub hint: Option<String>,
16}
17
18/// Enum representing the different types of errors that can occur.
19#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
20pub enum PostgrestUtilError {
21    Postgres(PostgresError),
22    Postgrest(PostgrestError),
23    Custom(CustomError),
24}
25
26impl PostgrestUtilError {
27    /// Creates an error from an `ErrorResponse`.
28    #[must_use]
29    pub fn from_error_response(resp: ErrorResponse) -> Self {
30        if resp.code.starts_with("PGRST") {
31            Self::Postgrest(PostgrestError::from_response(resp))
32        } else if resp.code.len() == 5 || resp.code.starts_with("XX") {
33            Self::Postgres(PostgresError::from_response(resp))
34        } else {
35            Self::Custom(CustomError::from_response(resp))
36        }
37    }
38
39    /// Returns the corresponding HTTP status code for the error.
40    #[must_use]
41    pub const fn http_status_code(&self, is_authenticated: bool) -> u16 {
42        match self {
43            Self::Postgres(err) => err.http_status_code(is_authenticated),
44            Self::Postgrest(err) => err.http_status_code(),
45            Self::Custom(_) => 400, // Default to 400 for custom errors
46        }
47    }
48}
49
50impl core::fmt::Display for PostgrestUtilError {
51    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            Self::Postgres(err) => {
54                write!(fmt, "Postgres [{}]: {}", err.code, err.message)
55            }
56            Self::Postgrest(err) => {
57                write!(fmt, "Postgrest [{}]: {}", err.code, err.message)
58            }
59            Self::Custom(err) => write!(fmt, "Custom [{}]: {}", err.code, err.message),
60        }
61    }
62}
63
64impl core::error::Error for PostgrestUtilError {}
65
66/// Represents an error returned by `PostgreSQL`.
67#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
68pub struct PostgresError {
69    pub code: PostgresErrorCode,
70    pub message: String,
71    pub details: Option<String>,
72    pub hint: Option<String>,
73}
74
75impl PostgresError {
76    #[must_use]
77    pub fn from_response(resp: ErrorResponse) -> Self {
78        let code = PostgresErrorCode::from_code(&resp.code);
79        Self {
80            code,
81            message: resp.message,
82            details: resp.details,
83            hint: resp.hint,
84        }
85    }
86
87    #[must_use]
88    pub const fn http_status_code(&self, is_authenticated: bool) -> u16 {
89        self.code.http_status_code(is_authenticated)
90    }
91}
92
93/// Enum representing `PostgreSQL` error codes.
94#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
95pub enum PostgresErrorCode {
96    // Specific codes
97    NotNullViolation,       // 23502
98    ForeignKeyViolation,    // 23503
99    UniqueViolation,        // 23505
100    ReadOnlySqlTransaction, // 25006
101    UndefinedFunction,      // 42883
102    UndefinedTable,         // 42P01
103    InfiniteRecursion,      // 42P17
104    InsufficientPrivilege,  // 42501
105    ConfigLimitExceeded,    // 53400
106    RaiseException,         // P0001
107    // Patterns
108    ConnectionException,                // 08*
109    TriggeredActionException,           // 09*
110    InvalidGrantor,                     // 0L*
111    InvalidRoleSpecification,           // 0P*
112    InvalidTransactionState,            // 25*
113    InvalidAuthorizationSpecification,  // 28*
114    InvalidTransactionTermination,      // 2D*
115    ExternalRoutineException,           // 38*
116    ExternalRoutineInvocationException, // 39*
117    SavepointException,                 // 3B*
118    TransactionRollback,                // 40*
119    InsufficientResources,              // 53*
120    ProgramLimitExceeded,               // 54*
121    ObjectNotInPrerequisiteState,       // 55*
122    OperatorIntervention,               // 57*
123    SystemError,                        // 58*
124    ConfigFileError,                    // F0*
125    FdwError,                           // HV*
126    PlpgsqlError,                       // P0*
127    InternalError,                      // XX*
128    // Other errors
129    Other(String), // Any other code
130}
131
132impl PostgresErrorCode {
133    #[must_use]
134    pub fn from_code(code: &str) -> Self {
135        match code {
136            // Specific codes
137            "23502" => Self::NotNullViolation,
138            "23503" => Self::ForeignKeyViolation,
139            "23505" => Self::UniqueViolation,
140            "25006" => Self::ReadOnlySqlTransaction,
141            "42883" => Self::UndefinedFunction,
142            "42P01" => Self::UndefinedTable,
143            "42P17" => Self::InfiniteRecursion,
144            "42501" => Self::InsufficientPrivilege,
145            "53400" => Self::ConfigLimitExceeded,
146            "P0001" => Self::RaiseException,
147            _ => {
148                // Check for patterns
149                if code.starts_with("08") {
150                    Self::ConnectionException
151                } else if code.starts_with("09") {
152                    Self::TriggeredActionException
153                } else if code.starts_with("0L") {
154                    Self::InvalidGrantor
155                } else if code.starts_with("0P") {
156                    Self::InvalidRoleSpecification
157                } else if code.starts_with("25") {
158                    Self::InvalidTransactionState
159                } else if code.starts_with("28") {
160                    Self::InvalidAuthorizationSpecification
161                } else if code.starts_with("2D") {
162                    Self::InvalidTransactionTermination
163                } else if code.starts_with("38") {
164                    Self::ExternalRoutineException
165                } else if code.starts_with("39") {
166                    Self::ExternalRoutineInvocationException
167                } else if code.starts_with("3B") {
168                    Self::SavepointException
169                } else if code.starts_with("40") {
170                    Self::TransactionRollback
171                } else if code.starts_with("53") {
172                    Self::InsufficientResources
173                } else if code.starts_with("54") {
174                    Self::ProgramLimitExceeded
175                } else if code.starts_with("55") {
176                    Self::ObjectNotInPrerequisiteState
177                } else if code.starts_with("57") {
178                    Self::OperatorIntervention
179                } else if code.starts_with("58") {
180                    Self::SystemError
181                } else if code.starts_with("F0") {
182                    Self::ConfigFileError
183                } else if code.starts_with("HV") {
184                    Self::FdwError
185                } else if code.starts_with("P0") {
186                    Self::PlpgsqlError
187                } else if code.starts_with("XX") {
188                    Self::InternalError
189                } else {
190                    Self::Other(code.to_owned())
191                }
192            }
193        }
194    }
195
196    #[must_use]
197    pub const fn http_status_code(&self, is_authenticated: bool) -> u16 {
198        match self {
199            // 500 status codes
200            Self::TriggeredActionException
201            | Self::InvalidTransactionState
202            | Self::InvalidTransactionTermination
203            | Self::ExternalRoutineException
204            | Self::ExternalRoutineInvocationException
205            | Self::SavepointException
206            | Self::TransactionRollback
207            | Self::ProgramLimitExceeded
208            | Self::ObjectNotInPrerequisiteState
209            | Self::OperatorIntervention
210            | Self::SystemError
211            | Self::ConfigFileError
212            | Self::FdwError
213            | Self::PlpgsqlError
214            | Self::InternalError
215            | Self::ConfigLimitExceeded
216            | Self::InfiniteRecursion => 500,
217
218            // 503 status codes
219            Self::ConnectionException | Self::InsufficientResources => 503,
220
221            // 403 status codes
222            Self::InvalidGrantor
223            | Self::InvalidRoleSpecification
224            | Self::InvalidAuthorizationSpecification => 403,
225
226            // 404 status codes
227            Self::UndefinedFunction | Self::UndefinedTable => 404,
228
229            // 400 status codes
230            Self::NotNullViolation | Self::RaiseException | Self::Other(_) => 400,
231
232            // 409 status codes
233            Self::ForeignKeyViolation | Self::UniqueViolation => 409,
234
235            // 405 status code
236            Self::ReadOnlySqlTransaction => 405,
237
238            // Conditional status code
239            Self::InsufficientPrivilege => {
240                if is_authenticated {
241                    403
242                } else {
243                    401
244                }
245            }
246        }
247    }
248}
249
250impl core::fmt::Display for PostgresErrorCode {
251    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
252        match self {
253            Self::NotNullViolation => write!(fmt, "23502"),
254            Self::ForeignKeyViolation => write!(fmt, "23503"),
255            Self::UniqueViolation => write!(fmt, "23505"),
256            Self::ReadOnlySqlTransaction => write!(fmt, "25006"),
257            Self::UndefinedFunction => write!(fmt, "42883"),
258            Self::UndefinedTable => write!(fmt, "42P01"),
259            Self::InfiniteRecursion => write!(fmt, "42P17"),
260            Self::InsufficientPrivilege => write!(fmt, "42501"),
261            Self::ConfigLimitExceeded => write!(fmt, "53400"),
262            Self::RaiseException => write!(fmt, "P0001"),
263            Self::ConnectionException => write!(fmt, "08*"),
264            Self::TriggeredActionException => write!(fmt, "09*"),
265            Self::InvalidGrantor => write!(fmt, "0L*"),
266            Self::InvalidRoleSpecification => write!(fmt, "0P*"),
267            Self::InvalidTransactionState => write!(fmt, "25*"),
268            Self::InvalidAuthorizationSpecification => write!(fmt, "28*"),
269            Self::InvalidTransactionTermination => write!(fmt, "2D*"),
270            Self::ExternalRoutineException => write!(fmt, "38*"),
271            Self::ExternalRoutineInvocationException => write!(fmt, "39*"),
272            Self::SavepointException => write!(fmt, "3B*"),
273            Self::TransactionRollback => write!(fmt, "40*"),
274            Self::InsufficientResources => write!(fmt, "53*"),
275            Self::ProgramLimitExceeded => write!(fmt, "54*"),
276            Self::ObjectNotInPrerequisiteState => write!(fmt, "55*"),
277            Self::OperatorIntervention => write!(fmt, "57*"),
278            Self::SystemError => write!(fmt, "58*"),
279            Self::ConfigFileError => write!(fmt, "F0*"),
280            Self::FdwError => write!(fmt, "HV*"),
281            Self::PlpgsqlError => write!(fmt, "P0*"),
282            Self::InternalError => write!(fmt, "XX*"),
283            Self::Other(code) => write!(fmt, "{code}"),
284        }
285    }
286}
287
288/// Represents an error returned by `PostgREST`.
289#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
290pub struct PostgrestError {
291    pub code: PostgrestErrorCode,
292    pub message: String,
293    pub details: Option<String>,
294    pub hint: Option<String>,
295}
296
297impl PostgrestError {
298    #[must_use]
299    pub fn from_response(resp: ErrorResponse) -> Self {
300        let code = PostgrestErrorCode::from_code(&resp.code);
301        Self {
302            code,
303            message: resp.message,
304            details: resp.details,
305            hint: resp.hint,
306        }
307    }
308
309    #[must_use]
310    pub const fn http_status_code(&self) -> u16 {
311        self.code.http_status_code()
312    }
313}
314
315/// Enum representing `PostgREST` error codes.
316#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
317pub enum PostgrestErrorCode {
318    // Group 0 - Connection
319    CouldNotConnectDatabase,    // PGRST000
320    InternalConnectionError,    // PGRST001
321    CouldNotConnectSchemaCache, // PGRST002
322    RequestTimedOut,            // PGRST003
323
324    // Group 1 - API Request
325    ParsingErrorQueryParameter,         // PGRST100
326    FunctionOnlySupportsGetOrPost,      // PGRST101
327    InvalidRequestBody,                 // PGRST102
328    InvalidRange,                       // PGRST103
329    InvalidPutRequest,                  // PGRST105
330    SchemaNotInConfig,                  // PGRST106
331    InvalidContentType,                 // PGRST107
332    FilterOnMissingEmbeddedResource,    // PGRST108
333    LimitedUpdateDeleteWithoutOrdering, // PGRST109
334    LimitedUpdateDeleteExceededMaxRows, // PGRST110
335    InvalidResponseHeaders,             // PGRST111
336    InvalidStatusCode,                  // PGRST112
337    UpsertPutWithLimitsOffsets,         // PGRST114
338    UpsertPutPrimaryKeyMismatch,        // PGRST115
339    InvalidSingularResponse,            // PGRST116
340    UnsupportedHttpVerb,                // PGRST117
341    CannotOrderByRelatedTable,          // PGRST118
342    CannotSpreadRelatedTable,           // PGRST119
343    InvalidEmbeddedResourceFilter,      // PGRST120
344    InvalidRaiseErrorJson,              // PGRST121
345    InvalidPreferHeader,                // PGRST122
346
347    // Group 2 - Schema Cache
348    RelationshipNotFound,        // PGRST200
349    AmbiguousEmbedding,          // PGRST201
350    FunctionNotFound,            // PGRST202
351    OverloadedFunctionAmbiguous, // PGRST203
352    ColumnNotFound,              // PGRST204
353
354    // Group 3 - JWT
355    JwtSecretMissing,      // PGRST300
356    JwtInvalid,            // PGRST301
357    AnonymousRoleDisabled, // PGRST302
358
359    // Group X - Internal
360    InternalLibraryError, // PGRSTX00
361
362    // Other errors
363    Other(String), // Any other code
364}
365
366impl PostgrestErrorCode {
367    #[must_use]
368    pub fn from_code(code: &str) -> Self {
369        match code {
370            "PGRST000" => Self::CouldNotConnectDatabase,
371            "PGRST001" => Self::InternalConnectionError,
372            "PGRST002" => Self::CouldNotConnectSchemaCache,
373            "PGRST003" => Self::RequestTimedOut,
374            "PGRST100" => Self::ParsingErrorQueryParameter,
375            "PGRST101" => Self::FunctionOnlySupportsGetOrPost,
376            "PGRST102" => Self::InvalidRequestBody,
377            "PGRST103" => Self::InvalidRange,
378            "PGRST105" => Self::InvalidPutRequest,
379            "PGRST106" => Self::SchemaNotInConfig,
380            "PGRST107" => Self::InvalidContentType,
381            "PGRST108" => Self::FilterOnMissingEmbeddedResource,
382            "PGRST109" => Self::LimitedUpdateDeleteWithoutOrdering,
383            "PGRST110" => Self::LimitedUpdateDeleteExceededMaxRows,
384            "PGRST111" => Self::InvalidResponseHeaders,
385            "PGRST112" => Self::InvalidStatusCode,
386            "PGRST114" => Self::UpsertPutWithLimitsOffsets,
387            "PGRST115" => Self::UpsertPutPrimaryKeyMismatch,
388            "PGRST116" => Self::InvalidSingularResponse,
389            "PGRST117" => Self::UnsupportedHttpVerb,
390            "PGRST118" => Self::CannotOrderByRelatedTable,
391            "PGRST119" => Self::CannotSpreadRelatedTable,
392            "PGRST120" => Self::InvalidEmbeddedResourceFilter,
393            "PGRST121" => Self::InvalidRaiseErrorJson,
394            "PGRST122" => Self::InvalidPreferHeader,
395            "PGRST200" => Self::RelationshipNotFound,
396            "PGRST201" => Self::AmbiguousEmbedding,
397            "PGRST202" => Self::FunctionNotFound,
398            "PGRST203" => Self::OverloadedFunctionAmbiguous,
399            "PGRST204" => Self::ColumnNotFound,
400            "PGRST300" => Self::JwtSecretMissing,
401            "PGRST301" => Self::JwtInvalid,
402            "PGRST302" => Self::AnonymousRoleDisabled,
403            "PGRSTX00" => Self::InternalLibraryError,
404            _ => Self::Other(code.to_owned()),
405        }
406    }
407
408    #[must_use]
409    pub const fn http_status_code(&self) -> u16 {
410        match self {
411            // 500 status codes
412            Self::InternalConnectionError
413            | Self::CouldNotConnectSchemaCache
414            | Self::InvalidResponseHeaders
415            | Self::InvalidStatusCode
416            | Self::InvalidRaiseErrorJson
417            | Self::JwtSecretMissing
418            | Self::InternalLibraryError
419            | Self::Other(_) => 500,
420            // 503
421            Self::CouldNotConnectDatabase => 503,
422            // 504
423            Self::RequestTimedOut => 504,
424            // 400
425            Self::ParsingErrorQueryParameter
426            | Self::InvalidRequestBody
427            | Self::FilterOnMissingEmbeddedResource
428            | Self::LimitedUpdateDeleteWithoutOrdering
429            | Self::LimitedUpdateDeleteExceededMaxRows
430            | Self::UpsertPutWithLimitsOffsets
431            | Self::UpsertPutPrimaryKeyMismatch
432            | Self::CannotOrderByRelatedTable
433            | Self::CannotSpreadRelatedTable
434            | Self::InvalidEmbeddedResourceFilter
435            | Self::InvalidPreferHeader
436            | Self::RelationshipNotFound
437            | Self::ColumnNotFound => 400,
438            // 405
439            Self::FunctionOnlySupportsGetOrPost
440            | Self::InvalidPutRequest
441            | Self::UnsupportedHttpVerb => 405,
442            // 416
443            Self::InvalidRange => 416,
444            // 406
445            Self::SchemaNotInConfig | Self::InvalidSingularResponse => 406,
446            // 415
447            Self::InvalidContentType => 415,
448            // 300
449            Self::AmbiguousEmbedding | Self::OverloadedFunctionAmbiguous => 300,
450            // 404
451            Self::FunctionNotFound => 404,
452            // 401
453            Self::JwtInvalid | Self::AnonymousRoleDisabled => 401,
454        }
455    }
456}
457
458impl core::fmt::Display for PostgrestErrorCode {
459    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
460        match self {
461            Self::CouldNotConnectDatabase => write!(fmt, "PGRST000"),
462            Self::InternalConnectionError => write!(fmt, "PGRST001"),
463            Self::CouldNotConnectSchemaCache => write!(fmt, "PGRST002"),
464            Self::RequestTimedOut => write!(fmt, "PGRST003"),
465            Self::ParsingErrorQueryParameter => write!(fmt, "PGRST100"),
466            Self::FunctionOnlySupportsGetOrPost => write!(fmt, "PGRST101"),
467            Self::InvalidRequestBody => write!(fmt, "PGRST102"),
468            Self::InvalidRange => write!(fmt, "PGRST103"),
469            Self::InvalidPutRequest => write!(fmt, "PGRST105"),
470            Self::SchemaNotInConfig => write!(fmt, "PGRST106"),
471            Self::InvalidContentType => write!(fmt, "PGRST107"),
472            Self::FilterOnMissingEmbeddedResource => write!(fmt, "PGRST108"),
473            Self::LimitedUpdateDeleteWithoutOrdering => write!(fmt, "PGRST109"),
474            Self::LimitedUpdateDeleteExceededMaxRows => write!(fmt, "PGRST110"),
475            Self::InvalidResponseHeaders => write!(fmt, "PGRST111"),
476            Self::InvalidStatusCode => write!(fmt, "PGRST112"),
477            Self::UpsertPutWithLimitsOffsets => write!(fmt, "PGRST114"),
478            Self::UpsertPutPrimaryKeyMismatch => write!(fmt, "PGRST115"),
479            Self::InvalidSingularResponse => write!(fmt, "PGRST116"),
480            Self::UnsupportedHttpVerb => write!(fmt, "PGRST117"),
481            Self::CannotOrderByRelatedTable => write!(fmt, "PGRST118"),
482            Self::CannotSpreadRelatedTable => write!(fmt, "PGRST119"),
483            Self::InvalidEmbeddedResourceFilter => write!(fmt, "PGRST120"),
484            Self::InvalidRaiseErrorJson => write!(fmt, "PGRST121"),
485            Self::InvalidPreferHeader => write!(fmt, "PGRST122"),
486            Self::RelationshipNotFound => write!(fmt, "PGRST200"),
487            Self::AmbiguousEmbedding => write!(fmt, "PGRST201"),
488            Self::FunctionNotFound => write!(fmt, "PGRST202"),
489            Self::OverloadedFunctionAmbiguous => write!(fmt, "PGRST203"),
490            Self::ColumnNotFound => write!(fmt, "PGRST204"),
491            Self::JwtSecretMissing => write!(fmt, "PGRST300"),
492            Self::JwtInvalid => write!(fmt, "PGRST301"),
493            Self::AnonymousRoleDisabled => write!(fmt, "PGRST302"),
494            Self::InternalLibraryError => write!(fmt, "PGRSTX00"),
495            Self::Other(code) => write!(fmt, "{code}"),
496        }
497    }
498}
499
500/// Represents a custom error.
501#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
502pub struct CustomError {
503    pub code: String,
504    pub message: String,
505    pub details: Option<String>,
506    pub hint: Option<String>,
507}
508
509impl CustomError {
510    #[must_use]
511    pub fn from_response(resp: ErrorResponse) -> Self {
512        Self {
513            code: resp.code,
514            message: resp.message,
515            details: resp.details,
516            hint: resp.hint,
517        }
518    }
519}
520
521#[cfg(test)]
522#[expect(clippy::panic, reason = "Allowed in test code for simplicity")]
523#[expect(
524    clippy::wildcard_enum_match_arm,
525    reason = "Allowed in test code for simplicity"
526)]
527mod tests {
528    use super::*;
529
530    #[test]
531    fn test_postgres_error_transformation() {
532        // Test a specific PostgreSQL error code: 23505 - Unique Violation
533        let error_response = ErrorResponse {
534            message: "duplicate key value violates unique constraint".to_owned(),
535            code: "23505".to_owned(),
536            details: Some("Key (id)=(1) already exists.".to_owned()),
537            hint: None,
538        };
539        let is_authenticated = true;
540        let error = PostgrestUtilError::from_error_response(error_response);
541
542        match error {
543            PostgrestUtilError::Postgres(pg_error) => {
544                assert_eq!(pg_error.code, PostgresErrorCode::UniqueViolation);
545                assert_eq!(pg_error.http_status_code(is_authenticated), 409);
546                assert_eq!(
547                    pg_error.message,
548                    "duplicate key value violates unique constraint"
549                );
550                assert_eq!(
551                    pg_error.details,
552                    Some("Key (id)=(1) already exists.".to_owned())
553                );
554            }
555            _ => panic!("Expected PostgresError"),
556        }
557    }
558
559    #[test]
560    fn test_postgrest_error_transformation() {
561        // Test a PostgREST error code: PGRST116 - Invalid Singular Response
562        let error_response = ErrorResponse {
563            message: "More than one item found".to_owned(),
564            code: "PGRST116".to_owned(),
565            details: None,
566            hint: Some("Use limit to restrict the number of results.".to_owned()),
567        };
568        let error = PostgrestUtilError::from_error_response(error_response);
569
570        match error {
571            PostgrestUtilError::Postgrest(pgrst_error) => {
572                assert_eq!(
573                    pgrst_error.code,
574                    PostgrestErrorCode::InvalidSingularResponse
575                );
576                assert_eq!(pgrst_error.http_status_code(), 406);
577                assert_eq!(pgrst_error.message, "More than one item found");
578                assert_eq!(
579                    pgrst_error.hint,
580                    Some("Use limit to restrict the number of results.".to_owned())
581                );
582            }
583            _ => panic!("Expected PostgrestError"),
584        }
585    }
586
587    #[test]
588    fn test_custom_error_transformation() {
589        // Test a custom error code not matching any known codes
590        let error_response = ErrorResponse {
591            message: "Custom error message".to_owned(),
592            code: "CUSTOM123".to_owned(),
593            details: Some("Some custom details.".to_owned()),
594            hint: Some("Some custom hint.".to_owned()),
595        };
596        let error = PostgrestUtilError::from_error_response(error_response);
597
598        match error {
599            PostgrestUtilError::Custom(custom_error) => {
600                assert_eq!(custom_error.code, "CUSTOM123");
601                assert_eq!(custom_error.message, "Custom error message");
602                assert_eq!(
603                    custom_error.details,
604                    Some("Some custom details.".to_owned())
605                );
606                assert_eq!(custom_error.hint, Some("Some custom hint.".to_owned()));
607            }
608            _ => panic!("Expected CustomError"),
609        }
610    }
611
612    #[test]
613    fn test_insufficient_privilege_error_authenticated() {
614        // Test error code 42501 - Insufficient Privilege when authenticated
615        let error_response = ErrorResponse {
616            message: "permission denied for relation".to_owned(),
617            code: "42501".to_owned(),
618            details: None,
619            hint: None,
620        };
621        let is_authenticated = true;
622        let error = PostgrestUtilError::from_error_response(error_response);
623
624        match error {
625            PostgrestUtilError::Postgres(pg_error) => {
626                assert_eq!(pg_error.code, PostgresErrorCode::InsufficientPrivilege);
627                assert_eq!(pg_error.http_status_code(is_authenticated), 403);
628            }
629            _ => panic!("Expected PostgresError"),
630        }
631    }
632
633    #[test]
634    fn test_insufficient_privilege_error_unauthenticated() {
635        // Test error code 42501 - Insufficient Privilege when not authenticated
636        let error_response = ErrorResponse {
637            message: "permission denied for relation".to_owned(),
638            code: "42501".to_owned(),
639            details: None,
640            hint: None,
641        };
642        let is_authenticated = false;
643        let error = PostgrestUtilError::from_error_response(error_response);
644
645        match error {
646            PostgrestUtilError::Postgres(pg_error) => {
647                assert_eq!(pg_error.code, PostgresErrorCode::InsufficientPrivilege);
648                assert_eq!(pg_error.http_status_code(is_authenticated), 401);
649            }
650            _ => panic!("Expected PostgresError"),
651        }
652    }
653
654    #[test]
655    fn test_pattern_error_transformation() {
656        // Test an error code that matches a pattern: 08006 - Connection Exception
657        let error_response = ErrorResponse {
658            message: "An error occurred while connecting to the database".to_owned(),
659            code: "08006".to_owned(),
660            details: None,
661            hint: None,
662        };
663        let is_authenticated = true;
664        let error = PostgrestUtilError::from_error_response(error_response);
665
666        match error {
667            PostgrestUtilError::Postgres(pg_error) => {
668                assert_eq!(pg_error.code, PostgresErrorCode::ConnectionException);
669                assert_eq!(pg_error.http_status_code(is_authenticated), 503);
670            }
671            _ => panic!("Expected PostgresError"),
672        }
673    }
674
675    #[test]
676    fn test_postgrest_internal_error() {
677        // Test PostgREST internal error code: PGRSTX00
678        let error_response = ErrorResponse {
679            message: "Internal server error".to_owned(),
680            code: "PGRSTX00".to_owned(),
681            details: Some("An unexpected error occurred.".to_owned()),
682            hint: None,
683        };
684        let error = PostgrestUtilError::from_error_response(error_response);
685
686        match error {
687            PostgrestUtilError::Postgrest(pgrst_error) => {
688                assert_eq!(pgrst_error.code, PostgrestErrorCode::InternalLibraryError);
689                assert_eq!(pgrst_error.http_status_code(), 500);
690            }
691            _ => panic!("Expected PostgrestError"),
692        }
693    }
694
695    #[test]
696    fn test_unknown_postgres_error_code() {
697        // Test an unknown PostgreSQL error code
698        let error_response = ErrorResponse {
699            message: "Unknown error".to_owned(),
700            code: "99999".to_owned(),
701            details: None,
702            hint: None,
703        };
704        let is_authenticated = true;
705        let error = PostgrestUtilError::from_error_response(error_response);
706
707        match &error {
708            PostgrestUtilError::Postgres(pg_error) => {
709                match &pg_error.code {
710                    PostgresErrorCode::Other(code) => assert_eq!(code, "99999"),
711                    _ => panic!("Expected Other variant"),
712                }
713                assert_eq!(pg_error.http_status_code(is_authenticated), 400);
714            }
715            _ => panic!("Expected PostgresError"),
716        }
717    }
718
719    #[test]
720    fn test_unknown_postgrest_error_code() {
721        // Test an unknown PostgREST error code
722        let error_response = ErrorResponse {
723            message: "Unknown PostgREST error".to_owned(),
724            code: "PGRST999".to_owned(),
725            details: None,
726            hint: None,
727        };
728        let error = PostgrestUtilError::from_error_response(error_response);
729
730        match &error {
731            PostgrestUtilError::Postgrest(pgrst_error) => {
732                match &pgrst_error.code {
733                    PostgrestErrorCode::Other(code) => assert_eq!(code, "PGRST999"),
734                    _ => panic!("Expected Other variant"),
735                }
736                assert_eq!(pgrst_error.http_status_code(), 500);
737            }
738            _ => panic!("Expected PostgrestError"),
739        }
740    }
741
742    #[test]
743    fn test_raise_exception_error() {
744        // Test error code P0001 - Raise Exception
745        let error_response = ErrorResponse {
746            message: "I refuse!".to_owned(),
747            code: "P0001".to_owned(),
748            details: Some("Pretty simple".to_owned()),
749            hint: Some("There is nothing you can do.".to_owned()),
750        };
751        let is_authenticated = true;
752        let error = PostgrestUtilError::from_error_response(error_response);
753
754        match error {
755            PostgrestUtilError::Postgres(pg_error) => {
756                assert_eq!(pg_error.code, PostgresErrorCode::RaiseException);
757                assert_eq!(pg_error.http_status_code(is_authenticated), 400);
758                assert_eq!(pg_error.message, "I refuse!");
759                assert_eq!(pg_error.details, Some("Pretty simple".to_owned()));
760                assert_eq!(
761                    pg_error.hint,
762                    Some("There is nothing you can do.".to_owned())
763                );
764            }
765            _ => panic!("Expected PostgresError"),
766        }
767    }
768
769    #[test]
770    fn test_custom_status_code_in_raise() {
771        // Test a custom error using RAISE with PTxyz SQLSTATE
772        let error_response = ErrorResponse {
773            message: "Payment Required".to_owned(),
774            code: "PT402".to_owned(),
775            details: Some("Quota exceeded".to_owned()),
776            hint: Some("Upgrade your plan".to_owned()),
777        };
778        let error = PostgrestUtilError::Custom(CustomError::from_response(error_response));
779
780        match error {
781            PostgrestUtilError::Custom(custom_error) => {
782                assert_eq!(custom_error.code, "PT402");
783                assert_eq!(custom_error.message, "Payment Required");
784                assert_eq!(custom_error.details, Some("Quota exceeded".to_owned()));
785                assert_eq!(custom_error.hint, Some("Upgrade your plan".to_owned()));
786            }
787            _ => panic!("Expected CustomError"),
788        }
789    }
790
791    #[test]
792    fn test_error_display_trait() {
793        // Test that the Display trait is implemented correctly
794        let error_response = ErrorResponse {
795            message: "Not null violation".to_owned(),
796            code: "23502".to_owned(),
797            details: None,
798            hint: None,
799        };
800        let error = PostgrestUtilError::from_error_response(error_response);
801
802        assert_eq!(format!("{error}"), "Postgres [23502]: Not null violation");
803    }
804
805    #[test]
806    fn test_error_trait() {
807        // Test that the Error trait is implemented
808        let error_response = ErrorResponse {
809            message: "Some error".to_owned(),
810            code: "23502".to_owned(),
811            details: None,
812            hint: None,
813        };
814        let error = PostgrestUtilError::from_error_response(error_response);
815
816        let std_error: &dyn core::error::Error = &error;
817        assert_eq!(std_error.to_string(), "Postgres [23502]: Some error");
818    }
819
820    #[test]
821    fn non_standard_error() {
822        let error_response = ErrorResponse {
823            message: "no Route matched with those values".to_owned(),
824            code: String::new(),
825            details: None,
826            hint: None,
827        };
828        let error = PostgrestUtilError::from_error_response(error_response);
829        let std_error: &dyn core::error::Error = &error;
830        assert_eq!(
831            std_error.to_string(),
832            "Custom []: no Route matched with those values"
833        );
834    }
835}