Compare commits

...

14 Commits

Author SHA1 Message Date
8a3965f666 v0.0.327
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m44s
2023-12-02 13:15:19 +01:00
4aa2f494b1 v0.0.326 ginext::WithSession
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m38s
2023-12-02 13:07:36 +01:00
8f15d42173 v0.0.325
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 2m48s
2023-11-27 14:14:58 +01:00
07fa21dcca v0.0.324
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 2m29s
2023-11-25 15:48:28 +01:00
e657de7f78 v0.0.323 fix langext.IsNil for reflect.Array
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m35s
2023-11-16 17:15:44 +01:00
c534e998e8 v0.0.322 bf SecondsF64
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m1s
2023-11-14 16:31:05 +01:00
88642770c5 v0.0.321 Add .NoLog() to lowest-level query exerr.Wrap in wmo (otherwise we get error logs on stdout even if the callign method allows mongo.ErrNoDocuments)
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m4s
2023-11-14 16:00:14 +01:00
8528b5cb66 v0.0.320 bugfix sort
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m14s
2023-11-14 14:50:27 +01:00
5ba84bd8ee v0.0.319 fix error when findoneÃ+pipeline fails
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m15s
2023-11-13 16:45:00 +01:00
1260b2dc77 v0.0.318 add failure mail to testx.yml
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m9s
2023-11-13 15:34:58 +01:00
7d18b913c6 v0.0.317 try fix tests on pipeline
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 2m38s
2023-11-13 15:28:37 +01:00
d1f9069f2f v0.0.316 bugfix sorting
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 2m14s
2023-11-13 15:19:48 +01:00
fa6d73301e v0.0.315 atomic
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 1m26s
2023-11-12 03:10:55 +01:00
bfe62799d3 v0.0.314
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 1m22s
2023-11-10 13:37:55 +01:00
22 changed files with 358 additions and 133 deletions

View File

@@ -6,7 +6,12 @@
name: Build Docker and Deploy
run-name: Build & Deploy ${{ gitea.ref }} on ${{ gitea.actor }}
on: [push]
on:
push:
branches:
- '*'
- '**'
jobs:
run_tests:
@@ -34,3 +39,17 @@ jobs:
- name: Run tests
run: cd "${{ gitea.workspace }}" && make test
- name: Send failure mail
if: failure()
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.fastmail.com
server_port: 465
secure: true
username: ${{secrets.MAIL_USERNAME}}
password: ${{secrets.MAIL_PASSWORD}}
subject: Pipeline on '${{ gitea.repository }}' failed
to: ${{ steps.commiter_info.outputs.MAIL }}
from: Gitea Actions <gitea_actions@blackforestbytes.de>
body: "Go to https://gogs.blackforestbytes.com/${{ gitea.repository }}/actions"

View File

@@ -37,7 +37,7 @@ var rexEnumPackage = rext.W(regexp.MustCompile(`^package\s+(?P<name>[A-Za-z0-9_]
var rexEnumDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*//\s*(@enum:type).*$`))
var rexEnumValueDef = rext.W(regexp.MustCompile(`^\s*(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*=\s*(?P<value>("[A-Za-z0-9_:\s]+"|[0-9]+))\s*(//(?P<descr>.*))?.*$`))
var rexEnumValueDef = rext.W(regexp.MustCompile(`^\s*(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*=\s*(?P<value>("[A-Za-z0-9_:\s\-]+"|[0-9]+))\s*(//(?P<descr>.*))?.*$`))
var rexEnumChecksumConst = rext.W(regexp.MustCompile(`const ChecksumEnumGenerator = "(?P<cs>[A-Za-z0-9_]*)"`))

View File

