c217e3c87a
The following changes were required: * integration/build: progressui's signature changed in6b8fbed01e
* builder-next: flightcontrol.Group has become a generic type in8ffc03b8f0
* builder-next/executor: add github.com/moby/buildkit/executor/resources types, necessitated by6e87e4b455
* builder-next: stub util/network/Namespace.Sample(), necessitated by963f16179f
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com> Co-authored-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
334 lines
7.9 KiB
Go
334 lines
7.9 KiB
Go
package converter
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
)
|
|
|
|
// ConvertFrom interface allows structs to define custom conversion functions if the automated reflection-based Convert
|
|
// is not able to convert properties due to name changes or other factors.
|
|
type ConvertFrom interface {
|
|
ConvertFrom(interface{}) error
|
|
}
|
|
|
|
// Convert takes two objects, e.g. v2_1.Document and &v2_2.Document{} and attempts to map all the properties from one
|
|
// to the other. After the automatic mapping, if a struct implements the ConvertFrom interface, this is called to
|
|
// perform any additional conversion logic necessary.
|
|
func Convert(from interface{}, to interface{}) error {
|
|
fromValue := reflect.ValueOf(from)
|
|
|
|
toValuePtr := reflect.ValueOf(to)
|
|
toTypePtr := toValuePtr.Type()
|
|
|
|
if !isPtr(toTypePtr) {
|
|
return fmt.Errorf("TO value provided was not a pointer, unable to set value: %v", to)
|
|
}
|
|
|
|
toValue, err := getValue(fromValue, toTypePtr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// don't set nil values
|
|
if toValue == nilValue {
|
|
return nil
|
|
}
|
|
|
|
// toValuePtr is the passed-in pointer, toValue is also the same type of pointer
|
|
toValuePtr.Elem().Set(toValue.Elem())
|
|
return nil
|
|
}
|
|
|
|
func getValue(fromValue reflect.Value, targetType reflect.Type) (reflect.Value, error) {
|
|
var err error
|
|
|
|
fromType := fromValue.Type()
|
|
|
|
var toValue reflect.Value
|
|
|
|
// handle incoming pointer Types
|
|
if isPtr(fromType) {
|
|
if fromValue.IsNil() {
|
|
return nilValue, nil
|
|
}
|
|
fromValue = fromValue.Elem()
|
|
if !fromValue.IsValid() || fromValue.IsZero() {
|
|
return nilValue, nil
|
|
}
|
|
fromType = fromValue.Type()
|
|
}
|
|
|
|
baseTargetType := targetType
|
|
if isPtr(targetType) {
|
|
baseTargetType = targetType.Elem()
|
|
}
|
|
|
|
switch {
|
|
case isStruct(fromType) && isStruct(baseTargetType):
|
|
// this always creates a pointer type
|
|
toValue = reflect.New(baseTargetType)
|
|
toValue = toValue.Elem()
|
|
|
|
for i := 0; i < fromType.NumField(); i++ {
|
|
fromField := fromType.Field(i)
|
|
fromFieldValue := fromValue.Field(i)
|
|
|
|
toField, exists := baseTargetType.FieldByName(fromField.Name)
|
|
if !exists {
|
|
continue
|
|
}
|
|
toFieldType := toField.Type
|
|
|
|
toFieldValue := toValue.FieldByName(toField.Name)
|
|
|
|
newValue, err := getValue(fromFieldValue, toFieldType)
|
|
if err != nil {
|
|
return nilValue, err
|
|
}
|
|
|
|
if newValue == nilValue {
|
|
continue
|
|
}
|
|
|
|
toFieldValue.Set(newValue)
|
|
}
|
|
|
|
// allow structs to implement a custom convert function from previous/next version struct
|
|
if reflect.PtrTo(baseTargetType).Implements(convertFromType) {
|
|
convertFrom := toValue.Addr().MethodByName(convertFromName)
|
|
if !convertFrom.IsValid() {
|
|
return nilValue, fmt.Errorf("unable to get ConvertFrom method")
|
|
}
|
|
args := []reflect.Value{fromValue}
|
|
out := convertFrom.Call(args)
|
|
err := out[0].Interface()
|
|
if err != nil {
|
|
return nilValue, fmt.Errorf("an error occurred calling %s.%s: %v", baseTargetType.Name(), convertFromName, err)
|
|
}
|
|
}
|
|
case isSlice(fromType) && isSlice(baseTargetType):
|
|
if fromValue.IsNil() {
|
|
return nilValue, nil
|
|
}
|
|
|
|
length := fromValue.Len()
|
|
targetElementType := baseTargetType.Elem()
|
|
toValue = reflect.MakeSlice(baseTargetType, length, length)
|
|
for i := 0; i < length; i++ {
|
|
v, err := getValue(fromValue.Index(i), targetElementType)
|
|
if err != nil {
|
|
return nilValue, err
|
|
}
|
|
if v.IsValid() {
|
|
toValue.Index(i).Set(v)
|
|
}
|
|
}
|
|
case isMap(fromType) && isMap(baseTargetType):
|
|
if fromValue.IsNil() {
|
|
return nilValue, nil
|
|
}
|
|
|
|
keyType := baseTargetType.Key()
|
|
elementType := baseTargetType.Elem()
|
|
toValue = reflect.MakeMap(baseTargetType)
|
|
for _, fromKey := range fromValue.MapKeys() {
|
|
fromVal := fromValue.MapIndex(fromKey)
|
|
k, err := getValue(fromKey, keyType)
|
|
if err != nil {
|
|
return nilValue, err
|
|
}
|
|
v, err := getValue(fromVal, elementType)
|
|
if err != nil {
|
|
return nilValue, err
|
|
}
|
|
if k == nilValue || v == nilValue {
|
|
continue
|
|
}
|
|
if v == nilValue {
|
|
continue
|
|
}
|
|
if k.IsValid() && v.IsValid() {
|
|
toValue.SetMapIndex(k, v)
|
|
}
|
|
}
|
|
default:
|
|
// TODO determine if there are other conversions
|
|
toValue = fromValue
|
|
}
|
|
|
|
// handle non-pointer returns -- the reflect.New earlier always creates a pointer
|
|
if !isPtr(baseTargetType) {
|
|
toValue = fromPtr(toValue)
|
|
}
|
|
|
|
toValue, err = convertValueTypes(toValue, baseTargetType)
|
|
|
|
if err != nil {
|
|
return nilValue, err
|
|
}
|
|
|
|
// handle elements which are now pointers
|
|
if isPtr(targetType) {
|
|
toValue = toPtr(toValue)
|
|
}
|
|
|
|
return toValue, nil
|
|
}
|
|
|
|
// convertValueTypes takes a value and a target type, and attempts to convert
|
|
// between the Types - e.g. string -> int. when this function is called the value
|
|
func convertValueTypes(value reflect.Value, targetType reflect.Type) (reflect.Value, error) {
|
|
typ := value.Type()
|
|
switch {
|
|
// if the Types are the same, just return the value
|
|
case typ.Kind() == targetType.Kind():
|
|
return value, nil
|
|
case value.IsZero() && isPrimitive(targetType):
|
|
|
|
case isPrimitive(typ) && isPrimitive(targetType):
|
|
// get a string representation of the value
|
|
str := fmt.Sprintf("%v", value.Interface()) // TODO is there a better way to get a string representation?
|
|
var err error
|
|
var out interface{}
|
|
switch {
|
|
case isString(targetType):
|
|
out = str
|
|
case isBool(targetType):
|
|
out, err = strconv.ParseBool(str)
|
|
case isInt(targetType):
|
|
out, err = strconv.Atoi(str)
|
|
case isUint(targetType):
|
|
out, err = strconv.ParseUint(str, 10, 64)
|
|
case isFloat(targetType):
|
|
out, err = strconv.ParseFloat(str, 64)
|
|
}
|
|
|
|
if err != nil {
|
|
return nilValue, err
|
|
}
|
|
|
|
v := reflect.ValueOf(out)
|
|
|
|
v = v.Convert(targetType)
|
|
|
|
return v, nil
|
|
case isSlice(typ) && isSlice(targetType):
|
|
// this should already be handled in getValue
|
|
case isSlice(typ):
|
|
// this may be lossy
|
|
if value.Len() > 0 {
|
|
v := value.Index(0)
|
|
v, err := convertValueTypes(v, targetType)
|
|
if err != nil {
|
|
return nilValue, err
|
|
}
|
|
return v, nil
|
|
}
|
|
return convertValueTypes(nilValue, targetType)
|
|
case isSlice(targetType):
|
|
elementType := targetType.Elem()
|
|
v, err := convertValueTypes(value, elementType)
|
|
if err != nil {
|
|
return nilValue, err
|
|
}
|
|
if v == nilValue {
|
|
return v, nil
|
|
}
|
|
slice := reflect.MakeSlice(targetType, 1, 1)
|
|
slice.Index(0).Set(v)
|
|
return slice, nil
|
|
}
|
|
|
|
return nilValue, fmt.Errorf("unable to convert from: %v to %v", value.Interface(), targetType.Name())
|
|
}
|
|
|
|
func isPtr(typ reflect.Type) bool {
|
|
return typ.Kind() == reflect.Ptr
|
|
}
|
|
|
|
func isPrimitive(typ reflect.Type) bool {
|
|
return isString(typ) || isBool(typ) || isInt(typ) || isUint(typ) || isFloat(typ)
|
|
}
|
|
|
|
func isString(typ reflect.Type) bool {
|
|
return typ.Kind() == reflect.String
|
|
}
|
|
|
|
func isBool(typ reflect.Type) bool {
|
|
return typ.Kind() == reflect.Bool
|
|
}
|
|
|
|
func isInt(typ reflect.Type) bool {
|
|
switch typ.Kind() {
|
|
case reflect.Int,
|
|
reflect.Int8,
|
|
reflect.Int16,
|
|
reflect.Int32,
|
|
reflect.Int64:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isUint(typ reflect.Type) bool {
|
|
switch typ.Kind() {
|
|
case reflect.Uint,
|
|
reflect.Uint8,
|
|
reflect.Uint16,
|
|
reflect.Uint32,
|
|
reflect.Uint64:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isFloat(typ reflect.Type) bool {
|
|
switch typ.Kind() {
|
|
case reflect.Float32,
|
|
reflect.Float64:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isStruct(typ reflect.Type) bool {
|
|
return typ.Kind() == reflect.Struct
|
|
}
|
|
|
|
func isSlice(typ reflect.Type) bool {
|
|
return typ.Kind() == reflect.Slice
|
|
}
|
|
|
|
func isMap(typ reflect.Type) bool {
|
|
return typ.Kind() == reflect.Map
|
|
}
|
|
|
|
func toPtr(val reflect.Value) reflect.Value {
|
|
typ := val.Type()
|
|
if !isPtr(typ) {
|
|
// this creates a pointer type inherently
|
|
ptrVal := reflect.New(typ)
|
|
ptrVal.Elem().Set(val)
|
|
val = ptrVal
|
|
}
|
|
return val
|
|
}
|
|
|
|
func fromPtr(val reflect.Value) reflect.Value {
|
|
if isPtr(val.Type()) {
|
|
val = val.Elem()
|
|
}
|
|
return val
|
|
}
|
|
|
|
// convertFromName constant to find the ConvertFrom method
|
|
const convertFromName = "ConvertFrom"
|
|
|
|
var (
|
|
// nilValue is returned in a number of cases when a value should not be set
|
|
nilValue = reflect.ValueOf(nil)
|
|
|
|
// convertFromType is the type to check for ConvertFrom implementations
|
|
convertFromType = reflect.TypeOf((*ConvertFrom)(nil)).Elem()
|
|
)
|