mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-12 20:57:20 +00:00
Allow map[string][]string to be converted to an object
Will allow query parameters to be converted to versioned objects.
This commit is contained in:
@@ -54,6 +54,12 @@ type Converter struct {
|
||||
// Map from a type to a function which applies defaults.
|
||||
defaultingFuncs map[reflect.Type]reflect.Value
|
||||
|
||||
// Map from an input type to a function which can apply a key name mapping
|
||||
inputFieldMappingFuncs map[reflect.Type]FieldMappingFunc
|
||||
|
||||
// Map from an input type to a set of default conversion flags.
|
||||
inputDefaultFlags map[reflect.Type]FieldMatchingFlags
|
||||
|
||||
// If non-nil, will be called to print helpful debugging info. Quite verbose.
|
||||
Debug DebugLogger
|
||||
|
||||
@@ -71,6 +77,9 @@ func NewConverter() *Converter {
|
||||
nameFunc: func(t reflect.Type) string { return t.Name() },
|
||||
structFieldDests: map[typeNamePair][]typeNamePair{},
|
||||
structFieldSources: map[typeNamePair][]typeNamePair{},
|
||||
|
||||
inputFieldMappingFuncs: map[reflect.Type]FieldMappingFunc{},
|
||||
inputDefaultFlags: map[reflect.Type]FieldMatchingFlags{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,12 +107,18 @@ type Scope interface {
|
||||
Meta() *Meta
|
||||
}
|
||||
|
||||
// FieldMappingFunc can convert an input field value into different values, depending on
|
||||
// the value of the source or destination struct tags.
|
||||
type FieldMappingFunc func(key string, sourceTag, destTag reflect.StructTag) (source string, dest string)
|
||||
|
||||
// Meta is supplied by Scheme, when it calls Convert.
|
||||
type Meta struct {
|
||||
SrcVersion string
|
||||
DestVersion string
|
||||
|
||||
// TODO: If needed, add a user data field here.
|
||||
// KeyNameMapping is an optional function which may map the listed key (field name)
|
||||
// into a source and destination value.
|
||||
KeyNameMapping FieldMappingFunc
|
||||
}
|
||||
|
||||
// scope contains information about an ongoing conversion.
|
||||
@@ -301,6 +316,21 @@ func (c *Converter) RegisterDefaultingFunc(defaultingFunc interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterInputDefaults registers a field name mapping function, used when converting
|
||||
// from maps to structs. Inputs to the conversion methods are checked for this type and a mapping
|
||||
// applied automatically if the input matches in. A set of default flags for the input conversion
|
||||
// may also be provided, which will be used when no explicit flags are requested.
|
||||
func (c *Converter) RegisterInputDefaults(in interface{}, fn FieldMappingFunc, defaultFlags FieldMatchingFlags) error {
|
||||
fv := reflect.ValueOf(in)
|
||||
ft := fv.Type()
|
||||
if ft.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("expected pointer 'in' argument, got: %v", ft)
|
||||
}
|
||||
c.inputFieldMappingFuncs[ft] = fn
|
||||
c.inputDefaultFlags[ft] = defaultFlags
|
||||
return nil
|
||||
}
|
||||
|
||||
// FieldMatchingFlags contains a list of ways in which struct fields could be
|
||||
// copied. These constants may be | combined.
|
||||
type FieldMatchingFlags int
|
||||
@@ -538,10 +568,16 @@ func (c *Converter) defaultConvert(sv, dv reflect.Value, scope *scope) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var stringType = reflect.TypeOf("")
|
||||
|
||||
func toKVValue(v reflect.Value) kvValue {
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
return structAdaptor(v)
|
||||
case reflect.Map:
|
||||
if v.Type().Key().AssignableTo(stringType) {
|
||||
return stringMapAdaptor(v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -561,15 +597,48 @@ type kvValue interface {
|
||||
confirmSet(key string, v reflect.Value) bool
|
||||
}
|
||||
|
||||
type stringMapAdaptor reflect.Value
|
||||
|
||||
func (a stringMapAdaptor) len() int {
|
||||
return reflect.Value(a).Len()
|
||||
}
|
||||
|
||||
func (a stringMapAdaptor) keys() []string {
|
||||
v := reflect.Value(a)
|
||||
keys := make([]string, v.Len())
|
||||
for i, v := range v.MapKeys() {
|
||||
if v.IsNil() {
|
||||
continue
|
||||
}
|
||||
switch t := v.Interface().(type) {
|
||||
case string:
|
||||
keys[i] = t
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (a stringMapAdaptor) tagOf(key string) reflect.StructTag {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a stringMapAdaptor) value(key string) reflect.Value {
|
||||
return reflect.Value(a).MapIndex(reflect.ValueOf(key))
|
||||
}
|
||||
|
||||
func (a stringMapAdaptor) confirmSet(key string, v reflect.Value) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type structAdaptor reflect.Value
|
||||
|
||||
func (sa structAdaptor) len() int {
|
||||
v := reflect.Value(sa)
|
||||
func (a structAdaptor) len() int {
|
||||
v := reflect.Value(a)
|
||||
return v.Type().NumField()
|
||||
}
|
||||
|
||||
func (sa structAdaptor) keys() []string {
|
||||
v := reflect.Value(sa)
|
||||
func (a structAdaptor) keys() []string {
|
||||
v := reflect.Value(a)
|
||||
t := v.Type()
|
||||
keys := make([]string, t.NumField())
|
||||
for i := range keys {
|
||||
@@ -578,8 +647,8 @@ func (sa structAdaptor) keys() []string {
|
||||
return keys
|
||||
}
|
||||
|
||||
func (sa structAdaptor) tagOf(key string) reflect.StructTag {
|
||||
v := reflect.Value(sa)
|
||||
func (a structAdaptor) tagOf(key string) reflect.StructTag {
|
||||
v := reflect.Value(a)
|
||||
field, ok := v.Type().FieldByName(key)
|
||||
if ok {
|
||||
return field.Tag
|
||||
@@ -587,12 +656,12 @@ func (sa structAdaptor) tagOf(key string) reflect.StructTag {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (sa structAdaptor) value(key string) reflect.Value {
|
||||
v := reflect.Value(sa)
|
||||
func (a structAdaptor) value(key string) reflect.Value {
|
||||
v := reflect.Value(a)
|
||||
return v.FieldByName(key)
|
||||
}
|
||||
|
||||
func (sa structAdaptor) confirmSet(key string, v reflect.Value) bool {
|
||||
func (a structAdaptor) confirmSet(key string, v reflect.Value) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -608,6 +677,12 @@ func (c *Converter) convertKV(skv, dkv kvValue, scope *scope) error {
|
||||
if scope.flags.IsSet(SourceToDest) {
|
||||
lister = skv
|
||||
}
|
||||
|
||||
var mapping FieldMappingFunc
|
||||
if scope.meta != nil && scope.meta.KeyNameMapping != nil {
|
||||
mapping = scope.meta.KeyNameMapping
|
||||
}
|
||||
|
||||
for _, key := range lister.keys() {
|
||||
if found, err := c.checkField(key, skv, dkv, scope); found {
|
||||
if err != nil {
|
||||
@@ -615,23 +690,31 @@ func (c *Converter) convertKV(skv, dkv kvValue, scope *scope) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
df := dkv.value(key)
|
||||
sf := skv.value(key)
|
||||
stag := skv.tagOf(key)
|
||||
dtag := dkv.tagOf(key)
|
||||
skey := key
|
||||
dkey := key
|
||||
if mapping != nil {
|
||||
skey, dkey = scope.meta.KeyNameMapping(key, stag, dtag)
|
||||
}
|
||||
|
||||
df := dkv.value(dkey)
|
||||
sf := skv.value(skey)
|
||||
if !df.IsValid() || !sf.IsValid() {
|
||||
switch {
|
||||
case scope.flags.IsSet(IgnoreMissingFields):
|
||||
// No error.
|
||||
case scope.flags.IsSet(SourceToDest):
|
||||
return scope.error("%v not present in dest", key)
|
||||
return scope.error("%v not present in dest", dkey)
|
||||
default:
|
||||
return scope.error("%v not present in src", key)
|
||||
return scope.error("%v not present in src", skey)
|
||||
}
|
||||
continue
|
||||
}
|
||||
scope.srcStack.top().key = key
|
||||
scope.srcStack.top().tag = skv.tagOf(key)
|
||||
scope.destStack.top().key = key
|
||||
scope.destStack.top().tag = dkv.tagOf(key)
|
||||
scope.srcStack.top().key = skey
|
||||
scope.srcStack.top().tag = stag
|
||||
scope.destStack.top().key = dkey
|
||||
scope.destStack.top().tag = dtag
|
||||
if err := c.convert(sf, df, scope); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/gofuzz"
|
||||
@@ -173,6 +174,105 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverter_MapsStringArrays(t *testing.T) {
|
||||
type A struct {
|
||||
Foo string
|
||||
Baz int
|
||||
Other string
|
||||
}
|
||||
c := NewConverter()
|
||||
c.Debug = t
|
||||
if err := c.RegisterConversionFunc(func(input *[]string, out *string, s Scope) error {
|
||||
if len(*input) == 0 {
|
||||
*out = ""
|
||||
}
|
||||
*out = (*input)[0]
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
x := map[string][]string{
|
||||
"Foo": {"bar"},
|
||||
"Baz": {"1"},
|
||||
"Other": {"", "test"},
|
||||
"other": {"wrong"},
|
||||
}
|
||||
y := A{"test", 2, "something"}
|
||||
|
||||
if err := c.Convert(&x, &y, AllowDifferentFieldTypeNames, nil); err == nil {
|
||||
t.Error("unexpected non-error")
|
||||
}
|
||||
|
||||
if err := c.RegisterConversionFunc(func(input *[]string, out *int, s Scope) error {
|
||||
if len(*input) == 0 {
|
||||
*out = 0
|
||||
}
|
||||
str := (*input)[0]
|
||||
i, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*out = i
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
if err := c.Convert(&x, &y, AllowDifferentFieldTypeNames, nil); err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(y, A{"bar", 1, ""}) {
|
||||
t.Errorf("unexpected result: %#v", y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverter_MapsStringArraysWithMappingKey(t *testing.T) {
|
||||
type A struct {
|
||||
Foo string `json:"test"`
|
||||
Baz int
|
||||
Other string
|
||||
}
|
||||
c := NewConverter()
|
||||
c.Debug = t
|
||||
if err := c.RegisterConversionFunc(func(input *[]string, out *string, s Scope) error {
|
||||
if len(*input) == 0 {
|
||||
*out = ""
|
||||
}
|
||||
*out = (*input)[0]
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
x := map[string][]string{
|
||||
"Foo": {"bar"},
|
||||
"test": {"baz"},
|
||||
}
|
||||
y := A{"", 0, ""}
|
||||
|
||||
if err := c.Convert(&x, &y, AllowDifferentFieldTypeNames|IgnoreMissingFields, &Meta{}); err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(y, A{"bar", 0, ""}) {
|
||||
t.Errorf("unexpected result: %#v", y)
|
||||
}
|
||||
|
||||
mapping := func(key string, sourceTag, destTag reflect.StructTag) (source string, dest string) {
|
||||
if s := destTag.Get("json"); len(s) > 0 {
|
||||
return strings.SplitN(s, ",", 2)[0], key
|
||||
}
|
||||
return key, key
|
||||
}
|
||||
|
||||
if err := c.Convert(&x, &y, AllowDifferentFieldTypeNames|IgnoreMissingFields, &Meta{KeyNameMapping: mapping}); err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(y, A{"baz", 0, ""}) {
|
||||
t.Errorf("unexpected result: %#v", y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConverter_fuzz(t *testing.T) {
|
||||
// Use the same types from the scheme test.
|
||||
table := []struct {
|
||||
|
@@ -58,7 +58,8 @@ func (s *Scheme) Decode(data []byte) (interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.converter.Convert(obj, objOut, 0, s.generateConvertMeta(version, s.InternalVersion)); err != nil {
|
||||
flags, meta := s.generateConvertMeta(version, s.InternalVersion, obj)
|
||||
if err := s.converter.Convert(obj, objOut, flags, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj = objOut
|
||||
@@ -101,7 +102,8 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
|
||||
if err := json.Unmarshal(data, external); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.converter.Convert(external, obj, 0, s.generateConvertMeta(dataVersion, objVersion)); err != nil {
|
||||
flags, meta := s.generateConvertMeta(dataVersion, objVersion, external)
|
||||
if err := s.converter.Convert(external, obj, flags, meta); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@@ -67,7 +67,8 @@ func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []by
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.converter.Convert(obj, objOut, 0, s.generateConvertMeta(objVersion, destVersion))
|
||||
flags, meta := s.generateConvertMeta(objVersion, destVersion, obj)
|
||||
err = s.converter.Convert(obj, objOut, flags, meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -232,6 +232,14 @@ func (s *Scheme) AddDefaultingFuncs(defaultingFuncs ...interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterInputDefaults sets the provided field mapping function and field matching
|
||||
// as the defaults for the provided input type. The fn may be nil, in which case no
|
||||
// mapping will happen by default. Use this method to register a mechanism for handling
|
||||
// a specific input type in conversion, such as a map[string]string to structs.
|
||||
func (s *Scheme) RegisterInputDefaults(in interface{}, fn FieldMappingFunc, defaultFlags FieldMatchingFlags) error {
|
||||
return s.converter.RegisterInputDefaults(in, fn, defaultFlags)
|
||||
}
|
||||
|
||||
// Convert will attempt to convert in into out. Both must be pointers. For easy
|
||||
// testing of conversion functions. Returns an error if the conversion isn't
|
||||
// possible. You can call this with types that haven't been registered (for example,
|
||||
@@ -247,7 +255,11 @@ func (s *Scheme) Convert(in, out interface{}) error {
|
||||
if v, _, err := s.ObjectVersionAndKind(out); err == nil {
|
||||
outVersion = v
|
||||
}
|
||||
return s.converter.Convert(in, out, AllowDifferentFieldTypeNames, s.generateConvertMeta(inVersion, outVersion))
|
||||
flags, meta := s.generateConvertMeta(inVersion, outVersion, in)
|
||||
if flags == 0 {
|
||||
flags = AllowDifferentFieldTypeNames
|
||||
}
|
||||
return s.converter.Convert(in, out, flags, meta)
|
||||
}
|
||||
|
||||
// ConvertToVersion attempts to convert an input object to its matching Kind in another
|
||||
@@ -279,7 +291,8 @@ func (s *Scheme) ConvertToVersion(in interface{}, outVersion string) (interface{
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.converter.Convert(in, out, 0, s.generateConvertMeta(inVersion, outVersion)); err != nil {
|
||||
flags, meta := s.generateConvertMeta(inVersion, outVersion, in)
|
||||
if err := s.converter.Convert(in, out, flags, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -290,11 +303,18 @@ func (s *Scheme) ConvertToVersion(in interface{}, outVersion string) (interface{
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Converter allows access to the converter for the scheme
|
||||
func (s *Scheme) Converter() *Converter {
|
||||
return s.converter
|
||||
}
|
||||
|
||||
// generateConvertMeta constructs the meta value we pass to Convert.
|
||||
func (s *Scheme) generateConvertMeta(srcVersion, destVersion string) *Meta {
|
||||
return &Meta{
|
||||
SrcVersion: srcVersion,
|
||||
DestVersion: destVersion,
|
||||
func (s *Scheme) generateConvertMeta(srcVersion, destVersion string, in interface{}) (FieldMatchingFlags, *Meta) {
|
||||
t := reflect.TypeOf(in)
|
||||
return s.converter.inputDefaultFlags[t], &Meta{
|
||||
SrcVersion: srcVersion,
|
||||
DestVersion: destVersion,
|
||||
KeyNameMapping: s.converter.inputFieldMappingFuncs[t],
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user