2019-07-31 13:47:30 -08:00
|
|
|
package weberror
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-08-03 15:01:17 -08:00
|
|
|
"fmt"
|
2019-07-31 13:47:30 -08:00
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
|
|
|
"net/http"
|
2019-08-03 15:01:17 -08:00
|
|
|
"strings"
|
2019-07-31 13:47:30 -08:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Error is used to pass an error during the request through the
|
|
|
|
// application with web specific context.
|
|
|
|
type Error struct {
|
2019-08-03 15:01:17 -08:00
|
|
|
Err error
|
|
|
|
Status int
|
|
|
|
Fields []FieldError
|
|
|
|
Cause error
|
|
|
|
Message string
|
|
|
|
isValidationError bool
|
2019-07-31 13:47:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// FieldError is used to indicate an error with a specific request field.
|
|
|
|
type FieldError struct {
|
|
|
|
Field string `json:"field"`
|
|
|
|
FormField string `json:"-"`
|
|
|
|
Value interface{} `json:"value"`
|
|
|
|
Tag string `json:"tag"`
|
|
|
|
Error string `json:"error"`
|
|
|
|
Display string `json:"display"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewError wraps a provided error with an HTTP status code. This
|
|
|
|
// function should be used when handlers encounter expected errors.
|
|
|
|
func NewError(ctx context.Context, er error, status int) error {
|
|
|
|
webErr, ok := er.(*Error)
|
|
|
|
if ok {
|
|
|
|
return webErr
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the error was of the type *Error, the handler has
|
|
|
|
// a specific status code and error to return.
|
|
|
|
webErr, ok = errors.Cause(er).(*Error)
|
|
|
|
if ok {
|
|
|
|
return webErr
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the error is not a validation error.
|
|
|
|
if ne, ok := NewValidationError(ctx, er); ok {
|
|
|
|
return ne
|
|
|
|
}
|
|
|
|
|
|
|
|
if er == webcontext.ErrContextRequired {
|
|
|
|
return NewShutdownError(er.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not, the handler sent any arbitrary error value so use 500.
|
|
|
|
if status == 0 {
|
|
|
|
status = http.StatusInternalServerError
|
|
|
|
}
|
|
|
|
|
|
|
|
cause := errors.Cause(er)
|
|
|
|
if cause == nil {
|
|
|
|
cause = er
|
|
|
|
}
|
|
|
|
|
2019-08-03 15:01:17 -08:00
|
|
|
return &Error{er, status, nil, cause, "", false}
|
2019-07-31 13:47:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Error implements the error interface. It uses the default message of the
|
|
|
|
// wrapped error. This is what will be shown in the services' logs.
|
|
|
|
func (err *Error) Error() string {
|
2019-08-01 11:34:03 -08:00
|
|
|
if err.Err != nil {
|
|
|
|
return err.Err.Error()
|
|
|
|
} else if err.Cause != nil {
|
|
|
|
return err.Cause.Error()
|
|
|
|
}
|
|
|
|
return err.Message
|
2019-07-31 13:47:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Display renders an error that can be returned as ErrorResponse to the user via the API.
|
2019-08-03 15:01:17 -08:00
|
|
|
func (er *Error) Response(ctx context.Context, htmlEntities bool) ErrorResponse {
|
2019-07-31 13:47:30 -08:00
|
|
|
var r ErrorResponse
|
|
|
|
|
2019-08-04 14:48:43 -08:00
|
|
|
r.StatusCode = er.Status
|
|
|
|
|
2019-07-31 13:47:30 -08:00
|
|
|
if er.Message != "" {
|
|
|
|
r.Error = er.Message
|
|
|
|
} else {
|
2019-08-03 15:01:17 -08:00
|
|
|
r.Error = http.StatusText(er.Status)
|
2019-07-31 13:47:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(er.Fields) > 0 {
|
|
|
|
r.Fields = er.Fields
|
|
|
|
}
|
|
|
|
|
2019-08-03 15:01:17 -08:00
|
|
|
switch webcontext.ContextEnv(ctx) {
|
|
|
|
case webcontext.Env_Dev, webcontext.Env_Stage:
|
|
|
|
r.Details = fmt.Sprintf("%v", er.Err)
|
|
|
|
|
|
|
|
if er.Cause != nil && er.Cause.Error() != er.Err.Error() {
|
|
|
|
r.StackTrace = fmt.Sprintf("%+v", er.Cause)
|
|
|
|
} else {
|
|
|
|
r.StackTrace = fmt.Sprintf("%+v", er.Err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if htmlEntities {
|
|
|
|
r.Details = strings.Replace(r.Details, "\n", "<br/>", -1)
|
|
|
|
r.StackTrace = strings.Replace(r.StackTrace, "\n", "<br/>", -1)
|
|
|
|
}
|
|
|
|
|
2019-07-31 13:47:30 -08:00
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrorResponse is the form used for API responses from failures in the API.
|
|
|
|
type ErrorResponse struct {
|
2019-08-03 15:01:17 -08:00
|
|
|
StatusCode int `json:"status_code"`
|
|
|
|
Error string `json:"error"`
|
|
|
|
Details string `json:"details,omitempty"`
|
|
|
|
StackTrace string `json:"stack_trace,omitempty"`
|
|
|
|
Fields []FieldError `json:"fields,omitempty"`
|
2019-07-31 13:47:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// String returns the ErrorResponse formatted as a string.
|
|
|
|
func (er ErrorResponse) String() string {
|
|
|
|
str := er.Error
|
|
|
|
|
|
|
|
if len(er.Fields) > 0 {
|
|
|
|
for _, f := range er.Fields {
|
|
|
|
str = str + "\t" + f.Error + "\n"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return str
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewErrorMessage wraps a provided error with an HTTP status code and message. The
|
|
|
|
// message value is given priority and returned as the error message.
|
|
|
|
func NewErrorMessage(ctx context.Context, er error, status int, msg string) error {
|
|
|
|
return WithMessage(ctx, NewError(ctx, er, status), msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMessage appends the error with a message.
|
|
|
|
func WithMessage(ctx context.Context, er error, msg string) error {
|
|
|
|
weberr := NewError(ctx, er, 0).(*Error)
|
|
|
|
weberr.Message = msg
|
|
|
|
return weberr
|
|
|
|
}
|
2019-08-03 15:01:17 -08:00
|
|
|
|
|
|
|
// SessionFlashError
|
|
|
|
func SessionFlashError(ctx context.Context, er error) {
|
|
|
|
|
|
|
|
webErr := NewError(ctx, er, 0).(*Error)
|
|
|
|
|
|
|
|
resp := webErr.Response(ctx, true)
|
|
|
|
|
|
|
|
msg := webcontext.FlashMsg{
|
|
|
|
Type: webcontext.FlashType_Error,
|
|
|
|
Title: resp.Error,
|
|
|
|
}
|
|
|
|
|
|
|
|
if webErr.isValidationError {
|
|
|
|
for _, f := range resp.Fields {
|
|
|
|
msg.Items = append(msg.Items, f.Display)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
msg.Text = resp.Details
|
|
|
|
msg.Details = resp.StackTrace
|
|
|
|
}
|
|
|
|
|
|
|
|
if pts := strings.Split(msg.Details, "<br/>"); len(pts) > 3 {
|
|
|
|
msg.Details = strings.Join(pts[0:3], "<br/>")
|
|
|
|
}
|
|
|
|
|
|
|
|
webcontext.SessionAddFlash(ctx, msg)
|
|
|
|
}
|