diff --git a/README.md b/README.md index 1d6c7d8..c2c5253 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This should not have any heavy dependencies (gin, mongo, etc) and add missing ba Potentially needs `export GOPRIVATE="gogs.mikescher.com"` -### Packages: +## Packages: | Name | Maintainer | Description | |-------------|------------|---------------------------------------------------------------------------------------------------------------| @@ -21,7 +21,7 @@ Potentially needs `export GOPRIVATE="gogs.mikescher.com"` | reflectext | Mike | Utility for golang reflection | | fsext | Mike | Utility for filesytem access | | | | | -| mongoext | Mike | Utility/Helper functions for mongodb | +| mongoext | Mike | Utility/Helper functions for mongodb (kinda abandoned) | | cursortoken | Mike | MongoDB cursortoken implementation | | pagination | Mike | Pagination implementation | | | | | @@ -42,4 +42,69 @@ Potentially needs `export GOPRIVATE="gogs.mikescher.com"` | wmo | Mike | Mongo Wrapper, wraps mongodb with a better interface | | | | | | scn | Mike | SimpleCloudNotifier | -| | | | \ No newline at end of file +| | | | + + + +## Usage: + +### exerr + + - see **mongoext/builder.go** for full info + +Short summary: + - An better error package with metadata, listener, api-output and error-traces + - Initialize with `exerr.Init()` + - *Never* return `err` direct, always use exerr.Wrap(err, "...") - add metadata where applicable + - at the end either Print(), Fatal() or Output() your error (print = stdout, fatal = panic, output = json API response) + - You can add listeners with exerr.RegisterListener(), and save the full errors to a db or smth + +### wmo + + - A typed wrapper around the official mongo-go-driver + - Use `wmo.W[...](...)` to wrap the collections and type-ify them + - The new collections have all the usual methods, but types + - Also they have List() and Paginate() methods for paginated listings (witehr with a cursortoken or page/limit) + - Register additional hooks with `WithDecodeFunc`, `WithUnmarshalHook`, `WithMarshalHook`, `WithModifyingPipeline`, `WithModifyingPipelineFunc` + - List(), Paginate(), etc support filter interfaces + - Rule(s) of thumb: + - filter the results in the filter interface + - sort the results in the sort function of the filter interface + - add joins ($lookup's) in the `WithModifyingPipelineFunc`/`WithModifyingPipeline` + +#### ginext + + - A wrapper around gin-gonic/gin + - create the gin engine with `ginext.NewEngine` + - Add routes with `engine.Routes()...` + - `.Use(..)` adds a middleware + - `.Group(..)` adds a group + - `.Get().Handle(..)` adds a handler + - Handler return values (in contract to ginext) - values implement the `ginext.HTTPResponse` interface + - Every handler starts with something like: +```go +func (handler Handler) CommunityMetricsValues(pctx ginext.PreContext) ginext.HTTPResponse { + type communityURI struct { + Version string `uri:"version"` + CommunityID models.CommunityID `uri:"cid"` + } + type body struct { + UserID models.UserID `json:"userID"` + EventID models.EventID `json:"eventID"` + } + + var u uri + var b body + ctx, gctx, httpErr := pctx.URI(&u).Body(&b).Start() // can have more unmarshaller, like header, form, etc + if httpErr != nil { + return *httpErr + } + defer ctx.Cancel() + + // do stuff +} +``` + +#### sq + + - TODO (like mongoext for sqlite/sql databases) \ No newline at end of file diff --git a/exerr/builder.go b/exerr/builder.go index e99ac1e..016d85f 100644 --- a/exerr/builder.go +++ b/exerr/builder.go @@ -30,6 +30,10 @@ import ( // If possible add metadata to the error (eg the id that was not found, ...), the methods are the same as in zerolog // return nil, exerror.Wrap(err, "do something failed").Str("someid", id).Int("count", in.Count).Build() // +// You can also add extra-data to an error with Extra(..) +// in contrast to metadata is extradata always printed in the resulting error and is more intended for additional (programmatically readable) data in addition to the errortype +// (metadata is more internal debug info/help) +// // You can change the errortype with `.User()` and `.System()` (User-errors are 400 and System-errors 500) // You can also manually set the statuscode with `.WithStatuscode(http.NotFound)` // You can set the type with `WithType(..)` @@ -76,12 +80,14 @@ func Wrap(err error, msg string) *Builder { return &Builder{errorData: newExErr(CatSystem, TypeInternal, msg)} // prevent NPE if we call Wrap with err==nil } + v := FromError(err) + if !pkgconfig.RecursiveErrors { - v := FromError(err) v.Message = msg return &Builder{wrappedErr: err, errorData: v} + } else { + return &Builder{wrappedErr: err, errorData: wrapExErr(v, msg, CatWrap, 1)} } - return &Builder{wrappedErr: err, errorData: wrapExErr(FromError(err), msg, CatWrap, 1)} } // ---------------------------------------------------------------------------- @@ -368,29 +374,6 @@ func (b *Builder) CtxData(method Method, ctx context.Context) *Builder { return b } -func formatHeader(header map[string][]string) string { - ml := 1 - for k, _ := range header { - if len(k) > ml { - ml = len(k) - } - } - r := "" - for k, v := range header { - if r != "" { - r += "\n" - } - for _, hval := range v { - value := hval - value = strings.ReplaceAll(value, "\n", "\\n") - value = strings.ReplaceAll(value, "\r", "\\r") - value = strings.ReplaceAll(value, "\t", "\\t") - r += langext.StrPadRight(k, " ", ml) + " := " + value - } - } - return r -} - func extractHeader(header map[string][]string) []string { r := make([]string, 0, len(header)) for k, v := range header { @@ -407,6 +390,16 @@ func extractHeader(header map[string][]string) []string { // ---------------------------------------------------------------------------- +// Extra adds additional data to the error +// this is not like the other metadata (like Id(), Str(), etc) +// this data is public and will be printed/outputted +func (b *Builder) Extra(key string, val any) *Builder { + b.errorData.Extra[key] = val + return b +} + +// ---------------------------------------------------------------------------- + // Build creates a new error, ready to pass up the stack // If the errors is not SevWarn or SevInfo it gets also logged (in short form, without stacktrace) onto stdout // Can be gloablly configured with ZeroLogErrTraces and ZeroLogAllTraces @@ -468,6 +461,10 @@ func (b *Builder) Print(ctxs ...context.Context) { b.errorData.Log(pkgconfig.ZeroLogger.Error()) } else if b.errorData.Severity == SevWarn { b.errorData.ShortLog(pkgconfig.ZeroLogger.Warn()) + } else if b.errorData.Severity == SevInfo { + b.errorData.ShortLog(pkgconfig.ZeroLogger.Info()) + } else { + b.errorData.ShortLog(pkgconfig.ZeroLogger.Debug()) } b.errorData.CallListener(MethodPrint) diff --git a/exerr/constructor.go b/exerr/constructor.go index 08b80b9..69a2bff 100644 --- a/exerr/constructor.go +++ b/exerr/constructor.go @@ -31,6 +31,7 @@ func FromError(err error) *ExErr { Caller: "", OriginalError: nil, Meta: getForeignMeta(err), + Extra: make(map[string]any), } } @@ -48,6 +49,7 @@ func newExErr(cat ErrorCategory, errtype ErrorType, msg string) *ExErr { Caller: callername(2), OriginalError: nil, Meta: make(map[string]MetaValue), + Extra: make(map[string]any), } } @@ -65,6 +67,7 @@ func wrapExErr(e *ExErr, msg string, cat ErrorCategory, stacktraceskip int) *ExE Caller: callername(1 + stacktraceskip), OriginalError: e, Meta: make(map[string]MetaValue), + Extra: langext.CopyMap(langext.ForceMap(e.Extra)), } } diff --git a/exerr/exerr.go b/exerr/exerr.go index e275c5f..dbc59b5 100644 --- a/exerr/exerr.go +++ b/exerr/exerr.go @@ -1,6 +1,7 @@ package exerr import ( + "fmt" "github.com/rs/xid" "github.com/rs/zerolog" "gogs.mikescher.com/BlackForestBytes/goext/langext" @@ -26,7 +27,8 @@ type ExErr struct { OriginalError *ExErr `json:"originalError"` - Meta MetaMap `json:"meta"` + Extra map[string]any `json:"extra"` + Meta MetaMap `json:"meta"` } func (ee *ExErr) Error() string { @@ -81,6 +83,23 @@ func (ee *ExErr) Log(evt *zerolog.Event) { } func (ee *ExErr) FormatLog(lvl LogPrintLevel) string { + + // [LogPrintShort] + // + // - Only print message and type + // - Used e.g. for logging to the console when Build is called + // - also used in Print() if level == Warn/Info + // + // [LogPrintOverview] + // + // - print message, extra and errortrace + // + // [LogPrintFull] + // + // - print full error, with meta and extra, and trace, etc + // - Used in Output() and Print() + // + if lvl == LogPrintShort { msg := ee.Message @@ -101,6 +120,10 @@ func (ee *ExErr) FormatLog(lvl LogPrintLevel) string { str := "[" + ee.RecursiveType().Key + "] <" + ee.UniqueID + "> " + strings.ReplaceAll(ee.RecursiveMessage(), "\n", " ") + "\n" + for exk, exv := range ee.Extra { + str += fmt.Sprintf(" # [[[ %s ==> %v ]]]\n", exk, exv) + } + indent := "" for curr := ee; curr != nil; curr = curr.OriginalError { indent += " " @@ -122,6 +145,10 @@ func (ee *ExErr) FormatLog(lvl LogPrintLevel) string { str := "[" + ee.RecursiveType().Key + "] <" + ee.UniqueID + "> " + strings.ReplaceAll(ee.RecursiveMessage(), "\n", " ") + "\n" + for exk, exv := range ee.Extra { + str += fmt.Sprintf(" # [[[ %s ==> %v ]]]\n", exk, exv) + } + indent := "" for curr := ee; curr != nil; curr = curr.OriginalError { indent += " " @@ -328,6 +355,14 @@ func (ee *ExErr) GetMetaTime(key string) (time.Time, bool) { return time.Time{}, false } +func (ee *ExErr) GetExtra(key string) (any, bool) { + if v, ok := ee.Extra[key]; ok { + return v, true + } + + return nil, false +} + // 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 { diff --git a/exerr/gin.go b/exerr/gin.go index 14d1b9d..dba9a05 100644 --- a/exerr/gin.go +++ b/exerr/gin.go @@ -90,6 +90,20 @@ func (ee *ExErr) ToAPIJson(applyExtendListener bool, includeWrappedErrors bool, apiOutput["__data"] = ee.toJson(0, applyExtendListener, includeMetaFields) } + for exkey, exval := range ee.Extra { + + // ensure we do not override existing values + for { + if _, ok := apiOutput[exkey]; ok { + exkey = "_" + exkey + } else { + break + } + } + + apiOutput[exkey] = exval + } + if applyExtendListener { pkgconfig.ExtendGinOutput(ee, apiOutput) } diff --git a/go.mod b/go.mod index 5b05c9a..0acd62c 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.4 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -52,7 +52,7 @@ require ( github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect - github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/image v0.18.0 // indirect golang.org/x/net v0.27.0 // indirect diff --git a/go.sum b/go.sum index 060f794..bc504ca 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= @@ -213,6 +215,8 @@ github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqTosly github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 h1:tBiBTKHnIjovYoLX/TPkcf+OjqqKGQrPtGT3Foz+Pgo= github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76/go.mod h1:SQliXeA7Dhkt//vS29v3zpbEwoa+zb2Cn5xj5uO4K5U= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= diff --git a/goextVersion.go b/goextVersion.go index 41cce2e..95a453f 100644 --- a/goextVersion.go +++ b/goextVersion.go @@ -1,5 +1,5 @@ package goext -const GoextVersion = "0.0.489" +const GoextVersion = "0.0.490" -const GoextVersionTimestamp = "2024-07-23T14:21:03+0200" +const GoextVersionTimestamp = "2024-07-27T23:44:18+0200" diff --git a/langext/maps.go b/langext/maps.go index 07c8360..a49bdc0 100644 --- a/langext/maps.go +++ b/langext/maps.go @@ -66,7 +66,7 @@ func CopyMap[K comparable, V any](a map[K]V) map[K]V { func ForceMap[K comparable, V any](v map[K]V) map[K]V { if v == nil { - return make(map[K]V, 0) + return make(map[K]V) } else { return v }