Compare commits
	
		
			31 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3d4afe7b25 | |||
| f5766d639c | |||
| cdf2a6e76b | |||
| 6d7cfb86f8 | |||
| 1e9d663ffe | |||
| 5b8d7ebf87 | |||
| 11dc6d2640 | |||
| 29a3f73f15 | |||
| 98105642fc | |||
| 0fd5f3b417 | |||
| 43cac4b3bb | |||
| cd68af8e66 | |||
| 113d838876 | |||
| 9e5bc0d3ea | |||
| 6d3bd13f61 | |||
| b5ca475b3f | |||
| a75b1291cb | |||
| 21cd1ee066 | |||
| ae43cbb623 | |||
| 9b752a911c | |||
| ec9ac26a4c | |||
| 39a0b73d56 | |||
| 2e2e15d4d2 | |||
| 0d16946aba | |||
| 14441c2378 | |||
| f6bcdc9903 | |||
| a95053211c | |||
| 813ce71e3e | |||
| 56ae0cfc6c | |||
| 202afc9068 | |||
| 56094b3cb6 | 
							
								
								
									
										9
									
								
								.gitea/workflows/Dockerfile_tests
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.gitea/workflows/Dockerfile_tests
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | FROM golang:latest | ||||||
|  |  | ||||||
|  | RUN apt install -y make curl python3 && go install gotest.tools/gotestsum@latest | ||||||
|  |  | ||||||
|  | COPY . /source | ||||||
|  |  | ||||||
|  | WORKDIR /source | ||||||
|  |  | ||||||
|  | CMD ["make", "test"] | ||||||
							
								
								
									
										30
									
								
								.gitea/workflows/tests.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.gitea/workflows/tests.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  |  | ||||||
