2022-02-24 22:49:46 +01:00
|
|
|
package errors
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2023-12-30 12:55:05 +01:00
|
|
|
"errors"
|
2022-02-24 22:49:46 +01:00
|
|
|
"fmt"
|
2022-03-28 13:00:26 +02:00
|
|
|
"net/http"
|
2022-02-24 22:49:46 +01:00
|
|
|
|
2022-03-28 13:00:26 +02:00
|
|
|
"github.com/kataras/iris/v12/context"
|
2022-02-24 22:49:46 +01:00
|
|
|
"github.com/kataras/iris/v12/x/client"
|
|
|
|
)
|
|
|
|
|
|
|
|
// LogErrorFunc is an alias of a function type which accepts the Iris request context and an error
|
|
|
|
// and it's fired whenever an error should be logged.
|
|
|
|
//
|
|
|
|
// See "OnErrorLog" variable to change the way an error is logged,
|
|
|
|
// by default the error is logged using the Application's Logger's Error method.
|
2022-03-28 13:00:26 +02:00
|
|
|
type LogErrorFunc = func(ctx *context.Context, err error)
|
2022-02-24 22:49:46 +01:00
|
|
|
|
|
|
|
// LogError can be modified to customize the way an error is logged to the server (most common: internal server errors, database errors et.c.).
|
|
|
|
// Can be used to customize the error logging, e.g. using Sentry (cloud-based error console).
|
2022-03-28 13:00:26 +02:00
|
|
|
var LogError LogErrorFunc = func(ctx *context.Context, err error) {
|
2022-02-24 22:49:46 +01:00
|
|
|
ctx.Application().Logger().Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SkipCanceled is a package-level setting which by default
|
|
|
|
// skips the logging of a canceled response or operation.
|
|
|
|
// See the "Context.IsCanceled()" method and "iris.IsCanceled()" function
|
|
|
|
// that decide if the error is caused by a canceled operation.
|
|
|
|
//
|
|
|
|
// Change of this setting MUST be done on initialization of the program.
|
|
|
|
var SkipCanceled = true
|
|
|
|
|
|
|
|
type (
|
|
|
|
// ErrorCodeName is a custom string type represents canonical error names.
|
|
|
|
//
|
|
|
|
// It contains functionality for safe and easy error populating.
|
|
|
|
// See its "Message", "Details", "Data" and "Log" methods.
|
|
|
|
ErrorCodeName string
|
|
|
|
|
|
|
|
// ErrorCode represents the JSON form ErrorCode of the Error.
|
|
|
|
ErrorCode struct {
|
|
|
|
CanonicalName ErrorCodeName `json:"canonical_name" yaml:"CanonicalName"`
|
|
|
|
Status int `json:"status" yaml:"Status"`
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// A read-only map of valid http error codes.
|
|
|
|
var errorCodeMap = make(map[ErrorCodeName]ErrorCode)
|
|
|
|
|
|
|
|
// E registers a custom HTTP Error and returns its canonical name for future use.
|
|
|
|
// The method "New" is reserved and was kept as it is for compatibility
|
|
|
|
// with the standard errors package, therefore the "E" name was chosen instead.
|
|
|
|
// The key stroke "e" is near and accessible while typing the "errors" word
|
|
|
|
// so developers may find it easy to use.
|
|
|
|
//
|
|
|
|
// See "RegisterErrorCode" and "RegisterErrorCodeMap" for alternatives.
|
|
|
|
//
|
|
|
|
// Example:
|
2022-06-17 21:03:18 +02:00
|
|
|
//
|
|
|
|
// var (
|
2023-12-31 17:56:14 +01:00
|
|
|
// NotFound = errors.E("NOT_FOUND", http.StatusNotFound)
|
2022-06-17 21:03:18 +02:00
|
|
|
// )
|
|
|
|
// ...
|
|
|
|
// NotFound.Details(ctx, "resource not found", "user with id: %q was not found", userID)
|
2022-02-24 22:49:46 +01:00
|
|
|
//
|
|
|
|
// This method MUST be called on initialization, before HTTP server starts as
|
|
|
|
// the internal map is not protected by mutex.
|
|
|
|
func E(httpErrorCanonicalName string, httpStatusCode int) ErrorCodeName {
|
|
|
|
canonicalName := ErrorCodeName(httpErrorCanonicalName)
|
|
|
|
RegisterErrorCode(canonicalName, httpStatusCode)
|
|
|
|
return canonicalName
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterErrorCode registers a custom HTTP Error.
|
|
|
|
//
|
|
|
|
// This method MUST be called on initialization, before HTTP server starts as
|
|
|
|
// the internal map is not protected by mutex.
|
|
|
|
func RegisterErrorCode(canonicalName ErrorCodeName, httpStatusCode int) {
|
|
|
|
errorCodeMap[canonicalName] = ErrorCode{
|
|
|
|
CanonicalName: canonicalName,
|
|
|
|
Status: httpStatusCode,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterErrorCodeMap registers one or more custom HTTP Errors.
|
|
|
|
//
|
|
|
|
// This method MUST be called on initialization, before HTTP server starts as
|
|
|
|
// the internal map is not protected by mutex.
|
|
|
|
func RegisterErrorCodeMap(errorMap map[ErrorCodeName]int) {
|
|
|
|
if len(errorMap) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for canonicalName, httpStatusCode := range errorMap {
|
|
|
|
RegisterErrorCode(canonicalName, httpStatusCode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// List of default error codes a server should follow and send back to the client.
|
|
|
|
var (
|
2022-03-28 13:00:26 +02:00
|
|
|
Cancelled ErrorCodeName = E("CANCELLED", context.StatusTokenRequired)
|
|
|
|
Unknown ErrorCodeName = E("UNKNOWN", http.StatusInternalServerError)
|
|
|
|
InvalidArgument ErrorCodeName = E("INVALID_ARGUMENT", http.StatusBadRequest)
|
|
|
|
DeadlineExceeded ErrorCodeName = E("DEADLINE_EXCEEDED", http.StatusGatewayTimeout)
|
|
|
|
NotFound ErrorCodeName = E("NOT_FOUND", http.StatusNotFound)
|
|
|
|
AlreadyExists ErrorCodeName = E("ALREADY_EXISTS", http.StatusConflict)
|
|
|
|
PermissionDenied ErrorCodeName = E("PERMISSION_DENIED", http.StatusForbidden)
|
|
|
|
Unauthenticated ErrorCodeName = E("UNAUTHENTICATED", http.StatusUnauthorized)
|
|
|
|
ResourceExhausted ErrorCodeName = E("RESOURCE_EXHAUSTED", http.StatusTooManyRequests)
|
|
|
|
FailedPrecondition ErrorCodeName = E("FAILED_PRECONDITION", http.StatusBadRequest)
|
|
|
|
Aborted ErrorCodeName = E("ABORTED", http.StatusConflict)
|
|
|
|
OutOfRange ErrorCodeName = E("OUT_OF_RANGE", http.StatusBadRequest)
|
|
|
|
Unimplemented ErrorCodeName = E("UNIMPLEMENTED", http.StatusNotImplemented)
|
|
|
|
Internal ErrorCodeName = E("INTERNAL", http.StatusInternalServerError)
|
|
|
|
Unavailable ErrorCodeName = E("UNAVAILABLE", http.StatusServiceUnavailable)
|
|
|
|
DataLoss ErrorCodeName = E("DATA_LOSS", http.StatusInternalServerError)
|
2022-02-24 22:49:46 +01:00
|
|
|
)
|
|
|
|
|
2023-12-29 22:45:36 +01:00
|
|
|
// errorFuncCodeMap is a read-only map of error code names and their error functions.
|
2023-12-31 17:56:14 +01:00
|
|
|
// See HandleError package-level function.
|
2023-12-29 22:45:36 +01:00
|
|
|
var errorFuncCodeMap = make(map[ErrorCodeName][]func(error) error)
|
|
|
|
|
|
|
|
// HandleError handles an error by sending it to the client
|
|
|
|
// based on the registered error code names and their error functions.
|
|
|
|
// Returns true if the error was handled, otherwise false.
|
|
|
|
// If the given "err" is nil then it returns false.
|
|
|
|
// If the given "err" is a type of validation error then it sends it to the client
|
|
|
|
// using the "Validation" method.
|
|
|
|
// If the given "err" is a type of client.APIError then it sends it to the client
|
|
|
|
// using the "HandleAPIError" function.
|
|
|
|
//
|
|
|
|
// See ErrorCodeName.MapErrorFunc and MapErrors methods too.
|
|
|
|
func HandleError(ctx *context.Context, err error) bool {
|
|
|
|
if err == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-12-30 12:55:05 +01:00
|
|
|
// Unwrap and collect the errors slice so the error result doesn't contain the ErrorCodeName type
|
|
|
|
// and fire the error status code and title based on this error code name itself.
|
|
|
|
var asErrCode ErrorCodeName
|
|
|
|
if As(err, &asErrCode) {
|
|
|
|
if unwrapJoined, ok := err.(joinedErrors); ok {
|
|
|
|
errs := unwrapJoined.Unwrap()
|
|
|
|
errsToKeep := make([]error, 0, len(errs)-1)
|
|
|
|
for _, src := range errs {
|
|
|
|
if _, isErrorCodeName := src.(ErrorCodeName); !isErrorCodeName {
|
|
|
|
errsToKeep = append(errsToKeep, src)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(errsToKeep) > 0 {
|
|
|
|
err = errors.Join(errsToKeep...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
asErrCode.Err(ctx, err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-12-29 22:45:36 +01:00
|
|
|
for errorCodeName, errorFuncs := range errorFuncCodeMap {
|
|
|
|
for _, errorFunc := range errorFuncs {
|
|
|
|
if errToSend := errorFunc(err); errToSend != nil {
|
|
|
|
errorCodeName.Err(ctx, errToSend)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Internal.LogErr(ctx, err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-12-30 12:55:05 +01:00
|
|
|
// Error returns an empty string, it is only declared as a method of ErrorCodeName type in order
|
|
|
|
// to be a compatible error to be joined within other errors:
|
|
|
|
//
|
|
|
|
// err = fmt.Errorf("%w%w", errors.InvalidArgument, err) OR
|
|
|
|
// err = errors.InvalidArgument.Wrap(err)
|
|
|
|
func (e ErrorCodeName) Error() string {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
type joinedErrors interface{ Unwrap() []error }
|
|
|
|
|
|
|
|
// Wrap wraps the given error with this ErrorCodeName.
|
|
|
|
// It calls the standard errors.Join package-level function.
|
|
|
|
// See HandleError function for more.
|
|
|
|
func (e ErrorCodeName) Wrap(err error) error {
|
|
|
|
return errors.Join(e, err)
|
|
|
|
}
|
|
|
|
|
2023-12-29 22:45:36 +01:00
|
|
|
// MapErrorFunc registers a function which will validate the incoming error and
|
|
|
|
// return the same error or overriden in order to be sent to the client, wrapped by this ErrorCodeName "e".
|
|
|
|
//
|
|
|
|
// This method MUST be called on initialization, before HTTP server starts as
|
|
|
|
// the internal map is not protected by mutex.
|
2023-12-30 12:03:01 +01:00
|
|
|
//
|
|
|
|
// Example Code:
|
|
|
|
//
|
|
|
|
// errors.InvalidArgument.MapErrorFunc(func(err error) error {
|
|
|
|
// stripeErr, ok := err.(*stripe.Error)
|
|
|
|
// if !ok {
|
|
|
|
// return nil
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return &errors.Error{
|
|
|
|
// Message: stripeErr.Msg,
|
|
|
|
// Details: stripeErr.DocURL,
|
|
|
|
// }
|
|
|
|
// })
|
2023-12-29 22:45:36 +01:00
|
|
|
func (e ErrorCodeName) MapErrorFunc(fn func(error) error) {
|
|
|
|
errorFuncCodeMap[e] = append(errorFuncCodeMap[e], fn)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MapError registers one or more errors which will be sent to the client wrapped by this "e" ErrorCodeName
|
|
|
|
// when the incoming error matches to at least one of the given "targets" one.
|
|
|
|
//
|
|
|
|
// This method MUST be called on initialization, before HTTP server starts as
|
|
|
|
// the internal map is not protected by mutex.
|
|
|
|
func (e ErrorCodeName) MapErrors(targets ...error) {
|
|
|
|
e.MapErrorFunc(func(err error) error {
|
|
|
|
for _, target := range targets {
|
|
|
|
if Is(err, target) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-02-24 22:49:46 +01:00
|
|
|
// Message sends an error with a simple message to the client.
|
2022-03-28 13:00:26 +02:00
|
|
|
func (e ErrorCodeName) Message(ctx *context.Context, format string, args ...interface{}) {
|
2022-02-24 22:49:46 +01:00
|
|
|
fail(ctx, e, sprintf(format, args...), "", nil, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Details sends an error with a message and details to the client.
|
2022-03-28 13:00:26 +02:00
|
|
|
func (e ErrorCodeName) Details(ctx *context.Context, msg, details string, detailsArgs ...interface{}) {
|
2022-02-24 22:49:46 +01:00
|
|
|
fail(ctx, e, msg, sprintf(details, detailsArgs...), nil, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Data sends an error with a message and json data to the client.
|
2022-03-28 13:00:26 +02:00
|
|
|
func (e ErrorCodeName) Data(ctx *context.Context, msg string, data interface{}) {
|
2022-02-24 22:49:46 +01:00
|
|
|
fail(ctx, e, msg, "", nil, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DataWithDetails sends an error with a message, details and json data to the client.
|
2022-03-28 13:00:26 +02:00
|
|
|
func (e ErrorCodeName) DataWithDetails(ctx *context.Context, msg, details string, data interface{}) {
|
2022-02-24 22:49:46 +01:00
|
|
|
fail(ctx, e, msg, details, nil, data)
|
|
|
|
}
|
|
|
|
|
2022-03-03 19:55:28 +01:00
|
|
|
// Validation sends an error which renders the invalid fields to the client.
|
2022-03-28 13:00:26 +02:00
|
|
|
func (e ErrorCodeName) Validation(ctx *context.Context, validationErrors ...ValidationError) {
|
2022-03-03 19:55:28 +01:00
|
|
|
e.validation(ctx, validationErrors)
|
|
|
|
}
|
|
|
|
|
2022-03-28 13:00:26 +02:00
|
|
|
func (e ErrorCodeName) validation(ctx *context.Context, validationErrors interface{}) {
|
2022-03-03 19:55:28 +01:00
|
|
|
fail(ctx, e, "validation failure", "fields were invalid", validationErrors, nil)
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Err sends the error's text as a message to the client.
|
|
|
|
// In exception, if the given "err" is a type of validation error
|
|
|
|
// then the Validation method is called instead.
|
2022-03-28 13:00:26 +02:00
|
|
|
func (e ErrorCodeName) Err(ctx *context.Context, err error) {
|
2022-02-24 22:49:46 +01:00
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if validationErrors, ok := AsValidationErrors(err); ok {
|
2022-03-03 19:55:28 +01:00
|
|
|
e.validation(ctx, validationErrors)
|
2022-02-24 22:49:46 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-12-29 22:45:36 +01:00
|
|
|
// If it's already an Error type then send it directly.
|
|
|
|
if httpErr, ok := err.(*Error); ok {
|
|
|
|
if errorCode, ok := errorCodeMap[e]; ok {
|
|
|
|
httpErr.ErrorCode = errorCode
|
|
|
|
ctx.StopWithJSON(errorCode.Status, httpErr) // here we override the fail function and send the error as it is.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-24 22:49:46 +01:00
|
|
|
e.Message(ctx, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Log sends an error of "format" and optional "args" to the client and prints that
|
|
|
|
// error using the "LogError" package-level function, which can be customized.
|
|
|
|
//
|
|
|
|
// See "LogErr" too.
|
2022-03-28 13:00:26 +02:00
|
|
|
func (e ErrorCodeName) Log(ctx *context.Context, format string, args ...interface{}) {
|
2022-02-24 22:49:46 +01:00
|
|
|
if SkipCanceled {
|
|
|
|
if ctx.IsCanceled() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, arg := range args {
|
|
|
|
if err, ok := arg.(error); ok {
|
2022-03-28 13:00:26 +02:00
|
|
|
if context.IsErrCanceled(err) {
|
2022-02-24 22:49:46 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err := fmt.Errorf(format, args...)
|
|
|
|
e.LogErr(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LogErr sends the given "err" as message to the client and prints that
|
|
|
|
// error to using the "LogError" package-level function, which can be customized.
|
2022-03-28 13:00:26 +02:00
|
|
|
func (e ErrorCodeName) LogErr(ctx *context.Context, err error) {
|
|
|
|
if SkipCanceled && (ctx.IsCanceled() || context.IsErrCanceled(err)) {
|
2022-02-24 22:49:46 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
LogError(ctx, err)
|
|
|
|
|
|
|
|
e.Message(ctx, "server error")
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleAPIError handles remote server errors.
|
|
|
|
// Optionally, use it when you write your server's HTTP clients using the the /x/client package.
|
|
|
|
// When the HTTP Client sends data to a remote server but that remote server
|
|
|
|
// failed to accept the request as expected, then the error will be proxied
|
|
|
|
// to this server's end-client.
|
|
|
|
//
|
|
|
|
// When the given "err" is not a type of client.APIError then
|
|
|
|
// the error will be sent using the "Internal.LogErr" method which sends
|
|
|
|
// HTTP internal server error to the end-client and
|
|
|
|
// prints the "err" using the "LogError" package-level function.
|
2022-03-28 13:00:26 +02:00
|
|
|
func HandleAPIError(ctx *context.Context, err error) {
|
2022-02-24 22:49:46 +01:00
|
|
|
// Error expected and came from the external server,
|
|
|
|
// save its body so we can forward it to the end-client.
|
|
|
|
if apiErr, ok := client.GetError(err); ok {
|
|
|
|
statusCode := apiErr.Response.StatusCode
|
|
|
|
if statusCode >= 400 && statusCode < 500 {
|
|
|
|
InvalidArgument.DataWithDetails(ctx, "remote server error", "invalid client request", apiErr.Body)
|
|
|
|
} else {
|
|
|
|
Internal.Data(ctx, "remote server error", apiErr.Body)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unavailable.DataWithDetails(ctx, "remote server error", "unavailable", apiErr.Body)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
Internal.LogErr(ctx, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrUnexpected is the HTTP error which sent to the client
|
|
|
|
// when server fails to send an error, it's a fallback error.
|
|
|
|
// The server fails to send an error on two cases:
|
|
|
|
// 1. when the provided error code name is not registered (the error value is the ErrUnexpectedErrorCode)
|
|
|
|
// 2. when the error contains data but cannot be encoded to json (the value of the error is the result error of json.Marshal).
|
2022-03-28 13:00:26 +02:00
|
|
|
ErrUnexpected = E("UNEXPECTED_ERROR", http.StatusInternalServerError)
|
2022-02-24 22:49:46 +01:00
|
|
|
// ErrUnexpectedErrorCode is the error which logged
|
|
|
|
// when the given error code name is not registered.
|
|
|
|
ErrUnexpectedErrorCode = New("unexpected error code name")
|
|
|
|
)
|
|
|
|
|
|
|
|
// Error represents the JSON form of "http wire errors".
|
2022-03-03 19:55:28 +01:00
|
|
|
//
|
|
|
|
// Examples can be found at:
|
2022-06-17 21:03:18 +02:00
|
|
|
//
|
2023-08-20 02:12:46 +02:00
|
|
|
// https://github.com/kataras/iris/tree/main/_examples/routing/http-wire-errors.
|
2022-02-24 22:49:46 +01:00
|
|
|
type Error struct {
|
2022-03-03 19:55:28 +01:00
|
|
|
ErrorCode ErrorCode `json:"http_error_code" yaml:"HTTPErrorCode"`
|
|
|
|
Message string `json:"message,omitempty" yaml:"Message"`
|
|
|
|
Details string `json:"details,omitempty" yaml:"Details"`
|
|
|
|
Validation interface{} `json:"validation,omitempty" yaml:"Validation,omitempty"`
|
|
|
|
Data json.RawMessage `json:"data,omitempty" yaml:"Data,omitempty"` // any other custom json data.
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Error method completes the error interface. It just returns the canonical name, status code, message and details.
|
2022-03-31 19:47:01 +02:00
|
|
|
func (err *Error) Error() string {
|
2022-02-24 22:49:46 +01:00
|
|
|
if err.Message == "" {
|
|
|
|
err.Message = "<empty>"
|
|
|
|
}
|
|
|
|
|
|
|
|
if err.Details == "" {
|
|
|
|
err.Details = "<empty>"
|
|
|
|
}
|
|
|
|
|
|
|
|
if err.ErrorCode.CanonicalName == "" {
|
|
|
|
err.ErrorCode.CanonicalName = ErrUnexpected
|
|
|
|
}
|
|
|
|
|
|
|
|
if err.ErrorCode.Status <= 0 {
|
2022-03-28 13:00:26 +02:00
|
|
|
err.ErrorCode.Status = http.StatusInternalServerError
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return sprintf("iris http wire error: canonical name: %s, http status code: %d, message: %s, details: %s", err.ErrorCode.CanonicalName, err.ErrorCode.Status, err.Message, err.Details)
|
|
|
|
}
|
|
|
|
|
2022-03-28 13:00:26 +02:00
|
|
|
func fail(ctx *context.Context, codeName ErrorCodeName, msg, details string, validationErrors interface{}, dataValue interface{}) {
|
2022-02-24 22:49:46 +01:00
|
|
|
errorCode, ok := errorCodeMap[codeName]
|
|
|
|
if !ok {
|
|
|
|
// This SHOULD NEVER happen, all ErrorCodeNames MUST be registered.
|
|
|
|
LogError(ctx, ErrUnexpectedErrorCode)
|
|
|
|
fail(ctx, ErrUnexpected, msg, details, validationErrors, dataValue)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var data json.RawMessage
|
|
|
|
if dataValue != nil {
|
|
|
|
switch v := dataValue.(type) {
|
|
|
|
case json.RawMessage:
|
|
|
|
data = v
|
|
|
|
case []byte:
|
|
|
|
data = v
|
|
|
|
case error:
|
|
|
|
if msg == "" {
|
|
|
|
msg = v.Error()
|
|
|
|
} else if details == "" {
|
|
|
|
details = v.Error()
|
|
|
|
} else {
|
|
|
|
data = json.RawMessage(v.Error())
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
b, err := json.Marshal(v)
|
|
|
|
if err != nil {
|
|
|
|
LogError(ctx, err)
|
|
|
|
fail(ctx, ErrUnexpected, err.Error(), "", nil, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
data = b
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err := Error{
|
2022-03-03 19:55:28 +01:00
|
|
|
ErrorCode: errorCode,
|
|
|
|
Message: msg,
|
|
|
|
Details: details,
|
|
|
|
Data: data,
|
|
|
|
Validation: validationErrors,
|
2022-02-24 22:49:46 +01:00
|
|
|
}
|
|
|
|
|
2022-03-31 19:47:01 +02:00
|
|
|
// ctx.SetErr(&err)
|
2022-02-24 22:49:46 +01:00
|
|
|
ctx.StopWithJSON(errorCode.Status, err)
|
|
|
|
}
|