github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/mapstruct/hooks.go (about)

     1  package mapstruct
     2  
     3  import (
     4  	"encoding"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"reflect"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  )
    13  
    14  // typedDecodeHook takes a raw HookFunc (an interface{}) and turns
    15  // it into the proper HookFunc type, such as HookFuncType.
    16  func typedDecodeHook(h HookFunc) HookFunc {
    17  	// Create variables here so we can reference them with the reflect pkg
    18  	var f1 HookFuncType
    19  	var f2 HookFuncKind
    20  	var f3 HookFuncValue
    21  
    22  	// Fill in the variables into this interface and the rest is done
    23  	// automatically using the reflect package.
    24  	potential := []interface{}{f1, f2, f3}
    25  
    26  	v := reflect.ValueOf(h)
    27  	vt := v.Type()
    28  	for _, raw := range potential {
    29  		pt := reflect.ValueOf(raw).Type()
    30  		if vt.ConvertibleTo(pt) {
    31  			return v.Convert(pt).Interface()
    32  		}
    33  	}
    34  
    35  	return nil
    36  }
    37  
    38  // DecodeHookExec executes the given decode hook. This should be used
    39  // since it'll naturally degrade to the older backwards compatible HookFunc
    40  // that took reflect.Kind instead of reflect.Type.
    41  func DecodeHookExec(raw HookFunc, from, to reflect.Value) (interface{}, error) {
    42  	switch f := typedDecodeHook(raw).(type) {
    43  	case HookFuncType:
    44  		return f(from.Type(), to.Type(), from.Interface())
    45  	case HookFuncKind:
    46  		return f(from.Kind(), to.Kind(), from.Interface())
    47  	case HookFuncValue:
    48  		return f(from, to)
    49  	default:
    50  		return nil, errors.New("invalid decode hook signature")
    51  	}
    52  }
    53  
    54  // ComposeDecodeHookFunc creates a single HookFunc that
    55  // automatically composes multiple DecodeHookFuncs.
    56  //
    57  // The composed funcs are called in order, with the result of the
    58  // previous transformation.
    59  func ComposeDecodeHookFunc(fs ...HookFunc) HookFunc {
    60  	return func(f reflect.Value, t reflect.Value) (interface{}, error) {
    61  		var err error
    62  		var data interface{}
    63  		newFrom := f
    64  		for _, f1 := range fs {
    65  			data, err = DecodeHookExec(f1, newFrom, t)
    66  			if err != nil {
    67  				return nil, err
    68  			}
    69  			newFrom = reflect.ValueOf(data)
    70  		}
    71  
    72  		return data, nil
    73  	}
    74  }
    75  
    76  // StringToSliceHookFunc returns a HookFunc that converts
    77  // string to []string by splitting on the given sep.
    78  func StringToSliceHookFunc(sep string) HookFunc {
    79  	return func(
    80  		f reflect.Kind,
    81  		t reflect.Kind,
    82  		data interface{},
    83  	) (interface{}, error) {
    84  		if f != reflect.String || t != reflect.Slice {
    85  			return data, nil
    86  		}
    87  
    88  		raw := data.(string)
    89  		if raw == "" {
    90  			return []string{}, nil
    91  		}
    92  
    93  		return strings.Split(raw, sep), nil
    94  	}
    95  }
    96  
    97  // StringToTimeDurationHookFunc returns a HookFunc that converts
    98  // strings to time.Duration.
    99  func StringToTimeDurationHookFunc() HookFunc {
   100  	return func(
   101  		f reflect.Type,
   102  		t reflect.Type,
   103  		data interface{},
   104  	) (interface{}, error) {
   105  		if f.Kind() != reflect.String {
   106  			return data, nil
   107  		}
   108  		if t != reflect.TypeOf(time.Duration(5)) {
   109  			return data, nil
   110  		}
   111  
   112  		// Convert it by parsing
   113  		return time.ParseDuration(data.(string))
   114  	}
   115  }
   116  
   117  // StringToIPHookFunc returns a HookFunc that converts
   118  // strings to net.IP
   119  func StringToIPHookFunc() HookFunc {
   120  	return func(
   121  		f reflect.Type,
   122  		t reflect.Type,
   123  		data interface{},
   124  	) (interface{}, error) {
   125  		if f.Kind() != reflect.String {
   126  			return data, nil
   127  		}
   128  		if t != reflect.TypeOf(net.IP{}) {
   129  			return data, nil
   130  		}
   131  
   132  		// Convert it by parsing
   133  		ip := net.ParseIP(data.(string))
   134  		if ip == nil {
   135  			return net.IP{}, fmt.Errorf("failed parsing ip %v", data)
   136  		}
   137  
   138  		return ip, nil
   139  	}
   140  }
   141  
   142  // StringToIPNetHookFunc returns a HookFunc that converts
   143  // strings to net.IPNet
   144  func StringToIPNetHookFunc() HookFunc {
   145  	return func(
   146  		f reflect.Type,
   147  		t reflect.Type,
   148  		data interface{},
   149  	) (interface{}, error) {
   150  		if f.Kind() != reflect.String {
   151  			return data, nil
   152  		}
   153  		if t != reflect.TypeOf(net.IPNet{}) {
   154  			return data, nil
   155  		}
   156  
   157  		// Convert it by parsing
   158  		_, n, err := net.ParseCIDR(data.(string))
   159  		return n, err
   160  	}
   161  }
   162  
   163  // StringToTimeHookFunc returns a HookFunc that converts
   164  // strings to time.Time.
   165  func StringToTimeHookFunc(layout string) HookFunc {
   166  	return func(
   167  		f reflect.Type,
   168  		t reflect.Type,
   169  		data interface{},
   170  	) (interface{}, error) {
   171  		if f.Kind() != reflect.String {
   172  			return data, nil
   173  		}
   174  		if t != reflect.TypeOf(time.Time{}) {
   175  			return data, nil
   176  		}
   177  
   178  		// Convert it by parsing
   179  		return time.Parse(layout, data.(string))
   180  	}
   181  }
   182  
   183  // WeaklyTypedHook is a HookFunc which adds support for weak typing to
   184  // the decoder.
   185  //
   186  // Note that this is significantly different from the WeakType option
   187  // of the Config.
   188  func WeaklyTypedHook(
   189  	f reflect.Kind,
   190  	t reflect.Kind,
   191  	data interface{},
   192  ) (interface{}, error) {
   193  	dataVal := reflect.ValueOf(data)
   194  	switch t {
   195  	case reflect.String:
   196  		switch f {
   197  		case reflect.Bool:
   198  			if dataVal.Bool() {
   199  				return "1", nil
   200  			}
   201  			return "0", nil
   202  		case reflect.Float32:
   203  			return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
   204  		case reflect.Int:
   205  			return strconv.FormatInt(dataVal.Int(), 10), nil
   206  		case reflect.Slice:
   207  			dataType := dataVal.Type()
   208  			elemKind := dataType.Elem().Kind()
   209  			if elemKind == reflect.Uint8 {
   210  				return string(dataVal.Interface().([]uint8)), nil
   211  			}
   212  		case reflect.Uint:
   213  			return strconv.FormatUint(dataVal.Uint(), 10), nil
   214  		}
   215  	}
   216  
   217  	return data, nil
   218  }
   219  
   220  func RecursiveStructToMapHookFunc() HookFunc {
   221  	return func(f reflect.Value, t reflect.Value) (interface{}, error) {
   222  		if f.Kind() != reflect.Struct {
   223  			return f.Interface(), nil
   224  		}
   225  
   226  		var i interface{} = struct{}{}
   227  		if t.Type() != reflect.TypeOf(&i).Elem() {
   228  			return f.Interface(), nil
   229  		}
   230  
   231  		m := make(map[string]interface{})
   232  		t.Set(reflect.ValueOf(m))
   233  
   234  		return f.Interface(), nil
   235  	}
   236  }
   237  
   238  // TextUnmarshallerHookFunc returns a HookFunc that applies
   239  // strings to the UnmarshalText function, when the target type
   240  // implements the encoding.TextUnmarshaler interface
   241  func TextUnmarshallerHookFunc() HookFuncType {
   242  	return func(
   243  		f reflect.Type,
   244  		t reflect.Type,
   245  		data interface{},
   246  	) (interface{}, error) {
   247  		if f.Kind() != reflect.String {
   248  			return data, nil
   249  		}
   250  		result := reflect.New(t).Interface()
   251  		unmarshaller, ok := result.(encoding.TextUnmarshaler)
   252  		if !ok {
   253  			return data, nil
   254  		}
   255  		if err := unmarshaller.UnmarshalText([]byte(data.(string))); err != nil {
   256  			return nil, err
   257  		}
   258  		return result, nil
   259  	}
   260  }