github.com/yandex/pandora@v0.5.32/core/config/hooks.go (about) 1 package config 2 3 import ( 4 "encoding" 5 stderrors "errors" 6 "fmt" 7 "net" 8 "net/url" 9 "reflect" 10 11 "github.com/asaskevich/govalidator" 12 "github.com/c2h5oh/datasize" 13 "github.com/facebookgo/stack" 14 "github.com/pkg/errors" 15 "github.com/yandex/pandora/lib/confutil" 16 "github.com/yandex/pandora/lib/tag" 17 "go.uber.org/zap" 18 ) 19 20 var InvalidURLError = errors.New("string is not valid URL") 21 22 var ( 23 urlPtrType = reflect.TypeOf(&url.URL{}) 24 urlType = reflect.TypeOf(url.URL{}) 25 ) 26 27 // StringToURLHook converts string to url.URL or *url.URL 28 func StringToURLHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { 29 if f.Kind() != reflect.String { 30 return data, nil 31 } 32 if t != urlPtrType && t != urlType { 33 return data, nil 34 } 35 str := data.(string) 36 37 if !govalidator.IsURL(str) { // checks more than url.Parse 38 return nil, errors.WithStack(InvalidURLError) 39 } 40 urlPtr, err := url.Parse(str) 41 if err != nil { 42 return nil, errors.WithStack(err) 43 } 44 45 if t == urlType { 46 return *urlPtr, nil 47 } 48 return urlPtr, nil 49 } 50 51 var ErrInvalidIP = stderrors.New("string is not valid IP") 52 53 // StringToIPHook converts string to net.IP 54 func StringToIPHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { 55 if f.Kind() != reflect.String { 56 return data, nil 57 } 58 if t != reflect.TypeOf(net.IP{}) { 59 return data, nil 60 } 61 str := data.(string) 62 ip := net.ParseIP(str) 63 if ip == nil { 64 return nil, errors.WithStack(ErrInvalidIP) 65 } 66 return ip, nil 67 } 68 69 // StringToDataSizeHook converts string to datasize.Single 70 func StringToDataSizeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { 71 if f.Kind() != reflect.String { 72 return data, nil 73 } 74 if t != reflect.TypeOf(datasize.B) { 75 return data, nil 76 } 77 var size datasize.ByteSize 78 err := size.UnmarshalText([]byte(data.(string))) 79 return size, err 80 } 81 82 var textUnmarshallerType = func() reflect.Type { 83 var ptr *encoding.TextUnmarshaler 84 return reflect.TypeOf(ptr).Elem() 85 } 86 87 func TextUnmarshallerHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { 88 if f.Kind() != reflect.String { 89 return data, nil 90 } 91 if t.Implements(textUnmarshallerType()) { 92 val := reflect.Zero(t) 93 if t.Kind() == reflect.Ptr { 94 val = reflect.New(t.Elem()) 95 } 96 err := unmarhsallText(val, data) 97 return val.Interface(), err 98 } 99 if reflect.PtrTo(t).Implements(textUnmarshallerType()) { 100 val := reflect.New(t) 101 err := unmarhsallText(val, data) 102 return val.Elem().Interface(), err 103 } 104 return data, nil 105 } 106 107 func unmarhsallText(v reflect.Value, data interface{}) error { 108 unmarshaller := v.Interface().(encoding.TextUnmarshaler) 109 // unmarshaller.UnmarshalText([]byte(data.(string))) 110 err := unmarshaller.UnmarshalText([]byte(data.(string))) 111 return err 112 } 113 114 // DebugHook used to debug config decode. 115 func DebugHook(f reflect.Type, t reflect.Type, data interface{}) (p interface{}, err error) { 116 p, err = data, nil 117 if !tag.Debug { 118 return 119 } 120 callers := stack.Callers(2) 121 122 var decodeCallers int 123 for _, caller := range callers { 124 if caller.Name == "(*Decoder).decode" { 125 decodeCallers++ 126 } 127 } 128 zap.L().Debug("Config decode", 129 zap.Int("depth", decodeCallers), 130 zap.Stringer("type", t), 131 zap.Stringer("from", f), 132 zap.String("data", fmt.Sprint(data)), 133 ) 134 return 135 } 136 137 // VariableInjectHook injects values into ${VAR_NAME} placeholders 138 func VariableInjectHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { 139 if f.Kind() != reflect.String { 140 return data, nil 141 } 142 143 str := data.(string) 144 res, err := confutil.ResolveCustomTags(str, t) 145 if err == confutil.ErrNoTagsFound { 146 return data, nil 147 } 148 149 if err != nil { 150 return data, err 151 } 152 153 return res, nil 154 }