Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
ffffe4bf24
|
|||
413bf3c848
|
|||
646990b549
|
|||
e5818146a8
|
|||
1310054121
|
|||
49d423915c
|
|||
1962cb3c52
|
|||
84f124dd4d
|
|||
ff8e066135
|
|||
bc5c61e43d
|
69
README.md
69
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"`
|
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)
|
@@ -3,8 +3,8 @@ package cursortoken
|
|||||||
import (
|
import (
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/exerr"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -127,7 +127,7 @@ func Decode(tok string) (CursorToken, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(tok, "tok_") {
|
if !strings.HasPrefix(tok, "tok_") {
|
||||||
return CursorToken{}, errors.New("could not decode token, missing prefix")
|
return CursorToken{}, exerr.New(exerr.TypeCursorTokenDecode, "could not decode token, missing prefix").Str("token", tok).Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := base32.StdEncoding.DecodeString(tok[len("tok_"):])
|
body, err := base32.StdEncoding.DecodeString(tok[len("tok_"):])
|
||||||
@@ -138,7 +138,7 @@ func Decode(tok string) (CursorToken, error) {
|
|||||||
var tokenDeserialize cursorTokenSerialize
|
var tokenDeserialize cursorTokenSerialize
|
||||||
err = json.Unmarshal(body, &tokenDeserialize)
|
err = json.Unmarshal(body, &tokenDeserialize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CursorToken{}, err
|
return CursorToken{}, exerr.Wrap(err, "failed to deserialize token").Str("token", tok).Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
token := CursorToken{Mode: CTMNormal}
|
token := CursorToken{Mode: CTMNormal}
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
14
exerr/gin.go
14
exerr/gin.go
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -3,13 +3,17 @@ package ginext
|
|||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CorsMiddleware() gin.HandlerFunc {
|
func CorsMiddleware(allowheader []string, exposeheader []string) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
c.Writer.Header().Set("Access-Control-Allow-Headers", strings.Join(allowheader, ", "))
|
||||||
|
if len(exposeheader) > 0 {
|
||||||
|
c.Writer.Header().Set("Access-Control-Expose-Headers", strings.Join(exposeheader, ", "))
|
||||||
|
}
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, PATCH, DELETE, COUNT")
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, PATCH, DELETE, COUNT")
|
||||||
|
|
||||||
if c.Request.Method == "OPTIONS" {
|
if c.Request.Method == "OPTIONS" {
|
||||||
|
@@ -21,12 +21,16 @@ type GinWrapper struct {
|
|||||||
|
|
||||||
opt Options
|
opt Options
|
||||||
allowCors bool
|
allowCors bool
|
||||||
|
corsAllowHeader []string
|
||||||
|
corsExposeHeader []string
|
||||||
ginDebug bool
|
ginDebug bool
|
||||||
bufferBody bool
|
bufferBody bool
|
||||||
requestTimeout time.Duration
|
requestTimeout time.Duration
|
||||||
listenerBeforeRequest []func(g *gin.Context)
|
listenerBeforeRequest []func(g *gin.Context)
|
||||||
listenerAfterRequest []func(g *gin.Context, resp HTTPResponse)
|
listenerAfterRequest []func(g *gin.Context, resp HTTPResponse)
|
||||||
|
|
||||||
|
buildRequestBindError func(g *gin.Context, fieldtype string, err error) HTTPResponse
|
||||||
|
|
||||||
routeSpecs []ginRouteSpec
|
routeSpecs []ginRouteSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +43,8 @@ type ginRouteSpec struct {
|
|||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
AllowCors *bool // Add cors handler to allow all CORS requests on the default http methods
|
AllowCors *bool // Add cors handler to allow all CORS requests on the default http methods
|
||||||
|
CorsAllowHeader *[]string // override the default values of Access-Control-Allow-Headers (AllowCors must be true)
|
||||||
|
CorsExposeHeader *[]string // return Access-Control-Expose-Headers (AllowCors must be true)
|
||||||
GinDebug *bool // Set gin.debug to true (adds more logs)
|
GinDebug *bool // Set gin.debug to true (adds more logs)
|
||||||
SuppressGinLogs *bool // Suppress our custom gin logs (even if GinDebug == true)
|
SuppressGinLogs *bool // Suppress our custom gin logs (even if GinDebug == true)
|
||||||
BufferBody *bool // Buffers the input body stream, this way the ginext error handler can later include the whole request body
|
BufferBody *bool // Buffers the input body stream, this way the ginext error handler can later include the whole request body
|
||||||
@@ -47,6 +53,7 @@ type Options struct {
|
|||||||
ListenerAfterRequest []func(g *gin.Context, resp HTTPResponse) // Register listener that are called after the handler method
|
ListenerAfterRequest []func(g *gin.Context, resp HTTPResponse) // Register listener that are called after the handler method
|
||||||
DebugTrimHandlerPrefixes []string // Trim these prefixes from the handler names in the debug print
|
DebugTrimHandlerPrefixes []string // Trim these prefixes from the handler names in the debug print
|
||||||
DebugReplaceHandlerNames map[string]string // Replace handler names in debug output
|
DebugReplaceHandlerNames map[string]string // Replace handler names in debug output
|
||||||
|
BuildRequestBindError func(g *gin.Context, fieldtype string, err error) HTTPResponse // Override function which generates the HTTPResponse errors that are returned by the preContext..Start() methids
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEngine creates a new (wrapped) ginEngine
|
// NewEngine creates a new (wrapped) ginEngine
|
||||||
@@ -72,18 +79,21 @@ func NewEngine(opt Options) *GinWrapper {
|
|||||||
opt: opt,
|
opt: opt,
|
||||||
suppressGinLogs: langext.Coalesce(opt.SuppressGinLogs, false),
|
suppressGinLogs: langext.Coalesce(opt.SuppressGinLogs, false),
|
||||||
allowCors: langext.Coalesce(opt.AllowCors, false),
|
allowCors: langext.Coalesce(opt.AllowCors, false),
|
||||||
|
corsAllowHeader: langext.Coalesce(opt.CorsAllowHeader, []string{"Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "accept", "origin", "Cache-Control", "X-Requested-With"}),
|
||||||
|
corsExposeHeader: langext.Coalesce(opt.CorsExposeHeader, []string{}),
|
||||||
ginDebug: ginDebug,
|
ginDebug: ginDebug,
|
||||||
bufferBody: langext.Coalesce(opt.BufferBody, false),
|
bufferBody: langext.Coalesce(opt.BufferBody, false),
|
||||||
requestTimeout: langext.Coalesce(opt.Timeout, 24*time.Hour),
|
requestTimeout: langext.Coalesce(opt.Timeout, 24*time.Hour),
|
||||||
listenerBeforeRequest: opt.ListenerBeforeRequest,
|
listenerBeforeRequest: opt.ListenerBeforeRequest,
|
||||||
listenerAfterRequest: opt.ListenerAfterRequest,
|
listenerAfterRequest: opt.ListenerAfterRequest,
|
||||||
|
buildRequestBindError: langext.Conditional(opt.BuildRequestBindError == nil, defaultBuildRequestBindError, opt.BuildRequestBindError),
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.RedirectFixedPath = false
|
engine.RedirectFixedPath = false
|
||||||
engine.RedirectTrailingSlash = false
|
engine.RedirectTrailingSlash = false
|
||||||
|
|
||||||
if wrapper.allowCors {
|
if wrapper.allowCors {
|
||||||
engine.Use(CorsMiddleware())
|
engine.Use(CorsMiddleware(wrapper.corsAllowHeader, wrapper.corsExposeHeader))
|
||||||
}
|
}
|
||||||
|
|
||||||
if ginDebug && !wrapper.suppressGinLogs {
|
if ginDebug && !wrapper.suppressGinLogs {
|
||||||
@@ -222,3 +232,7 @@ func (w *GinWrapper) ForwardRequest(writer http.ResponseWriter, req *http.Reques
|
|||||||
func (w *GinWrapper) ListRoutes() []gin.RouteInfo {
|
func (w *GinWrapper) ListRoutes() []gin.RouteInfo {
|
||||||
return w.engine.Routes()
|
return w.engine.Routes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func defaultBuildRequestBindError(g *gin.Context, fieldtype string, err error) HTTPResponse {
|
||||||
|
return Error(err)
|
||||||
|
}
|
||||||
|
9
ginext/jsonFilter.go
Normal file
9
ginext/jsonFilter.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package ginext
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
var jsonFilterKey = "goext.jsonfilter"
|
||||||
|
|
||||||
|
func SetJSONFilter(g *gin.Context, filter string) {
|
||||||
|
g.Set(jsonFilterKey, filter)
|
||||||
|
}
|
@@ -25,6 +25,7 @@ type PreContext struct {
|
|||||||
header any
|
header any
|
||||||
timeout *time.Duration
|
timeout *time.Duration
|
||||||
persistantData *preContextData // must be a ptr, so that we can get the values back in out Wrap func
|
persistantData *preContextData // must be a ptr, so that we can get the values back in out Wrap func
|
||||||
|
ignoreWrongContentType bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type preContextData struct {
|
type preContextData struct {
|
||||||
@@ -71,6 +72,11 @@ func (pctx *PreContext) WithSession(sessionObj SessionObject) *PreContext {
|
|||||||
return pctx
|
return pctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pctx *PreContext) IgnoreWrongContentType() *PreContext {
|
||||||
|
pctx.ignoreWrongContentType = true
|
||||||
|
return pctx
|
||||||
|
}
|
||||||
|
|
||||||
func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
|
func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
|
||||||
if pctx.uri != nil {
|
if pctx.uri != nil {
|
||||||
if err := pctx.ginCtx.ShouldBindUri(pctx.uri); err != nil {
|
if err := pctx.ginCtx.ShouldBindUri(pctx.uri); err != nil {
|
||||||
@@ -78,7 +84,7 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
|
|||||||
WithType(exerr.TypeBindFailURI).
|
WithType(exerr.TypeBindFailURI).
|
||||||
Str("struct_type", fmt.Sprintf("%T", pctx.uri)).
|
Str("struct_type", fmt.Sprintf("%T", pctx.uri)).
|
||||||
Build()
|
Build()
|
||||||
return nil, nil, langext.Ptr(Error(err))
|
return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "URI", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +94,7 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
|
|||||||
WithType(exerr.TypeBindFailQuery).
|
WithType(exerr.TypeBindFailQuery).
|
||||||
Str("struct_type", fmt.Sprintf("%T", pctx.query)).
|
Str("struct_type", fmt.Sprintf("%T", pctx.query)).
|
||||||
Build()
|
Build()
|
||||||
return nil, nil, langext.Ptr(Error(err))
|
return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "QUERY", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,13 +105,15 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
|
|||||||
WithType(exerr.TypeBindFailJSON).
|
WithType(exerr.TypeBindFailJSON).
|
||||||
Str("struct_type", fmt.Sprintf("%T", pctx.body)).
|
Str("struct_type", fmt.Sprintf("%T", pctx.body)).
|
||||||
Build()
|
Build()
|
||||||
return nil, nil, langext.Ptr(Error(err))
|
return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "JSON", err))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if !pctx.ignoreWrongContentType {
|
||||||
err := exerr.New(exerr.TypeBindFailJSON, "missing JSON body").
|
err := exerr.New(exerr.TypeBindFailJSON, "missing JSON body").
|
||||||
Str("struct_type", fmt.Sprintf("%T", pctx.body)).
|
Str("struct_type", fmt.Sprintf("%T", pctx.body)).
|
||||||
Build()
|
Build()
|
||||||
return nil, nil, langext.Ptr(Error(err))
|
return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "JSON", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,14 +121,14 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
|
|||||||
if brc, ok := pctx.ginCtx.Request.Body.(dataext.BufferedReadCloser); ok {
|
if brc, ok := pctx.ginCtx.Request.Body.(dataext.BufferedReadCloser); ok {
|
||||||
v, err := brc.BufferedAll()
|
v, err := brc.BufferedAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, langext.Ptr(Error(err))
|
return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "BODY", err))
|
||||||
}
|
}
|
||||||
*pctx.rawbody = v
|
*pctx.rawbody = v
|
||||||
} else {
|
} else {
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
_, err := io.Copy(buf, pctx.ginCtx.Request.Body)
|
_, err := io.Copy(buf, pctx.ginCtx.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, langext.Ptr(Error(err))
|
return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "BODY", err))
|
||||||
}
|
}
|
||||||
*pctx.rawbody = buf.Bytes()
|
*pctx.rawbody = buf.Bytes()
|
||||||
}
|
}
|
||||||
@@ -133,7 +141,7 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
|
|||||||
WithType(exerr.TypeBindFailFormData).
|
WithType(exerr.TypeBindFailFormData).
|
||||||
Str("struct_type", fmt.Sprintf("%T", pctx.form)).
|
Str("struct_type", fmt.Sprintf("%T", pctx.form)).
|
||||||
Build()
|
Build()
|
||||||
return nil, nil, langext.Ptr(Error(err))
|
return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "FORM", err))
|
||||||
}
|
}
|
||||||
} else if pctx.ginCtx.ContentType() == "application/x-www-form-urlencoded" {
|
} else if pctx.ginCtx.ContentType() == "application/x-www-form-urlencoded" {
|
||||||
if err := pctx.ginCtx.ShouldBindWith(pctx.form, binding.Form); err != nil {
|
if err := pctx.ginCtx.ShouldBindWith(pctx.form, binding.Form); err != nil {
|
||||||
@@ -141,13 +149,15 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
|
|||||||
WithType(exerr.TypeBindFailFormData).
|
WithType(exerr.TypeBindFailFormData).
|
||||||
Str("struct_type", fmt.Sprintf("%T", pctx.form)).
|
Str("struct_type", fmt.Sprintf("%T", pctx.form)).
|
||||||
Build()
|
Build()
|
||||||
return nil, nil, langext.Ptr(Error(err))
|
return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "FORM", err))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if !pctx.ignoreWrongContentType {
|
||||||
err := exerr.New(exerr.TypeBindFailFormData, "missing form body").
|
err := exerr.New(exerr.TypeBindFailFormData, "missing form body").
|
||||||
Str("struct_type", fmt.Sprintf("%T", pctx.form)).
|
Str("struct_type", fmt.Sprintf("%T", pctx.form)).
|
||||||
Build()
|
Build()
|
||||||
return nil, nil, langext.Ptr(Error(err))
|
return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "FORM", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +167,7 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
|
|||||||
WithType(exerr.TypeBindFailHeader).
|
WithType(exerr.TypeBindFailHeader).
|
||||||
Str("struct_type", fmt.Sprintf("%T", pctx.query)).
|
Str("struct_type", fmt.Sprintf("%T", pctx.query)).
|
||||||
Build()
|
Build()
|
||||||
return nil, nil, langext.Ptr(Error(err))
|
return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "HEADER", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +179,7 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
|
|||||||
err := pctx.persistantData.sessionObj.Init(pctx.ginCtx, actx)
|
err := pctx.persistantData.sessionObj.Init(pctx.ginCtx, actx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
actx.Cancel()
|
actx.Cancel()
|
||||||
return nil, nil, langext.Ptr(Error(exerr.Wrap(err, "Failed to init session").Build()))
|
return nil, nil, langext.Ptr(pctx.wrapper.buildRequestBindError(pctx.ginCtx, "INIT", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -36,6 +36,12 @@ type InspectableHTTPResponse interface {
|
|||||||
Headers() []string
|
Headers() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HTTPErrorResponse interface {
|
||||||
|
HTTPResponse
|
||||||
|
|
||||||
|
Error() error
|
||||||
|
}
|
||||||
|
|
||||||
func NotImplemented() HTTPResponse {
|
func NotImplemented() HTTPResponse {
|
||||||
return Error(exerr.New(exerr.TypeNotImplemented, "").Build())
|
return Error(exerr.New(exerr.TypeNotImplemented, "").Build())
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ type jsonHTTPResponse struct {
|
|||||||
|
|
||||||
func (j jsonHTTPResponse) jsonRenderer(g *gin.Context) json.GoJsonRender {
|
func (j jsonHTTPResponse) jsonRenderer(g *gin.Context) json.GoJsonRender {
|
||||||
var f *string
|
var f *string
|
||||||
if jsonfilter := g.GetString("goext.jsonfilter"); jsonfilter != "" {
|
if jsonfilter := g.GetString(jsonFilterKey); jsonfilter != "" {
|
||||||
f = &jsonfilter
|
f = &jsonfilter
|
||||||
}
|
}
|
||||||
return json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true, Filter: f}
|
return json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true, Filter: f}
|
||||||
|
@@ -13,6 +13,10 @@ type jsonAPIErrResponse struct {
|
|||||||
cookies []cookieval
|
cookies []cookieval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j jsonAPIErrResponse) Error() error {
|
||||||
|
return j.err
|
||||||
|
}
|
||||||
|
|
||||||
func (j jsonAPIErrResponse) Write(g *gin.Context) {
|
func (j jsonAPIErrResponse) Write(g *gin.Context) {
|
||||||
for _, v := range j.headers {
|
for _, v := range j.headers {
|
||||||
g.Header(v.Key, v.Val)
|
g.Header(v.Key, v.Val)
|
||||||
|
@@ -57,7 +57,7 @@ func (w *GinRoutesWrapper) Use(middleware ...gin.HandlerFunc) *GinRoutesWrapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *GinRoutesWrapper) WithJSONFilter(filter string) *GinRoutesWrapper {
|
func (w *GinRoutesWrapper) WithJSONFilter(filter string) *GinRoutesWrapper {
|
||||||
return w.Use(func(g *gin.Context) { g.Set("goext.jsonfilter", filter) })
|
return w.Use(func(g *gin.Context) { g.Set(jsonFilterKey, filter) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *GinRoutesWrapper) GET(relativePath string) *GinRouteBuilder {
|
func (w *GinRoutesWrapper) GET(relativePath string) *GinRouteBuilder {
|
||||||
@@ -112,7 +112,7 @@ func (w *GinRouteBuilder) Use(middleware ...gin.HandlerFunc) *GinRouteBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *GinRouteBuilder) WithJSONFilter(filter string) *GinRouteBuilder {
|
func (w *GinRouteBuilder) WithJSONFilter(filter string) *GinRouteBuilder {
|
||||||
return w.Use(func(g *gin.Context) { g.Set("goext.jsonfilter", filter) })
|
return w.Use(func(g *gin.Context) { g.Set(jsonFilterKey, filter) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *GinRouteBuilder) Handle(handler WHandlerFunc) {
|
func (w *GinRouteBuilder) Handle(handler WHandlerFunc) {
|
||||||
|
8
go.mod
8
go.mod
@@ -21,14 +21,14 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.11.9 // indirect
|
github.com/bytedance/sonic v1.12.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/bytedance/sonic/loader v0.2.0 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
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
|
||||||
|
8
go.sum
8
go.sum
@@ -24,9 +24,13 @@ github.com/bytedance/sonic v1.11.8 h1:Zw/j1KfiS+OYTi9lyB3bb0CFxPJVkM17k1wyDG32LR
|
|||||||
github.com/bytedance/sonic v1.11.8/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
github.com/bytedance/sonic v1.11.8/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg=
|
github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg=
|
||||||
github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
|
github.com/bytedance/sonic v1.12.0 h1:YGPgxF9xzaCNvd/ZKdQ28yRovhfMFZQjuk6fKBzZ3ls=
|
||||||
|
github.com/bytedance/sonic v1.12.0/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||||
github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9BqwBO3pm9H/+HY=
|
github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9BqwBO3pm9H/+HY=
|
||||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
|
||||||
|
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
@@ -57,6 +61,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 +219,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=
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
package goext
|
package goext
|
||||||
|
|
||||||
const GoextVersion = "0.0.482"
|
const GoextVersion = "0.0.492"
|
||||||
|
|
||||||
const GoextVersionTimestamp = "2024-07-12T16:33:42+0200"
|
const GoextVersionTimestamp = "2024-08-02T16:19:21+0200"
|
||||||
|
@@ -788,7 +788,7 @@ FieldLoop:
|
|||||||
|
|
||||||
if f.omitEmpty && isEmptyValue(fv) {
|
if f.omitEmpty && isEmptyValue(fv) {
|
||||||
continue
|
continue
|
||||||
} else if opts.filter != nil && len(f.jsonfilter) > 0 && !f.jsonfilter.Contains(*opts.filter) {
|
} else if opts.filter != nil && !matchesJSONFilter(f.jsonfilter, *opts.filter) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
e.WriteByte(next)
|
e.WriteByte(next)
|
||||||
@@ -808,6 +808,26 @@ FieldLoop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchesJSONFilter(filter jsonfilter, value string) bool {
|
||||||
|
if len(filter) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filter) == 1 && filter[0] == "-" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Contains(value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Contains("*") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func newStructEncoder(t reflect.Type, tagkey string) encoderFunc {
|
func newStructEncoder(t reflect.Type, tagkey string) encoderFunc {
|
||||||
se := structEncoder{fields: cachedTypeFields(t, tagkey)}
|
se := structEncoder{fields: cachedTypeFields(t, tagkey)}
|
||||||
return se.encode
|
return se.encode
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -60,12 +60,18 @@ func (c *Coll[TData]) Paginate(ctx context.Context, filter pag.MongoFilter, page
|
|||||||
return nil, pag.Pagination{}, exerr.Wrap(err, "failed to all-decode entities").Build()
|
return nil, pag.Pagination{}, exerr.Wrap(err, "failed to all-decode entities").Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tcRes totalCountResult
|
||||||
|
|
||||||
|
if limit == nil {
|
||||||
|
// optimization, limit==nil, so we query all entities anyway, just use the array length
|
||||||
|
tcRes.Count = len(entities)
|
||||||
|
} else {
|
||||||
|
|
||||||
cursorTotalCount, err := c.coll.Aggregate(ctx, pipelineTotalCount)
|
cursorTotalCount, err := c.coll.Aggregate(ctx, pipelineTotalCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, pag.Pagination{}, exerr.Wrap(err, "mongo-aggregation failed").Any("pipeline", pipelineTotalCount).Str("collection", c.Name()).Build()
|
return nil, pag.Pagination{}, exerr.Wrap(err, "mongo-aggregation failed").Any("pipeline", pipelineTotalCount).Str("collection", c.Name()).Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
var tcRes totalCountResult
|
|
||||||
if cursorTotalCount.Next(ctx) {
|
if cursorTotalCount.Next(ctx) {
|
||||||
err = cursorTotalCount.Decode(&tcRes)
|
err = cursorTotalCount.Decode(&tcRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -75,6 +81,8 @@ func (c *Coll[TData]) Paginate(ctx context.Context, filter pag.MongoFilter, page
|
|||||||
tcRes.Count = 0 // no entries in DB
|
tcRes.Count = 0 // no entries in DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
paginationObj := pag.Pagination{
|
paginationObj := pag.Pagination{
|
||||||
Page: page,
|
Page: page,
|
||||||
Limit: langext.Coalesce(limit, tcRes.Count),
|
Limit: langext.Coalesce(limit, tcRes.Count),
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -284,6 +285,13 @@ func (b *WPDFBuilder) Image(img *PDFImageRef, opts ...*PDFImageOpt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dataimg.ColorModel() != color.RGBAModel && dataimg.ColorModel() != color.NRGBAModel {
|
||||||
|
// the image cannto be 16bpp or similar - otherwise fpdf errors out
|
||||||
|
dataImgRGBA := image.NewNRGBA(image.Rect(0, 0, dataimg.Bounds().Dx(), dataimg.Bounds().Dy()))
|
||||||
|
draw.Draw(dataImgRGBA, dataImgRGBA.Bounds(), dataimg, dataimg.Bounds().Min, draw.Src)
|
||||||
|
dataimg = dataImgRGBA
|
||||||
|
}
|
||||||
|
|
||||||
bfr, imgMime, err := imageext.EncodeImage(dataimg, compression)
|
bfr, imgMime, err := imageext.EncodeImage(dataimg, compression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.b.SetError(err)
|
b.b.SetError(err)
|
||||||
|
Reference in New Issue
Block a user