agola/internal/util/errors.go

394 lines
8.4 KiB
Go

// Copyright 2019 Sorint.lab
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"encoding/json"
"fmt"
"strings"
"github.com/sorintlab/errors"
)
// Errors is an error that contains multiple errors
type Errors struct {
Errs []error
}
func (e *Errors) IsErr() bool {
return len(e.Errs) > 0
}
func (e *Errors) Append(err error) {
e.Errs = append(e.Errs, err)
}
func (e *Errors) Error() string {
errs := []string{}
for _, err := range e.Errs {
errs = append(errs, err.Error())
}
return strings.Join(errs, ", ")
}
func (e *Errors) Equal(e2 error) bool {
errs1 := []string{}
errs2 := []string{}
for _, err := range e.Errs {
errs1 = append(errs1, err.Error())
}
var es2 *Errors
if errors.As(e2, &es2) {
for _, err := range es2.Errs {
errs2 = append(errs2, err.Error())
}
} else {
errs2 = append(errs2, e2.Error())
}
return EqualStringSliceNoOrder(errs1, errs2)
}
// Wrapper error is an helper error type that (optionally) wrap an error and add stack information starting at the frame where the error has been created
// It's meant to be embedded in custom errors to avoid the need to redefine the Error, Unwrap and StackTrace methods.
//
// Example usage:
//
// type CustomError struct {
// *WrapperError
// }
//
// func NewCustomError(err error) error {
// return &CustomError{
// util.NewWrapperError(err, util.WithWrapperErrorMsg("connection error")),
// }
// }
//
// Create the error
//
// if err != nil {
// return NewCustomError(err)
// }
//
// Create the error without wrapping another error
//
// return NewCustomError(nil)
//
// Detect error type
//
// var werr *CustomError
// if errors.As(err, &werr) {
// fmt.Println("this is a CustomError")
// }
type WrapperError struct {
err error
msg string
stack *errors.Stack
}
func NewWrapperError(err error, options ...WrapperErrorOption) *WrapperError {
werr := &WrapperError{err: err}
for _, opt := range options {
opt(werr)
}
if werr.stack == nil {
// skip one frame by default if the error is used as in the example
werr.stack = errors.Callers(1)
}
return werr
}
type WrapperErrorOption func(e *WrapperError)
func WithWrapperErrorMsgf(format string, args ...any) WrapperErrorOption {
return func(e *WrapperError) {
e.msg = fmt.Sprintf(format, args...)
}
}
func WithWrapperErrorMsg(format string, a ...any) WrapperErrorOption {
return func(e *WrapperError) {
e.msg = fmt.Sprint(a...)
}
}
func WithWrapperErrorCallerDepth(depth int) WrapperErrorOption {
return func(e *WrapperError) {
e.stack = errors.Callers(depth + 1)
}
}
func (w *WrapperError) Error() string {
if w.err == nil {
return w.msg
}
if w.msg != "" {
return w.msg + ": " + w.err.Error()
} else {
return w.err.Error()
}
}
func (w *WrapperError) Unwrap() error { return w.err }
func (w *WrapperError) StackTrace() errors.StackTrace {
return w.stack.StackTrace()
}
type APIDetailedError struct {
Code ErrorCode
Details any
}
func NewAPIDetailedError(code ErrorCode, options ...APIDetailedErrorOption) *APIDetailedError {
aerr := &APIDetailedError{Code: code}
for _, opt := range options {
opt(aerr)
}
return aerr
}
type APIDetailedErrorOption func(e *APIDetailedError)
func WithAPIDetailedErrorDetails(details any) APIDetailedErrorOption {
return func(e *APIDetailedError) {
e.Details = details
}
}
type ErrorKind int
type ErrorCode string
const (
ErrBadRequest ErrorKind = iota
ErrNotExist
ErrForbidden
ErrUnauthorized
ErrInternal
)
func (k ErrorKind) String() string {
switch k {
case ErrBadRequest:
return "badrequest"
case ErrNotExist:
return "notexist"
case ErrForbidden:
return "forbidden"
case ErrUnauthorized:
return "unauthorized"
case ErrInternal:
return "internal"
}
return "unknown"
}
type APIError struct {
*WrapperError
Kind ErrorKind
DetailedErrors []*APIDetailedError
msg string
depth int
}
func NewAPIErrorWrap(kind ErrorKind, err error, options ...APIErrorOption) error {
aerr := &APIError{Kind: kind, depth: 1}
for _, opt := range options {
opt(aerr)
}
aerr.WrapperError = NewWrapperError(err, WithWrapperErrorMsg(aerr.message()), WithWrapperErrorCallerDepth(aerr.depth))
return aerr
}
func NewAPIError(kind ErrorKind, options ...APIErrorOption) error {
aerr := &APIError{Kind: kind, depth: 1}
for _, opt := range options {
opt(aerr)
}
aerr.WrapperError = NewWrapperError(nil, WithWrapperErrorMsg(aerr.message()), WithWrapperErrorCallerDepth(aerr.depth))
return aerr
}
func (e *APIError) message() string {
msg := e.msg
if msg != "" {
msg += ", "
}
msg += fmt.Sprintf("apiError (kind: %s", e.Kind)
uec := 0
for _, detailedError := range e.DetailedErrors {
if detailedError == nil {
continue
}
msg += fmt.Sprintf(", error[%d]: (", uec)
if detailedError.Code != "" {
msg += fmt.Sprintf("code: %s", detailedError.Code)
} else {
msg += "code: unknown"
}
if detailedError.Details != nil {
if out, err := json.Marshal(detailedError.Details); err != nil {
msg += ", details marshal error"
} else {
msg += fmt.Sprintf(", details: %s", out)
}
}
msg += ")"
uec++
}
msg += ")"
return msg
}
type APIErrorOption func(e *APIError)
// WithAPIErrorMsgf adds an internal message to the error. This message could
// contain sensitive data so it's just for internal logging and will not be sent
// to the api caller.
func WithAPIErrorMsgf(format string, args ...any) APIErrorOption {
return func(e *APIError) {
e.msg = fmt.Sprintf(format, args...)
}
}
// WithAPIErrorMsg adds an internal message to the error. This message could
// contain sensitive data so it's just for internal logging and will not be sent
// to the api caller.
func WithAPIErrorMsg(a ...any) APIErrorOption {
return func(e *APIError) {
e.msg = fmt.Sprint(a...)
}
}
func WithAPIErrorCallerDepth(depth int) APIErrorOption {
return func(e *APIError) {
e.depth = depth
}
}
func WithAPIErrorDetailedError(ue *APIDetailedError) APIErrorOption {
return func(e *APIError) {
e.DetailedErrors = append(e.DetailedErrors, ue)
}
}
func AsAPIError(err error) (*APIError, bool) {
var aerr *APIError
return aerr, errors.As(err, &aerr)
}
func APIErrorIs(err error, kind ErrorKind) bool {
if aerr, ok := AsAPIError(err); ok && aerr.Kind == kind {
return true
}
return false
}
type RemoteDetailedError struct {
Code ErrorCode
Details any
}
// RemoteError is an error received from a remote call. It's similar to
// APIError but with another type so it can be distinguished and won't be
// propagated to the api response.
type RemoteError struct {
Kind ErrorKind
DetailedErrors []*RemoteDetailedError
}
func NewRemoteError(kind ErrorKind, options ...RemoteErrorOption) error {
aerr := &RemoteError{Kind: kind}
for _, opt := range options {
opt(aerr)
}
return aerr
}
type RemoteErrorOption func(e *RemoteError)
func WithRemoteErrorDetailedError(ue *RemoteDetailedError) RemoteErrorOption {
return func(e *RemoteError) {
e.DetailedErrors = append(e.DetailedErrors, ue)
}
}
func (e *RemoteError) Error() string {
msg := fmt.Sprintf("remote error %s", e.Kind)
uec := 0
for _, detailedError := range e.DetailedErrors {
if detailedError == nil {
continue
}
msg += fmt.Sprintf(", error[%d]: (", uec)
if detailedError.Code != "" {
msg += fmt.Sprintf("code: %s", detailedError.Code)
} else {
msg += "code: unknown"
}
if detailedError.Details != nil {
if out, err := json.Marshal(detailedError.Details); err != nil {
msg += ", details marshal error"
} else {
msg += fmt.Sprintf(", details: %s", out)
}
}
msg += ")"
uec++
}
return msg
}
func AsRemoteError(err error) (*RemoteError, bool) {
var rerr *RemoteError
return rerr, errors.As(err, &rerr)
}
func RemoteErrorIs(err error, kind ErrorKind) bool {
if rerr, ok := AsRemoteError(err); ok && rerr.Kind == kind {
return true
}
return false
}
func KindFromRemoteError(err error) ErrorKind {
if rerr, ok := AsRemoteError(err); ok {
return rerr.Kind
}
return ErrInternal
}