@@ -70,6 +70,7 @@ func init() {
type Builder struct {
errorData *ExErr
containsGinData bool
noLog bool
}
func Get(err error) *Builder {
@@ -190,6 +191,13 @@ func (b *Builder) System() *Builder {
// ----------------------------------------------------------------------------
func (b *Builder) NoLog() *Builder {
b.noLog = true
return b
}
// ----------------------------------------------------------------------------
func (b *Builder) Id(key string, val fmt.Stringer) *Builder {
return b.addMeta(key, MDTID, newIDWrap(val))
}
@@ -401,12 +409,14 @@ func extractHeader(header map[string][]string) []string {
// 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
// Can be locally suppressed with Builder.NoLog()
func (b *Builder) Build() error {
warnOnPkgConfigNotInitialized()
if pkgconfig.ZeroLogErrTraces && (b.errorData.Severity == SevErr || b.errorData.Severity == SevFatal) {
if pkgconfig.ZeroLogErrTraces && !b.noLog && (b.errorData.Severity == SevErr || b.errorData.Severity == SevFatal) {
b.errorData.ShortLog(stackSkipLogger.Error())
} else if pkgconfig.ZeroLogAllTraces {
} else if pkgconfig.ZeroLogAllTraces && !b.noLog {
b.errorData.ShortLog(stackSkipLogger.Error())
}

View File

@@ -68,6 +68,10 @@ func Init(cfg ErrorPackageConfigInit) {
initialized = true
}
func Initialized() bool {
return initialized
}
func warnOnPkgConfigNotInitialized() {
if !initialized {
fmt.Printf("\n")

View File

@@ -14,7 +14,13 @@ func Wrap(w *GinWrapper, fn WHandlerFunc) gin.HandlerFunc {
reqctx := g.Request.Context()
wrap, stackTrace, panicObj := callPanicSafe(fn, PreContext{wrapper: w, ginCtx: g})
pctx := PreContext{
wrapper: w,
ginCtx: g,
persistantData: &preContextData{},
}
wrap, stackTrace, panicObj := callPanicSafe(fn, pctx)
if panicObj != nil {
fmt.Printf("\n======== ======== STACKTRACE ======== ========\n%s\n======== ======== ======== ========\n\n", stackTrace)
@@ -32,6 +38,13 @@ func Wrap(w *GinWrapper, fn WHandlerFunc) gin.HandlerFunc {
panic("Writing in WrapperFunc is not supported")
}
if pctx.persistantData.sessionObj != nil {
err := pctx.persistantData.sessionObj.Finish(reqctx, wrap)
if err != nil {
wrap = Error(exerr.Wrap(err, "Failed to finish session").Any("originalResponse", wrap).Build())
}
}
if reqctx.Err() == nil {
wrap.Write(g)
}

View File

@@ -24,6 +24,11 @@ type PreContext struct {
form any
header any
timeout *time.Duration
persistantData *preContextData // must be a ptr, so that we can get the values back in out Wrap func
}
type preContextData struct {
sessionObj SessionObject
}
func (pctx *PreContext) URI(uri any) *PreContext {
@@ -61,6 +66,11 @@ func (pctx *PreContext) WithTimeout(to time.Duration) *PreContext {
return pctx
}
func (pctx *PreContext) WithSession(sessionObj SessionObject) *PreContext {
pctx.persistantData.sessionObj = sessionObj
return pctx
}
func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
if pctx.uri != nil {
if err := pctx.ginCtx.ShouldBindUri(pctx.uri); err != nil {
@@ -152,6 +162,15 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) {
}
ictx, cancel := context.WithTimeout(context.Background(), langext.Coalesce(pctx.timeout, pctx.wrapper.requestTimeout))
if pctx.persistantData.sessionObj != nil {
err := pctx.persistantData.sessionObj.Init(pctx.ginCtx, ictx)
if err != nil {
cancel()
return nil, nil, langext.Ptr(Error(exerr.Wrap(err, "Failed to init session").Build()))
}
}
actx := CreateAppContext(pctx.ginCtx, ictx, cancel)
return actx, pctx.ginCtx, nil

View File

@@ -15,6 +15,7 @@ type headerval struct {
type HTTPResponse interface {
Write(g *gin.Context)
WithHeader(k string, v string) HTTPResponse
IsSuccess() bool
}
type jsonHTTPResponse struct {
@@ -39,6 +40,10 @@ func (j jsonHTTPResponse) WithHeader(k string, v string) HTTPResponse {
return j
}
func (j jsonHTTPResponse) IsSuccess() bool {
return j.statusCode >= 200 && j.statusCode <= 399
}
type emptyHTTPResponse struct {
statusCode int
headers []headerval
@@ -56,6 +61,10 @@ func (j emptyHTTPResponse) WithHeader(k string, v string) HTTPResponse {
return j
}
func (j emptyHTTPResponse) IsSuccess() bool {
return j.statusCode >= 200 && j.statusCode <= 399
}
type textHTTPResponse struct {
statusCode int
data string
@@ -74,6 +83,10 @@ func (j textHTTPResponse) WithHeader(k string, v string) HTTPResponse {
return j
}
func (j textHTTPResponse) IsSuccess() bool {
return j.statusCode >= 200 && j.statusCode <= 399
}
type dataHTTPResponse struct {
statusCode int
data []byte
@@ -93,6 +106,10 @@ func (j dataHTTPResponse) WithHeader(k string, v string) HTTPResponse {
return j
}
func (j dataHTTPResponse) IsSuccess() bool {
return j.statusCode >= 200 && j.statusCode <= 399
}
type fileHTTPResponse struct {
mimetype string
filepath string
@@ -117,6 +134,10 @@ func (j fileHTTPResponse) WithHeader(k string, v string) HTTPResponse {
return j
}
func (j fileHTTPResponse) IsSuccess() bool {
return true
}
type downloadDataHTTPResponse struct {
statusCode int
mimetype string
@@ -142,6 +163,10 @@ func (j downloadDataHTTPResponse) WithHeader(k string, v string) HTTPResponse {
return j
}
func (j downloadDataHTTPResponse) IsSuccess() bool {
return j.statusCode >= 200 && j.statusCode <= 399
}
type redirectHTTPResponse struct {
statusCode int
url string
@@ -157,6 +182,10 @@ func (j redirectHTTPResponse) WithHeader(k string, v string) HTTPResponse {
return j
}
func (j redirectHTTPResponse) IsSuccess() bool {
return j.statusCode >= 200 && j.statusCode <= 399
}
type jsonAPIErrResponse struct {
err *exerr.ExErr
headers []headerval
@@ -171,6 +200,10 @@ func (j jsonAPIErrResponse) WithHeader(k string, v string) HTTPResponse {
return j
}
func (j jsonAPIErrResponse) IsSuccess() bool {
return false
}
func Status(sc int) HTTPResponse {
return &emptyHTTPResponse{statusCode: sc}
}

11
ginext/session.go Normal file
View File

@@ -0,0 +1,11 @@
package ginext
import (
"context"
"github.com/gin-gonic/gin"
)
type SessionObject interface {
Init(g *gin.Context, ctx context.Context) error
Finish(ctx context.Context, resp HTTPResponse) error
}

14
go.mod
View File

@@ -1,6 +1,6 @@
module gogs.mikescher.com/BlackForestBytes/goext
go 1.19
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
@@ -8,9 +8,9 @@ require (
github.com/rs/xid v1.5.0
github.com/rs/zerolog v1.31.0
go.mongodb.org/mongo-driver v1.13.0
golang.org/x/crypto v0.15.0
golang.org/x/sys v0.14.0
golang.org/x/term v0.14.0
golang.org/x/crypto v0.16.0
golang.org/x/sys v0.15.0
golang.org/x/term v0.15.0
)
require (
@@ -25,7 +25,7 @@ require (
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
@@ -35,13 +35,13 @@ require (
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
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-20201027041543-1326539a0a0a // indirect
golang.org/x/arch v0.6.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect

14
go.sum
View File

@@ -55,6 +55,10 @@ github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2g
github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA=
github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@@ -106,6 +110,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
@@ -133,6 +139,8 @@ golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -145,6 +153,8 @@ golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
@@ -166,12 +176,16 @@ golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@@ -1,5 +1,5 @@
package goext
const GoextVersion = "0.0.313"
const GoextVersion = "0.0.327"
const GoextVersionTimestamp = "2023-11-10T13:26:30+0100"
const GoextVersionTimestamp = "2023-12-02T13:15:19+0100"

View File

@@ -35,7 +35,7 @@ func IsNil(i interface{}) bool {
return true
}
switch reflect.TypeOf(i).Kind() {
case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice:
case reflect.Ptr, reflect.Map, reflect.Chan, reflect.Slice, reflect.Func, reflect.UnsafePointer:
return reflect.ValueOf(i).IsNil()
}
return false

View File

@@ -5,6 +5,7 @@ import (
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/rfctime"
"reflect"
)
@@ -18,18 +19,21 @@ func CreateGoExtBsonRegistry() *bsoncodec.Registry {
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.RFC3339NanoTime{}), rfctime.RFC3339NanoTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.RFC3339NanoTime{}), rfctime.RFC3339NanoTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.RFC3339NanoTime{}), rfctime.UnixTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.RFC3339NanoTime{}), rfctime.UnixTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.UnixTime{}), rfctime.UnixTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.UnixTime{}), rfctime.UnixTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.RFC3339NanoTime{}), rfctime.UnixMilliTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.RFC3339NanoTime{}), rfctime.UnixMilliTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.UnixMilliTime{}), rfctime.UnixMilliTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.UnixMilliTime{}), rfctime.UnixMilliTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.RFC3339NanoTime{}), rfctime.UnixNanoTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.RFC3339NanoTime{}), rfctime.UnixNanoTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.UnixNanoTime{}), rfctime.UnixNanoTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.UnixNanoTime{}), rfctime.UnixNanoTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.Date{}), rfctime.Date{})
rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.Date{}), rfctime.Date{})
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.SecondsF64(0)), rfctime.SecondsF64(0))
rb.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(rfctime.SecondsF64(0))), rfctime.SecondsF64(0))
bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb)
bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb)

View File

@@ -2,7 +2,14 @@ package rfctime
import (
"encoding/json"
"errors"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
"reflect"
"time"
)
@@ -54,6 +61,63 @@ func (d SecondsF64) MarshalJSON() ([]byte, error) {
return json.Marshal(secs)
}
func (d *SecondsF64) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
if bt == bson.TypeNull {
// we can't set nil in UnmarshalBSONValue (so we use default(struct))
// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values
// https://stackoverflow.com/questions/75167597
// https://jira.mongodb.org/browse/GODRIVER-2252
*d = SecondsF64(0)
return nil
}
if bt != bson.TypeDouble {
return errors.New(fmt.Sprintf("cannot unmarshal %v into SecondsF64", bt))
}
var secValue float64
err := bson.RawValue{Type: bt, Value: data}.Unmarshal(&secValue)
if err != nil {
return err
}
*d = SecondsF64(int64(secValue * float64(time.Second)))
return nil
}
func (d SecondsF64) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.MarshalValue(d.Seconds())
}
func (d SecondsF64) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if val.Kind() == reflect.Ptr && val.IsNil() {
if !val.CanSet() {
return errors.New("ValueUnmarshalerDecodeValue")
}
val.Set(reflect.New(val.Type().Elem()))
}
tp, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
if err != nil {
return err
}
if val.Kind() == reflect.Ptr && len(src) == 0 {
val.Set(reflect.Zero(val.Type()))
return nil
}
err = d.UnmarshalBSONValue(tp, src)
if err != nil {
return err
}
if val.Kind() == reflect.Ptr {
val.Set(reflect.ValueOf(&d))
} else {
val.Set(reflect.ValueOf(d))
}
return nil
}
func NewSecondsF64(t time.Duration) SecondsF64 {
return SecondsF64(t)
}

