Compare commits
	
		
			17 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 16146494dc | |||
| b0e443ad99 | |||
| 9955eacf96 | |||
| f0347a9435 | |||
| 7c869c65f3 | |||
| 14f39a9162 | |||
| dcd106c1cd | |||
| b704e2a362 | |||
| 6b4bd5a6f8 | |||
| 6df4f5f2a1 | |||
| 780905ba35 | |||
| c679797765 | |||
| 401aad9fa4 | |||
| 645113d553 | |||
| 4a33986b6a | |||
| c1c8c64c76 | |||
| 0927fdc4d7 | 
| @@ -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() | ||||||
|   | |||||||
							
								
								
									
										62
									
								
								dataext/optional.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								dataext/optional.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | 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 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if m.value == nil { | ||||||
|  | 		return []byte("null"), nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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.404" | const GoextVersion = "0.0.421" | ||||||
|  |  | ||||||
| const GoextVersionTimestamp = "2024-03-10T15:25:30+0100" | const GoextVersionTimestamp = "2024-03-23T20:28:51+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) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -67,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 { | ||||||
| @@ -118,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 | ||||||
| @@ -130,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 { | ||||||
| @@ -166,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 { | ||||||
| @@ -230,11 +224,11 @@ func (t *Date) ParseString(v string) error { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.New("invalid date format: " + v + ": " + err.Error()) | 		return errors.New("invalid date format: " + v + ": " + err.Error()) | ||||||
| 	} | 	} | ||||||
| 	month, err := strconv.ParseInt(split[0], 10, 32) | 	month, err := strconv.ParseInt(split[1], 10, 32) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.New("invalid date format: " + v + ": " + err.Error()) | 		return errors.New("invalid date format: " + v + ": " + err.Error()) | ||||||
| 	} | 	} | ||||||
| 	day, err := strconv.ParseInt(split[0], 10, 32) | 	day, err := strconv.ParseInt(split[2], 10, 32) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.New("invalid date format: " + v + ": " + err.Error()) | 		return errors.New("invalid date format: " + v + ": " + err.Error()) | ||||||
| 	} | 	} | ||||||
| @@ -258,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(), | ||||||
|   | |||||||
| @@ -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 | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user