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  }