v0.0.490 documentation and extra-params in exerr
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 4m2s

This commit is contained in:
Mike Schwörer 2024-07-27 23:44:18 +02:00
parent e5818146a8
commit 646990b549
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
9 changed files with 152 additions and 34 deletions

View File

@ -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"` Potentially needs `export GOPRIVATE="gogs.mikescher.com"`
### Packages: ## Packages:
| Name | Maintainer | Description | | Name | Maintainer | Description |
|-------------|------------|---------------------------------------------------------------------------------------------------------------| |-------------|------------|---------------------------------------------------------------------------------------------------------------|
@ -21,7 +21,7 @@ Potentially needs `export GOPRIVATE="gogs.mikescher.com"`
| reflectext | Mike | Utility for golang reflection | | reflectext | Mike | Utility for golang reflection |
| fsext | Mike | Utility for filesytem access | | 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 | | cursortoken | Mike | MongoDB cursortoken implementation |
| pagination | Mike | Pagination implementation | | pagination | Mike | Pagination implementation |
| | | | | | | |
@ -43,3 +43,68 @@ Potentially needs `export GOPRIVATE="gogs.mikescher.com"`
| | | | | | | |
| scn | Mike | SimpleCloudNotifier | | scn | Mike | SimpleCloudNotifier |
| | | | | | | |
## 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)

View File

@ -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 // 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() // 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 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 also manually set the statuscode with `.WithStatuscode(http.NotFound)`
// You can set the type with `WithType(..)` // 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 return &Builder{errorData: newExErr(CatSystem, TypeInternal, msg)} // prevent NPE if we call Wrap with err==nil
} }
if !pkgconfig.RecursiveErrors {
v := FromError(err) v := FromError(err)
if !pkgconfig.RecursiveErrors {
v.Message = msg v.Message = msg
return &Builder{wrappedErr: err, errorData: v} 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 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 { func extractHeader(header map[string][]string) []string {
r := make([]string, 0, len(header)) r := make([]string, 0, len(header))
for k, v := range 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 // 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 // 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 // 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()) b.errorData.Log(pkgconfig.ZeroLogger.Error())
} else if b.errorData.Severity == SevWarn { } else if b.errorData.Severity == SevWarn {
b.errorData.ShortLog(pkgconfig.ZeroLogger.Warn()) 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) b.errorData.CallListener(MethodPrint)

View File

@ -31,6 +31,7 @@ func FromError(err error) *ExErr {
Caller: "", Caller: "",
OriginalError: nil, OriginalError: nil,
Meta: getForeignMeta(err), Meta: getForeignMeta(err),
Extra: make(map[string]any),
} }
} }
@ -48,6 +49,7 @@ func newExErr(cat ErrorCategory, errtype ErrorType, msg string) *ExErr {
Caller: callername(2), Caller: callername(2),
OriginalError: nil, OriginalError: nil,
Meta: make(map[string]MetaValue), 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), Caller: callername(1 + stacktraceskip),
OriginalError: e, OriginalError: e,
Meta: make(map[string]MetaValue), Meta: make(map[string]MetaValue),
Extra: langext.CopyMap(langext.ForceMap(e.Extra)),
} }
} }

View File

@ -1,6 +1,7 @@
package exerr package exerr
import ( import (
"fmt"
"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"
@ -26,6 +27,7 @@ type ExErr struct {
OriginalError *ExErr `json:"originalError"` OriginalError *ExErr `json:"originalError"`
Extra map[string]any `json:"extra"`
Meta MetaMap `json:"meta"` Meta MetaMap `json:"meta"`
} }
@ -81,6 +83,23 @@ func (ee *ExErr) Log(evt *zerolog.Event) {
} }
func (ee *ExErr) FormatLog(lvl LogPrintLevel) string { 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 { if lvl == LogPrintShort {
msg := ee.Message 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" 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 := "" indent := ""
for curr := ee; curr != nil; curr = curr.OriginalError { for curr := ee; curr != nil; curr = curr.OriginalError {
indent += " " indent += " "
@ -122,6 +145,10 @@ func (ee *ExErr) FormatLog(lvl LogPrintLevel) string {
str := "[" + ee.RecursiveType().Key + "] <" + ee.UniqueID + "> " + strings.ReplaceAll(ee.RecursiveMessage(), "\n", " ") + "\n" 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 := "" indent := ""
for curr := ee; curr != nil; curr = curr.OriginalError { for curr := ee; curr != nil; curr = curr.OriginalError {
indent += " " indent += " "
@ -328,6 +355,14 @@ func (ee *ExErr) GetMetaTime(key string) (time.Time, bool) {
return time.Time{}, false 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) // contains test if the supplied error is contained in this error (anywhere in the chain)
func (ee *ExErr) contains(original *ExErr) (*ExErr, bool) { func (ee *ExErr) contains(original *ExErr) (*ExErr, bool) {
if original == nil { if original == nil {

View File

@ -90,6 +90,20 @@ func (ee *ExErr) ToAPIJson(applyExtendListener bool, includeWrappedErrors bool,
apiOutput["__data"] = ee.toJson(0, applyExtendListener, includeMetaFields) 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 { if applyExtendListener {
pkgconfig.ExtendGinOutput(ee, apiOutput) pkgconfig.ExtendGinOutput(ee, apiOutput)
} }

4
go.mod
View File

@ -28,7 +28,7 @@ require (
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // 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/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.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/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // 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/arch v0.8.0 // indirect
golang.org/x/image v0.18.0 // indirect golang.org/x/image v0.18.0 // indirect
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.27.0 // indirect

4
go.sum
View File

@ -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.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 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.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 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 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= 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-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 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-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= 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 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=

View File

@ -1,5 +1,5 @@
package goext 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"

View File

@ -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 { func ForceMap[K comparable, V any](v map[K]V) map[K]V {
if v == nil { if v == nil {
return make(map[K]V, 0) return make(map[K]V)
} else { } else {
return v return v
} }