View File

@@ -76,7 +76,7 @@ func (t *UnixTime) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
return nil
}
if bt != bson.TypeDateTime {
return errors.New(fmt.Sprintf("cannot unmarshal %v into RFC3339NanoTime", bt))
return errors.New(fmt.Sprintf("cannot unmarshal %v into UnixTime", bt))
}
var tt time.Time
err := bson.RawValue{Type: bt, Value: data}.Unmarshal(&tt)

View File

@@ -76,7 +76,7 @@ func (t *UnixMilliTime) UnmarshalBSONValue(bt bsontype.Type, data []byte) error
return nil
}
if bt != bson.TypeDateTime {
return errors.New(fmt.Sprintf("cannot unmarshal %v into RFC3339NanoTime", bt))
return errors.New(fmt.Sprintf("cannot unmarshal %v into UnixMilliTime", bt))
}
var tt time.Time
err := bson.RawValue{Type: bt, Value: data}.Unmarshal(&tt)

View File

@@ -76,7 +76,7 @@ func (t *UnixNanoTime) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
return nil
}
if bt != bson.TypeDateTime {
return errors.New(fmt.Sprintf("cannot unmarshal %v into RFC3339NanoTime", bt))
return errors.New(fmt.Sprintf("cannot unmarshal %v into UnixNanoTime", bt))
}
var tt time.Time
err := bson.RawValue{Type: bt, Value: data}.Unmarshal(&tt)

