package apperrors import ( "net/http" "strings" ) type APPError interface { GetType() ErrorKey GetHTTPStatus() int GetDetail() string SetDetail(string) Error() string } // Error response structure type Error struct { Code int `json:"code"` Type ErrorKey `json:"-"` Message string `json:"message"` Detail string `json:"detail"` HTTPStatus int `json:"-"` ObjectID uint64 `json:"-"` ObjectName string `json:"-"` } func (err Error) GetType() ErrorKey { return err.Type } func (err Error) GetDetail() string { return err.Detail } func (err Error) SetDetail(detail string) { err.Detail = detail } func (err Error) GetHTTPStatus() int { return customErrors[err.Type].HTTPStatus } func (err Error) Error() string { return err.Detail } func NewError(errType ErrorKey, detail string) (err APPError) { switch errType { case DBError, InvalidDBQuery: detail = humanReadableDatabaseError(detail) case ValidationFailed: detail = humanReadableMarshalError(detail) } return Error{ Type: errType, Code: customErrors[errType].Code, Message: customErrors[errType].Message, Detail: detail, HTTPStatus: customErrors[errType].HTTPStatus, } } // customErrors object contains self defined error codes and messages var customErrors = map[ErrorKey]Error{ ItemNotFound: { Code: 1000, Message: MessageItemNotFound, HTTPStatus: http.StatusNotFound, }, InvalidID: { Code: 1001, Message: MessageInvalidID, HTTPStatus: http.StatusBadRequest, }, ValidationFailed: { Code: 1002, Message: MessageValidationFailed, HTTPStatus: http.StatusBadRequest, }, PathNotFound: { Code: 4000, Message: MessagePathNotFound, HTTPStatus: http.StatusNotFound, }, MalformedQueryParameter: { Code: 4001, Message: MessageMalformedQueryParameter, HTTPStatus: http.StatusBadRequest, }, MalformedFilterString: { Code: 4002, Message: MessageMalformedFilterString, HTTPStatus: http.StatusBadRequest, }, InvalidPagination: { Code: 4003, Message: MessageInvalidPagination, HTTPStatus: http.StatusBadRequest, }, UnknownError: { Code: 5000, Message: MessageUnknownError, HTTPStatus: http.StatusInternalServerError, }, DBError: { Code: 5002, Message: MessageDBError, HTTPStatus: http.StatusInternalServerError, }, InvalidDBQuery: { Code: 5003, Message: MessageInvalidDBQuery, HTTPStatus: http.StatusInternalServerError, }, EmptyDBResponse: { Code: 5004, Message: MessageEmptyDBResponse, HTTPStatus: http.StatusNotFound, }, Unauthorized: { Code: 6000, Message: MessageUnauthorized, HTTPStatus: http.StatusUnauthorized, }, Forbidden: { Code: 6001, Message: MessageForbidden, HTTPStatus: http.StatusForbidden, }, ReportBug: { Code: 9999, Message: MessageReportBug, HTTPStatus: http.StatusInternalServerError, }, } // humanReadableMarshalError translates some cryptic error messages func humanReadableMarshalError(detail string) string { switch { case strings.Contains(detail, "decode slice: expect [ or n, but found {"): return "payload must be a list" case strings.Contains(detail, "json: cannot unmarshal object into Go value of type ["): return "payload must be a list" case strings.Contains(detail, "strconv.ParseUint: parsing "): return "id must be numeric" case strings.Contains(detail, "json: cannot unmarshal array into Go value of type"), strings.HasPrefix(detail, "readObjectStart: expect { or n, but found ["): return "payload must be an object" default: return detail } } // humanReadableDatabaseError translates some cryptic error messages func humanReadableDatabaseError(detail string) string { switch { case strings.Contains(detail, "Cannot insert duplicate key row in object 'dbo.users' with unique index 'username'."): return "username already in use, no duplicate usernames allowed" case strings.Contains(detail, "Cannot insert duplicate key row in object 'dbo.roles' with unique index 'rolename'."): return "rolename already in use, no duplicate rolenames allowed" case (strings.Contains(detail, "Duplicate entry") && strings.HasSuffix(detail, "for key 'idx_property_name_tenant_sid'")): return "propertyname already in use, no duplicate propertynames allowed: " + detail case strings.Contains(detail, "Cannot insert duplicate key in object 'dbo.tenants'."): return "Tenant name or id in use, no duplicates allowed" default: return detail } }