Path: blob/main/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go
2880 views
package mapstructure12import (3"encoding"4"errors"5"fmt"6"net"7"net/netip"8"net/url"9"reflect"10"strconv"11"strings"12"time"13)1415// typedDecodeHook takes a raw DecodeHookFunc (an any) and turns16// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.17func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {18// Create variables here so we can reference them with the reflect pkg19var f1 DecodeHookFuncType20var f2 DecodeHookFuncKind21var f3 DecodeHookFuncValue2223// Fill in the variables into this interface and the rest is done24// automatically using the reflect package.25potential := []any{f1, f2, f3}2627v := reflect.ValueOf(h)28vt := v.Type()29for _, raw := range potential {30pt := reflect.ValueOf(raw).Type()31if vt.ConvertibleTo(pt) {32return v.Convert(pt).Interface()33}34}3536return nil37}3839// cachedDecodeHook takes a raw DecodeHookFunc (an any) and turns40// it into a closure to be used directly41// if the type fails to convert we return a closure always erroring to keep the previous behaviour42func cachedDecodeHook(raw DecodeHookFunc) func(from reflect.Value, to reflect.Value) (any, error) {43switch f := typedDecodeHook(raw).(type) {44case DecodeHookFuncType:45return func(from reflect.Value, to reflect.Value) (any, error) {46return f(from.Type(), to.Type(), from.Interface())47}48case DecodeHookFuncKind:49return func(from reflect.Value, to reflect.Value) (any, error) {50return f(from.Kind(), to.Kind(), from.Interface())51}52case DecodeHookFuncValue:53return func(from reflect.Value, to reflect.Value) (any, error) {54return f(from, to)55}56default:57return func(from reflect.Value, to reflect.Value) (any, error) {58return nil, errors.New("invalid decode hook signature")59}60}61}6263// DecodeHookExec executes the given decode hook. This should be used64// since it'll naturally degrade to the older backwards compatible DecodeHookFunc65// that took reflect.Kind instead of reflect.Type.66func DecodeHookExec(67raw DecodeHookFunc,68from reflect.Value, to reflect.Value,69) (any, error) {70switch f := typedDecodeHook(raw).(type) {71case DecodeHookFuncType:72return f(from.Type(), to.Type(), from.Interface())73case DecodeHookFuncKind:74return f(from.Kind(), to.Kind(), from.Interface())75case DecodeHookFuncValue:76return f(from, to)77default:78return nil, errors.New("invalid decode hook signature")79}80}8182// ComposeDecodeHookFunc creates a single DecodeHookFunc that83// automatically composes multiple DecodeHookFuncs.84//85// The composed funcs are called in order, with the result of the86// previous transformation.87func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {88cached := make([]func(from reflect.Value, to reflect.Value) (any, error), 0, len(fs))89for _, f := range fs {90cached = append(cached, cachedDecodeHook(f))91}92return func(f reflect.Value, t reflect.Value) (any, error) {93var err error94data := f.Interface()9596newFrom := f97for _, c := range cached {98data, err = c(newFrom, t)99if err != nil {100return nil, err101}102if v, ok := data.(reflect.Value); ok {103newFrom = v104} else {105newFrom = reflect.ValueOf(data)106}107}108109return data, nil110}111}112113// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned.114// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages.115func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc {116cached := make([]func(from reflect.Value, to reflect.Value) (any, error), 0, len(ff))117for _, f := range ff {118cached = append(cached, cachedDecodeHook(f))119}120return func(a, b reflect.Value) (any, error) {121var allErrs string122var out any123var err error124125for _, c := range cached {126out, err = c(a, b)127if err != nil {128allErrs += err.Error() + "\n"129continue130}131132return out, nil133}134135return nil, errors.New(allErrs)136}137}138139// StringToSliceHookFunc returns a DecodeHookFunc that converts140// string to []string by splitting on the given sep.141func StringToSliceHookFunc(sep string) DecodeHookFunc {142return func(143f reflect.Type,144t reflect.Type,145data any,146) (any, error) {147if f.Kind() != reflect.String {148return data, nil149}150if t != reflect.SliceOf(f) {151return data, nil152}153154raw := data.(string)155if raw == "" {156return []string{}, nil157}158159return strings.Split(raw, sep), nil160}161}162163// StringToWeakSliceHookFunc brings back the old (pre-v2) behavior of [StringToSliceHookFunc].164//165// As of mapstructure v2.0.0 [StringToSliceHookFunc] checks if the return type is a string slice.166// This function removes that check.167func StringToWeakSliceHookFunc(sep string) DecodeHookFunc {168return func(169f reflect.Type,170t reflect.Type,171data any,172) (any, error) {173if f.Kind() != reflect.String || t.Kind() != reflect.Slice {174return data, nil175}176177raw := data.(string)178if raw == "" {179return []string{}, nil180}181182return strings.Split(raw, sep), nil183}184}185186// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts187// strings to time.Duration.188func StringToTimeDurationHookFunc() DecodeHookFunc {189return func(190f reflect.Type,191t reflect.Type,192data any,193) (any, error) {194if f.Kind() != reflect.String {195return data, nil196}197if t != reflect.TypeOf(time.Duration(5)) {198return data, nil199}200201// Convert it by parsing202d, err := time.ParseDuration(data.(string))203204return d, wrapTimeParseDurationError(err)205}206}207208// StringToTimeLocationHookFunc returns a DecodeHookFunc that converts209// strings to *time.Location.210func StringToTimeLocationHookFunc() DecodeHookFunc {211return func(212f reflect.Type,213t reflect.Type,214data any,215) (any, error) {216if f.Kind() != reflect.String {217return data, nil218}219if t != reflect.TypeOf(time.Local) {220return data, nil221}222d, err := time.LoadLocation(data.(string))223224return d, wrapTimeParseLocationError(err)225}226}227228// StringToURLHookFunc returns a DecodeHookFunc that converts229// strings to *url.URL.230func StringToURLHookFunc() DecodeHookFunc {231return func(232f reflect.Type,233t reflect.Type,234data any,235) (any, error) {236if f.Kind() != reflect.String {237return data, nil238}239if t != reflect.TypeOf(&url.URL{}) {240return data, nil241}242243// Convert it by parsing244u, err := url.Parse(data.(string))245246return u, wrapUrlError(err)247}248}249250// StringToIPHookFunc returns a DecodeHookFunc that converts251// strings to net.IP252func StringToIPHookFunc() DecodeHookFunc {253return func(254f reflect.Type,255t reflect.Type,256data any,257) (any, error) {258if f.Kind() != reflect.String {259return data, nil260}261if t != reflect.TypeOf(net.IP{}) {262return data, nil263}264265// Convert it by parsing266ip := net.ParseIP(data.(string))267if ip == nil {268return net.IP{}, fmt.Errorf("failed parsing ip")269}270271return ip, nil272}273}274275// StringToIPNetHookFunc returns a DecodeHookFunc that converts276// strings to net.IPNet277func StringToIPNetHookFunc() DecodeHookFunc {278return func(279f reflect.Type,280t reflect.Type,281data any,282) (any, error) {283if f.Kind() != reflect.String {284return data, nil285}286if t != reflect.TypeOf(net.IPNet{}) {287return data, nil288}289290// Convert it by parsing291_, net, err := net.ParseCIDR(data.(string))292return net, wrapNetParseError(err)293}294}295296// StringToTimeHookFunc returns a DecodeHookFunc that converts297// strings to time.Time.298func StringToTimeHookFunc(layout string) DecodeHookFunc {299return func(300f reflect.Type,301t reflect.Type,302data any,303) (any, error) {304if f.Kind() != reflect.String {305return data, nil306}307if t != reflect.TypeOf(time.Time{}) {308return data, nil309}310311// Convert it by parsing312ti, err := time.Parse(layout, data.(string))313314return ti, wrapTimeParseError(err)315}316}317318// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to319// the decoder.320//321// Note that this is significantly different from the WeaklyTypedInput option322// of the DecoderConfig.323func WeaklyTypedHook(324f reflect.Kind,325t reflect.Kind,326data any,327) (any, error) {328dataVal := reflect.ValueOf(data)329switch t {330case reflect.String:331switch f {332case reflect.Bool:333if dataVal.Bool() {334return "1", nil335}336return "0", nil337case reflect.Float32:338return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil339case reflect.Int:340return strconv.FormatInt(dataVal.Int(), 10), nil341case reflect.Slice:342dataType := dataVal.Type()343elemKind := dataType.Elem().Kind()344if elemKind == reflect.Uint8 {345return string(dataVal.Interface().([]uint8)), nil346}347case reflect.Uint:348return strconv.FormatUint(dataVal.Uint(), 10), nil349}350}351352return data, nil353}354355func RecursiveStructToMapHookFunc() DecodeHookFunc {356return func(f reflect.Value, t reflect.Value) (any, error) {357if f.Kind() != reflect.Struct {358return f.Interface(), nil359}360361var i any = struct{}{}362if t.Type() != reflect.TypeOf(&i).Elem() {363return f.Interface(), nil364}365366m := make(map[string]any)367t.Set(reflect.ValueOf(m))368369return f.Interface(), nil370}371}372373// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies374// strings to the UnmarshalText function, when the target type375// implements the encoding.TextUnmarshaler interface376func TextUnmarshallerHookFunc() DecodeHookFuncType {377return func(378f reflect.Type,379t reflect.Type,380data any,381) (any, error) {382if f.Kind() != reflect.String {383return data, nil384}385result := reflect.New(t).Interface()386unmarshaller, ok := result.(encoding.TextUnmarshaler)387if !ok {388return data, nil389}390str, ok := data.(string)391if !ok {392str = reflect.Indirect(reflect.ValueOf(&data)).Elem().String()393}394if err := unmarshaller.UnmarshalText([]byte(str)); err != nil {395return nil, err396}397return result, nil398}399}400401// StringToNetIPAddrHookFunc returns a DecodeHookFunc that converts402// strings to netip.Addr.403func StringToNetIPAddrHookFunc() DecodeHookFunc {404return func(405f reflect.Type,406t reflect.Type,407data any,408) (any, error) {409if f.Kind() != reflect.String {410return data, nil411}412if t != reflect.TypeOf(netip.Addr{}) {413return data, nil414}415416// Convert it by parsing417addr, err := netip.ParseAddr(data.(string))418419return addr, wrapNetIPParseAddrError(err)420}421}422423// StringToNetIPAddrPortHookFunc returns a DecodeHookFunc that converts424// strings to netip.AddrPort.425func StringToNetIPAddrPortHookFunc() DecodeHookFunc {426return func(427f reflect.Type,428t reflect.Type,429data any,430) (any, error) {431if f.Kind() != reflect.String {432return data, nil433}434if t != reflect.TypeOf(netip.AddrPort{}) {435return data, nil436}437438// Convert it by parsing439addrPort, err := netip.ParseAddrPort(data.(string))440441return addrPort, wrapNetIPParseAddrPortError(err)442}443}444445// StringToNetIPPrefixHookFunc returns a DecodeHookFunc that converts446// strings to netip.Prefix.447func StringToNetIPPrefixHookFunc() DecodeHookFunc {448return func(449f reflect.Type,450t reflect.Type,451data any,452) (any, error) {453if f.Kind() != reflect.String {454return data, nil455}456if t != reflect.TypeOf(netip.Prefix{}) {457return data, nil458}459460// Convert it by parsing461prefix, err := netip.ParsePrefix(data.(string))462463return prefix, wrapNetIPParsePrefixError(err)464}465}466467// StringToBasicTypeHookFunc returns a DecodeHookFunc that converts468// strings to basic types.469// int8, uint8, int16, uint16, int32, uint32, int64, uint64, int, uint, float32, float64, bool, byte, rune, complex64, complex128470func StringToBasicTypeHookFunc() DecodeHookFunc {471return ComposeDecodeHookFunc(472StringToInt8HookFunc(),473StringToUint8HookFunc(),474StringToInt16HookFunc(),475StringToUint16HookFunc(),476StringToInt32HookFunc(),477StringToUint32HookFunc(),478StringToInt64HookFunc(),479StringToUint64HookFunc(),480StringToIntHookFunc(),481StringToUintHookFunc(),482StringToFloat32HookFunc(),483StringToFloat64HookFunc(),484StringToBoolHookFunc(),485// byte and rune are aliases for uint8 and int32 respectively486// StringToByteHookFunc(),487// StringToRuneHookFunc(),488StringToComplex64HookFunc(),489StringToComplex128HookFunc(),490)491}492493// StringToInt8HookFunc returns a DecodeHookFunc that converts494// strings to int8.495func StringToInt8HookFunc() DecodeHookFunc {496return func(f reflect.Type, t reflect.Type, data any) (any, error) {497if f.Kind() != reflect.String || t.Kind() != reflect.Int8 {498return data, nil499}500501// Convert it by parsing502i64, err := strconv.ParseInt(data.(string), 0, 8)503return int8(i64), wrapStrconvNumError(err)504}505}506507// StringToUint8HookFunc returns a DecodeHookFunc that converts508// strings to uint8.509func StringToUint8HookFunc() DecodeHookFunc {510return func(f reflect.Type, t reflect.Type, data any) (any, error) {511if f.Kind() != reflect.String || t.Kind() != reflect.Uint8 {512return data, nil513}514515// Convert it by parsing516u64, err := strconv.ParseUint(data.(string), 0, 8)517return uint8(u64), wrapStrconvNumError(err)518}519}520521// StringToInt16HookFunc returns a DecodeHookFunc that converts522// strings to int16.523func StringToInt16HookFunc() DecodeHookFunc {524return func(f reflect.Type, t reflect.Type, data any) (any, error) {525if f.Kind() != reflect.String || t.Kind() != reflect.Int16 {526return data, nil527}528529// Convert it by parsing530i64, err := strconv.ParseInt(data.(string), 0, 16)531return int16(i64), wrapStrconvNumError(err)532}533}534535// StringToUint16HookFunc returns a DecodeHookFunc that converts536// strings to uint16.537func StringToUint16HookFunc() DecodeHookFunc {538return func(f reflect.Type, t reflect.Type, data any) (any, error) {539if f.Kind() != reflect.String || t.Kind() != reflect.Uint16 {540return data, nil541}542543// Convert it by parsing544u64, err := strconv.ParseUint(data.(string), 0, 16)545return uint16(u64), wrapStrconvNumError(err)546}547}548549// StringToInt32HookFunc returns a DecodeHookFunc that converts550// strings to int32.551func StringToInt32HookFunc() DecodeHookFunc {552return func(f reflect.Type, t reflect.Type, data any) (any, error) {553if f.Kind() != reflect.String || t.Kind() != reflect.Int32 {554return data, nil555}556557// Convert it by parsing558i64, err := strconv.ParseInt(data.(string), 0, 32)559return int32(i64), wrapStrconvNumError(err)560}561}562563// StringToUint32HookFunc returns a DecodeHookFunc that converts564// strings to uint32.565func StringToUint32HookFunc() DecodeHookFunc {566return func(f reflect.Type, t reflect.Type, data any) (any, error) {567if f.Kind() != reflect.String || t.Kind() != reflect.Uint32 {568return data, nil569}570571// Convert it by parsing572u64, err := strconv.ParseUint(data.(string), 0, 32)573return uint32(u64), wrapStrconvNumError(err)574}575}576577// StringToInt64HookFunc returns a DecodeHookFunc that converts578// strings to int64.579func StringToInt64HookFunc() DecodeHookFunc {580return func(f reflect.Type, t reflect.Type, data any) (any, error) {581if f.Kind() != reflect.String || t.Kind() != reflect.Int64 {582return data, nil583}584585// Convert it by parsing586i64, err := strconv.ParseInt(data.(string), 0, 64)587return int64(i64), wrapStrconvNumError(err)588}589}590591// StringToUint64HookFunc returns a DecodeHookFunc that converts592// strings to uint64.593func StringToUint64HookFunc() DecodeHookFunc {594return func(f reflect.Type, t reflect.Type, data any) (any, error) {595if f.Kind() != reflect.String || t.Kind() != reflect.Uint64 {596return data, nil597}598599// Convert it by parsing600u64, err := strconv.ParseUint(data.(string), 0, 64)601return uint64(u64), wrapStrconvNumError(err)602}603}604605// StringToIntHookFunc returns a DecodeHookFunc that converts606// strings to int.607func StringToIntHookFunc() DecodeHookFunc {608return func(f reflect.Type, t reflect.Type, data any) (any, error) {609if f.Kind() != reflect.String || t.Kind() != reflect.Int {610return data, nil611}612613// Convert it by parsing614i64, err := strconv.ParseInt(data.(string), 0, 0)615return int(i64), wrapStrconvNumError(err)616}617}618619// StringToUintHookFunc returns a DecodeHookFunc that converts620// strings to uint.621func StringToUintHookFunc() DecodeHookFunc {622return func(f reflect.Type, t reflect.Type, data any) (any, error) {623if f.Kind() != reflect.String || t.Kind() != reflect.Uint {624return data, nil625}626627// Convert it by parsing628u64, err := strconv.ParseUint(data.(string), 0, 0)629return uint(u64), wrapStrconvNumError(err)630}631}632633// StringToFloat32HookFunc returns a DecodeHookFunc that converts634// strings to float32.635func StringToFloat32HookFunc() DecodeHookFunc {636return func(f reflect.Type, t reflect.Type, data any) (any, error) {637if f.Kind() != reflect.String || t.Kind() != reflect.Float32 {638return data, nil639}640641// Convert it by parsing642f64, err := strconv.ParseFloat(data.(string), 32)643return float32(f64), wrapStrconvNumError(err)644}645}646647// StringToFloat64HookFunc returns a DecodeHookFunc that converts648// strings to float64.649func StringToFloat64HookFunc() DecodeHookFunc {650return func(f reflect.Type, t reflect.Type, data any) (any, error) {651if f.Kind() != reflect.String || t.Kind() != reflect.Float64 {652return data, nil653}654655// Convert it by parsing656f64, err := strconv.ParseFloat(data.(string), 64)657return f64, wrapStrconvNumError(err)658}659}660661// StringToBoolHookFunc returns a DecodeHookFunc that converts662// strings to bool.663func StringToBoolHookFunc() DecodeHookFunc {664return func(f reflect.Type, t reflect.Type, data any) (any, error) {665if f.Kind() != reflect.String || t.Kind() != reflect.Bool {666return data, nil667}668669// Convert it by parsing670b, err := strconv.ParseBool(data.(string))671return b, wrapStrconvNumError(err)672}673}674675// StringToByteHookFunc returns a DecodeHookFunc that converts676// strings to byte.677func StringToByteHookFunc() DecodeHookFunc {678return StringToUint8HookFunc()679}680681// StringToRuneHookFunc returns a DecodeHookFunc that converts682// strings to rune.683func StringToRuneHookFunc() DecodeHookFunc {684return StringToInt32HookFunc()685}686687// StringToComplex64HookFunc returns a DecodeHookFunc that converts688// strings to complex64.689func StringToComplex64HookFunc() DecodeHookFunc {690return func(f reflect.Type, t reflect.Type, data any) (any, error) {691if f.Kind() != reflect.String || t.Kind() != reflect.Complex64 {692return data, nil693}694695// Convert it by parsing696c128, err := strconv.ParseComplex(data.(string), 64)697return complex64(c128), wrapStrconvNumError(err)698}699}700701// StringToComplex128HookFunc returns a DecodeHookFunc that converts702// strings to complex128.703func StringToComplex128HookFunc() DecodeHookFunc {704return func(f reflect.Type, t reflect.Type, data any) (any, error) {705if f.Kind() != reflect.String || t.Kind() != reflect.Complex128 {706return data, nil707}708709// Convert it by parsing710c128, err := strconv.ParseComplex(data.(string), 128)711return c128, wrapStrconvNumError(err)712}713}714715716