From 67180a55f77c717b418457d68931f29488368b80 Mon Sep 17 00:00:00 2001 From: Arvindh <30824765+arvindh123@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:27:06 +0530 Subject: [PATCH] NOISSUE - Update Errors (#374) * update MG errors Signed-off-by: Arvindh * update MG errors Signed-off-by: Arvindh * sync with supermq main Signed-off-by: Arvindh * update MG errors Signed-off-by: Arvindh --------- Signed-off-by: Arvindh --- .github/workflows/tests.yaml | 8 - alarms/alarms_test.go | 2 +- alarms/api/transport.go | 4 +- alarms/service_test.go | 2 +- bootstrap/api/endpoint_test.go | 28 +- bootstrap/api/transport.go | 23 +- bootstrap/postgres/configs.go | 8 +- bootstrap/service.go | 8 +- consumers/notifiers/api/endpoint_test.go | 10 +- consumers/notifiers/api/transport.go | 4 +- consumers/notifiers/postgres/subscriptions.go | 2 +- .../notifiers/postgres/subscriptions_test.go | 2 +- consumers/notifiers/service.go | 8 +- consumers/notifiers/service_test.go | 3 +- consumers/writers/postgres/consumer.go | 3 + consumers/writers/timescale/consumer.go | 3 + .../prometheus/grafana/example-dashboard.json | 4 +- internal/email/email.go | 2 +- pkg/errors/README.md | 5 - pkg/errors/doc.go | 5 - pkg/errors/errors.go | 128 ------- pkg/errors/errors_test.go | 352 ------------------ pkg/errors/repository/types.go | 39 -- pkg/errors/sdk_errors.go | 123 ------ pkg/errors/sdk_errors_test.go | 206 ---------- pkg/errors/service/types.go | 78 ---- pkg/errors/types.go | 32 -- pkg/reltime/reltime.go | 2 +- pkg/reltime/reltime_test.go | 2 +- pkg/schedule/schedule.go | 4 +- pkg/sdk/bootstrap_test.go | 12 +- pkg/sdk/consumers_test.go | 14 +- pkg/sdk/messages_test.go | 6 +- provision/api/endpoint_test.go | 2 +- provision/api/requests.go | 4 +- provision/api/transport.go | 6 +- re/api/endpoints_test.go | 2 +- re/api/transport.go | 16 +- readers/api/grpc/client.go | 2 +- readers/api/grpc/endpoint_test.go | 2 +- readers/api/http/endpoint_test.go | 4 +- readers/api/http/transport.go | 39 +- readers/postgres/messages.go | 3 + readers/timescale/messages.go | 3 + reports/api/endpoints_test.go | 2 +- reports/api/transport.go | 12 +- reports/postgres/errors.go | 27 ++ reports/postgres/reports.go | 2 +- reports/postgres/repository.go | 58 ++- reports/postgres/repository_test.go | 5 +- 50 files changed, 188 insertions(+), 1133 deletions(-) delete mode 100644 pkg/errors/README.md delete mode 100644 pkg/errors/doc.go delete mode 100644 pkg/errors/errors.go delete mode 100644 pkg/errors/errors_test.go delete mode 100644 pkg/errors/repository/types.go delete mode 100644 pkg/errors/sdk_errors.go delete mode 100644 pkg/errors/sdk_errors_test.go delete mode 100644 pkg/errors/service/types.go delete mode 100644 pkg/errors/types.go create mode 100644 reports/postgres/errors.go diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 84226da4a..4b8ea012f 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -100,9 +100,6 @@ jobs: internal: - "internal/**" - pkg-errors: - - "pkg/errors/**" - pkg-events: - "pkg/events/**" - "pkg/messaging/**" @@ -161,11 +158,6 @@ jobs: run: | go test --race -v -count=1 -coverprofile=coverage/internal.out ./internal/... - - name: Run pkg errors tests - if: steps.changes.outputs.pkg-errors == 'true' || steps.changes.outputs.workflow == 'true' - run: | - go test --race -v -count=1 -coverprofile=coverage/pkg-errors.out ./pkg/errors/... - - name: Run pkg sdk tests if: steps.changes.outputs.pkg-sdk == 'true' || steps.changes.outputs.workflow == 'true' run: | diff --git a/alarms/alarms_test.go b/alarms/alarms_test.go index 2fc3ef72e..96c75f8de 100644 --- a/alarms/alarms_test.go +++ b/alarms/alarms_test.go @@ -9,7 +9,7 @@ import ( "github.com/absmach/magistrala/alarms" "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/supermq/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/alarms/api/transport.go b/alarms/api/transport.go index 3087a18aa..d94596282 100644 --- a/alarms/api/transport.go +++ b/alarms/api/transport.go @@ -195,12 +195,12 @@ func decodeAlarmReq(_ context.Context, r *http.Request) (any, error) { func decodeUpdateAlarmReq(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return alarmReq{}, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return alarmReq{}, apiutil.ErrUnsupportedContentType } req := alarmReq{} if err := json.NewDecoder(r.Body).Decode(&req.Alarm); err != nil { - return alarmReq{}, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + return alarmReq{}, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } req.Alarm.ID = chi.URLParam(r, "alarmID") diff --git a/alarms/service_test.go b/alarms/service_test.go index ed721f687..a4ab2e285 100644 --- a/alarms/service_test.go +++ b/alarms/service_test.go @@ -12,8 +12,8 @@ import ( "github.com/absmach/magistrala/alarms" "github.com/absmach/magistrala/alarms/mocks" - "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/supermq/pkg/authn" + "github.com/absmach/supermq/pkg/errors" repoerr "github.com/absmach/supermq/pkg/errors/repository" "github.com/absmach/supermq/pkg/uuid" "github.com/stretchr/testify/assert" diff --git a/bootstrap/api/endpoint_test.go b/bootstrap/api/endpoint_test.go index 8d74cc796..f64db49de 100644 --- a/bootstrap/api/endpoint_test.go +++ b/bootstrap/api/endpoint_test.go @@ -89,11 +89,11 @@ var ( CACert: "newca", } - missingIDRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrMissingID.Error(), Msg: apiutil.ErrValidation.Error()}) - missingKeyRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrBearerKey.Error(), Msg: apiutil.ErrValidation.Error()}) - bsErrorRes = toJSON(apiutil.ErrorRes{Msg: bootstrap.ErrBootstrap.Error()}) - extKeyRes = toJSON(apiutil.ErrorRes{Msg: bootstrap.ErrExternalKey.Error()}) - extSecKeyRes = toJSON(apiutil.ErrorRes{Msg: bootstrap.ErrExternalKeySecure.Error()}) + missingIDRes = toJSON(apiutil.ErrMissingID) + missingKeyRes = toJSON(apiutil.ErrBearerKey) + unknownExternalIDErrorRes = toJSON(svcerr.ErrNotFound) + extKeyRes = toJSON(bootstrap.ErrExternalKey) + extSecKeyRes = toJSON(bootstrap.ErrExternalKeySecure) ) type testRequest struct { @@ -257,7 +257,7 @@ func TestAdd(t *testing.T) { domainID: domainID, token: validToken, contentType: contentType, - status: http.StatusConflict, + status: http.StatusBadRequest, location: "", err: svcerr.ErrConflict, }, @@ -267,7 +267,7 @@ func TestAdd(t *testing.T) { domainID: domainID, token: validToken, contentType: contentType, - status: http.StatusConflict, + status: http.StatusBadRequest, location: "", err: svcerr.ErrConflict, }, @@ -277,7 +277,7 @@ func TestAdd(t *testing.T) { domainID: domainID, token: validToken, contentType: contentType, - status: http.StatusConflict, + status: http.StatusBadRequest, location: "", err: svcerr.ErrConflict, }, @@ -1190,9 +1190,9 @@ func TestBootstrap(t *testing.T) { externalID: unknown, externalKey: c.ExternalKey, status: http.StatusNotFound, - res: bsErrorRes, + res: unknownExternalIDErrorRes, secure: false, - err: bootstrap.ErrBootstrap, + err: svcerr.ErrNotFound, }, { desc: "bootstrap a Client with an empty ID", @@ -1201,7 +1201,7 @@ func TestBootstrap(t *testing.T) { status: http.StatusBadRequest, res: missingIDRes, secure: false, - err: errors.Wrap(bootstrap.ErrBootstrap, svcerr.ErrMalformedEntity), + err: apiutil.ErrMissingID, }, { desc: "bootstrap a Client with unknown key", @@ -1210,16 +1210,16 @@ func TestBootstrap(t *testing.T) { status: http.StatusForbidden, res: extKeyRes, secure: false, - err: errors.Wrap(bootstrap.ErrExternalKey, errors.New("")), + err: bootstrap.ErrExternalKey, }, { desc: "bootstrap a Client with an empty key", externalID: c.ExternalID, externalKey: "", - status: http.StatusBadRequest, + status: http.StatusUnauthorized, res: missingKeyRes, secure: false, - err: errors.Wrap(bootstrap.ErrBootstrap, svcerr.ErrAuthentication), + err: apiutil.ErrBearerKey, }, { desc: "bootstrap known Client", diff --git a/bootstrap/api/transport.go b/bootstrap/api/transport.go index 547cc30e4..d16bd34e0 100644 --- a/bootstrap/api/transport.go +++ b/bootstrap/api/transport.go @@ -11,7 +11,6 @@ import ( "net/url" "strings" - mgapi "github.com/absmach/magistrala/api" "github.com/absmach/magistrala/bootstrap" "github.com/absmach/supermq" api "github.com/absmach/supermq/api/http" @@ -43,7 +42,7 @@ var ( // MakeHandler returns a HTTP handler for API endpoints. func MakeHandler(svc bootstrap.Service, authn smqauthn.AuthNMiddleware, reader bootstrap.ConfigReader, logger *slog.Logger, instanceID string) http.Handler { opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, mgapi.EncodeError)), + kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } r := chi.NewRouter() @@ -129,14 +128,14 @@ func MakeHandler(svc bootstrap.Service, authn smqauthn.AuthNMiddleware, reader b func decodeAddRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := addReq{ token: apiutil.ExtractBearerToken(r), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } return req, nil @@ -144,14 +143,14 @@ func decodeAddRequest(_ context.Context, r *http.Request) (any, error) { func decodeUpdateRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := updateReq{ id: chi.URLParam(r, "configID"), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } return req, nil @@ -159,14 +158,14 @@ func decodeUpdateRequest(_ context.Context, r *http.Request) (any, error) { func decodeUpdateCertRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := updateCertReq{ clientID: chi.URLParam(r, "certID"), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } return req, nil @@ -174,7 +173,7 @@ func decodeUpdateCertRequest(_ context.Context, r *http.Request) (any, error) { func decodeUpdateConnRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := updateConnReq{ @@ -182,7 +181,7 @@ func decodeUpdateConnRequest(_ context.Context, r *http.Request) (any, error) { id: chi.URLParam(r, "connID"), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } return req, nil @@ -224,7 +223,7 @@ func decodeBootstrapRequest(_ context.Context, r *http.Request) (any, error) { func decodeStateRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := changeStateReq{ @@ -232,7 +231,7 @@ func decodeStateRequest(_ context.Context, r *http.Request) (any, error) { id: chi.URLParam(r, "clientID"), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } return req, nil diff --git a/bootstrap/postgres/configs.go b/bootstrap/postgres/configs.go index 50e44f3e8..000c19214 100644 --- a/bootstrap/postgres/configs.go +++ b/bootstrap/postgres/configs.go @@ -104,7 +104,7 @@ func (cr configRepository) RetrieveByID(ctx context.Context, domainID, id string } if !row.Next() { - return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, sql.ErrNoRows) + return bootstrap.Config{}, repoerr.ErrNotFound } if err := row.StructScan(&dbcfg); err != nil { @@ -205,7 +205,7 @@ func (cr configRepository) RetrieveByExternalID(ctx context.Context, externalID } if !row.Next() { - return bootstrap.Config{}, errors.Wrap(repoerr.ErrNotFound, sql.ErrNoRows) + return bootstrap.Config{}, repoerr.ErrNotFound } if err := row.StructScan(&dbcfg); err != nil { @@ -275,7 +275,7 @@ func (cr configRepository) Update(ctx context.Context, cfg bootstrap.Config) err } func (cr configRepository) UpdateCert(ctx context.Context, domainID, clientID, clientCert, clientKey, caCert string) (bootstrap.Config, error) { - q := `UPDATE configs SET client_cert = :client_cert, client_key = :client_key, ca_cert = :ca_cert WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id + q := `UPDATE configs SET client_cert = :client_cert, client_key = :client_key, ca_cert = :ca_cert WHERE magistrala_client = :magistrala_client AND domain_id = :domain_id RETURNING magistrala_client, client_cert, client_key, ca_cert` dbcfg := dbConfig{ @@ -436,7 +436,7 @@ func (cr configRepository) UpdateChannel(ctx context.Context, c bootstrap.Channe return errors.Wrap(repoerr.ErrUpdateEntity, err) } - q := `UPDATE channels SET name = :name, metadata = :metadata, updated_at = :updated_at, updated_by = :updated_by + q := `UPDATE channels SET name = :name, metadata = :metadata, updated_at = :updated_at, updated_by = :updated_by WHERE magistrala_channel = :magistrala_channel` if _, err = cr.db.NamedExecContext(ctx, q, dbch); err != nil { return errors.Wrap(errUpdateChannels, err) diff --git a/bootstrap/service.go b/bootstrap/service.go index 3076b6450..cd4462c26 100644 --- a/bootstrap/service.go +++ b/bootstrap/service.go @@ -24,19 +24,19 @@ var ( ErrClients = errors.New("failed to receive response from Clients service") // ErrExternalKey indicates a non-existent bootstrap configuration for given external key. - ErrExternalKey = errors.New("failed to get bootstrap configuration for given external key") + ErrExternalKey = errors.NewAuthZError("failed to get bootstrap configuration for given external key") // ErrExternalKeySecure indicates error in getting bootstrap configuration for given encrypted external key. - ErrExternalKeySecure = errors.New("failed to get bootstrap configuration for given encrypted external key") + ErrExternalKeySecure = errors.NewAuthZError("failed to get bootstrap configuration for given encrypted external key") // ErrBootstrap indicates error in getting bootstrap configuration. ErrBootstrap = errors.New("failed to read bootstrap configuration") // ErrAddBootstrap indicates error in adding bootstrap configuration. - ErrAddBootstrap = errors.New("failed to add bootstrap configuration") + ErrAddBootstrap = errors.NewServiceError("failed to add bootstrap configuration") // ErrBootstrapState indicates an invalid bootstrap state. - ErrBootstrapState = errors.New("invalid bootstrap state") + ErrBootstrapState = errors.NewRequestError("invalid bootstrap state") // ErrNotInSameDomain indicates entities are not in the same domain. errNotInSameDomain = errors.New("entities are not in the same domain") diff --git a/consumers/notifiers/api/endpoint_test.go b/consumers/notifiers/api/endpoint_test.go index d55cf8f37..c7275b9cf 100644 --- a/consumers/notifiers/api/endpoint_test.go +++ b/consumers/notifiers/api/endpoint_test.go @@ -38,10 +38,10 @@ const ( ) var ( - notFoundRes = toJSON(apiutil.ErrorRes{Msg: svcerr.ErrNotFound.Error()}) - unauthRes = toJSON(apiutil.ErrorRes{Msg: svcerr.ErrAuthentication.Error()}) - invalidRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrInvalidQueryParams.Error(), Msg: apiutil.ErrValidation.Error()}) - missingTokRes = toJSON(apiutil.ErrorRes{Err: apiutil.ErrBearerToken.Error(), Msg: apiutil.ErrValidation.Error()}) + notFoundRes = toJSON(svcerr.ErrNotFound) + unauthRes = toJSON(svcerr.ErrAuthentication) + invalidRes = toJSON(apiutil.ErrInvalidQueryParams) + missingTokRes = toJSON(apiutil.ErrBearerToken) ) type testRequest struct { @@ -119,7 +119,7 @@ func TestCreate(t *testing.T) { req: data, contentType: contentType, auth: token, - status: http.StatusConflict, + status: http.StatusBadRequest, location: "", err: svcerr.ErrConflict, }, diff --git a/consumers/notifiers/api/transport.go b/consumers/notifiers/api/transport.go index 1cf0c7c6e..0bd44cb55 100644 --- a/consumers/notifiers/api/transport.go +++ b/consumers/notifiers/api/transport.go @@ -83,12 +83,12 @@ func MakeHandler(svc notifiers.Service, logger *slog.Logger, instanceID string) func decodeCreate(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), contentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := createSubReq{token: apiutil.ExtractBearerToken(r)} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } return req, nil diff --git a/consumers/notifiers/postgres/subscriptions.go b/consumers/notifiers/postgres/subscriptions.go index d96585e0a..a4969e36e 100644 --- a/consumers/notifiers/postgres/subscriptions.go +++ b/consumers/notifiers/postgres/subscriptions.go @@ -42,7 +42,7 @@ func (repo subscriptionsRepo) Save(ctx context.Context, sub notifiers.Subscripti row, err := repo.db.NamedQueryContext(ctx, q, dbSub) if err != nil { if pqErr, ok := err.(*pgconn.PgError); ok && pqErr.Code == pgerrcode.UniqueViolation { - return "", errors.Wrap(repoerr.ErrConflict, err) + return "", errors.Wrap(notifiers.ErrSubscriptionsAlreadyExists, err) } return "", errors.Wrap(repoerr.ErrCreateEntity, err) } diff --git a/consumers/notifiers/postgres/subscriptions_test.go b/consumers/notifiers/postgres/subscriptions_test.go index 22885d40c..be2410eb7 100644 --- a/consumers/notifiers/postgres/subscriptions_test.go +++ b/consumers/notifiers/postgres/subscriptions_test.go @@ -60,7 +60,7 @@ func TestSave(t *testing.T) { desc: "save duplicate", sub: sub2, id: "", - err: repoerr.ErrConflict, + err: notifiers.ErrSubscriptionsAlreadyExists, }, } diff --git a/consumers/notifiers/service.go b/consumers/notifiers/service.go index 784a5b06a..6b9b1c8bf 100644 --- a/consumers/notifiers/service.go +++ b/consumers/notifiers/service.go @@ -15,9 +15,13 @@ import ( "github.com/absmach/supermq/pkg/messaging" ) -// ErrMessage indicates an error converting a message to SuperMQ message. -var ErrMessage = errors.New("failed to convert to SuperMQ message") +var ( + // ErrMessage indicates an error converting a message to SuperMQ message. + ErrMessage = errors.New("failed to convert to SuperMQ message") + // ErrSubscriptionsAlreadyExists indicates subscription already exists. + ErrSubscriptionsAlreadyExists = errors.NewRequestError("subscription already exists") +) var _ consumers.AsyncConsumer = (*notifierService)(nil) // Service reprents a notification service. diff --git a/consumers/notifiers/service_test.go b/consumers/notifiers/service_test.go index 68483b6c0..b3e9b7cd3 100644 --- a/consumers/notifiers/service_test.go +++ b/consumers/notifiers/service_test.go @@ -16,7 +16,6 @@ import ( smqauthn "github.com/absmach/supermq/pkg/authn" authnmocks "github.com/absmach/supermq/pkg/authn/mocks" "github.com/absmach/supermq/pkg/errors" - repoerr "github.com/absmach/supermq/pkg/errors/repository" svcerr "github.com/absmach/supermq/pkg/errors/service" "github.com/absmach/supermq/pkg/messaging" "github.com/absmach/supermq/pkg/uuid" @@ -66,7 +65,7 @@ func TestCreateSubscription(t *testing.T) { token: exampleUser1, sub: notifiers.Subscription{Contact: exampleUser1, Topic: "valid.topic"}, id: "", - err: repoerr.ErrConflict, + err: notifiers.ErrSubscriptionsAlreadyExists, authenticateErr: nil, userID: validID, }, diff --git a/consumers/writers/postgres/consumer.go b/consumers/writers/postgres/consumer.go index 4fabc7aec..3fd958ca9 100644 --- a/consumers/writers/postgres/consumer.go +++ b/consumers/writers/postgres/consumer.go @@ -137,6 +137,9 @@ func (pr postgresRepo) insertJSON(ctx context.Context, msgs smqjson.Messages) er } if _, err = tx.NamedExec(q, dbmsg); err != nil { + if preErr, ok := err.(*pgconn.PrepareError); ok { + err = preErr.Unwrap() + } pgErr, ok := err.(*pgconn.PgError) if ok { switch pgErr.Code { diff --git a/consumers/writers/timescale/consumer.go b/consumers/writers/timescale/consumer.go index 7c67ef3cf..40b82b61c 100644 --- a/consumers/writers/timescale/consumer.go +++ b/consumers/writers/timescale/consumer.go @@ -139,6 +139,9 @@ func (tr timescaleRepo) insertJSON(ctx context.Context, msgs smqjson.Messages) e return errors.Wrap(errSaveMessage, err) } if _, err = tx.NamedExec(q, dbmsg); err != nil { + if preErr, ok := err.(*pgconn.PrepareError); ok { + err = preErr.Unwrap() + } pgErr, ok := err.(*pgconn.PgError) if ok { switch pgErr.Code { diff --git a/docker/supermq-docker/addons/prometheus/grafana/example-dashboard.json b/docker/supermq-docker/addons/prometheus/grafana/example-dashboard.json index b83e75bb7..c2eabb4fb 100644 --- a/docker/supermq-docker/addons/prometheus/grafana/example-dashboard.json +++ b/docker/supermq-docker/addons/prometheus/grafana/example-dashboard.json @@ -1002,7 +1002,7 @@ "uid": "PBFA97CFB590B2093" }, "exemplar": true, - "expr": "http_adapter_api_request_count{}", + "expr": "ws_adapter_api_request_count{}", "interval": "", "legendFormat": "{{method}}", "refId": "A" @@ -1107,7 +1107,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "label_replace(label_replace(label_replace(http_adapter_api_request_latency_microseconds, \"quantile\", \"50th percentile\", \"quantile\", \"0.5\"), \"quantile\", \"90th percentile\", \"quantile\", \"0.9\"), \"quantile\", \"99th percentile\", \"quantile\", \"0.99\")", + "expr": "label_replace(label_replace(label_replace(ws_adapter_api_request_latency_microseconds, \"quantile\", \"50th percentile\", \"quantile\", \"0.5\"), \"quantile\", \"90th percentile\", \"quantile\", \"0.9\"), \"quantile\", \"99th percentile\", \"quantile\", \"0.99\")", "format": "time_series", "instant": false, "interval": "", diff --git a/internal/email/email.go b/internal/email/email.go index eded71ac7..3acf9b6a5 100644 --- a/internal/email/email.go +++ b/internal/email/email.go @@ -12,7 +12,7 @@ import ( "strings" "text/template" - "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/supermq/pkg/errors" "gopkg.in/gomail.v2" ) diff --git a/pkg/errors/README.md b/pkg/errors/README.md deleted file mode 100644 index fc5ba548d..000000000 --- a/pkg/errors/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Errors - -`errors` package serve to build an arbitrary long error chain in order to capture errors returned from nested service calls. - -`errors` package contains the custom Go `error` interface implementation, `Error`. You use the `Error` interface to **wrap** two errors in a containing error as well as to test recursively if a given error **contains** some other error. diff --git a/pkg/errors/doc.go b/pkg/errors/doc.go deleted file mode 100644 index 021c48393..000000000 --- a/pkg/errors/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package errors contains Magistrala errors definitions. -package errors diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go deleted file mode 100644 index 6ca1637db..000000000 --- a/pkg/errors/errors.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package errors - -import ( - "encoding/json" -) - -// Error specifies an API that must be fullfiled by error type. -type Error interface { - // Error implements the error interface. - Error() string - - // Msg returns error message. - Msg() string - - // Err returns wrapped error. - Err() Error - - // MarshalJSON returns a marshaled error. - MarshalJSON() ([]byte, error) -} - -var _ Error = (*customError)(nil) - -// customError represents a Magistrala error. -type customError struct { - msg string - err Error -} - -// New returns an Error that formats as the given text. -func New(text string) Error { - return &customError{ - msg: text, - err: nil, - } -} - -func (ce *customError) Error() string { - if ce == nil { - return "" - } - if ce.err == nil { - return ce.msg - } - return ce.msg + " : " + ce.err.Error() -} - -func (ce *customError) Msg() string { - return ce.msg -} - -func (ce *customError) Err() Error { - return ce.err -} - -func (ce *customError) MarshalJSON() ([]byte, error) { - var val string - if e := ce.Err(); e != nil { - val = e.Msg() - } - return json.Marshal(&struct { - Err string `json:"error"` - Msg string `json:"message"` - }{ - Err: val, - Msg: ce.Msg(), - }) -} - -// Contains inspects if e2 error is contained in any layer of e1 error. -func Contains(e1, e2 error) bool { - if e1 == nil || e2 == nil { - return e2 == e1 - } - ce, ok := e1.(Error) - if ok { - if ce.Msg() == e2.Error() { - return true - } - return Contains(ce.Err(), e2) - } - return e1.Error() == e2.Error() -} - -// Wrap returns an Error that wrap err with wrapper. -func Wrap(wrapper, err error) error { - if wrapper == nil || err == nil { - return wrapper - } - if w, ok := wrapper.(Error); ok { - return &customError{ - msg: w.Msg(), - err: cast(err), - } - } - return &customError{ - msg: wrapper.Error(), - err: cast(err), - } -} - -// Unwrap returns the wrapper and the error by separating the Wrapper from the error. -func Unwrap(err error) (error, error) { - if ce, ok := err.(Error); ok { - if ce.Err() == nil { - return nil, New(ce.Msg()) - } - return New(ce.Msg()), ce.Err() - } - - return nil, err -} - -func cast(err error) Error { - if err == nil { - return nil - } - if e, ok := err.(Error); ok { - return e - } - return &customError{ - msg: err.Error(), - err: nil, - } -} diff --git a/pkg/errors/errors_test.go b/pkg/errors/errors_test.go deleted file mode 100644 index 925e9568c..000000000 --- a/pkg/errors/errors_test.go +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package errors_test - -import ( - nerrors "errors" - "fmt" - "strconv" - "testing" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/stretchr/testify/assert" -) - -const level = 10 - -var ( - err0 = errors.New("0") - err1 = errors.New("1") - err2 = errors.New("2") - nat = nerrors.New("native error") -) - -func TestError(t *testing.T) { - cases := []struct { - desc string - err error - msg string - bytes []byte - bytesErr error - }{ - { - desc: "level 0 wrapped error", - err: err0, - msg: "0", - bytes: []byte(`{"error":"","message":"0"}`), - bytesErr: nil, - }, - { - desc: "level 1 wrapped error", - err: wrap(1), - msg: message(1), - bytes: []byte(`{"error":"0","message":"1"}`), - bytesErr: nil, - }, - { - desc: "level 2 wrapped error", - err: wrap(2), - msg: message(2), - bytes: []byte(`{"error":"1","message":"2"}`), - bytesErr: nil, - }, - { - desc: fmt.Sprintf("level %d wrapped error", level), - err: wrap(level), - msg: message(level), - bytes: []byte(`{"error":"9","message":"` + strconv.Itoa(level) + `"}`), - bytesErr: nil, - }, - { - desc: "nil error", - err: errors.New(""), - msg: "", - bytes: []byte(`{"error":"","message":""}`), - bytesErr: nil, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - errMsg := c.err.Error() - assert.Equal(t, c.msg, errMsg) - err := c.err.(errors.Error) - data, derr := err.MarshalJSON() - assert.Equal(t, c.bytesErr, derr) - assert.Equal(t, c.bytes, data) - }) - } -} - -func TestContains(t *testing.T) { - cases := []struct { - desc string - container error - contained error - contains bool - }{ - { - desc: "nil contains nil", - container: nil, - contained: nil, - contains: true, - }, - { - desc: "nil contains non-nil", - container: nil, - contained: err0, - contains: false, - }, - { - desc: "non-nil contains nil", - container: err0, - contained: nil, - contains: false, - }, - { - desc: "non-nil contains non-nil", - container: err0, - contained: err1, - contains: false, - }, - { - desc: "res of errors.Wrap(err1, err0) contains err0", - container: errors.Wrap(err1, err0), - contained: err0, - contains: true, - }, - { - desc: "res of errors.Wrap(err1, err0) contains err1", - container: errors.Wrap(err1, err0), - contained: err1, - contains: true, - }, - { - desc: "res of errors.Wrap(err2, errors.Wrap(err1, err0)) contains err1", - container: errors.Wrap(err2, errors.Wrap(err1, err0)), - contained: err1, - contains: true, - }, - { - desc: fmt.Sprintf("level %d wrapped error contains", level), - container: wrap(level), - contained: errors.New(strconv.Itoa(level / 2)), - contains: true, - }, - { - desc: "superset wrapper error contains subset wrapper error", - container: wrap(level), - contained: wrap(level / 2), - contains: false, - }, - { - desc: "native error contains error", - container: nat, - contained: err0, - contains: false, - }, - { - desc: "res of errors.Wrap(err1, errors.New('')) contains err1", - container: errors.Wrap(err1, nat), - contained: err1, - contains: true, - }, - { - desc: "error contains native error", - container: err0, - contained: nat, - contains: false, - }, - { - desc: "res of errors.Wrap(errors.New(''), err0) contains err0", - container: errors.Wrap(nat, err0), - contained: err0, - contains: true, - }, - } - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - contains := errors.Contains(c.container, c.contained) - assert.Equal(t, c.contains, contains) - }) - } -} - -func TestWrap(t *testing.T) { - cases := []struct { - desc string - wrapper error - wrapped error - contained error - contains bool - }{ - { - desc: "err 1 wraps err 2", - wrapper: err1, - wrapped: err0, - contained: err0, - contains: true, - }, - { - desc: "err2 wraps err1 wraps err0 and contains err0", - wrapper: err2, - wrapped: errors.Wrap(err1, err0), - contained: err0, - contains: true, - }, - { - desc: "err2 wraps err1 wraps err0 and contains err1", - wrapper: err2, - wrapped: errors.Wrap(err1, err0), - contained: err1, - contains: true, - }, - { - desc: "nil wraps nil", - wrapper: nil, - wrapped: nil, - contained: nil, - contains: true, - }, - { - desc: "err0 wraps nil", - wrapper: err0, - wrapped: nil, - contained: nil, - contains: false, - }, - { - desc: "nil wraps err0", - wrapper: nil, - wrapped: err0, - contained: err0, - contains: false, - }, - { - desc: "err0 wraps native error", - wrapper: err0, - wrapped: nat, - contained: nat, - contains: true, - }, - { - desc: "nil wraps native error", - wrapper: nil, - wrapped: nat, - contained: nat, - contains: false, - }, - { - desc: "native error wraps err0", - wrapper: nat, - wrapped: err0, - contained: err0, - contains: true, - }, - { - desc: "native error wraps nil", - wrapper: nat, - wrapped: nil, - contained: nil, - contains: false, - }, - { - desc: "err0 wraps err1 wraps native error", - wrapper: err0, - wrapped: errors.Wrap(err1, nat), - contained: nat, - contains: true, - }, - { - desc: "native error wraps err1 wraps err0", - wrapper: nat, - wrapped: errors.Wrap(err1, err0), - contained: err0, - contains: true, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - err := errors.Wrap(c.wrapper, c.wrapped) - contains := errors.Contains(err, c.contained) - assert.Equal(t, c.contains, contains) - }) - } -} - -func TestUnwrap(t *testing.T) { - cases := []struct { - desc string - err error - wrapper error - wrapped error - }{ - { - desc: "err 1 wraped err 2", - err: errors.Wrap(err1, err2), - wrapper: err1, - wrapped: err2, - }, - { - desc: "err2 wraps err1 wraps err0", - err: errors.Wrap(err2, errors.Wrap(err1, err0)), - wrapper: err2, - wrapped: errors.Wrap(err1, err0), - }, - { - desc: "nil wraps nil", - err: errors.Wrap(nil, nil), - wrapper: nil, - wrapped: nil, - }, - { - desc: "err0 wraps nil", - err: errors.Wrap(err0, nil), - wrapper: nil, - wrapped: err0, - }, - { - desc: "nil wraps err0", - err: errors.Wrap(nil, err0), - wrapper: nil, - wrapped: nil, - }, - { - desc: "nil wraps native error", - err: errors.Wrap(nil, nat), - wrapper: nil, - wrapped: nil, - }, - { - desc: "native error wraps nil", - err: errors.Wrap(nat, nil), - wrapper: nil, - wrapped: nat, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - wrapper, wrapped := errors.Unwrap(c.err) - assert.Equal(t, c.wrapper, wrapper) - assert.Equal(t, c.wrapped, wrapped) - }) - } -} - -func wrap(level int) error { - if level == 0 { - return errors.New(strconv.Itoa(level)) - } - return errors.Wrap(errors.New(strconv.Itoa(level)), wrap(level-1)) -} - -// message generates error message of wrap() generated wrapper error. -func message(level int) string { - if level == 0 { - return "0" - } - return strconv.Itoa(level) + " : " + message(level-1) -} diff --git a/pkg/errors/repository/types.go b/pkg/errors/repository/types.go deleted file mode 100644 index a189ae9e6..000000000 --- a/pkg/errors/repository/types.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package repository - -import "github.com/absmach/magistrala/pkg/errors" - -// Wrapper for Repository errors. -var ( - // ErrMalformedEntity indicates a malformed entity specification. - ErrMalformedEntity = errors.New("malformed entity specification") - - // ErrNotFound indicates a non-existent entity request. - ErrNotFound = errors.New("entity not found") - - // ErrConflict indicates that entity already exists. - ErrConflict = errors.New("entity already exists") - - // ErrCreateEntity indicates error in creating entity or entities. - ErrCreateEntity = errors.New("failed to create entity in the db") - - // ErrViewEntity indicates error in viewing entity or entities. - ErrViewEntity = errors.New("view entity failed") - - // ErrUpdateEntity indicates error in updating entity or entities. - ErrUpdateEntity = errors.New("update entity failed") - - // ErrRemoveEntity indicates error in removing entity. - ErrRemoveEntity = errors.New("failed to remove entity") - - // ErrFailedOpDB indicates a failure in a database operation. - ErrFailedOpDB = errors.New("operation on db element failed") - - // ErrFailedToRetrieveAllGroups failed to retrieve groups. - ErrFailedToRetrieveAllGroups = errors.New("failed to retrieve all groups") - - // ErrMissingNames indicates missing first and last names. - ErrMissingNames = errors.New("missing first or last name") -) diff --git a/pkg/errors/sdk_errors.go b/pkg/errors/sdk_errors.go deleted file mode 100644 index 61535c916..000000000 --- a/pkg/errors/sdk_errors.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package errors - -import ( - "encoding/json" - "fmt" - "io" - "net/http" -) - -type errorRes struct { - Err string `json:"error"` - Msg string `json:"message"` -} - -// Failed to read response body. -var errRespBody = New("failed to read response body") - -// SDKError is an error type for Magistrala SDK. -type SDKError interface { - Error - StatusCode() int -} - -var _ SDKError = (*sdkError)(nil) - -type sdkError struct { - *customError - statusCode int -} - -func (ce *sdkError) Error() string { - if ce == nil { - return "" - } - if ce.customError == nil { - return http.StatusText(ce.statusCode) - } - return fmt.Sprintf("Status: %s: %s", http.StatusText(ce.statusCode), ce.customError.Error()) -} - -func (ce *sdkError) StatusCode() int { - return ce.statusCode -} - -// NewSDKError returns an SDK Error that formats as the given text. -func NewSDKError(err error) SDKError { - if err == nil { - return nil - } - - if e, ok := err.(Error); ok { - return &sdkError{ - statusCode: 0, - customError: &customError{ - msg: e.Msg(), - err: cast(e.Err()), - }, - } - } - return &sdkError{ - customError: &customError{ - msg: err.Error(), - err: nil, - }, - statusCode: 0, - } -} - -// NewSDKErrorWithStatus returns an SDK Error setting the status code. -func NewSDKErrorWithStatus(err error, statusCode int) SDKError { - if err == nil { - return nil - } - - if e, ok := err.(Error); ok { - return &sdkError{ - statusCode: statusCode, - customError: &customError{ - msg: e.Msg(), - err: cast(e.Err()), - }, - } - } - return &sdkError{ - statusCode: statusCode, - customError: &customError{ - msg: err.Error(), - err: nil, - }, - } -} - -// CheckError will check the HTTP response status code and matches it with the given status codes. -// Since multiple status codes can be valid, we can pass multiple status codes to the function. -// The function then checks for errors in the HTTP response. -func CheckError(resp *http.Response, expectedStatusCodes ...int) SDKError { - if resp == nil { - return nil - } - - for _, expectedStatusCode := range expectedStatusCodes { - if resp.StatusCode == expectedStatusCode { - return nil - } - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return NewSDKErrorWithStatus(Wrap(errRespBody, err), resp.StatusCode) - } - var content errorRes - if err := json.Unmarshal(body, &content); err != nil { - return NewSDKErrorWithStatus(err, resp.StatusCode) - } - if content.Err == "" { - return NewSDKErrorWithStatus(New(content.Msg), resp.StatusCode) - } - - return NewSDKErrorWithStatus(Wrap(New(content.Msg), New(content.Err)), resp.StatusCode) -} diff --git a/pkg/errors/sdk_errors_test.go b/pkg/errors/sdk_errors_test.go deleted file mode 100644 index ac31a2359..000000000 --- a/pkg/errors/sdk_errors_test.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package errors_test - -import ( - "bytes" - "fmt" - "io" - "net/http" - "testing" - - "github.com/absmach/magistrala/pkg/errors" - "github.com/stretchr/testify/assert" -) - -var body = []byte(`{"error":"error","message":"message"}`) - -func TestNewSDKError(t *testing.T) { - cases := []struct { - desc string - err error - }{ - { - desc: "nil error", - err: nil, - }, - { - desc: "non nil error", - err: err0, - }, - { - desc: "non nil error with wrapped error", - err: errors.Wrap(err0, err1), - }, - { - desc: "native error", - err: nat, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - sdk := errors.NewSDKError(c.err) - if c.err != nil { - assert.Equal(t, sdk.StatusCode(), 0) - assert.Equal(t, sdk.Error(), fmt.Sprintf("Status: %s: %s", http.StatusText(0), c.err.Error())) - } - }) - } -} - -func TestNewSDKErrorWithStatus(t *testing.T) { - cases := []struct { - desc string - err error - sc int - }{ - { - desc: "nil error with 0 status code", - err: nil, - sc: 0, - }, - { - desc: "nil error with 404 status code", - err: nil, - sc: 404, - }, - { - desc: "non nil error with 0 status code", - err: err0, - sc: 0, - }, - { - desc: "non nil error with 404 status code", - err: err0, - sc: 404, - }, - { - desc: "non nil error with wrapped error and 0 status code", - err: errors.Wrap(err0, err1), - sc: 0, - }, - { - desc: "non nil error with wrapped error and 404 status code", - err: errors.Wrap(err0, err1), - sc: 404, - }, - { - desc: "native error with 0 status code", - err: nat, - sc: 0, - }, - { - desc: "native error with 404 status code", - err: nat, - sc: 404, - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - sdk := errors.NewSDKErrorWithStatus(c.err, c.sc) - if c.err != nil { - assert.Equal(t, sdk.StatusCode(), c.sc) - assert.Equal(t, sdk.Error(), fmt.Sprintf("Status: %s: %s", http.StatusText(c.sc), c.err.Error())) - } - }) - } -} - -func TestCheckError(t *testing.T) { - cases := []struct { - desc string - resp *http.Response - codes []int - err errors.SDKError - }{ - { - desc: "nil response", - resp: nil, - codes: []int{http.StatusOK}, - err: nil, - }, - { - desc: "nil response with 404 status code", - resp: nil, - codes: []int{http.StatusNotFound}, - err: nil, - }, - { - desc: "valid response with 200 status code", - resp: &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(body)), - }, - codes: []int{http.StatusOK}, - err: nil, - }, - { - desc: "valid response with 404 status code", - resp: &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewReader(body)), - }, - codes: []int{http.StatusNotFound}, - err: nil, - }, - { - desc: "invalid response with 200 status code", - resp: &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewReader(body)), - }, - codes: []int{http.StatusOK}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.New("message"), errors.New("error")), http.StatusNotFound), - }, - { - desc: "invalid response with 404 status code", - resp: &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(body)), - }, - codes: []int{http.StatusNotFound}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(errors.New("message"), errors.New("error")), http.StatusOK), - }, - { - desc: "valid response with 200 status code and 404 status code", - resp: &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(body)), - }, - codes: []int{http.StatusOK, http.StatusNotFound}, - err: nil, - }, - { - desc: "error in JSON marshalling", - resp: &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewReader([]byte(`"error":`))), - }, - codes: []int{http.StatusOK}, - err: errors.NewSDKErrorWithStatus(errors.New("invalid character ':' after top-level value"), http.StatusNotFound), - }, - { - desc: "empty error message", - resp: &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewReader([]byte(`{"error":"","message":""}`))), - }, - codes: []int{http.StatusOK}, - err: errors.NewSDKErrorWithStatus(errors.New(""), http.StatusNotFound), - }, - } - - for _, c := range cases { - t.Run(c.desc, func(t *testing.T) { - sdk := errors.CheckError(c.resp, c.codes...) - assert.Equal(t, sdk, c.err) - if c.err != nil { - assert.Equal(t, sdk, c.err) - assert.Equal(t, sdk.StatusCode(), c.resp.StatusCode) - } - }) - } -} diff --git a/pkg/errors/service/types.go b/pkg/errors/service/types.go deleted file mode 100644 index 2eb33acec..000000000 --- a/pkg/errors/service/types.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package service - -import "github.com/absmach/magistrala/pkg/errors" - -// Wrapper for Service errors. -var ( - // ErrAuthentication indicates failure occurred while authenticating the entity. - ErrAuthentication = errors.New("failed to perform authentication over the entity") - - // ErrAuthorization indicates failure occurred while authorizing the entity. - ErrAuthorization = errors.New("failed to perform authorization over the entity") - - // ErrDomainAuthorization indicates failure occurred while authorizing the domain. - ErrDomainAuthorization = errors.New("failed to perform authorization over the domain") - - // ErrLogin indicates wrong login credentials. - ErrLogin = errors.New("invalid user id or secret") - - // ErrMalformedEntity indicates a malformed entity specification. - ErrMalformedEntity = errors.New("malformed entity specification") - - // ErrNotFound indicates a non-existent entity request. - ErrNotFound = errors.New("entity not found") - - // ErrConflict indicates that entity already exists. - ErrConflict = errors.New("entity already exists") - - // ErrCreateEntity indicates error in creating entity or entities. - ErrCreateEntity = errors.New("failed to create entity") - - // ErrRemoveEntity indicates error in removing entity. - ErrRemoveEntity = errors.New("failed to remove entity") - - // ErrViewEntity indicates error in viewing entity or entities. - ErrViewEntity = errors.New("view entity failed") - - // ErrUpdateEntity indicates error in updating entity or entities. - ErrUpdateEntity = errors.New("update entity failed") - - // ErrInvalidStatus indicates an invalid status. - ErrInvalidStatus = errors.New("invalid status") - - // ErrInvalidRole indicates that an invalid role. - ErrInvalidRole = errors.New("invalid client role") - - // ErrInvalidPolicy indicates that an invalid policy. - ErrInvalidPolicy = errors.New("invalid policy") - - // ErrEnableClient indicates error in enabling client. - ErrEnableClient = errors.New("failed to enable client") - - // ErrDisableClient indicates error in disabling client. - ErrDisableClient = errors.New("failed to disable client") - - // ErrAddPolicies indicates error in adding policies. - ErrAddPolicies = errors.New("failed to add policies") - - // ErrDeletePolicies indicates error in removing policies. - ErrDeletePolicies = errors.New("failed to remove policies") - - // ErrSearch indicates error in searching clients. - ErrSearch = errors.New("failed to search clients") - - // ErrInvitationAlreadyRejected indicates that the invitation is already rejected. - ErrInvitationAlreadyRejected = errors.New("invitation already rejected") - - // ErrInvitationAlreadyAccepted indicates that the invitation is already accepted. - ErrInvitationAlreadyAccepted = errors.New("invitation already accepted") - - // ErrParentGroupAuthorization indicates failure occurred while authorizing the parent group. - ErrParentGroupAuthorization = errors.New("failed to authorize parent group") - - // ErrMissingUsername indicates that the user's names are missing. - ErrMissingUsername = errors.New("missing usernames") -) diff --git a/pkg/errors/types.go b/pkg/errors/types.go deleted file mode 100644 index dab06016e..000000000 --- a/pkg/errors/types.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package errors - -import "errors" - -var ( - // ErrMalformedEntity indicates a malformed entity specification. - ErrMalformedEntity = New("malformed entity specification") - - // ErrUnsupportedContentType indicates invalid content type. - ErrUnsupportedContentType = errors.New("invalid content type") - - // ErrUnidentified indicates unidentified error. - ErrUnidentified = errors.New("unidentified error") - - // ErrEmptyPath indicates empty file path. - ErrEmptyPath = errors.New("empty file path") - - // ErrStatusAlreadyAssigned indicated that the client or group has already been assigned the status. - ErrStatusAlreadyAssigned = errors.New("status already assigned") - - // ErrRollbackTx indicates failed to rollback transaction. - ErrRollbackTx = errors.New("failed to rollback transaction") - - // ErrAuthentication indicates failure occurred while authenticating the entity. - ErrAuthentication = errors.New("failed to perform authentication over the entity") - - // ErrAuthorization indicates failure occurred while authorizing the entity. - ErrAuthorization = errors.New("failed to perform authorization over the entity") -) diff --git a/pkg/reltime/reltime.go b/pkg/reltime/reltime.go index f77aea90f..c3a83a63f 100644 --- a/pkg/reltime/reltime.go +++ b/pkg/reltime/reltime.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/supermq/pkg/errors" ) var ( diff --git a/pkg/reltime/reltime_test.go b/pkg/reltime/reltime_test.go index ba58a793e..4cc5aa4b6 100644 --- a/pkg/reltime/reltime_test.go +++ b/pkg/reltime/reltime_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/supermq/pkg/errors" "github.com/stretchr/testify/assert" ) diff --git a/pkg/schedule/schedule.go b/pkg/schedule/schedule.go index 9000f5e9a..e12b12371 100644 --- a/pkg/schedule/schedule.go +++ b/pkg/schedule/schedule.go @@ -19,8 +19,8 @@ const ( ) var ( - ErrInvalidRecurringType = errors.New("invalid recurring type") - ErrStartDateTimeInPast = errors.New("start_datetime must be greater than or equal to current time") + ErrInvalidRecurringType = errors.NewRequestError("invalid recurring type") + ErrStartDateTimeInPast = errors.NewRequestError("start_datetime must be greater than or equal to current time") ) // Type can be daily, weekly or monthly. diff --git a/pkg/sdk/bootstrap_test.go b/pkg/sdk/bootstrap_test.go index db10af191..1fa7ddc1f 100644 --- a/pkg/sdk/bootstrap_test.go +++ b/pkg/sdk/bootstrap_test.go @@ -224,7 +224,7 @@ func TestAddBootstrap(t *testing.T) { svcReq: bootstrapConfig, svcRes: bootstrap.Config{}, svcErr: svcerr.ErrConflict, - err: errors.NewSDKErrorWithStatus(svcerr.ErrConflict, http.StatusConflict), + err: errors.NewSDKErrorWithStatus(svcerr.ErrConflict, http.StatusBadRequest), }, { desc: "add empty config", @@ -234,7 +234,7 @@ func TestAddBootstrap(t *testing.T) { svcReq: bootstrap.Config{}, svcRes: bootstrap.Config{}, svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), }, { desc: "add with non-existent client Id", @@ -487,7 +487,7 @@ func TestWhiteList(t *testing.T) { state: -1, svcReq: bootstrap.Active, svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, bootstrap.ErrBootstrapState), http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(bootstrap.ErrBootstrapState, http.StatusBadRequest), }, { desc: "whitelist with empty client Id", @@ -586,9 +586,9 @@ func TestViewBootstrap(t *testing.T) { token: validToken, id: invalid, svcResp: bootstrap.Config{}, - svcErr: svcerr.ErrViewEntity, + svcErr: svcerr.ErrNotFound, response: sdk.BootstrapConfig{}, - err: errors.NewSDKErrorWithStatus(svcerr.ErrViewEntity, http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(svcerr.ErrNotFound, http.StatusNotFound), }, { desc: "view with response that cannot be unmarshalled", @@ -1203,7 +1203,7 @@ func TestBoostrap(t *testing.T) { svcErr: nil, readerResp: bootstrap.Config{}, readerErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerKey), http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerKey, http.StatusUnauthorized), }, } for _, tc := range cases { diff --git a/pkg/sdk/consumers_test.go b/pkg/sdk/consumers_test.go index bd07c4af4..1c300be42 100644 --- a/pkg/sdk/consumers_test.go +++ b/pkg/sdk/consumers_test.go @@ -101,7 +101,7 @@ func TestCreateSubscription(t *testing.T) { svcReq: notifiers.Subscription{}, svcRes: "", svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { desc: "create new subscription with invalid token", @@ -124,7 +124,7 @@ func TestCreateSubscription(t *testing.T) { svcReq: notifiers.Subscription{}, svcErr: nil, svcRes: "", - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidTopic), http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(apiutil.ErrInvalidTopic, http.StatusBadRequest), }, { desc: "create new subscription with empty contact", @@ -137,7 +137,7 @@ func TestCreateSubscription(t *testing.T) { svcReq: notifiers.Subscription{}, svcErr: nil, svcRes: "", - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidContact), http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(apiutil.ErrInvalidContact, http.StatusBadRequest), }, } for _, tc := range cases { @@ -209,7 +209,7 @@ func TestViewSubscription(t *testing.T) { svcRes: notifiers.Subscription{}, svcErr: nil, response: sdk.Subscription{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, } for _, tc := range cases { @@ -368,7 +368,7 @@ func TestListSubscription(t *testing.T) { svcRes: notifiers.Page{}, svcErr: nil, response: sdk.SubscriptionPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, } @@ -431,14 +431,14 @@ func TestDeleteSubscription(t *testing.T) { subID: subID, token: "", svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { desc: "delete subscription with empty subID", subID: "", token: validToken, svcErr: nil, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), }, } diff --git a/pkg/sdk/messages_test.go b/pkg/sdk/messages_test.go index bf23cfb25..91c80228c 100644 --- a/pkg/sdk/messages_test.go +++ b/pkg/sdk/messages_test.go @@ -143,7 +143,7 @@ func TestReadMessages(t *testing.T) { authzErr: svcerr.ErrAuthorization, repoRes: readers.MessagesPage{}, response: sdk.MessagesPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden), }, { desc: "read messages with empty token", @@ -161,7 +161,7 @@ func TestReadMessages(t *testing.T) { authnErr: svcerr.ErrAuthentication, repoRes: readers.MessagesPage{}, response: sdk.MessagesPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), + err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized), }, { desc: "read messages with empty channel ID", @@ -179,7 +179,7 @@ func TestReadMessages(t *testing.T) { repoRes: readers.MessagesPage{}, repoErr: nil, response: sdk.MessagesPage{}, - err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrMissingID), http.StatusBadRequest), + err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest), }, { desc: "read messages with invalid message page metadata", diff --git a/provision/api/endpoint_test.go b/provision/api/endpoint_test.go index 315f3415f..448d5517b 100644 --- a/provision/api/endpoint_test.go +++ b/provision/api/endpoint_test.go @@ -97,7 +97,7 @@ func TestProvision(t *testing.T) { token: validToken, domainID: validID, data: fmt.Sprintf(`{"name": "test", "external_id": "%s"}`, validID), - status: http.StatusBadRequest, + status: http.StatusUnauthorized, contentType: validContenType, svcErr: nil, }, diff --git a/provision/api/requests.go b/provision/api/requests.go index 60f9106a9..b8466c3a4 100644 --- a/provision/api/requests.go +++ b/provision/api/requests.go @@ -3,7 +3,9 @@ package api -import apiutil "github.com/absmach/supermq/api/http/util" +import ( + apiutil "github.com/absmach/supermq/api/http/util" +) type provisionReq struct { token string diff --git a/provision/api/transport.go b/provision/api/transport.go index 9867b94ac..dfbb16e07 100644 --- a/provision/api/transport.go +++ b/provision/api/transport.go @@ -55,7 +55,7 @@ func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string) func decodeProvisionRequest(_ context.Context, r *http.Request) (any, error) { if r.Header.Get("Content-Type") != contentType { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := provisionReq{ @@ -63,7 +63,7 @@ func decodeProvisionRequest(_ context.Context, r *http.Request) (any, error) { domainID: chi.URLParam(r, "domainID"), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } return req, nil @@ -71,7 +71,7 @@ func decodeProvisionRequest(_ context.Context, r *http.Request) (any, error) { func decodeMappingRequest(_ context.Context, r *http.Request) (any, error) { if r.Header.Get("Content-Type") != contentType { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := mappingReq{ diff --git a/re/api/endpoints_test.go b/re/api/endpoints_test.go index a47f3b04d..4d5a1d26f 100644 --- a/re/api/endpoints_test.go +++ b/re/api/endpoints_test.go @@ -798,7 +798,7 @@ func TestUpdateRuleTagsEndpoint(t *testing.T) { contentType: contentType, data: fmt.Sprintf(`{"tags":["%s"}`, newTag), status: http.StatusBadRequest, - err: errors.ErrMalformedEntity, + err: apiutil.ErrMalformedRequestBody, }, { desc: "update rule with empty id", diff --git a/re/api/transport.go b/re/api/transport.go index 54f0a41f9..d5e830cee 100644 --- a/re/api/transport.go +++ b/re/api/transport.go @@ -112,11 +112,11 @@ func MakeHandler(svc re.Service, authn smqauthn.AuthNMiddleware, mux *chi.Mux, l func decodeAddRuleRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } var rule re.Rule if err := json.NewDecoder(r.Body).Decode(&rule); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } return addRuleReq{Rule: rule}, nil } @@ -128,11 +128,11 @@ func decodeViewRuleRequest(_ context.Context, r *http.Request) (any, error) { func decodeUpdateRuleRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } var rule re.Rule if err := json.NewDecoder(r.Body).Decode(&rule); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } rule.ID = chi.URLParam(r, ruleIdKey) @@ -141,14 +141,14 @@ func decodeUpdateRuleRequest(_ context.Context, r *http.Request) (any, error) { func decodeUpdateRuleTags(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := updateRuleTagsReq{ id: chi.URLParam(r, ruleIdKey), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } return req, nil @@ -156,14 +156,14 @@ func decodeUpdateRuleTags(_ context.Context, r *http.Request) (any, error) { func decodeUpdateRuleScheduleRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := updateRuleScheduleReq{ id: chi.URLParam(r, ruleIdKey), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } return req, nil diff --git a/readers/api/grpc/client.go b/readers/api/grpc/client.go index 407dfde7c..8b1d798f5 100644 --- a/readers/api/grpc/client.go +++ b/readers/api/grpc/client.go @@ -11,7 +11,7 @@ import ( "time" grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1" - "github.com/absmach/magistrala/pkg/errors" + "github.com/absmach/supermq/pkg/errors" svcerr "github.com/absmach/supermq/pkg/errors/service" "github.com/absmach/supermq/pkg/transformers/senml" readers "github.com/absmach/supermq/readers" diff --git a/readers/api/grpc/endpoint_test.go b/readers/api/grpc/endpoint_test.go index 6fd09b588..d6056245b 100644 --- a/readers/api/grpc/endpoint_test.go +++ b/readers/api/grpc/endpoint_test.go @@ -12,9 +12,9 @@ import ( "time" grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1" - "github.com/absmach/magistrala/pkg/errors" grpcapi "github.com/absmach/magistrala/readers/api/grpc" apiutil "github.com/absmach/supermq/api/http/util" + "github.com/absmach/supermq/pkg/errors" "github.com/absmach/supermq/pkg/transformers/senml" "github.com/absmach/supermq/readers" "github.com/stretchr/testify/assert" diff --git a/readers/api/http/endpoint_test.go b/readers/api/http/endpoint_test.go index ccecb67f9..5c4992229 100644 --- a/readers/api/http/endpoint_test.go +++ b/readers/api/http/endpoint_test.go @@ -235,7 +235,7 @@ func TestReadAll(t *testing.T) { token: "", authResponse: false, authnErr: svcerr.ErrAuthentication, - status: http.StatusBadRequest, + status: http.StatusUnauthorized, err: svcerr.ErrAuthentication, }, { @@ -645,7 +645,7 @@ func TestReadAll(t *testing.T) { url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), token: "", authResponse: false, - status: http.StatusBadRequest, + status: http.StatusUnauthorized, err: svcerr.ErrAuthorization, }, { diff --git a/readers/api/http/transport.go b/readers/api/http/transport.go index 5f9b94408..072ffad37 100644 --- a/readers/api/http/transport.go +++ b/readers/api/http/transport.go @@ -51,7 +51,7 @@ const ( // MakeHandler returns a HTTP handler for API endpoints. func MakeHandler(svc readers.MessageRepository, authn smqauthn.Authentication, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient, svcName, instanceID string) http.Handler { opts := []kithttp.ServerOption{ - kithttp.ServerErrorEncoder(encodeError), + kithttp.ServerErrorEncoder(api.EncodeError), } mux := chi.NewRouter() @@ -209,43 +209,6 @@ func encodeResponse(_ context.Context, w http.ResponseWriter, response any) erro return json.NewEncoder(w).Encode(response) } -func encodeError(_ context.Context, err error, w http.ResponseWriter) { - w.Header().Set("Content-Type", contentType) - - switch { - case errors.Contains(err, nil): - w.WriteHeader(http.StatusOK) - case errors.Contains(err, apiutil.ErrInvalidQueryParams), - errors.Contains(err, svcerr.ErrMalformedEntity), - errors.Contains(err, apiutil.ErrValidation), - errors.Contains(err, apiutil.ErrMissingID), - errors.Contains(err, apiutil.ErrLimitSize), - errors.Contains(err, apiutil.ErrOffsetSize), - errors.Contains(err, apiutil.ErrInvalidComparator), - errors.Contains(err, apiutil.ErrInvalidAggregation), - errors.Contains(err, apiutil.ErrInvalidInterval), - errors.Contains(err, apiutil.ErrMissingFrom), - errors.Contains(err, apiutil.ErrMissingTo), - errors.Contains(err, apiutil.ErrMissingDomainID): - w.WriteHeader(http.StatusBadRequest) - case errors.Contains(err, svcerr.ErrAuthentication), - errors.Contains(err, apiutil.ErrBearerToken): - w.WriteHeader(http.StatusUnauthorized) - case errors.Contains(err, svcerr.ErrAuthorization): - w.WriteHeader(http.StatusForbidden) - case errors.Contains(err, readers.ErrReadMessages): - w.WriteHeader(http.StatusInternalServerError) - default: - w.WriteHeader(http.StatusInternalServerError) - } - - if errorVal, ok := err.(errors.Error); ok { - if err := json.NewEncoder(w).Encode(errorVal); err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - } -} - func authnAuthz(ctx context.Context, req listMessagesReq, authn smqauthn.Authentication, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient) error { clientID, clientType, err := authenticate(ctx, req, authn, clients) if err != nil { diff --git a/readers/postgres/messages.go b/readers/postgres/messages.go index f1d1c5dab..2f09d450d 100644 --- a/readers/postgres/messages.go +++ b/readers/postgres/messages.go @@ -59,6 +59,9 @@ func (tr postgresRepository) ReadAll(chanID string, rpm readers.PageMetadata) (r } rows, err := tr.db.NamedQuery(q, params) if err != nil { + if preErr, ok := err.(*pgconn.PrepareError); ok { + err = preErr.Unwrap() + } if pgErr, ok := err.(*pgconn.PgError); ok { if pgErr.Code == pgerrcode.UndefinedTable { return readers.MessagesPage{}, nil diff --git a/readers/timescale/messages.go b/readers/timescale/messages.go index 090893f6a..d3067d180 100644 --- a/readers/timescale/messages.go +++ b/readers/timescale/messages.go @@ -120,6 +120,9 @@ func (tr timescaleRepository) ReadAll(chanID string, rpm readers.PageMetadata) ( rows, err := tr.db.NamedQuery(q, params) if err != nil { + if preErr, ok := err.(*pgconn.PrepareError); ok { + err = preErr.Unwrap() + } if pgErr, ok := err.(*pgconn.PgError); ok { if pgErr.Code == pgerrcode.UndefinedTable { return readers.MessagesPage{}, nil diff --git a/reports/api/endpoints_test.go b/reports/api/endpoints_test.go index 3ab77e3cd..62bf2bdfa 100644 --- a/reports/api/endpoints_test.go +++ b/reports/api/endpoints_test.go @@ -1125,7 +1125,7 @@ func TestViewReportTemplateEndpoint(t *testing.T) { id: validID, contentType: contentType, svcErr: svcerr.ErrViewEntity, - status: http.StatusBadRequest, + status: http.StatusUnprocessableEntity, err: svcerr.ErrViewEntity, }, } diff --git a/reports/api/transport.go b/reports/api/transport.go index 236455b58..f0b9f226f 100644 --- a/reports/api/transport.go +++ b/reports/api/transport.go @@ -135,7 +135,7 @@ func MakeHandler(svc reports.Service, authn smqauthn.AuthNMiddleware, mux *chi.M func decodeGenerateReportRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } a, err := apiutil.ReadStringQuery(r, actionKey, defAction) @@ -159,7 +159,7 @@ func decodeGenerateReportRequest(_ context.Context, r *http.Request) (any, error func decodeAddReportConfigRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } var config reports.ReportConfig if err := json.NewDecoder(r.Body).Decode(&config); err != nil { @@ -175,7 +175,7 @@ func decodeViewReportConfigRequest(_ context.Context, r *http.Request) (any, err func decodeUpdateReportConfigRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } var config reports.ReportConfig if err := json.NewDecoder(r.Body).Decode(&config); err != nil { @@ -187,14 +187,14 @@ func decodeUpdateReportConfigRequest(_ context.Context, r *http.Request) (any, e func decodeUpdateReportScheduleRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := updateReportScheduleReq{ id: chi.URLParam(r, reportIdKey), } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err)) + return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err) } return req, nil @@ -214,7 +214,7 @@ func decodeDeleteReportConfigRequest(_ context.Context, r *http.Request) (any, e func decodeUpdateReportTemplateRequest(_ context.Context, r *http.Request) (any, error) { if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) { - return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) + return nil, apiutil.ErrUnsupportedContentType } req := updateReportTemplateReq{} diff --git a/reports/postgres/errors.go b/reports/postgres/errors.go new file mode 100644 index 000000000..80a3489b6 --- /dev/null +++ b/reports/postgres/errors.go @@ -0,0 +1,27 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres + +import ( + "github.com/absmach/supermq/pkg/errors" + repoerr "github.com/absmach/supermq/pkg/errors/repository" +) + +var _ errors.Mapper = (*duplicateErrors)(nil) + +type duplicateErrors struct{} + +// GetError maps constraint names to known errors. +func (d duplicateErrors) GetError(constraint string) (error, bool) { + switch constraint { + case "report_config_pkey": + return repoerr.ErrConflict, true + default: + return nil, false + } +} + +func NewDuplicateErrors() errors.Mapper { + return duplicateErrors{} +} diff --git a/reports/postgres/reports.go b/reports/postgres/reports.go index 86a2da1f7..98c53cbd4 100644 --- a/reports/postgres/reports.go +++ b/reports/postgres/reports.go @@ -8,9 +8,9 @@ import ( "encoding/json" "time" - "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/schedule" "github.com/absmach/magistrala/reports" + "github.com/absmach/supermq/pkg/errors" ) // dbReport represents the database structure for a Report. diff --git a/reports/postgres/repository.go b/reports/postgres/repository.go index 4fc181006..2ec7dbad1 100644 --- a/reports/postgres/repository.go +++ b/reports/postgres/repository.go @@ -10,19 +10,26 @@ import ( "strings" "time" - "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/reports" api "github.com/absmach/supermq/api/http" + "github.com/absmach/supermq/pkg/errors" repoerr "github.com/absmach/supermq/pkg/errors/repository" "github.com/absmach/supermq/pkg/postgres" ) type PostgresRepository struct { DB postgres.Database + eh errors.Handler } func NewRepository(db postgres.Database) reports.Repository { - return &PostgresRepository{DB: db} + errHandlerOptions := []errors.HandlerOption{ + postgres.WithDuplicateErrors(NewDuplicateErrors()), + } + return &PostgresRepository{ + DB: db, + eh: postgres.NewErrorHandler(errHandlerOptions...), + } } func (repo *PostgresRepository) AddReportConfig(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) { @@ -36,24 +43,24 @@ func (repo *PostgresRepository) AddReportConfig(ctx context.Context, cfg reports ` dbr, err := reportToDb(cfg) if err != nil { - return reports.ReportConfig{}, err + return reports.ReportConfig{}, repo.eh.HandleError(repoerr.ErrCreateEntity, err) } row, err := repo.DB.NamedQueryContext(ctx, q, dbr) if err != nil { - return reports.ReportConfig{}, err + return reports.ReportConfig{}, repo.eh.HandleError(repoerr.ErrCreateEntity, err) } defer row.Close() var dbReport dbReport if row.Next() { if err := row.StructScan(&dbReport); err != nil { - return reports.ReportConfig{}, err + return reports.ReportConfig{}, repo.eh.HandleError(repoerr.ErrCreateEntity, err) } } report, err := dbToReport(dbReport) if err != nil { - return reports.ReportConfig{}, err + return reports.ReportConfig{}, repo.eh.HandleError(repoerr.ErrCreateEntity, err) } return report, nil @@ -72,6 +79,9 @@ func (repo *PostgresRepository) ViewReportConfig(ctx context.Context, id string) } var dbr dbReport if err := row.StructScan(&dbr); err != nil { + if err == sql.ErrNoRows { + return reports.ReportConfig{}, repoerr.ErrNotFound + } return reports.ReportConfig{}, err } rpt, err := dbToReport(dbr) @@ -163,10 +173,14 @@ func (repo *PostgresRepository) UpdateReportConfig(ctx context.Context, cfg repo defer row.Close() var dbReport dbReport - if row.Next() { - if err := row.StructScan(&dbReport); err != nil { + if !row.Next() { + if err := row.Err(); err != nil { return reports.ReportConfig{}, err } + return reports.ReportConfig{}, repoerr.ErrNotFound + } + if err := row.StructScan(&dbReport); err != nil { + return reports.ReportConfig{}, err } rpt, err := dbToReport(dbReport) if err != nil { @@ -196,10 +210,14 @@ func (repo *PostgresRepository) UpdateReportSchedule(ctx context.Context, cfg re defer row.Close() var dbReport dbReport - if row.Next() { - if err := row.StructScan(&dbReport); err != nil { - return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err) + if !row.Next() { + if err := row.Err(); err != nil { + return reports.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) } + return reports.ReportConfig{}, repoerr.ErrNotFound + } + if err := row.StructScan(&dbReport); err != nil { + return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err) } report, err := dbToReport(dbReport) if err != nil { @@ -320,10 +338,14 @@ func (repo *PostgresRepository) UpdateReportDue(ctx context.Context, id string, defer row.Close() var dbReport dbReport - if row.Next() { - if err := row.StructScan(&dbReport); err != nil { - return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err) + if !row.Next() { + if err := row.Err(); err != nil { + return reports.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err) } + return reports.ReportConfig{}, repoerr.ErrNotFound + } + if err := row.StructScan(&dbReport); err != nil { + return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err) } report, err := dbToReport(dbReport) if err != nil { @@ -335,8 +357,8 @@ func (repo *PostgresRepository) UpdateReportDue(ctx context.Context, id string, func (repo *PostgresRepository) UpdateReportTemplate(ctx context.Context, domainID, reportID string, template reports.ReportTemplate) error { q := ` - UPDATE report_config - SET report_template = :report_template, updated_at = :updated_at + UPDATE report_config + SET report_template = :report_template, updated_at = :updated_at WHERE id = :id AND domain_id = :domain_id` dbr := dbReport{ @@ -357,8 +379,8 @@ func (repo *PostgresRepository) UpdateReportTemplate(ctx context.Context, domain func (repo *PostgresRepository) ViewReportTemplate(ctx context.Context, domainID, reportID string) (reports.ReportTemplate, error) { q := ` - SELECT COALESCE(report_template, '') as report_template - FROM report_config + SELECT COALESCE(report_template, '') as report_template + FROM report_config WHERE id = $1 AND domain_id = $2` var template reports.ReportTemplate diff --git a/reports/postgres/repository_test.go b/reports/postgres/repository_test.go index 7d6edf630..5ea2a2166 100644 --- a/reports/postgres/repository_test.go +++ b/reports/postgres/repository_test.go @@ -292,6 +292,7 @@ func TestUpdateReportConfigStatus(t *testing.T) { CreatedBy: generateUUID(t), UpdatedAt: time.Now().UTC(), UpdatedBy: generateUUID(t), + Metrics: []reports.ReqMetric{}, } saved, err := repo.AddReportConfig(context.Background(), reportConfig) @@ -361,6 +362,7 @@ func TestRemoveReportConfig(t *testing.T) { Status: reports.EnabledStatus, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), + Metrics: []reports.ReqMetric{}, } saved, err := repo.AddReportConfig(context.Background(), reportConfig) @@ -414,6 +416,7 @@ func TestListReportsConfig(t *testing.T) { Status: reports.EnabledStatus, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), + Metrics: []reports.ReqMetric{}, } _, err := repo.AddReportConfig(context.Background(), reportConfig) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) @@ -589,7 +592,7 @@ func TestUpdateReportDue(t *testing.T) { saved, err := repo.AddReportConfig(context.Background(), reportConfig) require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) - newDue := time.Now().UTC().Add(24 * time.Hour) + newDue := time.Now().UTC().Add(24 * time.Hour).Truncate(time.Microsecond) cases := []struct { desc string