Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
ac416f7b69
|
|||
e10140e143
|
|||
e165f0f62f
|
|||
655d4daad9
|
|||
87a004e577
|
|||
376c6cab50
|
|||
4a3f25baa0
|
|||
aa33bc8df3
|
|||
96b3718375
|
@@ -7,6 +7,9 @@ type SyncSet[TData comparable] struct {
|
|||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add adds `value` to the set
|
||||||
|
// returns true if the value was actually inserted
|
||||||
|
// returns false if the value already existed
|
||||||
func (s *SyncSet[TData]) Add(value TData) bool {
|
func (s *SyncSet[TData]) Add(value TData) bool {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
@@ -15,10 +18,10 @@ func (s *SyncSet[TData]) Add(value TData) bool {
|
|||||||
s.data = make(map[TData]bool)
|
s.data = make(map[TData]bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := s.data[value]
|
_, existsInPreState := s.data[value]
|
||||||
s.data[value] = true
|
s.data[value] = true
|
||||||
|
|
||||||
return !ok
|
return !existsInPreState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SyncSet[TData]) AddAll(values []TData) {
|
func (s *SyncSet[TData]) AddAll(values []TData) {
|
||||||
|
@@ -80,6 +80,10 @@ func New(t ErrorType, msg string) *Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Wrap(err error, msg string) *Builder {
|
func Wrap(err error, msg string) *Builder {
|
||||||
|
if err == nil {
|
||||||
|
return &Builder{errorData: newExErr(CatSystem, TypeInternal, msg)} // prevent NPE if we call Wrap with err==nil
|
||||||
|
}
|
||||||
|
|
||||||
if !pkgconfig.RecursiveErrors {
|
if !pkgconfig.RecursiveErrors {
|
||||||
v := FromError(err)
|
v := FromError(err)
|
||||||
v.Message = msg
|
v.Message = msg
|
||||||
@@ -270,7 +274,11 @@ func (b *Builder) Any(key string, val any) *Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) Stringer(key string, val fmt.Stringer) *Builder {
|
func (b *Builder) Stringer(key string, val fmt.Stringer) *Builder {
|
||||||
return b.addMeta(key, MDTString, val.String())
|
if val == nil {
|
||||||
|
return b.addMeta(key, MDTString, "(!nil)")
|
||||||
|
} else {
|
||||||
|
return b.addMeta(key, MDTString, val.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) Stack() *Builder {
|
func (b *Builder) Stack() *Builder {
|
||||||
|
@@ -27,6 +27,7 @@ func FromError(err error) *ExErr {
|
|||||||
StatusCode: nil,
|
StatusCode: nil,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
WrappedErrType: fmt.Sprintf("%T", err),
|
WrappedErrType: fmt.Sprintf("%T", err),
|
||||||
|
WrappedErr: err,
|
||||||
Caller: "",
|
Caller: "",
|
||||||
OriginalError: nil,
|
OriginalError: nil,
|
||||||
Meta: getForeignMeta(err),
|
Meta: getForeignMeta(err),
|
||||||
@@ -43,6 +44,7 @@ func newExErr(cat ErrorCategory, errtype ErrorType, msg string) *ExErr {
|
|||||||
StatusCode: nil,
|
StatusCode: nil,
|
||||||
Message: msg,
|
Message: msg,
|
||||||
WrappedErrType: "",
|
WrappedErrType: "",
|
||||||
|
WrappedErr: nil,
|
||||||
Caller: callername(2),
|
Caller: callername(2),
|
||||||
OriginalError: nil,
|
OriginalError: nil,
|
||||||
Meta: make(map[string]MetaValue),
|
Meta: make(map[string]MetaValue),
|
||||||
@@ -59,6 +61,7 @@ func wrapExErr(e *ExErr, msg string, cat ErrorCategory, stacktraceskip int) *ExE
|
|||||||
StatusCode: e.StatusCode,
|
StatusCode: e.StatusCode,
|
||||||
Message: msg,
|
Message: msg,
|
||||||
WrappedErrType: "",
|
WrappedErrType: "",
|
||||||
|
WrappedErr: nil,
|
||||||
Caller: callername(1 + stacktraceskip),
|
Caller: callername(1 + stacktraceskip),
|
||||||
OriginalError: e,
|
OriginalError: e,
|
||||||
Meta: make(map[string]MetaValue),
|
Meta: make(map[string]MetaValue),
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package exerr
|
package exerr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,25 +38,32 @@ type ErrorType struct {
|
|||||||
|
|
||||||
//goland:noinspection GoUnusedGlobalVariable
|
//goland:noinspection GoUnusedGlobalVariable
|
||||||
var (
|
var (
|
||||||
TypeInternal = ErrorType{"INTERNAL_ERROR", langext.Ptr(500)}
|
TypeInternal = NewType("INTERNAL_ERROR", langext.Ptr(500))
|
||||||
TypePanic = ErrorType{"PANIC", langext.Ptr(500)}
|
TypePanic = NewType("PANIC", langext.Ptr(500))
|
||||||
TypeNotImplemented = ErrorType{"NOT_IMPLEMENTED", langext.Ptr(500)}
|
TypeNotImplemented = NewType("NOT_IMPLEMENTED", langext.Ptr(500))
|
||||||
|
|
||||||
TypeWrap = ErrorType{"Wrap", nil}
|
TypeWrap = NewType("Wrap", nil)
|
||||||
|
|
||||||
TypeBindFailURI = ErrorType{"BINDFAIL_URI", langext.Ptr(400)}
|
TypeBindFailURI = NewType("BINDFAIL_URI", langext.Ptr(400))
|
||||||
TypeBindFailQuery = ErrorType{"BINDFAIL_QUERY", langext.Ptr(400)}
|
TypeBindFailQuery = NewType("BINDFAIL_QUERY", langext.Ptr(400))
|
||||||
TypeBindFailJSON = ErrorType{"BINDFAIL_JSON", langext.Ptr(400)}
|
TypeBindFailJSON = NewType("BINDFAIL_JSON", langext.Ptr(400))
|
||||||
TypeBindFailFormData = ErrorType{"BINDFAIL_FORMDATA", langext.Ptr(400)}
|
TypeBindFailFormData = NewType("BINDFAIL_FORMDATA", langext.Ptr(400))
|
||||||
TypeBindFailHeader = ErrorType{"BINDFAIL_HEADER", langext.Ptr(400)}
|
TypeBindFailHeader = NewType("BINDFAIL_HEADER", langext.Ptr(400))
|
||||||
|
|
||||||
TypeUnauthorized = ErrorType{"UNAUTHORIZED", langext.Ptr(401)}
|
TypeUnauthorized = NewType("UNAUTHORIZED", langext.Ptr(401))
|
||||||
TypeAuthFailed = ErrorType{"AUTH_FAILED", langext.Ptr(401)}
|
TypeAuthFailed = NewType("AUTH_FAILED", langext.Ptr(401))
|
||||||
|
|
||||||
// other values come from pkgconfig
|
// other values come from pkgconfig
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var registeredTypes = dataext.SyncSet[string]{}
|
||||||
|
|
||||||
func NewType(key string, defStatusCode *int) ErrorType {
|
func NewType(key string, defStatusCode *int) ErrorType {
|
||||||
|
insertOkay := registeredTypes.Add(key)
|
||||||
|
if !insertOkay {
|
||||||
|
panic("Cannot register same ErrType ('" + key + "') more than once")
|
||||||
|
}
|
||||||
|
|
||||||
return ErrorType{key, defStatusCode}
|
return ErrorType{key, defStatusCode}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -20,6 +21,7 @@ type ExErr struct {
|
|||||||
|
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
WrappedErrType string `json:"wrappedErrType"`
|
WrappedErrType string `json:"wrappedErrType"`
|
||||||
|
WrappedErr any `json:"-"`
|
||||||
Caller string `json:"caller"`
|
Caller string `json:"caller"`
|
||||||
|
|
||||||
OriginalError *ExErr `json:"originalError"`
|
OriginalError *ExErr `json:"originalError"`
|
||||||
@@ -33,6 +35,9 @@ func (ee *ExErr) Error() string {
|
|||||||
|
|
||||||
// Unwrap must be implemented so that some error.XXX methods work
|
// Unwrap must be implemented so that some error.XXX methods work
|
||||||
func (ee *ExErr) Unwrap() error {
|
func (ee *ExErr) Unwrap() error {
|
||||||
|
if ee.OriginalError == nil {
|
||||||
|
return nil // this is neccessary - otherwise we return a wrapped nil and the `x == nil` comparison fails (= panic in errors.Is and other failures)
|
||||||
|
}
|
||||||
return ee.OriginalError
|
return ee.OriginalError
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +46,36 @@ func (ee *ExErr) Is(e error) bool {
|
|||||||
return IsFrom(ee, e)
|
return IsFrom(ee, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// As must be implemented so that error.As(x) works
|
||||||
|
//
|
||||||
|
//goland:noinspection GoTypeAssertionOnErrors
|
||||||
|
func (ee *ExErr) As(target any) bool {
|
||||||
|
if dstErr, ok := target.(*ExErr); ok {
|
||||||
|
|
||||||
|
if dst0, ok := ee.contains(dstErr); ok {
|
||||||
|
dstErr = dst0
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
val := reflect.ValueOf(target)
|
||||||
|
|
||||||
|
typStr := val.Type().Elem().String()
|
||||||
|
|
||||||
|
for curr := ee; curr != nil; curr = curr.OriginalError {
|
||||||
|
if curr.Category == CatForeign && curr.WrappedErrType == typStr && curr.WrappedErr != nil {
|
||||||
|
val.Elem().Set(reflect.ValueOf(curr.WrappedErr))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ee *ExErr) Log(evt *zerolog.Event) {
|
func (ee *ExErr) Log(evt *zerolog.Event) {
|
||||||
evt.Msg(ee.FormatLog(LogPrintFull))
|
evt.Msg(ee.FormatLog(LogPrintFull))
|
||||||
}
|
}
|
||||||
@@ -196,6 +231,7 @@ func (ee *ExErr) RecursiveMeta(key string) *MetaValue {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Depth returns the depth of recursively contained errors
|
||||||
func (ee *ExErr) Depth() int {
|
func (ee *ExErr) Depth() int {
|
||||||
if ee.OriginalError == nil {
|
if ee.OriginalError == nil {
|
||||||
return 1
|
return 1
|
||||||
@@ -204,6 +240,59 @@ func (ee *ExErr) Depth() int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// contains test if the supplied error is contained in this error (anywhere in the chain)
|
||||||
|
func (ee *ExErr) contains(original *ExErr) (*ExErr, bool) {
|
||||||
|
if original == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ee == original {
|
||||||
|
return ee, true
|
||||||
|
}
|
||||||
|
|
||||||
|
for curr := ee; curr != nil; curr = curr.OriginalError {
|
||||||
|
if curr.equalsDirectProperties(curr) {
|
||||||
|
return curr, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// equalsDirectProperties tests if ee and other are equals, but only looks at primary properties (not `OriginalError` or `Meta`)
|
||||||
|
func (ee *ExErr) equalsDirectProperties(other *ExErr) bool {
|
||||||
|
|
||||||
|
if ee.UniqueID != other.UniqueID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ee.Timestamp != other.Timestamp {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ee.Category != other.Category {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ee.Severity != other.Severity {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ee.Type != other.Type {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ee.StatusCode != other.StatusCode {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ee.Message != other.Message {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ee.WrappedErrType != other.WrappedErrType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ee.Caller != other.Caller {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func newID() string {
|
func newID() string {
|
||||||
return xid.New().String()
|
return xid.New().String()
|
||||||
}
|
}
|
||||||
|
93
exerr/exerr_test.go
Normal file
93
exerr/exerr_test.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package exerr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/tst"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type golangErr struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g golangErr) Error() string {
|
||||||
|
return g.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
type golangErr2 struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g golangErr2) Error() string {
|
||||||
|
return g.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g simpleError) Error() string {
|
||||||
|
return "Something simple went wroong"
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleError2 struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g simpleError2) Error() string {
|
||||||
|
return "Something simple went wroong"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExErrIs1(t *testing.T) {
|
||||||
|
e0 := simpleError{}
|
||||||
|
|
||||||
|
wrap := Wrap(e0, "something went wrong").Str("test", "123").Build()
|
||||||
|
|
||||||
|
tst.AssertTrue(t, errors.Is(wrap, simpleError{}))
|
||||||
|
tst.AssertFalse(t, errors.Is(wrap, golangErr{}))
|
||||||
|
tst.AssertFalse(t, errors.Is(wrap, golangErr{"error1"}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExErrIs2(t *testing.T) {
|
||||||
|
e0 := golangErr{"error1"}
|
||||||
|
|
||||||
|
wrap := Wrap(e0, "something went wrong").Str("test", "123").Build()
|
||||||
|
|
||||||
|
tst.AssertTrue(t, errors.Is(wrap, e0))
|
||||||
|
tst.AssertTrue(t, errors.Is(wrap, golangErr{"error1"}))
|
||||||
|
tst.AssertFalse(t, errors.Is(wrap, golangErr{"error2"}))
|
||||||
|
tst.AssertFalse(t, errors.Is(wrap, simpleError{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExErrAs(t *testing.T) {
|
||||||
|
|
||||||
|
e0 := golangErr{"error1"}
|
||||||
|
|
||||||
|
w0 := Wrap(e0, "something went wrong").Str("test", "123").Build()
|
||||||
|
|
||||||
|
{
|
||||||
|
out := golangErr{}
|
||||||
|
ok := errors.As(w0, &out)
|
||||||
|
tst.AssertTrue(t, ok)
|
||||||
|
tst.AssertEqual(t, out.Message, "error1")
|
||||||
|
}
|
||||||
|
|
||||||
|
w1 := Wrap(w0, "outher error").Build()
|
||||||
|
|
||||||
|
{
|
||||||
|
out := golangErr{}
|
||||||
|
ok := errors.As(w1, &out)
|
||||||
|
tst.AssertTrue(t, ok)
|
||||||
|
tst.AssertEqual(t, out.Message, "error1")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
out := golangErr2{}
|
||||||
|
ok := errors.As(w1, &out)
|
||||||
|
tst.AssertFalse(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
out := simpleError2{}
|
||||||
|
ok := errors.As(w1, &out)
|
||||||
|
tst.AssertFalse(t, ok)
|
||||||
|
}
|
||||||
|
}
|
@@ -5,10 +5,8 @@ import (
|
|||||||
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
|
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BodyBuffer() gin.HandlerFunc {
|
func BodyBuffer(g *gin.Context) {
|
||||||
return func(g *gin.Context) {
|
if g.Request.Body != nil {
|
||||||
if g.Request.Body != nil {
|
g.Request.Body = dataext.NewBufferedReadCloser(g.Request.Body)
|
||||||
g.Request.Body = dataext.NewBufferedReadCloser(g.Request.Body)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -112,8 +112,8 @@ func (w *GinRouteBuilder) Use(middleware ...gin.HandlerFunc) *GinRouteBuilder {
|
|||||||
func (w *GinRouteBuilder) Handle(handler WHandlerFunc) {
|
func (w *GinRouteBuilder) Handle(handler WHandlerFunc) {
|
||||||
|
|
||||||
if w.routes.wrapper.bufferBody {
|
if w.routes.wrapper.bufferBody {
|
||||||
arr := make([]gin.HandlerFunc, len(w.handlers)+1)
|
arr := make([]gin.HandlerFunc, 0, len(w.handlers)+1)
|
||||||
arr = append(arr, BodyBuffer())
|
arr = append(arr, BodyBuffer)
|
||||||
arr = append(arr, w.handlers...)
|
arr = append(arr, w.handlers...)
|
||||||
w.handlers = arr
|
w.handlers = arr
|
||||||
}
|
}
|
||||||
@@ -143,13 +143,25 @@ func (w *GinRouteBuilder) Handle(handler WHandlerFunc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *GinWrapper) NoRoute(handler WHandlerFunc) {
|
func (w *GinWrapper) NoRoute(handler WHandlerFunc) {
|
||||||
w.engine.NoRoute(Wrap(w, handler))
|
|
||||||
|
handlers := make([]gin.HandlerFunc, 0)
|
||||||
|
|
||||||
|
if w.bufferBody {
|
||||||
|
handlers = append(handlers, BodyBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
middlewareNames := langext.ArrMap(handlers, func(v gin.HandlerFunc) string { return nameOfFunction(v) })
|
||||||
|
handlerName := nameOfFunction(handler)
|
||||||
|
|
||||||
|
handlers = append(handlers, Wrap(w, handler))
|
||||||
|
|
||||||
|
w.engine.NoRoute(handlers...)
|
||||||
|
|
||||||
w.routeSpecs = append(w.routeSpecs, ginRouteSpec{
|
w.routeSpecs = append(w.routeSpecs, ginRouteSpec{
|
||||||
Method: "ANY",
|
Method: "ANY",
|
||||||
URL: "[NO_ROUTE]",
|
URL: "[NO_ROUTE]",
|
||||||
Middlewares: nil,
|
Middlewares: middlewareNames,
|
||||||
Handler: nameOfFunction(handler),
|
Handler: handlerName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
package goext
|
package goext
|
||||||
|
|
||||||
const GoextVersion = "0.0.223"
|
const GoextVersion = "0.0.232"
|
||||||
|
|
||||||
const GoextVersionTimestamp = "2023-08-08T11:52:40+0200"
|
const GoextVersionTimestamp = "2023-08-08T18:01:00+0200"
|
||||||
|
Reference in New Issue
Block a user