From 75e578a9b503be520cd54f0230d4dc97ddc56d61 Mon Sep 17 00:00:00 2001 From: Jimil Desai Date: Fri, 30 Jul 2021 14:13:46 +0530 Subject: [PATCH 1/5] feat: added methods to get and put data into context Signed-off-by: Jimil Desai --- pkg/appctx/appctx.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pkg/appctx/appctx.go b/pkg/appctx/appctx.go index 8c63c3ddae..4c8ed7f2a7 100644 --- a/pkg/appctx/appctx.go +++ b/pkg/appctx/appctx.go @@ -20,6 +20,7 @@ package appctx import ( "context" + "unsafe" "github.com/rs/zerolog" ) @@ -27,6 +28,45 @@ import ( // DeletingSharedResource flags to a storage a shared resource is being deleted not by the owner. var DeletingSharedResource struct{} +type emptyCtx int + +type valueCtx struct { + context.Context + key, val interface{} +} + +type iface struct { + itab, data uintptr +} + +// GetKeyValues retrieves all the key-value pairs from the provided context. +func GetKeyValues(ctx context.Context) map[interface{}]interface{} { + m := make(map[interface{}]interface{}) + getKeyValue(ctx, m) + return m +} + +// PutKeyValues puts +func PutKeyValues(m map[interface{}]interface{}) context.Context { + ctx := context.Background() + for key, value := range m { + ctx = context.WithValue(ctx, key, value) + } + return ctx +} + +func getKeyValue(ctx context.Context, m map[interface{}]interface{}) { + ictx := *(*iface)(unsafe.Pointer(&ctx)) + if ictx.data == 0 || int(*(*emptyCtx)(unsafe.Pointer(ictx.data))) == 0 { + return + } + valCtx := (*valueCtx)(unsafe.Pointer(ictx.data)) + if valCtx != nil && valCtx.key != nil { + m[valCtx.key] = valCtx.val + } + getKeyValue(valCtx.Context, m) +} + // WithLogger returns a context with an associated logger. func WithLogger(ctx context.Context, l *zerolog.Logger) context.Context { return l.WithContext(ctx) From 5ca037a521380020894049f237ee0e772c804f29 Mon Sep 17 00:00:00 2001 From: Jimil Desai Date: Fri, 30 Jul 2021 14:22:12 +0530 Subject: [PATCH 2/5] add changelog Signed-off-by: Jimil Desai --- changelog/unreleased/context-methods.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelog/unreleased/context-methods.md diff --git a/changelog/unreleased/context-methods.md b/changelog/unreleased/context-methods.md new file mode 100644 index 0000000000..35d6a5292e --- /dev/null +++ b/changelog/unreleased/context-methods.md @@ -0,0 +1,5 @@ +Enhancement: Add methods to get and put context values. + +Added `GetKeyValues` and `PutKeyValues` methods to fetch/put values from/to context. + +https://github.com/cs3org/reva/pull/1938 \ No newline at end of file From eaa3683e1ece1543aca23ba46797aa69eebe5d75 Mon Sep 17 00:00:00 2001 From: Jimil Desai Date: Fri, 30 Jul 2021 16:49:45 +0530 Subject: [PATCH 3/5] code refractor and unit test Signed-off-by: Jimil Desai --- pkg/appctx/appctx.go | 40 ----------------- pkg/appctx/ctxmap.go | 63 +++++++++++++++++++++++++++ pkg/appctx/ctxmap_test.go | 90 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 40 deletions(-) create mode 100644 pkg/appctx/ctxmap.go create mode 100644 pkg/appctx/ctxmap_test.go diff --git a/pkg/appctx/appctx.go b/pkg/appctx/appctx.go index 4c8ed7f2a7..8c63c3ddae 100644 --- a/pkg/appctx/appctx.go +++ b/pkg/appctx/appctx.go @@ -20,7 +20,6 @@ package appctx import ( "context" - "unsafe" "github.com/rs/zerolog" ) @@ -28,45 +27,6 @@ import ( // DeletingSharedResource flags to a storage a shared resource is being deleted not by the owner. var DeletingSharedResource struct{} -type emptyCtx int - -type valueCtx struct { - context.Context - key, val interface{} -} - -type iface struct { - itab, data uintptr -} - -// GetKeyValues retrieves all the key-value pairs from the provided context. -func GetKeyValues(ctx context.Context) map[interface{}]interface{} { - m := make(map[interface{}]interface{}) - getKeyValue(ctx, m) - return m -} - -// PutKeyValues puts -func PutKeyValues(m map[interface{}]interface{}) context.Context { - ctx := context.Background() - for key, value := range m { - ctx = context.WithValue(ctx, key, value) - } - return ctx -} - -func getKeyValue(ctx context.Context, m map[interface{}]interface{}) { - ictx := *(*iface)(unsafe.Pointer(&ctx)) - if ictx.data == 0 || int(*(*emptyCtx)(unsafe.Pointer(ictx.data))) == 0 { - return - } - valCtx := (*valueCtx)(unsafe.Pointer(ictx.data)) - if valCtx != nil && valCtx.key != nil { - m[valCtx.key] = valCtx.val - } - getKeyValue(valCtx.Context, m) -} - // WithLogger returns a context with an associated logger. func WithLogger(ctx context.Context, l *zerolog.Logger) context.Context { return l.WithContext(ctx) diff --git a/pkg/appctx/ctxmap.go b/pkg/appctx/ctxmap.go new file mode 100644 index 0000000000..c56f376c05 --- /dev/null +++ b/pkg/appctx/ctxmap.go @@ -0,0 +1,63 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package appctx + +import ( + "context" + "unsafe" +) + +type emptyCtx int + +type valueCtx struct { + context.Context + key, val interface{} +} + +type iface struct { + itab, data uintptr //nolint +} + +// GetKeyValues retrieves all the key-value pairs from the provided context. +func GetKeyValues(ctx context.Context) map[interface{}]interface{} { + m := make(map[interface{}]interface{}) + getKeyValue(ctx, m) + return m +} + +// PutKeyValues puts +func PutKeyValues(m map[interface{}]interface{}) context.Context { + ctx := context.Background() + for key, value := range m { + ctx = context.WithValue(ctx, key, value) + } + return ctx +} + +func getKeyValue(ctx context.Context, m map[interface{}]interface{}) { + ictx := *(*iface)(unsafe.Pointer(&ctx)) + if ictx.data == 0 || int(*(*emptyCtx)(unsafe.Pointer(ictx.data))) == 0 { + return + } + valCtx := (*valueCtx)(unsafe.Pointer(ictx.data)) + if valCtx != nil && valCtx.key != nil { + m[valCtx.key] = valCtx.val + } + getKeyValue(valCtx.Context, m) +} diff --git a/pkg/appctx/ctxmap_test.go b/pkg/appctx/ctxmap_test.go new file mode 100644 index 0000000000..9e35c427e4 --- /dev/null +++ b/pkg/appctx/ctxmap_test.go @@ -0,0 +1,90 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package appctx + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetKeyValues(t *testing.T) { + tests := []struct { + name string + ctx context.Context + m map[interface{}]interface{} + }{ + { + "Background context", + context.Background(), + map[interface{}]interface{}{}, + }, + { + "Context with Values", + context.WithValue(context.Background(), "key", "value"), //nolint + map[interface{}]interface{}{ + "key": "value", + }, + }, + { + "Nested Context with Values", + context.WithValue(context.WithValue(context.Background(), "key", "value"), "key2", "value2"), //nolint + map[interface{}]interface{}{ + "key": "value", + "key2": "value2", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kvMap := GetKeyValues(tt.ctx) + assert.Equal(t, tt.m, kvMap) + }) + } +} + +func TestPutKeyValues(t *testing.T) { + tests := []struct { + name string + m map[interface{}]interface{} + ctx context.Context + }{ + { + "empty context", + map[interface{}]interface{}{}, + context.Background(), + }, + { + "single kv pair", + map[interface{}]interface{}{ + "key": "value", + }, + context.WithValue(context.Background(), "key", "value"), //nolint + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := PutKeyValues(tt.m) + assert.Equal(t, tt.ctx, ctx) + }) + } +} From 05c6f4d1429b85095a3a6adfd6b284cd52c702ce Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Fri, 30 Jul 2021 16:28:35 +0200 Subject: [PATCH 4/5] Use reflection to manipulate valueCtx objects --- pkg/appctx/ctxmap.go | 60 +++++++++++++++++++++------------------ pkg/appctx/ctxmap_test.go | 32 ++++++++++++++------- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/pkg/appctx/ctxmap.go b/pkg/appctx/ctxmap.go index c56f376c05..a7e10eaa8a 100644 --- a/pkg/appctx/ctxmap.go +++ b/pkg/appctx/ctxmap.go @@ -20,44 +20,50 @@ package appctx import ( "context" + "reflect" "unsafe" ) -type emptyCtx int - -type valueCtx struct { - context.Context - key, val interface{} -} - -type iface struct { - itab, data uintptr //nolint +// PutKeyValuesToCtx puts all the key-value pairs from the provided map to a background context. +func PutKeyValuesToCtx(m map[interface{}]interface{}) context.Context { + ctx := context.Background() + for key, value := range m { + ctx = context.WithValue(ctx, key, value) + } + return ctx } -// GetKeyValues retrieves all the key-value pairs from the provided context. -func GetKeyValues(ctx context.Context) map[interface{}]interface{} { +// GetKeyValuesFromCtx retrieves all the key-value pairs from the provided context. +func GetKeyValuesFromCtx(ctx context.Context) map[interface{}]interface{} { m := make(map[interface{}]interface{}) getKeyValue(ctx, m) return m } -// PutKeyValues puts -func PutKeyValues(m map[interface{}]interface{}) context.Context { - ctx := context.Background() - for key, value := range m { - ctx = context.WithValue(ctx, key, value) +func getKeyValue(ctx interface{}, m map[interface{}]interface{}) { + ctxVals := reflect.ValueOf(ctx).Elem() + ctxType := reflect.TypeOf(ctx).Elem() + + if ctxType.Kind() == reflect.Struct { + for i := 0; i < ctxVals.NumField(); i++ { + currField, currIf := extractField(ctxVals, ctxType, i) + switch currField { + case "Context": + getKeyValue(currIf, m) + case "key": + nextField, nextIf := extractField(ctxVals, ctxType, i+1) + if nextField == "val" { + m[currIf] = nextIf + i++ + } + } + } } - return ctx } -func getKeyValue(ctx context.Context, m map[interface{}]interface{}) { - ictx := *(*iface)(unsafe.Pointer(&ctx)) - if ictx.data == 0 || int(*(*emptyCtx)(unsafe.Pointer(ictx.data))) == 0 { - return - } - valCtx := (*valueCtx)(unsafe.Pointer(ictx.data)) - if valCtx != nil && valCtx.key != nil { - m[valCtx.key] = valCtx.val - } - getKeyValue(valCtx.Context, m) +func extractField(vals reflect.Value, fieldType reflect.Type, pos int) (string, interface{}) { + currVal := vals.Field(pos) + currVal = reflect.NewAt(currVal.Type(), unsafe.Pointer(currVal.UnsafeAddr())).Elem() + currField := fieldType.Field(pos) + return currField.Name, currVal.Interface() } diff --git a/pkg/appctx/ctxmap_test.go b/pkg/appctx/ctxmap_test.go index 9e35c427e4..ebe3be94d2 100644 --- a/pkg/appctx/ctxmap_test.go +++ b/pkg/appctx/ctxmap_test.go @@ -22,9 +22,13 @@ import ( "context" "testing" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" "github.com/stretchr/testify/assert" ) +type ctxStringKey string +type ctxIntKey int + func TestGetKeyValues(t *testing.T) { tests := []struct { name string @@ -38,24 +42,32 @@ func TestGetKeyValues(t *testing.T) { }, { "Context with Values", - context.WithValue(context.Background(), "key", "value"), //nolint + context.WithValue(context.Background(), ctxStringKey("key"), "value"), + map[interface{}]interface{}{ + ctxStringKey("key"): "value", + }, + }, + { + "Context with user object", + context.WithValue(context.WithValue(context.Background(), ctxStringKey("key"), "value"), ctxStringKey("user"), &userpb.User{Username: "einstein"}), map[interface{}]interface{}{ - "key": "value", + ctxStringKey("key"): "value", + ctxStringKey("user"): &userpb.User{Username: "einstein"}, }, }, { - "Nested Context with Values", - context.WithValue(context.WithValue(context.Background(), "key", "value"), "key2", "value2"), //nolint + "Nested Context with Values of different types", + context.WithValue(context.WithValue(context.Background(), ctxStringKey("key"), "value"), ctxIntKey(123), "value2"), map[interface{}]interface{}{ - "key": "value", - "key2": "value2", + ctxStringKey("key"): "value", + ctxIntKey(123): "value2", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - kvMap := GetKeyValues(tt.ctx) + kvMap := GetKeyValuesFromCtx(tt.ctx) assert.Equal(t, tt.m, kvMap) }) } @@ -75,15 +87,15 @@ func TestPutKeyValues(t *testing.T) { { "single kv pair", map[interface{}]interface{}{ - "key": "value", + ctxStringKey("key"): "value", }, - context.WithValue(context.Background(), "key", "value"), //nolint + context.WithValue(context.Background(), ctxStringKey("key"), "value"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx := PutKeyValues(tt.m) + ctx := PutKeyValuesToCtx(tt.m) assert.Equal(t, tt.ctx, ctx) }) } From 5c387077955e0376ed7a646ecb5c8f20751cb0cc Mon Sep 17 00:00:00 2001 From: Jimil Desai Date: Tue, 3 Aug 2021 13:59:17 +0530 Subject: [PATCH 5/5] fix: update changelog Signed-off-by: Jimil Desai --- changelog/unreleased/context-methods.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/unreleased/context-methods.md b/changelog/unreleased/context-methods.md index 35d6a5292e..5baf5233a3 100644 --- a/changelog/unreleased/context-methods.md +++ b/changelog/unreleased/context-methods.md @@ -1,4 +1,4 @@ -Enhancement: Add methods to get and put context values. +Enhancement: Add methods to get and put context values Added `GetKeyValues` and `PutKeyValues` methods to fetch/put values from/to context.