Compare commits

...

10 Commits

Author SHA1 Message Date
e7b2b040b2 v0.0.116 2023-05-05 18:22:15 +02:00
05d0f9e469 v0.0.115 2023-05-05 18:18:20 +02:00
ccd03e50c8 v0.0.114 2023-05-05 18:17:15 +02:00
1c77c2b8e8 v0.0.113 2023-05-05 18:05:58 +02:00
9f6f967299 v0.0.112 2023-05-05 18:00:25 +02:00
18c83f0f76 v0.0.111 2023-05-05 17:57:21 +02:00
a64f336e24 v0.0.110 2023-05-05 17:47:30 +02:00
14bbd205f8 v0.0.109 2023-05-05 15:04:08 +02:00
cecfb0d788 v0.0.108 2023-05-05 14:43:40 +02:00
a445e6f623 v0.0.107 2023-04-26 11:35:28 +02:00
9 changed files with 243 additions and 8 deletions

View File

@@ -167,15 +167,30 @@ func Marshal(v any) ([]byte, error) {
return buf, nil
}
type IndentOpt struct {
Prefix string
Indent string
}
// 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) ([]byte, error) {
func MarshalSafeCollections(v interface{}, nilSafeSlices bool, nilSafeMaps bool, indent *IndentOpt) ([]byte, error) {
e := &encodeState{}
err := e.marshal(v, encOpts{escapeHTML: true, nilSafeSlices: nilSafeSlices, nilSafeMaps: nilSafeMaps})
if err != nil {
return nil, err
}
b := e.Bytes()
if indent != nil {
var buf bytes.Buffer
err = Indent(&buf, b, indent.Prefix, indent.Indent)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
} else {
return e.Bytes(), nil
}
}
// MarshalIndent is like Marshal but applies Indent to format the output.

View File

