absmach / magistrala

Industrial IoT Messaging and Device Management Platform

Home Page:https://www.abstractmachines.fr/magistrala.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Feature: Remove repository error in API response

arvindh123 opened this issue · comments

Is your feature request related to a problem? Please describe.

I'm worried about getting Postgres error message in API response

Describe the feature you are requesting, as well as the possible use case(s) for it.

We need to have some kind of error layers.

Idea is to prevent or avoid sending of Reposistory errors in API response

One of the way which I have in my mind to implement this idea is, reposistory should always return erorrs from pacakge repoerr
service should return errors from package svcerr,

In API error encoder, We need to check for api errors and service errors and send only api errors and service errors in response and don't want to send repo errors in response.

Example:
for GET request of things/
In response If entity is not found, then i should get error message of svcerr.ErrNotFound, not from repository layer, error return by postgres library.

In logs all the errors should be present. So if there we can get the more error details from service logs.

We need to modify the api.EncodeError to something like below, so that actual error can be avoided to sending back to API response

func EncodeError(_ context.Context, err error, w http.ResponseWriter) {
	var wrapper error
	if errors.Contains(err, apiutil.ErrValidation) {
		wrapper, err = errors.Unwrap(err)
	}

	w.Header().Set("Content-Type", ContentType)

	switch {
	case errors.Contains(err, svcerr.ErrMalformedEntity):
		err = svcerr.ErrMalformedEntity
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, errors.ErrMalformedEntity):
		err = errors.ErrMalformedEntity
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrMissingID):
		err = apiutil.ErrMissingID
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrEmptyList):
		err = apiutil.ErrEmptyList
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrMissingMemberType):
		err = apiutil.ErrMissingMemberType
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrMissingMemberKind):
		err = apiutil.ErrMissingMemberKind
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrLimitSize):
		err = apiutil.ErrLimitSize
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrBearerKey):
		err = apiutil.ErrBearerKey
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrNameSize):
		err = apiutil.ErrNameSize
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, svcerr.ErrInvalidStatus):
		err = svcerr.ErrInvalidStatus
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrInvalidIDFormat):
		err = apiutil.ErrInvalidIDFormat
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrInvalidQueryParams):
		err = apiutil.ErrInvalidQueryParams
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrMissingRelation):
		err = apiutil.ErrMissingRelation
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrValidation):
		err = apiutil.ErrValidation
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrMissingIdentity):
		err = apiutil.ErrMissingIdentity
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrMissingSecret):
		err = apiutil.ErrMissingSecret
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrMissingPass):
		err = apiutil.ErrMissingPass
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrMissingConfPass):
		err = apiutil.ErrMissingConfPass
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, apiutil.ErrPasswordFormat):
		err = apiutil.ErrPasswordFormat
		w.WriteHeader(http.StatusBadRequest)

	case errors.Contains(err, svcerr.ErrAuthentication):
		err = svcerr.ErrAuthentication
		w.WriteHeader(http.StatusUnauthorized)

	case errors.Contains(err, apiutil.ErrBearerToken):
		err = apiutil.ErrBearerToken
		w.WriteHeader(http.StatusUnauthorized)

	case errors.Contains(err, svcerr.ErrNotFound):
		err = svcerr.ErrNotFound
		w.WriteHeader(http.StatusNotFound)

	case errors.Contains(err, postgres.ErrMemberAlreadyAssigned):
		err = postgres.ErrMemberAlreadyAssigned
		w.WriteHeader(http.StatusConflict)

	case errors.Contains(err, svcerr.ErrConflict):
		err = svcerr.ErrConflict
		w.WriteHeader(http.StatusConflict)

	case errors.Contains(err, svcerr.ErrAuthorization):
		err = svcerr.ErrAuthorization
		w.WriteHeader(http.StatusForbidden)

	case errors.Contains(err, svcerr.ErrDomainAuthorization):
		err = svcerr.ErrDomainAuthorization
		w.WriteHeader(http.StatusForbidden)

	case errors.Contains(err, apiutil.ErrUnsupportedContentType):
		err = apiutil.ErrUnsupportedContentType
		w.WriteHeader(http.StatusUnsupportedMediaType)

	case errors.Contains(err, svcerr.ErrCreateEntity):
		err = svcerr.ErrCreateEntity
		w.WriteHeader(http.StatusInternalServerError)

	case errors.Contains(err, svcerr.ErrUpdateEntity):
		err = svcerr.ErrUpdateEntity
		w.WriteHeader(http.StatusInternalServerError)

	case errors.Contains(err, svcerr.ErrViewEntity):
		err = svcerr.ErrViewEntity
		w.WriteHeader(http.StatusInternalServerError)

	case errors.Contains(err, svcerr.ErrRemoveEntity):
		err = svcerr.ErrRemoveEntity
		w.WriteHeader(http.StatusInternalServerError)

	default:
		w.WriteHeader(http.StatusInternalServerError)
	}

	if wrapper != nil {
		err = errors.Wrap(wrapper, err)
	}

	if errorVal, ok := err.(errors.Error); ok {
		if err := json.NewEncoder(w).Encode(errorVal); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
		}
	}
}```
https://github.com/absmach/magistrala/pull/2087#discussion_r1525990456

### Indicate the importance of this feature to you.

Must-have

### Anything else?

_No response_

This looks like we need to Unwrap and replace the whole error with the wrapper in the API layer.

@dborovcanin, unwrapping is a good idea but it will not always work because some errors are not wrapped, and since we will replace the entire error with the wrapper; we might get a nil error while there actually was an error.

Here is an example where and Authorization error is sent but it is not wrapped.

https://github.com/absmach/magistrala/blob/f733147517bb29d5f2a3815b3022462e7234bd1c/users/service.go#L643C1-L645C3

I think @arvindh123 's proposal is fine, if the a service error is contained in an error we replace the entire error with the service error. Then the other errors will be found as logs.