Compare commits
	
		
			25 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 758e5a67b5 | |||
| 678ddd7124 | |||
| 36b71dfaf3 | |||
| 9491b72b8d | |||
| 6c4af4006b | |||
| 8bf3a337cf | |||
| 16146494dc | |||
| b0e443ad99 | |||
| 9955eacf96 | |||
| f0347a9435 | |||
| 7c869c65f3 | |||
| 14f39a9162 | |||
| dcd106c1cd | |||
| b704e2a362 | |||
| 6b4bd5a6f8 | |||
| 6df4f5f2a1 | |||
| 780905ba35 | |||
| c679797765 | |||
| 401aad9fa4 | |||
| 645113d553 | |||
| 4a33986b6a | |||
| c1c8c64c76 | |||
| 0927fdc4d7 | |||
| 102a280dda | |||
| f13384d794 | 
| @@ -150,6 +150,6 @@ func {{.EnumTypeName}}ValuesDescriptionMeta() []enums.EnumDescriptionMetaValue { | |||||||
|  |  | ||||||
| func AllPackageEnums() []enums.Enum { | func AllPackageEnums() []enums.Enum { | ||||||
|     return []enums.Enum{ {{range .Enums}} |     return []enums.Enum{ {{range .Enums}} | ||||||
|         {{.EnumTypeName}}, {{end}} |         {{ if gt (len .Values) 0 }} {{  $v := index .Values 0 }} {{ $v.VarName}}, {{end}} // {{ .EnumTypeName }} {{end}} | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -38,7 +38,7 @@ var rexIDChecksumConst = rext.W(regexp.MustCompile(`const ChecksumIDGenerator = | |||||||
| //go:embed id-generate.template | //go:embed id-generate.template | ||||||
| var templateIDGenerateText string | var templateIDGenerateText string | ||||||
|  |  | ||||||
| func GenerateIDSpecs(sourceDir string, destFile string, opt *IDGenOptions) error { | func GenerateIDSpecs(sourceDir string, destFile string, opt IDGenOptions) error { | ||||||
|  |  | ||||||
| 	debugOutput := langext.Coalesce(opt.DebugOutput, false) | 	debugOutput := langext.Coalesce(opt.DebugOutput, false) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -34,10 +34,10 @@ func TestGenerateIDSpecs(t *testing.T) { | |||||||
| 	_, err = cmdext.Runner("tar").Arg("-xvzf").Arg(tmpFile).Arg("-C").Arg(tmpDir).FailOnExitCode().FailOnTimeout().Timeout(time.Minute).Run() | 	_, err = cmdext.Runner("tar").Arg("-xvzf").Arg(tmpFile).Arg("-C").Arg(tmpDir).FailOnExitCode().FailOnTimeout().Timeout(time.Minute).Run() | ||||||
| 	tst.AssertNoErr(t, err) | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
| 	err = GenerateIDSpecs(tmpDir, tmpDir+"/id_gen.go", &IDGenOptions{DebugOutput: langext.PTrue}) | 	err = GenerateIDSpecs(tmpDir, tmpDir+"/id_gen.go", IDGenOptions{DebugOutput: langext.PTrue}) | ||||||
| 	tst.AssertNoErr(t, err) | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
| 	err = GenerateIDSpecs(tmpDir, tmpDir+"/id_gen.go", &IDGenOptions{DebugOutput: langext.PTrue}) | 	err = GenerateIDSpecs(tmpDir, tmpDir+"/id_gen.go", IDGenOptions{DebugOutput: langext.PTrue}) | ||||||
| 	tst.AssertNoErr(t, err) | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
| 	fmt.Println() | 	fmt.Println() | ||||||
|   | |||||||
							
								
								
									
										59
									
								
								dataext/optional.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								dataext/optional.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | package dataext | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type JsonOpt[T any] struct { | ||||||
|  | 	isSet bool | ||||||
|  | 	value T | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MarshalJSON returns m as the JSON encoding of m. | ||||||
|  | func (m JsonOpt[T]) MarshalJSON() ([]byte, error) { | ||||||
|  | 	if !m.isSet { | ||||||
|  | 		return []byte("null"), nil // actually this would be undefined - but undefined is not valid JSON | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return json.Marshal(m.value) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UnmarshalJSON sets *m to a copy of data. | ||||||
|  | func (m *JsonOpt[T]) UnmarshalJSON(data []byte) error { | ||||||
|  | 	if m == nil { | ||||||
|  | 		return errors.New("JsonOpt: UnmarshalJSON on nil pointer") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	m.isSet = true | ||||||
|  | 	return json.Unmarshal(data, &m.value) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m JsonOpt[T]) IsSet() bool { | ||||||
|  | 	return m.isSet | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m JsonOpt[T]) IsUnset() bool { | ||||||
|  | 	return !m.isSet | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m JsonOpt[T]) Value() (T, bool) { | ||||||
|  | 	if !m.isSet { | ||||||
|  | 		return *new(T), false | ||||||
|  | 	} | ||||||
|  | 	return m.value, true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m JsonOpt[T]) ValueOrNil() *T { | ||||||
|  | 	if !m.isSet { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return &m.value | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m JsonOpt[T]) MustValue() T { | ||||||
|  | 	if !m.isSet { | ||||||
|  | 		panic("value not set") | ||||||
|  | 	} | ||||||
|  | 	return m.value | ||||||
|  | } | ||||||
| @@ -39,6 +39,7 @@ type ginRouteSpec struct { | |||||||
| type Options struct { | type Options struct { | ||||||
| 	AllowCors             *bool                                     // Add cors handler to allow all CORS requests on the default http methods | 	AllowCors             *bool                                     // Add cors handler to allow all CORS requests on the default http methods | ||||||
| 	GinDebug              *bool                                     // Set gin.debug to true (adds more logs) | 	GinDebug              *bool                                     // Set gin.debug to true (adds more logs) | ||||||
|  | 	SuppressGinLogs       *bool                                     // Suppress our custom gin logs (even if GinDebug == true) | ||||||
| 	BufferBody            *bool                                     // Buffers the input body stream, this way the ginext error handler can later include the whole request body | 	BufferBody            *bool                                     // Buffers the input body stream, this way the ginext error handler can later include the whole request body | ||||||
| 	Timeout               *time.Duration                            // The default handler timeout | 	Timeout               *time.Duration                            // The default handler timeout | ||||||
| 	ListenerBeforeRequest []func(g *gin.Context)                    // Register listener that are called before the handler method | 	ListenerBeforeRequest []func(g *gin.Context)                    // Register listener that are called before the handler method | ||||||
| @@ -51,7 +52,7 @@ func NewEngine(opt Options) *GinWrapper { | |||||||
|  |  | ||||||
| 	wrapper := &GinWrapper{ | 	wrapper := &GinWrapper{ | ||||||
| 		engine:                engine, | 		engine:                engine, | ||||||
| 		suppressGinLogs:       false, | 		suppressGinLogs:       langext.Coalesce(opt.SuppressGinLogs, false), | ||||||
| 		allowCors:             langext.Coalesce(opt.AllowCors, false), | 		allowCors:             langext.Coalesce(opt.AllowCors, false), | ||||||
| 		ginDebug:              langext.Coalesce(opt.GinDebug, true), | 		ginDebug:              langext.Coalesce(opt.GinDebug, true), | ||||||
| 		bufferBody:            langext.Coalesce(opt.BufferBody, false), | 		bufferBody:            langext.Coalesce(opt.BufferBody, false), | ||||||
| @@ -73,12 +74,12 @@ func NewEngine(opt Options) *GinWrapper { | |||||||
| 	if !wrapper.ginDebug { | 	if !wrapper.ginDebug { | ||||||
| 		gin.SetMode(gin.ReleaseMode) | 		gin.SetMode(gin.ReleaseMode) | ||||||
|  |  | ||||||
|  | 		if !wrapper.suppressGinLogs { | ||||||
| 			ginlogger := gin.Logger() | 			ginlogger := gin.Logger() | ||||||
| 			engine.Use(func(context *gin.Context) { | 			engine.Use(func(context *gin.Context) { | ||||||
| 			if !wrapper.suppressGinLogs { |  | ||||||
| 				ginlogger(context) | 				ginlogger(context) | ||||||
| 			} |  | ||||||
| 			}) | 			}) | ||||||
|  | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		gin.SetMode(gin.DebugMode) | 		gin.SetMode(gin.DebugMode) | ||||||
| 	} | 	} | ||||||
| @@ -193,3 +194,9 @@ func (w *GinWrapper) ServeHTTP(req *http.Request) *httptest.ResponseRecorder { | |||||||
| 	w.engine.ServeHTTP(respRec, req) | 	w.engine.ServeHTTP(respRec, req) | ||||||
| 	return respRec | 	return respRec | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ForwardRequest manually inserts a request into this router | ||||||
|  | // = behaves as if the request came from the outside (and writes the response to `writer`) | ||||||
|  | func (w *GinWrapper) ForwardRequest(writer http.ResponseWriter, req *http.Request) { | ||||||
|  | 	w.engine.ServeHTTP(writer, req) | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								go.mod
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| module gogs.mikescher.com/BlackForestBytes/goext | module gogs.mikescher.com/BlackForestBytes/goext | ||||||
|  |  | ||||||
| go 1.21 | go 1.22 | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/gin-gonic/gin v1.9.1 | 	github.com/gin-gonic/gin v1.9.1 | ||||||
| @@ -9,13 +9,13 @@ require ( | |||||||
| 	github.com/rs/xid v1.5.0 | 	github.com/rs/xid v1.5.0 | ||||||
| 	github.com/rs/zerolog v1.32.0 | 	github.com/rs/zerolog v1.32.0 | ||||||
| 	go.mongodb.org/mongo-driver v1.14.0 | 	go.mongodb.org/mongo-driver v1.14.0 | ||||||
| 	golang.org/x/crypto v0.21.0 | 	golang.org/x/crypto v0.22.0 | ||||||
| 	golang.org/x/sys v0.18.0 | 	golang.org/x/sys v0.19.0 | ||||||
| 	golang.org/x/term v0.18.0 | 	golang.org/x/term v0.19.0 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/bytedance/sonic v1.11.2 // indirect | 	github.com/bytedance/sonic v1.11.3 // 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.1 // indirect | 	github.com/chenzhuoyu/iasm v0.9.1 // indirect | ||||||
| 	github.com/dustin/go-humanize v1.0.1 // indirect | 	github.com/dustin/go-humanize v1.0.1 // indirect | ||||||
| @@ -36,7 +36,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.1.1 // indirect | 	github.com/pelletier/go-toml/v2 v2.2.0 // indirect | ||||||
| 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // 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.12 // indirect | 	github.com/ugorji/go/codec v1.2.12 // indirect | ||||||
| @@ -45,8 +45,8 @@ require ( | |||||||
| 	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.7.0 // indirect | 	golang.org/x/arch v0.7.0 // indirect | ||||||
| 	golang.org/x/net v0.22.0 // indirect | 	golang.org/x/net v0.24.0 // indirect | ||||||
| 	golang.org/x/sync v0.6.0 // indirect | 	golang.org/x/sync v0.7.0 // indirect | ||||||
| 	golang.org/x/text v0.14.0 // indirect | 	golang.org/x/text v0.14.0 // indirect | ||||||
| 	google.golang.org/protobuf v1.33.0 // indirect | 	google.golang.org/protobuf v1.33.0 // indirect | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								go.sum
									
									
									
									
									
								
							| @@ -8,6 +8,8 @@ github.com/bytedance/sonic v1.11.1 h1:JC0+6c9FoWYYxakaoa+c5QTtJeiSZNeByOBhXtAFSn | |||||||
| github.com/bytedance/sonic v1.11.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | github.com/bytedance/sonic v1.11.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | ||||||
| github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A= | github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A= | ||||||
| github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= | ||||||
|  | github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= | ||||||
|  | github.com/bytedance/sonic v1.11.3/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= | ||||||
| @@ -102,6 +104,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.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= | github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= | ||||||
| github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= | github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= | ||||||
|  | github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= | ||||||
|  | github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= | ||||||
| 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||||
| 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= | ||||||
| @@ -116,6 +120,7 @@ github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWR | |||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||||||
| github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||||||
|  | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= | ||||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
| github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
| @@ -124,6 +129,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o | |||||||
| github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||||||
| github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||||||
| github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||||||
|  | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||||||
|  | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
| github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= | ||||||
| github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= | ||||||
| github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= | ||||||
| @@ -159,6 +166,8 @@ golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= | |||||||
| golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= | golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= | ||||||
| golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= | ||||||
| golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= | ||||||
|  | golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= | ||||||
|  | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= | ||||||
| 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= | ||||||
| @@ -173,10 +182,14 @@ golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= | |||||||
| golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= | ||||||
| golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= | golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= | ||||||
| golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= | golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= | ||||||
|  | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= | ||||||
|  | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= | ||||||
| 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-20220722155255-886fb9371eb4/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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= | ||||||
| golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||||
|  | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= | ||||||
|  | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | 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-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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| @@ -194,6 +207,8 @@ golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= | |||||||
| golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
| golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= | ||||||
| golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
|  | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= | ||||||
|  | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
| 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.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= | golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= | ||||||
| @@ -202,6 +217,8 @@ golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= | |||||||
| golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= | ||||||
| golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= | golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= | ||||||
| golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= | golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= | ||||||
|  | golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= | ||||||
|  | golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= | ||||||
| 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= | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| package goext | package goext | ||||||
|  |  | ||||||
| const GoextVersion = "0.0.402" | const GoextVersion = "0.0.427" | ||||||
|  |  | ||||||
| const GoextVersionTimestamp = "2024-03-10T12:49:31+0100" | const GoextVersionTimestamp = "2024-04-07T15:10:21+0200" | ||||||
|   | |||||||
| @@ -217,6 +217,7 @@ type decodeState struct { | |||||||
| 	savedError            error | 	savedError            error | ||||||
| 	useNumber             bool | 	useNumber             bool | ||||||
| 	disallowUnknownFields bool | 	disallowUnknownFields bool | ||||||
|  | 	tagkey                *string | ||||||
| } | } | ||||||
|  |  | ||||||
| // readIndex returns the position of the last byte read. | // readIndex returns the position of the last byte read. | ||||||
| @@ -652,7 +653,11 @@ func (d *decodeState) object(v reflect.Value) error { | |||||||
| 			v.Set(reflect.MakeMap(t)) | 			v.Set(reflect.MakeMap(t)) | ||||||
| 		} | 		} | ||||||
| 	case reflect.Struct: | 	case reflect.Struct: | ||||||
| 		fields = cachedTypeFields(t) | 		tagkey := "json" | ||||||
|  | 		if d.tagkey != nil { | ||||||
|  | 			tagkey = *d.tagkey | ||||||
|  | 		} | ||||||
|  | 		fields = cachedTypeFields(t, tagkey) | ||||||
| 		// ok | 		// ok | ||||||
| 	default: | 	default: | ||||||
| 		d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)}) | 		d.saveError(&UnmarshalTypeError{Value: "object", Type: t, Offset: int64(d.off)}) | ||||||
|   | |||||||
| @@ -382,7 +382,12 @@ func isEmptyValue(v reflect.Value) bool { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) { | func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) { | ||||||
| 	valueEncoder(v)(e, v, opts) | 	tagkey := "json" | ||||||
|  | 	if opts.tagkey != nil { | ||||||
|  | 		tagkey = *opts.tagkey | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	valueEncoder(v, tagkey)(e, v, opts) | ||||||
| } | } | ||||||
|  |  | ||||||
| type encOpts struct { | type encOpts struct { | ||||||
| @@ -397,20 +402,22 @@ type encOpts struct { | |||||||
| 	// filter matches jsonfilter tag of struct | 	// filter matches jsonfilter tag of struct | ||||||
| 	// marshals if no jsonfilter is set or otherwise if jsonfilter has the filter value | 	// marshals if no jsonfilter is set or otherwise if jsonfilter has the filter value | ||||||
| 	filter *string | 	filter *string | ||||||
|  | 	// use different tag instead of "json" | ||||||
|  | 	tagkey *string | ||||||
| } | } | ||||||
|  |  | ||||||
| type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts) | type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts) | ||||||
|  |  | ||||||
| var encoderCache sync.Map // map[reflect.Type]encoderFunc | var encoderCache sync.Map // map[reflect.Type]encoderFunc | ||||||
|  |  | ||||||
| func valueEncoder(v reflect.Value) encoderFunc { | func valueEncoder(v reflect.Value, tagkey string) encoderFunc { | ||||||
| 	if !v.IsValid() { | 	if !v.IsValid() { | ||||||
| 		return invalidValueEncoder | 		return invalidValueEncoder | ||||||
| 	} | 	} | ||||||
| 	return typeEncoder(v.Type()) | 	return typeEncoder(v.Type(), tagkey) | ||||||
| } | } | ||||||
|  |  | ||||||
| func typeEncoder(t reflect.Type) encoderFunc { | func typeEncoder(t reflect.Type, tagkey string) encoderFunc { | ||||||
| 	if fi, ok := encoderCache.Load(t); ok { | 	if fi, ok := encoderCache.Load(t); ok { | ||||||
| 		return fi.(encoderFunc) | 		return fi.(encoderFunc) | ||||||
| 	} | 	} | ||||||
| @@ -433,7 +440,7 @@ func typeEncoder(t reflect.Type) encoderFunc { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Compute the real encoder and replace the indirect func with it. | 	// Compute the real encoder and replace the indirect func with it. | ||||||
| 	f = newTypeEncoder(t, true) | 	f = newTypeEncoder(t, true, tagkey) | ||||||
| 	wg.Done() | 	wg.Done() | ||||||
| 	encoderCache.Store(t, f) | 	encoderCache.Store(t, f) | ||||||
| 	return f | 	return f | ||||||
| @@ -446,19 +453,19 @@ var ( | |||||||
|  |  | ||||||
| // newTypeEncoder constructs an encoderFunc for a type. | // newTypeEncoder constructs an encoderFunc for a type. | ||||||
| // The returned encoder only checks CanAddr when allowAddr is true. | // The returned encoder only checks CanAddr when allowAddr is true. | ||||||
| func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { | func newTypeEncoder(t reflect.Type, allowAddr bool, tagkey string) encoderFunc { | ||||||
| 	// If we have a non-pointer value whose type implements | 	// If we have a non-pointer value whose type implements | ||||||
| 	// Marshaler with a value receiver, then we're better off taking | 	// Marshaler with a value receiver, then we're better off taking | ||||||
| 	// the address of the value - otherwise we end up with an | 	// the address of the value - otherwise we end up with an | ||||||
| 	// allocation as we cast the value to an interface. | 	// allocation as we cast the value to an interface. | ||||||
| 	if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) { | 	if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) { | ||||||
| 		return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false)) | 		return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false, tagkey)) | ||||||
| 	} | 	} | ||||||
| 	if t.Implements(marshalerType) { | 	if t.Implements(marshalerType) { | ||||||
| 		return marshalerEncoder | 		return marshalerEncoder | ||||||
| 	} | 	} | ||||||
| 	if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(textMarshalerType) { | 	if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(textMarshalerType) { | ||||||
| 		return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false)) | 		return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false, tagkey)) | ||||||
| 	} | 	} | ||||||
| 	if t.Implements(textMarshalerType) { | 	if t.Implements(textMarshalerType) { | ||||||
| 		return textMarshalerEncoder | 		return textMarshalerEncoder | ||||||
| @@ -480,15 +487,15 @@ func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { | |||||||
| 	case reflect.Interface: | 	case reflect.Interface: | ||||||
| 		return interfaceEncoder | 		return interfaceEncoder | ||||||
| 	case reflect.Struct: | 	case reflect.Struct: | ||||||
| 		return newStructEncoder(t) | 		return newStructEncoder(t, tagkey) | ||||||
| 	case reflect.Map: | 	case reflect.Map: | ||||||
| 		return newMapEncoder(t) | 		return newMapEncoder(t, tagkey) | ||||||
| 	case reflect.Slice: | 	case reflect.Slice: | ||||||
| 		return newSliceEncoder(t) | 		return newSliceEncoder(t, tagkey) | ||||||
| 	case reflect.Array: | 	case reflect.Array: | ||||||
| 		return newArrayEncoder(t) | 		return newArrayEncoder(t, tagkey) | ||||||
| 	case reflect.Pointer: | 	case reflect.Pointer: | ||||||
| 		return newPtrEncoder(t) | 		return newPtrEncoder(t, tagkey) | ||||||
| 	default: | 	default: | ||||||
| 		return unsupportedTypeEncoder | 		return unsupportedTypeEncoder | ||||||
| 	} | 	} | ||||||
| @@ -801,8 +808,8 @@ FieldLoop: | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func newStructEncoder(t reflect.Type) encoderFunc { | func newStructEncoder(t reflect.Type, tagkey string) encoderFunc { | ||||||
| 	se := structEncoder{fields: cachedTypeFields(t)} | 	se := structEncoder{fields: cachedTypeFields(t, tagkey)} | ||||||
| 	return se.encode | 	return se.encode | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -855,7 +862,7 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { | |||||||
| 	e.ptrLevel-- | 	e.ptrLevel-- | ||||||
| } | } | ||||||
|  |  | ||||||
| func newMapEncoder(t reflect.Type) encoderFunc { | func newMapEncoder(t reflect.Type, tagkey string) encoderFunc { | ||||||
| 	switch t.Key().Kind() { | 	switch t.Key().Kind() { | ||||||
| 	case reflect.String, | 	case reflect.String, | ||||||
| 		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | 		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | ||||||
| @@ -865,7 +872,7 @@ func newMapEncoder(t reflect.Type) encoderFunc { | |||||||
| 			return unsupportedTypeEncoder | 			return unsupportedTypeEncoder | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	me := mapEncoder{typeEncoder(t.Elem())} | 	me := mapEncoder{typeEncoder(t.Elem(), tagkey)} | ||||||
| 	return me.encode | 	return me.encode | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -936,7 +943,7 @@ func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { | |||||||
| 	e.ptrLevel-- | 	e.ptrLevel-- | ||||||
| } | } | ||||||
|  |  | ||||||
| func newSliceEncoder(t reflect.Type) encoderFunc { | func newSliceEncoder(t reflect.Type, tagkey string) encoderFunc { | ||||||
| 	// Byte slices get special treatment; arrays don't. | 	// Byte slices get special treatment; arrays don't. | ||||||
| 	if t.Elem().Kind() == reflect.Uint8 { | 	if t.Elem().Kind() == reflect.Uint8 { | ||||||
| 		p := reflect.PointerTo(t.Elem()) | 		p := reflect.PointerTo(t.Elem()) | ||||||
| @@ -944,7 +951,7 @@ func newSliceEncoder(t reflect.Type) encoderFunc { | |||||||
| 			return encodeByteSlice | 			return encodeByteSlice | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	enc := sliceEncoder{newArrayEncoder(t)} | 	enc := sliceEncoder{newArrayEncoder(t, tagkey)} | ||||||
| 	return enc.encode | 	return enc.encode | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -964,8 +971,8 @@ func (ae arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { | |||||||
| 	e.WriteByte(']') | 	e.WriteByte(']') | ||||||
| } | } | ||||||
|  |  | ||||||
| func newArrayEncoder(t reflect.Type) encoderFunc { | func newArrayEncoder(t reflect.Type, tagkey string) encoderFunc { | ||||||
| 	enc := arrayEncoder{typeEncoder(t.Elem())} | 	enc := arrayEncoder{typeEncoder(t.Elem(), tagkey)} | ||||||
| 	return enc.encode | 	return enc.encode | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -992,8 +999,8 @@ func (pe ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { | |||||||
| 	e.ptrLevel-- | 	e.ptrLevel-- | ||||||
| } | } | ||||||
|  |  | ||||||
| func newPtrEncoder(t reflect.Type) encoderFunc { | func newPtrEncoder(t reflect.Type, tagkey string) encoderFunc { | ||||||
| 	enc := ptrEncoder{typeEncoder(t.Elem())} | 	enc := ptrEncoder{typeEncoder(t.Elem(), tagkey)} | ||||||
| 	return enc.encode | 	return enc.encode | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1270,7 +1277,7 @@ func (x byIndex) Less(i, j int) bool { | |||||||
| // typeFields returns a list of fields that JSON should recognize for the given type. | // typeFields returns a list of fields that JSON should recognize for the given type. | ||||||
| // The algorithm is breadth-first search over the set of structs to include - the top struct | // The algorithm is breadth-first search over the set of structs to include - the top struct | ||||||
| // and then any reachable anonymous structs. | // and then any reachable anonymous structs. | ||||||
| func typeFields(t reflect.Type) structFields { | func typeFields(t reflect.Type, tagkey string) structFields { | ||||||
| 	// Anonymous fields to explore at the current level and the next. | 	// Anonymous fields to explore at the current level and the next. | ||||||
| 	current := []field{} | 	current := []field{} | ||||||
| 	next := []field{{typ: t}} | 	next := []field{{typ: t}} | ||||||
| @@ -1315,7 +1322,7 @@ func typeFields(t reflect.Type) structFields { | |||||||
| 					// Ignore unexported non-embedded fields. | 					// Ignore unexported non-embedded fields. | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
| 				tag := sf.Tag.Get("json") | 				tag := sf.Tag.Get(tagkey) | ||||||
| 				if tag == "-" { | 				if tag == "-" { | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
| @@ -1449,7 +1456,7 @@ func typeFields(t reflect.Type) structFields { | |||||||
|  |  | ||||||
| 	for i := range fields { | 	for i := range fields { | ||||||
| 		f := &fields[i] | 		f := &fields[i] | ||||||
| 		f.encoder = typeEncoder(typeByIndex(t, f.index)) | 		f.encoder = typeEncoder(typeByIndex(t, f.index), tagkey) | ||||||
| 	} | 	} | ||||||
| 	nameIndex := make(map[string]int, len(fields)) | 	nameIndex := make(map[string]int, len(fields)) | ||||||
| 	for i, field := range fields { | 	for i, field := range fields { | ||||||
| @@ -1474,13 +1481,26 @@ func dominantField(fields []field) (field, bool) { | |||||||
| 	return fields[0], true | 	return fields[0], true | ||||||
| } | } | ||||||
|  |  | ||||||
| var fieldCache sync.Map // map[reflect.Type]structFields | var fieldCache sync.Map // map[string]map[reflect.Type]structFields | ||||||
|  |  | ||||||
| // cachedTypeFields is like typeFields but uses a cache to avoid repeated work. | // cachedTypeFields is like typeFields but uses a cache to avoid repeated work. | ||||||
| func cachedTypeFields(t reflect.Type) structFields { | func cachedTypeFields(t reflect.Type, tagkey string) structFields { | ||||||
| 	if f, ok := fieldCache.Load(t); ok { | 	if m0, ok := fieldCache.Load(tagkey); ok { | ||||||
|  |  | ||||||
|  | 		if f, ok := m0.(*sync.Map).Load(t); ok { | ||||||
| 			return f.(structFields) | 			return f.(structFields) | ||||||
| 		} | 		} | ||||||
| 	f, _ := fieldCache.LoadOrStore(t, typeFields(t)) | 		f, _ := m0.(*sync.Map).LoadOrStore(t, typeFields(t, tagkey)) | ||||||
| 		return f.(structFields) | 		return f.(structFields) | ||||||
|  |  | ||||||
|  | 	} else { | ||||||
|  |  | ||||||
|  | 		m0 := &sync.Map{} | ||||||
|  | 		f, _ := m0.LoadOrStore(t, typeFields(t, tagkey)) | ||||||
|  |  | ||||||
|  | 		fieldCache.Store(tagkey, m0) | ||||||
|  |  | ||||||
|  | 		return f.(structFields) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -41,6 +41,9 @@ func (dec *Decoder) UseNumber() { dec.d.useNumber = true } | |||||||
| // non-ignored, exported fields in the destination. | // non-ignored, exported fields in the destination. | ||||||
| func (dec *Decoder) DisallowUnknownFields() { dec.d.disallowUnknownFields = true } | func (dec *Decoder) DisallowUnknownFields() { dec.d.disallowUnknownFields = true } | ||||||
|  |  | ||||||
|  | // TagKey sets a different TagKey (instead of "json") | ||||||
|  | func (dec *Decoder) TagKey(v string) { dec.d.tagkey = &v } | ||||||
|  |  | ||||||
| // Decode reads the next JSON-encoded value from its | // Decode reads the next JSON-encoded value from its | ||||||
| // input and stores it in the value pointed to by v. | // input and stores it in the value pointed to by v. | ||||||
| // | // | ||||||
|   | |||||||
| @@ -453,6 +453,15 @@ func ArrConcat[T any](arr ...[]T) []T { | |||||||
| 	return r | 	return r | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ArrAppend works similar to append(x, y, z) - but doe snot touch the old array and creates a new one | ||||||
|  | func ArrAppend[T any](arr []T, add ...T) []T { | ||||||
|  | 	r := ArrCopy(arr) | ||||||
|  | 	for _, v := range add { | ||||||
|  | 		r = append(r, v) | ||||||
|  | 	} | ||||||
|  | 	return r | ||||||
|  | } | ||||||
|  |  | ||||||
| // ArrCopy does a shallow copy of the 'in' array | // ArrCopy does a shallow copy of the 'in' array | ||||||
| func ArrCopy[T any](in []T) []T { | func ArrCopy[T any](in []T) []T { | ||||||
| 	out := make([]T, len(in)) | 	out := make([]T, len(in)) | ||||||
|   | |||||||
| @@ -5,12 +5,76 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func Coalesce[T any](v *T, def T) T { | func Coalesce[T any](v1 *T, def T) T { | ||||||
| 	if v == nil { | 	if v1 != nil { | ||||||
| 		return def | 		return *v1 | ||||||
| 	} else { |  | ||||||
| 		return *v |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	return def | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func CoalesceOpt[T any](v1 *T, v2 *T) *T { | ||||||
|  | 	if v1 != nil { | ||||||
|  | 		return v1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return v2 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func Coalesce3[T any](v1 *T, v2 *T, def T) T { | ||||||
|  | 	if v1 != nil { | ||||||
|  | 		return *v1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v2 != nil { | ||||||
|  | 		return *v2 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return def | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func Coalesce3Opt[T any](v1 *T, v2 *T, v3 *T) *T { | ||||||
|  | 	if v1 != nil { | ||||||
|  | 		return v1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v2 != nil { | ||||||
|  | 		return v2 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return v3 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func Coalesce4[T any](v1 *T, v2 *T, v3 *T, def T) T { | ||||||
|  | 	if v1 != nil { | ||||||
|  | 		return *v1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v2 != nil { | ||||||
|  | 		return *v2 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v3 != nil { | ||||||
|  | 		return *v3 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return def | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func Coalesce4Opt[T any](v1 *T, v2 *T, v3 *T, v4 *T) *T { | ||||||
|  | 	if v1 != nil { | ||||||
|  | 		return v1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v2 != nil { | ||||||
|  | 		return v2 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v3 != nil { | ||||||
|  | 		return v3 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return v4 | ||||||
| } | } | ||||||
|  |  | ||||||
| func CoalesceString(s *string, def string) string { | func CoalesceString(s *string, def string) string { | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								langext/object.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								langext/object.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | package langext | ||||||
|  |  | ||||||
|  | import "encoding/json" | ||||||
|  |  | ||||||
|  | func DeepCopyByJson[T any](v T) (T, error) { | ||||||
|  |  | ||||||
|  | 	bin, err := json.Marshal(v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return *new(T), err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var result T | ||||||
|  | 	err = json.Unmarshal(bin, &result) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return *new(T), err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result, nil | ||||||
|  | } | ||||||
| @@ -1,19 +1,30 @@ | |||||||
| package reflectext | package reflectext | ||||||
|  |  | ||||||
| import "reflect" | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"reflect" | ||||||
|  | ) | ||||||
|  |  | ||||||
| func ConvertStructToMap(v any) any { | type ConvertStructToMapOpt struct { | ||||||
| 	return reflectToMap(reflect.ValueOf(v)) | 	KeepJsonMarshalTypes bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func reflectToMap(fv reflect.Value) any { | func ConvertStructToMap(v any, opts ...ConvertStructToMapOpt) any { | ||||||
|  | 	opt := ConvertStructToMapOpt{} | ||||||
|  | 	if len(opts) > 0 { | ||||||
|  | 		opt = opts[0] | ||||||
|  | 	} | ||||||
|  | 	return reflectToMap(reflect.ValueOf(v), opt) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func reflectToMap(fv reflect.Value, opt ConvertStructToMapOpt) any { | ||||||
|  |  | ||||||
| 	if fv.Kind() == reflect.Ptr { | 	if fv.Kind() == reflect.Ptr { | ||||||
|  |  | ||||||
| 		if fv.IsNil() { | 		if fv.IsNil() { | ||||||
| 			return nil | 			return nil | ||||||
| 		} else { | 		} else { | ||||||
| 			return reflectToMap(fv.Elem()) | 			return reflectToMap(fv.Elem(), opt) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| @@ -30,7 +41,7 @@ func reflectToMap(fv reflect.Value) any { | |||||||
| 		arrlen := fv.Len() | 		arrlen := fv.Len() | ||||||
| 		arr := make([]any, arrlen) | 		arr := make([]any, arrlen) | ||||||
| 		for i := 0; i < arrlen; i++ { | 		for i := 0; i < arrlen; i++ { | ||||||
| 			arr[i] = reflectToMap(fv.Index(i)) | 			arr[i] = reflectToMap(fv.Index(i), opt) | ||||||
| 		} | 		} | ||||||
| 		return arr | 		return arr | ||||||
|  |  | ||||||
| @@ -41,7 +52,7 @@ func reflectToMap(fv reflect.Value) any { | |||||||
| 		arrlen := fv.Len() | 		arrlen := fv.Len() | ||||||
| 		arr := make([]any, arrlen) | 		arr := make([]any, arrlen) | ||||||
| 		for i := 0; i < arrlen; i++ { | 		for i := 0; i < arrlen; i++ { | ||||||
| 			arr[i] = reflectToMap(fv.Index(i)) | 			arr[i] = reflectToMap(fv.Index(i), opt) | ||||||
| 		} | 		} | ||||||
| 		return arr | 		return arr | ||||||
|  |  | ||||||
| @@ -56,11 +67,15 @@ func reflectToMap(fv reflect.Value) any { | |||||||
|  |  | ||||||
| 	if fv.Kind() == reflect.Struct { | 	if fv.Kind() == reflect.Struct { | ||||||
|  |  | ||||||
|  | 		if opt.KeepJsonMarshalTypes && fv.Type().Implements(reflect.TypeFor[json.Marshaler]()) { | ||||||
|  | 			return fv.Interface() | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		res := make(map[string]any) | 		res := make(map[string]any) | ||||||
|  |  | ||||||
| 		for i := 0; i < fv.NumField(); i++ { | 		for i := 0; i < fv.NumField(); i++ { | ||||||
| 			if fv.Type().Field(i).IsExported() { | 			if fv.Type().Field(i).IsExported() { | ||||||
| 				res[fv.Type().Field(i).Name] = reflectToMap(fv.Field(i)) | 				res[fv.Type().Field(i).Name] = reflectToMap(fv.Field(i), opt) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,6 +9,8 @@ import ( | |||||||
| 	"go.mongodb.org/mongo-driver/bson/bsonrw" | 	"go.mongodb.org/mongo-driver/bson/bsonrw" | ||||||
| 	"go.mongodb.org/mongo-driver/bson/bsontype" | 	"go.mongodb.org/mongo-driver/bson/bsontype" | ||||||
| 	"reflect" | 	"reflect" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -65,36 +67,20 @@ func (t *Date) UnmarshalJSON(data []byte) error { | |||||||
| 	if err := json.Unmarshal(data, &str); err != nil { | 	if err := json.Unmarshal(data, &str); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	t0, err := time.Parse(t.FormatStr(), str) | 	return t.ParseString(str) | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	t.Year = t0.Year() |  | ||||||
| 	t.Month = int(t0.Month()) |  | ||||||
| 	t.Day = t0.Day() |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (t Date) MarshalJSON() ([]byte, error) { | func (t Date) MarshalJSON() ([]byte, error) { | ||||||
| 	str := t.TimeUTC().Format(t.FormatStr()) | 	str := t.String() | ||||||
| 	return json.Marshal(str) | 	return json.Marshal(str) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (t Date) MarshalText() ([]byte, error) { | func (t Date) MarshalText() ([]byte, error) { | ||||||
| 	b := make([]byte, 0, len(t.FormatStr())) | 	return []byte(t.String()), nil | ||||||
| 	return t.TimeUTC().AppendFormat(b, t.FormatStr()), nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (t *Date) UnmarshalText(data []byte) error { | func (t *Date) UnmarshalText(data []byte) error { | ||||||
| 	var err error | 	return t.ParseString(string(data)) | ||||||
| 	v, err := time.Parse(t.FormatStr(), string(data)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	t.Year = v.Year() |  | ||||||
| 	t.Month = int(v.Month()) |  | ||||||
| 	t.Day = v.Day() |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (t *Date) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | func (t *Date) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | ||||||
| @@ -116,6 +102,13 @@ func (t *Date) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if tt == "" { | ||||||
|  | 		t.Year = 0 | ||||||
|  | 		t.Month = 0 | ||||||
|  | 		t.Day = 0 | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	v, err := time.Parse(t.FormatStr(), tt) | 	v, err := time.Parse(t.FormatStr(), tt) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -128,7 +121,10 @@ func (t *Date) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (t Date) MarshalBSONValue() (bsontype.Type, []byte, error) { | func (t Date) MarshalBSONValue() (bsontype.Type, []byte, error) { | ||||||
| 	return bson.MarshalValue(t.TimeUTC().Format(t.FormatStr())) | 	if t.IsZero() { | ||||||
|  | 		return bson.MarshalValue("") | ||||||
|  | 	} | ||||||
|  | 	return bson.MarshalValue(t.String()) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (t Date) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error { | func (t Date) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error { | ||||||
| @@ -164,7 +160,7 @@ func (t Date) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val | |||||||
| } | } | ||||||
|  |  | ||||||
| func (t Date) Serialize() string { | func (t Date) Serialize() string { | ||||||
| 	return t.TimeUTC().Format(t.FormatStr()) | 	return t.String() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (t Date) FormatStr() string { | func (t Date) FormatStr() string { | ||||||
| @@ -212,11 +208,52 @@ func (t Date) Format(layout string) string { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (t Date) GoString() string { | func (t Date) GoString() string { | ||||||
| 	return t.TimeUTC().GoString() | 	return fmt.Sprintf("rfctime.Date{Year: %d, Month: %d, Day: %d}", t.Year, t.Month, t.Day) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (t Date) String() string { | func (t Date) String() string { | ||||||
| 	return t.TimeUTC().String() | 	return fmt.Sprintf("%04d-%02d-%02d", t.Year, t.Month, t.Day) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *Date) ParseString(v string) error { | ||||||
|  | 	split := strings.Split(v, "-") | ||||||
|  | 	if len(split) != 3 { | ||||||
|  | 		return errors.New("invalid date format: " + v) | ||||||
|  | 	} | ||||||
|  | 	year, err := strconv.ParseInt(split[0], 10, 32) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.New("invalid date format: " + v + ": " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 	month, err := strconv.ParseInt(split[1], 10, 32) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.New("invalid date format: " + v + ": " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 	day, err := strconv.ParseInt(split[2], 10, 32) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.New("invalid date format: " + v + ": " + err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if year < 0 { | ||||||
|  | 		return errors.New("invalid date format: " + v + ": year is negative") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if month < 1 || month > 12 { | ||||||
|  | 		return errors.New("invalid date format: " + v + ": month is out of range") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if day < 1 || day > 31 { | ||||||
|  | 		return errors.New("invalid date format: " + v + ": day is out of range") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	t.Year = int(year) | ||||||
|  | 	t.Month = int(month) | ||||||
|  | 	t.Day = int(day) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t Date) IsZero() bool { | ||||||
|  | 	return t.Year == 0 && t.Month == 0 && t.Day == 0 | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewDate(t time.Time) Date { | func NewDate(t time.Time) Date { | ||||||
|   | |||||||
| @@ -7,8 +7,6 @@ import ( | |||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/rfctime" | 	"gogs.mikescher.com/BlackForestBytes/goext/rfctime" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/timeext" | 	"gogs.mikescher.com/BlackForestBytes/goext/timeext" | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -79,24 +77,12 @@ var ConverterRFC339NanoTimeToString = NewDBTypeConverter[rfctime.RFC3339NanoTime | |||||||
| var ConverterRFCDateToString = NewDBTypeConverter[rfctime.Date, string](func(v rfctime.Date) (string, error) { | var ConverterRFCDateToString = NewDBTypeConverter[rfctime.Date, string](func(v rfctime.Date) (string, error) { | ||||||
| 	return fmt.Sprintf("%04d-%02d-%02d", v.Year, v.Month, v.Day), nil | 	return fmt.Sprintf("%04d-%02d-%02d", v.Year, v.Month, v.Day), nil | ||||||
| }, func(v string) (rfctime.Date, error) { | }, func(v string) (rfctime.Date, error) { | ||||||
| 	split := strings.Split(v, "-") | 	d := rfctime.Date{} | ||||||
| 	if len(split) != 3 { | 	if err := d.ParseString(v); err != nil { | ||||||
| 		return rfctime.Date{}, errors.New("invalid date format: " + v) | 		return rfctime.Date{}, err | ||||||
|  | 	} else { | ||||||
|  | 		return d, nil | ||||||
| 	} | 	} | ||||||
| 	year, err := strconv.ParseInt(split[0], 10, 32) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return rfctime.Date{}, errors.New("invalid date format: " + v + ": " + err.Error()) |  | ||||||
| 	} |  | ||||||
| 	month, err := strconv.ParseInt(split[0], 10, 32) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return rfctime.Date{}, errors.New("invalid date format: " + v + ": " + err.Error()) |  | ||||||
| 	} |  | ||||||
| 	day, err := strconv.ParseInt(split[0], 10, 32) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return rfctime.Date{}, errors.New("invalid date format: " + v + ": " + err.Error()) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return rfctime.Date{Year: int(year), Month: int(month), Day: int(day)}, nil |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| var ConverterRFCTimeToString = NewDBTypeConverter[rfctime.Time, string](func(v rfctime.Time) (string, error) { | var ConverterRFCTimeToString = NewDBTypeConverter[rfctime.Time, string](func(v rfctime.Time) (string, error) { | ||||||
|   | |||||||
| @@ -47,3 +47,10 @@ func NewSimplePaginateFilter(filterClause string, filterParams PP, sort []Filter | |||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func NewEmptyPaginateFilter() PaginateFilter { | ||||||
|  | 	return genericPaginateFilter{ | ||||||
|  | 		sql:  func(params PP) (string, string, []string) { return "1=1", "", nil }, | ||||||
|  | 		sort: func() []FilterSort { return make([]FilterSort, 0) }, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										48
									
								
								sq/list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								sq/list.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | package sq | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func Iterate[TData any](ctx context.Context, q Queryable, table string, filter PaginateFilter, scanMode StructScanMode, scanSec StructScanSafety, page int, limit *int, consumer func(ctx context.Context, v TData) error) (int, error) { | ||||||
|  | 	if filter == nil { | ||||||
|  | 		filter = NewEmptyPaginateFilter() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	prepParams := PP{} | ||||||
|  |  | ||||||
|  | 	sortOrder := filter.Sort() | ||||||
|  | 	sortCond := "" | ||||||
|  | 	if len(sortOrder) > 0 { | ||||||
|  | 		sortCond = "ORDER BY " | ||||||
|  | 		for i, v := range sortOrder { | ||||||
|  | 			if i > 0 { | ||||||
|  | 				sortCond += ", " | ||||||
|  | 			} | ||||||
|  | 			sortCond += v.Field + " " + string(v.Direction) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	pageCond := "" | ||||||
|  | 	if limit != nil { | ||||||
|  | 		pageCond += fmt.Sprintf("LIMIT :%s OFFSET :%s", prepParams.Add(*limit+1), prepParams.Add(*limit*(page-1))) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	filterCond, joinCond, joinTables := filter.SQL(prepParams) | ||||||
|  |  | ||||||
|  | 	selectCond := table + ".*" | ||||||
|  | 	for _, v := range joinTables { | ||||||
|  | 		selectCond += ", " + v + ".*" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sqlQueryData := "SELECT " + selectCond + " FROM " + table + " " + joinCond + " WHERE ( " + filterCond + " ) " + sortCond + " " + pageCond | ||||||
|  |  | ||||||
|  | 	rows, err := q.Query(ctx, sqlQueryData, prepParams) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, exerr.Wrap(err, "failed to list paginated entries from DB").Str("table", table).Any("filter", filter).Int("page", page).Any("limit", limit).Build() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return IterateAll[TData](ctx, q, rows, scanMode, scanSec, true, consumer) | ||||||
|  | } | ||||||
| @@ -9,6 +9,10 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func Paginate[TData any](ctx context.Context, q Queryable, table string, filter PaginateFilter, scanMode StructScanMode, scanSec StructScanSafety, page int, limit *int) ([]TData, pag.Pagination, error) { | func Paginate[TData any](ctx context.Context, q Queryable, table string, filter PaginateFilter, scanMode StructScanMode, scanSec StructScanSafety, page int, limit *int) ([]TData, pag.Pagination, error) { | ||||||
|  | 	if filter == nil { | ||||||
|  | 		filter = NewEmptyPaginateFilter() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	prepParams := PP{} | 	prepParams := PP{} | ||||||
|  |  | ||||||
| 	sortOrder := filter.Sort() | 	sortOrder := filter.Sort() | ||||||
| @@ -90,6 +94,10 @@ func Paginate[TData any](ctx context.Context, q Queryable, table string, filter | |||||||
| } | } | ||||||
|  |  | ||||||
| func Count(ctx context.Context, q Queryable, table string, filter PaginateFilter) (int, error) { | func Count(ctx context.Context, q Queryable, table string, filter PaginateFilter) (int, error) { | ||||||
|  | 	if filter == nil { | ||||||
|  | 		filter = NewEmptyPaginateFilter() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	prepParams := PP{} | 	prepParams := PP{} | ||||||
|  |  | ||||||
| 	filterCond, joinCond, _ := filter.SQL(prepParams) | 	filterCond, joinCond, _ := filter.SQL(prepParams) | ||||||
|   | |||||||
| @@ -333,3 +333,79 @@ func ScanAll[TData any](ctx context.Context, q Queryable, rows *sqlx.Rows, mode | |||||||
| 	} | 	} | ||||||
| 	return res, nil | 	return res, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func IterateAll[TData any](ctx context.Context, q Queryable, rows *sqlx.Rows, mode StructScanMode, sec StructScanSafety, close bool, consumer func(ctx context.Context, v TData) error) (int, error) { | ||||||
|  | 	var strscan *StructScanner | ||||||
|  |  | ||||||
|  | 	if sec == Safe { | ||||||
|  | 		strscan = NewStructScanner(rows, false) | ||||||
|  | 		var data TData | ||||||
|  | 		err := strscan.Start(&data) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  | 	} else if sec == Unsafe { | ||||||
|  | 		strscan = NewStructScanner(rows, true) | ||||||
|  | 		var data TData | ||||||
|  | 		err := strscan.Start(&data) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		return 0, errors.New("unknown value for <sec>") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rcount := 0 | ||||||
|  |  | ||||||
|  | 	for rows.Next() { | ||||||
|  |  | ||||||
|  | 		if err := ctx.Err(); err != nil { | ||||||
|  | 			return rcount, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if mode == SModeFast { | ||||||
|  | 			var data TData | ||||||
|  | 			err := strscan.StructScanBase(&data) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return rcount, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			err = consumer(ctx, data) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return rcount, exerr.Wrap(err, "").Build() | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			rcount++ | ||||||
|  |  | ||||||
|  | 		} else if mode == SModeExtended { | ||||||
|  | 			var data TData | ||||||
|  | 			err := strscan.StructScanExt(q, &data) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return rcount, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			err = consumer(ctx, data) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return rcount, exerr.Wrap(err, "").Build() | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			rcount++ | ||||||
|  |  | ||||||
|  | 		} else { | ||||||
|  | 			return rcount, errors.New("unknown value for <mode>") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if close { | ||||||
|  | 		err := strscan.rows.Close() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return rcount, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := rows.Err(); err != nil { | ||||||
|  | 		return rcount, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return rcount, nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -146,3 +146,37 @@ func UnixFloatSeconds(v float64) time.Time { | |||||||
| func FloorTime(t time.Time) time.Time { | func FloorTime(t time.Time) 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()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func SubtractYears(t time.Time, yearCount float64, tz *time.Location) time.Time { | ||||||
|  | 	t = t.In(tz) | ||||||
|  |  | ||||||
|  | 	if yearCount < 0 { | ||||||
|  | 		return AddYears(t, -yearCount, tz) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	intCount, floatCount := math.Modf(yearCount) | ||||||
|  |  | ||||||
|  | 	t.AddDate(-int(intCount), 0, 0) | ||||||
|  |  | ||||||
|  | 	t0 := TimeToYearStart(t, tz) | ||||||
|  | 	t1 := TimeToYearEnd(t, tz) | ||||||
|  |  | ||||||
|  | 	return t.Add(time.Duration(float64(t1.Sub(t0)) * floatCount * -1)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func AddYears(t time.Time, yearCount float64, tz *time.Location) time.Time { | ||||||
|  | 	t = t.In(tz) | ||||||
|  |  | ||||||
|  | 	if yearCount < 0 { | ||||||
|  | 		return SubtractYears(t, -yearCount, tz) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	intCount, floatCount := math.Modf(yearCount) | ||||||
|  |  | ||||||
|  | 	t.AddDate(int(intCount), 0, 0) | ||||||
|  |  | ||||||
|  | 	t0 := TimeToYearStart(t, tz) | ||||||
|  | 	t1 := TimeToYearEnd(t, tz) | ||||||
|  |  | ||||||
|  | 	return t.Add(time.Duration(float64(t1.Sub(t0)) * floatCount)) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -171,7 +171,7 @@ func createPaginationPipeline[TData any](coll *Coll[TData], token ct.CursorToken | |||||||
| 				bson.M{*fieldSecondary: bson.M{"$gt": valueSecondary}}, | 				bson.M{*fieldSecondary: bson.M{"$gt": valueSecondary}}, | ||||||
| 			}}) | 			}}) | ||||||
|  |  | ||||||
| 			sort = append(sort, bson.E{Key: fieldPrimary, Value: +1}) | 			sort = append(sort, bson.E{Key: *fieldSecondary, Value: +1}) | ||||||
|  |  | ||||||
| 		} else if *sortSecondary == ct.SortDESC { | 		} else if *sortSecondary == ct.SortDESC { | ||||||
|  |  | ||||||
| @@ -181,7 +181,7 @@ func createPaginationPipeline[TData any](coll *Coll[TData], token ct.CursorToken | |||||||
| 				bson.M{*fieldSecondary: bson.M{"$lt": valueSecondary}}, | 				bson.M{*fieldSecondary: bson.M{"$lt": valueSecondary}}, | ||||||
| 			}}) | 			}}) | ||||||
|  |  | ||||||
| 			sort = append(sort, bson.E{Key: fieldPrimary, Value: -1}) | 			sort = append(sort, bson.E{Key: *fieldSecondary, Value: -1}) | ||||||
|  |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user