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
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
}
type fullTypeRef[TData any] struct {
type fullTypeRef struct {
IsPointer bool
Kind reflect.Kind
RealType reflect.Type
@ -38,9 +38,11 @@ type fullTypeRef[TData any] struct {
}
type Coll[TData any] struct {
coll *mongo.Collection
dataTypeMap map[string]fullTypeRef[TData]
customDecoder *func(ctx context.Context, dec Decodable) (TData, error)
coll *mongo.Collection // internal mongo collection, access via Collection()
dataTypeMap map[string]fullTypeRef // list of TData fields (only if TData is not an interface)
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 {
@ -51,7 +53,10 @@ func (c *Coll[TData]) Name() string {
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)
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]
c.EnsureInitializedReflection(last)
nextToken, err := c.createToken(sortPrimary, sortDirPrimary, sortSecondary, sortDirSecondary, last, pageSize)
if err != nil {
return nil, ct.CursorToken{}, err

View File

@ -7,19 +7,56 @@ import (
"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)
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()
@ -50,7 +87,7 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int
if rvfield.Type().Kind() == reflect.Pointer {
c.dataTypeMap[fullKey] = fullTypeRef[TData]{
m[fullKey] = fullTypeRef{
IsPointer: true,
RealType: rvfield.Type(),
Kind: rvfield.Type().Elem().Kind(),
@ -62,7 +99,7 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int
} else {
c.dataTypeMap[fullKey] = fullTypeRef[TData]{
m[fullKey] = fullTypeRef{
IsPointer: false,
RealType: rvfield.Type(),
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 {
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]
}

View File

@ -1,11 +1,14 @@
package wmo
import (
"context"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/rfctime"
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
"gogs.mikescher.com/BlackForestBytes/goext/tst"
"reflect"
"testing"
"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, "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)
}