diff --git a/api_handler.go b/api_handler.go index 4f2c119f..291b1ee2 100644 --- a/api_handler.go +++ b/api_handler.go @@ -121,7 +121,7 @@ func (a *jobCancelEndpoint) Execute(ctx context.Context, req *jobCancelRequest) if errors.Is(err, river.ErrNotFound) { return nil, apierror.NewNotFoundJob(jobID) } - return nil, fmt.Errorf("error canceling jobs: %w", err) + return nil, err } updatedJobs[jobID] = job } diff --git a/go.mod b/go.mod index ba8d8788..e5e5b0d1 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/riverqueue/riverui go 1.22 require ( + github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 github.com/jackc/pgx/v5 v5.6.0 github.com/joho/godotenv v1.5.1 github.com/riverqueue/river v0.7.0 diff --git a/go.sum b/go.sum index 422b8837..9f7f7692 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= -github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= diff --git a/internal/apiendpoint/api_endpoint.go b/internal/apiendpoint/api_endpoint.go index 9bbe4e3e..5de0914c 100644 --- a/internal/apiendpoint/api_endpoint.go +++ b/internal/apiendpoint/api_endpoint.go @@ -12,6 +12,9 @@ import ( "net/http" "time" + "github.com/jackc/pgerrcode" + "github.com/jackc/pgx/v5/pgconn" + "github.com/riverqueue/riverui/internal/apierror" ) @@ -139,6 +142,18 @@ func executeAPIEndpoint[TReq any, TResp any](w http.ResponseWriter, r *http.Requ return nil }() if err != nil { + // Convert certain types of Postgres errors into something more + // user-friendly than an internal server error. + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + if pgErr.Code == pgerrcode.InsufficientPrivilege { + err = apierror.WithInternalError( + apierror.NewBadRequest("Insufficient database privilege to perform this operation."), + err, + ) + } + } + var apiErr apierror.Interface if errors.As(err, &apiErr) { logAttrs := []any{ diff --git a/internal/apiendpoint/api_endpoint_test.go b/internal/apiendpoint/api_endpoint_test.go index 287bd180..24ed15bf 100644 --- a/internal/apiendpoint/api_endpoint_test.go +++ b/internal/apiendpoint/api_endpoint_test.go @@ -5,11 +5,14 @@ import ( "context" "encoding/json" "errors" + "fmt" "net/http" "net/http/httptest" "testing" "time" + "github.com/jackc/pgerrcode" + "github.com/jackc/pgx/v5/pgconn" "github.com/stretchr/testify/require" "github.com/riverqueue/riverui/internal/apierror" @@ -99,6 +102,18 @@ func TestMountAndServe(t *testing.T) { requireStatusAndJSONResponse(t, http.StatusBadRequest, &apierror.APIError{Message: "Missing message value."}, bundle.recorder) }) + t.Run("InterpretedPostgresError", func(t *testing.T) { + t.Parallel() + + mux, bundle := setup(t) + + req := httptest.NewRequest(http.MethodPost, "/api/post-endpoint", + bytes.NewBuffer(mustMarshalJSON(t, &postRequest{MakePostgresError: true}))) + mux.ServeHTTP(bundle.recorder, req) + + requireStatusAndJSONResponse(t, http.StatusBadRequest, &apierror.APIError{Message: "Insufficient database privilege to perform this operation."}, bundle.recorder) + }) + t.Run("Timeout", func(t *testing.T) { t.Parallel() @@ -219,6 +234,7 @@ func (*postEndpoint) Meta() *EndpointMeta { type postRequest struct { MakeInternalError bool `json:"make_internal_error"` + MakePostgresError bool `json:"make_postgres_error"` Message string `json:"message"` } @@ -231,6 +247,11 @@ func (a *postEndpoint) Execute(_ context.Context, req *postRequest) (*postRespon return nil, errors.New("an internal error occurred") } + if req.MakePostgresError { + // Wrap the error to make it more realistic. + return nil, fmt.Errorf("error runnning Postgres query: %w", &pgconn.PgError{Code: pgerrcode.InsufficientPrivilege}) + } + if req.Message == "" { return nil, apierror.NewBadRequest("Missing message value.") }