@@ -1274,7 +1274,7 @@ func TestMarshalSafeCollections(t *testing.T) {
}
for i, tt := range tests {
b, err := MarshalSafeCollections(tt.in, true, true)
b, err := MarshalSafeCollections(tt.in, true, true, nil)
if err != nil {
t.Errorf("test %d, unexpected failure: %v", i, err)
}

44
gojson/gionic.go Normal file
View File

@@ -0,0 +1,44 @@
package json
import (
"net/http"
)
// Render interface is copied from github.com/gin-gonic/gin@v1.8.1/render/render.go
type Render interface {
// Render writes data with custom ContentType.
Render(http.ResponseWriter) error
// WriteContentType writes custom ContentType.
WriteContentType(w http.ResponseWriter)
}
type GoJsonRender struct {
Data any
NilSafeSlices bool
NilSafeMaps bool
Indent *IndentOpt
}
func (r GoJsonRender) Render(w http.ResponseWriter) error {
header := w.Header()
if val := header["Content-Type"]; len(val) == 0 {
header["Content-Type"] = []string{"application/json; charset=utf-8"}
}
jsonBytes, err := MarshalSafeCollections(r.Data, r.NilSafeSlices, r.NilSafeMaps, r.Indent)
if err != nil {
panic(err)
}
_, err = w.Write(jsonBytes)
if err != nil {
panic(err)
}
return nil
}
func (r GoJsonRender) WriteContentType(w http.ResponseWriter) {
header := w.Header()
if val := header["Content-Type"]; len(val) == 0 {
header["Content-Type"] = []string{"application/json; charset=utf-8"}
}
}

View File

@@ -12,7 +12,7 @@ func (p PanicWrappedErr) ReoveredObj() any {
return p.panic
}
func runPanicSafe(fn func()) (err error) {
func RunPanicSafe(fn func()) (err error) {
defer func() {
if rec := recover(); rec != nil {
err = PanicWrappedErr{panic: rec}
@@ -24,7 +24,7 @@ func runPanicSafe(fn func()) (err error) {
return nil
}
func runPanicSafeR1(fn func() error) (err error) {
func RunPanicSafeR1(fn func() error) (err error) {
defer func() {
if rec := recover(); rec != nil {
err = PanicWrappedErr{panic: rec}
@@ -34,7 +34,7 @@ func runPanicSafeR1(fn func() error) (err error) {
return fn()
}
func runPanicSafeR2[T1 any](fn func() (T1, error)) (r1 T1, err error) {
func RunPanicSafeR2[T1 any](fn func() (T1, error)) (r1 T1, err error) {
defer func() {
if rec := recover(); rec != nil {
r1 = *new(T1)
@@ -45,7 +45,7 @@ func runPanicSafeR2[T1 any](fn func() (T1, error)) (r1 T1, err error) {
return fn()
}
func runPanicSafeR3[T1 any, T2 any](fn func() (T1, T2, error)) (r1 T1, r2 T2, err error) {
func RunPanicSafeR3[T1 any, T2 any](fn func() (T1, T2, error)) (r1 T1, r2 T2, err error) {
defer func() {
if rec := recover(); rec != nil {
r1 = *new(T1)
@@ -57,7 +57,7 @@ func runPanicSafeR3[T1 any, T2 any](fn func() (T1, T2, error)) (r1 T1, r2 T2, er
return fn()
}
func runPanicSafeR4[T1 any, T2 any, T3 any](fn func() (T1, T2, T3, error)) (r1 T1, r2 T2, r3 T3, err error) {
func RunPanicSafeR4[T1 any, T2 any, T3 any](fn func() (T1, T2, T3, error)) (r1 T1, r2 T2, r3 T3, err error) {
defer func() {
if rec := recover(); rec != nil {
r1 = *new(T1)

49
mongoext/pipeline.go Normal file
View File

@@ -0,0 +1,49 @@
package mongoext
import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
// FixTextSearchPipeline moves {$match:{$text:{$search}}} entries to the front of the pipeline (otherwise its an mongo error)
func FixTextSearchPipeline(pipeline mongo.Pipeline) mongo.Pipeline {
dget := func(v bson.D, k string) (bson.M, bool) {
for _, e := range v {
if e.Key == k {
if mv, ok := e.Value.(bson.M); ok {
return mv, true
}
}
}
return nil, false
}
mget := func(v bson.M, k string) (bson.M, bool) {
for ekey, eval := range v {
if ekey == k {
if mv, ok := eval.(bson.M); ok {
return mv, true
}
}
}
return nil, false
}
result := make([]bson.D, 0, len(pipeline))
for _, entry := range pipeline {
if v0, ok := dget(entry, "$match"); ok {
if v1, ok := mget(v0, "$text"); ok {
if _, ok := v1["$search"]; ok {
result = append([]bson.D{entry}, result...)
continue
}
}
}
result = append(result, entry)
}
return result
}

30
mongoext/projections.go Normal file
View File

@@ -0,0 +1,30 @@
package mongoext
import (
"go.mongodb.org/mongo-driver/bson"
"reflect"
"strings"
)
// ProjectionFromStruct automatically generated a mongodb projection for a struct
// This way you can pretty much always write
// `options.FindOne().SetProjection(mongoutils.ProjectionFromStruct(...your_model...))`
// to only get the data from mongodb that you will actually use in the later decode step
func ProjectionFromStruct(obj interface{}) bson.M {
v := reflect.ValueOf(obj)
t := v.Type()
result := bson.M{}
for i := 0; i < v.NumField(); i++ {
tag := t.Field(i).Tag.Get("bson")
if tag == "" {
continue
}
tag = strings.Split(tag, ",")[0]
result[tag] = 1
}
return result
}

25
mongoext/registry.go Normal file
View File

@@ -0,0 +1,25 @@
package mongoext
import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"gogs.mikescher.com/BlackForestBytes/goext/rfctime"
"reflect"
)
func CreateGoExtBsonRegistry() *bsoncodec.Registry {
rb := bsoncodec.NewRegistryBuilder()
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.RFC3339Time{}), rfctime.RFC3339Time{})
rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.RFC3339Time{}), rfctime.RFC3339Time{})
rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.RFC3339NanoTime{}), rfctime.RFC3339NanoTime{})
rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.RFC3339NanoTime{}), rfctime.RFC3339NanoTime{})
bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb)
bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb)
bson.PrimitiveCodecs{}.RegisterPrimitiveCodecs(rb)
return rb.Build()
}

View File

@@ -5,7 +5,10 @@ import (
"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"
"reflect"
"time"
)
@@ -67,6 +70,10 @@ func (t *RFC3339Time) UnmarshalText(data []byte) error {
func (t *RFC3339Time) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
if bt == bsontype.Null {
// 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
*t = RFC3339Time{}
return nil
}
@@ -86,6 +93,32 @@ func (t RFC3339Time) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.MarshalValue(time.Time(t))
}
func (t RFC3339Time) 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 = t.UnmarshalBSONValue(tp, src)
if err != nil {
return err
}
return nil
}
func (t RFC3339Time) Serialize() string {
return t.Time().Format(t.FormatStr())
}

View File

@@ -5,7 +5,10 @@ import (
"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"
"reflect"
"time"
)
@@ -67,6 +70,10 @@ func (t *RFC3339NanoTime) UnmarshalText(data []byte) error {
func (t *RFC3339NanoTime) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
if bt == bsontype.Null {
// 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
*t = RFC3339NanoTime{}
return nil
}
@@ -86,6 +93,38 @@ func (t RFC3339NanoTime) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.MarshalValue(time.Time(t))
}
func (t RFC3339NanoTime) 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 = t.UnmarshalBSONValue(tp, src)
if err != nil {
return err
}
if val.Kind() == reflect.Ptr {
val.Set(reflect.ValueOf(&t))
} else {
val.Set(reflect.ValueOf(t))
}
return nil
}
func (t RFC3339NanoTime) Serialize() string {
return t.Time().Format(t.FormatStr())
}