Compare commits
	
		
			15 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5fba7e0e2f | |||
| 8757643399 | |||
| 42bd4cf58d | |||
| 413178e2d3 | |||
| 9264a2e99b | |||
| 2a0471fb3d | |||
| 1497c013f9 | |||
| ef78b7467b | |||
| 0eda32b725 | |||
| f9ccafb976 | |||
| 6e90239fef | |||
| 05580c384a | |||
| 3188b951fb | |||
| 6b211d1443 | |||
| b2b9b40792 | 
							
								
								
									
										6
									
								
								.idea/sqldialects.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/sqldialects.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="SqlDialectMappings"> | ||||||
|  |     <file url="file://$PROJECT_DIR$/sq/sq_test.go" dialect="SQLite" /> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
| @@ -41,3 +41,5 @@ Potentially needs `export GOPRIVATE="gogs.mikescher.com"` | |||||||
| | rext        | Mike       | Regex Wrapper, wraps regexp with a better interface                                                           | | | rext        | Mike       | Regex Wrapper, wraps regexp with a better interface                                                           | | ||||||
| | wmo         | Mike       | Mongo Wrapper, wraps mongodb with a better interface                                                          | | | wmo         | Mike       | Mongo Wrapper, wraps mongodb with a better interface                                                          | | ||||||
| |             |            |                                                                                                               | | |             |            |                                                                                                               | | ||||||
|  | | scn         | Mike       | SimpleCloudNotifier                                                                                           | | ||||||
|  | |             |            |                                                                                                               | | ||||||
| @@ -48,6 +48,9 @@ var ( | |||||||
| 	TypeMongoReflection   = NewType("MONGO_REFLECTION", langext.Ptr(500)) | 	TypeMongoReflection   = NewType("MONGO_REFLECTION", langext.Ptr(500)) | ||||||
| 	TypeMongoInvalidOpt   = NewType("MONGO_INVALIDOPT", langext.Ptr(500)) | 	TypeMongoInvalidOpt   = NewType("MONGO_INVALIDOPT", langext.Ptr(500)) | ||||||
|  |  | ||||||
|  | 	TypeSQLQuery = NewType("SQL_QUERY", langext.Ptr(500)) | ||||||
|  | 	TypeSQLBuild = NewType("SQL_BUILD", langext.Ptr(500)) | ||||||
|  |  | ||||||
| 	TypeWrap = NewType("Wrap", nil) | 	TypeWrap = NewType("Wrap", nil) | ||||||
|  |  | ||||||
| 	TypeBindFailURI      = NewType("BINDFAIL_URI", langext.Ptr(400)) | 	TypeBindFailURI      = NewType("BINDFAIL_URI", langext.Ptr(400)) | ||||||
| @@ -65,7 +68,7 @@ var ( | |||||||
| 	TypeUnauthorized = NewType("UNAUTHORIZED", langext.Ptr(401)) | 	TypeUnauthorized = NewType("UNAUTHORIZED", langext.Ptr(401)) | ||||||
| 	TypeAuthFailed   = NewType("AUTH_FAILED", langext.Ptr(401)) | 	TypeAuthFailed   = NewType("AUTH_FAILED", langext.Ptr(401)) | ||||||
|  |  | ||||||
| 	// other values come the used package | 	// other values come from the downstream application that uses goext | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var registeredTypes = dataext.SyncSet[string]{} | var registeredTypes = dataext.SyncSet[string]{} | ||||||
|   | |||||||
| @@ -258,6 +258,73 @@ func (ee *ExErr) Depth() int { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetMeta returns the meta value with the specified key | ||||||
|  | // this method recurses through all wrapped errors and returns the first matching meta value | ||||||
|  | func (ee *ExErr) GetMeta(key string) (any, bool) { | ||||||
|  | 	for curr := ee; curr != nil; curr = curr.OriginalError { | ||||||
|  | 		if v, ok := curr.Meta[key]; ok { | ||||||
|  | 			return v.Value, true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetMetaString functions the same as GetMeta, but returns false if the type does not match | ||||||
|  | func (ee *ExErr) GetMetaString(key string) (string, bool) { | ||||||
|  | 	if v1, ok := ee.GetMeta(key); ok { | ||||||
|  | 		if v2, ok := v1.(string); ok { | ||||||
|  | 			return v2, true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return "", false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ee *ExErr) GetMetaBool(key string) (bool, bool) { | ||||||
|  | 	if v1, ok := ee.GetMeta(key); ok { | ||||||
|  | 		if v2, ok := v1.(bool); ok { | ||||||
|  | 			return v2, true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false, false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ee *ExErr) GetMetaInt(key string) (int, bool) { | ||||||
|  | 	if v1, ok := ee.GetMeta(key); ok { | ||||||
|  | 		if v2, ok := v1.(int); ok { | ||||||
|  | 			return v2, true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return 0, false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ee *ExErr) GetMetaFloat32(key string) (float32, bool) { | ||||||
|  | 	if v1, ok := ee.GetMeta(key); ok { | ||||||
|  | 		if v2, ok := v1.(float32); ok { | ||||||
|  | 			return v2, true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return 0, false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ee *ExErr) GetMetaFloat64(key string) (float64, bool) { | ||||||
|  | 	if v1, ok := ee.GetMeta(key); ok { | ||||||
|  | 		if v2, ok := v1.(float64); ok { | ||||||
|  | 			return v2, true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return 0, false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (ee *ExErr) GetMetaTime(key string) (time.Time, bool) { | ||||||
|  | 	if v1, ok := ee.GetMeta(key); ok { | ||||||
|  | 		if v2, ok := v1.(time.Time); ok { | ||||||
|  | 			return v2, true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return time.Time{}, false | ||||||
|  | } | ||||||
|  |  | ||||||
| // contains test if the supplied error is contained in this error (anywhere in the chain) | // contains test if the supplied error is contained in this error (anywhere in the chain) | ||||||
| func (ee *ExErr) contains(original *ExErr) (*ExErr, bool) { | func (ee *ExErr) contains(original *ExErr) (*ExErr, bool) { | ||||||
| 	if original == nil { | 	if original == nil { | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								exerr/gin.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								exerr/gin.go
									
									
									
									
									
								
							| @@ -57,6 +57,19 @@ func (ee *ExErr) toJson(depth int, applyExtendListener bool, outputMeta bool) la | |||||||
| 	return ginJson | 	return ginJson | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (ee *ExErr) ToDefaultAPIJson() (string, error) { | ||||||
|  |  | ||||||
|  | 	gjr := json.GoJsonRender{Data: ee.ToAPIJson(true, pkgconfig.ExtendedGinOutput, pkgconfig.IncludeMetaInGinOutput), NilSafeSlices: true, NilSafeMaps: true} | ||||||
|  |  | ||||||
|  | 	r, err := gjr.RenderString() | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return r, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // ToAPIJson converts the ExError to a json object | // ToAPIJson converts the ExError to a json object | ||||||
| // (the same object as used in the Output(gin) method) | // (the same object as used in the Output(gin) method) | ||||||
| // | // | ||||||
|   | |||||||
| @@ -22,6 +22,8 @@ type GinWrapper struct { | |||||||
| 	ginDebug              bool | 	ginDebug              bool | ||||||
| 	bufferBody            bool | 	bufferBody            bool | ||||||
| 	requestTimeout        time.Duration | 	requestTimeout        time.Duration | ||||||
|  | 	listenerBeforeRequest []func(g *gin.Context) | ||||||
|  | 	listenerAfterRequest  []func(g *gin.Context, resp HTTPResponse) | ||||||
|  |  | ||||||
| 	routeSpecs []ginRouteSpec | 	routeSpecs []ginRouteSpec | ||||||
| } | } | ||||||
| @@ -33,35 +35,41 @@ type ginRouteSpec struct { | |||||||
| 	Handler     string | 	Handler     string | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type Options struct { | ||||||
|  | 	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) | ||||||
|  | 	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 | ||||||
|  | 	ListenerBeforeRequest []func(g *gin.Context)                    // Register listener that are called before the handler method | ||||||
|  | 	ListenerAfterRequest  []func(g *gin.Context, resp HTTPResponse) // Register listener that are called after the handler method | ||||||
|  | } | ||||||
|  |  | ||||||
| // NewEngine creates a new (wrapped) ginEngine | // NewEngine creates a new (wrapped) ginEngine | ||||||
| // Parameters are: | func NewEngine(opt Options) *GinWrapper { | ||||||
| // - [allowCors]    Add cors handler to allow all CORS requests on the default http methods |  | ||||||
| // - [ginDebug]     Set gin.debug to true (adds more logs) |  | ||||||
| // - [bufferBody]   Buffers the input body stream, this way the ginext error handler can later include the whole request body |  | ||||||
| // - [timeout]      The default handler timeout |  | ||||||
| func NewEngine(allowCors bool, ginDebug bool, bufferBody bool, timeout time.Duration) *GinWrapper { |  | ||||||
| 	engine := gin.New() | 	engine := gin.New() | ||||||
|  |  | ||||||
| 	wrapper := &GinWrapper{ | 	wrapper := &GinWrapper{ | ||||||
| 		engine:                engine, | 		engine:                engine, | ||||||
| 		SuppressGinLogs:       false, | 		SuppressGinLogs:       false, | ||||||
| 		allowCors:       allowCors, | 		allowCors:             langext.Coalesce(opt.AllowCors, false), | ||||||
| 		ginDebug:        ginDebug, | 		ginDebug:              langext.Coalesce(opt.GinDebug, true), | ||||||
| 		bufferBody:      bufferBody, | 		bufferBody:            langext.Coalesce(opt.BufferBody, false), | ||||||
| 		requestTimeout:  timeout, | 		requestTimeout:        langext.Coalesce(opt.Timeout, 24*time.Hour), | ||||||
|  | 		listenerBeforeRequest: opt.ListenerBeforeRequest, | ||||||
|  | 		listenerAfterRequest:  opt.ListenerAfterRequest, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	engine.RedirectFixedPath = false | 	engine.RedirectFixedPath = false | ||||||
| 	engine.RedirectTrailingSlash = false | 	engine.RedirectTrailingSlash = false | ||||||
|  |  | ||||||
| 	if allowCors { | 	if wrapper.allowCors { | ||||||
| 		engine.Use(CorsMiddleware()) | 		engine.Use(CorsMiddleware()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// do not debug-print routes | 	// do not debug-print routes | ||||||
| 	gin.DebugPrintRouteFunc = func(_, _, _ string, _ int) {} | 	gin.DebugPrintRouteFunc = func(_, _, _ string, _ int) {} | ||||||
|  |  | ||||||
| 	if !ginDebug { | 	if !wrapper.ginDebug { | ||||||
| 		gin.SetMode(gin.ReleaseMode) | 		gin.SetMode(gin.ReleaseMode) | ||||||
|  |  | ||||||
| 		ginlogger := gin.Logger() | 		ginlogger := gin.Logger() | ||||||
|   | |||||||
| @@ -20,6 +20,10 @@ func Wrap(w *GinWrapper, fn WHandlerFunc) gin.HandlerFunc { | |||||||
| 			persistantData: &preContextData{}, | 			persistantData: &preContextData{}, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		for _, lstr := range w.listenerBeforeRequest { | ||||||
|  | 			lstr(g) | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		wrap, stackTrace, panicObj := callPanicSafe(fn, pctx) | 		wrap, stackTrace, panicObj := callPanicSafe(fn, pctx) | ||||||
| 		if panicObj != nil { | 		if panicObj != nil { | ||||||
|  |  | ||||||
| @@ -45,6 +49,10 @@ func Wrap(w *GinWrapper, fn WHandlerFunc) gin.HandlerFunc { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		for _, lstr := range w.listenerAfterRequest { | ||||||
|  | 			lstr(g, wrap) | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		if reqctx.Err() == nil { | 		if reqctx.Err() == nil { | ||||||
| 			wrap.Write(g) | 			wrap.Write(g) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -5,6 +5,8 @@ import ( | |||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
| 	json "gogs.mikescher.com/BlackForestBytes/goext/gojson" | 	json "gogs.mikescher.com/BlackForestBytes/goext/gojson" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
|  | 	"os" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type headerval struct { | type headerval struct { | ||||||
| @@ -18,21 +20,34 @@ type HTTPResponse interface { | |||||||
| 	IsSuccess() bool | 	IsSuccess() bool | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type InspectableHTTPResponse interface { | ||||||
|  | 	HTTPResponse | ||||||
|  |  | ||||||
|  | 	Statuscode() int | ||||||
|  | 	BodyString(g *gin.Context) *string | ||||||
|  | 	ContentType() string | ||||||
|  | 	Headers() []string | ||||||
|  | } | ||||||
|  |  | ||||||
| type jsonHTTPResponse struct { | type jsonHTTPResponse struct { | ||||||
| 	statusCode int | 	statusCode int | ||||||
| 	data       any | 	data       any | ||||||
| 	headers    []headerval | 	headers    []headerval | ||||||
| } | } | ||||||
|  |  | ||||||
| func (j jsonHTTPResponse) Write(g *gin.Context) { | func (j jsonHTTPResponse) jsonRenderer(g *gin.Context) json.GoJsonRender { | ||||||
| 	for _, v := range j.headers { |  | ||||||
| 		g.Header(v.Key, v.Val) |  | ||||||
| 	} |  | ||||||
| 	var f *string | 	var f *string | ||||||
| 	if jsonfilter := g.GetString("goext.jsonfilter"); jsonfilter != "" { | 	if jsonfilter := g.GetString("goext.jsonfilter"); jsonfilter != "" { | ||||||
| 		f = &jsonfilter | 		f = &jsonfilter | ||||||
| 	} | 	} | ||||||
| 	g.Render(j.statusCode, json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true, Filter: f}) | 	return json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true, Filter: f} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j jsonHTTPResponse) Write(g *gin.Context) { | ||||||
|  | 	for _, v := range j.headers { | ||||||
|  | 		g.Header(v.Key, v.Val) | ||||||
|  | 	} | ||||||
|  | 	g.Render(j.statusCode, j.jsonRenderer(g)) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (j jsonHTTPResponse) WithHeader(k string, v string) HTTPResponse { | func (j jsonHTTPResponse) WithHeader(k string, v string) HTTPResponse { | ||||||
| @@ -44,6 +59,26 @@ func (j jsonHTTPResponse) IsSuccess() bool { | |||||||
| 	return j.statusCode >= 200 && j.statusCode <= 399 | 	return j.statusCode >= 200 && j.statusCode <= 399 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (j jsonHTTPResponse) Statuscode() int { | ||||||
|  | 	return j.statusCode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j jsonHTTPResponse) BodyString(g *gin.Context) *string { | ||||||
|  | 	if str, err := j.jsonRenderer(g).RenderString(); err == nil { | ||||||
|  | 		return &str | ||||||
|  | 	} else { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j jsonHTTPResponse) ContentType() string { | ||||||
|  | 	return "application/json" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j jsonHTTPResponse) Headers() []string { | ||||||
|  | 	return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) | ||||||
|  | } | ||||||
|  |  | ||||||
| type emptyHTTPResponse struct { | type emptyHTTPResponse struct { | ||||||
| 	statusCode int | 	statusCode int | ||||||
| 	headers    []headerval | 	headers    []headerval | ||||||
| @@ -65,6 +100,22 @@ func (j emptyHTTPResponse) IsSuccess() bool { | |||||||
| 	return j.statusCode >= 200 && j.statusCode <= 399 | 	return j.statusCode >= 200 && j.statusCode <= 399 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (j emptyHTTPResponse) Statuscode() int { | ||||||
|  | 	return j.statusCode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j emptyHTTPResponse) BodyString(*gin.Context) *string { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j emptyHTTPResponse) ContentType() string { | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j emptyHTTPResponse) Headers() []string { | ||||||
|  | 	return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) | ||||||
|  | } | ||||||
|  |  | ||||||
| type textHTTPResponse struct { | type textHTTPResponse struct { | ||||||
| 	statusCode int | 	statusCode int | ||||||
| 	data       string | 	data       string | ||||||
| @@ -87,6 +138,22 @@ func (j textHTTPResponse) IsSuccess() bool { | |||||||
| 	return j.statusCode >= 200 && j.statusCode <= 399 | 	return j.statusCode >= 200 && j.statusCode <= 399 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (j textHTTPResponse) Statuscode() int { | ||||||
|  | 	return j.statusCode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j textHTTPResponse) BodyString(*gin.Context) *string { | ||||||
|  | 	return langext.Ptr(j.data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j textHTTPResponse) ContentType() string { | ||||||
|  | 	return "text/plain" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j textHTTPResponse) Headers() []string { | ||||||
|  | 	return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) | ||||||
|  | } | ||||||
|  |  | ||||||
| type dataHTTPResponse struct { | type dataHTTPResponse struct { | ||||||
| 	statusCode  int | 	statusCode  int | ||||||
| 	data        []byte | 	data        []byte | ||||||
| @@ -110,6 +177,22 @@ func (j dataHTTPResponse) IsSuccess() bool { | |||||||
| 	return j.statusCode >= 200 && j.statusCode <= 399 | 	return j.statusCode >= 200 && j.statusCode <= 399 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (j dataHTTPResponse) Statuscode() int { | ||||||
|  | 	return j.statusCode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j dataHTTPResponse) BodyString(*gin.Context) *string { | ||||||
|  | 	return langext.Ptr(string(j.data)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j dataHTTPResponse) ContentType() string { | ||||||
|  | 	return j.contentType | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j dataHTTPResponse) Headers() []string { | ||||||
|  | 	return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) | ||||||
|  | } | ||||||
|  |  | ||||||
| type fileHTTPResponse struct { | type fileHTTPResponse struct { | ||||||
| 	mimetype string | 	mimetype string | ||||||
| 	filepath string | 	filepath string | ||||||
| @@ -138,6 +221,26 @@ func (j fileHTTPResponse) IsSuccess() bool { | |||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (j fileHTTPResponse) Statuscode() int { | ||||||
|  | 	return 200 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j fileHTTPResponse) BodyString(*gin.Context) *string { | ||||||
|  | 	data, err := os.ReadFile(j.filepath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return langext.Ptr(string(data)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j fileHTTPResponse) ContentType() string { | ||||||
|  | 	return j.mimetype | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j fileHTTPResponse) Headers() []string { | ||||||
|  | 	return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) | ||||||
|  | } | ||||||
|  |  | ||||||
| type downloadDataHTTPResponse struct { | type downloadDataHTTPResponse struct { | ||||||
| 	statusCode int | 	statusCode int | ||||||
| 	mimetype   string | 	mimetype   string | ||||||
| @@ -167,6 +270,22 @@ func (j downloadDataHTTPResponse) IsSuccess() bool { | |||||||
| 	return j.statusCode >= 200 && j.statusCode <= 399 | 	return j.statusCode >= 200 && j.statusCode <= 399 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (j downloadDataHTTPResponse) Statuscode() int { | ||||||
|  | 	return j.statusCode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j downloadDataHTTPResponse) BodyString(*gin.Context) *string { | ||||||
|  | 	return langext.Ptr(string(j.data)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j downloadDataHTTPResponse) ContentType() string { | ||||||
|  | 	return j.mimetype | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j downloadDataHTTPResponse) Headers() []string { | ||||||
|  | 	return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) | ||||||
|  | } | ||||||
|  |  | ||||||
| type redirectHTTPResponse struct { | type redirectHTTPResponse struct { | ||||||
| 	statusCode int | 	statusCode int | ||||||
| 	url        string | 	url        string | ||||||
| @@ -186,6 +305,22 @@ func (j redirectHTTPResponse) IsSuccess() bool { | |||||||
| 	return j.statusCode >= 200 && j.statusCode <= 399 | 	return j.statusCode >= 200 && j.statusCode <= 399 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (j redirectHTTPResponse) Statuscode() int { | ||||||
|  | 	return j.statusCode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j redirectHTTPResponse) BodyString(*gin.Context) *string { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j redirectHTTPResponse) ContentType() string { | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j redirectHTTPResponse) Headers() []string { | ||||||
|  | 	return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) | ||||||
|  | } | ||||||
|  |  | ||||||
| type jsonAPIErrResponse struct { | type jsonAPIErrResponse struct { | ||||||
| 	err     *exerr.ExErr | 	err     *exerr.ExErr | ||||||
| 	headers []headerval | 	headers []headerval | ||||||
| @@ -204,6 +339,30 @@ func (j jsonAPIErrResponse) IsSuccess() bool { | |||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (j jsonAPIErrResponse) Statuscode() int { | ||||||
|  | 	return langext.Coalesce(j.err.RecursiveStatuscode(), 0) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j jsonAPIErrResponse) BodyString(*gin.Context) *string { | ||||||
|  | 	if str, err := j.err.ToDefaultAPIJson(); err == nil { | ||||||
|  | 		return &str | ||||||
|  | 	} else { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j jsonAPIErrResponse) ContentType() string { | ||||||
|  | 	return "application/json" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j jsonAPIErrResponse) Headers() []string { | ||||||
|  | 	return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (j jsonAPIErrResponse) Unwrap() error { | ||||||
|  | 	return j.err | ||||||
|  | } | ||||||
|  |  | ||||||
| func Status(sc int) HTTPResponse { | func Status(sc int) HTTPResponse { | ||||||
| 	return &emptyHTTPResponse{statusCode: sc} | 	return &emptyHTTPResponse{statusCode: sc} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								go.mod
									
									
									
									
									
								
							| @@ -8,15 +8,17 @@ require ( | |||||||
| 	github.com/rs/xid v1.5.0 | 	github.com/rs/xid v1.5.0 | ||||||
| 	github.com/rs/zerolog v1.31.0 | 	github.com/rs/zerolog v1.31.0 | ||||||
| 	go.mongodb.org/mongo-driver v1.13.1 | 	go.mongodb.org/mongo-driver v1.13.1 | ||||||
| 	golang.org/x/crypto v0.16.0 | 	golang.org/x/crypto v0.17.0 | ||||||
| 	golang.org/x/sys v0.15.0 | 	golang.org/x/sys v0.16.0 | ||||||
| 	golang.org/x/term v0.15.0 | 	golang.org/x/term v0.16.0 | ||||||
|  | 	github.com/glebarez/go-sqlite  v1.22.0 // only needed for tests -.- | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/bytedance/sonic v1.10.2 // indirect | 	github.com/bytedance/sonic v1.10.2 // 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/gabriel-vasile/mimetype v1.4.3 // indirect | 	github.com/gabriel-vasile/mimetype v1.4.3 // indirect | ||||||
| 	github.com/gin-contrib/sse v0.1.0 // indirect | 	github.com/gin-contrib/sse v0.1.0 // indirect | ||||||
| 	github.com/go-playground/locales v0.14.1 // indirect | 	github.com/go-playground/locales v0.14.1 // indirect | ||||||
| @@ -24,6 +26,7 @@ require ( | |||||||
| 	github.com/go-playground/validator/v10 v10.16.0 // indirect | 	github.com/go-playground/validator/v10 v10.16.0 // indirect | ||||||
| 	github.com/goccy/go-json v0.10.2 // indirect | 	github.com/goccy/go-json v0.10.2 // indirect | ||||||
| 	github.com/golang/snappy v0.0.4 // indirect | 	github.com/golang/snappy v0.0.4 // indirect | ||||||
|  | 	github.com/google/uuid v1.5.0 // indirect | ||||||
| 	github.com/json-iterator/go v1.1.12 // indirect | 	github.com/json-iterator/go v1.1.12 // indirect | ||||||
| 	github.com/klauspost/compress v1.17.4 // indirect | 	github.com/klauspost/compress v1.17.4 // indirect | ||||||
| 	github.com/klauspost/cpuid/v2 v2.2.6 // indirect | 	github.com/klauspost/cpuid/v2 v2.2.6 // indirect | ||||||
| @@ -34,16 +37,21 @@ require ( | |||||||
| 	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.1.1 // 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 | ||||||
| 	github.com/xdg-go/pbkdf2 v1.0.0 // indirect | 	github.com/xdg-go/pbkdf2 v1.0.0 // indirect | ||||||
| 	github.com/xdg-go/scram v1.1.2 // indirect | 	github.com/xdg-go/scram v1.1.2 // indirect | ||||||
| 	github.com/xdg-go/stringprep v1.0.4 // indirect | 	github.com/xdg-go/stringprep v1.0.4 // indirect | ||||||
| 	github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect | 	github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect | ||||||
| 	golang.org/x/arch v0.6.0 // indirect | 	golang.org/x/arch v0.7.0 // indirect | ||||||
| 	golang.org/x/net v0.19.0 // indirect | 	golang.org/x/net v0.19.0 // indirect | ||||||
| 	golang.org/x/sync v0.5.0 // indirect | 	golang.org/x/sync v0.6.0 // indirect | ||||||
| 	golang.org/x/text v0.14.0 // indirect | 	golang.org/x/text v0.14.0 // indirect | ||||||
| 	google.golang.org/protobuf v1.31.0 // indirect | 	google.golang.org/protobuf v1.32.0 // indirect | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
|  | 	modernc.org/libc v1.37.6 // indirect | ||||||
|  | 	modernc.org/mathutil v1.6.0 // indirect | ||||||
|  | 	modernc.org/memory v1.7.2 // indirect | ||||||
|  | 	modernc.org/sqlite v1.28.0 // indirect | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										99
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										99
									
								
								go.sum
									
									
									
									
									
								
							| @@ -6,7 +6,6 @@ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F | |||||||
| 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= | ||||||
| github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= | ||||||
| github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= |  | ||||||
| github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= | github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= | ||||||
| github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= | github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= | ||||||
| github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= | github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= | ||||||
| @@ -14,21 +13,22 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV | |||||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= | ||||||
| github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= | ||||||
| github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= | ||||||
| github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= | ||||||
| github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= | ||||||
| github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= | ||||||
| github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= | ||||||
| github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= | ||||||
|  | github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= | ||||||
|  | github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= | ||||||
| github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= | ||||||
|  | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= | ||||||
| github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= | ||||||
| github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= | ||||||
| github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= | ||||||
| github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= | ||||||
| github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= |  | ||||||
| github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= |  | ||||||
| github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= | github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= | ||||||
| github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= | ||||||
| github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= | ||||||
| @@ -36,7 +36,6 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB | |||||||
| github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= | ||||||
| github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= | ||||||
| github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= | ||||||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= |  | ||||||
| github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||||
| github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | ||||||
| github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||||
| @@ -44,24 +43,18 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ | |||||||
| github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= | ||||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
|  | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= | ||||||
|  | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= | ||||||
|  | github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= | ||||||
|  | github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
| github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= | github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= | ||||||
| github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= | github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= | ||||||
| github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | ||||||
| github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | ||||||
| github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= | ||||||
| github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= |  | ||||||
| github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= |  | ||||||
| github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= |  | ||||||
| github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= |  | ||||||
| github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= |  | ||||||
| github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= |  | ||||||
| github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= |  | ||||||
| github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= |  | ||||||
| github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= | github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= | ||||||
| github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= | github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= | ||||||
| github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | ||||||
| github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= |  | ||||||
| github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= |  | ||||||
| github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= | github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= | ||||||
| github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= | github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= | ||||||
| github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= | ||||||
| @@ -72,12 +65,12 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | |||||||
| github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= | ||||||
| github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | ||||||
| github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||||||
| github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= |  | ||||||
| github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||||
| github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||||||
| github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||||
| github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= |  | ||||||
| github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= | ||||||
|  | github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= | ||||||
|  | github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= | ||||||
| github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= | ||||||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||||
| @@ -86,13 +79,13 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY | |||||||
| github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= | ||||||
| github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= | github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= | ||||||
| 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.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= |  | ||||||
| github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= |  | ||||||
| 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/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= | ||||||
|  | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= | ||||||
|  | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= | ||||||
| github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= | github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= | ||||||
| github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= | ||||||
| github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= | github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= | ||||||
| @@ -110,8 +103,6 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU | |||||||
| 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/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.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= |  | ||||||
| github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= |  | ||||||
| github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= | ||||||
| github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= | ||||||
| github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= | ||||||
| @@ -124,47 +115,29 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul | |||||||
| github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= | github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= | ||||||
| github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= | github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= | ||||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||||
| go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= |  | ||||||
| go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= |  | ||||||
| go.mongodb.org/mongo-driver v1.13.0 h1:67DgFFjYOCMWdtTEmKFpV3ffWlFnh+CYZ8ZS/tXWUfY= |  | ||||||
| go.mongodb.org/mongo-driver v1.13.0/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= |  | ||||||
| go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= | go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= | ||||||
| go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= | go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= | ||||||
| golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= | ||||||
| golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= | golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= | ||||||
| golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= | golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= | ||||||
| golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= |  | ||||||
| golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= |  | ||||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
| golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||||
| golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||||
| golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= | golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= | ||||||
| golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= | ||||||
| golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= |  | ||||||
| golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= |  | ||||||
| golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= |  | ||||||
| golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= |  | ||||||
| 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= | ||||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||||
| golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||||
| golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= |  | ||||||
| golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= |  | ||||||
| golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= |  | ||||||
| golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= |  | ||||||
| golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= |  | ||||||
| golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= |  | ||||||
| golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= | ||||||
| golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= | ||||||
| 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.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= | ||||||
| golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||||
| golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= |  | ||||||
| golang.org/x/sync v0.5.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= | ||||||
| @@ -176,43 +149,41 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc | |||||||
| golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= | ||||||
| golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
| golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= |  | ||||||
| golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |  | ||||||
| golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= |  | ||||||
| golang.org/x/sys v0.15.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.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= | golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= | ||||||
| golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= | golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= | ||||||
| golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= |  | ||||||
| golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= |  | ||||||
| golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= |  | ||||||
| golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= |  | ||||||
| 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= | ||||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||||
| golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= | ||||||
| golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||||
| golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= |  | ||||||
| golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= |  | ||||||
| golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= | ||||||
| golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | ||||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= |  | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= | ||||||
| google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= | ||||||
| google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= | ||||||
|  | google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|  | modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= | ||||||
|  | modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= | ||||||
|  | modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= | ||||||
|  | modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= | ||||||
|  | modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= | ||||||
|  | modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= | ||||||
|  | modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= | ||||||
|  | modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= | ||||||
| nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= | ||||||
| rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| package goext | package goext | ||||||
|  |  | ||||||
| const GoextVersion = "0.0.346" | const GoextVersion = "0.0.360" | ||||||
|  |  | ||||||
| const GoextVersionTimestamp = "2023-12-13T16:22:15+0100" | const GoextVersionTimestamp = "2024-01-06T01:31:07+0100" | ||||||
|   | |||||||
| @@ -37,6 +37,14 @@ func (r GoJsonRender) Render(w http.ResponseWriter) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (r GoJsonRender) RenderString() (string, error) { | ||||||
|  | 	jsonBytes, err := MarshalSafeCollections(r.Data, r.NilSafeSlices, r.NilSafeMaps, r.Indent, r.Filter) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return string(jsonBytes), nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func (r GoJsonRender) WriteContentType(w http.ResponseWriter) { | func (r GoJsonRender) WriteContentType(w http.ResponseWriter) { | ||||||
| 	header := w.Header() | 	header := w.Header() | ||||||
| 	if val := header["Content-Type"]; len(val) == 0 { | 	if val := header["Content-Type"]; len(val) == 0 { | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package googleapi | package googleapi | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | ||||||
| 	"os" | 	"os" | ||||||
| 	"testing" | 	"testing" | ||||||
| @@ -18,7 +17,7 @@ func TestEncodeMimeMail(t *testing.T) { | |||||||
| 		MailBody{Plain: "Plain Text"}, | 		MailBody{Plain: "Plain Text"}, | ||||||
| 		nil) | 		nil) | ||||||
|  |  | ||||||
| 	fmt.Printf("%s\n\n", mail) | 	verifyMime(mail) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestEncodeMimeMail2(t *testing.T) { | func TestEncodeMimeMail2(t *testing.T) { | ||||||
| @@ -35,7 +34,7 @@ func TestEncodeMimeMail2(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 		nil) | 		nil) | ||||||
|  |  | ||||||
| 	fmt.Printf("%s\n\n", mail) | 	verifyMime(mail) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestEncodeMimeMail3(t *testing.T) { | func TestEncodeMimeMail3(t *testing.T) { | ||||||
| @@ -53,12 +52,12 @@ func TestEncodeMimeMail3(t *testing.T) { | |||||||
| 			{Data: []byte("HelloWorld"), Filename: "test.txt", IsInline: false, ContentType: "text/plain"}, | 			{Data: []byte("HelloWorld"), Filename: "test.txt", IsInline: false, ContentType: "text/plain"}, | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 	fmt.Printf("%s\n\n", mail) | 	verifyMime(mail) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestEncodeMimeMail4(t *testing.T) { | func TestEncodeMimeMail4(t *testing.T) { | ||||||
|  |  | ||||||
| 	b := tst.Must(os.ReadFile("/home/mike/Pictures/Screenshot_20220706_190205.png"))(t) | 	b := tst.Must(os.ReadFile("test_placeholder.png"))(t) | ||||||
|  |  | ||||||
| 	mail := encodeMimeMail( | 	mail := encodeMimeMail( | ||||||
| 		"noreply@heydyno.de", | 		"noreply@heydyno.de", | ||||||
| @@ -73,5 +72,9 @@ func TestEncodeMimeMail4(t *testing.T) { | |||||||
| 			{Data: b, Filename: "img.png", IsInline: true, ContentType: "image/png"}, | 			{Data: b, Filename: "img.png", IsInline: true, ContentType: "image/png"}, | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 	fmt.Printf("%s\n\n", mail) | 	verifyMime(mail) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func verifyMime(mail string) { | ||||||
|  | 	//fmt.Printf("%s\n\n", mail) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -127,7 +127,7 @@ func TestSendMail4(t *testing.T) { | |||||||
|  |  | ||||||
| 	gclient := NewGoogleClient(auth) | 	gclient := NewGoogleClient(auth) | ||||||
|  |  | ||||||
| 	b := tst.Must(os.ReadFile("/home/mike/Pictures/Screenshot_20220706_190205.png"))(t) | 	b := tst.Must(os.ReadFile("test_placeholder.png"))(t) | ||||||
|  |  | ||||||
| 	mail, err := gclient.SendMail( | 	mail, err := gclient.SendMail( | ||||||
| 		ctx, | 		ctx, | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								googleapi/test_placeholder.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								googleapi/test_placeholder.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 11 KiB | 
| @@ -5,12 +5,15 @@ type Pagination struct { | |||||||
| 	Limit            int `json:"limit"`            // max-page-size | 	Limit            int `json:"limit"`            // max-page-size | ||||||
| 	TotalPages       int `json:"totalPages"`       // total page-count | 	TotalPages       int `json:"totalPages"`       // total page-count | ||||||
| 	TotalItems       int `json:"totalItems"`       // total items-count | 	TotalItems       int `json:"totalItems"`       // total items-count | ||||||
| 	CurrentPageCount int `json:"currntPageCount"` // item-count in current page ( == len(data) ) | 	CurrentPageCount int `json:"currentPageCount"` // item-count in current page ( == len(data) ) | ||||||
| } | } | ||||||
|  |  | ||||||
| func CalcPaginationTotalPages(totalItems int, limit int) int { | func CalcPaginationTotalPages(totalItems int, limit int) int { | ||||||
| 	if totalItems == 0 { | 	if totalItems == 0 { | ||||||
| 		return 0 | 		return 0 | ||||||
| 	} | 	} | ||||||
|  | 	if limit == 0 { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
| 	return 1 + (totalItems-1)/limit | 	return 1 + (totalItems-1)/limit | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								scn/scn.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								scn/scn.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | package scn | ||||||
|  |  | ||||||
|  | type Connection struct { | ||||||
|  | 	uid   string | ||||||
|  | 	token string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func New(userid string, token string) *Connection { | ||||||
|  | 	return &Connection{ | ||||||
|  | 		uid:   userid, | ||||||
|  | 		token: token, | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										171
									
								
								scn/send.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								scn/send.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,171 @@ | |||||||
|  | package scn | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
|  | 	json "gogs.mikescher.com/BlackForestBytes/goext/gojson" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	ErrAuthFailed        = exerr.NewType("GOEXT_SCN_AUTHFAILED", nil) | ||||||
|  | 	ErrQuota             = exerr.NewType("GOEXT_SCN_QUOTAEXCEEDED", nil) | ||||||
|  | 	ErrBadRequest        = exerr.NewType("GOEXT_SCN_BADREQUEST", nil) | ||||||
|  | 	ErrInternalServerErr = exerr.NewType("GOEXT_SCN_INTERNALSERVER", nil) | ||||||
|  | 	ErrOther             = exerr.NewType("GOEXT_SCN_OTHER", nil) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type MessageResponse struct { | ||||||
|  | 	ErrHighlight int    `json:"errhighlight"` | ||||||
|  | 	Error        int    `json:"error"` | ||||||
|  | 	IsPro        bool   `json:"is_pro"` | ||||||
|  | 	Message      string `json:"message"` | ||||||
|  | 	Messagecount int    `json:"messagecount"` | ||||||
|  | 	Quota        int    `json:"quota"` | ||||||
|  | 	QuotaMax     int    `json:"quota_max"` | ||||||
|  | 	SCNMessageID string `json:"scn_msg_id"` | ||||||
|  | 	Success      bool   `json:"success"` | ||||||
|  | 	SuppressSend bool   `json:"suppress_send"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type MessageErrResponse struct { | ||||||
|  | 	Errhighlight int    `json:"errhighlight"` | ||||||
|  | 	Error        int    `json:"error"` | ||||||
|  | 	Message      string `json:"message"` | ||||||
|  | 	Success      bool   `json:"success"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type MessageBuilder struct { | ||||||
|  | 	conn       *Connection | ||||||
|  | 	title      string | ||||||
|  | 	content    *string | ||||||
|  | 	channel    *string | ||||||
|  | 	time       *time.Time | ||||||
|  | 	sendername *string | ||||||
|  | 	priority   *int | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Connection) Message(title string) *MessageBuilder { | ||||||
|  | 	return &MessageBuilder{conn: c, title: title} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *MessageBuilder) Channel(channel string) *MessageBuilder { | ||||||
|  | 	c.channel = &channel | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *MessageBuilder) Content(content string) *MessageBuilder { | ||||||
|  | 	c.content = &content | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *MessageBuilder) Time(t time.Time) *MessageBuilder { | ||||||
|  | 	c.time = &t | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *MessageBuilder) SenderName(sn string) *MessageBuilder { | ||||||
|  | 	c.sendername = &sn | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *MessageBuilder) Priority(p int) *MessageBuilder { | ||||||
|  | 	c.priority = &p | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *MessageBuilder) Send(ctx context.Context) (MessageResponse, error) { | ||||||
|  | 	client := http.Client{Timeout: 5 * time.Second} | ||||||
|  |  | ||||||
|  | 	body := langext.H{} | ||||||
|  |  | ||||||
|  | 	body["user_id"] = c.conn.uid | ||||||
|  | 	body["key"] = c.conn.token | ||||||
|  |  | ||||||
|  | 	if c.channel != nil { | ||||||
|  | 		body["channel"] = *c.channel | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	body["title"] = c.title | ||||||
|  |  | ||||||
|  | 	if c.content != nil { | ||||||
|  | 		body["content"] = *c.content | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if c.sendername != nil { | ||||||
|  | 		body["content"] = *c.sendername | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if c.time != nil { | ||||||
|  | 		body["timestamp"] = c.time.Unix() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if c.priority != nil { | ||||||
|  | 		body["priority"] = *c.priority | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	body["msg_id"] = langext.MustHexUUID() | ||||||
|  |  | ||||||
|  | 	rawbody, err := json.Marshal(body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return MessageResponse{}, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req, err := http.NewRequestWithContext(ctx, "POST", "https://simplecloudnotifier.de/", bytes.NewBuffer(rawbody)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return MessageResponse{}, err | ||||||
|  | 	} | ||||||
|  | 	req.Header.Set("Content-Type", "application/json") | ||||||
|  |  | ||||||
|  | 	resp, err := client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return MessageResponse{}, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	defer func() { _ = resp.Body.Close() }() | ||||||
|  |  | ||||||
|  | 	if resp.StatusCode >= 200 && resp.StatusCode < 300 { | ||||||
|  |  | ||||||
|  | 		raw, err := io.ReadAll(resp.Body) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return MessageResponse{}, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		var mr MessageResponse | ||||||
|  | 		err = json.Unmarshal(raw, &mr) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return MessageResponse{}, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return mr, nil | ||||||
|  | 	} else { | ||||||
|  | 		errMessage := resp.Status | ||||||
|  |  | ||||||
|  | 		if raw, err := io.ReadAll(resp.Body); err == nil { | ||||||
|  | 			var mr MessageErrResponse | ||||||
|  | 			if err = json.Unmarshal(raw, &mr); err == nil { | ||||||
|  | 				errMessage = mr.Message | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if resp.StatusCode == 400 { | ||||||
|  | 			return MessageResponse{}, exerr.New(ErrBadRequest, errMessage).Build() | ||||||
|  | 		} | ||||||
|  | 		if resp.StatusCode == 401 { | ||||||
|  | 			return MessageResponse{}, exerr.New(ErrAuthFailed, errMessage).Build() | ||||||
|  | 		} | ||||||
|  | 		if resp.StatusCode == 403 { | ||||||
|  | 			return MessageResponse{}, exerr.New(ErrQuota, errMessage).Build() | ||||||
|  | 		} | ||||||
|  | 		if resp.StatusCode == 500 { | ||||||
|  | 			return MessageResponse{}, exerr.New(ErrInternalServerErr, errMessage).Build() | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return MessageResponse{}, exerr.New(ErrOther, errMessage).Build() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								scn/send_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								scn/send_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | package scn | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestSendSCN(t *testing.T) { | ||||||
|  | 	t.Skip() | ||||||
|  | 	return | ||||||
|  |  | ||||||
|  | 	mr, err := New("TODO", "TODO"). | ||||||
|  | 		Message("Hello Test"). | ||||||
|  | 		Content("This is a test / goext"). | ||||||
|  | 		Send(context.Background()) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fmt.Printf("%+v\n", mr) | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								sq/builder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								sq/builder.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | package sq | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/exerr" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func BuildUpdateStatement(q Queryable, tableName string, obj any, idColumn string) (string, PP, error) { | ||||||
|  | 	rval := reflect.ValueOf(obj) | ||||||
|  | 	rtyp := rval.Type() | ||||||
|  |  | ||||||
|  | 	params := PP{} | ||||||
|  |  | ||||||
|  | 	setClauses := make([]string, 0) | ||||||
|  |  | ||||||
|  | 	matchClause := "" | ||||||
|  |  | ||||||
|  | 	for i := 0; i < rtyp.NumField(); i++ { | ||||||
|  |  | ||||||
|  | 		rsfield := rtyp.Field(i) | ||||||
|  | 		rvfield := rval.Field(i) | ||||||
|  |  | ||||||
|  | 		if !rsfield.IsExported() { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		columnName := rsfield.Tag.Get("db") | ||||||
|  | 		if columnName == "" || columnName == "-" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if idColumn == columnName { | ||||||
|  | 			idValue, err := convertValueToDB(q, rvfield.Interface()) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return "", nil, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			matchClause = fmt.Sprintf("(%s = :%s)", columnName, params.Add(idValue)) | ||||||
|  |  | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if rsfield.Type.Kind() == reflect.Ptr && rvfield.IsNil() { | ||||||
|  |  | ||||||
|  | 			setClauses = append(setClauses, fmt.Sprintf("%s = NULL", columnName)) | ||||||
|  |  | ||||||
|  | 		} else { | ||||||
|  |  | ||||||
|  | 			val, err := convertValueToDB(q, rvfield.Interface()) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return "", nil, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			setClauses = append(setClauses, fmt.Sprintf("(%s = :%s)", columnName, params.Add(val))) | ||||||
|  |  | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(setClauses) == 0 { | ||||||
|  | 		return "", nil, exerr.New(exerr.TypeSQLBuild, "no updates clauses found in object").Build() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if matchClause == "" { | ||||||
|  | 		return "", nil, exerr.New(exerr.TypeSQLBuild, "id column not found in object").Build() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//goland:noinspection SqlNoDataSourceInspection | ||||||
|  | 	return fmt.Sprintf("UPDATE %s SET %s WHERE %s", tableName, strings.Join(setClauses, ", "), matchClause), params, nil | ||||||
|  | } | ||||||
							
								
								
									
										87
									
								
								sq/builder_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								sq/builder_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | package sq | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"database/sql" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/glebarez/go-sqlite" | ||||||
|  | 	"github.com/jmoiron/sqlx" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type dummyQueryable struct { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d dummyQueryable) Exec(ctx context.Context, sql string, prep PP) (sql.Result, error) { | ||||||
|  | 	return nil, errors.New("err") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d dummyQueryable) Query(ctx context.Context, sql string, prep PP) (*sqlx.Rows, error) { | ||||||
|  | 	return nil, errors.New("err") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d dummyQueryable) ListConverter() []DBTypeConverter { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestCreateUpdateStatement(t *testing.T) { | ||||||
|  |  | ||||||
|  | 	type request struct { | ||||||
|  | 		ID        string  `json:"id"        db:"id"` | ||||||
|  | 		Timestamp int     `json:"timestamp" db:"timestamp"` | ||||||
|  | 		StrVal    string  `json:"strVal"    db:"str_val"` | ||||||
|  | 		FloatVal  float64 `json:"floatVal"    db:"float_val"` | ||||||
|  | 		Dummy     bool    `json:"dummyBool" db:"dummy_bool"` | ||||||
|  | 		JsonVal   JsonObj `json:"jsonVal" db:"json_val"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !langext.InArray("sqlite3", sql.Drivers()) { | ||||||
|  | 		sqlite.RegisterAsSQLITE3() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx := context.Background() | ||||||
|  |  | ||||||
|  | 	dbdir := t.TempDir() | ||||||
|  | 	dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3") | ||||||
|  |  | ||||||
|  | 	url := fmt.Sprintf("file:%s?_pragma=journal_mode(%s)&_pragma=timeout(%d)&_pragma=foreign_keys(%s)&_pragma=busy_timeout(%d)", dbfile1, "DELETE", 1000, "true", 1000) | ||||||
|  |  | ||||||
|  | 	xdb := tst.Must(sqlx.Open("sqlite", url))(t) | ||||||
|  |  | ||||||
|  | 	db := NewDB(xdb) | ||||||
|  | 	db.RegisterDefaultConverter() | ||||||
|  |  | ||||||
|  | 	_, err := db.Exec(ctx, "CREATE TABLE `requests` ( id TEXT NOT NULL, timestamp INTEGER NOT NULL, PRIMARY KEY (id) ) STRICT", PP{}) | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
|  | 	sqlStr, pp, err := BuildUpdateStatement(db, "requests", request{ | ||||||
|  | 		ID:        "9927", | ||||||
|  | 		Timestamp: 12321, | ||||||
|  | 		StrVal:    "hello world", | ||||||
|  | 		Dummy:     true, | ||||||
|  | 		FloatVal:  3.14159, | ||||||
|  | 		JsonVal: JsonObj{ | ||||||
|  | 			"firs":   1, | ||||||
|  | 			"second": true, | ||||||
|  | 		}, | ||||||
|  | 	}, "id") | ||||||
|  |  | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
|  | 	fmt.Printf("\n\n") | ||||||
|  |  | ||||||
|  | 	fmt.Printf("######## PP:\n") | ||||||
|  | 	for k, v := range pp { | ||||||
|  | 		fmt.Printf("['%s']       =>       %+v\n", k, v) | ||||||
|  | 	} | ||||||
|  | 	fmt.Printf("\n\n") | ||||||
|  |  | ||||||
|  | 	fmt.Printf("######## SQL:\n%+v\n\n", sqlStr) | ||||||
|  |  | ||||||
|  | 	fmt.Printf("\n\n") | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										118
									
								
								sq/converter.go
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								sq/converter.go
									
									
									
									
									
								
							| @@ -1,20 +1,15 @@ | |||||||
| package sq | package sq | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/rfctime" | ||||||
|  | 	"reflect" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| //TODO UNFINISHED |  | ||||||
| // this is not finished |  | ||||||
| // idea was that we can register converter in the database struct |  | ||||||
| // they get inherited from the transactions |  | ||||||
| // and when marshallingunmarshaling (sq.Query | sq.QueryAll) |  | ||||||
| // or marshaling (sq.InsertSingle) |  | ||||||
| // the types get converter automatically... |  | ||||||
|  |  | ||||||
| type DBTypeConverter interface { | type DBTypeConverter interface { | ||||||
| 	ModelTypeString() string | 	ModelTypeString() string | ||||||
| 	DBTypeString() string | 	DBTypeString() string | ||||||
| @@ -40,16 +35,74 @@ var ConverterTimeToUnixMillis = NewDBTypeConverter[time.Time, int64](func(v time | |||||||
| 	return time.UnixMilli(v), nil | 	return time.UnixMilli(v), nil | ||||||
| }) | }) | ||||||
|  |  | ||||||
| var ConverterOptTimeToUnixMillis = NewDBTypeConverter[*time.Time, *int64](func(v *time.Time) (*int64, error) { | var ConverterRFCUnixMilliTimeToUnixMillis = NewDBTypeConverter[rfctime.UnixMilliTime, int64](func(v rfctime.UnixMilliTime) (int64, error) { | ||||||
| 	if v == nil { | 	return v.UnixMilli(), nil | ||||||
| 		return nil, nil | }, func(v int64) (rfctime.UnixMilliTime, error) { | ||||||
|  | 	return rfctime.NewUnixMilli(time.UnixMilli(v)), nil | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | var ConverterRFCUnixNanoTimeToUnixNanos = NewDBTypeConverter[rfctime.UnixNanoTime, int64](func(v rfctime.UnixNanoTime) (int64, error) { | ||||||
|  | 	return v.UnixNano(), nil | ||||||
|  | }, func(v int64) (rfctime.UnixNanoTime, error) { | ||||||
|  | 	return rfctime.NewUnixNano(time.Unix(0, v)), nil | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | var ConverterRFCUnixTimeToUnixSeconds = NewDBTypeConverter[rfctime.UnixTime, int64](func(v rfctime.UnixTime) (int64, error) { | ||||||
|  | 	return v.Unix(), nil | ||||||
|  | }, func(v int64) (rfctime.UnixTime, error) { | ||||||
|  | 	return rfctime.NewUnix(time.Unix(v, 0)), nil | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | // ConverterRFC339TimeToString | ||||||
|  | // Does not really use RFC339 - but sqlite does not understand timezones and the `T` delimiter | ||||||
|  | var ConverterRFC339TimeToString = NewDBTypeConverter[rfctime.RFC3339Time, string](func(v rfctime.RFC3339Time) (string, error) { | ||||||
|  | 	return v.Time().In(time.UTC).Format("2006-01-02 15:04:05"), nil | ||||||
|  | }, func(v string) (rfctime.RFC3339Time, error) { | ||||||
|  | 	t, err := time.Parse("2006-01-02 15:04:05", v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return rfctime.RFC3339Time{}, err | ||||||
| 	} | 	} | ||||||
| 	return langext.Ptr(v.UnixMilli()), nil | 	return rfctime.NewRFC3339(t), nil | ||||||
| }, func(v *int64) (*time.Time, error) { | }) | ||||||
| 	if v == nil { |  | ||||||
| 		return nil, nil | // ConverterRFC339NanoTimeToString | ||||||
|  | // Does not really use RFC339 - but sqlite does not understand timezones and the `T` delimiter | ||||||
|  | var ConverterRFC339NanoTimeToString = NewDBTypeConverter[rfctime.RFC3339NanoTime, string](func(v rfctime.RFC3339NanoTime) (string, error) { | ||||||
|  | 	return v.Time().In(time.UTC).Format("2006-01-02 15:04:05.999999999"), nil | ||||||
|  | }, func(v string) (rfctime.RFC3339NanoTime, error) { | ||||||
|  | 	t, err := time.ParseInLocation("2006-01-02 15:04:05.999999999", v, time.UTC) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return rfctime.RFC3339NanoTime{}, err | ||||||
| 	} | 	} | ||||||
| 	return langext.Ptr(time.UnixMilli(*v)), nil | 	return rfctime.NewRFC3339Nano(t), nil | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | var ConverterJsonObjToString = NewDBTypeConverter[JsonObj, string](func(v JsonObj) (string, error) { | ||||||
|  | 	mrsh, err := json.Marshal(v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return string(mrsh), nil | ||||||
|  | }, func(v string) (JsonObj, error) { | ||||||
|  | 	var mrsh JsonObj | ||||||
|  | 	if err := json.Unmarshal([]byte(v), &mrsh); err != nil { | ||||||
|  | 		return JsonObj{}, err | ||||||
|  | 	} | ||||||
|  | 	return mrsh, nil | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | var ConverterJsonArrToString = NewDBTypeConverter[JsonArr, string](func(v JsonArr) (string, error) { | ||||||
|  | 	mrsh, err := json.Marshal(v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return string(mrsh), nil | ||||||
|  | }, func(v string) (JsonArr, error) { | ||||||
|  | 	var mrsh JsonArr | ||||||
|  | 	if err := json.Unmarshal([]byte(v), &mrsh); err != nil { | ||||||
|  | 		return JsonArr{}, err | ||||||
|  | 	} | ||||||
|  | 	return mrsh, nil | ||||||
| }) | }) | ||||||
|  |  | ||||||
| type dbTypeConverterImpl[TModelData any, TDBData any] struct { | type dbTypeConverterImpl[TModelData any, TDBData any] struct { | ||||||
| @@ -89,3 +142,36 @@ func NewDBTypeConverter[TModelData any, TDBData any](todb func(v TModelData) (TD | |||||||
| 		tomodel:         tomodel, | 		tomodel:         tomodel, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func convertValueToDB(q Queryable, value any) (any, error) { | ||||||
|  | 	modelTypeStr := fmt.Sprintf("%T", value) | ||||||
|  |  | ||||||
|  | 	for _, conv := range q.ListConverter() { | ||||||
|  | 		if conv.ModelTypeString() == modelTypeStr { | ||||||
|  | 			return conv.ModelToDB(value) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if value != nil && reflect.TypeOf(value).Kind() == reflect.Ptr { | ||||||
|  | 		vof := reflect.ValueOf(value) | ||||||
|  | 		if vof.IsNil() { | ||||||
|  | 			return nil, nil | ||||||
|  | 		} else { | ||||||
|  | 			return convertValueToDB(q, vof.Elem().Interface()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return value, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func convertValueToModel(q Queryable, value any, destinationType string) (any, error) { | ||||||
|  | 	dbTypeString := fmt.Sprintf("%T", value) | ||||||
|  |  | ||||||
|  | 	for _, conv := range q.ListConverter() { | ||||||
|  | 		if conv.ModelTypeString() == destinationType && conv.DBTypeString() == dbTypeString { | ||||||
|  | 			return conv.DBToModel(value) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return value, nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -4,16 +4,19 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"database/sql" | 	"database/sql" | ||||||
| 	"github.com/jmoiron/sqlx" | 	"github.com/jmoiron/sqlx" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
| 	"sync" | 	"sync" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type DB interface { | type DB interface { | ||||||
| 	Exec(ctx context.Context, sql string, prep PP) (sql.Result, error) | 	Queryable | ||||||
| 	Query(ctx context.Context, sql string, prep PP) (*sqlx.Rows, error) |  | ||||||
| 	Ping(ctx context.Context) error | 	Ping(ctx context.Context) error | ||||||
| 	BeginTransaction(ctx context.Context, iso sql.IsolationLevel) (Tx, error) | 	BeginTransaction(ctx context.Context, iso sql.IsolationLevel) (Tx, error) | ||||||
| 	AddListener(listener Listener) | 	AddListener(listener Listener) | ||||||
| 	Exit() error | 	Exit() error | ||||||
|  | 	RegisterConverter(DBTypeConverter) | ||||||
|  | 	RegisterDefaultConverter() | ||||||
| } | } | ||||||
|  |  | ||||||
| type database struct { | type database struct { | ||||||
| @@ -21,6 +24,7 @@ type database struct { | |||||||
| 	txctr uint16 | 	txctr uint16 | ||||||
| 	lock  sync.Mutex | 	lock  sync.Mutex | ||||||
| 	lstr  []Listener | 	lstr  []Listener | ||||||
|  | 	conv  []DBTypeConverter | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewDB(db *sqlx.DB) DB { | func NewDB(db *sqlx.DB) DB { | ||||||
| @@ -120,9 +124,30 @@ func (db *database) BeginTransaction(ctx context.Context, iso sql.IsolationLevel | |||||||
| 		v.PostTxBegin(txid, err) | 		v.PostTxBegin(txid, err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return NewTransaction(xtx, txid, db.lstr), nil | 	return NewTransaction(xtx, txid, db), nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (db *database) Exit() error { | func (db *database) Exit() error { | ||||||
| 	return db.db.Close() | 	return db.db.Close() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (db *database) ListConverter() []DBTypeConverter { | ||||||
|  | 	return db.conv | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (db *database) RegisterConverter(conv DBTypeConverter) { | ||||||
|  | 	db.conv = langext.ArrFilter(db.conv, func(v DBTypeConverter) bool { return v.ModelTypeString() != conv.ModelTypeString() }) | ||||||
|  | 	db.conv = append(db.conv, conv) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (db *database) RegisterDefaultConverter() { | ||||||
|  | 	db.RegisterConverter(ConverterBoolToBit) | ||||||
|  | 	db.RegisterConverter(ConverterTimeToUnixMillis) | ||||||
|  | 	db.RegisterConverter(ConverterRFCUnixMilliTimeToUnixMillis) | ||||||
|  | 	db.RegisterConverter(ConverterRFCUnixNanoTimeToUnixNanos) | ||||||
|  | 	db.RegisterConverter(ConverterRFCUnixTimeToUnixSeconds) | ||||||
|  | 	db.RegisterConverter(ConverterRFC339TimeToString) | ||||||
|  | 	db.RegisterConverter(ConverterRFC339NanoTimeToString) | ||||||
|  | 	db.RegisterConverter(ConverterJsonObjToString) | ||||||
|  | 	db.RegisterConverter(ConverterJsonArrToString) | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								sq/hasher.go
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								sq/hasher.go
									
									
									
									
									
								
							| @@ -13,7 +13,9 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func HashSqliteSchema(ctx context.Context, schemaStr string) (string, error) { | // HashMattnSqliteSchema | ||||||
|  | // use if github.com/glebarez/go-sqlite | ||||||
|  | func HashMattnSqliteSchema(ctx context.Context, schemaStr string) (string, error) { | ||||||
| 	dbdir := os.TempDir() | 	dbdir := os.TempDir() | ||||||
| 	dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3") | 	dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3") | ||||||
|  |  | ||||||
| @@ -39,6 +41,34 @@ func HashSqliteSchema(ctx context.Context, schemaStr string) (string, error) { | |||||||
| 	return HashSqliteDatabase(ctx, db) | 	return HashSqliteDatabase(ctx, db) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // HashGoSqliteSchema | ||||||
|  | // use if mattn/go-sqlite3 | ||||||
|  | func HashGoSqliteSchema(ctx context.Context, schemaStr string) (string, error) { | ||||||
|  | 	dbdir := os.TempDir() | ||||||
|  | 	dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3") | ||||||
|  |  | ||||||
|  | 	err := os.MkdirAll(dbdir, os.ModePerm) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	url := fmt.Sprintf("file:%s?_pragma=journal_mode(%s)&_pragma=timeout(%d)&_pragma=foreign_keys(%s)&_pragma=busy_timeout(%d)", dbfile1, "DELETE", 1000, "true", 1000) | ||||||
|  |  | ||||||
|  | 	xdb, err := sqlx.Open("sqlite", url) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	db := NewDB(xdb) | ||||||
|  |  | ||||||
|  | 	_, err = db.Exec(ctx, schemaStr, PP{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return HashSqliteDatabase(ctx, db) | ||||||
|  | } | ||||||
|  |  | ||||||
| func HashSqliteDatabase(ctx context.Context, db Queryable) (string, error) { | func HashSqliteDatabase(ctx context.Context, db Queryable) (string, error) { | ||||||
| 	ss, err := CreateSqliteDatabaseSchemaString(ctx, db) | 	ss, err := CreateSqliteDatabaseSchemaString(ctx, db) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -91,7 +121,7 @@ func CreateSqliteDatabaseSchemaString(ctx context.Context, db Queryable) (string | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| 	tableList, err := ScanAll[tabInfo](rowsTableList, SModeFast, Unsafe, true) | 	tableList, err := ScanAll[tabInfo](ctx, db, rowsTableList, SModeFast, Unsafe, true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| @@ -113,7 +143,7 @@ func CreateSqliteDatabaseSchemaString(ctx context.Context, db Queryable) (string | |||||||
| 				return "", err | 				return "", err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			columnList, err := ScanAll[colInfo](rowsColumnList, SModeFast, Unsafe, true) | 			columnList, err := ScanAll[colInfo](ctx, db, rowsColumnList, SModeFast, Unsafe, true) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return "", err | 				return "", err | ||||||
| 			} | 			} | ||||||
| @@ -128,7 +158,7 @@ func CreateSqliteDatabaseSchemaString(ctx context.Context, db Queryable) (string | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return "", err | 				return "", err | ||||||
| 			} | 			} | ||||||
| 			idxList, err := ScanAll[idxInfo](rowsIdxList, SModeFast, Unsafe, true) | 			idxList, err := ScanAll[idxInfo](ctx, db, rowsIdxList, SModeFast, Unsafe, true) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return "", err | 				return "", err | ||||||
| 			} | 			} | ||||||
| @@ -143,7 +173,7 @@ func CreateSqliteDatabaseSchemaString(ctx context.Context, db Queryable) (string | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return "", err | 				return "", err | ||||||
| 			} | 			} | ||||||
| 			fkyList, err := ScanAll[fkyInfo](rowsIdxList, SModeFast, Unsafe, true) | 			fkyList, err := ScanAll[fkyInfo](ctx, db, rowsIdxList, SModeFast, Unsafe, true) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return "", err | 				return "", err | ||||||
| 			} | 			} | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								sq/json.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								sq/json.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | package sq | ||||||
|  |  | ||||||
|  | type JsonObj map[string]any | ||||||
|  |  | ||||||
|  | type JsonArr []any | ||||||
							
								
								
									
										12
									
								
								sq/params.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								sq/params.go
									
									
									
									
									
								
							| @@ -1,5 +1,7 @@ | |||||||
| package sq | package sq | ||||||
|  |  | ||||||
|  | import "gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
|  |  | ||||||
| type PP map[string]any | type PP map[string]any | ||||||
|  |  | ||||||
| func Join(pps ...PP) PP { | func Join(pps ...PP) PP { | ||||||
| @@ -11,3 +13,13 @@ func Join(pps ...PP) PP { | |||||||
| 	} | 	} | ||||||
| 	return r | 	return r | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (pp *PP) Add(v any) string { | ||||||
|  | 	id := PPID() | ||||||
|  | 	(*pp)[id] = v | ||||||
|  | 	return id | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func PPID() string { | ||||||
|  | 	return "p_" + langext.RandBase62(8) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -9,4 +9,5 @@ import ( | |||||||
| type Queryable interface { | type Queryable interface { | ||||||
| 	Exec(ctx context.Context, sql string, prep PP) (sql.Result, error) | 	Exec(ctx context.Context, sql string, prep PP) (sql.Result, error) | ||||||
| 	Query(ctx context.Context, sql string, prep PP) (*sqlx.Rows, error) | 	Query(ctx context.Context, sql string, prep PP) (*sqlx.Rows, error) | ||||||
|  | 	ListConverter() []DBTypeConverter | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,8 +13,8 @@ import ( | |||||||
| type StructScanMode string | type StructScanMode string | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	SModeFast     StructScanMode = "FAST" | 	SModeFast     StructScanMode = "FAST"     // Use default sq.Scan, does not work with joined/resolved types and/or custom value converter | ||||||
| 	SModeExtended StructScanMode = "EXTENDED" | 	SModeExtended StructScanMode = "EXTENDED" // Fully featured perhaps (?) a tiny bit slower - default | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type StructScanSafety string | type StructScanSafety string | ||||||
| @@ -51,7 +51,13 @@ func InsertSingle[TData any](ctx context.Context, q Queryable, tableName string, | |||||||
|  |  | ||||||
| 		columns = append(columns, "\""+columnName+"\"") | 		columns = append(columns, "\""+columnName+"\"") | ||||||
| 		params = append(params, ":"+paramkey) | 		params = append(params, ":"+paramkey) | ||||||
| 		pp[paramkey] = rvfield.Interface() |  | ||||||
|  | 		val, err := convertValueToDB(q, rvfield.Interface()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		pp[paramkey] = val | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -71,7 +77,7 @@ func QuerySingle[TData any](ctx context.Context, q Queryable, sql string, pp PP, | |||||||
| 		return *new(TData), err | 		return *new(TData), err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	data, err := ScanSingle[TData](rows, mode, sec, true) | 	data, err := ScanSingle[TData](ctx, q, rows, mode, sec, true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return *new(TData), err | 		return *new(TData), err | ||||||
| 	} | 	} | ||||||
| @@ -85,7 +91,7 @@ func QueryAll[TData any](ctx context.Context, q Queryable, sql string, pp PP, mo | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	data, err := ScanAll[TData](rows, mode, sec, true) | 	data, err := ScanAll[TData](ctx, q, rows, mode, sec, true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -93,7 +99,7 @@ func QueryAll[TData any](ctx context.Context, q Queryable, sql string, pp PP, mo | |||||||
| 	return data, nil | 	return data, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func ScanSingle[TData any](rows *sqlx.Rows, mode StructScanMode, sec StructScanSafety, close bool) (TData, error) { | func ScanSingle[TData any](ctx context.Context, q Queryable, rows *sqlx.Rows, mode StructScanMode, sec StructScanSafety, close bool) (TData, error) { | ||||||
| 	if rows.Next() { | 	if rows.Next() { | ||||||
| 		var strscan *StructScanner | 		var strscan *StructScanner | ||||||
|  |  | ||||||
| @@ -123,7 +129,7 @@ func ScanSingle[TData any](rows *sqlx.Rows, mode StructScanMode, sec StructScanS | |||||||
| 				return *new(TData), err | 				return *new(TData), err | ||||||
| 			} | 			} | ||||||
| 		} else if mode == SModeExtended { | 		} else if mode == SModeExtended { | ||||||
| 			err := strscan.StructScanExt(&data) | 			err := strscan.StructScanExt(q, &data) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return *new(TData), err | 				return *new(TData), err | ||||||
| 			} | 			} | ||||||
| @@ -149,6 +155,10 @@ func ScanSingle[TData any](rows *sqlx.Rows, mode StructScanMode, sec StructScanS | |||||||
| 			return *new(TData), err | 			return *new(TData), err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if err := ctx.Err(); err != nil { | ||||||
|  | 			return *new(TData), err | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		return data, nil | 		return data, nil | ||||||
|  |  | ||||||
| 	} else { | 	} else { | ||||||
| @@ -159,7 +169,7 @@ func ScanSingle[TData any](rows *sqlx.Rows, mode StructScanMode, sec StructScanS | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func ScanAll[TData any](rows *sqlx.Rows, mode StructScanMode, sec StructScanSafety, close bool) ([]TData, error) { | func ScanAll[TData any](ctx context.Context, q Queryable, rows *sqlx.Rows, mode StructScanMode, sec StructScanSafety, close bool) ([]TData, error) { | ||||||
| 	var strscan *StructScanner | 	var strscan *StructScanner | ||||||
|  |  | ||||||
| 	if sec == Safe { | 	if sec == Safe { | ||||||
| @@ -182,6 +192,11 @@ func ScanAll[TData any](rows *sqlx.Rows, mode StructScanMode, sec StructScanSafe | |||||||
|  |  | ||||||
| 	res := make([]TData, 0) | 	res := make([]TData, 0) | ||||||
| 	for rows.Next() { | 	for rows.Next() { | ||||||
|  |  | ||||||
|  | 		if err := ctx.Err(); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		if mode == SModeFast { | 		if mode == SModeFast { | ||||||
| 			var data TData | 			var data TData | ||||||
| 			err := strscan.StructScanBase(&data) | 			err := strscan.StructScanBase(&data) | ||||||
| @@ -191,7 +206,7 @@ func ScanAll[TData any](rows *sqlx.Rows, mode StructScanMode, sec StructScanSafe | |||||||
| 			res = append(res, data) | 			res = append(res, data) | ||||||
| 		} else if mode == SModeExtended { | 		} else if mode == SModeExtended { | ||||||
| 			var data TData | 			var data TData | ||||||
| 			err := strscan.StructScanExt(&data) | 			err := strscan.StructScanExt(q, &data) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
|   | |||||||
							
								
								
									
										95
									
								
								sq/sq_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								sq/sq_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | |||||||
|  | package sq | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"database/sql" | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/glebarez/go-sqlite" | ||||||
|  | 	"github.com/jmoiron/sqlx" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/rfctime" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestTypeConverter1(t *testing.T) { | ||||||
|  | 	type RequestData struct { | ||||||
|  | 		ID        string    `db:"id"` | ||||||
|  | 		Timestamp time.Time `db:"timestamp"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !langext.InArray("sqlite3", sql.Drivers()) { | ||||||
|  | 		sqlite.RegisterAsSQLITE3() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx := context.Background() | ||||||
|  |  | ||||||
|  | 	dbdir := t.TempDir() | ||||||
|  | 	dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3") | ||||||
|  |  | ||||||
|  | 	tst.AssertNoErr(t, os.MkdirAll(dbdir, os.ModePerm)) | ||||||
|  |  | ||||||
|  | 	url := fmt.Sprintf("file:%s?_pragma=journal_mode(%s)&_pragma=timeout(%d)&_pragma=foreign_keys(%s)&_pragma=busy_timeout(%d)", dbfile1, "DELETE", 1000, "true", 1000) | ||||||
|  |  | ||||||
|  | 	xdb := tst.Must(sqlx.Open("sqlite", url))(t) | ||||||
|  |  | ||||||
|  | 	db := NewDB(xdb) | ||||||
|  | 	db.RegisterDefaultConverter() | ||||||
|  |  | ||||||
|  | 	_, err := db.Exec(ctx, "CREATE TABLE `requests` ( id TEXT NOT NULL, timestamp INTEGER NOT NULL, PRIMARY KEY (id) ) STRICT", PP{}) | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
|  | 	_, err = InsertSingle(ctx, db, "requests", RequestData{ | ||||||
|  | 		ID:        "001", | ||||||
|  | 		Timestamp: time.Date(2000, 06, 15, 12, 0, 0, 0, time.UTC), | ||||||
|  | 	}) | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTypeConverter2(t *testing.T) { | ||||||
|  |  | ||||||
|  | 	if !langext.InArray("sqlite3", sql.Drivers()) { | ||||||
|  | 		sqlite.RegisterAsSQLITE3() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	type RequestData struct { | ||||||
|  | 		ID        string                `db:"id"` | ||||||
|  | 		Timestamp rfctime.UnixMilliTime `db:"timestamp"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx := context.Background() | ||||||
|  |  | ||||||
|  | 	dbdir := t.TempDir() | ||||||
|  | 	dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3") | ||||||
|  |  | ||||||
|  | 	tst.AssertNoErr(t, os.MkdirAll(dbdir, os.ModePerm)) | ||||||
|  |  | ||||||
|  | 	url := fmt.Sprintf("file:%s?_pragma=journal_mode(%s)&_pragma=timeout(%d)&_pragma=foreign_keys(%s)&_pragma=busy_timeout(%d)", dbfile1, "DELETE", 1000, "true", 1000) | ||||||
|  |  | ||||||
|  | 	xdb := tst.Must(sqlx.Open("sqlite", url))(t) | ||||||
|  |  | ||||||
|  | 	db := NewDB(xdb) | ||||||
|  | 	db.RegisterDefaultConverter() | ||||||
|  |  | ||||||
|  | 	_, err := db.Exec(ctx, "CREATE TABLE `requests` ( id TEXT NOT NULL, timestamp INTEGER NOT NULL, PRIMARY KEY (id) ) STRICT", PP{}) | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
|  | 	t0 := rfctime.NewUnixMilli(time.Date(2012, 03, 01, 16, 0, 0, 0, time.UTC)) | ||||||
|  |  | ||||||
|  | 	_, err = InsertSingle(ctx, db, "requests", RequestData{ | ||||||
|  | 		ID:        "002", | ||||||
|  | 		Timestamp: t0, | ||||||
|  | 	}) | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
|  | 	r, err := QuerySingle[RequestData](ctx, db, "SELECT * FROM requests WHERE id = '002'", PP{}, SModeExtended, Safe) | ||||||
|  | 	tst.AssertNoErr(t, err) | ||||||
|  |  | ||||||
|  | 	fmt.Printf("%+v\n", r) | ||||||
|  |  | ||||||
|  | 	tst.AssertEqual(t, "002", r.ID) | ||||||
|  | 	tst.AssertEqual(t, t0.UnixNano(), r.Timestamp.UnixNano()) | ||||||
|  | } | ||||||
| @@ -5,6 +5,7 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/jmoiron/sqlx" | 	"github.com/jmoiron/sqlx" | ||||||
| 	"github.com/jmoiron/sqlx/reflectx" | 	"github.com/jmoiron/sqlx/reflectx" | ||||||
|  | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -17,6 +18,7 @@ type StructScanner struct { | |||||||
|  |  | ||||||
| 	fields    [][]int | 	fields    [][]int | ||||||
| 	values    []any | 	values    []any | ||||||
|  | 	converter []DBTypeConverter | ||||||
| 	columns   []string | 	columns   []string | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -47,13 +49,15 @@ func (r *StructScanner) Start(dest any) error { | |||||||
| 		return fmt.Errorf("missing destination name %s in %T", columns[f], dest) | 		return fmt.Errorf("missing destination name %s in %T", columns[f], dest) | ||||||
| 	} | 	} | ||||||
| 	r.values = make([]interface{}, len(columns)) | 	r.values = make([]interface{}, len(columns)) | ||||||
|  | 	r.converter = make([]DBTypeConverter, len(columns)) | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // StructScanExt forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go | // StructScanExt forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go | ||||||
| // does also wok with nullabel structs (from LEFT JOIN's) | // does also work with nullabel structs (from LEFT JOIN's) | ||||||
| func (r *StructScanner) StructScanExt(dest any) error { | // does also work with custom value converters | ||||||
|  | func (r *StructScanner) StructScanExt(q Queryable, dest any) error { | ||||||
| 	v := reflect.ValueOf(dest) | 	v := reflect.ValueOf(dest) | ||||||
|  |  | ||||||
| 	if v.Kind() != reflect.Ptr { | 	if v.Kind() != reflect.Ptr { | ||||||
| @@ -64,7 +68,7 @@ func (r *StructScanner) StructScanExt(dest any) error { | |||||||
|  |  | ||||||
| 	v = v.Elem() | 	v = v.Elem() | ||||||
|  |  | ||||||
| 	err := fieldsByTraversalExtended(v, r.fields, r.values) | 	err := fieldsByTraversalExtended(q, v, r.fields, r.values, r.converter) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -131,7 +135,6 @@ func (r *StructScanner) StructScanExt(dest any) error { | |||||||
|  |  | ||||||
| 		val1 := reflect.ValueOf(r.values[i]) | 		val1 := reflect.ValueOf(r.values[i]) | ||||||
| 		val2 := val1.Elem() | 		val2 := val1.Elem() | ||||||
| 		val3 := val2.Elem() |  | ||||||
|  |  | ||||||
| 		if val2.IsNil() { | 		if val2.IsNil() { | ||||||
| 			if f.Kind() != reflect.Pointer { | 			if f.Kind() != reflect.Pointer { | ||||||
| @@ -140,7 +143,16 @@ func (r *StructScanner) StructScanExt(dest any) error { | |||||||
|  |  | ||||||
| 			f.Set(reflect.Zero(f.Type())) // set to nil | 			f.Set(reflect.Zero(f.Type())) // set to nil | ||||||
| 		} else { | 		} else { | ||||||
| 			f.Set(val3) | 			if r.converter[i] != nil { | ||||||
|  | 				val3 := val2.Elem().Interface() | ||||||
|  | 				conv3, err := r.converter[i].DBToModel(val3) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				f.Set(reflect.ValueOf(conv3)) | ||||||
|  | 			} else { | ||||||
|  | 				f.Set(val2.Elem()) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| @@ -172,7 +184,7 @@ func (r *StructScanner) StructScanBase(dest any) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| // fieldsByTraversal forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go | // fieldsByTraversal forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go | ||||||
| func fieldsByTraversalExtended(v reflect.Value, traversals [][]int, values []interface{}) error { | func fieldsByTraversalExtended(q Queryable, v reflect.Value, traversals [][]int, values []interface{}, converter []DBTypeConverter) error { | ||||||
| 	v = reflect.Indirect(v) | 	v = reflect.Indirect(v) | ||||||
| 	if v.Kind() != reflect.Struct { | 	if v.Kind() != reflect.Struct { | ||||||
| 		return errors.New("argument not a struct") | 		return errors.New("argument not a struct") | ||||||
| @@ -185,7 +197,23 @@ func fieldsByTraversalExtended(v reflect.Value, traversals [][]int, values []int | |||||||
| 		} | 		} | ||||||
| 		f := reflectx.FieldByIndexes(v, traversal) | 		f := reflectx.FieldByIndexes(v, traversal) | ||||||
|  |  | ||||||
|  | 		typeStr := f.Type().String() | ||||||
|  |  | ||||||
|  | 		foundConverter := false | ||||||
|  | 		for _, conv := range q.ListConverter() { | ||||||
|  | 			if conv.ModelTypeString() == typeStr { | ||||||
|  | 				_v := langext.Ptr[any](nil) | ||||||
|  | 				values[i] = _v | ||||||
|  | 				foundConverter = true | ||||||
|  | 				converter[i] = conv | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !foundConverter { | ||||||
| 			values[i] = reflect.New(reflect.PointerTo(f.Type())).Interface() | 			values[i] = reflect.New(reflect.PointerTo(f.Type())).Interface() | ||||||
|  | 			converter[i] = nil | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,35 +17,35 @@ const ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| type Tx interface { | type Tx interface { | ||||||
|  | 	Queryable | ||||||
|  |  | ||||||
| 	Rollback() error | 	Rollback() error | ||||||
| 	Commit() error | 	Commit() error | ||||||
| 	Status() TxStatus | 	Status() TxStatus | ||||||
| 	Exec(ctx context.Context, sql string, prep PP) (sql.Result, error) |  | ||||||
| 	Query(ctx context.Context, sql string, prep PP) (*sqlx.Rows, error) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type transaction struct { | type transaction struct { | ||||||
| 	tx       *sqlx.Tx | 	tx       *sqlx.Tx | ||||||
| 	id       uint16 | 	id       uint16 | ||||||
| 	lstr     []Listener |  | ||||||
| 	status   TxStatus | 	status   TxStatus | ||||||
| 	execCtr  int | 	execCtr  int | ||||||
| 	queryCtr int | 	queryCtr int | ||||||
|  | 	db       *database | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewTransaction(xtx *sqlx.Tx, txid uint16, lstr []Listener) Tx { | func NewTransaction(xtx *sqlx.Tx, txid uint16, db *database) Tx { | ||||||
| 	return &transaction{ | 	return &transaction{ | ||||||
| 		tx:       xtx, | 		tx:       xtx, | ||||||
| 		id:       txid, | 		id:       txid, | ||||||
| 		lstr:     lstr, |  | ||||||
| 		status:   TxStatusInitial, | 		status:   TxStatusInitial, | ||||||
| 		execCtr:  0, | 		execCtr:  0, | ||||||
| 		queryCtr: 0, | 		queryCtr: 0, | ||||||
|  | 		db:       db, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (tx *transaction) Rollback() error { | func (tx *transaction) Rollback() error { | ||||||
| 	for _, v := range tx.lstr { | 	for _, v := range tx.db.lstr { | ||||||
| 		err := v.PreTxRollback(tx.id) | 		err := v.PreTxRollback(tx.id) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| @@ -58,7 +58,7 @@ func (tx *transaction) Rollback() error { | |||||||
| 		tx.status = TxStatusRollback | 		tx.status = TxStatusRollback | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, v := range tx.lstr { | 	for _, v := range tx.db.lstr { | ||||||
| 		v.PostTxRollback(tx.id, result) | 		v.PostTxRollback(tx.id, result) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -66,7 +66,7 @@ func (tx *transaction) Rollback() error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (tx *transaction) Commit() error { | func (tx *transaction) Commit() error { | ||||||
| 	for _, v := range tx.lstr { | 	for _, v := range tx.db.lstr { | ||||||
| 		err := v.PreTxCommit(tx.id) | 		err := v.PreTxCommit(tx.id) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| @@ -79,7 +79,7 @@ func (tx *transaction) Commit() error { | |||||||
| 		tx.status = TxStatusComitted | 		tx.status = TxStatusComitted | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, v := range tx.lstr { | 	for _, v := range tx.db.lstr { | ||||||
| 		v.PostTxRollback(tx.id, result) | 		v.PostTxRollback(tx.id, result) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -88,7 +88,7 @@ func (tx *transaction) Commit() error { | |||||||
|  |  | ||||||
| func (tx *transaction) Exec(ctx context.Context, sqlstr string, prep PP) (sql.Result, error) { | func (tx *transaction) Exec(ctx context.Context, sqlstr string, prep PP) (sql.Result, error) { | ||||||
| 	origsql := sqlstr | 	origsql := sqlstr | ||||||
| 	for _, v := range tx.lstr { | 	for _, v := range tx.db.lstr { | ||||||
| 		err := v.PreExec(ctx, langext.Ptr(tx.id), &sqlstr, &prep) | 		err := v.PreExec(ctx, langext.Ptr(tx.id), &sqlstr, &prep) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| @@ -101,7 +101,7 @@ func (tx *transaction) Exec(ctx context.Context, sqlstr string, prep PP) (sql.Re | |||||||
| 		tx.status = TxStatusActive | 		tx.status = TxStatusActive | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, v := range tx.lstr { | 	for _, v := range tx.db.lstr { | ||||||
| 		v.PostExec(langext.Ptr(tx.id), origsql, sqlstr, prep) | 		v.PostExec(langext.Ptr(tx.id), origsql, sqlstr, prep) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -113,7 +113,7 @@ func (tx *transaction) Exec(ctx context.Context, sqlstr string, prep PP) (sql.Re | |||||||
|  |  | ||||||
| func (tx *transaction) Query(ctx context.Context, sqlstr string, prep PP) (*sqlx.Rows, error) { | func (tx *transaction) Query(ctx context.Context, sqlstr string, prep PP) (*sqlx.Rows, error) { | ||||||
| 	origsql := sqlstr | 	origsql := sqlstr | ||||||
| 	for _, v := range tx.lstr { | 	for _, v := range tx.db.lstr { | ||||||
| 		err := v.PreQuery(ctx, langext.Ptr(tx.id), &sqlstr, &prep) | 		err := v.PreQuery(ctx, langext.Ptr(tx.id), &sqlstr, &prep) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| @@ -126,7 +126,7 @@ func (tx *transaction) Query(ctx context.Context, sqlstr string, prep PP) (*sqlx | |||||||
| 		tx.status = TxStatusActive | 		tx.status = TxStatusActive | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, v := range tx.lstr { | 	for _, v := range tx.db.lstr { | ||||||
| 		v.PostQuery(langext.Ptr(tx.id), origsql, sqlstr, prep) | 		v.PostQuery(langext.Ptr(tx.id), origsql, sqlstr, prep) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -140,6 +140,10 @@ func (tx *transaction) Status() TxStatus { | |||||||
| 	return tx.status | 	return tx.status | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (tx *transaction) ListConverter() []DBTypeConverter { | ||||||
|  | 	return tx.db.conv | ||||||
|  | } | ||||||
|  |  | ||||||
| func (tx *transaction) Traffic() (int, int) { | func (tx *transaction) Traffic() (int, int) { | ||||||
| 	return tx.execCtr, tx.queryCtr | 	return tx.execCtr, tx.queryCtr | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user