moby/vendor/github.com/anchore/go-struct-converter/converter.go
Bjorn Neergaard c217e3c87a
vendor: github.com/moby/buildkit v0.12.2
The following changes were required:
* integration/build: progressui's signature changed in 6b8fbed01e
* builder-next: flightcontrol.Group has become a generic type in 8ffc03b8f0
* builder-next/executor: add github.com/moby/buildkit/executor/resources types, necessitated by 6e87e4b455
* builder-next: stub util/network/Namespace.Sample(), necessitated by 963f16179f

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>
2023-09-21 14:18:45 -06:00

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()
)