diff --git a/changelog/unreleased/context-methods.md b/changelog/unreleased/context-methods.md new file mode 100644 index 0000000000..5baf5233a3 --- /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 diff --git a/pkg/appctx/ctxmap.go b/pkg/appctx/ctxmap.go new file mode 100644 index 0000000000..a7e10eaa8a --- /dev/null +++ b/pkg/appctx/ctxmap.go @@ -0,0 +1,69 @@ +// 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" + "reflect" + "unsafe" +) + +// 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 +} + +// 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 +} + +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++ + } + } + } + } +} + +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 new file mode 100644 index 0000000000..ebe3be94d2 --- /dev/null +++ b/pkg/appctx/ctxmap_test.go @@ -0,0 +1,102 @@ +// 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" + + 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 + ctx context.Context + m map[interface{}]interface{} + }{ + { + "Background context", + context.Background(), + map[interface{}]interface{}{}, + }, + { + "Context with Values", + 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{}{ + ctxStringKey("key"): "value", + ctxStringKey("user"): &userpb.User{Username: "einstein"}, + }, + }, + { + "Nested Context with Values of different types", + context.WithValue(context.WithValue(context.Background(), ctxStringKey("key"), "value"), ctxIntKey(123), "value2"), + map[interface{}]interface{}{ + ctxStringKey("key"): "value", + ctxIntKey(123): "value2", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kvMap := GetKeyValuesFromCtx(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{}{ + ctxStringKey("key"): "value", + }, + context.WithValue(context.Background(), ctxStringKey("key"), "value"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := PutKeyValuesToCtx(tt.m) + assert.Equal(t, tt.ctx, ctx) + }) + } +}