This commit is contained in:
Mike Schwörer 2023-06-10 18:35:56 +02:00
parent a30da61419
commit ceff0161c6
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
5 changed files with 91 additions and 16 deletions

View File

@ -1,5 +1,5 @@
package goext package goext
const GoextVersion = "0.0.158" const GoextVersion = "0.0.159"
const GoextVersionTimestamp = "2023-06-10T16:28:50+0200" const GoextVersionTimestamp = "2023-06-10T18:35:56+0200"

View File

@ -27,7 +27,7 @@ type Cursorable interface {
Next(ctx context.Context) bool Next(ctx context.Context) bool
} }
type fullTypeRef[TData any] struct { type fullTypeRef struct {
IsPointer bool IsPointer bool
Kind reflect.Kind Kind reflect.Kind
RealType reflect.Type RealType reflect.Type
@ -38,9 +38,11 @@ type fullTypeRef[TData any] struct {
} }
type Coll[TData any] struct { type Coll[TData any] struct {
coll *mongo.Collection coll *mongo.Collection // internal mongo collection, access via Collection()
dataTypeMap map[string]fullTypeRef[TData] dataTypeMap map[string]fullTypeRef // list of TData fields (only if TData is not an interface)
customDecoder *func(ctx context.Context, dec Decodable) (TData, error) implDataTypeMap map[reflect.Type]map[string]fullTypeRef // dynamic list of fields of TData implementations (only if TData is an interface)
customDecoder *func(ctx context.Context, dec Decodable) (TData, error) // custom decoding function (useful if TData is an interface)
isInterfaceDataType bool // true if TData is an interface (not a struct)
} }
func (c *Coll[TData]) Collection() *mongo.Collection { func (c *Coll[TData]) Collection() *mongo.Collection {
@ -51,7 +53,10 @@ func (c *Coll[TData]) Name() string {
return c.coll.Name() return c.coll.Name()
} }
func (c *Coll[TData]) WithDecodeFunc(cdf func(ctx context.Context, dec Decodable) (TData, error)) *Coll[TData] { func (c *Coll[TData]) WithDecodeFunc(cdf func(ctx context.Context, dec Decodable) (TData, error), example TData) *Coll[TData] {
c.EnsureInitializedReflection(example)
c.customDecoder = langext.Ptr(cdf) c.customDecoder = langext.Ptr(cdf)
return c return c
} }

View File

@ -62,6 +62,8 @@ func (c *Coll[TData]) List(ctx context.Context, filter ct.Filter, pageSize *int,
last := entities[len(entities)-1] last := entities[len(entities)-1]
c.EnsureInitializedReflection(last)
nextToken, err := c.createToken(sortPrimary, sortDirPrimary, sortSecondary, sortDirSecondary, last, pageSize) nextToken, err := c.createToken(sortPrimary, sortDirPrimary, sortSecondary, sortDirSecondary, last, pageSize)
if err != nil { if err != nil {
return nil, ct.CursorToken{}, err return nil, ct.CursorToken{}, err

View File

@ -7,19 +7,56 @@ import (
"strings" "strings"
) )
func (c *Coll[TData]) init() { func (c *Coll[TData]) EnsureInitializedReflection(v TData) {
c.dataTypeMap = make(map[string]fullTypeRef[TData]) if !c.isInterfaceDataType {
return // only dynamically load dataTypeMap on interface TData
}
rval := reflect.ValueOf(v)
for rval.Type().Kind() == reflect.Pointer {
rval = rval.Elem()
}
if _, ok := c.implDataTypeMap[rval.Type()]; ok {
return // already loaded
}
m := make(map[string]fullTypeRef)
c.initFields("", rval, m, make([]int, 0))
c.implDataTypeMap[rval.Type()] = m
}
func (c *Coll[TData]) init() {
example := *new(TData) example := *new(TData)
v := reflect.ValueOf(example) datatype := reflect.TypeOf(&example).Elem()
c.initFields("", v, make([]int, 0)) if datatype.Kind() == reflect.Interface {
c.isInterfaceDataType = true
c.dataTypeMap = make(map[string]fullTypeRef)
c.implDataTypeMap = make(map[reflect.Type]map[string]fullTypeRef)
} else {
c.isInterfaceDataType = false
c.dataTypeMap = make(map[string]fullTypeRef)
c.implDataTypeMap = make(map[reflect.Type]map[string]fullTypeRef)
v := reflect.ValueOf(example)
c.initFields("", v, c.dataTypeMap, make([]int, 0))
} }
func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int) { }
func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, m map[string]fullTypeRef, idxarr []int) {
rtyp := rval.Type() rtyp := rval.Type()
@ -50,7 +87,7 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int
if rvfield.Type().Kind() == reflect.Pointer { if rvfield.Type().Kind() == reflect.Pointer {
c.dataTypeMap[fullKey] = fullTypeRef[TData]{ m[fullKey] = fullTypeRef{
IsPointer: true, IsPointer: true,
RealType: rvfield.Type(), RealType: rvfield.Type(),
Kind: rvfield.Type().Elem().Kind(), Kind: rvfield.Type().Elem().Kind(),
@ -62,7 +99,7 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int
} else { } else {
c.dataTypeMap[fullKey] = fullTypeRef[TData]{ m[fullKey] = fullTypeRef{
IsPointer: false, IsPointer: false,
RealType: rvfield.Type(), RealType: rvfield.Type(),
Kind: rvfield.Type().Kind(), Kind: rvfield.Type().Kind(),
@ -75,7 +112,7 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int
} }
if rvfield.Kind() == reflect.Struct { if rvfield.Kind() == reflect.Struct {
c.initFields(fullKey+".", rvfield, newIdxArr) c.initFields(fullKey+".", rvfield, m, newIdxArr)
} }
} }
@ -102,7 +139,7 @@ func (c *Coll[TData]) getFieldValueAsTokenString(entity TData, fieldName string)
} }
func (c *Coll[TData]) getFieldType(fieldName string) fullTypeRef[TData] { func (c *Coll[TData]) getFieldType(fieldName string) fullTypeRef {
return c.dataTypeMap[fieldName] return c.dataTypeMap[fieldName]
} }

View File

@ -1,11 +1,14 @@
package wmo package wmo
import ( import (
"context"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
"gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/rfctime" "gogs.mikescher.com/BlackForestBytes/goext/rfctime"
"gogs.mikescher.com/BlackForestBytes/goext/timeext" "gogs.mikescher.com/BlackForestBytes/goext/timeext"
"gogs.mikescher.com/BlackForestBytes/goext/tst" "gogs.mikescher.com/BlackForestBytes/goext/tst"
"reflect"
"testing" "testing"
"time" "time"
) )
@ -177,3 +180,31 @@ func TestReflectionGetFieldValueAsTokenString(t *testing.T) {
tst.AssertEqual(t, gfvats(d, "cdate"), t0.Format(time.RFC3339Nano)) tst.AssertEqual(t, gfvats(d, "cdate"), t0.Format(time.RFC3339Nano))
tst.AssertEqual(t, gfvats(d, "mdate"), t0.Format(time.RFC3339Nano)) tst.AssertEqual(t, gfvats(d, "mdate"), t0.Format(time.RFC3339Nano))
} }
func TestReflectionWithInterface(t *testing.T) {
type TestData struct {
ID primitive.ObjectID `bson:"_id"`
CDate time.Time `bson:"cdate"`
}
type TestInterface interface {
}
coll1 := W[TestInterface](&mongo.Collection{})
tst.AssertTrue(t, coll1.coll != nil)
tst.AssertEqual(t, 0, len(coll1.implDataTypeMap))
df := func(ctx context.Context, dec Decodable) (TestInterface, error) {
return TestData{}, nil
}
coll2 := W[TestInterface](&mongo.Collection{}).WithDecodeFunc(df, TestData{})
tst.AssertTrue(t, coll2.coll != nil)
tst.AssertEqual(t, 1, len(coll2.implDataTypeMap))
tst.AssertEqual(t, "ID", coll2.implDataTypeMap[reflect.TypeOf(TestData{})]["_id"].Name)
tst.AssertEqual(t, "CDate", coll2.implDataTypeMap[reflect.TypeOf(TestData{})]["cdate"].Name)
}