scalar.go raw

   1  // Package scalar parses strings into values of scalar type.
   2  
   3  package scalar
   4  
   5  import (
   6  	"encoding"
   7  	"errors"
   8  	"fmt"
   9  	"net"
  10  	"net/mail"
  11  	"net/url"
  12  	"reflect"
  13  	"strconv"
  14  	"time"
  15  )
  16  
  17  // The reflected form of some special types
  18  var (
  19  	textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem()
  20  	durationType        = reflect.TypeOf(time.Duration(0))
  21  	mailAddressType     = reflect.TypeOf(mail.Address{})
  22  	macType             = reflect.TypeOf(net.HardwareAddr{})
  23  	urlType             = reflect.TypeOf(url.URL{})
  24  )
  25  
  26  var (
  27  	errNotSettable    = errors.New("value is not settable")
  28  	errPtrNotSettable = errors.New("value is a nil pointer and is not settable")
  29  )
  30  
  31  // Parse assigns a value to v by parsing s.
  32  func Parse(dest interface{}, s string) error {
  33  	return ParseValue(reflect.ValueOf(dest), s)
  34  }
  35  
  36  // ParseValue assigns a value to v by parsing s.
  37  func ParseValue(v reflect.Value, s string) error {
  38  	// If we have a nil pointer then allocate a new object
  39  	if v.Kind() == reflect.Ptr && v.IsNil() {
  40  		if !v.CanSet() {
  41  			return errPtrNotSettable
  42  		}
  43  
  44  		v.Set(reflect.New(v.Type().Elem()))
  45  	}
  46  
  47  	// If it implements encoding.TextUnmarshaler then use that
  48  	if scalar, ok := v.Interface().(encoding.TextUnmarshaler); ok {
  49  		return scalar.UnmarshalText([]byte(s))
  50  	}
  51  	// If it's a value instead of a pointer, check that we can unmarshal it
  52  	// via TextUnmarshaler as well
  53  	if v.CanAddr() {
  54  		if scalar, ok := v.Addr().Interface().(encoding.TextUnmarshaler); ok {
  55  			return scalar.UnmarshalText([]byte(s))
  56  		}
  57  	}
  58  
  59  	// If we have a pointer then dereference it
  60  	if v.Kind() == reflect.Ptr {
  61  		v = v.Elem()
  62  	}
  63  
  64  	if !v.CanSet() {
  65  		return errNotSettable
  66  	}
  67  
  68  	// Switch on concrete type
  69  	switch scalar := v.Interface(); scalar.(type) {
  70  	case time.Duration:
  71  		duration, err := time.ParseDuration(s)
  72  		if err != nil {
  73  			return err
  74  		}
  75  		v.Set(reflect.ValueOf(duration))
  76  		return nil
  77  	case mail.Address:
  78  		addr, err := mail.ParseAddress(s)
  79  		if err != nil {
  80  			return err
  81  		}
  82  		v.Set(reflect.ValueOf(*addr))
  83  		return nil
  84  	case net.HardwareAddr:
  85  		ip, err := net.ParseMAC(s)
  86  		if err != nil {
  87  			return err
  88  		}
  89  		v.Set(reflect.ValueOf(ip))
  90  		return nil
  91  	case url.URL:
  92  		url, err := url.Parse(s)
  93  		if err != nil {
  94  			return err
  95  		}
  96  		v.Set(reflect.ValueOf(*url))
  97  		return nil
  98  	}
  99  
 100  	// Switch on kind so that we can handle derived types
 101  	switch v.Kind() {
 102  	case reflect.String:
 103  		v.SetString(s)
 104  	case reflect.Bool:
 105  		x, err := strconv.ParseBool(s)
 106  		if err != nil {
 107  			return err
 108  		}
 109  		v.SetBool(x)
 110  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 111  		x, err := strconv.ParseInt(s, 0, v.Type().Bits())
 112  		if err != nil {
 113  			return err
 114  		}
 115  		v.SetInt(x)
 116  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 117  		x, err := strconv.ParseUint(s, 0, v.Type().Bits())
 118  		if err != nil {
 119  			return err
 120  		}
 121  		v.SetUint(x)
 122  	case reflect.Float32, reflect.Float64:
 123  		x, err := strconv.ParseFloat(s, v.Type().Bits())
 124  		if err != nil {
 125  			return err
 126  		}
 127  		v.SetFloat(x)
 128  	default:
 129  		return fmt.Errorf("cannot parse into %v", v.Type())
 130  	}
 131  	return nil
 132  }
 133  
 134  // CanParse returns true if the type can be parsed from a string.
 135  func CanParse(t reflect.Type) bool {
 136  	// If it implements encoding.TextUnmarshaler then use that
 137  	if t.Implements(textUnmarshalerType) || reflect.PtrTo(t).Implements(textUnmarshalerType) {
 138  		return true
 139  	}
 140  
 141  	// If we have a pointer then dereference it
 142  	if t.Kind() == reflect.Ptr {
 143  		t = t.Elem()
 144  	}
 145  
 146  	// Check for other special types
 147  	switch t {
 148  	case durationType, mailAddressType, macType, urlType:
 149  		return true
 150  	}
 151  
 152  	// Fall back to checking the kind
 153  	switch t.Kind() {
 154  	case reflect.Bool:
 155  		return true
 156  	case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
 157  		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
 158  		reflect.Float32, reflect.Float64:
 159  		return true
 160  	}
 161  	return false
 162  }
 163