Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
ef664607b4 | |||
62e482a14b | |||
a390ebf80f | |||
9a44960be7 | |||
c846c59b9b | |||
|
902bf6326b | ||
|
bddf3bf502 | ||
|
284131da98 | ||
927c7ea3c2 | |||
0e51a79bb6 | |||
1de9911b73 | |||
b4092c6619 | |||
024868bfd7 |
19
.github/renovate.json
vendored
Normal file
19
.github/renovate.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"groupName": "all deps",
|
||||
"separateMajorMinor": true,
|
||||
"groupSlug": "all",
|
||||
"packagePatterns": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
- name: cache
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: lint
|
||||
uses: golangci/golangci-lint-action@v1
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||
|
4
.github/workflows/pr.yml
vendored
4
.github/workflows/pr.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15
|
||||
- name: cache
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: lint
|
||||
uses: golangci/golangci-lint-action@v1
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||
|
@@ -98,6 +98,7 @@ func Encode(e *Endpoint) map[string]string {
|
||||
set("method", strings.Join(e.Method, ","))
|
||||
set("path", strings.Join(e.Path, ","))
|
||||
set("host", strings.Join(e.Host, ","))
|
||||
set("body", e.Body)
|
||||
|
||||
return ep
|
||||
}
|
||||
@@ -118,6 +119,7 @@ func Decode(e metadata.Metadata) *Endpoint {
|
||||
ephost, _ := e.Get("host")
|
||||
ep.Host = []string{ephost}
|
||||
ep.Handler, _ = e.Get("handler")
|
||||
ep.Body, _ = e.Get("body")
|
||||
|
||||
return ep
|
||||
}
|
||||
|
@@ -187,7 +187,7 @@ func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOpti
|
||||
|
||||
options := NewPublishOptions(opts...)
|
||||
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.New(0)
|
||||
}
|
||||
|
6
go.mod
6
go.mod
@@ -7,15 +7,15 @@ require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/ef-ds/deque v1.0.4
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/google/uuid v1.1.5
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/miekg/dns v1.1.38
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||
google.golang.org/protobuf v1.25.0
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
|
13
go.sum
13
go.sum
@@ -31,16 +31,16 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
|
||||
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw=
|
||||
github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
@@ -52,7 +52,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/unistack-org/micro v1.18.0 h1:EbFiII0bKV0Xcua7o6J30MFmm4/g0Hv3ECOKzsUBihU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
@@ -69,8 +68,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@@ -5,35 +5,104 @@ import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type mdIncomingKey struct{}
|
||||
type mdOutgoingKey struct{}
|
||||
type mdKey struct{}
|
||||
|
||||
// FromIncomingContext returns metadata from incoming ctx
|
||||
// returned metadata shoud not be modified or race condition happens
|
||||
func FromIncomingContext(ctx context.Context) (Metadata, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
md, ok := ctx.Value(mdIncomingKey{}).(*rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return md.md, ok
|
||||
}
|
||||
|
||||
// FromOutgoingContext returns metadata from outgoing ctx
|
||||
// returned metadata shoud not be modified or race condition happens
|
||||
func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
md, ok := ctx.Value(mdOutgoingKey{}).(*rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return md.md, ok
|
||||
}
|
||||
|
||||
// FromContext returns metadata from the given context
|
||||
// returned metadata shoud not be modified or race condition happens
|
||||
//
|
||||
// Deprecated: use FromIncomingContext or FromOutgoingContext
|
||||
func FromContext(ctx context.Context) (Metadata, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
md, ok := ctx.Value(metadataKey{}).(Metadata)
|
||||
md, ok := ctx.Value(mdKey{}).(*rawMetadata)
|
||||
if !ok {
|
||||
return nil, ok
|
||||
return nil, false
|
||||
}
|
||||
nmd := Copy(md)
|
||||
return nmd, ok
|
||||
return md.md, ok
|
||||
}
|
||||
|
||||
// NewContext creates a new context with the given metadata
|
||||
//
|
||||
// Deprecated: use NewIncomingContext or NewOutgoingContext
|
||||
func NewContext(ctx context.Context, md Metadata) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, metadataKey{}, Copy(md))
|
||||
ctx = context.WithValue(ctx, mdKey{}, &rawMetadata{md})
|
||||
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{})
|
||||
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{})
|
||||
return ctx
|
||||
}
|
||||
|
||||
// MergeContext merges metadata to existing metadata, overwriting if specified
|
||||
func MergeContext(ctx context.Context, pmd Metadata, overwrite bool) context.Context {
|
||||
// SetOutgoingContext modify outgoing context with given metadata
|
||||
func SetOutgoingContext(ctx context.Context, md Metadata) bool {
|
||||
if ctx == nil {
|
||||
return false
|
||||
}
|
||||
if omd, ok := ctx.Value(mdOutgoingKey{}).(*rawMetadata); ok {
|
||||
omd.md = md
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIncomingContext modify incoming context with given metadata
|
||||
func SetIncomingContext(ctx context.Context, md Metadata) bool {
|
||||
if ctx == nil {
|
||||
return false
|
||||
}
|
||||
if omd, ok := ctx.Value(mdIncomingKey{}).(*rawMetadata); ok {
|
||||
omd.md = md
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NewIncomingContext creates a new context with incoming metadata attached
|
||||
func NewIncomingContext(ctx context.Context, md Metadata) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
return context.WithValue(ctx, metadataKey{}, Copy(pmd))
|
||||
}
|
||||
return context.WithValue(ctx, metadataKey{}, Merge(md, pmd, overwrite))
|
||||
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{md})
|
||||
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{})
|
||||
return ctx
|
||||
}
|
||||
|
||||
// NewOutgoingContext creates a new context with outcoming metadata attached
|
||||
func NewOutgoingContext(ctx context.Context, md Metadata) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{md})
|
||||
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{})
|
||||
return ctx
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/textproto"
|
||||
"sort"
|
||||
)
|
||||
@@ -12,16 +11,18 @@ var (
|
||||
HeaderPrefix = "Micro-"
|
||||
)
|
||||
|
||||
type metadataKey struct{}
|
||||
|
||||
// Metadata is our way of representing request headers internally.
|
||||
// They're used at the RPC level and translate back and forth
|
||||
// from Transport headers.
|
||||
type Metadata map[string]string
|
||||
|
||||
type rawMetadata struct {
|
||||
md Metadata
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultMetadataSize used when need to init new Metadata
|
||||
DefaultMetadataSize = 6
|
||||
// defaultMetadataSize used when need to init new Metadata
|
||||
defaultMetadataSize = 2
|
||||
)
|
||||
|
||||
type Iterator struct {
|
||||
@@ -72,12 +73,9 @@ func (md Metadata) Set(key, val string) {
|
||||
// Del is used to remove value from metadata
|
||||
func (md Metadata) Del(key string) {
|
||||
// fast path
|
||||
if _, ok := md[key]; ok {
|
||||
delete(md, key)
|
||||
} else {
|
||||
// slow path
|
||||
delete(md, textproto.CanonicalMIMEHeaderKey(key))
|
||||
}
|
||||
delete(md, key)
|
||||
// slow path
|
||||
delete(md, textproto.CanonicalMIMEHeaderKey(key))
|
||||
}
|
||||
|
||||
// Copy makes a copy of the metadata
|
||||
@@ -89,39 +87,10 @@ func Copy(md Metadata) Metadata {
|
||||
return nmd
|
||||
}
|
||||
|
||||
// Del deletes key from metadata
|
||||
func Del(ctx context.Context, key string) context.Context {
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
md = New(0)
|
||||
}
|
||||
md.Del(key)
|
||||
return context.WithValue(ctx, metadataKey{}, md)
|
||||
}
|
||||
|
||||
// Set add key with val to metadata
|
||||
func Set(ctx context.Context, key, val string) context.Context {
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
md = New(0)
|
||||
}
|
||||
md.Set(key, val)
|
||||
return context.WithValue(ctx, metadataKey{}, md)
|
||||
}
|
||||
|
||||
// Get returns a single value from metadata in the context
|
||||
func Get(ctx context.Context, key string) (string, bool) {
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
return "", ok
|
||||
}
|
||||
return md.Get(key)
|
||||
}
|
||||
|
||||
// New return new sized metadata
|
||||
func New(size int) Metadata {
|
||||
if size == 0 {
|
||||
size = DefaultMetadataSize
|
||||
size = defaultMetadataSize
|
||||
}
|
||||
return make(Metadata, size)
|
||||
}
|
||||
|
@@ -3,10 +3,31 @@ package metadata
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testCtx(ctx context.Context) {
|
||||
md := New(2)
|
||||
md.Set("Key1", "Val1_new")
|
||||
md.Set("Key3", "Val3")
|
||||
SetOutgoingContext(ctx, md)
|
||||
}
|
||||
|
||||
func TestPassing(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
md1 := New(2)
|
||||
md1.Set("Key1", "Val1")
|
||||
md1.Set("Key2", "Val2")
|
||||
|
||||
ctx = NewIncomingContext(ctx, md1)
|
||||
testCtx(ctx)
|
||||
md, ok := FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
t.Fatalf("missing metadata from outgoing context")
|
||||
}
|
||||
fmt.Printf("%#+v\n", md)
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
omd := Metadata{
|
||||
"key1": "val1",
|
||||
@@ -32,26 +53,27 @@ func TestIterator(t *testing.T) {
|
||||
var k, v string
|
||||
|
||||
for iter.Next(&k, &v) {
|
||||
fmt.Printf("k: %s, v: %s\n", k, v)
|
||||
//fmt.Printf("k: %s, v: %s\n", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMedataCanonicalKey(t *testing.T) {
|
||||
ctx := Set(context.TODO(), "x-request-id", "12345")
|
||||
v, ok := Get(ctx, "x-request-id")
|
||||
md := New(1)
|
||||
md.Set("x-request-id", "12345")
|
||||
v, ok := md.Get("x-request-id")
|
||||
if !ok {
|
||||
t.Fatalf("failed to get x-request-id")
|
||||
} else if v != "12345" {
|
||||
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
||||
}
|
||||
|
||||
v, ok = Get(ctx, "X-Request-Id")
|
||||
v, ok = md.Get("X-Request-Id")
|
||||
if !ok {
|
||||
t.Fatalf("failed to get x-request-id")
|
||||
} else if v != "12345" {
|
||||
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
||||
}
|
||||
v, ok = Get(ctx, "X-Request-ID")
|
||||
v, ok = md.Get("X-Request-ID")
|
||||
if !ok {
|
||||
t.Fatalf("failed to get x-request-id")
|
||||
} else if v != "12345" {
|
||||
@@ -61,9 +83,11 @@ func TestMedataCanonicalKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMetadataSet(t *testing.T) {
|
||||
ctx := Set(context.TODO(), "Key", "val")
|
||||
md := New(1)
|
||||
|
||||
val, ok := Get(ctx, "Key")
|
||||
md.Set("Key", "val")
|
||||
|
||||
val, ok := md.Get("Key")
|
||||
if !ok {
|
||||
t.Fatal("key Key not found")
|
||||
}
|
||||
@@ -78,15 +102,8 @@ func TestMetadataDelete(t *testing.T) {
|
||||
"Baz": "empty",
|
||||
}
|
||||
|
||||
ctx := NewContext(context.TODO(), md)
|
||||
ctx = Del(ctx, "Baz")
|
||||
|
||||
emd, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
t.Fatal("key Key not found")
|
||||
}
|
||||
|
||||
_, ok = emd["Baz"]
|
||||
md.Del("Baz")
|
||||
_, ok := md.Get("Baz")
|
||||
if ok {
|
||||
t.Fatal("key Baz not deleted")
|
||||
}
|
||||
@@ -137,42 +154,3 @@ func TestMetadataContext(t *testing.T) {
|
||||
t.Errorf("Expected metadata length 1 got %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeContext(t *testing.T) {
|
||||
type args struct {
|
||||
existing Metadata
|
||||
append Metadata
|
||||
overwrite bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Metadata
|
||||
}{
|
||||
{
|
||||
name: "matching key, overwrite false",
|
||||
args: args{
|
||||
existing: Metadata{"Foo": "bar", "Sumo": "demo"},
|
||||
append: Metadata{"Sumo": "demo2"},
|
||||
overwrite: false,
|
||||
},
|
||||
want: Metadata{"Foo": "bar", "Sumo": "demo"},
|
||||
},
|
||||
{
|
||||
name: "matching key, overwrite true",
|
||||
args: args{
|
||||
existing: Metadata{"Foo": "bar", "Sumo": "demo"},
|
||||
append: Metadata{"Sumo": "demo2"},
|
||||
overwrite: true,
|
||||
},
|
||||
want: Metadata{"Foo": "bar", "Sumo": "demo2"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got, _ := FromContext(MergeContext(NewContext(context.TODO(), tt.args.existing), tt.args.append, tt.args.overwrite)); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("MergeContext() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -14,11 +14,12 @@ const (
|
||||
|
||||
// FromContext returns a span from context
|
||||
func FromContext(ctx context.Context) (traceID string, parentSpanID string, isFound bool) {
|
||||
if ctx == nil {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
traceID, traceOk := metadata.Get(ctx, traceIDKey)
|
||||
microID, microOk := metadata.Get(ctx, "Micro-Id")
|
||||
traceID, traceOk := md.Get(traceIDKey)
|
||||
microID, microOk := md.Get("Micro-Id")
|
||||
if !traceOk && !microOk {
|
||||
isFound = false
|
||||
return
|
||||
@@ -26,17 +27,17 @@ func FromContext(ctx context.Context) (traceID string, parentSpanID string, isFo
|
||||
if !traceOk {
|
||||
traceID = microID
|
||||
}
|
||||
parentSpanID, ok := metadata.Get(ctx, spanIDKey)
|
||||
parentSpanID, ok = md.Get(spanIDKey)
|
||||
return traceID, parentSpanID, ok
|
||||
}
|
||||
|
||||
// NewContext saves the trace and span ids in the context
|
||||
func NewContext(ctx context.Context, traceID, parentSpanID string) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.New(2)
|
||||
}
|
||||
md := metadata.New(2)
|
||||
md.Set(traceIDKey, traceID)
|
||||
md.Set(spanIDKey, parentSpanID)
|
||||
return metadata.MergeContext(ctx, md, true)
|
||||
return metadata.NewContext(ctx, md)
|
||||
}
|
||||
|
@@ -10,10 +10,9 @@ import (
|
||||
|
||||
func FromRequest(r *http.Request) context.Context {
|
||||
ctx := r.Context()
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
// create needed map with specific len
|
||||
md = make(metadata.Metadata, len(r.Header)+2)
|
||||
md = metadata.New(len(r.Header) + 2)
|
||||
}
|
||||
for key, val := range r.Header {
|
||||
md.Set(key, strings.Join(val, ","))
|
||||
@@ -22,5 +21,5 @@ func FromRequest(r *http.Request) context.Context {
|
||||
md["Host"] = r.Host
|
||||
// pass http method
|
||||
md["Method"] = r.Method
|
||||
return metadata.NewContext(ctx, md)
|
||||
return metadata.NewIncomingContext(ctx, md)
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ func TestRequestToContext(t *testing.T) {
|
||||
|
||||
for _, d := range testData {
|
||||
ctx := FromRequest(d.request)
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
t.Fatalf("Expected metadata for request %+v", d.request)
|
||||
}
|
||||
|
@@ -2,24 +2,53 @@ package reflect
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
bracketSplitter = regexp.MustCompile(`\[|\]`)
|
||||
ErrInvalidStruct = errors.New("invalid struct specified")
|
||||
ErrInvalidParam = errors.New("invalid url query param provided")
|
||||
)
|
||||
|
||||
func fieldName(name string) string {
|
||||
newstr := make([]rune, 0)
|
||||
upper := false
|
||||
for idx, chr := range name {
|
||||
if idx == 0 {
|
||||
upper = true
|
||||
} else if chr == '_' {
|
||||
upper = true
|
||||
continue
|
||||
}
|
||||
if upper {
|
||||
newstr = append(newstr, unicode.ToUpper(chr))
|
||||
} else {
|
||||
newstr = append(newstr, chr)
|
||||
}
|
||||
upper = false
|
||||
}
|
||||
|
||||
return string(newstr)
|
||||
}
|
||||
|
||||
func IsEmpty(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
switch getKind(v) {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
case reflect.Int:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
case reflect.Uint:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
case reflect.Float32:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
@@ -119,3 +148,450 @@ func CopyFrom(a, b interface{}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func URLMap(query string) (map[string]interface{}, error) {
|
||||
var (
|
||||
mp interface{} = make(map[string]interface{})
|
||||
)
|
||||
|
||||
params := strings.Split(query, "&")
|
||||
|
||||
for _, part := range params {
|
||||
tm, err := queryToMap(part)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp = merge(mp, tm)
|
||||
}
|
||||
|
||||
return mp.(map[string]interface{}), nil
|
||||
}
|
||||
|
||||
func FlattenMap(a map[string]interface{}) map[string]interface{} {
|
||||
// preprocess map
|
||||
nb := make(map[string]interface{}, len(a))
|
||||
for k, v := range a {
|
||||
ps := strings.Split(k, ".")
|
||||
if len(ps) == 1 {
|
||||
nb[k] = v
|
||||
continue
|
||||
}
|
||||
em := make(map[string]interface{})
|
||||
em[ps[len(ps)-1]] = v
|
||||
for i := len(ps) - 2; i > 0; i-- {
|
||||
nm := make(map[string]interface{})
|
||||
nm[ps[i]] = em
|
||||
em = nm
|
||||
}
|
||||
if vm, ok := nb[ps[0]]; ok {
|
||||
// nested map
|
||||
nm := vm.(map[string]interface{})
|
||||
for vk, vv := range em {
|
||||
nm[vk] = vv
|
||||
}
|
||||
nb[ps[0]] = nm
|
||||
} else {
|
||||
nb[ps[0]] = em
|
||||
}
|
||||
}
|
||||
return nb
|
||||
}
|
||||
|
||||
func MergeMap(a interface{}, b map[string]interface{}) error {
|
||||
var err error
|
||||
|
||||
ta := reflect.TypeOf(a)
|
||||
if ta.Kind() == reflect.Ptr {
|
||||
ta = ta.Elem()
|
||||
}
|
||||
va := reflect.ValueOf(a)
|
||||
if va.Kind() == reflect.Ptr {
|
||||
va = va.Elem()
|
||||
}
|
||||
|
||||
for mk, mv := range b {
|
||||
vmv := reflect.ValueOf(mv)
|
||||
name := fieldName(mk)
|
||||
fva := va.FieldByName(name)
|
||||
fta, found := ta.FieldByName(name)
|
||||
if !found || !fva.IsValid() || !fva.CanSet() || fta.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
// fast path via direct assign
|
||||
if vmv.Type().AssignableTo(fta.Type) && !IsEmpty(vmv) {
|
||||
fva.Set(vmv)
|
||||
continue
|
||||
}
|
||||
switch getKind(fva) {
|
||||
case reflect.Bool:
|
||||
err = mergeBool(fva, vmv)
|
||||
case reflect.String:
|
||||
err = mergeString(fva, vmv)
|
||||
case reflect.Int:
|
||||
err = mergeInt(fva, vmv)
|
||||
case reflect.Uint:
|
||||
err = mergeUint(fva, vmv)
|
||||
case reflect.Float32:
|
||||
err = mergeFloat(fva, vmv)
|
||||
case reflect.Array:
|
||||
//fmt.Printf("Array %#+v %#+v\n", fva, vmv)
|
||||
case reflect.Slice:
|
||||
err = mergeSlice(fva, vmv)
|
||||
case reflect.Ptr:
|
||||
if fva.IsNil() {
|
||||
fva.Set(reflect.New(fva.Type().Elem()))
|
||||
if fva.Elem().Type().Kind() == reflect.Struct {
|
||||
for i := 0; i < fva.Elem().NumField(); i++ {
|
||||
field := fva.Elem().Field(i)
|
||||
if field.Type().Kind() == reflect.Ptr && field.IsNil() && fva.Elem().Type().Field(i).Anonymous == true {
|
||||
field.Set(reflect.New(field.Type().Elem()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if nmp, ok := vmv.Interface().(map[string]interface{}); ok {
|
||||
err = MergeMap(fva.Interface(), nmp)
|
||||
} else {
|
||||
err = fmt.Errorf("cant fill")
|
||||
}
|
||||
case reflect.Struct:
|
||||
if nmp, ok := vmv.Interface().(map[string]interface{}); ok {
|
||||
err = MergeMap(fva.Interface(), nmp)
|
||||
} else {
|
||||
err = fmt.Errorf("cant fill")
|
||||
}
|
||||
case reflect.Map:
|
||||
//fmt.Printf("Map %#+v %#+v\n", fva, vmv)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeSlice(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
/*
|
||||
case reflect.Int:
|
||||
if vb.Int() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
case reflect.Uint:
|
||||
if vb.Uint() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
case reflect.Float64:
|
||||
if vb.Float() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
*/
|
||||
case reflect.String:
|
||||
var err error
|
||||
fn := func(c rune) bool { return c == ',' || c == ';' || c == ' ' }
|
||||
slice := strings.FieldsFunc(vb.String(), fn)
|
||||
va.Set(reflect.MakeSlice(va.Type(), len(slice), len(slice)))
|
||||
for idx, sl := range slice {
|
||||
vl := reflect.ValueOf(sl)
|
||||
switch va.Type().Elem().Kind() {
|
||||
case reflect.Bool:
|
||||
err = mergeBool(va.Index(idx), vl)
|
||||
case reflect.String:
|
||||
err = mergeString(va.Index(idx), vl)
|
||||
case reflect.Ptr:
|
||||
if va.Index(idx).IsNil() {
|
||||
va.Index(idx).Set(reflect.New(va.Index(idx).Type().Elem()))
|
||||
}
|
||||
switch va.Type().Elem().String() {
|
||||
case "*wrapperspb.BoolValue":
|
||||
if eva := reflect.Indirect(va.Index(idx)).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeBool(eva, vl)
|
||||
}
|
||||
case "*wrapperspb.BytesValue":
|
||||
if eva := va.Index(idx).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeUint(eva, vl)
|
||||
}
|
||||
case "*wrapperspb.DoubleValue", "*wrapperspb.FloatValue":
|
||||
if eva := reflect.Indirect(va.Index(idx)).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeFloat(eva, vl)
|
||||
}
|
||||
case "*wrapperspb.Int32Value", "*wrapperspb.Int64Value":
|
||||
if eva := reflect.Indirect(va.Index(idx)).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeInt(eva, vl)
|
||||
}
|
||||
case "*wrapperspb.StringValue":
|
||||
if eva := reflect.Indirect(va.Index(idx)).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeString(eva, vl)
|
||||
}
|
||||
case "*wrapperspb.UInt32Value", "*wrapperspb.UInt64Value":
|
||||
if eva := reflect.Indirect(va.Index(idx)).FieldByName("Value"); eva.IsValid() {
|
||||
err = mergeUint(eva, vl)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeBool(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
case reflect.Int:
|
||||
if vb.Int() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
case reflect.Uint:
|
||||
if vb.Uint() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
case reflect.Float32:
|
||||
if vb.Float() == 1 {
|
||||
va.SetBool(true)
|
||||
}
|
||||
case reflect.String:
|
||||
if b, err := strconv.ParseBool(vb.String()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
va.SetBool(b)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeString(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
case reflect.Int:
|
||||
va.SetString(fmt.Sprintf("%d", vb.Int()))
|
||||
case reflect.Uint:
|
||||
va.SetString(fmt.Sprintf("%d", vb.Uint()))
|
||||
case reflect.Float32:
|
||||
va.SetString(fmt.Sprintf("%f", vb.Float()))
|
||||
case reflect.String:
|
||||
va.Set(vb)
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeInt(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
case reflect.Int:
|
||||
va.Set(vb)
|
||||
case reflect.Uint:
|
||||
va.SetInt(int64(vb.Uint()))
|
||||
case reflect.Float32:
|
||||
va.SetInt(int64(vb.Float()))
|
||||
case reflect.String:
|
||||
if f, err := strconv.ParseInt(vb.String(), 10, va.Type().Bits()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
va.SetInt(f)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeUint(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
case reflect.Int:
|
||||
va.SetUint(uint64(vb.Int()))
|
||||
case reflect.Uint:
|
||||
va.Set(vb)
|
||||
case reflect.Float32:
|
||||
va.SetUint(uint64(vb.Float()))
|
||||
case reflect.String:
|
||||
if f, err := strconv.ParseUint(vb.String(), 10, va.Type().Bits()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
va.SetUint(f)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeFloat(va, vb reflect.Value) error {
|
||||
switch getKind(vb) {
|
||||
case reflect.Int:
|
||||
va.SetFloat(float64(vb.Int()))
|
||||
case reflect.Uint:
|
||||
va.SetFloat(float64(vb.Uint()))
|
||||
case reflect.Float32:
|
||||
va.Set(vb)
|
||||
case reflect.String:
|
||||
if f, err := strconv.ParseFloat(vb.String(), va.Type().Bits()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
va.SetFloat(f)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cant merge %v %s with %v %s", va, va.Kind(), vb, vb.Kind())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKind(val reflect.Value) reflect.Kind {
|
||||
kind := val.Kind()
|
||||
switch {
|
||||
case kind >= reflect.Int && kind <= reflect.Int64:
|
||||
return reflect.Int
|
||||
case kind >= reflect.Uint && kind <= reflect.Uint64:
|
||||
return reflect.Uint
|
||||
case kind >= reflect.Float32 && kind <= reflect.Float64:
|
||||
return reflect.Float32
|
||||
}
|
||||
return kind
|
||||
}
|
||||
|
||||
func btSplitter(str string) []string {
|
||||
r := bracketSplitter.Split(str, -1)
|
||||
for idx, s := range r {
|
||||
if len(s) == 0 {
|
||||
if len(r) > idx+1 {
|
||||
copy(r[idx:], r[idx+1:])
|
||||
r = r[:len(r)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// queryToMap turns something like a[b][c]=4 into
|
||||
// map[string]interface{}{
|
||||
// "a": map[string]interface{}{
|
||||
// "b": map[string]interface{}{
|
||||
// "c": 4,
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
func queryToMap(param string) (map[string]interface{}, error) {
|
||||
rawKey, rawValue, err := splitKeyAndValue(param)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawValue, err = url.QueryUnescape(rawValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawKey, err = url.QueryUnescape(rawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pieces := btSplitter(rawKey)
|
||||
key := pieces[0]
|
||||
|
||||
// If len==1 then rawKey has no [] chars and we can just
|
||||
// decode this as key=value into {key: value}
|
||||
if len(pieces) == 1 {
|
||||
return map[string]interface{}{
|
||||
key: rawValue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// If len > 1 then we have something like a[b][c]=2
|
||||
// so we need to turn this into {"a": {"b": {"c": 2}}}
|
||||
// To do this we break our key into two pieces:
|
||||
// a and b[c]
|
||||
// and then we set {"a": queryToMap("b[c]", value)}
|
||||
ret := make(map[string]interface{})
|
||||
ret[key], err = queryToMap(buildNewKey(rawKey) + "=" + rawValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// When URL params have a set of empty brackets (eg a[]=1)
|
||||
// it is assumed to be an array. This will get us the
|
||||
// correct value for the array item and return it as an
|
||||
// []interface{} so that it can be merged properly.
|
||||
if pieces[1] == "" {
|
||||
temp := ret[key].(map[string]interface{})
|
||||
ret[key] = []interface{}{temp[""]}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// buildNewKey will take something like:
|
||||
// origKey = "bar[one][two]"
|
||||
// pieces = [bar one two ]
|
||||
// and return "one[two]"
|
||||
func buildNewKey(origKey string) string {
|
||||
pieces := btSplitter(origKey)
|
||||
|
||||
ret := origKey[len(pieces[0])+1:]
|
||||
ret = ret[:len(pieces[1])] + ret[len(pieces[1])+1:]
|
||||
return ret
|
||||
}
|
||||
|
||||
// splitKeyAndValue splits a URL param at the last equal
|
||||
// sign and returns the two strings. If no equal sign is
|
||||
// found, the ErrInvalidParam error is returned.
|
||||
func splitKeyAndValue(param string) (string, string, error) {
|
||||
li := strings.LastIndex(param, "=")
|
||||
if li == -1 {
|
||||
return "", "", ErrInvalidParam
|
||||
}
|
||||
return param[:li], param[li+1:], nil
|
||||
}
|
||||
|
||||
// merge merges a with b if they are either both slices
|
||||
// or map[string]interface{} types. Otherwise it returns b.
|
||||
func merge(a interface{}, b interface{}) interface{} {
|
||||
if av, aok := a.(map[string]interface{}); aok {
|
||||
if bv, bok := b.(map[string]interface{}); bok {
|
||||
return mergeMapIface(av, bv)
|
||||
}
|
||||
}
|
||||
if av, aok := a.([]interface{}); aok {
|
||||
if bv, bok := b.([]interface{}); bok {
|
||||
return mergeSliceIface(av, bv)
|
||||
}
|
||||
}
|
||||
|
||||
va := reflect.ValueOf(a)
|
||||
vb := reflect.ValueOf(b)
|
||||
if (va.Type().Kind() == reflect.Slice) && (va.Type().Elem().Kind() == vb.Type().Kind() || vb.Type().ConvertibleTo(va.Type().Elem())) {
|
||||
va = reflect.Append(va, vb.Convert(va.Type().Elem()))
|
||||
return va.Interface()
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// mergeMap merges a with b, attempting to merge any nested
|
||||
// values in nested maps but eventually overwriting anything
|
||||
// in a that can't be merged with whatever is in b.
|
||||
func mergeMapIface(a map[string]interface{}, b map[string]interface{}) map[string]interface{} {
|
||||
for bK, bV := range b {
|
||||
if aV, ok := a[bK]; ok {
|
||||
if (reflect.ValueOf(aV).Type().Kind() == reflect.ValueOf(bV).Type().Kind()) ||
|
||||
((reflect.ValueOf(aV).Type().Kind() == reflect.Slice) && reflect.ValueOf(aV).Type().Elem().Kind() == reflect.ValueOf(bV).Type().Kind()) {
|
||||
nV := []interface{}{aV, bV}
|
||||
a[bK] = nV
|
||||
} else {
|
||||
a[bK] = merge(a[bK], bV)
|
||||
}
|
||||
} else {
|
||||
a[bK] = bV
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// mergeSlice merges a with b and returns the result.
|
||||
func mergeSliceIface(a []interface{}, b []interface{}) []interface{} {
|
||||
a = append(a, b...)
|
||||
return a
|
||||
}
|
||||
|
45
util/reflect/reflect_test.go
Normal file
45
util/reflect/reflect_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package reflect
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestURLSliceVars(t *testing.T) {
|
||||
u, err := url.Parse("http://localhost/v1/test/call/my_name?key=arg1&key=arg2&key=arg3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mp, err := URLMap(u.RawQuery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
v, ok := mp["key"]
|
||||
if !ok {
|
||||
t.Fatalf("key not exists: %#+v", mp)
|
||||
}
|
||||
|
||||
vm, ok := v.([]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("invalid key value")
|
||||
}
|
||||
|
||||
if len(vm) != 3 {
|
||||
t.Fatalf("missing key value: %#+v", mp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLVars(t *testing.T) {
|
||||
u, err := url.Parse("http://localhost/v1/test/call/my_name?req=key&arg1=arg1&arg2=12345&nested.string_args=str1&nested.string_args=str2&arg2=54321")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mp, err := URLMap(u.RawQuery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = mp
|
||||
}
|
Reference in New Issue
Block a user