From de6579ca704fedd3d6874a8cdb28902418597af7 Mon Sep 17 00:00:00 2001 From: Jonathan Gunawan Date: Thu, 16 Apr 2026 16:17:04 +0700 Subject: [PATCH 1/2] fix: preserve original error in Translate to maintain error chain --- error_translator.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/error_translator.go b/error_translator.go index 5f81350..b4b8938 100644 --- a/error_translator.go +++ b/error_translator.go @@ -2,6 +2,7 @@ package postgres import ( "encoding/json" + "fmt" "gorm.io/gorm" @@ -27,7 +28,7 @@ type ErrMessage struct { func (dialector Dialector) Translate(err error) error { if pgErr, ok := err.(*pgconn.PgError); ok { if translatedErr, found := errCodes[pgErr.Code]; found { - return translatedErr + return fmt.Errorf("%w: %w", translatedErr, pgErr) } return err } @@ -44,7 +45,7 @@ func (dialector Dialector) Translate(err error) error { } if translatedErr, found := errCodes[errMsg.Code]; found { - return translatedErr + return fmt.Errorf("%w: %s", translatedErr, errMsg.Message) } return err } From b0afc36e358f98c2a71fa8cf66f77737338f65fe Mon Sep 17 00:00:00 2001 From: Jonathan Gunawan Date: Thu, 16 Apr 2026 16:21:08 +0700 Subject: [PATCH 2/2] test: add test case to verify original pgError is preserved in error chain --- error_translator_test.go | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/error_translator_test.go b/error_translator_test.go index 27769ec..e860492 100644 --- a/error_translator_test.go +++ b/error_translator_test.go @@ -53,3 +53,59 @@ func TestDialector_Translate(t *testing.T) { }) } } + +func TestDialector_Translate_PreservesOriginalError(t *testing.T) { + tests := []struct { + name string + pgErr *pgconn.PgError + wantGormErr error + }{ + { + name: "it should preserve original pgError detail on ErrDuplicatedKey", + pgErr: &pgconn.PgError{ + Code: "23505", + Message: "duplicate key value violates unique constraint", + Detail: "Key (email)=(foo@bar.com) already exists.", + ConstraintName: "users_email_key", + }, + wantGormErr: gorm.ErrDuplicatedKey, + }, + { + name: "it should preserve original pgError detail on ErrForeignKeyViolated", + pgErr: &pgconn.PgError{ + Code: "23503", + Message: "insert or update on table violates foreign key constraint", + Detail: "Key (user_id)=(999) is not present in table \"users\".", + ConstraintName: "orders_user_id_fkey", + }, + wantGormErr: gorm.ErrForeignKeyViolated, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dialector := Dialector{} + err := dialector.Translate(tt.pgErr) + + // errors.Is() must still work after wrapping + if !errors.Is(err, tt.wantGormErr) { + t.Errorf("errors.Is() expected %v, got %v", tt.wantGormErr, err) + } + + // errors.As() must be able to unwrap original pgErr + var pgErr *pgconn.PgError + if !errors.As(err, &pgErr) { + t.Errorf("errors.As() failed: original *pgconn.PgError is not accessible") + return + } + + // original detail must be preserved + if pgErr.Detail != tt.pgErr.Detail { + t.Errorf("Detail expected %q, got %q", tt.pgErr.Detail, pgErr.Detail) + } + if pgErr.ConstraintName != tt.pgErr.ConstraintName { + t.Errorf("ConstraintName expected %q, got %q", tt.pgErr.ConstraintName, pgErr.ConstraintName) + } + }) + } +} \ No newline at end of file