From ceff0161c674c34650e22df7c1fde21672c72ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Sat, 10 Jun 2023 18:35:56 +0200 Subject: [PATCH] v0.0.159 --- goextVersion.go | 4 +-- wmo/collection.go | 15 ++++++++---- wmo/queryList.go | 2 ++ wmo/reflection.go | 55 +++++++++++++++++++++++++++++++++++------- wmo/reflection_test.go | 31 ++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 16 deletions(-) diff --git a/goextVersion.go b/goextVersion.go index 0fdb054..8693c5e 100644 --- a/goextVersion.go +++ b/goextVersion.go @@ -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" diff --git a/wmo/collection.go b/wmo/collection.go index 31a8a82..4a59f42 100644 --- a/wmo/collection.go +++ b/wmo/collection.go @@ -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 } diff --git a/wmo/queryList.go b/wmo/queryList.go index 28c7888..10358ce 100644 --- a/wmo/queryList.go +++ b/wmo/queryList.go @@ -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 diff --git a/wmo/reflection.go b/wmo/reflection.go index 47dc17f..068a801 100644 --- a/wmo/reflection.go +++ b/wmo/reflection.go @@ -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] } diff --git a/wmo/reflection_test.go b/wmo/reflection_test.go index 6201510..3639632 100644 --- a/wmo/reflection_test.go +++ b/wmo/reflection_test.go @@ -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) +}