Compare commits
2 Commits
v4.1.8
...
dee7bc9c38
| Author | SHA1 | Date | |
|---|---|---|---|
| dee7bc9c38 | |||
| 053fe2a69d |
@@ -25,7 +25,7 @@ jobs:
|
|||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
cache-dependency-path: "**/*.sum"
|
cache-dependency-path: "**/*.sum"
|
||||||
go-version: 'stable'
|
go-version: 'stable'
|
||||||
|
|
||||||
- name: test coverage
|
- name: test coverage
|
||||||
run: |
|
run: |
|
||||||
@@ -42,8 +42,8 @@ jobs:
|
|||||||
name: autocommit
|
name: autocommit
|
||||||
with:
|
with:
|
||||||
commit_message: Apply Code Coverage Badge
|
commit_message: Apply Code Coverage Badge
|
||||||
skip_fetch: false
|
skip_fetch: true
|
||||||
skip_checkout: false
|
skip_checkout: true
|
||||||
file_pattern: ./README.md
|
file_pattern: ./README.md
|
||||||
|
|
||||||
- name: push
|
- name: push
|
||||||
@@ -51,4 +51,4 @@ jobs:
|
|||||||
uses: ad-m/github-push-action@master
|
uses: ad-m/github-push-action@master
|
||||||
with:
|
with:
|
||||||
github_token: ${{ github.token }}
|
github_token: ${{ github.token }}
|
||||||
branch: ${{ github.ref }}
|
branch: ${{ github.ref }}
|
||||||
@@ -24,6 +24,6 @@ jobs:
|
|||||||
- name: setup deps
|
- name: setup deps
|
||||||
run: go get -v ./...
|
run: go get -v ./...
|
||||||
- name: run lint
|
- name: run lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: https://github.com/golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: 'latest'
|
version: 'latest'
|
||||||
@@ -1,57 +1,54 @@
|
|||||||
name: sync
|
name: syncpull
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '*/5 * * * *'
|
- cron: '* * * * *'
|
||||||
push:
|
|
||||||
branches: [ master, v3, v4 ]
|
|
||||||
paths-ignore:
|
|
||||||
- '.github/**'
|
|
||||||
- '.gitea/**'
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
pull:
|
||||||
if: github.server_url != 'https://github.com'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: init
|
- name: init
|
||||||
run: |
|
run: |
|
||||||
git config --global user.email "vtolstov <vtolstov@users.noreply.github.com>"
|
git config --global user.email "vtolstov <vtolstov@users.noreply.github.com>"
|
||||||
git config --global user.name "github-actions[bot]"
|
git config --global user.name "github-actions[bot]"
|
||||||
echo "machine git.unistack.org login vtolstov password ${{ secrets.TOKEN_GITEA }}" >> /root/.netrc
|
echo "machine git.unistack.org login vtolstov password ${{ secrets.TOKEN_GITEA }}" | tee -a /root/.netrc
|
||||||
echo "machine github.com login vtolstov password ${{ secrets.TOKEN_GITHUB }}" >> /root/.netrc
|
echo "machine github.com login vtolstov password ${{ secrets.TOKEN_GITHUB }}" | tee -a /root/.netrc
|
||||||
|
|
||||||
- name: sync master
|
- name: track master
|
||||||
run: |
|
run: |
|
||||||
git clone --filter=blob:none --filter=tree:0 --branch master --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
git clone --depth=10 --branch master --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
||||||
cd repo
|
cd repo
|
||||||
git remote add --no-tags --track master upstream https://github.com/${GITHUB_REPOSITORY}
|
git remote add --no-tags --fetch --track master upstream https://github.com/${GITHUB_REPOSITORY}
|
||||||
git pull --rebase upstream master
|
git pull --rebase upstream master
|
||||||
git push upstream master --progress
|
git push upstream master --progress
|
||||||
|
git merge --allow-unrelated-histories "upstream/master"
|
||||||
git push origin master --progress
|
git push origin master --progress
|
||||||
cd ../
|
cd ../
|
||||||
rm -rf repo
|
rm -rf repo
|
||||||
|
|
||||||
- name: sync v3
|
- name: track v3
|
||||||
run: |
|
run: |
|
||||||
git clone --filter=blob:none --filter=tree:0 --branch v3 --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
git clone --depth=10 --branch v3 --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
||||||
cd repo
|
cd repo
|
||||||
git remote add --no-tags --fetch --track v3 upstream https://github.com/${GITHUB_REPOSITORY}
|
git remote add --no-tags --fetch --track v3 upstream https://github.com/${GITHUB_REPOSITORY}
|
||||||
git pull --rebase upstream v3
|
git pull --rebase upstream v3
|
||||||
git push upstream v3
|
git push upstream v3
|
||||||
|
git merge --allow-unrelated-histories "upstream/v3"
|
||||||
git push origin v3 --progress
|
git push origin v3 --progress
|
||||||
cd ../
|
cd ../
|
||||||
rm -rf repo
|
rm -rf repo
|
||||||
|
|
||||||
- name: sync v4
|
- name: track v4
|
||||||
run: |
|
run: |
|
||||||
git clone --filter=blob:none --filter=tree:0 --branch v4 --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
git clone --depth=10 --branch v4 --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
||||||
cd repo
|
cd repo
|
||||||
git remote add --no-tags --fetch --track v4 upstream https://github.com/${GITHUB_REPOSITORY}
|
git remote add --no-tags --fetch --track v4 upstream https://github.com/${GITHUB_REPOSITORY}
|
||||||
git pull --rebase upstream v4
|
git pull --rebase upstream v4
|
||||||
git push upstream v4
|
git push upstream v4
|
||||||
|
git merge --allow-unrelated-histories "upstream/v4"
|
||||||
git push origin v4 --progress
|
git push origin v4 --progress
|
||||||
cd ../
|
cd ../
|
||||||
rm -rf repo
|
rm -rf repo
|
||||||
@@ -32,19 +32,19 @@ jobs:
|
|||||||
go-version: 'stable'
|
go-version: 'stable'
|
||||||
- name: setup go work
|
- name: setup go work
|
||||||
env:
|
env:
|
||||||
GOWORK: ${{ github.workspace }}/go.work
|
GOWORK: /workspace/${{ github.repository_owner }}/go.work
|
||||||
run: |
|
run: |
|
||||||
go work init
|
go work init
|
||||||
go work use .
|
go work use .
|
||||||
go work use micro-tests
|
go work use micro-tests
|
||||||
- name: setup deps
|
- name: setup deps
|
||||||
env:
|
env:
|
||||||
GOWORK: ${{ github.workspace }}/go.work
|
GOWORK: /workspace/${{ github.repository_owner }}/go.work
|
||||||
run: go get -v ./...
|
run: go get -v ./...
|
||||||
- name: run tests
|
- name: run tests
|
||||||
env:
|
env:
|
||||||
INTEGRATION_TESTS: yes
|
INTEGRATION_TESTS: yes
|
||||||
GOWORK: ${{ github.workspace }}/go.work
|
GOWORK: /workspace/${{ github.repository_owner }}/go.work
|
||||||
run: |
|
run: |
|
||||||
cd micro-tests
|
cd micro-tests
|
||||||
go test -mod readonly -v ./... || true
|
go test -mod readonly -v ./... || true
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
run:
|
run:
|
||||||
concurrency: 8
|
concurrency: 8
|
||||||
timeout: 5m
|
deadline: 5m
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
tests: true
|
tests: true
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Micro
|
# Micro
|
||||||