|  | # https://docs.gitea.com/next/usage/actions/quickstart | ||||||
|  | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions | ||||||
|  | # https://docs.github.com/en/actions/learn-github-actions/contexts#github-context | ||||||
|  |  | ||||||
|  | name: Build Docker and Deploy | ||||||
|  | run-name: Build & Deploy ${{ gitea.ref }} on ${{ gitea.actor }} | ||||||
|  |  | ||||||
|  | on: [push] | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   run_tests: | ||||||
|  |     name: Run goext test-suite | ||||||
|  |     runs-on: bfb-cicd-latest | ||||||
|  |     steps: | ||||||
|  |  | ||||||
|  |       - name: Check out code | ||||||
|  |         uses: actions/checkout@v3 | ||||||
|  |  | ||||||
|  |       - name: Build test docker | ||||||
|  |         id: build_docker | ||||||
|  |         run: echo "DOCKER_IMG_ID=$(docker build -q . -f .gitea/workflows/Dockerfile_tests || echo __err_build__)" >> $GITHUB_OUTPUT | ||||||
|  |  | ||||||
|  |       - name: Run tests | ||||||
|  |         run: docker run --rm "${{ steps.build_docker.outputs.DOCKER_IMG_ID }}" | ||||||
|  |  | ||||||
|  |       - name: Cleanup | ||||||
|  |         if: always() | ||||||
|  |         run: docker image rm "${{ steps.build_docker.outputs.DOCKER_IMG_ID }}" | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @@ -5,7 +5,7 @@ run: | |||||||
| test: | test: | ||||||
| 	# go test ./... | 	# go test ./... | ||||||
| 	which gotestsum || go install gotest.tools/gotestsum@latest | 	which gotestsum || go install gotest.tools/gotestsum@latest | ||||||
| 	gotestsum --format "testname" -- -tags="timetzdata sqlite_fts5 sqlite_foreign_keys" "./test" | 	gotestsum --format "testname" -- -tags="timetzdata sqlite_fts5 sqlite_foreign_keys" "./..." | ||||||
|  |  | ||||||
| version: | version: | ||||||
| 	_data/version.sh | 	_data/version.sh | ||||||
							
								
								
									
										12
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								TODO.md
									
									
									
									
									
								
							| @@ -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 |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								bfcodegen/_test_example.tgz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bfcodegen/_test_example.tgz
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -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 { | ||||||
|   | |||||||
| @@ -1,15 +1,42 @@ | |||||||
| package bfcodegen | package bfcodegen | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	_ "embed" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/cmdext" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestApplyEnvOverridesSimple(t *testing.T) { | //go:embed _test_example.tgz | ||||||
|  | var ExampleModels []byte | ||||||
|  |  | ||||||
| 	err := GenerateEnumSpecs("/home/mike/Code/reiff/badennet/bnet-backend/models", "/home/mike/Code/reiff/badennet/bnet-backend/models/enums_gen.go") | func TestGenerateEnumSpecs(t *testing.T) { | ||||||
| 	if err != nil { |  | ||||||
| 		t.Error(err) | 	tmpFile := filepath.Join(t.TempDir(), langext.MustHexUUID()+".tgz") | ||||||
| 		t.Fail() |  | ||||||
| 	} | 	tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID()) | ||||||
|  |  | ||||||
|  | 	err := os.WriteFile(tmpFile, ExampleModels, 0o777) | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
|  | 	t.Cleanup(func() { _ = os.Remove(tmpFile) }) | ||||||
|  |  | ||||||
|  | 	err = os.Mkdir(tmpDir, 0o777) | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
|  | 	t.Cleanup(func() { _ = os.RemoveAll(tmpFile) }) | ||||||
|  |  | ||||||
|  | 	_, err = cmdext.Runner("tar").Arg("-xvzf").Arg(tmpFile).Arg("-C").Arg(tmpDir).FailOnExitCode().FailOnTimeout().Timeout(time.Minute).Run() | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
|  | 	err = GenerateEnumSpecs(tmpDir, tmpDir+"/enums_gen.go") | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
|  | 	err = GenerateEnumSpecs(tmpDir, tmpDir+"/enums_gen.go") | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										229
									
								
								bfcodegen/id-generate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								bfcodegen/id-generate.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,229 @@ | |||||||
|  | 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" | ||||||
|  |  | ||||||
|  | 		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 | ||||||
|  | } | ||||||
| @@ -33,7 +33,7 @@ func TestStdout(t *testing.T) { | |||||||
|  |  | ||||||
| func TestStderr(t *testing.T) { | func TestStderr(t *testing.T) { | ||||||
|  |  | ||||||
| 	res1, err := Runner("python").Arg("-c").Arg("import sys; print(\"error\", file=sys.stderr, end='')").Run() | 	res1, err := Runner("python3").Arg("-c").Arg("import sys; print(\"error\", file=sys.stderr, end='')").Run() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("%v", err) | 		t.Errorf("%v", err) | ||||||
| 	} | 	} | ||||||
| @@ -56,7 +56,7 @@ func TestStderr(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestStdcombined(t *testing.T) { | func TestStdcombined(t *testing.T) { | ||||||
| 	res1, err := Runner("python"). | 	res1, err := Runner("python3"). | ||||||
| 		Arg("-c"). | 		Arg("-c"). | ||||||
| 		Arg("import sys; import time; print(\"1\", file=sys.stderr, flush=True); time.sleep(0.1); print(\"2\", file=sys.stdout, flush=True); time.sleep(0.1); print(\"3\", file=sys.stderr, flush=True)"). | 		Arg("import sys; import time; print(\"1\", file=sys.stderr, flush=True); time.sleep(0.1); print(\"2\", file=sys.stdout, flush=True); time.sleep(0.1); print(\"3\", file=sys.stderr, flush=True)"). | ||||||
| 		Run() | 		Run() | ||||||
| @@ -82,7 +82,7 @@ func TestStdcombined(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestPartialRead(t *testing.T) { | func TestPartialRead(t *testing.T) { | ||||||
| 	res1, err := Runner("python"). | 	res1, err := Runner("python3"). | ||||||
| 		Arg("-c"). | 		Arg("-c"). | ||||||
| 		Arg("import sys; import time; print(\"first message\", flush=True); time.sleep(5); print(\"cant see me\", flush=True);"). | 		Arg("import sys; import time; print(\"first message\", flush=True); time.sleep(5); print(\"cant see me\", flush=True);"). | ||||||
| 		Timeout(100 * time.Millisecond). | 		Timeout(100 * time.Millisecond). | ||||||
| @@ -106,7 +106,7 @@ func TestPartialRead(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestPartialReadStderr(t *testing.T) { | func TestPartialReadStderr(t *testing.T) { | ||||||
| 	res1, err := Runner("python"). | 	res1, err := Runner("python3"). | ||||||
| 		Arg("-c"). | 		Arg("-c"). | ||||||
| 		Arg("import sys; import time; print(\"first message\", file=sys.stderr, flush=True); time.sleep(5); print(\"cant see me\", file=sys.stderr, flush=True);"). | 		Arg("import sys; import time; print(\"first message\", file=sys.stderr, flush=True); time.sleep(5); print(\"cant see me\", file=sys.stderr, flush=True);"). | ||||||
| 		Timeout(100 * time.Millisecond). | 		Timeout(100 * time.Millisecond). | ||||||
| @@ -131,7 +131,7 @@ func TestPartialReadStderr(t *testing.T) { | |||||||
|  |  | ||||||
| func TestReadUnflushedStdout(t *testing.T) { | func TestReadUnflushedStdout(t *testing.T) { | ||||||
|  |  | ||||||
| 	res1, err := Runner("python").Arg("-c").Arg("import sys; print(\"message101\", file=sys.stdout, end='')").Run() | 	res1, err := Runner("python3").Arg("-c").Arg("import sys; print(\"message101\", file=sys.stdout, end='')").Run() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("%v", err) | 		t.Errorf("%v", err) | ||||||
| 	} | 	} | ||||||
| @@ -155,7 +155,7 @@ func TestReadUnflushedStdout(t *testing.T) { | |||||||
|  |  | ||||||
| func TestReadUnflushedStderr(t *testing.T) { | func TestReadUnflushedStderr(t *testing.T) { | ||||||
|  |  | ||||||
| 	res1, err := Runner("python").Arg("-c").Arg("import sys; print(\"message101\", file=sys.stderr, end='')").Run() | 	res1, err := Runner("python3").Arg("-c").Arg("import sys; print(\"message101\", file=sys.stderr, end='')").Run() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("%v", err) | 		t.Errorf("%v", err) | ||||||
| 	} | 	} | ||||||
| @@ -180,7 +180,7 @@ func TestReadUnflushedStderr(t *testing.T) { | |||||||
| func TestPartialReadUnflushed(t *testing.T) { | func TestPartialReadUnflushed(t *testing.T) { | ||||||
| 	t.SkipNow() | 	t.SkipNow() | ||||||
|  |  | ||||||
| 	res1, err := Runner("python"). | 	res1, err := Runner("python3"). | ||||||
| 		Arg("-c"). | 		Arg("-c"). | ||||||
| 		Arg("import sys; import time; print(\"first message\", end=''); time.sleep(5); print(\"cant see me\", end='');"). | 		Arg("import sys; import time; print(\"first message\", end=''); time.sleep(5); print(\"cant see me\", end='');"). | ||||||
| 		Timeout(100 * time.Millisecond). | 		Timeout(100 * time.Millisecond). | ||||||
| @@ -206,7 +206,7 @@ func TestPartialReadUnflushed(t *testing.T) { | |||||||
| func TestPartialReadUnflushedStderr(t *testing.T) { | func TestPartialReadUnflushedStderr(t *testing.T) { | ||||||
| 	t.SkipNow() | 	t.SkipNow() | ||||||
|  |  | ||||||
| 	res1, err := Runner("python"). | 	res1, err := Runner("python3"). | ||||||
| 		Arg("-c"). | 		Arg("-c"). | ||||||
| 		Arg("import sys; import time; print(\"first message\", file=sys.stderr, end=''); time.sleep(5); print(\"cant see me\", file=sys.stderr, end='');"). | 		Arg("import sys; import time; print(\"first message\", file=sys.stderr, end=''); time.sleep(5); print(\"cant see me\", file=sys.stderr, end='');"). | ||||||
| 		Timeout(100 * time.Millisecond). | 		Timeout(100 * time.Millisecond). | ||||||
| @@ -231,7 +231,7 @@ func TestPartialReadUnflushedStderr(t *testing.T) { | |||||||
|  |  | ||||||
| func TestListener(t *testing.T) { | func TestListener(t *testing.T) { | ||||||
|  |  | ||||||
| 	res1, err := Runner("python"). | 	res1, err := Runner("python3"). | ||||||
| 		Arg("-c"). | 		Arg("-c"). | ||||||
| 		Arg("import sys;" + | 		Arg("import sys;" + | ||||||
| 			"import time;" + | 			"import time;" + | ||||||
| @@ -264,7 +264,7 @@ func TestListener(t *testing.T) { | |||||||
|  |  | ||||||
| func TestLongStdout(t *testing.T) { | func TestLongStdout(t *testing.T) { | ||||||
|  |  | ||||||
| 	res1, err := Runner("python"). | 	res1, err := Runner("python3"). | ||||||
| 		Arg("-c"). | 		Arg("-c"). | ||||||
| 		Arg("import sys; import time; print(\"X\" * 125001 + \"\\n\"); print(\"Y\" * 125001 + \"\\n\"); print(\"Z\" * 125001 + \"\\n\");"). | 		Arg("import sys; import time; print(\"X\" * 125001 + \"\\n\"); print(\"Y\" * 125001 + \"\\n\"); print(\"Z\" * 125001 + \"\\n\");"). | ||||||
| 		Timeout(5000 * time.Millisecond). | 		Timeout(5000 * time.Millisecond). | ||||||
| @@ -298,7 +298,7 @@ func TestFailOnTimeout(t *testing.T) { | |||||||
|  |  | ||||||
| func TestFailOnStderr(t *testing.T) { | func TestFailOnStderr(t *testing.T) { | ||||||
|  |  | ||||||
| 	res1, err := Runner("python").Arg("-c").Arg("import sys; print(\"error\", file=sys.stderr, end='')").FailOnStderr().Run() | 	res1, err := Runner("python3").Arg("-c").Arg("import sys; print(\"error\", file=sys.stderr, end='')").FailOnStderr().Run() | ||||||
| 	if err == nil { | 	if err == nil { | ||||||
| 		t.Errorf("no err") | 		t.Errorf("no err") | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										170
									
								
								dataext/tuple.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								dataext/tuple.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | |||||||
|  | package dataext | ||||||
|  |  | ||||||
|  | type ValueGroup interface { | ||||||
|  | 	TupleLength() int | ||||||
|  | 	TupleValues() []any | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type Single[T1 any] struct { | ||||||
|  | 	V1 T1 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s Single[T1]) TupleLength() int { | ||||||
|  | 	return 1 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s Single[T1]) TupleValues() []any { | ||||||
|  | 	return []any{s.V1} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type Tuple[T1 any, T2 any] struct { | ||||||
|  | 	V1 T1 | ||||||
|  | 	V2 T2 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Tuple[T1, T2]) TupleLength() int { | ||||||
|  | 	return 2 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Tuple[T1, T2]) TupleValues() []any { | ||||||
|  | 	return []any{t.V1, t.V2} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type Triple[T1 any, T2 any, T3 any] struct { | ||||||
|  | 	V1 T1 | ||||||
|  | 	V2 T2 | ||||||
|  | 	V3 T3 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Triple[T1, T2, T3]) TupleLength() int { | ||||||
|  | 	return 3 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Triple[T1, T2, T3]) TupleValues() []any { | ||||||
|  | 	return []any{t.V1, t.V2, t.V3} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type Quadruple[T1 any, T2 any, T3 any, T4 any] struct { | ||||||
|  | 	V1 T1 | ||||||
|  | 	V2 T2 | ||||||
|  | 	V3 T3 | ||||||
|  | 	V4 T4 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Quadruple[T1, T2, T3, T4]) TupleLength() int { | ||||||
|  | 	return 4 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Quadruple[T1, T2, T3, T4]) TupleValues() []any { | ||||||
|  | 	return []any{t.V1, t.V2, t.V3, t.V4} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type Quintuple[T1 any, T2 any, T3 any, T4 any, T5 any] struct { | ||||||
|  | 	V1 T1 | ||||||
|  | 	V2 T2 | ||||||
|  | 	V3 T3 | ||||||
|  | 	V4 T4 | ||||||
|  | 	V5 T5 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Quintuple[T1, T2, T3, T4, T5]) TupleLength() int { | ||||||
|  | 	return 5 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Quintuple[T1, T2, T3, T4, T5]) TupleValues() []any { | ||||||
|  | 	return []any{t.V1, t.V2, t.V3, t.V4, t.V5} | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type Sextuple[T1 any, T2 any, T3 any, T4 any, T5 any, T6 any] struct { | ||||||
|  | 	V1 T1 | ||||||
|  | 	V2 T2 | ||||||
|  | 	V3 T3 | ||||||
|  | 	V4 T4 | ||||||
|  | 	V5 T5 | ||||||
|  | 	V6 T6 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Sextuple[T1, T2, T3, T4, T5, T6]) TupleLength() int { | ||||||
|  | 	return 6 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Sextuple[T1, T2, T3, T4, T5, T6]) TupleValues() []any { | ||||||
|  | 	return []any{t.V1, t.V2, t.V3, t.V4, t.V5, t.V6} | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type Septuple[T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any] struct { | ||||||
|  | 	V1 T1 | ||||||
|  | 	V2 T2 | ||||||
|  | 	V3 T3 | ||||||
|  | 	V4 T4 | ||||||
|  | 	V5 T5 | ||||||
|  | 	V6 T6 | ||||||
|  | 	V7 T7 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Septuple[T1, T2, T3, T4, T5, T6, T7]) TupleLength() int { | ||||||
|  | 	return 7 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Septuple[T1, T2, T3, T4, T5, T6, T7]) TupleValues() []any { | ||||||
|  | 	return []any{t.V1, t.V2, t.V3, t.V4, t.V5, t.V6, t.V7} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type Octuple[T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any, T8 any] struct { | ||||||
|  | 	V1 T1 | ||||||
|  | 	V2 T2 | ||||||
|  | 	V3 T3 | ||||||
|  | 	V4 T4 | ||||||
|  | 	V5 T5 | ||||||
|  | 	V6 T6 | ||||||
|  | 	V7 T7 | ||||||
|  | 	V8 T8 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Octuple[T1, T2, T3, T4, T5, T6, T7, T8]) TupleLength() int { | ||||||
|  | 	return 8 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Octuple[T1, T2, T3, T4, T5, T6, T7, T8]) TupleValues() []any { | ||||||
|  | 	return []any{t.V1, t.V2, t.V3, t.V4, t.V5, t.V6, t.V7, t.V8} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type Nonuple[T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any, T8 any, T9 any] struct { | ||||||
|  | 	V1 T1 | ||||||
|  | 	V2 T2 | ||||||
|  | 	V3 T3 | ||||||
|  | 	V4 T4 | ||||||
|  | 	V5 T5 | ||||||
|  | 	V6 T6 | ||||||
|  | 	V7 T7 | ||||||
|  | 	V8 T8 | ||||||
|  | 	V9 T9 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Nonuple[T1, T2, T3, T4, T5, T6, T7, T8, T9]) TupleLength() int { | ||||||
|  | 	return 9 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Nonuple[T1, T2, T3, T4, T5, T6, T7, T8, T9]) TupleValues() []any { | ||||||
|  | 	return []any{t.V1, t.V2, t.V3, t.V4, t.V5, t.V6, t.V7, t.V8, t.V9} | ||||||
|  | } | ||||||
| @@ -322,7 +322,9 @@ func (b *Builder) GinReq(ctx context.Context, g *gin.Context, req *http.Request) | |||||||
| 	if ctxVal := g.GetString("reqid"); ctxVal != "" { | 	if ctxVal := g.GetString("reqid"); ctxVal != "" { | ||||||
| 		b.Str("gin.context.reqid", ctxVal) | 		b.Str("gin.context.reqid", ctxVal) | ||||||
| 	} | 	} | ||||||
| 	if req.Method != "GET" && req.Body != nil && req.Header.Get("Content-Type") == "application/json" { | 	if req.Method != "GET" && req.Body != nil { | ||||||
|  |  | ||||||
|  | 		if req.Header.Get("Content-Type") == "application/json" { | ||||||
| 			if brc, ok := req.Body.(dataext.BufferedReadCloser); ok { | 			if brc, ok := req.Body.(dataext.BufferedReadCloser); ok { | ||||||
| 				if bin, err := brc.BufferedAll(); err == nil { | 				if bin, err := brc.BufferedAll(); err == nil { | ||||||
| 					if len(bin) < 16*1024 { | 					if len(bin) < 16*1024 { | ||||||
| @@ -334,12 +336,26 @@ func (b *Builder) GinReq(ctx context.Context, g *gin.Context, req *http.Request) | |||||||
| 							b.Bytes("gin.body", bin) | 							b.Bytes("gin.body", bin) | ||||||
| 						} | 						} | ||||||
| 					} else { | 					} else { | ||||||
| 					b.Str("gin.body", fmt.Sprintf("[[%v bytes]]", len(bin))) | 						b.Str("gin.body", fmt.Sprintf("[[%v bytes | %s]]", len(bin), req.Header.Get("Content-Type"))) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if req.Header.Get("Content-Type") == "multipart/form-data" || req.Header.Get("Content-Type") == "x-www-form-urlencoded" { | ||||||
|  | 			if brc, ok := req.Body.(dataext.BufferedReadCloser); ok { | ||||||
|  | 				if bin, err := brc.BufferedAll(); err == nil { | ||||||
|  | 					if len(bin) < 16*1024 { | ||||||
|  | 						b.Bytes("gin.body", bin) | ||||||
|  | 					} else { | ||||||
|  | 						b.Str("gin.body", fmt.Sprintf("[[%v bytes | %s]]", len(bin), req.Header.Get("Content-Type"))) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	b.containsGinData = true | 	b.containsGinData = true | ||||||
| 	return b | 	return b | ||||||
| } | } | ||||||
|   | |||||||
| @@ -42,6 +42,11 @@ var ( | |||||||
| 	TypePanic          = NewType("PANIC", langext.Ptr(500)) | 	TypePanic          = NewType("PANIC", langext.Ptr(500)) | ||||||
| 	TypeNotImplemented = NewType("NOT_IMPLEMENTED", langext.Ptr(500)) | 	TypeNotImplemented = NewType("NOT_IMPLEMENTED", langext.Ptr(500)) | ||||||
|  |  | ||||||
|  | 	TypeMongoQuery        = NewType("MONGO_QUERY", langext.Ptr(500)) | ||||||
|  | 	TypeCursorTokenDecode = NewType("CURSOR_TOKEN_DECODE", langext.Ptr(500)) | ||||||
|  | 	TypeMongoFilter       = NewType("MONGO_FILTER", langext.Ptr(500)) | ||||||
|  | 	TypeMongoReflection   = NewType("MONGO_REFLECTION", langext.Ptr(500)) | ||||||
|  |  | ||||||
| 	TypeWrap = NewType("Wrap", nil) | 	TypeWrap = NewType("Wrap", nil) | ||||||
|  |  | ||||||
| 	TypeBindFailURI      = NewType("BINDFAIL_URI", langext.Ptr(400)) | 	TypeBindFailURI      = NewType("BINDFAIL_URI", langext.Ptr(400)) | ||||||
| @@ -50,10 +55,12 @@ 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)) | ||||||
|  |  | ||||||
| 	// other values come from pkgconfig | 	// other values come the used package | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var registeredTypes = dataext.SyncSet[string]{} | var registeredTypes = dataext.SyncSet[string]{} | ||||||
|   | |||||||
| @@ -16,11 +16,11 @@ type ErrorPackageConfig struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type ErrorPackageConfigInit struct { | type ErrorPackageConfigInit struct { | ||||||
| 	ZeroLogErrTraces       bool | 	ZeroLogErrTraces       *bool | ||||||
| 	ZeroLogAllTraces       bool | 	ZeroLogAllTraces       *bool | ||||||
| 	RecursiveErrors        bool | 	RecursiveErrors        *bool | ||||||
| 	ExtendedGinOutput      bool | 	ExtendedGinOutput      *bool | ||||||
| 	IncludeMetaInGinOutput bool | 	IncludeMetaInGinOutput *bool | ||||||
| 	ExtendGinOutput        func(err *ExErr, json map[string]any) | 	ExtendGinOutput        func(err *ExErr, json map[string]any) | ||||||
| 	ExtendGinDataOutput    func(err *ExErr, depth int, json map[string]any) | 	ExtendGinDataOutput    func(err *ExErr, depth int, json map[string]any) | ||||||
| } | } | ||||||
| @@ -56,11 +56,11 @@ func Init(cfg ErrorPackageConfigInit) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pkgconfig = ErrorPackageConfig{ | 	pkgconfig = ErrorPackageConfig{ | ||||||
| 		ZeroLogErrTraces:       cfg.ZeroLogErrTraces, | 		ZeroLogErrTraces:       langext.Coalesce(cfg.ZeroLogErrTraces, pkgconfig.ZeroLogErrTraces), | ||||||
| 		ZeroLogAllTraces:       cfg.ZeroLogAllTraces, | 		ZeroLogAllTraces:       langext.Coalesce(cfg.ZeroLogAllTraces, pkgconfig.ZeroLogAllTraces), | ||||||
| 		RecursiveErrors:        cfg.RecursiveErrors, | 		RecursiveErrors:        langext.Coalesce(cfg.RecursiveErrors, pkgconfig.RecursiveErrors), | ||||||
| 		ExtendedGinOutput:      cfg.ExtendedGinOutput, | 		ExtendedGinOutput:      langext.Coalesce(cfg.ExtendedGinOutput, pkgconfig.ExtendedGinOutput), | ||||||
| 		IncludeMetaInGinOutput: cfg.IncludeMetaInGinOutput, | 		IncludeMetaInGinOutput: langext.Coalesce(cfg.IncludeMetaInGinOutput, pkgconfig.IncludeMetaInGinOutput), | ||||||
| 		ExtendGinOutput:        ego, | 		ExtendGinOutput:        ego, | ||||||
| 		ExtendGinDataOutput:    egdo, | 		ExtendGinDataOutput:    egdo, | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -612,7 +612,18 @@ func (v MetaValue) rawValueForJson() any { | |||||||
| 		if v.Value.(AnyWrap).IsNil { | 		if v.Value.(AnyWrap).IsNil { | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
| 		return v.Value.(AnyWrap).Serialize() | 		if v.Value.(AnyWrap).IsError { | ||||||
|  | 			return bson.M{"@error": true} | ||||||
|  | 		} | ||||||
|  | 		jsonobj := primitive.M{} | ||||||
|  | 		jsonarr := primitive.A{} | ||||||
|  | 		if err := json.Unmarshal([]byte(v.Value.(AnyWrap).Json), &jsonobj); err == nil { | ||||||
|  | 			return jsonobj | ||||||
|  | 		} else if err := json.Unmarshal([]byte(v.Value.(AnyWrap).Json), &jsonarr); err == nil { | ||||||
|  | 			return jsonarr | ||||||
|  | 		} else { | ||||||
|  | 			return bson.M{"type": v.Value.(AnyWrap).Type, "data": v.Value.(AnyWrap).Json} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	if v.DataType == MDTID { | 	if v.DataType == MDTID { | ||||||
| 		if v.Value.(IDWrap).IsNil { | 		if v.Value.(IDWrap).IsNil { | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import ( | |||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
| 	"runtime/debug" | 	"runtime/debug" | ||||||
|  | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type PreContext struct { | type PreContext struct { | ||||||
| @@ -18,6 +19,7 @@ type PreContext struct { | |||||||
| 	body    any | 	body    any | ||||||
| 	form    any | 	form    any | ||||||
| 	header  any | 	header  any | ||||||
|  | 	timeout *time.Duration | ||||||
| } | } | ||||||
|  |  | ||||||
| func (pctx *PreContext) URI(uri any) *PreContext { | func (pctx *PreContext) URI(uri any) *PreContext { | ||||||
| @@ -45,6 +47,11 @@ func (pctx *PreContext) Header(header any) *PreContext { | |||||||
| 	return pctx | 	return pctx | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (pctx *PreContext) WithTimeout(to time.Duration) *PreContext { | ||||||
|  | 	pctx.timeout = &to | ||||||
|  | 	return pctx | ||||||
|  | } | ||||||
|  |  | ||||||
| func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) { | func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) { | ||||||
| 	if pctx.uri != nil { | 	if pctx.uri != nil { | ||||||
| 		if err := pctx.ginCtx.ShouldBindUri(pctx.uri); err != nil { | 		if err := pctx.ginCtx.ShouldBindUri(pctx.uri); err != nil { | ||||||
| @@ -109,7 +116,7 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if pctx.header != nil { | 	if pctx.header != nil { | ||||||
| 		if err := pctx.ginCtx.ShouldBindHeader(pctx.query); err != nil { | 		if err := pctx.ginCtx.ShouldBindHeader(pctx.header); err != nil { | ||||||
| 			err = exerr.Wrap(err, "Failed to read header"). | 			err = exerr.Wrap(err, "Failed to read header"). | ||||||
| 				WithType(exerr.TypeBindFailHeader). | 				WithType(exerr.TypeBindFailHeader). | ||||||
| 				Str("struct_type", fmt.Sprintf("%T", pctx.query)). | 				Str("struct_type", fmt.Sprintf("%T", pctx.query)). | ||||||
| @@ -118,7 +125,7 @@ func (pctx PreContext) Start() (*AppContext, *gin.Context, *HTTPResponse) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ictx, cancel := context.WithTimeout(context.Background(), pctx.wrapper.requestTimeout) | 	ictx, cancel := context.WithTimeout(context.Background(), langext.Coalesce(pctx.timeout, pctx.wrapper.requestTimeout)) | ||||||
| 	actx := CreateAppContext(pctx.ginCtx, ictx, cancel) | 	actx := CreateAppContext(pctx.ginCtx, ictx, cancel) | ||||||
|  |  | ||||||
| 	return actx, pctx.ginCtx, nil | 	return actx, pctx.ginCtx, nil | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								go.mod
									
									
									
									
									
								
							| @@ -8,20 +8,20 @@ require ( | |||||||
| 	github.com/rs/xid v1.5.0 | 	github.com/rs/xid v1.5.0 | ||||||
| 	github.com/rs/zerolog v1.30.0 | 	github.com/rs/zerolog v1.30.0 | ||||||
| 	go.mongodb.org/mongo-driver v1.12.1 | 	go.mongodb.org/mongo-driver v1.12.1 | ||||||
| 	golang.org/x/crypto v0.12.0 | 	golang.org/x/crypto v0.13.0 | ||||||
| 	golang.org/x/sys v0.11.0 | 	golang.org/x/sys v0.12.0 | ||||||
| 	golang.org/x/term v0.11.0 | 	golang.org/x/term v0.12.0 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/bytedance/sonic v1.10.0-rc3 // indirect | 	github.com/bytedance/sonic v1.10.1 // indirect | ||||||
| 	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect | 	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect | ||||||
| 	github.com/chenzhuoyu/iasm v0.9.0 // indirect | 	github.com/chenzhuoyu/iasm v0.9.0 // indirect | ||||||
| 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect | 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect | ||||||
| 	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.0 // 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 | ||||||
| @@ -33,7 +33,7 @@ require ( | |||||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||||
| 	github.com/modern-go/reflect2 v1.0.2 // indirect | 	github.com/modern-go/reflect2 v1.0.2 // indirect | ||||||
| 	github.com/montanaflynn/stats v0.7.1 // indirect | 	github.com/montanaflynn/stats v0.7.1 // indirect | ||||||
| 	github.com/pelletier/go-toml/v2 v2.0.9 // indirect | 	github.com/pelletier/go-toml/v2 v2.1.0 // indirect | ||||||
| 	github.com/pkg/errors v0.9.1 // indirect | 	github.com/pkg/errors v0.9.1 // indirect | ||||||
| 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect | 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect | ||||||
| 	github.com/ugorji/go/codec v1.2.11 // indirect | 	github.com/ugorji/go/codec v1.2.11 // indirect | ||||||
| @@ -41,10 +41,10 @@ require ( | |||||||
| 	github.com/xdg-go/scram v1.1.2 // indirect | 	github.com/xdg-go/scram v1.1.2 // indirect | ||||||
| 	github.com/xdg-go/stringprep v1.0.4 // indirect | 	github.com/xdg-go/stringprep v1.0.4 // indirect | ||||||
| 	github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect | 	github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect | ||||||
| 	golang.org/x/arch v0.4.0 // indirect | 	golang.org/x/arch v0.5.0 // indirect | ||||||
| 	golang.org/x/net v0.14.0 // indirect | 	golang.org/x/net v0.15.0 // indirect | ||||||
| 	golang.org/x/sync v0.3.0 // indirect | 	golang.org/x/sync v0.3.0 // indirect | ||||||
| 	golang.org/x/text v0.12.0 // indirect | 	golang.org/x/text v0.13.0 // indirect | ||||||
| 	google.golang.org/protobuf v1.31.0 // indirect | 	google.golang.org/protobuf v1.31.0 // indirect | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								go.sum
									
									
									
									
									
								
							| @@ -4,6 +4,10 @@ github.com/bytedance/sonic v1.10.0-rc2 h1:oDfRZ+4m6AYCOC0GFeOCeYqvBmucy1isvouS2K | |||||||
| github.com/bytedance/sonic v1.10.0-rc2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | github.com/bytedance/sonic v1.10.0-rc2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | ||||||
| github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0= | github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0= | ||||||
| github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | ||||||
|  | github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= | ||||||
|  | github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | ||||||
|  | github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= | ||||||
|  | github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | ||||||
| github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= | ||||||
| github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= | ||||||
| github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= | ||||||
| @@ -28,6 +32,12 @@ github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+j | |||||||
| github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | ||||||
| github.com/go-playground/validator/v10 v10.15.0 h1:nDU5XeOKtB3GEa+uB7GNYwhVKsgjAR7VgKoNB6ryXfw= | github.com/go-playground/validator/v10 v10.15.0 h1:nDU5XeOKtB3GEa+uB7GNYwhVKsgjAR7VgKoNB6ryXfw= | ||||||
| github.com/go-playground/validator/v10 v10.15.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | github.com/go-playground/validator/v10 v10.15.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | ||||||
|  | github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= | ||||||
|  | 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/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= | ||||||
| @@ -79,6 +89,8 @@ github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8 | |||||||
| github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= | github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= | ||||||
| github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= | github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= | ||||||
| github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= | github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= | ||||||
|  | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= | ||||||
|  | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= | ||||||
| github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
| @@ -131,6 +143,8 @@ go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl | |||||||
| golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= | ||||||
| golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= | golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= | ||||||
| golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= | golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= | ||||||
|  | golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= | ||||||
|  | golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= | ||||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
| golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||||
| @@ -141,6 +155,8 @@ golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= | |||||||
| golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= | ||||||
| golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= | golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= | ||||||
| golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= | golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= | ||||||
|  | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= | ||||||
|  | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= | ||||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
| @@ -153,6 +169,8 @@ golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= | |||||||
| golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= | golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= | ||||||
| golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= | golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= | ||||||
| golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= | golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= | ||||||
|  | golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= | ||||||
|  | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= | ||||||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= | ||||||
| golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| @@ -180,6 +198,8 @@ golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= | |||||||
| golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= | ||||||
| golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= | ||||||
|  | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||||
| golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= | golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= | ||||||
| @@ -190,6 +210,8 @@ golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= | |||||||
| golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= | golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= | ||||||
| golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= | golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= | ||||||
| golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= | golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= | ||||||
|  | golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= | ||||||
|  | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= | ||||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
| @@ -202,6 +224,8 @@ golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= | |||||||
| golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||||||
| golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= | golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= | ||||||
| golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||||||
|  | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= | ||||||
|  | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| package goext | package goext | ||||||
|  |  | ||||||
| const GoextVersion = "0.0.238" | const GoextVersion = "0.0.263" | ||||||
|  |  | ||||||
| const GoextVersionTimestamp = "2023-08-09T19:51:41+0200" | const GoextVersionTimestamp = "2023-09-18T10:43:29+0200" | ||||||
|   | |||||||
| @@ -400,7 +400,7 @@ func ArrCastErr[T1 any, T2 any](arr []T1) ([]T2, error) { | |||||||
| 		if vcast, ok := any(v).(T2); ok { | 		if vcast, ok := any(v).(T2); ok { | ||||||
| 			r[i] = vcast | 			r[i] = vcast | ||||||
| 		} else { | 		} else { | ||||||
| 			return nil, errors.New(fmt.Sprintf("Cannot cast element %d of type %T to type %s", i, v, *new(T2))) | 			return nil, errors.New(fmt.Sprintf("Cannot cast element %d of type %T to type %v", i, v, *new(T2))) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return r, nil | 	return r, nil | ||||||
| @@ -412,7 +412,7 @@ func ArrCastPanic[T1 any, T2 any](arr []T1) []T2 { | |||||||
| 		if vcast, ok := any(v).(T2); ok { | 		if vcast, ok := any(v).(T2); ok { | ||||||
| 			r[i] = vcast | 			r[i] = vcast | ||||||
| 		} else { | 		} else { | ||||||
| 			panic(fmt.Sprintf("Cannot cast element %d of type %T to type %s", i, v, *new(T2))) | 			panic(fmt.Sprintf("Cannot cast element %d of type %T to type %v", i, v, *new(T2))) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return r | 	return r | ||||||
| @@ -467,3 +467,15 @@ func ArrayToInterface[T any](t []T) []interface{} { | |||||||
| 	} | 	} | ||||||
| 	return res | 	return res | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func JoinString(arr []string, delimiter string) string { | ||||||
|  | 	str := "" | ||||||
|  | 	for i, v := range arr { | ||||||
|  | 		str += v | ||||||
|  | 		if i < len(arr)-1 { | ||||||
|  | 			str += delimiter | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return str | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								langext/array_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								langext/array_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | package langext | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestJoinString(t *testing.T) { | ||||||
|  | 	ids := []string{"1", "2", "3"} | ||||||
|  | 	res := JoinString(ids, ",") | ||||||
|  | 	tst.AssertEqual(t, res, "1,2,3") | ||||||
|  | } | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| package langext | package langext | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -59,9 +60,3 @@ func TestBase58FlickrDecoding(t *testing.T) { | |||||||
| 	tst.AssertEqual(t, _decStr(t, Base58FlickrEncoding, "9aJCVZR"), "Hello") | 	tst.AssertEqual(t, _decStr(t, Base58FlickrEncoding, "9aJCVZR"), "Hello") | ||||||
| 	tst.AssertEqual(t, _decStr(t, Base58FlickrEncoding, "48638rmBiUzG5NKQoX4KcuE5C8paCFACnE28F7qDx13PRtennAmYSSJQ5gJSRihf5ZDyEQS4UimtihR7uARt4wbty2fW9duTQTM9n1DwUBevreyzGwu6W4YSgrvQgCPDxsiE1mCdZsF8VEBpuHHEiJyw"), "If debugging is the process of removing software bugs, then programming must be the process of putting them in.") | 	tst.AssertEqual(t, _decStr(t, Base58FlickrEncoding, "48638rmBiUzG5NKQoX4KcuE5C8paCFACnE28F7qDx13PRtennAmYSSJQ5gJSRihf5ZDyEQS4UimtihR7uARt4wbty2fW9duTQTM9n1DwUBevreyzGwu6W4YSgrvQgCPDxsiE1mCdZsF8VEBpuHHEiJyw"), "If debugging is the process of removing software bugs, then programming must be the process of putting them in.") | ||||||
| } | } | ||||||
|  |  | ||||||
| func tst.AssertEqual(t *testing.T, actual string, expected string) { |  | ||||||
| 	if actual != expected { |  | ||||||
| 		t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ type Regex interface { | |||||||
| 	ReplaceAllFunc(haystack string, repl func(string) string) string | 	ReplaceAllFunc(haystack string, repl func(string) string) string | ||||||
| 	RemoveAll(haystack string) string | 	RemoveAll(haystack string) string | ||||||
| 	GroupCount() int | 	GroupCount() int | ||||||
|  | 	String() string | ||||||
| } | } | ||||||
|  |  | ||||||
| type regexWrapper struct { | type regexWrapper struct { | ||||||
| @@ -42,6 +43,7 @@ func W(rex *regexp.Regexp) Regex { | |||||||
|  |  | ||||||
| // --------------------------------------------------------------------------------------------------------------------- | // --------------------------------------------------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | // IsMatch reports whether the string s contains any match of the regular expression re. | ||||||
| func (w *regexWrapper) IsMatch(haystack string) bool { | func (w *regexWrapper) IsMatch(haystack string) bool { | ||||||
| 	return w.rex.MatchString(haystack) | 	return w.rex.MatchString(haystack) | ||||||
| } | } | ||||||
| @@ -88,6 +90,11 @@ func (w *regexWrapper) GroupCount() int { | |||||||
| 	return len(w.subnames) - 1 | 	return len(w.subnames) - 1 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // String returns the source text used to compile the regular expression. | ||||||
|  | func (w *regexWrapper) String() string { | ||||||
|  | 	return w.rex.String() | ||||||
|  | } | ||||||
|  |  | ||||||
| // --------------------------------------------------------------------------------------------------------------------- | // --------------------------------------------------------------------------------------------------------------------- | ||||||
|  |  | ||||||
| func (m RegexMatch) FullMatch() RegexMatchGroup { | func (m RegexMatch) FullMatch() RegexMatchGroup { | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ func (t *RFC3339Time) UnmarshalText(data []byte) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (t *RFC3339Time) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | func (t *RFC3339Time) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | ||||||
| 	if bt == bsontype.Null { | 	if bt == bson.TypeNull { | ||||||
| 		// we can't set nil in UnmarshalBSONValue (so we use default(struct)) | 		// we can't set nil in UnmarshalBSONValue (so we use default(struct)) | ||||||
| 		// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values | 		// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values | ||||||
| 		// https://stackoverflow.com/questions/75167597 | 		// https://stackoverflow.com/questions/75167597 | ||||||
| @@ -77,7 +77,7 @@ func (t *RFC3339Time) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | |||||||
| 		*t = RFC3339Time{} | 		*t = RFC3339Time{} | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	if bt != bsontype.DateTime { | 	if bt != bson.TypeDateTime { | ||||||
| 		return errors.New(fmt.Sprintf("cannot unmarshal %v into RFC3339Time", bt)) | 		return errors.New(fmt.Sprintf("cannot unmarshal %v into RFC3339Time", bt)) | ||||||
| 	} | 	} | ||||||
| 	var tt time.Time | 	var tt time.Time | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ func (t *RFC3339NanoTime) UnmarshalText(data []byte) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (t *RFC3339NanoTime) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | func (t *RFC3339NanoTime) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | ||||||
| 	if bt == bsontype.Null { | 	if bt == bson.TypeNull { | ||||||
| 		// we can't set nil in UnmarshalBSONValue (so we use default(struct)) | 		// we can't set nil in UnmarshalBSONValue (so we use default(struct)) | ||||||
| 		// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values | 		// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values | ||||||
| 		// https://stackoverflow.com/questions/75167597 | 		// https://stackoverflow.com/questions/75167597 | ||||||
| @@ -77,7 +77,7 @@ func (t *RFC3339NanoTime) UnmarshalBSONValue(bt bsontype.Type, data []byte) erro | |||||||
| 		*t = RFC3339NanoTime{} | 		*t = RFC3339NanoTime{} | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	if bt != bsontype.DateTime { | 	if bt != bson.TypeDateTime { | ||||||
| 		return errors.New(fmt.Sprintf("cannot unmarshal %v into RFC3339NanoTime", bt)) | 		return errors.New(fmt.Sprintf("cannot unmarshal %v into RFC3339NanoTime", bt)) | ||||||
| 	} | 	} | ||||||
| 	var tt time.Time | 	var tt time.Time | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package rfctime | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/timeext" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
| @@ -13,7 +14,7 @@ func TestRoundtrip(t *testing.T) { | |||||||
| 		Value RFC3339NanoTime `json:"v"` | 		Value RFC3339NanoTime `json:"v"` | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	val1 := NewRFC3339Nano(time.Unix(0, 1675951556820915171)) | 	val1 := NewRFC3339Nano(time.Unix(0, 1675951556820915171).In(timeext.TimezoneBerlin)) | ||||||
| 	w1 := Wrap{val1} | 	w1 := Wrap{val1} | ||||||
|  |  | ||||||
| 	jstr1, err := json.Marshal(w1) | 	jstr1, err := json.Marshal(w1) | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package termext | package termext | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | ||||||
| 	"math/rand" | 	"math/rand" | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
| @@ -32,9 +33,3 @@ func TestColor(t *testing.T) { | |||||||
| 	tst.AssertEqual(t, CleanString(Gray("test")), "test") | 	tst.AssertEqual(t, CleanString(Gray("test")), "test") | ||||||
| 	tst.AssertEqual(t, CleanString(White("test")), "test") | 	tst.AssertEqual(t, CleanString(White("test")), "test") | ||||||
| } | } | ||||||
|  |  | ||||||
| func tst.AssertEqual(t *testing.T, actual string, expected string) { |  | ||||||
| 	if actual != expected { |  | ||||||
| 		t.Errorf("values differ: Actual: '%v', Expected: '%v'", actual, expected) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -7,56 +7,56 @@ import ( | |||||||
|  |  | ||||||
| func TestParseDurationShortString(t *testing.T) { | func TestParseDurationShortString(t *testing.T) { | ||||||
|  |  | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(1), "1s") | 	assertPDSSEqual(t, FromSeconds(1), "1s") | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(1), "1sec") | 	assertPDSSEqual(t, FromSeconds(1), "1sec") | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(1), "1second") | 	assertPDSSEqual(t, FromSeconds(1), "1second") | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(1), "1seconds") | 	assertPDSSEqual(t, FromSeconds(1), "1seconds") | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(100), "100second") | 	assertPDSSEqual(t, FromSeconds(100), "100second") | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(100), "100seconds") | 	assertPDSSEqual(t, FromSeconds(100), "100seconds") | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(1883639.77), "1883639.77second") | 	assertPDSSEqual(t, FromSeconds(1883639.77), "1883639.77second") | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(1883639.77), "1883639.77seconds") | 	assertPDSSEqual(t, FromSeconds(1883639.77), "1883639.77seconds") | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(50), "50s") | 	assertPDSSEqual(t, FromSeconds(50), "50s") | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(50), "50sec") | 	assertPDSSEqual(t, FromSeconds(50), "50sec") | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(1), "1second") | 	assertPDSSEqual(t, FromSeconds(1), "1second") | ||||||
| 	tst.AssertPDSSEqual(t, FromSeconds(50), "50seconds") | 	assertPDSSEqual(t, FromSeconds(50), "50seconds") | ||||||
|  |  | ||||||
| 	tst.AssertPDSSEqual(t, FromMinutes(10), "10m") | 	assertPDSSEqual(t, FromMinutes(10), "10m") | ||||||
| 	tst.AssertPDSSEqual(t, FromMinutes(10), "10min") | 	assertPDSSEqual(t, FromMinutes(10), "10min") | ||||||
| 	tst.AssertPDSSEqual(t, FromMinutes(1), "1minute") | 	assertPDSSEqual(t, FromMinutes(1), "1minute") | ||||||
| 	tst.AssertPDSSEqual(t, FromMinutes(10), "10minutes") | 	assertPDSSEqual(t, FromMinutes(10), "10minutes") | ||||||
| 	tst.AssertPDSSEqual(t, FromMinutes(10.5), "10.5minutes") | 	assertPDSSEqual(t, FromMinutes(10.5), "10.5minutes") | ||||||
|  |  | ||||||
| 	tst.AssertPDSSEqual(t, FromMilliseconds(100), "100ms") | 	assertPDSSEqual(t, FromMilliseconds(100), "100ms") | ||||||
| 	tst.AssertPDSSEqual(t, FromMilliseconds(100), "100milliseconds") | 	assertPDSSEqual(t, FromMilliseconds(100), "100milliseconds") | ||||||
| 	tst.AssertPDSSEqual(t, FromMilliseconds(100), "100millisecond") | 	assertPDSSEqual(t, FromMilliseconds(100), "100millisecond") | ||||||
|  |  | ||||||
| 	tst.AssertPDSSEqual(t, FromNanoseconds(99235), "99235ns") | 	assertPDSSEqual(t, FromNanoseconds(99235), "99235ns") | ||||||
| 	tst.AssertPDSSEqual(t, FromNanoseconds(99235), "99235nanoseconds") | 	assertPDSSEqual(t, FromNanoseconds(99235), "99235nanoseconds") | ||||||
| 	tst.AssertPDSSEqual(t, FromNanoseconds(99235), "99235nanosecond") | 	assertPDSSEqual(t, FromNanoseconds(99235), "99235nanosecond") | ||||||
|  |  | ||||||
| 	tst.AssertPDSSEqual(t, FromMicroseconds(99235), "99235us") | 	assertPDSSEqual(t, FromMicroseconds(99235), "99235us") | ||||||
| 	tst.AssertPDSSEqual(t, FromMicroseconds(99235), "99235microseconds") | 	assertPDSSEqual(t, FromMicroseconds(99235), "99235microseconds") | ||||||
| 	tst.AssertPDSSEqual(t, FromMicroseconds(99235), "99235microsecond") | 	assertPDSSEqual(t, FromMicroseconds(99235), "99235microsecond") | ||||||
|  |  | ||||||
| 	tst.AssertPDSSEqual(t, FromHours(1), "1h") | 	assertPDSSEqual(t, FromHours(1), "1h") | ||||||
| 	tst.AssertPDSSEqual(t, FromHours(1), "1hour") | 	assertPDSSEqual(t, FromHours(1), "1hour") | ||||||
| 	tst.AssertPDSSEqual(t, FromHours(2), "2hours") | 	assertPDSSEqual(t, FromHours(2), "2hours") | ||||||
|  |  | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(1), "1d") | 	assertPDSSEqual(t, FromDays(1), "1d") | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(1), "1day") | 	assertPDSSEqual(t, FromDays(1), "1day") | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(10), "10days") | 	assertPDSSEqual(t, FromDays(10), "10days") | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(1), "1days") | 	assertPDSSEqual(t, FromDays(1), "1days") | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(10), "10day") | 	assertPDSSEqual(t, FromDays(10), "10day") | ||||||
|  |  | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d10m") | 	assertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d10m") | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(1)+FromMinutes(10)+FromSeconds(200), "1d10m200sec") | 	assertPDSSEqual(t, FromDays(1)+FromMinutes(10)+FromSeconds(200), "1d10m200sec") | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d:10m") | 	assertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d:10m") | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d 10m") | 	assertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d 10m") | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d,10m") | 	assertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d,10m") | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d, 10m") | 	assertPDSSEqual(t, FromDays(1)+FromMinutes(10), "1d, 10m") | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(1)+FromSeconds(1000), "1d   1000seconds") | 	assertPDSSEqual(t, FromDays(1)+FromSeconds(1000), "1d   1000seconds") | ||||||
|  |  | ||||||
| 	tst.AssertPDSSEqual(t, FromDays(1), "86400s") | 	assertPDSSEqual(t, FromDays(1), "86400s") | ||||||
| } | } | ||||||
|  |  | ||||||
| func assertPDSSEqual(t *testing.T, expected time.Duration, fmt string) { | func assertPDSSEqual(t *testing.T, expected time.Duration, fmt string) { | ||||||
|   | |||||||
| @@ -22,6 +22,17 @@ func TimeToDatePart(t time.Time, tz *time.Location) time.Time { | |||||||
| 	return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) | 	return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TimeToDayStart returns a timestamp at the start of the day which contains t (= 00:00:00) | ||||||
|  | func TimeToDayStart(t time.Time, tz *time.Location) time.Time { | ||||||
|  | 	t = t.In(tz) | ||||||
|  | 	return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TimeToDayEnd returns a timestamp at the end of the day which contains t (= 23:59:59) | ||||||
|  | func TimeToDayEnd(t time.Time, tz *time.Location) time.Time { | ||||||
|  | 	return TimeToDayStart(t, tz).AddDate(0, 0, 1).Add(-1) | ||||||
|  | } | ||||||
|  |  | ||||||
| // TimeToWeekStart returns a timestamp at the start of the week which contains t (= Monday 00:00:00) | // TimeToWeekStart returns a timestamp at the start of the week which contains t (= Monday 00:00:00) | ||||||
| func TimeToWeekStart(t time.Time, tz *time.Location) time.Time { | func TimeToWeekStart(t time.Time, tz *time.Location) time.Time { | ||||||
| 	t = TimeToDatePart(t, tz) | 	t = TimeToDatePart(t, tz) | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import ( | |||||||
| 	"go.mongodb.org/mongo-driver/bson/bsontype" | 	"go.mongodb.org/mongo-driver/bson/bsontype" | ||||||
| 	"go.mongodb.org/mongo-driver/mongo" | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
| 	ct "gogs.mikescher.com/BlackForestBytes/goext/cursortoken" | 	ct "gogs.mikescher.com/BlackForestBytes/goext/cursortoken" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| ) | ) | ||||||
| @@ -66,21 +67,25 @@ func (c *Coll[TData]) Indexes() mongo.IndexView { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) Drop(ctx context.Context) error { | func (c *Coll[TData]) Drop(ctx context.Context) error { | ||||||
| 	return c.coll.Drop(ctx) | 	err := c.coll.Drop(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return exerr.Wrap(err, "failed to drop collection").Str("collection", c.Name()).Build() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) createToken(fieldPrimary string, dirPrimary ct.SortDirection, fieldSecondary *string, dirSecondary *ct.SortDirection, lastEntity TData, pageSize *int) (ct.CursorToken, error) { | func (c *Coll[TData]) createToken(fieldPrimary string, dirPrimary ct.SortDirection, fieldSecondary *string, dirSecondary *ct.SortDirection, lastEntity TData, pageSize *int) (ct.CursorToken, error) { | ||||||
|  |  | ||||||
| 	valuePrimary, err := c.getFieldValueAsTokenString(lastEntity, fieldPrimary) | 	valuePrimary, err := c.getFieldValueAsTokenString(lastEntity, fieldPrimary) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return ct.CursorToken{}, err | 		return ct.CursorToken{}, exerr.Wrap(err, "failed to get (primary) field-value as token-string").Type("lastEntity", lastEntity).Str("fieldPrimary", fieldPrimary).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	valueSeconary := "" | 	valueSeconary := "" | ||||||
| 	if fieldSecondary != nil && dirSecondary != nil { | 	if fieldSecondary != nil && dirSecondary != nil { | ||||||
| 		valueSeconary, err = c.getFieldValueAsTokenString(lastEntity, *fieldSecondary) | 		valueSeconary, err = c.getFieldValueAsTokenString(lastEntity, *fieldSecondary) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return ct.CursorToken{}, err | 			return ct.CursorToken{}, exerr.Wrap(err, "failed to get (secondary) field-value as token-string").Type("lastEntity", lastEntity).StrPtr("fieldSecondary", fieldSecondary).Build() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,12 +2,17 @@ package wmo | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Coll[TData]) decodeSingle(ctx context.Context, dec Decodable) (TData, error) { | func (c *Coll[TData]) decodeSingle(ctx context.Context, dec Decodable) (TData, error) { | ||||||
| 	if c.customDecoder != nil { | 	if c.customDecoder != nil { | ||||||
|  |  | ||||||
| 		return (*c.customDecoder)(ctx, dec) | 		res, err := (*c.customDecoder)(ctx, dec) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return *new(TData), exerr.Wrap(err, "failed to decode single entity with custom-decoder").Type("decoder", *c.customDecoder).Build() | ||||||
|  | 		} | ||||||
|  | 		return res, nil | ||||||
|  |  | ||||||
| 	} else { | 	} else { | ||||||
|  |  | ||||||
| @@ -15,7 +20,7 @@ func (c *Coll[TData]) decodeSingle(ctx context.Context, dec Decodable) (TData, e | |||||||
|  |  | ||||||
| 		err := dec.Decode(&res) | 		err := dec.Decode(&res) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return *new(TData), err | 			return *new(TData), exerr.Wrap(err, "failed to decode single entity").Type("target-type", res).Build() | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return res, nil | 		return res, nil | ||||||
| @@ -31,7 +36,7 @@ func (c *Coll[TData]) decodeAll(ctx context.Context, cursor Cursorable) ([]TData | |||||||
| 		for cursor.Next(ctx) { | 		for cursor.Next(ctx) { | ||||||
| 			entry, err := (*c.customDecoder)(ctx, cursor) | 			entry, err := (*c.customDecoder)(ctx, cursor) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, exerr.Wrap(err, "failed to decode entity with custom-decoder").Type("decoder", *c.customDecoder).Build() | ||||||
| 			} | 			} | ||||||
| 			res = append(res, entry) | 			res = append(res, entry) | ||||||
| 		} | 		} | ||||||
| @@ -44,7 +49,7 @@ func (c *Coll[TData]) decodeAll(ctx context.Context, cursor Cursorable) ([]TData | |||||||
|  |  | ||||||
| 		err := cursor.All(ctx, &res) | 		err := cursor.All(ctx, &res) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, exerr.Wrap(err, "failed to batch-decode entity").Type("target-type", res).Build() | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return res, nil | 		return res, nil | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| package wmo | package wmo | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" |  | ||||||
| 	"go.mongodb.org/mongo-driver/bson" | 	"go.mongodb.org/mongo-driver/bson" | ||||||
| 	ct "gogs.mikescher.com/BlackForestBytes/goext/cursortoken" | 	ct "gogs.mikescher.com/BlackForestBytes/goext/cursortoken" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func CreatePagination[TData any](coll *Coll[TData], token ct.CursorToken, fieldPrimary string, sortPrimary ct.SortDirection, fieldSecondary *string, sortSecondary *ct.SortDirection, pageSize *int) ([]bson.D, error) { | func CreatePagination[TData any](coll *Coll[TData], token ct.CursorToken, fieldPrimary string, sortPrimary ct.SortDirection, fieldSecondary *string, sortSecondary *ct.SortDirection, pageSize *int) ([]bson.D, error) { | ||||||
| @@ -13,7 +13,7 @@ func CreatePagination[TData any](coll *Coll[TData], token ct.CursorToken, fieldP | |||||||
|  |  | ||||||
| 	valuePrimary, err := coll.getTokenValueAsMongoType(token.ValuePrimary, fieldPrimary) | 	valuePrimary, err := coll.getTokenValueAsMongoType(token.ValuePrimary, fieldPrimary) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, exerr.Wrap(err, "failed to get (primary) token-value as mongo-type").Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if sortPrimary == ct.SortASC { | 	if sortPrimary == ct.SortASC { | ||||||
| @@ -30,7 +30,7 @@ func CreatePagination[TData any](coll *Coll[TData], token ct.CursorToken, fieldP | |||||||
|  |  | ||||||
| 		valueSecondary, err := coll.getTokenValueAsMongoType(token.ValueSecondary, *fieldSecondary) | 		valueSecondary, err := coll.getTokenValueAsMongoType(token.ValueSecondary, *fieldSecondary) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, exerr.Wrap(err, "failed to get (secondary) token-value as mongo-type").Build() | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if *sortSecondary == ct.SortASC { | 		if *sortSecondary == ct.SortASC { | ||||||
| @@ -73,7 +73,7 @@ func CreatePagination[TData any](coll *Coll[TData], token ct.CursorToken, fieldP | |||||||
|  |  | ||||||
| 	} else { | 	} else { | ||||||
|  |  | ||||||
| 		return nil, errors.New("unknown ct mode: " + string(token.Mode)) | 		return nil, exerr.New(exerr.TypeInternal, "unknown ct mode: "+string(token.Mode)).Any("token.Mode", token.Mode).Build() | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,20 +2,20 @@ package wmo | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" |  | ||||||
| 	"go.mongodb.org/mongo-driver/mongo" | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
| 	"go.mongodb.org/mongo-driver/mongo/options" | 	"go.mongodb.org/mongo-driver/mongo/options" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Coll[TData]) Aggregate(ctx context.Context, pipeline mongo.Pipeline, opts ...*options.AggregateOptions) ([]TData, error) { | func (c *Coll[TData]) Aggregate(ctx context.Context, pipeline mongo.Pipeline, opts ...*options.AggregateOptions) ([]TData, error) { | ||||||
| 	cursor, err := c.coll.Aggregate(ctx, pipeline, opts...) | 	cursor, err := c.coll.Aggregate(ctx, pipeline, opts...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, exerr.Wrap(err, "mongo-aggregation failed").Any("pipeline", pipeline).Any("options", opts).Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	res, err := c.decodeAll(ctx, cursor) | 	res, err := c.decodeAll(ctx, cursor) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, exerr.Wrap(err, "failed to decode values").Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return res, nil | 	return res, nil | ||||||
| @@ -24,13 +24,13 @@ func (c *Coll[TData]) Aggregate(ctx context.Context, pipeline mongo.Pipeline, op | |||||||
| func (c *Coll[TData]) AggregateOneOpt(ctx context.Context, pipeline mongo.Pipeline, opts ...*options.AggregateOptions) (*TData, error) { | func (c *Coll[TData]) AggregateOneOpt(ctx context.Context, pipeline mongo.Pipeline, opts ...*options.AggregateOptions) (*TData, error) { | ||||||
| 	cursor, err := c.coll.Aggregate(ctx, pipeline, opts...) | 	cursor, err := c.coll.Aggregate(ctx, pipeline, opts...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, exerr.Wrap(err, "mongo-aggregation failed").Any("pipeline", pipeline).Any("options", opts).Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if cursor.Next(ctx) { | 	if cursor.Next(ctx) { | ||||||
| 		v, err := c.decodeSingle(ctx, cursor) | 		v, err := c.decodeSingle(ctx, cursor) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, exerr.Wrap(err, "failed to decode single value").Build() | ||||||
| 		} | 		} | ||||||
| 		return &v, nil | 		return &v, nil | ||||||
| 	} | 	} | ||||||
| @@ -41,16 +41,16 @@ func (c *Coll[TData]) AggregateOneOpt(ctx context.Context, pipeline mongo.Pipeli | |||||||
| func (c *Coll[TData]) AggregateOne(ctx context.Context, pipeline mongo.Pipeline, opts ...*options.AggregateOptions) (TData, error) { | func (c *Coll[TData]) AggregateOne(ctx context.Context, pipeline mongo.Pipeline, opts ...*options.AggregateOptions) (TData, error) { | ||||||
| 	cursor, err := c.coll.Aggregate(ctx, pipeline, opts...) | 	cursor, err := c.coll.Aggregate(ctx, pipeline, opts...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return *new(TData), err | 		return *new(TData), exerr.Wrap(err, "mongo-aggregation failed").Any("pipeline", pipeline).Any("options", opts).Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if cursor.Next(ctx) { | 	if cursor.Next(ctx) { | ||||||
| 		v, err := c.decodeSingle(ctx, cursor) | 		v, err := c.decodeSingle(ctx, cursor) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return *new(TData), err | 			return *new(TData), exerr.Wrap(err, "failed to decode single value").Build() | ||||||
| 		} | 		} | ||||||
| 		return v, nil | 		return v, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return *new(TData), errors.New("no document in result") | 	return *new(TData), exerr.Wrap(mongo.ErrNoDocuments, "no document in result").Build() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,12 +4,13 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"go.mongodb.org/mongo-driver/bson" | 	"go.mongodb.org/mongo-driver/bson" | ||||||
| 	"go.mongodb.org/mongo-driver/mongo" | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Coll[TData]) DeleteOneByID(ctx context.Context, id EntityID) error { | func (c *Coll[TData]) DeleteOneByID(ctx context.Context, id EntityID) error { | ||||||
| 	_, err := c.coll.DeleteOne(ctx, bson.M{"_id": id}) | 	_, err := c.coll.DeleteOne(ctx, bson.M{"_id": id}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return exerr.Wrap(err, "mongo-query[delete-one-by-id] failed").Id("id", id).Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -18,7 +19,7 @@ func (c *Coll[TData]) DeleteOneByID(ctx context.Context, id EntityID) error { | |||||||
| func (c *Coll[TData]) DeleteOne(ctx context.Context, filterQuery bson.M) error { | func (c *Coll[TData]) DeleteOne(ctx context.Context, filterQuery bson.M) error { | ||||||
| 	_, err := c.coll.DeleteOne(ctx, filterQuery) | 	_, err := c.coll.DeleteOne(ctx, filterQuery) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return exerr.Wrap(err, "mongo-query[delete-one] failed").Any("filterQuery", filterQuery).Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -27,7 +28,7 @@ func (c *Coll[TData]) DeleteOne(ctx context.Context, filterQuery bson.M) error { | |||||||
| func (c *Coll[TData]) DeleteMany(ctx context.Context, filterQuery bson.M) (*mongo.DeleteResult, error) { | func (c *Coll[TData]) DeleteMany(ctx context.Context, filterQuery bson.M) (*mongo.DeleteResult, error) { | ||||||
| 	res, err := c.coll.DeleteMany(ctx, filterQuery) | 	res, err := c.coll.DeleteMany(ctx, filterQuery) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, exerr.Wrap(err, "mongo-query[delete-many] failed").Any("filterQuery", filterQuery).Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return res, nil | 	return res, nil | ||||||
|   | |||||||
| @@ -2,13 +2,21 @@ package wmo | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"errors" | ||||||
| 	"go.mongodb.org/mongo-driver/bson" | 	"go.mongodb.org/mongo-driver/bson" | ||||||
| 	"go.mongodb.org/mongo-driver/mongo" | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
| 	"go.mongodb.org/mongo-driver/mongo/options" | 	"go.mongodb.org/mongo-driver/mongo/options" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Coll[TData]) FindOne(ctx context.Context, filter bson.M) (TData, error) { | func (c *Coll[TData]) FindOne(ctx context.Context, filter bson.M) (TData, error) { | ||||||
| 	mongoRes := c.coll.FindOne(ctx, filter) | 	mongoRes := c.coll.FindOne(ctx, filter) | ||||||
|  | 	if err := mongoRes.Err(); err != nil { | ||||||
|  | 		return *new(TData), exerr.Wrap(err, "mongo-query[find-one] failed"). | ||||||
|  | 			Str("collection", c.Name()). | ||||||
|  | 			Any("filter", filter). | ||||||
|  | 			Build() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return c.decodeSingle(ctx, mongoRes) | 	return c.decodeSingle(ctx, mongoRes) | ||||||
| } | } | ||||||
| @@ -17,11 +25,11 @@ func (c *Coll[TData]) FindOneOpt(ctx context.Context, filter bson.M) (*TData, er | |||||||
| 	mongoRes := c.coll.FindOne(ctx, filter) | 	mongoRes := c.coll.FindOne(ctx, filter) | ||||||
|  |  | ||||||
| 	res, err := c.decodeSingle(ctx, mongoRes) | 	res, err := c.decodeSingle(ctx, mongoRes) | ||||||
| 	if err == mongo.ErrNoDocuments { | 	if errors.Is(err, mongo.ErrNoDocuments) { | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, exerr.Wrap(err, "mongo-query[find-one-opt] failed").Any("filter", filter).Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return &res, nil | 	return &res, nil | ||||||
| @@ -29,6 +37,12 @@ func (c *Coll[TData]) FindOneOpt(ctx context.Context, filter bson.M) (*TData, er | |||||||
|  |  | ||||||
| func (c *Coll[TData]) FindOneByID(ctx context.Context, id EntityID) (TData, error) { | func (c *Coll[TData]) FindOneByID(ctx context.Context, id EntityID) (TData, error) { | ||||||
| 	mongoRes := c.coll.FindOne(ctx, bson.M{"_id": id}) | 	mongoRes := c.coll.FindOne(ctx, bson.M{"_id": id}) | ||||||
|  | 	if err := mongoRes.Err(); err != nil { | ||||||
|  | 		return *new(TData), exerr.Wrap(err, "mongo-query[find-one-by-id] failed"). | ||||||
|  | 			Str("collection", c.Name()). | ||||||
|  | 			Id("id", id). | ||||||
|  | 			Build() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return c.decodeSingle(ctx, mongoRes) | 	return c.decodeSingle(ctx, mongoRes) | ||||||
| } | } | ||||||
| @@ -37,11 +51,11 @@ func (c *Coll[TData]) FindOneOptByID(ctx context.Context, id EntityID) (*TData, | |||||||
| 	mongoRes := c.coll.FindOne(ctx, bson.M{"_id": id}) | 	mongoRes := c.coll.FindOne(ctx, bson.M{"_id": id}) | ||||||
|  |  | ||||||
| 	res, err := c.decodeSingle(ctx, mongoRes) | 	res, err := c.decodeSingle(ctx, mongoRes) | ||||||
| 	if err == mongo.ErrNoDocuments { | 	if errors.Is(err, mongo.ErrNoDocuments) { | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, exerr.Wrap(err, "mongo-query[find-one-opt-by-id] failed").Id("id", id).Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return &res, nil | 	return &res, nil | ||||||
| @@ -50,12 +64,12 @@ func (c *Coll[TData]) FindOneOptByID(ctx context.Context, id EntityID) (*TData, | |||||||
| func (c *Coll[TData]) Find(ctx context.Context, filter bson.M, opts ...*options.FindOptions) ([]TData, error) { | func (c *Coll[TData]) Find(ctx context.Context, filter bson.M, opts ...*options.FindOptions) ([]TData, error) { | ||||||
| 	cursor, err := c.coll.Find(ctx, filter, opts...) | 	cursor, err := c.coll.Find(ctx, filter, opts...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, exerr.Wrap(err, "mongo-query[find-one-opt] failed").Any("filter", filter).Any("opts", opts).Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	res, err := c.decodeAll(ctx, cursor) | 	res, err := c.decodeAll(ctx, cursor) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, exerr.Wrap(err, "failed to decode values").Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return res, nil | 	return res, nil | ||||||
|   | |||||||
| @@ -4,13 +4,14 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"go.mongodb.org/mongo-driver/bson" | 	"go.mongodb.org/mongo-driver/bson" | ||||||
| 	"go.mongodb.org/mongo-driver/mongo" | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Coll[TData]) InsertOne(ctx context.Context, valueIn TData) (TData, error) { | func (c *Coll[TData]) InsertOne(ctx context.Context, valueIn TData) (TData, error) { | ||||||
| 	insRes, err := c.coll.InsertOne(ctx, valueIn) | 	insRes, err := c.coll.InsertOne(ctx, valueIn) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return *new(TData), err | 		return *new(TData), exerr.Wrap(err, "mongo-query[insert-one] failed").Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mongoRes := c.coll.FindOne(ctx, bson.M{"_id": insRes.InsertedID}) | 	mongoRes := c.coll.FindOne(ctx, bson.M{"_id": insRes.InsertedID}) | ||||||
| @@ -19,5 +20,10 @@ func (c *Coll[TData]) InsertOne(ctx context.Context, valueIn TData) (TData, erro | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) InsertMany(ctx context.Context, valueIn []TData) (*mongo.InsertManyResult, error) { | func (c *Coll[TData]) InsertMany(ctx context.Context, valueIn []TData) (*mongo.InsertManyResult, error) { | ||||||
| 	return c.coll.InsertMany(ctx, langext.ArrayToInterface(valueIn)) | 	insRes, err := c.coll.InsertMany(ctx, langext.ArrayToInterface(valueIn)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, exerr.Wrap(err, "mongo-query[insert-many] failed").Int("len(valueIn)", len(valueIn)).Str("collection", c.Name()).Build() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return insRes, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import ( | |||||||
| 	"go.mongodb.org/mongo-driver/bson" | 	"go.mongodb.org/mongo-driver/bson" | ||||||
| 	"go.mongodb.org/mongo-driver/mongo" | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
| 	ct "gogs.mikescher.com/BlackForestBytes/goext/cursortoken" | 	ct "gogs.mikescher.com/BlackForestBytes/goext/cursortoken" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Coll[TData]) List(ctx context.Context, filter ct.Filter, pageSize *int, inTok ct.CursorToken) ([]TData, ct.CursorToken, error) { | func (c *Coll[TData]) List(ctx context.Context, filter ct.Filter, pageSize *int, inTok ct.CursorToken) ([]TData, ct.CursorToken, error) { | ||||||
| @@ -35,21 +36,31 @@ func (c *Coll[TData]) List(ctx context.Context, filter ct.Filter, pageSize *int, | |||||||
|  |  | ||||||
| 	paginationPipeline, err := CreatePagination(c, inTok, sortPrimary, sortDirPrimary, sortSecondary, sortDirSecondary, pageSize) | 	paginationPipeline, err := CreatePagination(c, inTok, sortPrimary, sortDirPrimary, sortSecondary, sortDirSecondary, pageSize) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, ct.CursorToken{}, err | 		return nil, ct.CursorToken{}, exerr. | ||||||
|  | 			Wrap(err, "failed to create pagination"). | ||||||
|  | 			WithType(exerr.TypeCursorTokenDecode). | ||||||
|  | 			Str("collection", c.Name()). | ||||||
|  | 			Any("inTok", inTok). | ||||||
|  | 			Any("sortPrimary", sortPrimary). | ||||||
|  | 			Any("sortDirPrimary", sortDirPrimary). | ||||||
|  | 			Any("sortSecondary", sortSecondary). | ||||||
|  | 			Any("sortDirSecondary", sortDirSecondary). | ||||||
|  | 			Any("pageSize", pageSize). | ||||||
|  | 			Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pipeline = append(pipeline, paginationPipeline...) | 	pipeline = append(pipeline, paginationPipeline...) | ||||||
|  |  | ||||||
| 	cursor, err := c.coll.Aggregate(ctx, pipeline) | 	cursor, err := c.coll.Aggregate(ctx, pipeline) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, ct.CursorToken{}, err | 		return nil, ct.CursorToken{}, exerr.Wrap(err, "mongo-aggregation failed").Any("pipeline", pipeline).Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// fast branch | 	// fast branch | ||||||
| 	if pageSize == nil { | 	if pageSize == nil { | ||||||
| 		entries, err := c.decodeAll(ctx, cursor) | 		entries, err := c.decodeAll(ctx, cursor) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, ct.CursorToken{}, err | 			return nil, ct.CursorToken{}, exerr.Wrap(err, "failed to all-decode entities").Build() | ||||||
| 		} | 		} | ||||||
| 		return entries, ct.End(), nil | 		return entries, ct.End(), nil | ||||||
| 	} | 	} | ||||||
| @@ -59,7 +70,7 @@ func (c *Coll[TData]) List(ctx context.Context, filter ct.Filter, pageSize *int, | |||||||
| 		var entry TData | 		var entry TData | ||||||
| 		entry, err = c.decodeSingle(ctx, cursor) | 		entry, err = c.decodeSingle(ctx, cursor) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, ct.CursorToken{}, err | 			return nil, ct.CursorToken{}, exerr.Wrap(err, "failed to decode entity").Build() | ||||||
| 		} | 		} | ||||||
| 		entities = append(entities, entry) | 		entities = append(entities, entry) | ||||||
| 	} | 	} | ||||||
| @@ -74,7 +85,7 @@ func (c *Coll[TData]) List(ctx context.Context, filter ct.Filter, pageSize *int, | |||||||
|  |  | ||||||
| 	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{}, exerr.Wrap(err, "failed to create (out)-token").Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return entities, nextToken, nil | 	return entities, nextToken, nil | ||||||
| @@ -91,14 +102,14 @@ func (c *Coll[TData]) Count(ctx context.Context, filter ct.Filter) (int64, error | |||||||
|  |  | ||||||
| 	cursor, err := c.coll.Aggregate(ctx, pipeline) | 	cursor, err := c.coll.Aggregate(ctx, pipeline) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return 0, err | 		return 0, exerr.Wrap(err, "mongo-aggregation failed").Any("pipeline", pipeline).Str("collection", c.Name()).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if cursor.Next(ctx) { | 	if cursor.Next(ctx) { | ||||||
| 		v := countRes{} | 		v := countRes{} | ||||||
| 		err = cursor.Decode(&v) | 		err = cursor.Decode(&v) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return 0, err | 			return 0, exerr.Wrap(err, "failed to decode entity").Build() | ||||||
| 		} | 		} | ||||||
| 		return v.Count, nil | 		return v.Count, nil | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -5,10 +5,18 @@ import ( | |||||||
| 	"go.mongodb.org/mongo-driver/bson" | 	"go.mongodb.org/mongo-driver/bson" | ||||||
| 	"go.mongodb.org/mongo-driver/mongo" | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
| 	"go.mongodb.org/mongo-driver/mongo/options" | 	"go.mongodb.org/mongo-driver/mongo/options" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Coll[TData]) FindOneAndUpdate(ctx context.Context, filterQuery bson.M, updateQuery bson.M) (TData, error) { | func (c *Coll[TData]) FindOneAndUpdate(ctx context.Context, filterQuery bson.M, updateQuery bson.M) (TData, error) { | ||||||
| 	mongoRes := c.coll.FindOneAndUpdate(ctx, filterQuery, updateQuery, options.FindOneAndUpdate().SetReturnDocument(options.After)) | 	mongoRes := c.coll.FindOneAndUpdate(ctx, filterQuery, updateQuery, options.FindOneAndUpdate().SetReturnDocument(options.After)) | ||||||
|  | 	if err := mongoRes.Err(); err != nil { | ||||||
|  | 		return *new(TData), exerr.Wrap(err, "mongo-query[find-one-and-update] failed"). | ||||||
|  | 			Str("collection", c.Name()). | ||||||
|  | 			Any("filterQuery", filterQuery). | ||||||
|  | 			Any("updateQuery", updateQuery). | ||||||
|  | 			Build() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return c.decodeSingle(ctx, mongoRes) | 	return c.decodeSingle(ctx, mongoRes) | ||||||
| } | } | ||||||
| @@ -16,7 +24,11 @@ func (c *Coll[TData]) FindOneAndUpdate(ctx context.Context, filterQuery bson.M, | |||||||
| func (c *Coll[TData]) UpdateOne(ctx context.Context, filterQuery bson.M, updateQuery bson.M) error { | func (c *Coll[TData]) UpdateOne(ctx context.Context, filterQuery bson.M, updateQuery bson.M) error { | ||||||
| 	_, err := c.coll.UpdateOne(ctx, filterQuery, updateQuery) | 	_, err := c.coll.UpdateOne(ctx, filterQuery, updateQuery) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return exerr.Wrap(err, "mongo-query[update-one] failed"). | ||||||
|  | 			Str("collection", c.Name()). | ||||||
|  | 			Any("filterQuery", filterQuery). | ||||||
|  | 			Any("updateQuery", updateQuery). | ||||||
|  | 			Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -25,7 +37,11 @@ func (c *Coll[TData]) UpdateOne(ctx context.Context, filterQuery bson.M, updateQ | |||||||
| func (c *Coll[TData]) UpdateOneByID(ctx context.Context, id EntityID, updateQuery bson.M) error { | func (c *Coll[TData]) UpdateOneByID(ctx context.Context, id EntityID, updateQuery bson.M) error { | ||||||
| 	_, err := c.coll.UpdateOne(ctx, bson.M{"_id": id}, updateQuery) | 	_, err := c.coll.UpdateOne(ctx, bson.M{"_id": id}, updateQuery) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return exerr.Wrap(err, "mongo-query[update-one-by-id] failed"). | ||||||
|  | 			Str("collection", c.Name()). | ||||||
|  | 			Id("id", id). | ||||||
|  | 			Any("updateQuery", updateQuery). | ||||||
|  | 			Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -34,7 +50,11 @@ func (c *Coll[TData]) UpdateOneByID(ctx context.Context, id EntityID, updateQuer | |||||||
| func (c *Coll[TData]) UpdateMany(ctx context.Context, filterQuery bson.M, updateQuery bson.M) (*mongo.UpdateResult, error) { | func (c *Coll[TData]) UpdateMany(ctx context.Context, filterQuery bson.M, updateQuery bson.M) (*mongo.UpdateResult, error) { | ||||||
| 	res, err := c.coll.UpdateMany(ctx, filterQuery, updateQuery) | 	res, err := c.coll.UpdateMany(ctx, filterQuery, updateQuery) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, exerr.Wrap(err, "mongo-query[update-many] failed"). | ||||||
|  | 			Str("collection", c.Name()). | ||||||
|  | 			Any("filterQuery", filterQuery). | ||||||
|  | 			Any("updateQuery", updateQuery). | ||||||
|  | 			Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return res, nil | 	return res, nil | ||||||
| @@ -43,7 +63,10 @@ func (c *Coll[TData]) UpdateMany(ctx context.Context, filterQuery bson.M, update | |||||||
| func (c *Coll[TData]) ReplaceOne(ctx context.Context, filterQuery bson.M, value TData) error { | func (c *Coll[TData]) ReplaceOne(ctx context.Context, filterQuery bson.M, value TData) error { | ||||||
| 	_, err := c.coll.UpdateOne(ctx, filterQuery, bson.M{"$set": value}) | 	_, err := c.coll.UpdateOne(ctx, filterQuery, bson.M{"$set": value}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return exerr.Wrap(err, "mongo-query[replace-one] failed"). | ||||||
|  | 			Str("collection", c.Name()). | ||||||
|  | 			Any("filterQuery", filterQuery). | ||||||
|  | 			Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -51,6 +74,12 @@ func (c *Coll[TData]) ReplaceOne(ctx context.Context, filterQuery bson.M, value | |||||||
|  |  | ||||||
| func (c *Coll[TData]) FindOneAndReplace(ctx context.Context, filterQuery bson.M, value TData) (TData, error) { | func (c *Coll[TData]) FindOneAndReplace(ctx context.Context, filterQuery bson.M, value TData) (TData, error) { | ||||||
| 	mongoRes := c.coll.FindOneAndUpdate(ctx, filterQuery, bson.M{"$set": value}, options.FindOneAndUpdate().SetReturnDocument(options.After)) | 	mongoRes := c.coll.FindOneAndUpdate(ctx, filterQuery, bson.M{"$set": value}, options.FindOneAndUpdate().SetReturnDocument(options.After)) | ||||||
|  | 	if err := mongoRes.Err(); err != nil { | ||||||
|  | 		return *new(TData), exerr.Wrap(err, "mongo-query[find-one-and-update] failed"). | ||||||
|  | 			Str("collection", c.Name()). | ||||||
|  | 			Any("filterQuery", filterQuery). | ||||||
|  | 			Build() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return c.decodeSingle(ctx, mongoRes) | 	return c.decodeSingle(ctx, mongoRes) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package wmo | package wmo | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/reflectext" | 	"gogs.mikescher.com/BlackForestBytes/goext/reflectext" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| @@ -25,7 +25,7 @@ func (c *Coll[TData]) EnsureInitializedReflection(v TData) { | |||||||
|  |  | ||||||
| 	m := make(map[string]fullTypeRef) | 	m := make(map[string]fullTypeRef) | ||||||
|  |  | ||||||
| 	c.initFields("", rval, 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,20 +50,16 @@ 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, 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, rval reflect.Value, 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) { | ||||||
|  |  | ||||||
| 	rtyp := rval.Type() |  | ||||||
|  |  | ||||||
| 	for i := 0; i < rtyp.NumField(); i++ { | 	for i := 0; i < rtyp.NumField(); i++ { | ||||||
|  |  | ||||||
| 		rsfield := rtyp.Field(i) | 		rsfield := rtyp.Field(i) | ||||||
| 		rvfield := rval.Field(i) |  | ||||||
|  |  | ||||||
| 		if !rsfield.IsExported() { | 		if !rsfield.IsExported() { | ||||||
| 			continue | 			continue | ||||||
| @@ -91,21 +87,21 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, m map[string | |||||||
| 		newIdxArr := langext.ArrCopy(idxarr) | 		newIdxArr := langext.ArrCopy(idxarr) | ||||||
| 		newIdxArr = append(newIdxArr, i) | 		newIdxArr = append(newIdxArr, i) | ||||||
|  |  | ||||||
| 		if langext.InArray("inline", bsontags) && rvfield.Kind() == reflect.Struct { | 		if langext.InArray("inline", bsontags) && rsfield.Type.Kind() == reflect.Struct { | ||||||
|  |  | ||||||
| 			// pass-through field | 			// pass-through field | ||||||
| 			c.initFields(prefix, rvfield, m, newIdxArr) | 			c.initFields(prefix, rsfield.Type, m, newIdxArr, typesInPath) | ||||||
|  |  | ||||||
| 		} else { | 		} else { | ||||||
|  |  | ||||||
| 			if rvfield.Type().Kind() == reflect.Pointer { | 			if rsfield.Type.Kind() == reflect.Pointer { | ||||||
|  |  | ||||||
| 				m[fullKey] = fullTypeRef{ | 				m[fullKey] = fullTypeRef{ | ||||||
| 					IsPointer:      true, | 					IsPointer:      true, | ||||||
| 					RealType:       rvfield.Type(), | 					RealType:       rsfield.Type, | ||||||
| 					Kind:           rvfield.Type().Elem().Kind(), | 					Kind:           rsfield.Type.Elem().Kind(), | ||||||
| 					Type:           rvfield.Type().Elem(), | 					Type:           rsfield.Type.Elem(), | ||||||
| 					UnderlyingType: reflectext.Underlying(rvfield.Type().Elem()), | 					UnderlyingType: reflectext.Underlying(rsfield.Type.Elem()), | ||||||
| 					Name:           rsfield.Name, | 					Name:           rsfield.Name, | ||||||
| 					Index:          newIdxArr, | 					Index:          newIdxArr, | ||||||
| 				} | 				} | ||||||
| @@ -114,20 +110,37 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, m map[string | |||||||
|  |  | ||||||
| 				m[fullKey] = fullTypeRef{ | 				m[fullKey] = fullTypeRef{ | ||||||
| 					IsPointer:      false, | 					IsPointer:      false, | ||||||
| 					RealType:       rvfield.Type(), | 					RealType:       rsfield.Type, | ||||||
| 					Kind:           rvfield.Type().Kind(), | 					Kind:           rsfield.Type.Kind(), | ||||||
| 					Type:           rvfield.Type(), | 					Type:           rsfield.Type, | ||||||
| 					UnderlyingType: reflectext.Underlying(rvfield.Type()), | 					UnderlyingType: reflectext.Underlying(rsfield.Type), | ||||||
| 					Name:           rsfield.Name, | 					Name:           rsfield.Name, | ||||||
| 					Index:          newIdxArr, | 					Index:          newIdxArr, | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if rvfield.Kind() == reflect.Struct { | 			if rsfield.Type.Kind() == reflect.Struct { | ||||||
| 				c.initFields(fullKey+".", rvfield, m, newIdxArr) | 				c.initFields(fullKey+".", rsfield.Type, m, newIdxArr, typesInPath) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			if rsfield.Type.Kind() == reflect.Pointer && rsfield.Type.Elem().Kind() == reflect.Struct { | ||||||
|  | 				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) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| @@ -138,7 +151,7 @@ func (c *Coll[TData]) getTokenValueAsMongoType(value string, fieldName string) ( | |||||||
|  |  | ||||||
| 	fref, err := c.getFieldType(fieldName) | 	fref, err := c.getFieldType(fieldName) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, exerr.Wrap(err, "failed to get-field-type").Str("fieldName", fieldName).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pss := reflectext.PrimitiveStringSerializer{} | 	pss := reflectext.PrimitiveStringSerializer{} | ||||||
| @@ -151,7 +164,7 @@ func (c *Coll[TData]) getFieldValueAsTokenString(entity TData, fieldName string) | |||||||
|  |  | ||||||
| 	realValue, err := c.getFieldValue(entity, fieldName) | 	realValue, err := c.getFieldValue(entity, fieldName) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", exerr.Wrap(err, "failed to get-field-value").Str("fieldName", fieldName).Build() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pss := reflectext.PrimitiveStringSerializer{} | 	pss := reflectext.PrimitiveStringSerializer{} | ||||||
| @@ -169,14 +182,14 @@ func (c *Coll[TData]) getFieldType(fieldName string) (fullTypeRef, error) { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return fullTypeRef{}, errors.New("unknown field: '" + fieldName + "' (in any impl)") | 		return fullTypeRef{}, exerr.New(exerr.TypeMongoReflection, "unknown field: '"+fieldName+"' (in any impl)").Str("fieldName", fieldName).Build() | ||||||
|  |  | ||||||
| 	} else { | 	} else { | ||||||
|  |  | ||||||
| 		if r, ok := c.dataTypeMap[fieldName]; ok { | 		if r, ok := c.dataTypeMap[fieldName]; ok { | ||||||
| 			return r, nil | 			return r, nil | ||||||
| 		} else { | 		} else { | ||||||
| 			return fullTypeRef{}, errors.New("unknown field: '" + fieldName + "'") | 			return fullTypeRef{}, exerr.New(exerr.TypeMongoReflection, "unknown field: '"+fieldName+"'").Str("fieldName", fieldName).Build() | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| @@ -196,10 +209,10 @@ func (c *Coll[TData]) getFieldValue(data TData, fieldName string) (any, error) { | |||||||
| 				rval := reflect.ValueOf(data) | 				rval := reflect.ValueOf(data) | ||||||
| 				return rval.FieldByIndex(fref.Index).Interface(), nil | 				return rval.FieldByIndex(fref.Index).Interface(), nil | ||||||
| 			} else { | 			} else { | ||||||
| 				return nil, errors.New("unknown bson field '" + fieldName + "' in type '" + rval.Type().String() + "'") | 				return nil, exerr.New(exerr.TypeMongoReflection, "unknown bson field '"+fieldName+"' in type '"+rval.Type().String()+"'").Str("fieldName", fieldName).Type("rval", rval).Build() | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			return nil, errors.New("unknown TData type: '" + rval.Type().String() + "'") | 			return nil, exerr.New(exerr.TypeMongoReflection, "unknown TData type: '"+rval.Type().String()+"'").Type("rval", rval).Build() | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	} else { | 	} else { | ||||||
| @@ -208,7 +221,7 @@ func (c *Coll[TData]) getFieldValue(data TData, fieldName string) (any, error) { | |||||||
| 			rval := reflect.ValueOf(data) | 			rval := reflect.ValueOf(data) | ||||||
| 			return rval.FieldByIndex(fref.Index).Interface(), nil | 			return rval.FieldByIndex(fref.Index).Interface(), nil | ||||||
| 		} else { | 		} else { | ||||||
| 			return nil, errors.New("unknown bson field '" + fieldName + "'") | 			return nil, exerr.New(exerr.TypeMongoReflection, "unknown bson field '"+fieldName+"'").Str("fieldName", fieldName).Build() | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -23,6 +23,9 @@ func TestReflectionGetFieldType(t *testing.T) { | |||||||
| 		Sub   struct { | 		Sub   struct { | ||||||
| 			A string `bson:"a"` | 			A string `bson:"a"` | ||||||
| 		} `bson:"sub"` | 		} `bson:"sub"` | ||||||
|  | 		SubPtr *struct { | ||||||
|  | 			A string `bson:"a"` | ||||||
|  | 		} `bson:"subPtr"` | ||||||
| 		Str   string                  `bson:"str"` | 		Str   string                  `bson:"str"` | ||||||
| 		Ptr   *int                    `bson:"ptr"` | 		Ptr   *int                    `bson:"ptr"` | ||||||
| 		MDate rfctime.RFC3339NanoTime `bson:"mdate"` | 		MDate rfctime.RFC3339NanoTime `bson:"mdate"` | ||||||
| @@ -43,6 +46,11 @@ func TestReflectionGetFieldType(t *testing.T) { | |||||||
| 		}{ | 		}{ | ||||||
| 			A: "2", | 			A: "2", | ||||||
| 		}, | 		}, | ||||||
|  | 		SubPtr: &struct { | ||||||
|  | 			A string `bson:"a"` | ||||||
|  | 		}{ | ||||||
|  | 			A: "4", | ||||||
|  | 		}, | ||||||
| 		Str:   "3", | 		Str:   "3", | ||||||
| 		Ptr:   langext.Ptr(4), | 		Ptr:   langext.Ptr(4), | ||||||
| 		MDate: t1, | 		MDate: t1, | ||||||
| @@ -82,6 +90,12 @@ func TestReflectionGetFieldType(t *testing.T) { | |||||||
| 	tst.AssertEqual(t, gft("sub.a").IsPointer, false) | 	tst.AssertEqual(t, gft("sub.a").IsPointer, false) | ||||||
| 	tst.AssertEqual(t, gfv("sub.a").(string), "2") | 	tst.AssertEqual(t, gfv("sub.a").(string), "2") | ||||||
|  |  | ||||||
|  | 	tst.AssertEqual(t, gft("subPtr.a").Kind.String(), "string") | ||||||
|  | 	tst.AssertEqual(t, gft("subPtr.a").Type.String(), "string") | ||||||
|  | 	tst.AssertEqual(t, gft("subPtr.a").Name, "A") | ||||||
|  | 	tst.AssertEqual(t, gft("subPtr.a").IsPointer, false) | ||||||
|  | 	tst.AssertEqual(t, gfv("subPtr.a").(string), "4") | ||||||
|  |  | ||||||
| 	tst.AssertEqual(t, gft("str").Kind.String(), "string") | 	tst.AssertEqual(t, gft("str").Kind.String(), "string") | ||||||
| 	tst.AssertEqual(t, gft("str").Type.String(), "string") | 	tst.AssertEqual(t, gft("str").Type.String(), "string") | ||||||
| 	tst.AssertEqual(t, gft("str").Name, "Str") | 	tst.AssertEqual(t, gft("str").Name, "Str") | ||||||
| @@ -99,16 +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 { | ||||||
|  | 			A string `bson:"a"` | ||||||
|  | 		} `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{}) | ||||||
| @@ -130,6 +153,9 @@ 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", "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) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user