package routeutils import ( "database/sql" "fmt" "net" "net/http" "os/exec" "reflect" "strings" "bitbucket.org/nemt/nemt-portal-api/infra/errors" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/go-sql-driver/mysql" "github.com/labstack/echo" ) // resultWrapper has fields for standard message responses type resultWrapper struct { Error bool `json:"error,omitempty"` Message string `json:"message,omitempty"` Redirect bool `json:"redirect,omitempty"` Data interface{} `json:"data,omitempty"` } // ResponseNoContent returns a standard API success with no content response func ResponseNoContent(c echo.Context, data interface{}) error { return c.NoContent(http.StatusNoContent) } // ResponseAPIOK returns a standard API success response func ResponseAPIOK(c echo.Context, data interface{}) error { return c.JSON(http.StatusOK, data) } // ResponseAPIError returns a standard API error to the response func ResponseAPIError(c echo.Context, status int, message string, redirect bool) error { returnValue := resultWrapper{ Error: true, Message: message, Redirect: redirect, } return c.JSON(status, returnValue) } // ResponseAPIAuthError returns a standard API auth error to the response func ResponseAPIAuthError(c echo.Context, message string, redirect bool) error { return ResponseAPIError(c, http.StatusUnauthorized, message, redirect) } // ResponseAPIAuthorizationError returns a standard API auth error to the response func ResponseAPIAuthorizationError(c echo.Context) error { return ResponseAPIError(c, http.StatusForbidden, "Forbidden by controller", false) } // ResponseAPIServiceError returns a standard API service unavailable error to the response func ResponseAPIServiceError(c echo.Context, message string) error { return ResponseAPIError(c, http.StatusServiceUnavailable, message, false) } // ResponseAPIValidationError returns a standard API validation error to the response func ResponseAPIValidationError(c echo.Context, message string) error { return ResponseAPIError(c, http.StatusUnprocessableEntity, message, false) } // ResponseAPIFieldValidationError returns a standard API field validation error to the response func ResponseAPIFieldValidationError(c echo.Context, field string, message string) error { err := errors.NewValidationError(field, message) return HandleAPIError(c, err) } // ResponseAPINotFoundError returns a standard API not found error to the response func ResponseAPINotFoundError(c echo.Context) error { return ResponseAPIError(c, http.StatusNotFound, "Not Found", false) } func ignoreDefaultWrappedErrors(c echo.Context, errorToHandle *errors.WrappedError, handler func(echo.Context, error) error) error { err := errorToHandle.GetOriginalError() if err != nil { switch e := (*err).(type) { case *errors.ValidationError: case *errors.NotAuthorizedError: case *errors.HTTPError: case awserr.Error: default: if e != sql.ErrNoRows { c.Logger().Errorf("%s", e.Error()) } } return handler(c, *err) } return nil } // HandleAPIError applies the default error handling to the response func HandleAPIError(c echo.Context, errorToHandle error) (err error) { statusCode := http.StatusServiceUnavailable errorMessage := "Service Unavailable" if errorToHandle != nil { logger := c.Logger() errorString := errorToHandle.Error() switch e := errorToHandle.(type) { case *errors.WrappedError: return ignoreDefaultWrappedErrors(c, e, HandleAPIError) case *errors.ApplicationError: logger.Errorf("Application Error: %s - %v", e.Path, e.Message) case *errors.ValidationError: return ResponseAPIValidationError(c, errorString) case *errors.NotAuthorizedError: statusCode = http.StatusUnauthorized errorMessage = errorString case *errors.HTTPError: statusCode = e.Status errorMessage = e.Message case *mysql.MySQLError: logger.Errorf("MySQL Error: %v - %v", e.Number, e.Message) errorMessage = "Service temporarily unavailable" case net.Error: logger.Errorf("Network/Connection error: %v", e.Error()) errorMessage = "Service temporarily unavailable" case *echo.HTTPError: statusCode = e.Code errorMessage = fmt.Sprint(e.Message) case *exec.ExitError: logger.Errorf("Exec Error: %s - %v", e.ProcessState, e.Error()) default: if e == sql.ErrNoRows { logger.Debugf("SQL Error: %v", e.Error()) return ResponseAPINotFoundError(c) } else if e == sql.ErrTxDone || strings.HasPrefix(errorString, "sql: ") { logger.Errorf("SQL Error: %v", e.Error()) return ResponseAPIServiceError(c, "Service temporarily unavailable") } logger.Errorf("%v: %v", reflect.TypeOf(e), e.Error()) } } return ResponseAPIError(c, statusCode, errorMessage, false) } // HandleHTMLError applies the default error handling to the response func HandleHTMLError(c echo.Context, errorToHandle error) error { statusCode := http.StatusServiceUnavailable errorMessage := "Service Unavailable" if errorToHandle != nil { logger := c.Logger() errorString := errorToHandle.Error() switch e := errorToHandle.(type) { case *errors.WrappedError: return ignoreDefaultWrappedErrors(c, e, HandleHTMLError) case *errors.ApplicationError: logger.Errorf("Application Error: %s - %v", e.Path, e.Message) case *errors.ValidationError: return ResponseAPIValidationError(c, errorString) case *errors.NotAuthorizedError: return ResponseAPIError(c, http.StatusUnauthorized, errorString, false) case *errors.HTTPError: errorMessage = e.Message case *mysql.MySQLError: logger.Errorf("MySQL Error: %v - %v", e.Number, e.Message) errorMessage = "Service temporarily unavailable" case net.Error: logger.Errorf("Network/Connection error: %v", e.Error()) errorMessage = "Service temporarily unavailable" case *echo.HTTPError: logger.Errorf("%v: %v - %v", reflect.TypeOf(e), e.Code, e.Message) statusCode = e.Code default: if e == sql.ErrNoRows { logger.Debugf("SQL Error: %v", e.Error()) statusCode = http.StatusNotFound errorMessage = "Not Found" } else if e == sql.ErrTxDone || strings.HasPrefix(errorString, "sql: ") { logger.Errorf("SQL Error: %v", e.Error()) errorMessage = "Service temporarily unavailable" } else { logger.Errorf("%v: %v", reflect.TypeOf(e), e.Error()) } } } return c.HTML(statusCode, errorMessage) }