mirror of
https://gitee.com/johng/gf.git
synced 2024-11-30 03:07:45 +08:00
186 lines
5.8 KiB
Go
186 lines
5.8 KiB
Go
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
// If a copy of the MIT was not distributed with this file,
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
package gconv
|
|
|
|
import (
|
|
"reflect"
|
|
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
)
|
|
|
|
type (
|
|
converterInType = reflect.Type
|
|
converterOutType = reflect.Type
|
|
converterFunc = reflect.Value
|
|
)
|
|
|
|
// customConverters for internal converter storing.
|
|
var customConverters = make(map[converterInType]map[converterOutType]converterFunc)
|
|
|
|
// RegisterConverter to register custom converter.
|
|
// It must be registered before you use this custom converting feature.
|
|
// It is suggested to do it in boot procedure of the process.
|
|
//
|
|
// Note:
|
|
// 1. The parameter `fn` must be defined as pattern `func(T1) (T2, error)`.
|
|
// It will convert type `T1` to type `T2`.
|
|
// 2. The `T1` should not be type of pointer, but the `T2` should be type of pointer.
|
|
func RegisterConverter(fn interface{}) (err error) {
|
|
var (
|
|
fnReflectType = reflect.TypeOf(fn)
|
|
errType = reflect.TypeOf((*error)(nil)).Elem()
|
|
)
|
|
if fnReflectType.Kind() != reflect.Func ||
|
|
fnReflectType.NumIn() != 1 || fnReflectType.NumOut() != 2 ||
|
|
!fnReflectType.Out(1).Implements(errType) {
|
|
err = gerror.NewCodef(
|
|
gcode.CodeInvalidParameter,
|
|
"parameter must be type of converter function and defined as pattern `func(T1) (T2, error)`, but defined as `%s`",
|
|
fnReflectType.String(),
|
|
)
|
|
return
|
|
}
|
|
|
|
// The Key and Value of the converter map should not be pointer.
|
|
var (
|
|
inType = fnReflectType.In(0)
|
|
outType = fnReflectType.Out(0)
|
|
)
|
|
if inType.Kind() == reflect.Pointer {
|
|
err = gerror.NewCodef(
|
|
gcode.CodeInvalidParameter,
|
|
"invalid converter function `%s`: invalid input parameter type `%s`, should not be type of pointer",
|
|
fnReflectType.String(), inType.String(),
|
|
)
|
|
return
|
|
}
|
|
if outType.Kind() != reflect.Pointer {
|
|
err = gerror.NewCodef(
|
|
gcode.CodeInvalidParameter,
|
|
"invalid converter function `%s`: invalid output parameter type `%s` should be type of pointer",
|
|
fnReflectType.String(), outType.String(),
|
|
)
|
|
return
|
|
}
|
|
|
|
registeredOutTypeMap, ok := customConverters[inType]
|
|
if !ok {
|
|
registeredOutTypeMap = make(map[converterOutType]converterFunc)
|
|
customConverters[inType] = registeredOutTypeMap
|
|
}
|
|
if _, ok = registeredOutTypeMap[outType]; ok {
|
|
err = gerror.NewCodef(
|
|
gcode.CodeInvalidOperation,
|
|
"the converter parameter type `%s` to type `%s` has already been registered",
|
|
inType.String(), outType.String(),
|
|
)
|
|
return
|
|
}
|
|
registeredOutTypeMap[outType] = reflect.ValueOf(fn)
|
|
return
|
|
}
|
|
|
|
func getRegisteredConverterFuncAndSrcType(
|
|
srcReflectValue, dstReflectValueForRefer reflect.Value,
|
|
) (f converterFunc, srcType reflect.Type, ok bool) {
|
|
if len(customConverters) == 0 {
|
|
return reflect.Value{}, nil, false
|
|
}
|
|
srcType = srcReflectValue.Type()
|
|
for srcType.Kind() == reflect.Pointer {
|
|
srcType = srcType.Elem()
|
|
}
|
|
var registeredOutTypeMap map[converterOutType]converterFunc
|
|
// firstly, it searches the map by input parameter type.
|
|
registeredOutTypeMap, ok = customConverters[srcType]
|
|
if !ok {
|
|
return reflect.Value{}, nil, false
|
|
}
|
|
var dstType = dstReflectValueForRefer.Type()
|
|
if dstType.Kind() == reflect.Pointer {
|
|
// Might be **struct, which is support as designed.
|
|
if dstType.Elem().Kind() == reflect.Pointer {
|
|
dstType = dstType.Elem()
|
|
}
|
|
} else if dstReflectValueForRefer.IsValid() && dstReflectValueForRefer.CanAddr() {
|
|
dstType = dstReflectValueForRefer.Addr().Type()
|
|
} else {
|
|
dstType = reflect.PointerTo(dstType)
|
|
}
|
|
// secondly, it searches the input parameter type map
|
|
// and finds the result converter function by the output parameter type.
|
|
f, ok = registeredOutTypeMap[dstType]
|
|
if !ok {
|
|
return reflect.Value{}, nil, false
|
|
}
|
|
return
|
|
}
|
|
|
|
func callCustomConverterWithRefer(
|
|
srcReflectValue, referReflectValue reflect.Value,
|
|
) (dstReflectValue reflect.Value, converted bool, err error) {
|
|
registeredConverterFunc, srcType, ok := getRegisteredConverterFuncAndSrcType(srcReflectValue, referReflectValue)
|
|
if !ok {
|
|
return reflect.Value{}, false, nil
|
|
}
|
|
dstReflectValue = reflect.New(referReflectValue.Type()).Elem()
|
|
converted, err = doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType)
|
|
return
|
|
}
|
|
|
|
// callCustomConverter call the custom converter. It will try some possible type.
|
|
func callCustomConverter(srcReflectValue, dstReflectValue reflect.Value) (converted bool, err error) {
|
|
registeredConverterFunc, srcType, ok := getRegisteredConverterFuncAndSrcType(srcReflectValue, dstReflectValue)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
return doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType)
|
|
}
|
|
|
|
func doCallCustomConverter(
|
|
srcReflectValue reflect.Value,
|
|
dstReflectValue reflect.Value,
|
|
registeredConverterFunc converterFunc,
|
|
srcType reflect.Type,
|
|
) (converted bool, err error) {
|
|
// Converter function calling.
|
|
for srcReflectValue.Type() != srcType {
|
|
srcReflectValue = srcReflectValue.Elem()
|
|
}
|
|
result := registeredConverterFunc.Call([]reflect.Value{srcReflectValue})
|
|
if !result[1].IsNil() {
|
|
return false, result[1].Interface().(error)
|
|
}
|
|
// The `result[0]` is a pointer.
|
|
if result[0].IsNil() {
|
|
return false, nil
|
|
}
|
|
var resultValue = result[0]
|
|
for {
|
|
if resultValue.Type() == dstReflectValue.Type() && dstReflectValue.CanSet() {
|
|
dstReflectValue.Set(resultValue)
|
|
converted = true
|
|
} else if dstReflectValue.Kind() == reflect.Pointer {
|
|
if resultValue.Type() == dstReflectValue.Elem().Type() && dstReflectValue.Elem().CanSet() {
|
|
dstReflectValue.Elem().Set(resultValue)
|
|
converted = true
|
|
}
|
|
}
|
|
if converted {
|
|
break
|
|
}
|
|
if resultValue.Kind() == reflect.Pointer {
|
|
resultValue = resultValue.Elem()
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
return converted, nil
|
|
}
|