Compare commits
	
		
			16 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9491b72b8d | |||
| 6c4af4006b | |||
| 8bf3a337cf | |||
| 16146494dc | |||
| b0e443ad99 | |||
| 9955eacf96 | |||
| f0347a9435 | |||
| 7c869c65f3 | |||
| 14f39a9162 | |||
| dcd106c1cd | |||
| b704e2a362 | |||
| 6b4bd5a6f8 | |||
| 6df4f5f2a1 | |||
| 780905ba35 | |||
| c679797765 | |||
| 401aad9fa4 | 
| @@ -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() | ||||||
|   | |||||||
							
								
								
									
										58
									
								
								dataext/optional.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								dataext/optional.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | 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") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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) | ||||||
|  |  | ||||||
| 		ginlogger := gin.Logger() | 		if !wrapper.suppressGinLogs { | ||||||
| 		engine.Use(func(context *gin.Context) { | 			ginlogger := gin.Logger() | ||||||
| 			if !wrapper.suppressGinLogs { | 			engine.Use(func(context *gin.Context) { | ||||||
| 				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) | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								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 | ||||||
| @@ -15,7 +15,7 @@ require ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| 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 | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								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= | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| package goext | package goext | ||||||
|  |  | ||||||
| const GoextVersion = "0.0.408" | const GoextVersion = "0.0.424" | ||||||
|  |  | ||||||
| const GoextVersionTimestamp = "2024-03-11T16:41:47+0100" | const GoextVersionTimestamp = "2024-03-30T03:01:55+0100" | ||||||
|   | |||||||
| @@ -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) | ||||||
|  | 		} | ||||||
|  | 		f, _ := m0.(*sync.Map).LoadOrStore(t, typeFields(t, tagkey)) | ||||||
|  | 		return f.(structFields) | ||||||
|  |  | ||||||
|  | 	} else { | ||||||
|  |  | ||||||
|  | 		m0 := &sync.Map{} | ||||||
|  | 		f, _ := m0.LoadOrStore(t, typeFields(t, tagkey)) | ||||||
|  |  | ||||||
|  | 		fieldCache.Store(tagkey, m0) | ||||||
|  |  | ||||||
| 		return f.(structFields) | 		return f.(structFields) | ||||||
| 	} | 	} | ||||||
| 	f, _ := fieldCache.LoadOrStore(t, typeFields(t)) |  | ||||||
| 	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. | ||||||
| // | // | ||||||
|   | |||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -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) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -102,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 | ||||||
| @@ -114,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 { | ||||||
| @@ -242,6 +252,10 @@ func (t *Date) ParseString(v string) error { | |||||||
| 	return nil | 	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 { | ||||||
| 	return Date{ | 	return Date{ | ||||||
| 		Year:  t.Year(), | 		Year:  t.Year(), | ||||||
|   | |||||||
| @@ -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