Compare commits

...

8 Commits

Author SHA1 Message Date
d56a0235af v0.0.265 add ListWithCount
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 49s
2023-09-18 12:57:27 +02:00
de2ca763c1 add function for ListWithCount 2023-09-18 12:56:56 +02:00
da52bb5c90 v0.0.264 added Valid() to id-gen
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 49s
2023-09-18 11:46:17 +02:00
3d4afe7b25 v0.0.263 re-add checksum guard to id-generate
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 50s
2023-09-18 10:43:29 +02:00
f5766d639c v0.0.262 ignore _gen files in bfcodegen checksum-calc
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 46s
2023-09-18 10:42:43 +02:00
cdf2a6e76b v0.0.261 added id-generate.go
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m7s
2023-09-18 10:38:25 +02:00
6d7cfb86f8 v0.0.260 wmo: fix endless recursion in wmo reflection
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 50s
2023-09-12 11:40:39 +02:00
1e9d663ffe fix endless recursion in wmo reflection 2023-09-12 11:39:51 +02:00
10 changed files with 303 additions and 32 deletions

12
TODO.md
View File

@@ -2,12 +2,6 @@
- cronext - cronext
- cursortoken - rfctime.DateOnly
- rfctime.HMSTimeOnly
- typed/geenric mongo wrapper - rfctime.NanoTimeOnly
- error package
- rfctime.DateOnly
- rfctime.HMSTimeOnly
- rfctime.NanoTimeOnly

View File

