mirror of https://github.com/databricks/cli.git
Populate struct field with `config.Value` instance if possible (#1010)
## Changes If a struct has a field of type `config.Value`, then we set it to the source value while converting a `config.Value` instance to a struct as part of a call to `convert.ToTyped`. This is convenient when dealing with deeply nested structs where functions on inner structs need access to the metadata provided by their corresponding `config.Value` (e.g. where they were defined). ## Tests Added unit tests pass.
This commit is contained in:
parent
ef97e249ec
commit
f5f57b6bf9
|
@ -4,6 +4,8 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/databricks/cli/libs/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// structInfo holds the type information we need to efficiently
|
// structInfo holds the type information we need to efficiently
|
||||||
|
@ -11,6 +13,10 @@ import (
|
||||||
type structInfo struct {
|
type structInfo struct {
|
||||||
// Fields maps the JSON-name of the field to the field's index for use with [FieldByIndex].
|
// Fields maps the JSON-name of the field to the field's index for use with [FieldByIndex].
|
||||||
Fields map[string][]int
|
Fields map[string][]int
|
||||||
|
|
||||||
|
// ValueField maps to the field with a [config.Value].
|
||||||
|
// The underlying type is expected to only have one of these.
|
||||||
|
ValueField []int
|
||||||
}
|
}
|
||||||
|
|
||||||
// structInfoCache caches type information.
|
// structInfoCache caches type information.
|
||||||
|
@ -68,6 +74,15 @@ func buildStructInfo(typ reflect.Type) structInfo {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this field has type [config.Value], we populate it with the source [config.Value] from [ToTyped].
|
||||||
|
if sf.IsExported() && sf.Type == configValueType {
|
||||||
|
if out.ValueField != nil {
|
||||||
|
panic("multiple config.Value fields")
|
||||||
|
}
|
||||||
|
out.ValueField = append(prefix, sf.Index...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
name, _, _ := strings.Cut(sf.Tag.Get("json"), ",")
|
name, _, _ := strings.Cut(sf.Tag.Get("json"), ",")
|
||||||
if name == "" || name == "-" {
|
if name == "" || name == "-" {
|
||||||
continue
|
continue
|
||||||
|
@ -113,3 +128,6 @@ func (s *structInfo) FieldValues(v reflect.Value) map[string]reflect.Value {
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type of [config.Value].
|
||||||
|
var configValueType = reflect.TypeOf((*config.Value)(nil)).Elem()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/databricks/cli/libs/config"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -194,3 +195,32 @@ func TestStructInfoFieldValuesAnonymousByPointer(t *testing.T) {
|
||||||
assert.Empty(t, fv)
|
assert.Empty(t, fv)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStructInfoValueFieldAbsent(t *testing.T) {
|
||||||
|
type Tmp struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
si := getStructInfo(reflect.TypeOf(Tmp{}))
|
||||||
|
assert.Nil(t, si.ValueField)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructInfoValueFieldPresent(t *testing.T) {
|
||||||
|
type Tmp struct {
|
||||||
|
Foo config.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
si := getStructInfo(reflect.TypeOf(Tmp{}))
|
||||||
|
assert.NotNil(t, si.ValueField)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructInfoValueFieldMultiple(t *testing.T) {
|
||||||
|
type Tmp struct {
|
||||||
|
Foo config.Value
|
||||||
|
Bar config.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
getStructInfo(reflect.TypeOf(Tmp{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -83,6 +83,12 @@ func toTypedStruct(dst reflect.Value, src config.Value) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populate field(s) for [config.Value], if any.
|
||||||
|
if info.ValueField != nil {
|
||||||
|
vv := dst.FieldByIndex(info.ValueField)
|
||||||
|
vv.Set(reflect.ValueOf(src))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
case config.KindNil:
|
case config.KindNil:
|
||||||
dst.SetZero()
|
dst.SetZero()
|
||||||
|
|
|
@ -133,6 +133,24 @@ func TestToTypedStructNilOverwrite(t *testing.T) {
|
||||||
assert.Equal(t, Tmp{}, out)
|
assert.Equal(t, Tmp{}, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToTypedStructWithValueField(t *testing.T) {
|
||||||
|
type Tmp struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
|
||||||
|
ConfigValue config.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
var out Tmp
|
||||||
|
v := config.V(map[string]config.Value{
|
||||||
|
"foo": config.V("bar"),
|
||||||
|
})
|
||||||
|
|
||||||
|
err := ToTyped(&out, v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "bar", out.Foo)
|
||||||
|
assert.Equal(t, v, out.ConfigValue)
|
||||||
|
}
|
||||||
|
|
||||||
func TestToTypedMap(t *testing.T) {
|
func TestToTypedMap(t *testing.T) {
|
||||||
var out = map[string]string{}
|
var out = map[string]string{}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue