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