@@ -31,13 +31,13 @@ type EnumDef struct {
Values []EnumDefVal Values []EnumDefVal
} }
var rexPackage = rext.W(regexp.MustCompile(`^package\s+(?P<name>[A-Za-z0-9_]+)\s*$`)) var rexEnumPackage = rext.W(regexp.MustCompile(`^package\s+(?P<name>[A-Za-z0-9_]+)\s*$`))
var rexEnumDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*//\s*(@enum:type).*$`)) var rexEnumDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*//\s*(@enum:type).*$`))
var rexValueDef = rext.W(regexp.MustCompile(`^\s*(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*=\s*(?P<value>("[A-Za-z0-9_:]+"|[0-9]+))\s*(//(?P<descr>.*))?.*$`)) var rexEnumValueDef = rext.W(regexp.MustCompile(`^\s*(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*=\s*(?P<value>("[A-Za-z0-9_:]+"|[0-9]+))\s*(//(?P<descr>.*))?.*$`))
var rexChecksumConst = rext.W(regexp.MustCompile(`const ChecksumGenerator = "(?P<cs>[A-Za-z0-9_]*)"`)) var rexEnumChecksumConst = rext.W(regexp.MustCompile(`const ChecksumEnumGenerator = "(?P<cs>[A-Za-z0-9_]*)"`))
func GenerateEnumSpecs(sourceDir string, destFile string) error { func GenerateEnumSpecs(sourceDir string, destFile string) error {
@@ -52,13 +52,14 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error {
if err != nil { if err != nil {
return err return err
} }
if m, ok := rexChecksumConst.MatchFirst(string(content)); ok { if m, ok := rexEnumChecksumConst.MatchFirst(string(content)); ok {
oldChecksum = m.GroupByName("cs").Value() oldChecksum = m.GroupByName("cs").Value()
} }
} }
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return v.Name() != path.Base(destFile) }) files = langext.ArrFilter(files, func(v os.DirEntry) bool { return v.Name() != path.Base(destFile) })
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return strings.HasSuffix(v.Name(), ".go") }) files = langext.ArrFilter(files, func(v os.DirEntry) bool { return strings.HasSuffix(v.Name(), ".go") })
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return !strings.HasSuffix(v.Name(), "_gen.go") })
langext.SortBy(files, func(v os.DirEntry) string { return v.Name() }) langext.SortBy(files, func(v os.DirEntry) string { return v.Name() })
newChecksumStr := goext.GoextVersion newChecksumStr := goext.GoextVersion
@@ -85,7 +86,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error {
for _, f := range files { for _, f := range files {
fmt.Printf("========= %s =========\n\n", f.Name()) fmt.Printf("========= %s =========\n\n", f.Name())
fileEnums, pn, err := processFile(sourceDir, path.Join(sourceDir, f.Name())) fileEnums, pn, err := processEnumFile(sourceDir, path.Join(sourceDir, f.Name()))
if err != nil { if err != nil {
return err return err
} }
@@ -103,7 +104,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error {
return errors.New("no package name found in any file") return errors.New("no package name found in any file")
} }
err = os.WriteFile(destFile, []byte(fmtOutput(newChecksum, allEnums, pkgname)), 0o755) err = os.WriteFile(destFile, []byte(fmtEnumOutput(newChecksum, allEnums, pkgname)), 0o755)
if err != nil { if err != nil {
return err return err
} }
@@ -125,7 +126,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error {
return nil return nil
} }
func processFile(basedir string, fn string) ([]EnumDef, string, error) { func processEnumFile(basedir string, fn string) ([]EnumDef, string, error) {
file, err := os.Open(fn) file, err := os.Open(fn)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
@@ -149,7 +150,7 @@ func processFile(basedir string, fn string) ([]EnumDef, string, error) {
break break
} }
if match, ok := rexPackage.MatchFirst(line); i == 0 && ok { if match, ok := rexEnumPackage.MatchFirst(line); i == 0 && ok {
pkgname = match.GroupByName("name").Value() pkgname = match.GroupByName("name").Value()
continue continue
} }
@@ -172,7 +173,7 @@ func processFile(basedir string, fn string) ([]EnumDef, string, error) {
fmt.Printf("Found enum definition { '%s' -> '%s' }\n", def.EnumTypeName, def.Type) fmt.Printf("Found enum definition { '%s' -> '%s' }\n", def.EnumTypeName, def.Type)
} }
if match, ok := rexValueDef.MatchFirst(line); ok { if match, ok := rexEnumValueDef.MatchFirst(line); ok {
typename := match.GroupByName("type").Value() typename := match.GroupByName("type").Value()
def := EnumDefVal{ def := EnumDefVal{
VarName: match.GroupByName("name").Value(), VarName: match.GroupByName("name").Value(),
@@ -202,7 +203,7 @@ func processFile(basedir string, fn string) ([]EnumDef, string, error) {
return enums, pkgname, nil return enums, pkgname, nil
} }
func fmtOutput(cs string, enums []EnumDef, pkgname string) string { func fmtEnumOutput(cs string, enums []EnumDef, pkgname string) string {
str := "// Code generated by enum-generate.go DO NOT EDIT.\n" str := "// Code generated by enum-generate.go DO NOT EDIT.\n"
str += "\n" str += "\n"
str += "package " + pkgname + "\n" str += "package " + pkgname + "\n"
@@ -212,7 +213,7 @@ func fmtOutput(cs string, enums []EnumDef, pkgname string) string {
str += "import \"gogs.mikescher.com/BlackForestBytes/goext/enums\"" + "\n" str += "import \"gogs.mikescher.com/BlackForestBytes/goext/enums\"" + "\n"
str += "\n" str += "\n"
str += "const ChecksumGenerator = \"" + cs + "\" // GoExtVersion: " + goext.GoextVersion + "\n" str += "const ChecksumEnumGenerator = \"" + cs + "\" // GoExtVersion: " + goext.GoextVersion + "\n"
str += "\n" str += "\n"
for _, enumdef := range enums { for _, enumdef := range enums {

236
bfcodegen/id-generate.go Normal file
View File

@@ -0,0 +1,236 @@
package bfcodegen
import (
"errors"
"fmt"
"gogs.mikescher.com/BlackForestBytes/goext"
"gogs.mikescher.com/BlackForestBytes/goext/cmdext"
"gogs.mikescher.com/BlackForestBytes/goext/cryptext"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/rext"
"io"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"time"
)
type IDDef struct {
File string
FileRelative string
Name string
}
var rexIDPackage = rext.W(regexp.MustCompile(`^package\s+(?P<name>[A-Za-z0-9_]+)\s*$`))
var rexIDDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P<name>[A-Za-z0-9_]+)\s+string\s*//\s*(@id:type).*$`))
var rexIDChecksumConst = rext.W(regexp.MustCompile(`const ChecksumIDGenerator = "(?P<cs>[A-Za-z0-9_]*)"`))
func GenerateIDSpecs(sourceDir string, destFile string) error {
files, err := os.ReadDir(sourceDir)
if err != nil {
return err
}
oldChecksum := "N/A"
if _, err := os.Stat(destFile); !os.IsNotExist(err) {
content, err := os.ReadFile(destFile)
if err != nil {
return err
}
if m, ok := rexIDChecksumConst.MatchFirst(string(content)); ok {
oldChecksum = m.GroupByName("cs").Value()
}
}
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return v.Name() != path.Base(destFile) })
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return strings.HasSuffix(v.Name(), ".go") })
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return !strings.HasSuffix(v.Name(), "_gen.go") })
langext.SortBy(files, func(v os.DirEntry) string { return v.Name() })
newChecksumStr := goext.GoextVersion
for _, f := range files {
content, err := os.ReadFile(path.Join(sourceDir, f.Name()))
if err != nil {
return err
}
newChecksumStr += "\n" + f.Name() + "\t" + cryptext.BytesSha256(content)
}
newChecksum := cryptext.BytesSha256([]byte(newChecksumStr))
if newChecksum != oldChecksum {
fmt.Printf("[IDGenerate] Checksum has changed ( %s -> %s ), will generate new file\n\n", oldChecksum, newChecksum)
} else {
fmt.Printf("[IDGenerate] Checksum unchanged ( %s ), nothing to do\n", oldChecksum)
return nil
}
allIDs := make([]IDDef, 0)
pkgname := ""
for _, f := range files {
fmt.Printf("========= %s =========\n\n", f.Name())
fileIDs, pn, err := processIDFile(sourceDir, path.Join(sourceDir, f.Name()))
if err != nil {
return err
}
fmt.Printf("\n")
allIDs = append(allIDs, fileIDs...)
if pn != "" {
pkgname = pn
}
}
if pkgname == "" {
return errors.New("no package name found in any file")
}
err = os.WriteFile(destFile, []byte(fmtIDOutput(newChecksum, allIDs, pkgname)), 0o755)
if err != nil {
return err
}
res, err := cmdext.RunCommand("go", []string{"fmt", destFile}, langext.Ptr(2*time.Second))
if err != nil {
return err
}
if res.CommandTimedOut {
fmt.Println(res.StdCombined)
return errors.New("go fmt timed out")
}
if res.ExitCode != 0 {
fmt.Println(res.StdCombined)
return errors.New("go fmt did not succeed")
}
return nil
}
func processIDFile(basedir string, fn string) ([]IDDef, string, error) {
file, err := os.Open(fn)
if err != nil {
return nil, "", err
}
defer func() { _ = file.Close() }()
bin, err := io.ReadAll(file)
if err != nil {
return nil, "", err
}
lines := strings.Split(string(bin), "\n")
ids := make([]IDDef, 0)
pkgname := ""
for i, line := range lines {
if i == 0 && strings.HasPrefix(line, "// Code generated by") {
break
}
if match, ok := rexIDPackage.MatchFirst(line); i == 0 && ok {
pkgname = match.GroupByName("name").Value()
continue
}
if match, ok := rexIDDef.MatchFirst(line); ok {
rfp, err := filepath.Rel(basedir, fn)
if err != nil {
return nil, "", err
}
def := IDDef{
File: fn,
FileRelative: rfp,
Name: match.GroupByName("name").Value(),
}
fmt.Printf("Found ID definition { '%s' }\n", def.Name)
ids = append(ids, def)
}
}
return ids, pkgname, nil
}
func fmtIDOutput(cs string, ids []IDDef, pkgname string) string {
str := "// Code generated by id-generate.go DO NOT EDIT.\n"
str += "\n"
str += "package " + pkgname + "\n"
str += "\n"
str += "import \"go.mongodb.org/mongo-driver/bson\"" + "\n"
str += "import \"go.mongodb.org/mongo-driver/bson/bsontype\"" + "\n"
str += "import \"go.mongodb.org/mongo-driver/bson/primitive\"" + "\n"
str += "import \"gogs.mikescher.com/BlackForestBytes/goext/exerr\"" + "\n"
str += "\n"
str += "const ChecksumIDGenerator = \"" + cs + "\" // GoExtVersion: " + goext.GoextVersion + "\n"
str += "\n"
anyDef := langext.ArrFirstOrNil(ids, func(def IDDef) bool { return def.Name == "AnyID" || def.Name == "AnyId" })
for _, iddef := range ids {
str += "// ================================ " + iddef.Name + " (" + iddef.FileRelative + ") ================================" + "\n"
str += "" + "\n"
str += "func (i " + iddef.Name + ") MarshalBSONValue() (bsontype.Type, []byte, error) {" + "\n"
str += " if objId, err := primitive.ObjectIDFromHex(string(i)); err == nil {" + "\n"
str += " return bson.MarshalValue(objId)" + "\n"
str += " } else {" + "\n"
str += " return 0, nil, exerr.New(exerr.TypeMarshalEntityID, \"Failed to marshal " + iddef.Name + "(\"+i.String()+\") to ObjectId\").Str(\"value\", string(i)).Type(\"type\", i).Build()" + "\n"
str += " }" + "\n"
str += "}" + "\n"
str += "" + "\n"
str += "func (i " + iddef.Name + ") String() string {" + "\n"
str += " return string(i)" + "\n"
str += "}" + "\n"
str += "" + "\n"
str += "func (i " + iddef.Name + ") ObjID() (primitive.ObjectID, error) {" + "\n"
str += " return primitive.ObjectIDFromHex(string(i))" + "\n"
str += "}" + "\n"
str += "" + "\n"
str += "func (i " + iddef.Name + ") Valid() bool {" + "\n"
str += " _, err := primitive.ObjectIDFromHex(string(i))" + "\n"
str += " return err == nil" + "\n"
str += "}" + "\n"
str += "" + "\n"
if anyDef != nil {
str += "func (i " + iddef.Name + ") AsAny() " + anyDef.Name + " {" + "\n"
str += " return " + anyDef.Name + "(i)" + "\n"
str += "}" + "\n"
str += "" + "\n"
}
str += "func New" + iddef.Name + "() " + iddef.Name + " {" + "\n"
str += " return " + iddef.Name + "(primitive.NewObjectID().Hex())" + "\n"
str += "}" + "\n"
str += "" + "\n"
}
return str
}

View File

@@ -55,6 +55,8 @@ var (
TypeBindFailFormData = NewType("BINDFAIL_FORMDATA", langext.Ptr(400)) TypeBindFailFormData = NewType("BINDFAIL_FORMDATA", langext.Ptr(400))
TypeBindFailHeader = NewType("BINDFAIL_HEADER", langext.Ptr(400)) TypeBindFailHeader = NewType("BINDFAIL_HEADER", langext.Ptr(400))
TypeMarshalEntityID = NewType("MARSHAL_ENTITY_ID", langext.Ptr(400))
TypeUnauthorized = NewType("UNAUTHORIZED", langext.Ptr(401)) TypeUnauthorized = NewType("UNAUTHORIZED", langext.Ptr(401))
TypeAuthFailed = NewType("AUTH_FAILED", langext.Ptr(401)) TypeAuthFailed = NewType("AUTH_FAILED", langext.Ptr(401))

4
go.mod
View File

@@ -21,11 +21,11 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.3 // indirect github.com/go-playground/validator/v10 v10.15.4 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/compress v1.17.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/leodido/go-urn v1.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect

4
go.sum
View File

@@ -36,6 +36,8 @@ github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2r
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo= github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo=
github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-playground/validator/v10 v10.15.4 h1:zMXza4EpOdooxPel5xDqXEdXG5r+WggpvnAKMsalBjs=
github.com/go-playground/validator/v10 v10.15.4/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@@ -56,6 +58,8 @@ github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQ
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=

View File

@@ -1,5 +1,5 @@
package goext package goext
const GoextVersion = "0.0.259" const GoextVersion = "0.0.265"
const GoextVersionTimestamp = "2023-09-12T10:48:57+0200" const GoextVersionTimestamp = "2023-09-18T12:57:27+0200"

View File

@@ -116,3 +116,17 @@ func (c *Coll[TData]) Count(ctx context.Context, filter ct.Filter) (int64, error
return 0, nil return 0, nil
} }
func (c *Coll[TData]) ListWithCount(ctx context.Context, filter ct.Filter, pageSize *int, inTok ct.CursorToken) ([]TData, ct.CursorToken, int64, error) {
// NOTE: Possible optimization: Cache count in CursorToken, then fetch count only on first page.
count, err := c.Count(ctx, filter)
if err != nil {
return nil, ct.CursorToken{}, 0, err
}
data, token, err := c.List(ctx, filter, pageSize, inTok)
if err != nil {
return nil, ct.CursorToken{}, 0, err
}
return data, token, count, nil
}

View File

@@ -25,7 +25,7 @@ func (c *Coll[TData]) EnsureInitializedReflection(v TData) {
m := make(map[string]fullTypeRef) m := make(map[string]fullTypeRef)
c.initFields("", rval.Type(), m, make([]int, 0)) c.initFields("", rval.Type(), m, make([]int, 0), make([]reflect.Type, 0))
c.implDataTypeMap[rval.Type()] = m c.implDataTypeMap[rval.Type()] = m
} }
@@ -50,14 +50,13 @@ func (c *Coll[TData]) init() {
c.implDataTypeMap = make(map[reflect.Type]map[string]fullTypeRef) c.implDataTypeMap = make(map[reflect.Type]map[string]fullTypeRef)
v := reflect.ValueOf(example) v := reflect.ValueOf(example)
c.initFields("", v.Type(), c.dataTypeMap, make([]int, 0)) c.initFields("", v.Type(), c.dataTypeMap, make([]int, 0), make([]reflect.Type, 0))
} }
} }
func (c *Coll[TData]) initFields(prefix string, rtyp reflect.Type, m map[string]fullTypeRef, idxarr []int) { func (c *Coll[TData]) initFields(prefix string, rtyp reflect.Type, m map[string]fullTypeRef, idxarr []int, typesInPath []reflect.Type) {
for i := 0; i < rtyp.NumField(); i++ { for i := 0; i < rtyp.NumField(); i++ {
rsfield := rtyp.Field(i) rsfield := rtyp.Field(i)
@@ -91,7 +90,7 @@ func (c *Coll[TData]) initFields(prefix string, rtyp reflect.Type, m map[string]
if langext.InArray("inline", bsontags) && rsfield.Type.Kind() == reflect.Struct { if langext.InArray("inline", bsontags) && rsfield.Type.Kind() == reflect.Struct {
// pass-through field // pass-through field
c.initFields(prefix, rsfield.Type, m, newIdxArr) c.initFields(prefix, rsfield.Type, m, newIdxArr, typesInPath)
} else { } else {
@@ -122,11 +121,25 @@ func (c *Coll[TData]) initFields(prefix string, rtyp reflect.Type, m map[string]
} }
if rsfield.Type.Kind() == reflect.Struct { if rsfield.Type.Kind() == reflect.Struct {
c.initFields(fullKey+".", rsfield.Type, m, newIdxArr) c.initFields(fullKey+".", rsfield.Type, m, newIdxArr, typesInPath)
} }
if rsfield.Type.Kind() == reflect.Pointer && rsfield.Type.Elem().Kind() == reflect.Struct { if rsfield.Type.Kind() == reflect.Pointer && rsfield.Type.Elem().Kind() == reflect.Struct {
c.initFields(fullKey+".", rsfield.Type.Elem(), m, newIdxArr) innerType := rsfield.Type.Elem()
// check if there is recursion
recursion := false
for _, typ := range typesInPath {
recursion = recursion || (typ == innerType)
}
if !recursion {
// Store all seen types before that deref a pointer to prevent endless recursion
newTypesInPath := make([]reflect.Type, len(typesInPath))
copy(newTypesInPath, typesInPath)
newTypesInPath = append(newTypesInPath, rtyp)
c.initFields(fullKey+".", innerType, m, newIdxArr, newTypesInPath)
}
} }
} }

View File

@@ -113,19 +113,25 @@ func TestReflectionGetTokenValueAsMongoType(t *testing.T) {
type IDType string type IDType string
type RecurseiveType struct {
Other int `bson:"other"`
Inner *RecurseiveType `bson:"inner"`
}
type TestData struct { type TestData struct {
ID IDType `bson:"_id"` ID IDType `bson:"_id"`
CDate time.Time `bson:"cdate"` CDate time.Time `bson:"cdate"`
Sub struct { Sub struct {
A string `bson:"a"` A string `bson:"a"`
} `bson:"sub"` } `bson:"sub"`
SubPtr struct { SubPtr *struct {
A string `bson:"a"` A string `bson:"a"`
} `bson:"subPtr"` } `bson:"subPtr"`
Str string `bson:"str"` Str string `bson:"str"`
Ptr *int `bson:"ptr"` Ptr *int `bson:"ptr"`
Num int `bson:"num"` Num int `bson:"num"`
MDate rfctime.RFC3339NanoTime `bson:"mdate"` MDate rfctime.RFC3339NanoTime `bson:"mdate"`
Rec RecurseiveType `bson:"rec"`
} }
coll := W[TestData](&mongo.Collection{}) coll := W[TestData](&mongo.Collection{})
@@ -149,6 +155,7 @@ func TestReflectionGetTokenValueAsMongoType(t *testing.T) {
tst.AssertEqual(t, gtvasmt("hello", "str").(string), "hello") tst.AssertEqual(t, gtvasmt("hello", "str").(string), "hello")
tst.AssertEqual(t, gtvasmt("hello", "sub.a").(string), "hello") tst.AssertEqual(t, gtvasmt("hello", "sub.a").(string), "hello")
tst.AssertEqual(t, gtvasmt("hello", "subPtr.a").(string), "hello") tst.AssertEqual(t, gtvasmt("hello", "subPtr.a").(string), "hello")
tst.AssertEqual(t, gtvasmt("4", "rec.other").(int), 4)
tst.AssertEqual(t, gtvasmt("4", "num").(int), 4) tst.AssertEqual(t, gtvasmt("4", "num").(int), 4)
tst.AssertEqual(t, gtvasmt("asdf", "_id").(IDType), "asdf") tst.AssertEqual(t, gtvasmt("asdf", "_id").(IDType), "asdf")
tst.AssertEqual(t, gtvasmt("", "ptr").(*int), nil) tst.AssertEqual(t, gtvasmt("", "ptr").(*int), nil)