From 45d4fd710162a557d9737ebe25dbcb1f96d57439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Wed, 7 Jun 2023 16:58:17 +0200 Subject: [PATCH] v0.0.147 --- README.md | 57 ++--- _data/version.sh | 2 + go.mod | 24 +- go.sum | 46 ++++ goextVersion.go | 4 +- .../reflection.go => reflectext/casting.go | 2 +- reflectext/primStrSer.go | 136 ++++++++++++ reflectext/primitiveStringSerializer.go | 88 ++++++++ wmo/collection.go | 1 + wmo/pagination.go | 5 + wmo/reflection.go | 208 +----------------- wmo/reflection_test.go | 47 ++-- 12 files changed, 365 insertions(+), 255 deletions(-) rename langext/reflection.go => reflectext/casting.go (99%) create mode 100644 reflectext/primStrSer.go create mode 100644 reflectext/primitiveStringSerializer.go diff --git a/README.md b/README.md index 9ba0cbb..6ea405f 100644 --- a/README.md +++ b/README.md @@ -10,31 +10,32 @@ Potentially needs `export GOPRIVATE="gogs.mikescher.com"` ### Packages: -| Name | Maintainer | Description | -|-------------|------------|---------------------------------------------------------------------------------------------------------------| -| langext | Mike | General uttility/helper functions, (everything thats missing from go standard library) | -| mathext | Mike | Utility/Helper functions for math | -| cryptext | Mike | Utility/Helper functions for encryption | -| syncext | Mike | Utility/Helper funtions for multi-threading / mutex / channels | -| dataext | Mike | Various useful data structures | -| zipext | Mike | Utility for zip/gzip/tar etc | -| | | | -| mongoext | Mike | Utility/Helper functions for mongodb | -| cursortoken | Mike | MongoDB cursortoken implementation | -| | | | -| totpext | Mike | Implementation of TOTP (2-Factor-Auth) | -| termext | Mike | Utilities for terminals (mostly color output) | -| confext | Mike | Parses environment configuration into structs | -| cmdext | Mike | Runner for external commands/processes | -| | | | -| sq | Mike | Utility functions for sql based databases | -| tst | Mike | Utility functions for unit tests | -| | | | -| rfctime | Mike | Classes for time seriallization, with different marshallign method for mongo and json | -| gojson | Mike | Same interface for marshalling/unmarshalling as go/json, except with proper serialization of null arrays/maps | -| | | | -| bfcodegen | Mike | Various codegen tools (run via go generate) | -| | | | -| rext | Mike | Regex Wrapper, wraps regexp with a better interface | -| wmo | Mike | Mongo Wrapper, wraps mongodb with a better interface | -| | | | \ No newline at end of file +| Name | Maintainer | Description | +|--------------|------------|---------------------------------------------------------------------------------------------------------------| +| langext | Mike | General uttility/helper functions, (everything thats missing from go standard library) | +| mathext | Mike | Utility/Helper functions for math | +| cryptext | Mike | Utility/Helper functions for encryption | +| syncext | Mike | Utility/Helper funtions for multi-threading / mutex / channels | +| dataext | Mike | Various useful data structures | +| zipext | Mike | Utility for zip/gzip/tar etc | +| reflectext | Mike | Utility for golagn reflection | +| | | | +| mongoext | Mike | Utility/Helper functions for mongodb | +| cursortoken | Mike | MongoDB cursortoken implementation | +| | | | +| totpext | Mike | Implementation of TOTP (2-Factor-Auth) | +| termext | Mike | Utilities for terminals (mostly color output) | +| confext | Mike | Parses environment configuration into structs | +| cmdext | Mike | Runner for external commands/processes | +| | | | +| sq | Mike | Utility functions for sql based databases | +| tst | Mike | Utility functions for unit tests | +| | | | +| rfctime | Mike | Classes for time seriallization, with different marshallign method for mongo and json | +| gojson | Mike | Same interface for marshalling/unmarshalling as go/json, except with proper serialization of null arrays/maps | +| | | | +| bfcodegen | Mike | Various codegen tools (run via go generate) | +| | | | +| rext | Mike | Regex Wrapper, wraps regexp with a better interface | +| wmo | Mike | Mongo Wrapper, wraps mongodb with a better interface | +| | | | \ No newline at end of file diff --git a/_data/version.sh b/_data/version.sh index 18874e7..31eeb1b 100755 --- a/_data/version.sh +++ b/_data/version.sh @@ -23,6 +23,8 @@ fi git pull --ff +go get -u ./... + curr_vers=$(git describe --tags --abbrev=0 | sed 's/v//g') next_ver=$(echo "$curr_vers" | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++; $NF=sprintf("%0*d", length($NF), ($NF+1)%(10^length($NF))); print}') diff --git a/go.mod b/go.mod index 0eaf9fe..b9da025 100644 --- a/go.mod +++ b/go.mod @@ -4,21 +4,21 @@ go 1.19 require ( github.com/jmoiron/sqlx v1.3.5 - go.mongodb.org/mongo-driver v1.11.1 - golang.org/x/crypto v0.4.0 - golang.org/x/sys v0.3.0 - golang.org/x/term v0.3.0 + go.mongodb.org/mongo-driver v1.11.7 + golang.org/x/crypto v0.9.0 + golang.org/x/sys v0.8.0 + golang.org/x/term v0.8.0 ) require ( - github.com/golang/snappy v0.0.1 // indirect - github.com/klauspost/compress v1.13.6 // indirect - github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/klauspost/compress v1.16.5 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect - github.com/xdg-go/scram v1.1.1 // indirect - github.com/xdg-go/stringprep v1.0.3 // indirect - github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/text v0.5.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/text v0.9.0 // indirect ) diff --git a/go.sum b/go.sum index 9672f74..f70c369 100644 --- a/go.sum +++ b/go.sum @@ -5,12 +5,16 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -22,6 +26,8 @@ github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRU github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -35,31 +41,71 @@ github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8= go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= +go.mongodb.org/mongo-driver v1.11.7 h1:LIwYxASDLGUg/8wOhgOOZhX8tQa/9tgZPgzZoVqJvcs= +go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY= +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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/goextVersion.go b/goextVersion.go index 5dae375..ba3e393 100644 --- a/goextVersion.go +++ b/goextVersion.go @@ -1,5 +1,5 @@ package goext -const GoextVersion = "0.0.146" +const GoextVersion = "0.0.147" -const GoextVersionTimestamp = "2023-06-07T12:59:15+0200" +const GoextVersionTimestamp = "2023-06-07T16:58:17+0200" diff --git a/langext/reflection.go b/reflectext/casting.go similarity index 99% rename from langext/reflection.go rename to reflectext/casting.go index 30e6157..8cdfdca 100644 --- a/langext/reflection.go +++ b/reflectext/casting.go @@ -1,4 +1,4 @@ -package langext +package reflectext import ( "reflect" diff --git a/reflectext/primStrSer.go b/reflectext/primStrSer.go new file mode 100644 index 0000000..5f1c0ff --- /dev/null +++ b/reflectext/primStrSer.go @@ -0,0 +1,136 @@ +package reflectext + +import ( + "errors" + "fmt" + "go.mongodb.org/mongo-driver/bson/primitive" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "reflect" + "strconv" + "strings" + "time" +) + +var primitiveSerializer = map[reflect.Type]genSerializer{ + + reflect.TypeOf(""): newGenSerializer(serStringToString, serStringToString), + + reflect.TypeOf(int(0)): newGenSerializer(serIntNumToString[int], serStringToSIntNum[int]), + reflect.TypeOf(int32(0)): newGenSerializer(serIntNumToString[int32], serStringToSIntNum[int32]), + reflect.TypeOf(int64(0)): newGenSerializer(serIntNumToString[int64], serStringToSIntNum[int64]), + + reflect.TypeOf(uint(0)): newGenSerializer(serIntNumToString[uint], serStringToUIntNum[uint]), + reflect.TypeOf(uint32(0)): newGenSerializer(serIntNumToString[uint32], serStringToUIntNum[uint32]), + reflect.TypeOf(uint64(0)): newGenSerializer(serIntNumToString[uint64], serStringToUIntNum[uint64]), + + reflect.TypeOf(float32(0)): newGenSerializer(serFloatNumToString[float32], serStringToFloatNum[float32]), + reflect.TypeOf(float64(0)): newGenSerializer(serFloatNumToString[float64], serStringToFloatNum[float64]), + + reflect.TypeOf(true): newGenSerializer(serBoolToString, serStringToBool), + + reflect.TypeOf(primitive.ObjectID{}): newGenSerializer(serObjectIDToString, serStringToObjectID), + + reflect.TypeOf(time.Time{}): newGenSerializer(serTimeToString, serStringToTime), +} + +type genSerializer struct { + ToString func(v any) (string, error) + FromString func(v string) (any, error) +} + +func newGenSerializer[TData any](tostr func(v TData) (string, error), fromstr func(v string) (TData, error)) genSerializer { + return genSerializer{ + ToString: func(v any) (string, error) { + if tdv, ok := v.(TData); ok { + rv, err := tostr(tdv) + if err != nil { + return "", err + } + return rv, nil + } else { + return "", errors.New(fmt.Sprintf("cannot convert type %T to TData (%T)", v, *new(TData))) + } + }, + FromString: func(v string) (any, error) { + nv, err := fromstr(v) + if err != nil { + return "", err + } + return nv, nil + }, + } +} + +func serStringToString(v string) (string, error) { + return v, nil +} + +func serIntNumToString[TNum langext.IntegerConstraint](v TNum) (string, error) { + return strconv.FormatInt(int64(v), 10), nil +} + +func serStringToSIntNum[TNum langext.SignedConstraint](v string) (TNum, error) { + r, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return 0, err + } + return TNum(r), nil +} + +func serStringToUIntNum[TNum langext.UnsignedConstraint](v string) (TNum, error) { + r, err := strconv.ParseUint(v, 10, 64) + if err != nil { + return 0, err + } + return TNum(r), nil +} + +func serFloatNumToString[TNum langext.FloatConstraint](v TNum) (string, error) { + return strconv.FormatFloat(float64(v), 'f', -1, 64), nil +} + +func serStringToFloatNum[TNum langext.FloatConstraint](v string) (TNum, error) { + r, err := strconv.ParseFloat(v, 64) + if err != nil { + return 0, err + } + return TNum(r), nil +} + +func serBoolToString(v bool) (string, error) { + return langext.Conditional(v, "true", "false"), nil +} + +func serStringToBool(v string) (bool, error) { + if strings.ToLower(v) == "true" { + return true, nil + } + if strings.ToLower(v) == "false" { + return true, nil + } + return false, errors.New(fmt.Sprintf("invalid boolean value '%s'", v)) +} + +func serObjectIDToString(v primitive.ObjectID) (string, error) { + return v.Hex(), nil +} + +func serStringToObjectID(v string) (primitive.ObjectID, error) { + if rv, err := primitive.ObjectIDFromHex(v); err == nil { + return rv, nil + } else { + return primitive.ObjectID{}, err + } +} + +func serTimeToString(v time.Time) (string, error) { + return v.Format(time.RFC3339Nano), nil +} + +func serStringToTime(v string) (time.Time, error) { + if rv, err := time.Parse(time.RFC3339Nano, v); err == nil { + return rv, nil + } else { + return time.Time{}, err + } +} diff --git a/reflectext/primitiveStringSerializer.go b/reflectext/primitiveStringSerializer.go new file mode 100644 index 0000000..ffceeb9 --- /dev/null +++ b/reflectext/primitiveStringSerializer.go @@ -0,0 +1,88 @@ +package reflectext + +import ( + "errors" + "fmt" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "reflect" +) + +// PrimitiveStringSerializer is used to serialize primitive types (and a few more) from and to string +// This is not really intended to be user facing, and more as a simple building block for other mechanisms +// supports: +// - golang primitives (ints, uints, floats, bool, string) +// - type aliases +// - time.Time +// - primitive.ObjectID +type PrimitiveStringSerializer struct{} + +func (pss PrimitiveStringSerializer) ValueToString(v any) (string, error) { + + inType := reflect.TypeOf(v) + + if inType.Kind() == reflect.Ptr && langext.IsNil(v) { + return "", nil + } + + if inType.Kind() == reflect.Ptr { + rval1 := reflect.ValueOf(v) + rval2 := rval1.Elem() + rval3 := rval2.Interface() + return pss.ValueToString(rval3) + } + + if conv, ok := primitiveSerializer[inType]; ok { + return conv.ToString(v) + } + + for convType, conv := range primitiveSerializer { + if castV, ok := TryCastType(v, convType); ok { + return conv.ToString(castV) + } + } + + return "", errors.New(fmt.Sprintf("failed to find a matching generic conversion fo type %T", v)) +} + +func (pss PrimitiveStringSerializer) ValueFromString(str string, outType reflect.Type) (any, error) { + + if outType.Kind() == reflect.Ptr && str == "" { + return reflect.Zero(outType).Interface(), nil // = nil.(outType), nil + } + + if outType.Kind() == reflect.Ptr { + + innerValue, err := pss.ValueFromString(str, outType.Elem()) + if err != nil { + return nil, err + } + + // this weird piece of shit converts innerValue to &innerValue (while keeping types) + + rval1 := reflect.ValueOf(innerValue) + rval2 := rval1.Convert(outType.Elem()) + rval3 := reflect.New(outType.Elem()) + rval3.Elem().Set(rval2) + rval4 := rval3.Interface() + + return rval4, nil + } + + if conv, ok := primitiveSerializer[outType]; ok { + return conv.FromString(str) + } + + emptyResultVal := reflect.Zero(outType).Interface() + + for convType, conv := range primitiveSerializer { + if _, ok := TryCastType(emptyResultVal, convType); ok { + if convVal, err := conv.FromString(str); err == nil { + if resVal, ok := TryCastType(convVal, outType); ok { + return resVal, nil + } + } + } + } + + return "", errors.New(fmt.Sprintf("failed to find a matching generic conversion fo type %s", outType.String())) +} diff --git a/wmo/collection.go b/wmo/collection.go index 1b28a74..4fb117f 100644 --- a/wmo/collection.go +++ b/wmo/collection.go @@ -13,6 +13,7 @@ type EntityID = any type fullTypeRef[TData any] struct { IsPointer bool Kind reflect.Kind + RealType reflect.Type Type reflect.Type UnderlyingType reflect.Type Name string diff --git a/wmo/pagination.go b/wmo/pagination.go index 2d70c22..afeb0eb 100644 --- a/wmo/pagination.go +++ b/wmo/pagination.go @@ -1,6 +1,7 @@ package wmo import ( + "errors" "go.mongodb.org/mongo-driver/bson" ct "gogs.mikescher.com/BlackForestBytes/goext/cursortoken" ) @@ -70,6 +71,10 @@ func CreatePagination[TData any](coll *Coll[TData], token ct.CursorToken, fieldP // false pipeline = append(pipeline, bson.D{{Key: "$match", Value: bson.M{"$eq": bson.A{"1", "0"}}}}) + } else { + + return nil, errors.New("unknown ct mode: " + string(token.Mode)) + } pipeline = append(pipeline, bson.D{{Key: "$sort", Value: sort}}) diff --git a/wmo/reflection.go b/wmo/reflection.go index 73ab171..7793ac1 100644 --- a/wmo/reflection.go +++ b/wmo/reflection.go @@ -1,12 +1,9 @@ package wmo import ( - "errors" - "fmt" "gogs.mikescher.com/BlackForestBytes/goext/langext" + "gogs.mikescher.com/BlackForestBytes/goext/reflectext" "reflect" - "strconv" - "time" ) func (c *Coll[TData]) init() { @@ -48,9 +45,10 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int c.dataTypeMap[fullKey] = fullTypeRef[TData]{ IsPointer: true, + RealType: rvfield.Type(), Kind: rvfield.Type().Elem().Kind(), Type: rvfield.Type().Elem(), - UnderlyingType: langext.Underlying(rvfield.Type().Elem()), + UnderlyingType: reflectext.Underlying(rvfield.Type().Elem()), Name: rsfield.Name, Index: newIdxArr, } @@ -59,9 +57,10 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int c.dataTypeMap[fullKey] = fullTypeRef[TData]{ IsPointer: false, + RealType: rvfield.Type(), Kind: rvfield.Type().Kind(), Type: rvfield.Type(), - UnderlyingType: langext.Underlying(rvfield.Type()), + UnderlyingType: reflectext.Underlying(rvfield.Type()), Name: rsfield.Name, Index: newIdxArr, } @@ -77,210 +76,23 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int } func (c *Coll[TData]) getTokenValueAsMongoType(value string, fieldName string) (any, error) { + fref := c.dataTypeMap[fieldName] - if fref.IsPointer && value == "" { - pointertype := reflect.New(fref.Type).Type() - nilvalue := reflect.Zero(pointertype) - outvalue := nilvalue.Interface() - return outvalue, nil - } + pss := reflectext.PrimitiveStringSerializer{} - pointerize := func(v any) any { - if !fref.IsPointer { - return v - } + return pss.ValueFromString(value, fref.RealType) - rval1 := reflect.ValueOf(v) - rval2 := rval1.Convert(fref.Type) - rval3 := reflect.New(fref.Type) - rval3.Elem().Set(rval2) - return rval3.Interface() - } - - if fref.UnderlyingType == reflect.TypeOf("") { - - rt, ok := langext.TryCastType(value, fref.Type) - if !ok { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' from string to %s", fieldName, fref.Type.String())) - } - - return pointerize(rt), nil - } - - if fref.UnderlyingType == reflect.TypeOf(time.Time{}) { - - t, err := time.Parse(time.RFC3339Nano, value) - if err != nil { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' as time.Time ('%s')", fieldName, value)) - } - - rt, ok := langext.TryCastType(t, fref.Type) - if !ok { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' from time.Time to %s", fieldName, fref.Type.String())) - } - - return pointerize(rt), nil - } - - if fref.UnderlyingType == reflect.TypeOf(int(0)) { - - t, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' as int64 ('%s')", fieldName, value)) - } - - rt, ok := langext.TryCastType(int(t), fref.Type) - if !ok { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' from int to %s", fieldName, fref.Type.String())) - } - - return pointerize(rt), nil - } - - if fref.UnderlyingType == reflect.TypeOf(int32(0)) { - - t, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' as int32 ('%s')", fieldName, value)) - } - - rt, ok := langext.TryCastType(int32(t), fref.Type) - if !ok { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' from int32 to %s", fieldName, fref.Type.String())) - } - - return pointerize(rt), nil - } - - if fref.UnderlyingType == reflect.TypeOf(int64(0)) { - - t, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' as int64 ('%s')", fieldName, value)) - } - - rt, ok := langext.TryCastType(int64(t), fref.Type) - if !ok { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' from int64 to %s", fieldName, fref.Type.String())) - } - - return pointerize(rt), nil - } - - if fref.UnderlyingType == reflect.TypeOf(float32(0)) { - - t, err := strconv.ParseFloat(value, 64) - if err != nil { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' as float32 ('%s')", fieldName, value)) - } - - rt, ok := langext.TryCastType(float32(t), fref.Type) - if !ok { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' from float32 to %s", fieldName, fref.Type.String())) - } - - return pointerize(rt), nil - } - - if fref.UnderlyingType == reflect.TypeOf(float64(0)) { - - t, err := strconv.ParseFloat(value, 64) - if err != nil { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' as float64 ('%s')", fieldName, value)) - } - - rt, ok := langext.TryCastType(float64(t), fref.Type) - if !ok { - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' from float64 to %s", fieldName, fref.Type.String())) - } - - return pointerize(rt), nil - } - - return nil, errors.New(fmt.Sprintf("failed to parse field '%s' of type %s (%s)", fieldName, fref.Type.String(), fref.UnderlyingType.String())) } func (c *Coll[TData]) getFieldValueAsTokenString(entity TData, fieldName string) (string, error) { - fref := c.dataTypeMap[fieldName] realValue := c.getFieldValue(entity, fieldName) - if langext.IsNil(realValue) { - return "", nil - } + pss := reflectext.PrimitiveStringSerializer{} - reflValue := reflect.ValueOf(realValue) - if reflValue.Kind() == reflect.Pointer { - reflValue = reflValue.Elem() - realValue = reflValue.Interface() - } + return pss.ValueToString(realValue) - if fref.UnderlyingType == reflect.TypeOf("") { - - rt, ok := langext.TryCastType(realValue, reflect.TypeOf("")) - if !ok { - return "", errors.New(fmt.Sprintf("failed to cast field '%s' from %s to string", fieldName, fref.Type.String())) - } - - return rt.(string), nil - } - - if fref.UnderlyingType == reflect.TypeOf(time.Time{}) { - rt, ok := langext.TryCastType(realValue, reflect.TypeOf(time.Time{})) - if !ok { - return "", errors.New(fmt.Sprintf("failed to cast field '%s' from %s to time.Time", fieldName, fref.Type.String())) - } - - return rt.(time.Time).Format(time.RFC3339Nano), nil - } - - if fref.UnderlyingType == reflect.TypeOf(int(0)) { - rt, ok := langext.TryCastType(realValue, reflect.TypeOf(int(0))) - if !ok { - return "", errors.New(fmt.Sprintf("failed to cast field '%s' from %s to int", fieldName, fref.Type.String())) - } - - return strconv.Itoa(rt.(int)), nil - } - - if fref.UnderlyingType == reflect.TypeOf(int32(0)) { - rt, ok := langext.TryCastType(realValue, reflect.TypeOf(int32(0))) - if !ok { - return "", errors.New(fmt.Sprintf("failed to cast field '%s' from %s to int32", fieldName, fref.Type.String())) - } - - return strconv.FormatInt(int64(rt.(int32)), 10), nil - } - - if fref.UnderlyingType == reflect.TypeOf(int64(0)) { - rt, ok := langext.TryCastType(realValue, reflect.TypeOf(int64(0))) - if !ok { - return "", errors.New(fmt.Sprintf("failed to cast field '%s' from %s to int64", fieldName, fref.Type.String())) - } - - return strconv.FormatInt(rt.(int64), 10), nil - } - - if fref.UnderlyingType == reflect.TypeOf(float32(0)) { - rt, ok := langext.TryCastType(realValue, reflect.TypeOf(float32(0))) - if !ok { - return "", errors.New(fmt.Sprintf("failed to cast field '%s' from %s to float32", fieldName, fref.Type.String())) - } - - return strconv.FormatFloat(float64(rt.(float32)), 'f', -1, 32), nil - } - - if fref.UnderlyingType == reflect.TypeOf(float64(0)) { - rt, ok := langext.TryCastType(realValue, reflect.TypeOf(float64(0))) - if !ok { - return "", errors.New(fmt.Sprintf("failed to cast field '%s' from %s to float64", fieldName, fref.Type.String())) - } - - return strconv.FormatFloat(rt.(float64), 'f', -1, 64), nil - } - - return "", errors.New(fmt.Sprintf("failed to parse field '%s' of type %s (%s)", fieldName, fref.Type.String(), fref.UnderlyingType.String())) } func (c *Coll[TData]) getFieldType(fieldName string) fullTypeRef[TData] { diff --git a/wmo/reflection_test.go b/wmo/reflection_test.go index 4267f48..6201510 100644 --- a/wmo/reflection_test.go +++ b/wmo/reflection_test.go @@ -3,6 +3,8 @@ package wmo import ( "go.mongodb.org/mongo-driver/mongo" "gogs.mikescher.com/BlackForestBytes/goext/langext" + "gogs.mikescher.com/BlackForestBytes/goext/rfctime" + "gogs.mikescher.com/BlackForestBytes/goext/timeext" "gogs.mikescher.com/BlackForestBytes/goext/tst" "testing" "time" @@ -18,8 +20,9 @@ func TestReflectionGetFieldType(t *testing.T) { Sub struct { A string `bson:"a"` } `bson:"sub"` - Str string `bson:"str"` - Ptr *int `bson:"ptr"` + Str string `bson:"str"` + Ptr *int `bson:"ptr"` + MDate rfctime.RFC3339NanoTime `bson:"mdate"` } coll := W[TestData](&mongo.Collection{}) @@ -27,6 +30,7 @@ func TestReflectionGetFieldType(t *testing.T) { coll.init() t0 := time.Now() + t1 := rfctime.NewRFC3339Nano(t0) d := TestData{ ID: "1", @@ -36,8 +40,9 @@ func TestReflectionGetFieldType(t *testing.T) { }{ A: "2", }, - Str: "3", - Ptr: langext.Ptr(4), + Str: "3", + Ptr: langext.Ptr(4), + MDate: t1, } tst.AssertEqual(t, coll.getFieldType("_id").Kind.String(), "string") @@ -81,9 +86,10 @@ func TestReflectionGetTokenValueAsMongoType(t *testing.T) { Sub struct { A string `bson:"a"` } `bson:"sub"` - Str string `bson:"str"` - Ptr *int `bson:"ptr"` - Num int `bson:"num"` + Str string `bson:"str"` + Ptr *int `bson:"ptr"` + Num int `bson:"num"` + MDate rfctime.RFC3339NanoTime `bson:"mdate"` } coll := W[TestData](&mongo.Collection{}) @@ -94,15 +100,23 @@ func TestReflectionGetTokenValueAsMongoType(t *testing.T) { v, err := coll.getTokenValueAsMongoType(value, fieldName) if err != nil { t.Errorf("%s", "failed to getTokenValueAsMongoType") + t.Errorf("%v+", err) } return v } + tx, err := time.Parse(time.RFC3339Nano, "2009-11-10T23:00:00Z") + if err != nil { + t.Errorf("%v", err) + } + tst.AssertEqual(t, gtvasmt("hello", "str").(string), "hello") tst.AssertEqual(t, gtvasmt("4", "num").(int), 4) tst.AssertEqual(t, gtvasmt("asdf", "_id").(IDType), "asdf") tst.AssertEqual(t, gtvasmt("", "ptr").(*int), nil) tst.AssertEqual(t, *(gtvasmt("123", "ptr").(*int)), 123) + tst.AssertEqual(t, gtvasmt("2009-11-10T23:00:00Z", "cdate").(time.Time), tx) + tst.AssertEqual(t, gtvasmt("2009-11-10T23:00:00Z", "mdate").(rfctime.RFC3339NanoTime), rfctime.NewRFC3339Nano(tx)) } func TestReflectionGetFieldValueAsTokenString(t *testing.T) { @@ -115,22 +129,25 @@ func TestReflectionGetFieldValueAsTokenString(t *testing.T) { Sub struct { A string `bson:"a"` } `bson:"sub"` - Str string `bson:"str"` - Ptr *int `bson:"ptr"` - Num int `bson:"num"` - Ptr2 *int `bson:"ptr2"` - FFF float64 `bson:"fff"` + Str string `bson:"str"` + Ptr *int `bson:"ptr"` + Num int `bson:"num"` + Ptr2 *int `bson:"ptr2"` + FFF float64 `bson:"fff"` + MDate rfctime.RFC3339NanoTime `bson:"mdate"` } coll := W[TestData](&mongo.Collection{}) coll.init() - t0 := time.Now() + t0 := time.Date(2000, 1, 1, 12, 0, 0, 0, timeext.TimezoneBerlin) + t1 := rfctime.NewRFC3339Nano(t0) d := TestData{ ID: "1", CDate: t0, + MDate: t1, Sub: struct { A string `bson:"a"` }{ @@ -146,7 +163,7 @@ func TestReflectionGetFieldValueAsTokenString(t *testing.T) { gfvats := func(value TestData, fieldName string) string { v, err := coll.getFieldValueAsTokenString(value, fieldName) if err != nil { - t.Errorf("%s", "failed to getTokenValueAsMongoType") + t.Errorf("%s: %v", "failed to getTokenValueAsMongoType", err) } return v } @@ -157,4 +174,6 @@ func TestReflectionGetFieldValueAsTokenString(t *testing.T) { tst.AssertEqual(t, gfvats(d, "ptr"), "4") tst.AssertEqual(t, gfvats(d, "ptr2"), "") tst.AssertEqual(t, gfvats(d, "fff"), "22.5") + tst.AssertEqual(t, gfvats(d, "cdate"), t0.Format(time.RFC3339Nano)) + tst.AssertEqual(t, gfvats(d, "mdate"), t0.Format(time.RFC3339Nano)) }