github.com/gogf/gf/v2@v2.7.4/util/gconv/gconv_converter.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package gconv 8 9 import ( 10 "reflect" 11 12 "github.com/gogf/gf/v2/errors/gcode" 13 "github.com/gogf/gf/v2/errors/gerror" 14 "github.com/gogf/gf/v2/util/gconv/internal/structcache" 15 ) 16 17 type ( 18 converterInType = reflect.Type 19 converterOutType = reflect.Type 20 converterFunc = reflect.Value 21 ) 22 23 // customConverters for internal converter storing. 24 var customConverters = make(map[converterInType]map[converterOutType]converterFunc) 25 26 // RegisterConverter to register custom converter. 27 // It must be registered before you use this custom converting feature. 28 // It is suggested to do it in boot procedure of the process. 29 // 30 // Note: 31 // 1. The parameter `fn` must be defined as pattern `func(T1) (T2, error)`. 32 // It will convert type `T1` to type `T2`. 33 // 2. The `T1` should not be type of pointer, but the `T2` should be type of pointer. 34 func RegisterConverter(fn interface{}) (err error) { 35 var ( 36 fnReflectType = reflect.TypeOf(fn) 37 errType = reflect.TypeOf((*error)(nil)).Elem() 38 ) 39 if fnReflectType.Kind() != reflect.Func || 40 fnReflectType.NumIn() != 1 || fnReflectType.NumOut() != 2 || 41 !fnReflectType.Out(1).Implements(errType) { 42 err = gerror.NewCodef( 43 gcode.CodeInvalidParameter, 44 "parameter must be type of converter function and defined as pattern `func(T1) (T2, error)`, but defined as `%s`", 45 fnReflectType.String(), 46 ) 47 return 48 } 49 50 // The Key and Value of the converter map should not be pointer. 51 var ( 52 inType = fnReflectType.In(0) 53 outType = fnReflectType.Out(0) 54 ) 55 if inType.Kind() == reflect.Pointer { 56 err = gerror.NewCodef( 57 gcode.CodeInvalidParameter, 58 "invalid converter function `%s`: invalid input parameter type `%s`, should not be type of pointer", 59 fnReflectType.String(), inType.String(), 60 ) 61 return 62 } 63 if outType.Kind() != reflect.Pointer { 64 err = gerror.NewCodef( 65 gcode.CodeInvalidParameter, 66 "invalid converter function `%s`: invalid output parameter type `%s` should be type of pointer", 67 fnReflectType.String(), outType.String(), 68 ) 69 return 70 } 71 72 registeredOutTypeMap, ok := customConverters[inType] 73 if !ok { 74 registeredOutTypeMap = make(map[converterOutType]converterFunc) 75 customConverters[inType] = registeredOutTypeMap 76 } 77 if _, ok = registeredOutTypeMap[outType]; ok { 78 err = gerror.NewCodef( 79 gcode.CodeInvalidOperation, 80 "the converter parameter type `%s` to type `%s` has already been registered", 81 inType.String(), outType.String(), 82 ) 83 return 84 } 85 registeredOutTypeMap[outType] = reflect.ValueOf(fn) 86 structcache.RegisterCustomConvertType(outType) 87 return 88 } 89 90 func getRegisteredConverterFuncAndSrcType( 91 srcReflectValue, dstReflectValueForRefer reflect.Value, 92 ) (f converterFunc, srcType reflect.Type, ok bool) { 93 if len(customConverters) == 0 { 94 return reflect.Value{}, nil, false 95 } 96 srcType = srcReflectValue.Type() 97 for srcType.Kind() == reflect.Pointer { 98 srcType = srcType.Elem() 99 } 100 var registeredOutTypeMap map[converterOutType]converterFunc 101 // firstly, it searches the map by input parameter type. 102 registeredOutTypeMap, ok = customConverters[srcType] 103 if !ok { 104 return reflect.Value{}, nil, false 105 } 106 var dstType = dstReflectValueForRefer.Type() 107 if dstType.Kind() == reflect.Pointer { 108 // Might be **struct, which is support as designed. 109 if dstType.Elem().Kind() == reflect.Pointer { 110 dstType = dstType.Elem() 111 } 112 } else if dstReflectValueForRefer.IsValid() && dstReflectValueForRefer.CanAddr() { 113 dstType = dstReflectValueForRefer.Addr().Type() 114 } else { 115 dstType = reflect.PointerTo(dstType) 116 } 117 // secondly, it searches the input parameter type map 118 // and finds the result converter function by the output parameter type. 119 f, ok = registeredOutTypeMap[dstType] 120 if !ok { 121 return reflect.Value{}, nil, false 122 } 123 return 124 } 125 126 func callCustomConverterWithRefer( 127 srcReflectValue, referReflectValue reflect.Value, 128 ) (dstReflectValue reflect.Value, converted bool, err error) { 129 registeredConverterFunc, srcType, ok := getRegisteredConverterFuncAndSrcType(srcReflectValue, referReflectValue) 130 if !ok { 131 return reflect.Value{}, false, nil 132 } 133 dstReflectValue = reflect.New(referReflectValue.Type()).Elem() 134 converted, err = doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType) 135 return 136 } 137 138 // callCustomConverter call the custom converter. It will try some possible type. 139 func callCustomConverter(srcReflectValue, dstReflectValue reflect.Value) (converted bool, err error) { 140 registeredConverterFunc, srcType, ok := getRegisteredConverterFuncAndSrcType(srcReflectValue, dstReflectValue) 141 if !ok { 142 return false, nil 143 } 144 return doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType) 145 } 146 147 func doCallCustomConverter( 148 srcReflectValue reflect.Value, 149 dstReflectValue reflect.Value, 150 registeredConverterFunc converterFunc, 151 srcType reflect.Type, 152 ) (converted bool, err error) { 153 // Converter function calling. 154 for srcReflectValue.Type() != srcType { 155 srcReflectValue = srcReflectValue.Elem() 156 } 157 result := registeredConverterFunc.Call([]reflect.Value{srcReflectValue}) 158 if !result[1].IsNil() { 159 return false, result[1].Interface().(error) 160 } 161 // The `result[0]` is a pointer. 162 if result[0].IsNil() { 163 return false, nil 164 } 165 var resultValue = result[0] 166 for { 167 if resultValue.Type() == dstReflectValue.Type() && dstReflectValue.CanSet() { 168 dstReflectValue.Set(resultValue) 169 converted = true 170 } else if dstReflectValue.Kind() == reflect.Pointer { 171 if resultValue.Type() == dstReflectValue.Elem().Type() && dstReflectValue.Elem().CanSet() { 172 dstReflectValue.Elem().Set(resultValue) 173 converted = true 174 } 175 } 176 if converted { 177 break 178 } 179 if resultValue.Kind() == reflect.Pointer { 180 resultValue = resultValue.Elem() 181 } else { 182 break 183 } 184 } 185 186 return converted, nil 187 }