View File

@@ -1,33 +1,28 @@
package syncext
import (
"context"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"sync"
"time"
)
type AtomicBool struct {
v bool
listener map[string]chan bool
lock sync.Mutex
type Atomic[T any] struct {
v T
lock sync.RWMutex
}
func NewAtomicBool(value bool) *AtomicBool {
return &AtomicBool{
func NewAtomic[T any](value T) *Atomic[T] {
return &Atomic[T]{
v: value,
listener: make(map[string]chan bool),
lock: sync.Mutex{},
lock: sync.RWMutex{},
}
}
func (a *AtomicBool) Get() bool {
a.lock.Lock()
defer a.lock.Unlock()
func (a *Atomic[T]) Get() T {
a.lock.RLock()
defer a.lock.RUnlock()
return a.v
}
func (a *AtomicBool) Set(value bool) bool {
func (a *Atomic[T]) Set(value T) T {
a.lock.Lock()
defer a.lock.Unlock()
@@ -35,79 +30,5 @@ func (a *AtomicBool) Set(value bool) bool {
a.v = value
for k, v := range a.listener {
select {
case v <- value:
// message sent
default:
// no receiver on channel
delete(a.listener, k)
}
}
return oldValue
}
func (a *AtomicBool) Wait(waitFor bool) {
_ = a.WaitWithContext(context.Background(), waitFor)
}
func (a *AtomicBool) WaitWithTimeout(timeout time.Duration, waitFor bool) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return a.WaitWithContext(ctx, waitFor)
}
func (a *AtomicBool) WaitWithContext(ctx context.Context, waitFor bool) error {
if err := ctx.Err(); err != nil {
return err
}
if a.Get() == waitFor {
return nil
}
uuid, _ := langext.NewHexUUID()
waitchan := make(chan bool)
a.lock.Lock()
a.listener[uuid] = waitchan
a.lock.Unlock()
defer func() {
a.lock.Lock()
delete(a.listener, uuid)
a.lock.Unlock()
}()
for {
if err := ctx.Err(); err != nil {
return err
}
timeOut := 1024 * time.Millisecond
if dl, ok := ctx.Deadline(); ok {
timeOutMax := dl.Sub(time.Now())
if timeOutMax <= 0 {
timeOut = 0
} else if 0 < timeOutMax && timeOutMax < timeOut {
timeOut = timeOutMax
}
}
if v, ok := ReadChannelWithTimeout(waitchan, timeOut); ok {
if v == waitFor {
return nil
}
} else {
if err := ctx.Err(); err != nil {
return err
}
if a.Get() == waitFor {
return nil
}
}
}
}

113
syncext/bool.go Normal file
View File

@@ -0,0 +1,113 @@
package syncext
import (
"context"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"sync"
"time"
)
type AtomicBool struct {
v bool
listener map[string]chan bool
lock sync.Mutex
}
func NewAtomicBool(value bool) *AtomicBool {
return &AtomicBool{
v: value,
listener: make(map[string]chan bool),
lock: sync.Mutex{},
}
}
func (a *AtomicBool) Get() bool {
a.lock.Lock()
defer a.lock.Unlock()
return a.v
}
func (a *AtomicBool) Set(value bool) bool {
a.lock.Lock()
defer a.lock.Unlock()
oldValue := a.v
a.v = value
for k, v := range a.listener {
select {
case v <- value:
// message sent
default:
// no receiver on channel
delete(a.listener, k)
}
}
return oldValue
}
func (a *AtomicBool) Wait(waitFor bool) {
_ = a.WaitWithContext(context.Background(), waitFor)
}
func (a *AtomicBool) WaitWithTimeout(timeout time.Duration, waitFor bool) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return a.WaitWithContext(ctx, waitFor)
}
func (a *AtomicBool) WaitWithContext(ctx context.Context, waitFor bool) error {
if err := ctx.Err(); err != nil {
return err
}
if a.Get() == waitFor {
return nil
}
uuid, _ := langext.NewHexUUID()
waitchan := make(chan bool)
a.lock.Lock()
a.listener[uuid] = waitchan
a.lock.Unlock()
defer func() {
a.lock.Lock()
delete(a.listener, uuid)
a.lock.Unlock()
}()
for {
if err := ctx.Err(); err != nil {
return err
}
timeOut := 1024 * time.Millisecond
if dl, ok := ctx.Deadline(); ok {
timeOutMax := dl.Sub(time.Now())
if timeOutMax <= 0 {
timeOut = 0
} else if 0 < timeOutMax && timeOutMax < timeOut {
timeOut = timeOutMax
}
}
if v, ok := ReadChannelWithTimeout(waitchan, timeOut); ok {
if v == waitFor {
return nil
}
} else {
if err := ctx.Err(); err != nil {
return err
}
if a.Get() == waitFor {
return nil
}
}
}
}

View File

@@ -14,12 +14,12 @@ func (c *Coll[TData]) decodeSingle(ctx context.Context, dec Decodable) (TData, e
if c.customDecoder != nil {
res, err = (*c.customDecoder)(ctx, dec)
if err != nil {
return *new(TData), exerr.Wrap(err, "failed to decode single entity with custom-decoder").Type("decoder", *c.customDecoder).Build()
return *new(TData), exerr.Wrap(err, "failed to decode single entity with custom-decoder").Type("decoder", *c.customDecoder).NoLog().Build()
}
} else {
err = dec.Decode(&res)
if err != nil {
return *new(TData), exerr.Wrap(err, "failed to decode single entity").Type("target-type", res).Build()
return *new(TData), exerr.Wrap(err, "failed to decode single entity").Type("target-type", res).NoLog().Build()
}
}
@@ -75,12 +75,12 @@ func (c *Coll[TData]) decodeSingleOrRequery(ctx context.Context, dec Decodable)
var res genDoc
err := dec.Decode(&res)
if err != nil {
return *new(TData), exerr.Wrap(err, "failed to ID-decode entity").Build()
return *new(TData), exerr.Wrap(err, "failed to ID-decode entity").NoLog().Build()
}
v, err := c.findOneInternal(ctx, bson.M{"_id": res.ID}, false)
if err != nil {
return *new(TData), exerr.Wrap(err, "failed to re-query entity").Any("_id", res.ID).Build()
return *new(TData), exerr.Wrap(err, "failed to re-query entity").Any("_id", res.ID).NoLog().Build()
}
return *v, nil

View File

@@ -58,7 +58,7 @@ func (c *Coll[TData]) findOneInternal(ctx context.Context, filter bson.M, allowN
return nil, nil
}
if err != nil {
return nil, exerr.Wrap(err, "mongo-query[find-one] failed").Any("filter", filter).Str("collection", c.Name()).Build()
return nil, exerr.Wrap(err, "mongo-query[find-one] failed").Any("filter", filter).Str("collection", c.Name()).NoLog().Build()
}
return &res, nil
@@ -75,19 +75,19 @@ func (c *Coll[TData]) findOneInternal(ctx context.Context, filter bson.M, allowN
cursor, err := c.coll.Aggregate(ctx, pipeline)
if err != nil {
return nil, exerr.Wrap(err, "mongo-aggregation [find-one] failed").Any("pipeline", pipeline).Str("collection", c.Name()).Build()
return nil, exerr.Wrap(err, "mongo-aggregation [find-one] failed").Any("pipeline", pipeline).Str("collection", c.Name()).NoLog().Build()
}
if cursor.Next(ctx) {
v, err := c.decodeSingle(ctx, cursor)
if err != nil {
return nil, exerr.Wrap(err, "mongo-aggregation [find-one] failed").Any("pipeline", pipeline).Str("collection", c.Name()).Build()
return nil, exerr.Wrap(err, "mongo-aggregation [find-one] failed to decode results").Any("pipeline", pipeline).Str("collection", c.Name()).NoLog().Build()
}
return &v, nil
} else if allowNull {
return nil, nil
} else {
return nil, exerr.Wrap(err, "mongo-aggregation [find-one] failed").Any("pipeline", pipeline).Str("collection", c.Name()).Build()
return nil, exerr.Wrap(mongo.ErrNoDocuments, "mongo-aggregation [find-one] returned no documents").Any("pipeline", pipeline).Str("collection", c.Name()).NoLog().Build()
}
}
}

View File

@@ -42,8 +42,8 @@ func (c *Coll[TData]) Paginate(ctx context.Context, filter pag.Filter, page int,
pipelineCount := mongo.Pipeline{}
pipelineCount = append(pipelineCount, bson.D{{Key: "$count", Value: "count"}})
pipelineList := langext.ArrConcat(mongo.Pipeline{}, pipelineFilter, pipelinePaginate, c.extraModPipeline)
pipelineTotalCount := langext.ArrConcat(mongo.Pipeline{}, pipelineFilter, pipelineCount)
pipelineList := langext.ArrConcat(pipelineFilter, pipelineSort, pipelinePaginate, c.extraModPipeline, pipelineSort)
pipelineTotalCount := langext.ArrConcat(pipelineFilter, pipelineCount)
cursorList, err := c.coll.Aggregate(ctx, pipelineList)
if err != nil {