Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
d08b2e565a
|
|||
d29e84894d
|
|||
617298c366
|
|||
668f308565 |
@@ -9,6 +9,7 @@ import (
|
|||||||
"gogs.mikescher.com/BlackForestBytes/goext/rext"
|
"gogs.mikescher.com/BlackForestBytes/goext/rext"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -16,7 +17,7 @@ import (
|
|||||||
|
|
||||||
type GinWrapper struct {
|
type GinWrapper struct {
|
||||||
engine *gin.Engine
|
engine *gin.Engine
|
||||||
SuppressGinLogs bool
|
suppressGinLogs bool
|
||||||
|
|
||||||
allowCors bool
|
allowCors bool
|
||||||
ginDebug bool
|
ginDebug bool
|
||||||
@@ -50,7 +51,7 @@ func NewEngine(opt Options) *GinWrapper {
|
|||||||
|
|
||||||
wrapper := &GinWrapper{
|
wrapper := &GinWrapper{
|
||||||
engine: engine,
|
engine: engine,
|
||||||
SuppressGinLogs: false,
|
suppressGinLogs: false,
|
||||||
allowCors: langext.Coalesce(opt.AllowCors, false),
|
allowCors: langext.Coalesce(opt.AllowCors, false),
|
||||||
ginDebug: langext.Coalesce(opt.GinDebug, true),
|
ginDebug: langext.Coalesce(opt.GinDebug, true),
|
||||||
bufferBody: langext.Coalesce(opt.BufferBody, false),
|
bufferBody: langext.Coalesce(opt.BufferBody, false),
|
||||||
@@ -74,7 +75,7 @@ func NewEngine(opt Options) *GinWrapper {
|
|||||||
|
|
||||||
ginlogger := gin.Logger()
|
ginlogger := gin.Logger()
|
||||||
engine.Use(func(context *gin.Context) {
|
engine.Use(func(context *gin.Context) {
|
||||||
if !wrapper.SuppressGinLogs {
|
if !wrapper.suppressGinLogs {
|
||||||
ginlogger(context)
|
ginlogger(context)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -185,3 +186,10 @@ func (w *GinWrapper) cleanMiddlewareName(fname string) string {
|
|||||||
|
|
||||||
return fname
|
return fname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServeHTTP only used for unit tests
|
||||||
|
func (w *GinWrapper) ServeHTTP(req *http.Request) *httptest.ResponseRecorder {
|
||||||
|
respRec := httptest.NewRecorder()
|
||||||
|
w.engine.ServeHTTP(respRec, req)
|
||||||
|
return respRec
|
||||||
|
}
|
||||||
|
@@ -9,6 +9,16 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type cookieval struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
maxAge int
|
||||||
|
path string
|
||||||
|
domain string
|
||||||
|
secure bool
|
||||||
|
httpOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
type headerval struct {
|
type headerval struct {
|
||||||
Key string
|
Key string
|
||||||
Val string
|
Val string
|
||||||
@@ -17,6 +27,7 @@ type headerval struct {
|
|||||||
type HTTPResponse interface {
|
type HTTPResponse interface {
|
||||||
Write(g *gin.Context)
|
Write(g *gin.Context)
|
||||||
WithHeader(k string, v string) HTTPResponse
|
WithHeader(k string, v string) HTTPResponse
|
||||||
|
WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse
|
||||||
IsSuccess() bool
|
IsSuccess() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +44,7 @@ type jsonHTTPResponse struct {
|
|||||||
statusCode int
|
statusCode int
|
||||||
data any
|
data any
|
||||||
headers []headerval
|
headers []headerval
|
||||||
|
cookies []cookieval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j jsonHTTPResponse) jsonRenderer(g *gin.Context) json.GoJsonRender {
|
func (j jsonHTTPResponse) jsonRenderer(g *gin.Context) json.GoJsonRender {
|
||||||
@@ -47,6 +59,9 @@ func (j jsonHTTPResponse) Write(g *gin.Context) {
|
|||||||
for _, v := range j.headers {
|
for _, v := range j.headers {
|
||||||
g.Header(v.Key, v.Val)
|
g.Header(v.Key, v.Val)
|
||||||
}
|
}
|
||||||
|
for _, v := range j.cookies {
|
||||||
|
g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly)
|
||||||
|
}
|
||||||
g.Render(j.statusCode, j.jsonRenderer(g))
|
g.Render(j.statusCode, j.jsonRenderer(g))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +70,11 @@ func (j jsonHTTPResponse) WithHeader(k string, v string) HTTPResponse {
|
|||||||
return j
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j jsonHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse {
|
||||||
|
j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly})
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
func (j jsonHTTPResponse) IsSuccess() bool {
|
func (j jsonHTTPResponse) IsSuccess() bool {
|
||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
@@ -82,12 +102,16 @@ func (j jsonHTTPResponse) Headers() []string {
|
|||||||
type emptyHTTPResponse struct {
|
type emptyHTTPResponse struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
headers []headerval
|
headers []headerval
|
||||||
|
cookies []cookieval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j emptyHTTPResponse) Write(g *gin.Context) {
|
func (j emptyHTTPResponse) Write(g *gin.Context) {
|
||||||
for _, v := range j.headers {
|
for _, v := range j.headers {
|
||||||
g.Header(v.Key, v.Val)
|
g.Header(v.Key, v.Val)
|
||||||
}
|
}
|
||||||
|
for _, v := range j.cookies {
|
||||||
|
g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly)
|
||||||
|
}
|
||||||
g.Status(j.statusCode)
|
g.Status(j.statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +120,11 @@ func (j emptyHTTPResponse) WithHeader(k string, v string) HTTPResponse {
|
|||||||
return j
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j emptyHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse {
|
||||||
|
j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly})
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
func (j emptyHTTPResponse) IsSuccess() bool {
|
func (j emptyHTTPResponse) IsSuccess() bool {
|
||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
@@ -120,12 +149,16 @@ type textHTTPResponse struct {
|
|||||||
statusCode int
|
statusCode int
|
||||||
data string
|
data string
|
||||||
headers []headerval
|
headers []headerval
|
||||||
|
cookies []cookieval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j textHTTPResponse) Write(g *gin.Context) {
|
func (j textHTTPResponse) Write(g *gin.Context) {
|
||||||
for _, v := range j.headers {
|
for _, v := range j.headers {
|
||||||
g.Header(v.Key, v.Val)
|
g.Header(v.Key, v.Val)
|
||||||
}
|
}
|
||||||
|
for _, v := range j.cookies {
|
||||||
|
g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly)
|
||||||
|
}
|
||||||
g.String(j.statusCode, "%s", j.data)
|
g.String(j.statusCode, "%s", j.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +167,11 @@ func (j textHTTPResponse) WithHeader(k string, v string) HTTPResponse {
|
|||||||
return j
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j textHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse {
|
||||||
|
j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly})
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
func (j textHTTPResponse) IsSuccess() bool {
|
func (j textHTTPResponse) IsSuccess() bool {
|
||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
@@ -159,12 +197,16 @@ type dataHTTPResponse struct {
|
|||||||
data []byte
|
data []byte
|
||||||
contentType string
|
contentType string
|
||||||
headers []headerval
|
headers []headerval
|
||||||
|
cookies []cookieval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j dataHTTPResponse) Write(g *gin.Context) {
|
func (j dataHTTPResponse) Write(g *gin.Context) {
|
||||||
for _, v := range j.headers {
|
for _, v := range j.headers {
|
||||||
g.Header(v.Key, v.Val)
|
g.Header(v.Key, v.Val)
|
||||||
}
|
}
|
||||||
|
for _, v := range j.cookies {
|
||||||
|
g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly)
|
||||||
|
}
|
||||||
g.Data(j.statusCode, j.contentType, j.data)
|
g.Data(j.statusCode, j.contentType, j.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +215,11 @@ func (j dataHTTPResponse) WithHeader(k string, v string) HTTPResponse {
|
|||||||
return j
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j dataHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse {
|
||||||
|
j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly})
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
func (j dataHTTPResponse) IsSuccess() bool {
|
func (j dataHTTPResponse) IsSuccess() bool {
|
||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
@@ -198,6 +245,7 @@ type fileHTTPResponse struct {
|
|||||||
filepath string
|
filepath string
|
||||||
filename *string
|
filename *string
|
||||||
headers []headerval
|
headers []headerval
|
||||||
|
cookies []cookieval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j fileHTTPResponse) Write(g *gin.Context) {
|
func (j fileHTTPResponse) Write(g *gin.Context) {
|
||||||
@@ -209,6 +257,9 @@ func (j fileHTTPResponse) Write(g *gin.Context) {
|
|||||||
for _, v := range j.headers {
|
for _, v := range j.headers {
|
||||||
g.Header(v.Key, v.Val)
|
g.Header(v.Key, v.Val)
|
||||||
}
|
}
|
||||||
|
for _, v := range j.cookies {
|
||||||
|
g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly)
|
||||||
|
}
|
||||||
g.File(j.filepath)
|
g.File(j.filepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +268,11 @@ func (j fileHTTPResponse) WithHeader(k string, v string) HTTPResponse {
|
|||||||
return j
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j fileHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse {
|
||||||
|
j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly})
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
func (j fileHTTPResponse) IsSuccess() bool {
|
func (j fileHTTPResponse) IsSuccess() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -247,17 +303,20 @@ type downloadDataHTTPResponse struct {
|
|||||||
data []byte
|
data []byte
|
||||||
filename *string
|
filename *string
|
||||||
headers []headerval
|
headers []headerval
|
||||||
|
cookies []cookieval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j downloadDataHTTPResponse) Write(g *gin.Context) {
|
func (j downloadDataHTTPResponse) Write(g *gin.Context) {
|
||||||
g.Header("Content-Type", j.mimetype) // if we don't set it here gin does weird file-sniffing later...
|
g.Header("Content-Type", j.mimetype) // if we don't set it here gin does weird file-sniffing later...
|
||||||
if j.filename != nil {
|
if j.filename != nil {
|
||||||
g.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *j.filename))
|
g.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *j.filename))
|
||||||
|
|
||||||
}
|
}
|
||||||
for _, v := range j.headers {
|
for _, v := range j.headers {
|
||||||
g.Header(v.Key, v.Val)
|
g.Header(v.Key, v.Val)
|
||||||
}
|
}
|
||||||
|
for _, v := range j.cookies {
|
||||||
|
g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly)
|
||||||
|
}
|
||||||
g.Data(j.statusCode, j.mimetype, j.data)
|
g.Data(j.statusCode, j.mimetype, j.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,6 +325,11 @@ func (j downloadDataHTTPResponse) WithHeader(k string, v string) HTTPResponse {
|
|||||||
return j
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j downloadDataHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse {
|
||||||
|
j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly})
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
func (j downloadDataHTTPResponse) IsSuccess() bool {
|
func (j downloadDataHTTPResponse) IsSuccess() bool {
|
||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
@@ -290,9 +354,16 @@ type redirectHTTPResponse struct {
|
|||||||
statusCode int
|
statusCode int
|
||||||
url string
|
url string
|
||||||
headers []headerval
|
headers []headerval
|
||||||
|
cookies []cookieval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j redirectHTTPResponse) Write(g *gin.Context) {
|
func (j redirectHTTPResponse) Write(g *gin.Context) {
|
||||||
|
for _, v := range j.headers {
|
||||||
|
g.Header(v.Key, v.Val)
|
||||||
|
}
|
||||||
|
for _, v := range j.cookies {
|
||||||
|
g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly)
|
||||||
|
}
|
||||||
g.Redirect(j.statusCode, j.url)
|
g.Redirect(j.statusCode, j.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,6 +372,11 @@ func (j redirectHTTPResponse) WithHeader(k string, v string) HTTPResponse {
|
|||||||
return j
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j redirectHTTPResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse {
|
||||||
|
j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly})
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
func (j redirectHTTPResponse) IsSuccess() bool {
|
func (j redirectHTTPResponse) IsSuccess() bool {
|
||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
@@ -324,9 +400,16 @@ func (j redirectHTTPResponse) Headers() []string {
|
|||||||
type jsonAPIErrResponse struct {
|
type jsonAPIErrResponse struct {
|
||||||
err *exerr.ExErr
|
err *exerr.ExErr
|
||||||
headers []headerval
|
headers []headerval
|
||||||
|
cookies []cookieval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j jsonAPIErrResponse) Write(g *gin.Context) {
|
func (j jsonAPIErrResponse) Write(g *gin.Context) {
|
||||||
|
for _, v := range j.headers {
|
||||||
|
g.Header(v.Key, v.Val)
|
||||||
|
}
|
||||||
|
for _, v := range j.cookies {
|
||||||
|
g.SetCookie(v.name, v.value, v.maxAge, v.path, v.domain, v.secure, v.httpOnly)
|
||||||
|
}
|
||||||
j.err.Output(g)
|
j.err.Output(g)
|
||||||
|
|
||||||
j.err.CallListener(exerr.MethodOutput)
|
j.err.CallListener(exerr.MethodOutput)
|
||||||
@@ -337,6 +420,11 @@ func (j jsonAPIErrResponse) WithHeader(k string, v string) HTTPResponse {
|
|||||||
return j
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j jsonAPIErrResponse) WithCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) HTTPResponse {
|
||||||
|
j.cookies = append(j.cookies, cookieval{name, value, maxAge, path, domain, secure, httpOnly})
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
func (j jsonAPIErrResponse) IsSuccess() bool {
|
func (j jsonAPIErrResponse) IsSuccess() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
1
go.sum
1
go.sum
@@ -36,6 +36,7 @@ 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=
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
package goext
|
package goext
|
||||||
|
|
||||||
const GoextVersion = "0.0.363"
|
const GoextVersion = "0.0.367"
|
||||||
|
|
||||||
const GoextVersionTimestamp = "2024-01-09T08:51:46+0100"
|
const GoextVersionTimestamp = "2024-01-12T18:40:29+0100"
|
||||||
|
98
reflectext/mapAccess.go
Normal file
98
reflectext/mapAccess.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package reflectext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetMapPath returns the value deep inside a hierahically nested map structure
|
||||||
|
// eg:
|
||||||
|
// x := langext.H{"K1": langext.H{"K2": 665}}
|
||||||
|
// GetMapPath[int](x, "K1.K2") == 665
|
||||||
|
func GetMapPath[TData any](mapval any, path string) (TData, bool) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
split := strings.Split(path, ".")
|
||||||
|
|
||||||
|
for i, key := range split {
|
||||||
|
|
||||||
|
if i < len(split)-1 {
|
||||||
|
mapval, ok = GetMapField[any](mapval, key)
|
||||||
|
if !ok {
|
||||||
|
return *new(TData), false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return GetMapField[TData](mapval, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *new(TData), false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMapField gets the value of a map, without knowing the actual types (mapval is any)
|
||||||
|
// eg:
|
||||||
|
// x := langext.H{"K1": 665}
|
||||||
|
// GetMapPath[int](x, "K1") == 665
|
||||||
|
//
|
||||||
|
// works with aliased types and autom. dereferences pointes
|
||||||
|
func GetMapField[TData any, TKey comparable](mapval any, key TKey) (TData, bool) {
|
||||||
|
|
||||||
|
rval := reflect.ValueOf(mapval)
|
||||||
|
|
||||||
|
for rval.Kind() == reflect.Ptr && !rval.IsNil() {
|
||||||
|
rval = rval.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if rval.Kind() != reflect.Map {
|
||||||
|
return *new(TData), false // mapval is not a map
|
||||||
|
}
|
||||||
|
|
||||||
|
kval := reflect.ValueOf(key)
|
||||||
|
|
||||||
|
if !kval.Type().AssignableTo(rval.Type().Key()) {
|
||||||
|
return *new(TData), false // key cannot index mapval
|
||||||
|
}
|
||||||
|
|
||||||
|
eval := rval.MapIndex(kval)
|
||||||
|
if !eval.IsValid() {
|
||||||
|
return *new(TData), false // key does not exist in mapval
|
||||||
|
}
|
||||||
|
|
||||||
|
destType := reflect.TypeOf(new(TData)).Elem()
|
||||||
|
|
||||||
|
if eval.Type() == destType {
|
||||||
|
return eval.Interface().(TData), true
|
||||||
|
}
|
||||||
|
|
||||||
|
if eval.CanConvert(destType) && !preventConvert(eval.Type(), destType) {
|
||||||
|
return eval.Convert(destType).Interface().(TData), true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eval.Kind() == reflect.Ptr || eval.Kind() == reflect.Interface) && eval.IsNil() && destType.Kind() == reflect.Ptr {
|
||||||
|
return *new(TData), false // special case: mapval[key] is nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for (eval.Kind() == reflect.Ptr || eval.Kind() == reflect.Interface) && !eval.IsNil() {
|
||||||
|
eval = eval.Elem()
|
||||||
|
|
||||||
|
if eval.Type() == destType {
|
||||||
|
return eval.Interface().(TData), true
|
||||||
|
}
|
||||||
|
|
||||||
|
if eval.CanConvert(destType) && !preventConvert(eval.Type(), destType) {
|
||||||
|
return eval.Convert(destType).Interface().(TData), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *new(TData), false // mapval[key] is not of type TData
|
||||||
|
}
|
||||||
|
|
||||||
|
func preventConvert(t1 reflect.Type, t2 reflect.Type) bool {
|
||||||
|
if t1.Kind() == reflect.String && t1.Kind() != reflect.String {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if t2.Kind() == reflect.String && t1.Kind() != reflect.String {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
55
reflectext/mapAccess_test.go
Normal file
55
reflectext/mapAccess_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package reflectext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/tst"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetMapPath(t *testing.T) {
|
||||||
|
type PseudoInt = int64
|
||||||
|
|
||||||
|
mymap2 := map[string]map[string]any{"Test": {"Second": 3}}
|
||||||
|
|
||||||
|
var maany2 any = mymap2
|
||||||
|
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapPath[int](maany2, "Test.Second")), "3 true")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapPath[int](maany2, "Test2.Second")), "0 false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapPath[int](maany2, "Test.Second2")), "0 false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapPath[string](maany2, "Test.Second")), "false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapPath[string](maany2, "Test2.Second")), "false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapPath[string](maany2, "Test.Second2")), "false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapPath[PseudoInt](maany2, "Test.Second")), "3 true")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapPath[PseudoInt](maany2, "Test2.Second")), "0 false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapPath[PseudoInt](maany2, "Test.Second2")), "0 false")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMapField(t *testing.T) {
|
||||||
|
type PseudoInt = int64
|
||||||
|
|
||||||
|
mymap1 := map[string]any{"Test": 12}
|
||||||
|
mymap2 := map[string]int{"Test": 12}
|
||||||
|
|
||||||
|
var maany1 any = mymap1
|
||||||
|
var maany2 any = mymap2
|
||||||
|
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[int](maany1, "Test")), "12 true")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[int](maany1, "Test2")), "0 false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[string](maany1, "Test")), "false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[string](maany1, "Test2")), "false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[PseudoInt](maany1, "Test")), "12 true")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[PseudoInt](maany1, "Test2")), "0 false")
|
||||||
|
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[int](maany2, "Test")), "12 true")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[int](maany2, "Test2")), "0 false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[string](maany2, "Test")), "false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[string](maany2, "Test2")), "false")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[PseudoInt](maany2, "Test")), "12 true")
|
||||||
|
tst.AssertEqual(t, fmt.Sprint(GetMapField[PseudoInt](maany2, "Test2")), "0 false")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main2() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
}
|
Reference in New Issue
Block a user