Compare commits
19 Commits
v0.0.265
...
feature/js
Author | SHA1 | Date | |
---|---|---|---|
0f52b860ea | |||
b5cd116219 | |||
98486842ae
|
|||
7577a2dd47
|
|||
08681756b6
|
|||
64772d0474 | |||
127764556e | |||
170f43d806 | |||
9dffc41274
|
|||
c63cf442f8
|
|||
a2ba283632 | |||
4a1fb1ae18 | |||
a127b24e62 | |||
69d6290376 | |||
c08a739158
|
|||
5f5f0e44f0 | |||
6e6797eac5 | |||
cd9406900a | |||
6c81f7f6bc |
6
Makefile
6
Makefile
@@ -7,5 +7,11 @@ test:
|
||||
which gotestsum || go install gotest.tools/gotestsum@latest
|
||||
gotestsum --format "testname" -- -tags="timetzdata sqlite_fts5 sqlite_foreign_keys" "./..."
|
||||
|
||||
test-in-docker:
|
||||
tag="goext_temp_test_image:$(shell uuidgen | tr -d '-')"; \
|
||||
docker build --tag $$tag . -f .gitea/workflows/Dockerfile_tests; \
|
||||
docker run --rm $$tag; \
|
||||
docker rmi $$tag
|
||||
|
||||
version:
|
||||
_data/version.sh
|
@@ -133,9 +133,6 @@ func run(opt CommandRunner) (CommandResult, error) {
|
||||
|
||||
case <-stderrFailChan:
|
||||
_ = cmd.Process.Kill()
|
||||
for _, lstr := range opt.listener {
|
||||
lstr.Timeout()
|
||||
}
|
||||
|
||||
if fallback, ok := syncext.ReadChannelWithTimeout(outputChan, 32*time.Millisecond); ok {
|
||||
// most of the time the cmd.Process.Kill() should also have finished the pipereader
|
||||
@@ -160,7 +157,8 @@ func run(opt CommandRunner) (CommandResult, error) {
|
||||
}
|
||||
|
||||
case outobj := <-outputChan:
|
||||
if exiterr, ok := outobj.err.(*exec.ExitError); ok {
|
||||
var exiterr *exec.ExitError
|
||||
if errors.As(outobj.err, &exiterr) {
|
||||
excode := exiterr.ExitCode()
|
||||
for _, lstr := range opt.listener {
|
||||
lstr.Finished(excode)
|
||||
|
@@ -32,8 +32,8 @@ func (pr *pipeReader) Read(listener []CommandListener) (string, string, string,
|
||||
stdout := ""
|
||||
go func() {
|
||||
buf := make([]byte, 128)
|
||||
for true {
|
||||
n, out := pr.stdout.Read(buf)
|
||||
for {
|
||||
n, err := pr.stdout.Read(buf)
|
||||
if n > 0 {
|
||||
txt := string(buf[:n])
|
||||
stdout += txt
|
||||
@@ -42,11 +42,11 @@ func (pr *pipeReader) Read(listener []CommandListener) (string, string, string,
|
||||
lstr.ReadRawStdout(buf[:n])
|
||||
}
|
||||
}
|
||||
if out == io.EOF {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if out != nil {
|
||||
errch <- out
|
||||
if err != nil {
|
||||
errch <- err
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func (pr *pipeReader) Read(listener []CommandListener) (string, string, string,
|
||||
stderr := ""
|
||||
go func() {
|
||||
buf := make([]byte, 128)
|
||||
for true {
|
||||
for {
|
||||
n, err := pr.stderr.Read(buf)
|
||||
|
||||
if n > 0 {
|
||||
|
@@ -164,7 +164,7 @@ func (ee *ExErr) FormatLog(lvl LogPrintLevel) string {
|
||||
}
|
||||
|
||||
func (ee *ExErr) ShortLog(evt *zerolog.Event) {
|
||||
ee.Meta.Apply(evt).Msg(ee.FormatLog(LogPrintShort))
|
||||
ee.Meta.Apply(evt, langext.Ptr(240)).Msg(ee.FormatLog(LogPrintShort))
|
||||
}
|
||||
|
||||
// RecursiveMessage returns the message to show
|
||||
|
@@ -217,23 +217,35 @@ func (v MetaValue) ShortString(lim int) string {
|
||||
return "(err)"
|
||||
}
|
||||
|
||||
func (v MetaValue) Apply(key string, evt *zerolog.Event) *zerolog.Event {
|
||||
func (v MetaValue) Apply(key string, evt *zerolog.Event, limitLen *int) *zerolog.Event {
|
||||
switch v.DataType {
|
||||
case MDTString:
|
||||
return evt.Str(key, v.Value.(string))
|
||||
if limitLen == nil {
|
||||
return evt.Str(key, v.Value.(string))
|
||||
} else {
|
||||
return evt.Str(key, langext.StrLimit(v.Value.(string), *limitLen, "..."))
|
||||
}
|
||||
case MDTID:
|
||||
return evt.Str(key, v.Value.(IDWrap).Value)
|
||||
case MDTAny:
|
||||
if v.Value.(AnyWrap).IsError {
|
||||
return evt.Str(key, "(err)")
|
||||
} else {
|
||||
return evt.Str(key, v.Value.(AnyWrap).Json)
|
||||
if limitLen == nil {
|
||||
return evt.Str(key, v.Value.(AnyWrap).Json)
|
||||
} else {
|
||||
return evt.Str(key, langext.StrLimit(v.Value.(AnyWrap).Json, *limitLen, "..."))
|
||||
}
|
||||
}
|
||||
case MDTStringPtr:
|
||||
if langext.IsNil(v.Value) {
|
||||
return evt.Str(key, "<<null>>")
|
||||
}
|
||||
return evt.Str(key, langext.CoalesceString(v.Value.(*string), "<<null>>"))
|
||||
if limitLen == nil {
|
||||
return evt.Str(key, langext.CoalesceString(v.Value.(*string), "<<null>>"))
|
||||
} else {
|
||||
return evt.Str(key, langext.StrLimit(langext.CoalesceString(v.Value.(*string), "<<null>>"), *limitLen, "..."))
|
||||
}
|
||||
case MDTInt:
|
||||
return evt.Int(key, v.Value.(int))
|
||||
case MDTInt8:
|
||||
@@ -702,9 +714,9 @@ func (mm MetaMap) Any() bool {
|
||||
return len(mm) > 0
|
||||
}
|
||||
|
||||
func (mm MetaMap) Apply(evt *zerolog.Event) *zerolog.Event {
|
||||
func (mm MetaMap) Apply(evt *zerolog.Event, limitLen *int) *zerolog.Event {
|
||||
for key, val := range mm {
|
||||
evt = val.Apply(key, evt)
|
||||
evt = val.Apply(key, evt, limitLen)
|
||||
}
|
||||
return evt
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@@ -6,7 +6,7 @@ require (
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/rs/xid v1.5.0
|
||||
github.com/rs/zerolog v1.30.0
|
||||
github.com/rs/zerolog v1.31.0
|
||||
go.mongodb.org/mongo-driver v1.12.1
|
||||
golang.org/x/crypto v0.13.0
|
||||
golang.org/x/sys v0.12.0
|
||||
|
2
go.sum
2
go.sum
@@ -105,6 +105,8 @@ github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
|
||||
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
|
||||
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
|
||||
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
|
||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
|
@@ -1,5 +1,5 @@
|
||||
package goext
|
||||
|
||||
const GoextVersion = "0.0.265"
|
||||
const GoextVersion = "0.0.275"
|
||||
|
||||
const GoextVersionTimestamp = "2023-09-18T12:57:27+0200"
|
||||
const GoextVersionTimestamp = "2023-09-29T16:00:40+0200"
|
||||
|
@@ -156,7 +156,6 @@ import (
|
||||
// an error.
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
e := newEncodeState()
|
||||
defer encodeStatePool.Put(e)
|
||||
|
||||
err := e.marshal(v, encOpts{escapeHTML: true})
|
||||
if err != nil {
|
||||
@@ -164,6 +163,8 @@ func Marshal(v any) ([]byte, error) {
|
||||
}
|
||||
buf := append([]byte(nil), e.Bytes()...)
|
||||
|
||||
encodeStatePool.Put(e)
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
@@ -174,9 +175,9 @@ type IndentOpt struct {
|
||||
|
||||
// MarshalSafeCollections is like Marshal except it will marshal nil maps and
|
||||
// slices as '{}' and '[]' respectfully instead of 'null'
|
||||
func MarshalSafeCollections(v interface{}, nilSafeSlices bool, nilSafeMaps bool, indent *IndentOpt) ([]byte, error) {
|
||||
func MarshalSafeCollections(v interface{}, nilSafeSlices bool, nilSafeMaps bool, indent *IndentOpt, filter *string) ([]byte, error) {
|
||||
e := &encodeState{}
|
||||
err := e.marshal(v, encOpts{escapeHTML: true, nilSafeSlices: nilSafeSlices, nilSafeMaps: nilSafeMaps})
|
||||
err := e.marshal(v, encOpts{escapeHTML: true, nilSafeSlices: nilSafeSlices, nilSafeMaps: nilSafeMaps, filter: filter})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -393,6 +394,9 @@ type encOpts struct {
|
||||
nilSafeSlices bool
|
||||
// nilSafeMaps marshals a nil maps '{}' instead of 'null'
|
||||
nilSafeMaps bool
|
||||
// filter matches jsonfilter tag of struct
|
||||
// marshals if no jsonfilter is set or otherwise if jsonfilter has the filter value
|
||||
filter *string
|
||||
}
|
||||
|
||||
type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts)
|
||||
@@ -777,6 +781,8 @@ FieldLoop:
|
||||
|
||||
if f.omitEmpty && isEmptyValue(fv) {
|
||||
continue
|
||||
} else if opts.filter != nil && len(f.jsonfilter) > 0 && !f.jsonfilter.Contains(*opts.filter) {
|
||||
continue
|
||||
}
|
||||
e.WriteByte(next)
|
||||
next = ','
|
||||
@@ -1220,15 +1226,28 @@ type field struct {
|
||||
nameNonEsc string // `"` + name + `":`
|
||||
nameEscHTML string // `"` + HTMLEscape(name) + `":`
|
||||
|
||||
tag bool
|
||||
index []int
|
||||
typ reflect.Type
|
||||
omitEmpty bool
|
||||
quoted bool
|
||||
tag bool
|
||||
index []int
|
||||
typ reflect.Type
|
||||
omitEmpty bool
|
||||
jsonfilter jsonfilter
|
||||
quoted bool
|
||||
|
||||
encoder encoderFunc
|
||||
}
|
||||
|
||||
// jsonfilter stores the value of the jsonfilter struct tag
|
||||
type jsonfilter []string
|
||||
|
||||
func (j jsonfilter) Contains(t string) bool {
|
||||
for _, tag := range j {
|
||||
if t == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// byIndex sorts field by index sequence.
|
||||
type byIndex []field
|
||||
|
||||
@@ -1304,6 +1323,13 @@ func typeFields(t reflect.Type) structFields {
|
||||
if !isValidTag(name) {
|
||||
name = ""
|
||||
}
|
||||
|
||||
var jsonfilter []string
|
||||
jsonfilterTag := sf.Tag.Get("jsonfilter")
|
||||
if isValidTag(jsonfilterTag) {
|
||||
jsonfilter = strings.Split(jsonfilterTag, ",")
|
||||
}
|
||||
|
||||
index := make([]int, len(f.index)+1)
|
||||
copy(index, f.index)
|
||||
index[len(f.index)] = i
|
||||
@@ -1334,12 +1360,13 @@ func typeFields(t reflect.Type) structFields {
|
||||
name = sf.Name
|
||||
}
|
||||
field := field{
|
||||
name: name,
|
||||
tag: tagged,
|
||||
index: index,
|
||||
typ: ft,
|
||||
omitEmpty: opts.Contains("omitempty"),
|
||||
quoted: quoted,
|
||||
name: name,
|
||||
tag: tagged,
|
||||
index: index,
|
||||
typ: ft,
|
||||
omitEmpty: opts.Contains("omitempty"),
|
||||
jsonfilter: jsonfilter,
|
||||
quoted: quoted,
|
||||
}
|
||||
field.nameBytes = []byte(field.name)
|
||||
field.equalFold = foldFunc(field.nameBytes)
|
||||
|
@@ -1253,6 +1253,10 @@ func TestMarshalSafeCollections(t *testing.T) {
|
||||
nilMapStruct struct {
|
||||
NilMap map[string]interface{} `json:"nil_map"`
|
||||
}
|
||||
testWithFilter struct {
|
||||
Test1 string `json:"test1" jsonfilter:"FILTERONE"`
|
||||
Test2 string `json:"test2" jsonfilter:"FILTERTWO"`
|
||||
}
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
@@ -1271,10 +1275,12 @@ func TestMarshalSafeCollections(t *testing.T) {
|
||||
{map[string]interface{}{"1": 1, "2": 2, "3": 3}, "{\"1\":1,\"2\":2,\"3\":3}"},
|
||||
{pNilMap, "null"},
|
||||
{nilMapStruct{}, "{\"nil_map\":{}}"},
|
||||
{testWithFilter{}, "{\"test1\":\"\"}"},
|
||||
}
|
||||
|
||||
filter := "FILTERONE"
|
||||
for i, tt := range tests {
|
||||
b, err := MarshalSafeCollections(tt.in, true, true, nil)
|
||||
b, err := MarshalSafeCollections(tt.in, true, true, nil, &filter)
|
||||
if err != nil {
|
||||
t.Errorf("test %d, unexpected failure: %v", i, err)
|
||||
}
|
||||
|
@@ -97,7 +97,10 @@ func equalFoldRight(s, t []byte) bool {
|
||||
t = t[size:]
|
||||
|
||||
}
|
||||
return len(t) == 0
|
||||
if len(t) > 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// asciiEqualFold is a specialization of bytes.EqualFold for use when
|
||||
|
@@ -52,7 +52,9 @@ func TestFold(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFoldAgainstUnicode(t *testing.T) {
|
||||
var buf1, buf2 []byte
|
||||
const bufSize = 5
|
||||
buf1 := make([]byte, 0, bufSize)
|
||||
buf2 := make([]byte, 0, bufSize)
|
||||
var runes []rune
|
||||
for i := 0x20; i <= 0x7f; i++ {
|
||||
runes = append(runes, rune(i))
|
||||
@@ -94,8 +96,12 @@ func TestFoldAgainstUnicode(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
for _, r2 := range runes {
|
||||
buf1 = append(utf8.AppendRune(append(buf1[:0], 'x'), r), 'x')
|
||||
buf2 = append(utf8.AppendRune(append(buf2[:0], 'x'), r2), 'x')
|
||||
buf1 := append(buf1[:0], 'x')
|
||||
buf2 := append(buf2[:0], 'x')
|
||||
buf1 = buf1[:1+utf8.EncodeRune(buf1[1:bufSize], r)]
|
||||
buf2 = buf2[:1+utf8.EncodeRune(buf2[1:bufSize], r2)]
|
||||
buf1 = append(buf1, 'x')
|
||||
buf2 = append(buf2, 'x')
|
||||
want := bytes.EqualFold(buf1, buf2)
|
||||
if got := ff.fold(buf1, buf2); got != want {
|
||||
t.Errorf("%s(%q, %q) = %v; want %v", ff.name, buf1, buf2, got, want)
|
||||
|
@@ -17,6 +17,7 @@ type GoJsonRender struct {
|
||||
NilSafeSlices bool
|
||||
NilSafeMaps bool
|
||||
Indent *IndentOpt
|
||||
Filter *string
|
||||
}
|
||||
|
||||
func (r GoJsonRender) Render(w http.ResponseWriter) error {
|
||||
@@ -25,7 +26,7 @@ func (r GoJsonRender) Render(w http.ResponseWriter) error {
|
||||
header["Content-Type"] = []string{"application/json; charset=utf-8"}
|
||||
}
|
||||
|
||||
jsonBytes, err := MarshalSafeCollections(r.Data, r.NilSafeSlices, r.NilSafeMaps, r.Indent)
|
||||
jsonBytes, err := MarshalSafeCollections(r.Data, r.NilSafeSlices, r.NilSafeMaps, r.Indent, r.Filter)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@@ -116,3 +116,18 @@ func TestNumberIsValid(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNumberIsValid(b *testing.B) {
|
||||
s := "-61657.61667E+61673"
|
||||
for i := 0; i < b.N; i++ {
|
||||
isValidNumber(s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNumberIsValidRegexp(b *testing.B) {
|
||||
var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
|
||||
s := "-61657.61667E+61673"
|
||||
for i := 0; i < b.N; i++ {
|
||||
jsonNumberRegexp.MatchString(s)
|
||||
}
|
||||
}
|
||||
|
@@ -594,7 +594,7 @@ func (s *scanner) error(c byte, context string) int {
|
||||
return scanError
|
||||
}
|
||||
|
||||
// quoteChar formats c as a quoted character literal.
|
||||
// quoteChar formats c as a quoted character literal
|
||||
func quoteChar(c byte) string {
|
||||
// special cases - different from quoted strings
|
||||
if c == '\'' {
|
||||
|
@@ -179,11 +179,9 @@ func nonSpace(b []byte) bool {
|
||||
|
||||
// An Encoder writes JSON values to an output stream.
|
||||
type Encoder struct {
|
||||
w io.Writer
|
||||
err error
|
||||
escapeHTML bool
|
||||
nilSafeSlices bool
|
||||
nilSafeMaps bool
|
||||
w io.Writer
|
||||
err error
|
||||
escapeHTML bool
|
||||
|
||||
indentBuf *bytes.Buffer
|
||||
indentPrefix string
|
||||
@@ -204,11 +202,8 @@ func (enc *Encoder) Encode(v any) error {
|
||||
if enc.err != nil {
|
||||
return enc.err
|
||||
}
|
||||
|
||||
e := newEncodeState()
|
||||
defer encodeStatePool.Put(e)
|
||||
|
||||
err := e.marshal(v, encOpts{escapeHTML: enc.escapeHTML, nilSafeMaps: enc.nilSafeMaps, nilSafeSlices: enc.nilSafeSlices})
|
||||
err := e.marshal(v, encOpts{escapeHTML: enc.escapeHTML})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -236,6 +231,7 @@ func (enc *Encoder) Encode(v any) error {
|
||||
if _, err = enc.w.Write(b); err != nil {
|
||||
enc.err = err
|
||||
}
|
||||
encodeStatePool.Put(e)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -247,13 +243,6 @@ func (enc *Encoder) SetIndent(prefix, indent string) {
|
||||
enc.indentValue = indent
|
||||
}
|
||||
|
||||
// SetNilSafeCollection specifies whether to represent nil slices and maps as
|
||||
// '[]' or '{}' respectfully (flag on) instead of 'null' (default) when marshaling json.
|
||||
func (enc *Encoder) SetNilSafeCollection(nilSafeSlices bool, nilSafeMaps bool) {
|
||||
enc.nilSafeSlices = nilSafeSlices
|
||||
enc.nilSafeMaps = nilSafeMaps
|
||||
}
|
||||
|
||||
// SetEscapeHTML specifies whether problematic HTML characters
|
||||
// should be escaped inside JSON quoted strings.
|
||||
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e
|
||||
|
@@ -12,7 +12,6 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -42,7 +41,7 @@ false
|
||||
|
||||
func TestEncoder(t *testing.T) {
|
||||
for i := 0; i <= len(streamTest); i++ {
|
||||
var buf strings.Builder
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
// Check that enc.SetIndent("", "") turns off indentation.
|
||||
enc.SetIndent(">", ".")
|
||||
@@ -60,43 +59,6 @@ func TestEncoder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoderErrorAndReuseEncodeState(t *testing.T) {
|
||||
// Disable the GC temporarily to prevent encodeState's in Pool being cleaned away during the test.
|
||||
percent := debug.SetGCPercent(-1)
|
||||
defer debug.SetGCPercent(percent)
|
||||
|
||||
// Trigger an error in Marshal with cyclic data.
|
||||
type Dummy struct {
|
||||
Name string
|
||||
Next *Dummy
|
||||
}
|
||||
dummy := Dummy{Name: "Dummy"}
|
||||
dummy.Next = &dummy
|
||||
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
if err := enc.Encode(dummy); err == nil {
|
||||
t.Errorf("Encode(dummy) == nil; want error")
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
A string
|
||||
I int
|
||||
}
|
||||
data := Data{A: "a", I: 1}
|
||||
if err := enc.Encode(data); err != nil {
|
||||
t.Errorf("Marshal(%v) = %v", data, err)
|
||||
}
|
||||
|
||||
var data2 Data
|
||||
if err := Unmarshal(buf.Bytes(), &data2); err != nil {
|
||||
t.Errorf("Unmarshal(%v) = %v", data2, err)
|
||||
}
|
||||
if data2 != data {
|
||||
t.Errorf("expect: %v, but get: %v", data, data2)
|
||||
}
|
||||
}
|
||||
|
||||
var streamEncodedIndent = `0.1
|
||||
"hello"
|
||||
null
|
||||
@@ -115,7 +77,7 @@ false
|
||||
`
|
||||
|
||||
func TestEncoderIndent(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
enc.SetIndent(">", ".")
|
||||
for _, v := range streamTest {
|
||||
@@ -185,7 +147,7 @@ func TestEncoderSetEscapeHTML(t *testing.T) {
|
||||
`{"bar":"\"<html>foobar</html>\""}`,
|
||||
},
|
||||
} {
|
||||
var buf strings.Builder
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
if err := enc.Encode(tt.v); err != nil {
|
||||
t.Errorf("Encode(%s): %s", tt.name, err)
|
||||
@@ -347,6 +309,21 @@ func TestBlocking(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncoderEncode(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
type T struct {
|
||||
X, Y string
|
||||
}
|
||||
v := &T{"foo", "bar"}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := NewEncoder(io.Discard).Encode(v); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type tokenStreamCase struct {
|
||||
json string
|
||||
expTokens []any
|
||||
@@ -495,45 +472,3 @@ func TestHTTPDecoding(t *testing.T) {
|
||||
t.Errorf("err = %v; want io.EOF", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoderSetNilSafeCollection(t *testing.T) {
|
||||
var (
|
||||
nilSlice []interface{}
|
||||
pNilSlice *[]interface{}
|
||||
nilMap map[string]interface{}
|
||||
pNilMap *map[string]interface{}
|
||||
)
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
v interface{}
|
||||
want string
|
||||
rescuedWant string
|
||||
}{
|
||||
{"nilSlice", nilSlice, "null", "[]"},
|
||||
{"nonNilSlice", []interface{}{}, "[]", "[]"},
|
||||
{"sliceWithValues", []interface{}{1, 2, 3}, "[1,2,3]", "[1,2,3]"},
|
||||
{"pNilSlice", pNilSlice, "null", "null"},
|
||||
{"nilMap", nilMap, "null", "{}"},
|
||||
{"nonNilMap", map[string]interface{}{}, "{}", "{}"},
|
||||
{"mapWithValues", map[string]interface{}{"1": 1, "2": 2, "3": 3}, "{\"1\":1,\"2\":2,\"3\":3}", "{\"1\":1,\"2\":2,\"3\":3}"},
|
||||
{"pNilMap", pNilMap, "null", "null"},
|
||||
} {
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
if err := enc.Encode(tt.v); err != nil {
|
||||
t.Fatalf("Encode(%s): %s", tt.name, err)
|
||||
}
|
||||
if got := strings.TrimSpace(buf.String()); got != tt.want {
|
||||
t.Errorf("Encode(%s) = %#q, want %#q", tt.name, got, tt.want)
|
||||
}
|
||||
buf.Reset()
|
||||
enc.SetNilSafeCollection(true, true)
|
||||
if err := enc.Encode(tt.v); err != nil {
|
||||
t.Fatalf("SetNilSafeCollection(true) Encode(%s): %s", tt.name, err)
|
||||
}
|
||||
if got := strings.TrimSpace(buf.String()); got != tt.rescuedWant {
|
||||
t.Errorf("SetNilSafeCollection(true) Encode(%s) = %#q, want %#q",
|
||||
tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,10 @@
|
||||
package langext
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
type PanicWrappedErr struct {
|
||||
panic any
|
||||
Stack string
|
||||
}
|
||||
|
||||
func (p PanicWrappedErr) Error() string {
|
||||
@@ -15,7 +18,7 @@ func (p PanicWrappedErr) ReoveredObj() any {
|
||||
func RunPanicSafe(fn func()) (err error) {
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
err = PanicWrappedErr{panic: rec}
|
||||
err = PanicWrappedErr{panic: rec, Stack: string(debug.Stack())}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -27,7 +30,7 @@ func RunPanicSafe(fn func()) (err error) {
|
||||
func RunPanicSafeR1(fn func() error) (err error) {
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
err = PanicWrappedErr{panic: rec}
|
||||
err = PanicWrappedErr{panic: rec, Stack: string(debug.Stack())}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -38,7 +41,7 @@ func RunPanicSafeR2[T1 any](fn func() (T1, error)) (r1 T1, err error) {
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
r1 = *new(T1)
|
||||
err = PanicWrappedErr{panic: rec}
|
||||
err = PanicWrappedErr{panic: rec, Stack: string(debug.Stack())}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -50,7 +53,7 @@ func RunPanicSafeR3[T1 any, T2 any](fn func() (T1, T2, error)) (r1 T1, r2 T2, er
|
||||
if rec := recover(); rec != nil {
|
||||
r1 = *new(T1)
|
||||
r2 = *new(T2)
|
||||
err = PanicWrappedErr{panic: rec}
|
||||
err = PanicWrappedErr{panic: rec, Stack: string(debug.Stack())}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -63,7 +66,7 @@ func RunPanicSafeR4[T1 any, T2 any, T3 any](fn func() (T1, T2, T3, error)) (r1 T
|
||||
r1 = *new(T1)
|
||||
r2 = *new(T2)
|
||||
r3 = *new(T3)
|
||||
err = PanicWrappedErr{panic: rec}
|
||||
err = PanicWrappedErr{panic: rec, Stack: string(debug.Stack())}
|
||||
}
|
||||
}()
|
||||
|
||||
|
@@ -27,10 +27,12 @@ func (a *AtomicBool) Get() bool {
|
||||
return a.v
|
||||
}
|
||||
|
||||
func (a *AtomicBool) Set(value bool) {
|
||||
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 {
|
||||
@@ -42,6 +44,8 @@ func (a *AtomicBool) Set(value bool) {
|
||||
delete(a.listener, k)
|
||||
}
|
||||
}
|
||||
|
||||
return oldValue
|
||||
}
|
||||
|
||||
func (a *AtomicBool) Wait(waitFor bool) {
|
||||
|
28
timeext/calendarweek.go
Normal file
28
timeext/calendarweek.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package timeext
|
||||
|
||||
import "time"
|
||||
|
||||
func WeekStart(year, week int) time.Time {
|
||||
|
||||
// https://stackoverflow.com/a/52303730/1761622
|
||||
|
||||
// Start from the middle of the year:
|
||||
t := time.Date(year, 7, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// Roll back to Monday:
|
||||
if wd := t.Weekday(); wd == time.Sunday {
|
||||
t = t.AddDate(0, 0, -6)
|
||||
} else {
|
||||
t = t.AddDate(0, 0, -int(wd)+1)
|
||||
}
|
||||
|
||||
// Difference in weeks:
|
||||
_, w := t.ISOWeek()
|
||||
t = t.AddDate(0, 0, (week-w)*7)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func WeekEnd(year, week int) time.Time {
|
||||
return WeekStart(year, week).AddDate(0, 0, 7).Add(time.Duration(-1))
|
||||
}
|
25
timeext/calendarweek_test.go
Normal file
25
timeext/calendarweek_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package timeext
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/tst"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWeekStart(t *testing.T) {
|
||||
|
||||
tst.AssertEqual(t, WeekStart(2018, 1).Format(time.RFC3339Nano), "2018-01-01T00:00:00Z")
|
||||
tst.AssertEqual(t, WeekStart(2018, 2).Format(time.RFC3339Nano), "2018-01-08T00:00:00Z")
|
||||
tst.AssertEqual(t, WeekStart(2019, 1).Format(time.RFC3339Nano), "2018-12-31T00:00:00Z")
|
||||
tst.AssertEqual(t, WeekStart(2019, 2).Format(time.RFC3339Nano), "2019-01-07T00:00:00Z")
|
||||
|
||||
}
|
||||
|
||||
func TestWeekEnd(t *testing.T) {
|
||||
|
||||
tst.AssertEqual(t, WeekEnd(2018, 1).Format(time.RFC3339Nano), "2018-01-07T23:59:59.999999999Z")
|
||||
tst.AssertEqual(t, WeekEnd(2018, 2).Format(time.RFC3339Nano), "2018-01-14T23:59:59.999999999Z")
|
||||
tst.AssertEqual(t, WeekEnd(2019, 1).Format(time.RFC3339Nano), "2019-01-06T23:59:59.999999999Z")
|
||||
tst.AssertEqual(t, WeekEnd(2019, 2).Format(time.RFC3339Nano), "2019-01-13T23:59:59.999999999Z")
|
||||
|
||||
}
|
@@ -2,23 +2,59 @@ package tst
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func AssertEqual[T comparable](t *testing.T, actual T, expected T) {
|
||||
t.Helper()
|
||||
if actual != expected {
|
||||
t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertNotEqual[T comparable](t *testing.T, actual T, expected T) {
|
||||
t.Helper()
|
||||
if actual == expected {
|
||||
t.Errorf("values do not differ: Actual: '%v', Expected: '%v'", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertDeepEqual[T any](t *testing.T, actual T, expected T) {
|
||||
t.Helper()
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertSetDeepEqual[T any](t *testing.T, actual []T, expected []T) {
|
||||
t.Helper()
|
||||
if len(actual) != len(expected) {
|
||||
t.Errorf("values differ in length: Actual (n=%d): '%v', Expected (n=%d): '%v'", len(actual), actual, len(expected), expected)
|
||||
}
|
||||
|
||||
for _, a := range expected {
|
||||
found := false
|
||||
for _, b := range actual {
|
||||
found = found || reflect.DeepEqual(a, b)
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("values differ: Element '%v' not found. Actual: '%v', Expected: '%v'", a, actual, expected)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func AssertNotDeepEqual[T any](t *testing.T, actual T, expected T) {
|
||||
t.Helper()
|
||||
if reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("values do not differ: Actual: '%v', Expected: '%v'", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertDeRefEqual[T comparable](t *testing.T, actual *T, expected T) {
|
||||
t.Helper()
|
||||
if actual == nil {
|
||||
t.Errorf("values differ: Actual: NIL, Expected: '%v'", expected)
|
||||
}
|
||||
@@ -28,6 +64,7 @@ func AssertDeRefEqual[T comparable](t *testing.T, actual *T, expected T) {
|
||||
}
|
||||
|
||||
func AssertPtrEqual[T comparable](t *testing.T, actual *T, expected *T) {
|
||||
t.Helper()
|
||||
if actual == nil && expected == nil {
|
||||
return
|
||||
}
|
||||
@@ -47,6 +84,7 @@ func AssertPtrEqual[T comparable](t *testing.T, actual *T, expected *T) {
|
||||
}
|
||||
|
||||
func AssertHexEqual(t *testing.T, expected string, actual []byte) {
|
||||
t.Helper()
|
||||
actualStr := hex.EncodeToString(actual)
|
||||
if actualStr != expected {
|
||||
t.Errorf("values differ: Actual: '%v', Expected: '%v'", actualStr, expected)
|
||||
@@ -54,18 +92,21 @@ func AssertHexEqual(t *testing.T, expected string, actual []byte) {
|
||||
}
|
||||
|
||||
func AssertTrue(t *testing.T, value bool) {
|
||||
t.Helper()
|
||||
if !value {
|
||||
t.Error("value should be true\n" + string(debug.Stack()))
|
||||
}
|
||||
}
|
||||
|
||||
func AssertFalse(t *testing.T, value bool) {
|
||||
t.Helper()
|
||||
if value {
|
||||
t.Error("value should be false\n" + string(debug.Stack()))
|
||||
}
|
||||
}
|
||||
|
||||
func AssertNoErr(t *testing.T, anerr error) {
|
||||
t.Helper()
|
||||
if anerr != nil {
|
||||
t.Error("Function returned an error: " + anerr.Error() + "\n" + string(debug.Stack()))
|
||||
}
|
||||
|
@@ -73,7 +73,7 @@ func (c *Coll[TData]) ReplaceOne(ctx context.Context, filterQuery bson.M, value
|
||||
}
|
||||
|
||||
func (c *Coll[TData]) FindOneAndReplace(ctx context.Context, filterQuery bson.M, value TData) (TData, error) {
|
||||
mongoRes := c.coll.FindOneAndUpdate(ctx, filterQuery, bson.M{"$set": value}, options.FindOneAndUpdate().SetReturnDocument(options.After))
|
||||
mongoRes := c.coll.FindOneAndReplace(ctx, filterQuery, value, options.FindOneAndReplace().SetReturnDocument(options.After))
|
||||
if err := mongoRes.Err(); err != nil {
|
||||
return *new(TData), exerr.Wrap(err, "mongo-query[find-one-and-update] failed").
|
||||
Str("collection", c.Name()).
|
||||
|
Reference in New Issue
Block a user