|

|
||||||
[](https://opensource.org/licenses/Apache-2.0)
|
[](https://opensource.org/licenses/Apache-2.0)
|
||||||
[](https://pkg.go.dev/go.unistack.org/micro/v4?tab=overview)
|
[](https://pkg.go.dev/go.unistack.org/micro/v4?tab=overview)
|
||||||
[](https://git.unistack.org/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Av4+event%3Apush)
|
[](https://git.unistack.org/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Av4+event%3Apush)
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -12,7 +12,6 @@ require (
|
|||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
|
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
|
||||||
github.com/spf13/cast v1.7.1
|
github.com/spf13/cast v1.7.1
|
||||||
github.com/stretchr/testify v1.10.0
|
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
go.uber.org/automaxprocs v1.6.0
|
go.uber.org/automaxprocs v1.6.0
|
||||||
go.unistack.org/micro-proto/v4 v4.1.0
|
go.unistack.org/micro-proto/v4 v4.1.0
|
||||||
@@ -27,6 +26,7 @@ require (
|
|||||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func NewClientCallWrapper(keys ...string) client.CallWrapper {
|
|||||||
omd = metadata.New(len(imd))
|
omd = metadata.New(len(imd))
|
||||||
}
|
}
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
if v := imd.Get(k); v != nil {
|
if v, ok := imd.Get(k); ok {
|
||||||
omd.Add(k, v...)
|
omd.Add(k, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{},
|
|||||||
omd = metadata.New(len(imd))
|
omd = metadata.New(len(imd))
|
||||||
}
|
}
|
||||||
for _, k := range w.keys {
|
for _, k := range w.keys {
|
||||||
if v := imd.Get(k); v != nil {
|
if v, ok := imd.Get(k); ok {
|
||||||
omd.Add(k, v...)
|
omd.Add(k, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client
|
|||||||
omd = metadata.New(len(imd))
|
omd = metadata.New(len(imd))
|
||||||
}
|
}
|
||||||
for _, k := range w.keys {
|
for _, k := range w.keys {
|
||||||
if v := imd.Get(k); v != nil {
|
if v, ok := imd.Get(k); ok {
|
||||||
omd.Add(k, v...)
|
omd.Add(k, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ func NewServerHandlerWrapper(keys ...string) server.HandlerWrapper {
|
|||||||
omd = metadata.New(len(imd))
|
omd = metadata.New(len(imd))
|
||||||
}
|
}
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
if v := imd.Get(k); v != nil {
|
if v, ok := imd.Get(k); ok {
|
||||||
omd.Add(k, v...)
|
omd.Add(k, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,16 +38,18 @@ var DefaultMetadataFunc = func(ctx context.Context) (context.Context, error) {
|
|||||||
|
|
||||||
if xid == "" {
|
if xid == "" {
|
||||||
var ids []string
|
var ids []string
|
||||||
|
if ids, iok = imd.Get(DefaultMetadataKey); iok {
|
||||||
for i := range imd.Get(DefaultMetadataKey) {
|
for i := range ids {
|
||||||
if ids[i] != "" {
|
if ids[i] != "" {
|
||||||
xid = ids[i]
|
xid = ids[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ids, ook = omd.Get(DefaultMetadataKey); ook {
|
||||||
for i := range omd.Get(DefaultMetadataKey) {
|
for i := range ids {
|
||||||
if ids[i] != "" {
|
if ids[i] != "" {
|
||||||
xid = ids[i]
|
xid = ids[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package requestid
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"slices"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.unistack.org/micro/v4/metadata"
|
"go.unistack.org/micro/v4/metadata"
|
||||||
@@ -25,10 +24,10 @@ func TestDefaultMetadataFunc(t *testing.T) {
|
|||||||
t.Fatalf("md missing in outgoing context")
|
t.Fatalf("md missing in outgoing context")
|
||||||
}
|
}
|
||||||
|
|
||||||
iv := imd.Get(DefaultMetadataKey)
|
_, iok := imd.Get(DefaultMetadataKey)
|
||||||
ov := omd.Get(DefaultMetadataKey)
|
_, ook := omd.Get(DefaultMetadataKey)
|
||||||
|
|
||||||
if !slices.Equal(iv, ov) {
|
if !iok || !ook {
|
||||||
t.Fatalf("missing metadata key value %v != %v", iv, ov)
|
t.Fatalf("missing metadata key value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ func TestLogger(t *testing.T) {
|
|||||||
func Test_WithContextAttrFunc(t *testing.T) {
|
func Test_WithContextAttrFunc(t *testing.T) {
|
||||||
loggerContextAttrFuncs := []logger.ContextAttrFunc{
|
loggerContextAttrFuncs := []logger.ContextAttrFunc{
|
||||||
func(ctx context.Context) []interface{} {
|
func(ctx context.Context) []interface{} {
|
||||||
md, ok := metadata.FromOutgoingContext(ctx)
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -425,7 +425,7 @@ func Test_WithContextAttrFunc(t *testing.T) {
|
|||||||
logger.DefaultContextAttrFuncs = append(logger.DefaultContextAttrFuncs, loggerContextAttrFuncs...)
|
logger.DefaultContextAttrFuncs = append(logger.DefaultContextAttrFuncs, loggerContextAttrFuncs...)
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
ctx = metadata.AppendOutgoingContext(ctx, "X-Request-Id", uuid.New().String(),
|
ctx = metadata.AppendIncomingContext(ctx, "X-Request-Id", uuid.New().String(),
|
||||||
"Source-Service", "Test-System")
|
"Source-Service", "Test-System")
|
||||||
|
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
@@ -445,9 +445,9 @@ func Test_WithContextAttrFunc(t *testing.T) {
|
|||||||
t.Fatalf("logger info, buf %s", buf.Bytes())
|
t.Fatalf("logger info, buf %s", buf.Bytes())
|
||||||
}
|
}
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
omd, _ := metadata.FromOutgoingContext(ctx)
|
imd, _ := metadata.FromIncomingContext(ctx)
|
||||||
l.Info(ctx, "test message1")
|
l.Info(ctx, "test message1")
|
||||||
omd.Set("Source-Service", "Test-System2")
|
imd.Set("Source-Service", "Test-System2")
|
||||||
l.Info(ctx, "test message2")
|
l.Info(ctx, "test message2")
|
||||||
|
|
||||||
// t.Logf("xxx %s", buf.Bytes())
|
// t.Logf("xxx %s", buf.Bytes())
|
||||||
|
|||||||
@@ -106,7 +106,16 @@ func (md Metadata) CopyTo(out Metadata) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get obtains the values for a given key.
|
// Get obtains the values for a given key.
|
||||||
func (md Metadata) Get(k string) []string {
|
func (md Metadata) MustGet(k string) []string {
|
||||||
|
v, ok := md.Get(k)
|
||||||
|
if !ok {
|
||||||
|
panic("missing metadata key")
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get obtains the values for a given key.
|
||||||
|
func (md Metadata) Get(k string) ([]string, bool) {
|
||||||
v, ok := md[k]
|
v, ok := md[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
v, ok = md[strings.ToLower(k)]
|
v, ok = md[strings.ToLower(k)]
|
||||||
@@ -114,13 +123,27 @@ func (md Metadata) Get(k string) []string {
|
|||||||
if !ok {
|
if !ok {
|
||||||
v, ok = md[textproto.CanonicalMIMEHeaderKey(k)]
|
v, ok = md[textproto.CanonicalMIMEHeaderKey(k)]
|
||||||
}
|
}
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetJoined obtains the values for a given key
|
||||||
|
// with joined values with "," symbol
|
||||||
|
func (md Metadata) MustGetJoined(k string) string {
|
||||||
|
v, ok := md.GetJoined(k)
|
||||||
|
if !ok {
|
||||||
|
panic("missing metadata key")
|
||||||
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetJoined obtains the values for a given key
|
// GetJoined obtains the values for a given key
|
||||||
// with joined values with "," symbol
|
// with joined values with "," symbol
|
||||||
func (md Metadata) GetJoined(k string) string {
|
func (md Metadata) GetJoined(k string) (string, bool) {
|
||||||
return strings.Join(md.Get(k), ",")
|
v, ok := md.Get(k)
|
||||||
|
if !ok {
|
||||||
|
return "", ok
|
||||||
|
}
|
||||||
|
return strings.Join(v, ","), true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets the value of a given key with a slice of values.
|
// Set sets the value of a given key with a slice of values.
|
||||||
@@ -218,6 +241,24 @@ func AppendContext(ctx context.Context, kv ...string) context.Context {
|
|||||||
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md.md, added: added})
|
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md.md, added: added})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppendIncomingContext returns a new context with the provided kv merged
|
||||||
|
// with any existing metadata in the context. Please refer to the documentation
|
||||||
|
// of Pairs for a description of kv.
|
||||||
|
func AppendIncomingContext(ctx context.Context, kv ...string) context.Context {
|
||||||
|
if len(kv)%2 == 1 {
|
||||||
|
panic(fmt.Sprintf("metadata: AppendIncomingContext got an odd number of input pairs for metadata: %d", len(kv)))
|
||||||
|
}
|
||||||
|
md, _ := ctx.Value(metadataIncomingKey{}).(rawMetadata)
|
||||||
|
added := make([][]string, len(md.added)+1)
|
||||||
|
copy(added, md.added)
|
||||||
|
kvCopy := make([]string, 0, len(kv))
|
||||||
|
for i := 0; i < len(kv); i += 2 {
|
||||||
|
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
|
||||||
|
}
|
||||||
|
added[len(added)-1] = kvCopy
|
||||||
|
return context.WithValue(ctx, metadataIncomingKey{}, rawMetadata{md: md.md, added: added})
|
||||||
|
}
|
||||||
|
|
||||||
// AppendOutgoingContext returns a new context with the provided kv merged
|
// AppendOutgoingContext returns a new context with the provided kv merged
|
||||||
// with any existing metadata in the context. Please refer to the documentation
|
// with any existing metadata in the context. Please refer to the documentation
|
||||||
// of Pairs for a description of kv.
|
// of Pairs for a description of kv.
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ func TestAppendOutgoingContextModify(t *testing.T) {
|
|||||||
func TestLowercase(t *testing.T) {
|
func TestLowercase(t *testing.T) {
|
||||||
md := New(1)
|
md := New(1)
|
||||||
md["x-request-id"] = []string{"12345"}
|
md["x-request-id"] = []string{"12345"}
|
||||||
v := md.GetJoined("X-Request-Id")
|
v, ok := md.GetJoined("X-Request-Id")
|
||||||
if v == "" {
|
if !ok || v == "" {
|
||||||
t.Fatalf("metadata invalid %#+v", md)
|
t.Fatalf("metadata invalid %#+v", md)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,17 +51,29 @@ func TestMetadataSetMultiple(t *testing.T) {
|
|||||||
md := New(4)
|
md := New(4)
|
||||||
md.Set("key1", "val1", "key2", "val2")
|
md.Set("key1", "val1", "key2", "val2")
|
||||||
|
|
||||||
if v := md.GetJoined("key1"); v != "val1" {
|
if v, ok := md.GetJoined("key1"); !ok || v != "val1" {
|
||||||
t.Fatalf("invalid kv %#+v", md)
|
t.Fatalf("invalid kv %#+v", md)
|
||||||
}
|
}
|
||||||
if v := md.GetJoined("key2"); v != "val2" {
|
if v, ok := md.GetJoined("key2"); !ok || v != "val2" {
|
||||||
t.Fatalf("invalid kv %#+v", md)
|
t.Fatalf("invalid kv %#+v", md)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAppend(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = AppendIncomingContext(ctx, "key1", "val1", "key2", "val2")
|
||||||
|
md, ok := FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("metadata empty")
|
||||||
|
}
|
||||||
|
if _, ok := md.Get("key1"); !ok {
|
||||||
|
t.Fatal("key1 not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPairs(t *testing.T) {
|
func TestPairs(t *testing.T) {
|
||||||
md := Pairs("key1", "val1", "key2", "val2")
|
md := Pairs("key1", "val1", "key2", "val2")
|
||||||
if v := md.Get("key1"); v == nil {
|
if _, ok := md.Get("key1"); !ok {
|
||||||
t.Fatal("key1 not found")
|
t.Fatal("key1 not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +97,7 @@ func TestPassing(t *testing.T) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("missing metadata from outgoing context")
|
t.Fatalf("missing metadata from outgoing context")
|
||||||
}
|
}
|
||||||
if v := md.Get("Key1"); v == nil || v[0] != "Val1" {
|
if v, ok := md.Get("Key1"); !ok || v[0] != "Val1" {
|
||||||
t.Fatalf("invalid metadata value %#+v", md)
|
t.Fatalf("invalid metadata value %#+v", md)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,21 +127,21 @@ func TestIterator(t *testing.T) {
|
|||||||
func TestMedataCanonicalKey(t *testing.T) {
|
func TestMedataCanonicalKey(t *testing.T) {
|
||||||
md := New(1)
|
md := New(1)
|
||||||
md.Set("x-request-id", "12345")
|
md.Set("x-request-id", "12345")
|
||||||
v := md.GetJoined("x-request-id")
|
v, ok := md.GetJoined("x-request-id")
|
||||||
if v == "" {
|
if !ok {
|
||||||
t.Fatalf("failed to get x-request-id")
|
t.Fatalf("failed to get x-request-id")
|
||||||
} else if v != "12345" {
|
} else if v != "12345" {
|
||||||
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
v = md.GetJoined("X-Request-Id")
|
v, ok = md.GetJoined("X-Request-Id")
|
||||||
if v == "" {
|
if !ok {
|
||||||
t.Fatalf("failed to get x-request-id")
|
t.Fatalf("failed to get x-request-id")
|
||||||
} else if v != "12345" {
|
} else if v != "12345" {
|
||||||
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
||||||
}
|
}
|
||||||
v = md.GetJoined("X-Request-ID")
|
v, ok = md.GetJoined("X-Request-ID")
|
||||||
if v == "" {
|
if !ok {
|
||||||
t.Fatalf("failed to get x-request-id")
|
t.Fatalf("failed to get x-request-id")
|
||||||
} else if v != "12345" {
|
} else if v != "12345" {
|
||||||
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
||||||
@@ -141,8 +153,8 @@ func TestMetadataSet(t *testing.T) {
|
|||||||
|
|
||||||
md.Set("Key", "val")
|
md.Set("Key", "val")
|
||||||
|
|
||||||
val := md.GetJoined("Key")
|
val, ok := md.GetJoined("Key")
|
||||||
if val == "" {
|
if !ok {
|
||||||
t.Fatal("key Key not found")
|
t.Fatal("key Key not found")
|
||||||
}
|
}
|
||||||
if val != "val" {
|
if val != "val" {
|
||||||
@@ -157,8 +169,8 @@ func TestMetadataDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
md.Del("Baz")
|
md.Del("Baz")
|
||||||
v := md.Get("Baz")
|
_, ok := md.Get("Baz")
|
||||||
if v != nil {
|
if ok {
|
||||||
t.Fatal("key Baz not deleted")
|
t.Fatal("key Baz not deleted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,6 +269,20 @@ func TestNewOutgoingContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAppendIncomingContext(t *testing.T) {
|
||||||
|
md := New(1)
|
||||||
|
md.Set("key1", "val1")
|
||||||
|
ctx := AppendIncomingContext(context.TODO(), "key2", "val2")
|
||||||
|
|
||||||
|
nmd, ok := FromIncomingContext(ctx)
|
||||||
|
if nmd == nil || !ok {
|
||||||
|
t.Fatal("AppendIncomingContext not works")
|
||||||
|
}
|
||||||
|
if v, ok := nmd.GetJoined("key2"); !ok || v != "val2" {
|
||||||
|
t.Fatal("AppendIncomingContext not works")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAppendOutgoingContext(t *testing.T) {
|
func TestAppendOutgoingContext(t *testing.T) {
|
||||||
md := New(1)
|
md := New(1)
|
||||||
md.Set("key1", "val1")
|
md.Set("key1", "val1")
|
||||||
@@ -266,7 +292,7 @@ func TestAppendOutgoingContext(t *testing.T) {
|
|||||||
if nmd == nil || !ok {
|
if nmd == nil || !ok {
|
||||||
t.Fatal("AppendOutgoingContext not works")
|
t.Fatal("AppendOutgoingContext not works")
|
||||||
}
|
}
|
||||||
if v := nmd.GetJoined("key2"); v != "val2" {
|
if v, ok := nmd.GetJoined("key2"); !ok || v != "val2" {
|
||||||
t.Fatal("AppendOutgoingContext not works")
|
t.Fatal("AppendOutgoingContext not works")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -489,74 +489,35 @@ func URLMap(query string) (map[string]interface{}, error) {
|
|||||||
return mp.(map[string]interface{}), nil
|
return mp.(map[string]interface{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlattenMap flattens a nested map into a single-level map using dot notation for nested keys.
|
// FlattenMap expand key.subkey to nested map
|
||||||
// In case of key conflicts, all nested levels will be discarded in favor of the first-level key.
|
func FlattenMap(a map[string]interface{}) map[string]interface{} {
|
||||||
//
|
// preprocess map
|
||||||
// Example #1:
|
nb := make(map[string]interface{}, len(a))
|
||||||
//
|
for k, v := range a {
|
||||||
// Input:
|
ps := strings.Split(k, ".")
|
||||||
// {
|
if len(ps) == 1 {
|
||||||
// "user.name": "alex",
|
nb[k] = v
|
||||||
// "user.document.id": "document_id"
|
|
||||||
// "user.document.number": "document_number"
|
|
||||||
// }
|
|
||||||
// Output:
|
|
||||||
// {
|
|
||||||
// "user": {
|
|
||||||
// "name": "alex",
|
|
||||||
// "document": {
|
|
||||||
// "id": "document_id"
|
|
||||||
// "number": "document_number"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Example #2 (with conflicts):
|
|
||||||
//
|
|
||||||
// Input:
|
|
||||||
// {
|
|
||||||
// "user": "alex",
|
|
||||||
// "user.document.id": "document_id"
|
|
||||||
// "user.document.number": "document_number"
|
|
||||||
// }
|
|
||||||
// Output:
|
|
||||||
// {
|
|
||||||
// "user": "alex"
|
|
||||||
// }
|
|
||||||
func FlattenMap(input map[string]interface{}) map[string]interface{} {
|
|
||||||
result := make(map[string]interface{})
|
|
||||||
|
|
||||||
for k, v := range input {
|
|
||||||
parts := strings.Split(k, ".")
|
|
||||||
|
|
||||||
if len(parts) == 1 {
|
|
||||||
result[k] = v
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
em := make(map[string]interface{})
|
||||||
current := result
|
em[ps[len(ps)-1]] = v
|
||||||
|
for i := len(ps) - 2; i > 0; i-- {
|
||||||
for i, part := range parts {
|
nm := make(map[string]interface{})
|
||||||
// last element in the path
|
nm[ps[i]] = em
|
||||||
if i == len(parts)-1 {
|
em = nm
|
||||||
current[part] = v
|
}
|
||||||
break
|
if vm, ok := nb[ps[0]]; ok {
|
||||||
}
|
// nested map
|
||||||
|
nm := vm.(map[string]interface{})
|
||||||
// initialize map for current level if not exist
|
for vk, vv := range em {
|
||||||
if _, ok := current[part]; !ok {
|
nm[vk] = vv
|
||||||
current[part] = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
if nested, ok := current[part].(map[string]interface{}); ok {
|
|
||||||
current = nested // continue to the nested map
|
|
||||||
} else {
|
|
||||||
break // if current element is not a map, ignore it
|
|
||||||
}
|
}
|
||||||
|
nb[ps[0]] = nm
|
||||||
|
} else {
|
||||||
|
nb[ps[0]] = em
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nb
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
rutil "go.unistack.org/micro/v4/util/reflect"
|
rutil "go.unistack.org/micro/v4/util/reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -320,140 +319,3 @@ func TestIsZero(t *testing.T) {
|
|||||||
|
|
||||||
// t.Logf("XX %#+v\n", ok)
|
// t.Logf("XX %#+v\n", ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFlattenMap(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input map[string]interface{}
|
|
||||||
expected map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty map",
|
|
||||||
input: map[string]interface{}{},
|
|
||||||
expected: map[string]interface{}{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nil map",
|
|
||||||
input: nil,
|
|
||||||
expected: map[string]interface{}{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "single level",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"username": "username",
|
|
||||||
"password": "password",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"username": "username",
|
|
||||||
"password": "password",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "two level",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user.name": "username",
|
|
||||||
"user.password": "password",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user": map[string]interface{}{
|
|
||||||
"name": "username",
|
|
||||||
"password": "password",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "three level",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user.name": "username",
|
|
||||||
"user.password": "password",
|
|
||||||
"user.document.id": "document_id",
|
|
||||||
"user.document.number": "document_number",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user": map[string]interface{}{
|
|
||||||
"name": "username",
|
|
||||||
"password": "password",
|
|
||||||
"document": map[string]interface{}{
|
|
||||||
"id": "document_id",
|
|
||||||
"number": "document_number",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "four level",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user.name": "username",
|
|
||||||
"user.password": "password",
|
|
||||||
"user.document.id": "document_id",
|
|
||||||
"user.document.number": "document_number",
|
|
||||||
"user.info.permissions.read": "available",
|
|
||||||
"user.info.permissions.write": "available",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user": map[string]interface{}{
|
|
||||||
"name": "username",
|
|
||||||
"password": "password",
|
|
||||||
"document": map[string]interface{}{
|
|
||||||
"id": "document_id",
|
|
||||||
"number": "document_number",
|
|
||||||
},
|
|
||||||
"info": map[string]interface{}{
|
|
||||||
"permissions": map[string]interface{}{
|
|
||||||
"read": "available",
|
|
||||||
"write": "available",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "key conflicts",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"user": "user",
|
|
||||||
"user.name": "username",
|
|
||||||
"user.password": "password",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"user": "user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "overwriting conflicts",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user.document.id": "document_id",
|
|
||||||
"user.document.number": "document_number",
|
|
||||||
"user.info.address": "address",
|
|
||||||
"user.info.phone": "phone",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user": map[string]interface{}{
|
|
||||||
"document": map[string]interface{}{
|
|
||||||
"id": "document_id",
|
|
||||||
"number": "document_number",
|
|
||||||
},
|
|
||||||
"info": map[string]interface{}{
|
|
||||||
"address": "address",
|
|
||||||
"phone": "phone",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
for range 100 { // need to exclude the impact of key order in the map on the test.
|
|
||||||
require.Equal(t, tt.expected, rutil.FlattenMap(tt.input))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user