Factor out API defaulting from validation logic

Currently, the validation logic validates fields in an object and supply default
values wherever applies. This change factors out defaulting to a set of
defaulting callback functions for decoding (see #1502 for more discussion).

 * This change is based on pull request 2587.

 * Most defaulting has been migrated to defaults.go where the defaulting
   functions are added.

 * validation_test.go and converter_test.go have been adapted to not testing the
   default values.

 * Fixed all tests with that create invalid objects with the absence of
   defaulting logic.
This commit is contained in:
Yu-Ju Hong
2015-01-26 09:52:50 -08:00
parent 1ddb68d8d7
commit 4a72addaeb
40 changed files with 1059 additions and 384 deletions

View File

@@ -68,7 +68,6 @@ func NewConverter() *Converter {
return &Converter{
conversionFuncs: map[typePair]reflect.Value{},
defaultingFuncs: map[reflect.Type]reflect.Value{},
funcs: map[typePair]reflect.Value{},
nameFunc: func(t reflect.Type) string { return t.Name() },
structFieldDests: map[typeNamePair][]typeNamePair{},
structFieldSources: map[typeNamePair][]typeNamePair{},
@@ -221,7 +220,7 @@ func (s *scope) error(message string, args ...interface{}) error {
// used if recursive conversion calls are desired). It must return an error.
//
// Example:
// c.RegisteConversionFuncr(
// c.RegisteConversionFunc(
// func(in *Pod, out *v1beta1.Pod, s Scope) error {
// // conversion logic...
// return nil
@@ -279,7 +278,7 @@ func (c *Converter) SetStructFieldCopy(srcFieldType interface{}, srcFieldName st
// defaultingFunc must take one parameters: a pointer to the input type.
//
// Example:
// c.RegisteDefaultingFuncr(
// c.RegisteDefaultingFunc(
// func(in *v1beta1.Pod) {
// // defaulting logic...
// })
@@ -415,7 +414,6 @@ func (c *Converter) callCustom(sv, dv, custom reflect.Value, scope *scope) error
// one is registered.
func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error {
dt, st := dv.Type(), sv.Type()
// Apply default values.
if fv, ok := c.defaultingFuncs[st]; ok {
if c.Debug != nil {

View File

@@ -36,11 +36,11 @@ func TestConverter_DefaultConvert(t *testing.T) {
}
c := NewConverter()
c.Debug = t
c.NameFunc = func(t reflect.Type) string { return "MyType" }
c.nameFunc = func(t reflect.Type) string { return "MyType" }
// Ensure conversion funcs can call DefaultConvert to get default behavior,
// then fixup remaining fields manually
err := c.Register(func(in *A, out *B, s Scope) error {
err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
if err := s.DefaultConvert(in, out, IgnoreMissingFields); err != nil {
return err
}
@@ -224,7 +224,7 @@ func TestConverter_MapElemAddr(t *testing.T) {
}
c := NewConverter()
c.Debug = t
err := c.Register(
err := c.RegisterConversionFunc(
func(in *int, out *string, s Scope) error {
*out = fmt.Sprintf("%v", *in)
return nil
@@ -233,7 +233,7 @@ func TestConverter_MapElemAddr(t *testing.T) {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
err = c.Register(
err = c.RegisterConversionFunc(
func(in *string, out *int, s Scope) error {
if str, err := strconv.Atoi(*in); err != nil {
return err

View File

@@ -103,27 +103,19 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
dataVersion = objVersion
}
if objVersion == dataVersion {
// Easy case!
err = yaml.Unmarshal(data, obj)
if err != nil {
return err
}
} else {
external, err := s.NewObject(dataVersion, dataKind)
if err != nil {
return err
}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, external)
if err != nil {
return err
}
err = s.converter.Convert(external, obj, 0, s.generateConvertMeta(dataVersion, objVersion))
if err != nil {
return err
}
external, err := s.NewObject(dataVersion, dataKind)
if err != nil {
return err
}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, external)
if err != nil {
return err
}
err = s.converter.Convert(external, obj, 0, s.generateConvertMeta(dataVersion, objVersion))
if err != nil {
return err
}
// Version and Kind should be blank in memory.

View File

@@ -233,3 +233,140 @@ func (e Equalities) DeepEqual(a1, a2 interface{}) bool {
}
return e.deepValueEqual(v1, v2, make(map[visit]bool), 0)
}
func (e Equalities) deepValueDerive(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
if !v1.IsValid() || !v2.IsValid() {
return v1.IsValid() == v2.IsValid()
}
if v1.Type() != v2.Type() {
return false
}
if fv, ok := e[v1.Type()]; ok {
return fv.Call([]reflect.Value{v1, v2})[0].Bool()
}
hard := func(k reflect.Kind) bool {
switch k {
case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct:
return true
}
return false
}
if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) {
addr1 := v1.UnsafeAddr()
addr2 := v2.UnsafeAddr()
if addr1 > addr2 {
// Canonicalize order to reduce number of entries in visited.
addr1, addr2 = addr2, addr1
}
// Short circuit if references are identical ...
if addr1 == addr2 {
return true
}
// ... or already seen
typ := v1.Type()
v := visit{addr1, addr2, typ}
if visited[v] {
return true
}
// Remember for later.
visited[v] = true
}
switch v1.Kind() {
case reflect.Array:
for i := 0; i < v1.Len(); i++ {
if !e.deepValueDerive(v1.Index(i), v2.Index(i), visited, depth+1) {
return false
}
}
return true
case reflect.Slice:
if (v1.IsNil() || v1.Len() == 0) != (v2.IsNil() || v2.Len() == 0) {
return false
}
if v1.IsNil() || v1.Len() == 0 {
return true
}
if v1.Pointer() == v2.Pointer() {
return true
}
for i := 0; i < v1.Len(); i++ {
if !e.deepValueDerive(v1.Index(i), v2.Index(i), visited, depth+1) {
return false
}
}
return true
case reflect.String:
if v1.Len() == 0 {
return true
}
return v1.String() == v2.String()
case reflect.Interface:
if v1.IsNil() {
return true
}
return e.deepValueDerive(v1.Elem(), v2.Elem(), visited, depth+1)
case reflect.Ptr:
if v1.IsNil() {
return true
}
return e.deepValueDerive(v1.Elem(), v2.Elem(), visited, depth+1)
case reflect.Struct:
for i, n := 0, v1.NumField(); i < n; i++ {
if !e.deepValueDerive(v1.Field(i), v2.Field(i), visited, depth+1) {
return false
}
}
return true
case reflect.Map:
if (v1.IsNil() || v1.Len() == 0) != (v2.IsNil() || v2.Len() == 0) {
return false
}
if v1.IsNil() || v1.Len() == 0 {
return true
}
if v1.Pointer() == v2.Pointer() {
return true
}
for _, k := range v1.MapKeys() {
if !e.deepValueDerive(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) {
return false
}
}
return true
case reflect.Func:
if v1.IsNil() && v2.IsNil() {
return true
}
// Can't do better than this:
return false
default:
// Normal equality suffices
if v1.CanInterface() && v2.CanInterface() {
return v1.Interface() == v2.Interface()
}
return v1.CanInterface() == v2.CanInterface()
}
}
// DeepDerivative is similar to DeepEqual except that unset fields in a1 are
// ignored (not compared). This allows we to focus on the fields that matter to
// the semantic comparison.
//
// The unset fields include a nil pointer and an empty string.
func (e Equalities) DeepDerivative(a1, a2 interface{}) bool {
if a1 == nil {
return true
}
v1 := reflect.ValueOf(a1)
v2 := reflect.ValueOf(a2)
if v1.Type() != v2.Type() {
return false
}
return e.deepValueDerive(v1, v2, make(map[visit]bool), 0)
}