|
@@ -0,0 +1,828 @@
|
|
|
+package copier
|
|
|
+
|
|
|
+import (
|
|
|
+ "database/sql"
|
|
|
+ "database/sql/driver"
|
|
|
+ "fmt"
|
|
|
+ "reflect"
|
|
|
+ "strings"
|
|
|
+ "sync"
|
|
|
+ "unicode"
|
|
|
+)
|
|
|
+
|
|
|
+// These flags define options for tag handling
|
|
|
+const (
|
|
|
+ // Denotes that a destination field must be copied to. If copying fails then a panic will ensue.
|
|
|
+ tagMust uint8 = 1 << iota
|
|
|
+
|
|
|
+ // Denotes that the program should not panic when the must flag is on and
|
|
|
+ // value is not copied. The program will return an error instead.
|
|
|
+ tagNoPanic
|
|
|
+
|
|
|
+ // Ignore a destination field from being copied to.
|
|
|
+ tagIgnore
|
|
|
+
|
|
|
+ // Denotes that the value as been copied
|
|
|
+ hasCopied
|
|
|
+
|
|
|
+ // Some default converter types for a nicer syntax
|
|
|
+ String string = ""
|
|
|
+ Bool bool = false
|
|
|
+ Int int = 0
|
|
|
+ Float32 float32 = 0
|
|
|
+ Float64 float64 = 0
|
|
|
+)
|
|
|
+
|
|
|
+// Option sets copy options
|
|
|
+type Option struct {
|
|
|
+ // setting this value to true will ignore copying zero values of all the fields, including bools, as well as a
|
|
|
+ // struct having all it's fields set to their zero values respectively (see IsZero() in reflect/value.go)
|
|
|
+ IgnoreEmpty bool
|
|
|
+ CaseSensitive bool
|
|
|
+ DeepCopy bool
|
|
|
+ Converters []TypeConverter
|
|
|
+ // Custom field name mappings to copy values with different names in `fromValue` and `toValue` types.
|
|
|
+ // Examples can be found in `copier_field_name_mapping_test.go`.
|
|
|
+ FieldNameMapping []FieldNameMapping
|
|
|
+}
|
|
|
+
|
|
|
+func (opt Option) converters() map[converterPair]TypeConverter {
|
|
|
+ var converters = map[converterPair]TypeConverter{}
|
|
|
+
|
|
|
+ // save converters into map for faster lookup
|
|
|
+ for i := range opt.Converters {
|
|
|
+ pair := converterPair{
|
|
|
+ SrcType: reflect.TypeOf(opt.Converters[i].SrcType),
|
|
|
+ DstType: reflect.TypeOf(opt.Converters[i].DstType),
|
|
|
+ }
|
|
|
+
|
|
|
+ converters[pair] = opt.Converters[i]
|
|
|
+ }
|
|
|
+
|
|
|
+ return converters
|
|
|
+}
|
|
|
+
|
|
|
+type TypeConverter struct {
|
|
|
+ SrcType interface{}
|
|
|
+ DstType interface{}
|
|
|
+ Fn func(src interface{}) (dst interface{}, err error)
|
|
|
+}
|
|
|
+
|
|
|
+type converterPair struct {
|
|
|
+ SrcType reflect.Type
|
|
|
+ DstType reflect.Type
|
|
|
+}
|
|
|
+
|
|
|
+func (opt Option) fieldNameMapping() map[converterPair]FieldNameMapping {
|
|
|
+ var mapping = map[converterPair]FieldNameMapping{}
|
|
|
+
|
|
|
+ for i := range opt.FieldNameMapping {
|
|
|
+ pair := converterPair{
|
|
|
+ SrcType: reflect.TypeOf(opt.FieldNameMapping[i].SrcType),
|
|
|
+ DstType: reflect.TypeOf(opt.FieldNameMapping[i].DstType),
|
|
|
+ }
|
|
|
+
|
|
|
+ mapping[pair] = opt.FieldNameMapping[i]
|
|
|
+ }
|
|
|
+
|
|
|
+ return mapping
|
|
|
+}
|
|
|
+
|
|
|
+type FieldNameMapping struct {
|
|
|
+ SrcType interface{}
|
|
|
+ DstType interface{}
|
|
|
+ Mapping map[string]string
|
|
|
+}
|
|
|
+
|
|
|
+// Tag Flags
|
|
|
+type flags struct {
|
|
|
+ BitFlags map[string]uint8
|
|
|
+ SrcNames tagNameMapping
|
|
|
+ DestNames tagNameMapping
|
|
|
+}
|
|
|
+
|
|
|
+// Field Tag name mapping
|
|
|
+type tagNameMapping struct {
|
|
|
+ FieldNameToTag map[string]string
|
|
|
+ TagToFieldName map[string]string
|
|
|
+}
|
|
|
+
|
|
|
+// Copy copy things
|
|
|
+func Copy(toValue interface{}, fromValue interface{}) (err error) {
|
|
|
+ return copier(toValue, fromValue, Option{})
|
|
|
+}
|
|
|
+
|
|
|
+// CopyWithOption copy with option
|
|
|
+func CopyWithOption(toValue interface{}, fromValue interface{}, opt Option) (err error) {
|
|
|
+ return copier(toValue, fromValue, opt)
|
|
|
+}
|
|
|
+
|
|
|
+func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) {
|
|
|
+ var (
|
|
|
+ isSlice bool
|
|
|
+ amount = 1
|
|
|
+ from = indirect(reflect.ValueOf(fromValue))
|
|
|
+ to = indirect(reflect.ValueOf(toValue))
|
|
|
+ converters = opt.converters()
|
|
|
+ mappings = opt.fieldNameMapping()
|
|
|
+ )
|
|
|
+
|
|
|
+ if !to.CanAddr() {
|
|
|
+ return ErrInvalidCopyDestination
|
|
|
+ }
|
|
|
+
|
|
|
+ // Return is from value is invalid
|
|
|
+ if !from.IsValid() {
|
|
|
+ return ErrInvalidCopyFrom
|
|
|
+ }
|
|
|
+
|
|
|
+ fromType, isPtrFrom := indirectType(from.Type())
|
|
|
+ toType, _ := indirectType(to.Type())
|
|
|
+
|
|
|
+ if fromType.Kind() == reflect.Interface {
|
|
|
+ fromType = reflect.TypeOf(from.Interface())
|
|
|
+ }
|
|
|
+
|
|
|
+ if toType.Kind() == reflect.Interface {
|
|
|
+ toType, _ = indirectType(reflect.TypeOf(to.Interface()))
|
|
|
+ oldTo := to
|
|
|
+ to = reflect.New(reflect.TypeOf(to.Interface())).Elem()
|
|
|
+ defer func() {
|
|
|
+ oldTo.Set(to)
|
|
|
+ }()
|
|
|
+ }
|
|
|
+
|
|
|
+ // Just set it if possible to assign for normal types
|
|
|
+ if from.Kind() != reflect.Slice && from.Kind() != reflect.Struct && from.Kind() != reflect.Map && (from.Type().AssignableTo(to.Type()) || from.Type().ConvertibleTo(to.Type())) {
|
|
|
+ if !isPtrFrom || !opt.DeepCopy {
|
|
|
+ to.Set(from.Convert(to.Type()))
|
|
|
+ } else {
|
|
|
+ fromCopy := reflect.New(from.Type())
|
|
|
+ fromCopy.Set(from.Elem())
|
|
|
+ to.Set(fromCopy.Convert(to.Type()))
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if from.Kind() != reflect.Slice && fromType.Kind() == reflect.Map && toType.Kind() == reflect.Map {
|
|
|
+ if !fromType.Key().ConvertibleTo(toType.Key()) {
|
|
|
+ return ErrMapKeyNotMatch
|
|
|
+ }
|
|
|
+
|
|
|
+ if to.IsNil() {
|
|
|
+ to.Set(reflect.MakeMapWithSize(toType, from.Len()))
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, k := range from.MapKeys() {
|
|
|
+ toKey := indirect(reflect.New(toType.Key()))
|
|
|
+ isSet, err := set(toKey, k, opt.DeepCopy, converters)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if !isSet {
|
|
|
+ return fmt.Errorf("%w map, old key: %v, new key: %v", ErrNotSupported, k.Type(), toType.Key())
|
|
|
+ }
|
|
|
+
|
|
|
+ elemType := toType.Elem()
|
|
|
+ if elemType.Kind() != reflect.Slice {
|
|
|
+ elemType, _ = indirectType(elemType)
|
|
|
+ }
|
|
|
+ toValue := indirect(reflect.New(elemType))
|
|
|
+ isSet, err = set(toValue, from.MapIndex(k), opt.DeepCopy, converters)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if !isSet {
|
|
|
+ if err = copier(toValue.Addr().Interface(), from.MapIndex(k).Interface(), opt); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for {
|
|
|
+ if elemType == toType.Elem() {
|
|
|
+ to.SetMapIndex(toKey, toValue)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ elemType = reflect.PtrTo(elemType)
|
|
|
+ toValue = toValue.Addr()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if from.Kind() == reflect.Slice && to.Kind() == reflect.Slice {
|
|
|
+ if to.IsNil() {
|
|
|
+ slice := reflect.MakeSlice(reflect.SliceOf(to.Type().Elem()), from.Len(), from.Cap())
|
|
|
+ to.Set(slice)
|
|
|
+ }
|
|
|
+ if fromType.ConvertibleTo(toType) {
|
|
|
+ for i := 0; i < from.Len(); i++ {
|
|
|
+ if to.Len() < i+1 {
|
|
|
+ to.Set(reflect.Append(to, reflect.New(to.Type().Elem()).Elem()))
|
|
|
+ }
|
|
|
+ isSet, err := set(to.Index(i), from.Index(i), opt.DeepCopy, converters)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if !isSet {
|
|
|
+ // ignore error while copy slice element
|
|
|
+ err = copier(to.Index(i).Addr().Interface(), from.Index(i).Interface(), opt)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct {
|
|
|
+ // skip not supported type
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(converters) > 0 {
|
|
|
+ if ok, e := set(to, from, opt.DeepCopy, converters); e == nil && ok {
|
|
|
+ // converter supported
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if from.Kind() == reflect.Slice || to.Kind() == reflect.Slice {
|
|
|
+ isSlice = true
|
|
|
+ if from.Kind() == reflect.Slice {
|
|
|
+ amount = from.Len()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for i := 0; i < amount; i++ {
|
|
|
+ var dest, source reflect.Value
|
|
|
+
|
|
|
+ if isSlice {
|
|
|
+ // source
|
|
|
+ if from.Kind() == reflect.Slice {
|
|
|
+ source = indirect(from.Index(i))
|
|
|
+ } else {
|
|
|
+ source = indirect(from)
|
|
|
+ }
|
|
|
+ // dest
|
|
|
+ dest = indirect(reflect.New(toType).Elem())
|
|
|
+ } else {
|
|
|
+ source = indirect(from)
|
|
|
+ dest = indirect(to)
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(converters) > 0 {
|
|
|
+ if ok, e := set(dest, source, opt.DeepCopy, converters); e == nil && ok {
|
|
|
+ if isSlice {
|
|
|
+ // FIXME: maybe should check the other types?
|
|
|
+ if to.Type().Elem().Kind() == reflect.Ptr {
|
|
|
+ to.Index(i).Set(dest.Addr())
|
|
|
+ } else {
|
|
|
+ if to.Len() < i+1 {
|
|
|
+ reflect.Append(to, dest)
|
|
|
+ } else {
|
|
|
+ to.Index(i).Set(dest)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ to.Set(dest)
|
|
|
+ }
|
|
|
+
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ destKind := dest.Kind()
|
|
|
+ initDest := false
|
|
|
+ if destKind == reflect.Interface {
|
|
|
+ initDest = true
|
|
|
+ dest = indirect(reflect.New(toType))
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get tag options
|
|
|
+ flgs, err := getFlags(dest, source, toType, fromType)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // check source
|
|
|
+ if source.IsValid() {
|
|
|
+ copyUnexportedStructFields(dest, source)
|
|
|
+
|
|
|
+ // Copy from source field to dest field or method
|
|
|
+ fromTypeFields := deepFields(fromType)
|
|
|
+ for _, field := range fromTypeFields {
|
|
|
+ name := field.Name
|
|
|
+
|
|
|
+ // Get bit flags for field
|
|
|
+ fieldFlags := flgs.BitFlags[name]
|
|
|
+
|
|
|
+ // Check if we should ignore copying
|
|
|
+ if (fieldFlags & tagIgnore) != 0 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ fieldNamesMapping := getFieldNamesMapping(mappings, fromType, toType)
|
|
|
+
|
|
|
+ srcFieldName, destFieldName := getFieldName(name, flgs, fieldNamesMapping)
|
|
|
+ if fromField := fieldByNameOrZeroValue(source, srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, opt.IgnoreEmpty) {
|
|
|
+ // process for nested anonymous field
|
|
|
+ destFieldNotSet := false
|
|
|
+ if f, ok := dest.Type().FieldByName(destFieldName); ok {
|
|
|
+ // only initialize parent embedded struct pointer in the path
|
|
|
+ for idx := range f.Index[:len(f.Index)-1] {
|
|
|
+ destField := dest.FieldByIndex(f.Index[:idx+1])
|
|
|
+
|
|
|
+ if destField.Kind() != reflect.Ptr {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if !destField.IsNil() {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if !destField.CanSet() {
|
|
|
+ destFieldNotSet = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ // destField is a nil pointer that can be set
|
|
|
+ newValue := reflect.New(destField.Type().Elem())
|
|
|
+ destField.Set(newValue)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if destFieldNotSet {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ toField := fieldByName(dest, destFieldName, opt.CaseSensitive)
|
|
|
+ if toField.IsValid() {
|
|
|
+ if toField.CanSet() {
|
|
|
+ isSet, err := set(toField, fromField, opt.DeepCopy, converters)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if !isSet {
|
|
|
+ if err := copier(toField.Addr().Interface(), fromField.Interface(), opt); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if fieldFlags != 0 {
|
|
|
+ // Note that a copy was made
|
|
|
+ flgs.BitFlags[name] = fieldFlags | hasCopied
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // try to set to method
|
|
|
+ var toMethod reflect.Value
|
|
|
+ if dest.CanAddr() {
|
|
|
+ toMethod = dest.Addr().MethodByName(destFieldName)
|
|
|
+ } else {
|
|
|
+ toMethod = dest.MethodByName(destFieldName)
|
|
|
+ }
|
|
|
+
|
|
|
+ if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) {
|
|
|
+ toMethod.Call([]reflect.Value{fromField})
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Copy from from method to dest field
|
|
|
+ for _, field := range deepFields(toType) {
|
|
|
+ name := field.Name
|
|
|
+ srcFieldName, destFieldName := getFieldName(name, flgs, getFieldNamesMapping(mappings, fromType, toType))
|
|
|
+
|
|
|
+ var fromMethod reflect.Value
|
|
|
+ if source.CanAddr() {
|
|
|
+ fromMethod = source.Addr().MethodByName(srcFieldName)
|
|
|
+ } else {
|
|
|
+ fromMethod = source.MethodByName(srcFieldName)
|
|
|
+ }
|
|
|
+
|
|
|
+ if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, opt.IgnoreEmpty) {
|
|
|
+ if toField := fieldByName(dest, destFieldName, opt.CaseSensitive); toField.IsValid() && toField.CanSet() {
|
|
|
+ values := fromMethod.Call([]reflect.Value{})
|
|
|
+ if len(values) >= 1 {
|
|
|
+ set(toField, values[0], opt.DeepCopy, converters)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if isSlice && to.Kind() == reflect.Slice {
|
|
|
+ if dest.Addr().Type().AssignableTo(to.Type().Elem()) {
|
|
|
+ if to.Len() < i+1 {
|
|
|
+ to.Set(reflect.Append(to, dest.Addr()))
|
|
|
+ } else {
|
|
|
+ isSet, err := set(to.Index(i), dest.Addr(), opt.DeepCopy, converters)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if !isSet {
|
|
|
+ // ignore error while copy slice element
|
|
|
+ err = copier(to.Index(i).Addr().Interface(), dest.Addr().Interface(), opt)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if dest.Type().AssignableTo(to.Type().Elem()) {
|
|
|
+ if to.Len() < i+1 {
|
|
|
+ to.Set(reflect.Append(to, dest))
|
|
|
+ } else {
|
|
|
+ isSet, err := set(to.Index(i), dest, opt.DeepCopy, converters)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if !isSet {
|
|
|
+ // ignore error while copy slice element
|
|
|
+ err = copier(to.Index(i).Addr().Interface(), dest.Interface(), opt)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if initDest {
|
|
|
+ to.Set(dest)
|
|
|
+ }
|
|
|
+
|
|
|
+ err = checkBitFlags(flgs.BitFlags)
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func getFieldNamesMapping(mappings map[converterPair]FieldNameMapping, fromType reflect.Type, toType reflect.Type) map[string]string {
|
|
|
+ var fieldNamesMapping map[string]string
|
|
|
+
|
|
|
+ if len(mappings) > 0 {
|
|
|
+ pair := converterPair{
|
|
|
+ SrcType: fromType,
|
|
|
+ DstType: toType,
|
|
|
+ }
|
|
|
+ if v, ok := mappings[pair]; ok {
|
|
|
+ fieldNamesMapping = v.Mapping
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return fieldNamesMapping
|
|
|
+}
|
|
|
+
|
|
|
+func fieldByNameOrZeroValue(source reflect.Value, fieldName string) (value reflect.Value) {
|
|
|
+ defer func() {
|
|
|
+ if err := recover(); err != nil {
|
|
|
+ value = reflect.Value{}
|
|
|
+ }
|
|
|
+ }()
|
|
|
+
|
|
|
+ return source.FieldByName(fieldName)
|
|
|
+}
|
|
|
+
|
|
|
+func copyUnexportedStructFields(to, from reflect.Value) {
|
|
|
+ if from.Kind() != reflect.Struct || to.Kind() != reflect.Struct || !from.Type().AssignableTo(to.Type()) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // create a shallow copy of 'to' to get all fields
|
|
|
+ tmp := indirect(reflect.New(to.Type()))
|
|
|
+ tmp.Set(from)
|
|
|
+
|
|
|
+ // revert exported fields
|
|
|
+ for i := 0; i < to.NumField(); i++ {
|
|
|
+ if tmp.Field(i).CanSet() {
|
|
|
+ tmp.Field(i).Set(to.Field(i))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ to.Set(tmp)
|
|
|
+}
|
|
|
+
|
|
|
+func shouldIgnore(v reflect.Value, ignoreEmpty bool) bool {
|
|
|
+ return ignoreEmpty && v.IsZero()
|
|
|
+}
|
|
|
+
|
|
|
+var deepFieldsLock sync.RWMutex
|
|
|
+var deepFieldsMap = make(map[reflect.Type][]reflect.StructField)
|
|
|
+
|
|
|
+func deepFields(reflectType reflect.Type) []reflect.StructField {
|
|
|
+ deepFieldsLock.RLock()
|
|
|
+ cache, ok := deepFieldsMap[reflectType]
|
|
|
+ deepFieldsLock.RUnlock()
|
|
|
+ if ok {
|
|
|
+ return cache
|
|
|
+ }
|
|
|
+ var res []reflect.StructField
|
|
|
+ if reflectType, _ = indirectType(reflectType); reflectType.Kind() == reflect.Struct {
|
|
|
+ fields := make([]reflect.StructField, 0, reflectType.NumField())
|
|
|
+
|
|
|
+ for i := 0; i < reflectType.NumField(); i++ {
|
|
|
+ v := reflectType.Field(i)
|
|
|
+ // PkgPath is the package path that qualifies a lower case (unexported)
|
|
|
+ // field name. It is empty for upper case (exported) field names.
|
|
|
+ // See https://golang.org/ref/spec#Uniqueness_of_identifiers
|
|
|
+ if v.PkgPath == "" {
|
|
|
+ fields = append(fields, v)
|
|
|
+ if v.Anonymous {
|
|
|
+ // also consider fields of anonymous fields as fields of the root
|
|
|
+ fields = append(fields, deepFields(v.Type)...)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ res = fields
|
|
|
+ }
|
|
|
+
|
|
|
+ deepFieldsLock.Lock()
|
|
|
+ deepFieldsMap[reflectType] = res
|
|
|
+ deepFieldsLock.Unlock()
|
|
|
+ return res
|
|
|
+}
|
|
|
+
|
|
|
+func indirect(reflectValue reflect.Value) reflect.Value {
|
|
|
+ for reflectValue.Kind() == reflect.Ptr {
|
|
|
+ reflectValue = reflectValue.Elem()
|
|
|
+ }
|
|
|
+ return reflectValue
|
|
|
+}
|
|
|
+
|
|
|
+func indirectType(reflectType reflect.Type) (_ reflect.Type, isPtr bool) {
|
|
|
+ for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice {
|
|
|
+ reflectType = reflectType.Elem()
|
|
|
+ isPtr = true
|
|
|
+ }
|
|
|
+ return reflectType, isPtr
|
|
|
+}
|
|
|
+
|
|
|
+func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]TypeConverter) (bool, error) {
|
|
|
+ if !from.IsValid() {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+ if ok, err := lookupAndCopyWithConverter(to, from, converters); err != nil {
|
|
|
+ return false, err
|
|
|
+ } else if ok {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ if to.Kind() == reflect.Ptr {
|
|
|
+ // set `to` to nil if from is nil
|
|
|
+ if from.Kind() == reflect.Ptr && from.IsNil() {
|
|
|
+ to.Set(reflect.Zero(to.Type()))
|
|
|
+ return true, nil
|
|
|
+ } else if to.IsNil() {
|
|
|
+ // `from` -> `to`
|
|
|
+ // sql.NullString -> *string
|
|
|
+ if fromValuer, ok := driverValuer(from); ok {
|
|
|
+ v, err := fromValuer.Value()
|
|
|
+ if err != nil {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+ // if `from` is not valid do nothing with `to`
|
|
|
+ if v == nil {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // allocate new `to` variable with default value (eg. *string -> new(string))
|
|
|
+ to.Set(reflect.New(to.Type().Elem()))
|
|
|
+ }
|
|
|
+ // depointer `to`
|
|
|
+ to = to.Elem()
|
|
|
+ }
|
|
|
+
|
|
|
+ if deepCopy {
|
|
|
+ toKind := to.Kind()
|
|
|
+ if toKind == reflect.Interface && to.IsNil() {
|
|
|
+ if reflect.TypeOf(from.Interface()) != nil {
|
|
|
+ to.Set(reflect.New(reflect.TypeOf(from.Interface())).Elem())
|
|
|
+ toKind = reflect.TypeOf(to.Interface()).Kind()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if from.Kind() == reflect.Ptr && from.IsNil() {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+ if _, ok := to.Addr().Interface().(sql.Scanner); !ok && (toKind == reflect.Struct || toKind == reflect.Map || toKind == reflect.Slice) {
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if from.Type().ConvertibleTo(to.Type()) {
|
|
|
+ to.Set(from.Convert(to.Type()))
|
|
|
+ } else if toScanner, ok := to.Addr().Interface().(sql.Scanner); ok {
|
|
|
+ // `from` -> `to`
|
|
|
+ // *string -> sql.NullString
|
|
|
+ if from.Kind() == reflect.Ptr {
|
|
|
+ // if `from` is nil do nothing with `to`
|
|
|
+ if from.IsNil() {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+ // depointer `from`
|
|
|
+ from = indirect(from)
|
|
|
+ }
|
|
|
+ // `from` -> `to`
|
|
|
+ // string -> sql.NullString
|
|
|
+ // set `to` by invoking method Scan(`from`)
|
|
|
+ err := toScanner.Scan(from.Interface())
|
|
|
+ if err != nil {
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+ } else if fromValuer, ok := driverValuer(from); ok {
|
|
|
+ // `from` -> `to`
|
|
|
+ // sql.NullString -> string
|
|
|
+ v, err := fromValuer.Value()
|
|
|
+ if err != nil {
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+ // if `from` is not valid do nothing with `to`
|
|
|
+ if v == nil {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+ rv := reflect.ValueOf(v)
|
|
|
+ if rv.Type().AssignableTo(to.Type()) {
|
|
|
+ to.Set(rv)
|
|
|
+ } else if to.CanSet() && rv.Type().ConvertibleTo(to.Type()) {
|
|
|
+ to.Set(rv.Convert(to.Type()))
|
|
|
+ }
|
|
|
+ } else if from.Kind() == reflect.Ptr {
|
|
|
+ return set(to, from.Elem(), deepCopy, converters)
|
|
|
+ } else {
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return true, nil
|
|
|
+}
|
|
|
+
|
|
|
+// lookupAndCopyWithConverter looks up the type pair, on success the TypeConverter Fn func is called to copy src to dst field.
|
|
|
+func lookupAndCopyWithConverter(to, from reflect.Value, converters map[converterPair]TypeConverter) (copied bool, err error) {
|
|
|
+ pair := converterPair{
|
|
|
+ SrcType: from.Type(),
|
|
|
+ DstType: to.Type(),
|
|
|
+ }
|
|
|
+
|
|
|
+ if cnv, ok := converters[pair]; ok {
|
|
|
+ result, err := cnv.Fn(from.Interface())
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+
|
|
|
+ if result != nil {
|
|
|
+ to.Set(reflect.ValueOf(result))
|
|
|
+ } else {
|
|
|
+ // in case we've got a nil value to copy
|
|
|
+ to.Set(reflect.Zero(to.Type()))
|
|
|
+ }
|
|
|
+
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return false, nil
|
|
|
+}
|
|
|
+
|
|
|
+// parseTags Parses struct tags and returns uint8 bit flags.
|
|
|
+func parseTags(tag string) (flg uint8, name string, err error) {
|
|
|
+ for _, t := range strings.Split(tag, ",") {
|
|
|
+ switch t {
|
|
|
+ case "-":
|
|
|
+ flg = tagIgnore
|
|
|
+ return
|
|
|
+ case "must":
|
|
|
+ flg = flg | tagMust
|
|
|
+ case "nopanic":
|
|
|
+ flg = flg | tagNoPanic
|
|
|
+ default:
|
|
|
+ if unicode.IsUpper([]rune(t)[0]) {
|
|
|
+ name = strings.TrimSpace(t)
|
|
|
+ } else {
|
|
|
+ err = ErrFieldNameTagStartNotUpperCase
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// getTagFlags Parses struct tags for bit flags, field name.
|
|
|
+func getFlags(dest, src reflect.Value, toType, fromType reflect.Type) (flags, error) {
|
|
|
+ flgs := flags{
|
|
|
+ BitFlags: map[string]uint8{},
|
|
|
+ SrcNames: tagNameMapping{
|
|
|
+ FieldNameToTag: map[string]string{},
|
|
|
+ TagToFieldName: map[string]string{},
|
|
|
+ },
|
|
|
+ DestNames: tagNameMapping{
|
|
|
+ FieldNameToTag: map[string]string{},
|
|
|
+ TagToFieldName: map[string]string{},
|
|
|
+ },
|
|
|
+ }
|
|
|
+ var toTypeFields, fromTypeFields []reflect.StructField
|
|
|
+ if dest.IsValid() {
|
|
|
+ toTypeFields = deepFields(toType)
|
|
|
+ }
|
|
|
+ if src.IsValid() {
|
|
|
+ fromTypeFields = deepFields(fromType)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get a list dest of tags
|
|
|
+ for _, field := range toTypeFields {
|
|
|
+ tags := field.Tag.Get("copier")
|
|
|
+ if tags != "" {
|
|
|
+ var name string
|
|
|
+ var err error
|
|
|
+ if flgs.BitFlags[field.Name], name, err = parseTags(tags); err != nil {
|
|
|
+ return flags{}, err
|
|
|
+ } else if name != "" {
|
|
|
+ flgs.DestNames.FieldNameToTag[field.Name] = name
|
|
|
+ flgs.DestNames.TagToFieldName[name] = field.Name
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get a list source of tags
|
|
|
+ for _, field := range fromTypeFields {
|
|
|
+ tags := field.Tag.Get("copier")
|
|
|
+ if tags != "" {
|
|
|
+ var name string
|
|
|
+ var err error
|
|
|
+ if _, name, err = parseTags(tags); err != nil {
|
|
|
+ return flags{}, err
|
|
|
+ } else if name != "" {
|
|
|
+ flgs.SrcNames.FieldNameToTag[field.Name] = name
|
|
|
+ flgs.SrcNames.TagToFieldName[name] = field.Name
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return flgs, nil
|
|
|
+}
|
|
|
+
|
|
|
+// checkBitFlags Checks flags for error or panic conditions.
|
|
|
+func checkBitFlags(flagsList map[string]uint8) (err error) {
|
|
|
+ // Check flag conditions were met
|
|
|
+ for name, flgs := range flagsList {
|
|
|
+ if flgs&hasCopied == 0 {
|
|
|
+ switch {
|
|
|
+ case flgs&tagMust != 0 && flgs&tagNoPanic != 0:
|
|
|
+ err = fmt.Errorf("field %s has must tag but was not copied", name)
|
|
|
+ return
|
|
|
+ case flgs&(tagMust) != 0:
|
|
|
+ panic(fmt.Sprintf("Field %s has must tag but was not copied", name))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func getFieldName(fieldName string, flgs flags, fieldNameMapping map[string]string) (srcFieldName string, destFieldName string) {
|
|
|
+ // get dest field name
|
|
|
+ if name, ok := fieldNameMapping[fieldName]; ok {
|
|
|
+ srcFieldName = fieldName
|
|
|
+ destFieldName = name
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if srcTagName, ok := flgs.SrcNames.FieldNameToTag[fieldName]; ok {
|
|
|
+ destFieldName = srcTagName
|
|
|
+ if destTagName, ok := flgs.DestNames.TagToFieldName[srcTagName]; ok {
|
|
|
+ destFieldName = destTagName
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if destTagName, ok := flgs.DestNames.TagToFieldName[fieldName]; ok {
|
|
|
+ destFieldName = destTagName
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if destFieldName == "" {
|
|
|
+ destFieldName = fieldName
|
|
|
+ }
|
|
|
+
|
|
|
+ // get source field name
|
|
|
+ if destTagName, ok := flgs.DestNames.FieldNameToTag[fieldName]; ok {
|
|
|
+ srcFieldName = destTagName
|
|
|
+ if srcField, ok := flgs.SrcNames.TagToFieldName[destTagName]; ok {
|
|
|
+ srcFieldName = srcField
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if srcField, ok := flgs.SrcNames.TagToFieldName[fieldName]; ok {
|
|
|
+ srcFieldName = srcField
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if srcFieldName == "" {
|
|
|
+ srcFieldName = fieldName
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func driverValuer(v reflect.Value) (i driver.Valuer, ok bool) {
|
|
|
+ if !v.CanAddr() {
|
|
|
+ i, ok = v.Interface().(driver.Valuer)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ i, ok = v.Addr().Interface().(driver.Valuer)
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func fieldByName(v reflect.Value, name string, caseSensitive bool) reflect.Value {
|
|
|
+ if caseSensitive {
|
|
|
+ return v.FieldByName(name)
|
|
|
+ }
|
|
|
+
|
|
|
+ return v.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) })
|
|
|
+}
|