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  }