github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/validation.go (about)

     1  package goa
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"net/mail"
     7  	"net/url"
     8  	"regexp"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/goadesign/goa/uuid"
    13  )
    14  
    15  // Format defines a validation format.
    16  type Format string
    17  
    18  const (
    19  	// FormatDateTime defines RFC3339 date time values.
    20  	FormatDateTime Format = "date-time"
    21  
    22  	// FormatUUID defines RFC4122 uuid values.
    23  	FormatUUID Format = "uuid"
    24  
    25  	// FormatEmail defines RFC5322 email addresses.
    26  	FormatEmail = "email"
    27  
    28  	// FormatHostname defines RFC1035 Internet host names.
    29  	FormatHostname = "hostname"
    30  
    31  	// FormatIPv4 defines RFC2373 IPv4 address values.
    32  	FormatIPv4 = "ipv4"
    33  
    34  	// FormatIPv6 defines RFC2373 IPv6 address values.
    35  	FormatIPv6 = "ipv6"
    36  
    37  	// FormatIP defines RFC2373 IPv4 or IPv6 address values.
    38  	FormatIP = "ip"
    39  
    40  	// FormatURI defines RFC3986 URI values.
    41  	FormatURI = "uri"
    42  
    43  	// FormatMAC defines IEEE 802 MAC-48, EUI-48 or EUI-64 MAC address values.
    44  	FormatMAC = "mac"
    45  
    46  	// FormatCIDR defines RFC4632 and RFC4291 CIDR notation IP address values.
    47  	FormatCIDR = "cidr"
    48  
    49  	// FormatRegexp Regexp defines regular expression syntax accepted by RE2.
    50  	FormatRegexp = "regexp"
    51  )
    52  
    53  var (
    54  	// Regular expression used to validate RFC1035 hostnames*/
    55  	hostnameRegex = regexp.MustCompile(`^[[:alnum:]][[:alnum:]\-]{0,61}[[:alnum:]]|[[:alpha:]]$`)
    56  
    57  	// Simple regular expression for IPv4 values, more rigorous checking is done via net.ParseIP
    58  	ipv4Regex = regexp.MustCompile(`^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`)
    59  )
    60  
    61  // ValidateFormat validates a string against a standard format.
    62  // It returns nil if the string conforms to the format, an error otherwise.
    63  // The format specification follows the json schema draft 4 validation extension.
    64  // see http://json-schema.org/latest/json-schema-validation.html#anchor105
    65  // Supported formats are:
    66  //
    67  //     - "date-time": RFC3339 date time value
    68  //     - "email": RFC5322 email address
    69  //     - "hostname": RFC1035 Internet host name
    70  //     - "ipv4", "ipv6", "ip": RFC2673 and RFC2373 IP address values
    71  //     - "uri": RFC3986 URI value
    72  //     - "mac": IEEE 802 MAC-48, EUI-48 or EUI-64 MAC address value
    73  //     - "cidr": RFC4632 and RFC4291 CIDR notation IP address value
    74  //     - "regexp": Regular expression syntax accepted by RE2
    75  func ValidateFormat(f Format, val string) error {
    76  	var err error
    77  	switch f {
    78  	case FormatDateTime:
    79  		_, err = time.Parse(time.RFC3339, val)
    80  	case FormatUUID:
    81  		_, err = uuid.FromString(val)
    82  	case FormatEmail:
    83  		_, err = mail.ParseAddress(val)
    84  	case FormatHostname:
    85  		if !hostnameRegex.MatchString(val) {
    86  			err = fmt.Errorf("hostname value '%s' does not match %s",
    87  				val, hostnameRegex.String())
    88  		}
    89  	case FormatIPv4, FormatIPv6, FormatIP:
    90  		ip := net.ParseIP(val)
    91  		if ip == nil {
    92  			err = fmt.Errorf("\"%s\" is an invalid %s value", val, f)
    93  		}
    94  		if f == FormatIPv4 {
    95  			if !ipv4Regex.MatchString(val) {
    96  				err = fmt.Errorf("\"%s\" is an invalid ipv4 value", val)
    97  			}
    98  		}
    99  		if f == FormatIPv6 {
   100  			if ipv4Regex.MatchString(val) {
   101  				err = fmt.Errorf("\"%s\" is an invalid ipv6 value", val)
   102  			}
   103  		}
   104  	case FormatURI:
   105  		_, err = url.ParseRequestURI(val)
   106  	case FormatMAC:
   107  		_, err = net.ParseMAC(val)
   108  	case FormatCIDR:
   109  		_, _, err = net.ParseCIDR(val)
   110  	case FormatRegexp:
   111  		_, err = regexp.Compile(val)
   112  	default:
   113  		return fmt.Errorf("unknown format %#v", f)
   114  	}
   115  	if err != nil {
   116  		go IncrCounter([]string{"goa", "validation", "error", string(f)}, 1.0)
   117  		return fmt.Errorf("invalid %s value, %s", f, err)
   118  	}
   119  	return nil
   120  }
   121  
   122  // knownPatterns records the compiled patterns.
   123  // TBD: refactor all this so that the generated code initializes the map on start to get rid of the
   124  // need for a RW mutex.
   125  var knownPatterns = make(map[string]*regexp.Regexp)
   126  
   127  // knownPatternsLock is the mutex used to access knownPatterns
   128  var knownPatternsLock = &sync.RWMutex{}
   129  
   130  // ValidatePattern returns an error if val does not match the regular expression p.
   131  // It makes an effort to minimize the number of times the regular expression needs to be compiled.
   132  func ValidatePattern(p string, val string) bool {
   133  	knownPatternsLock.RLock()
   134  	r, ok := knownPatterns[p]
   135  	knownPatternsLock.RUnlock()
   136  	if !ok {
   137  		r = regexp.MustCompile(p) // DSL validation makes sure regexp is valid
   138  		knownPatternsLock.Lock()
   139  		knownPatterns[p] = r
   140  		knownPatternsLock.Unlock()
   141  	}
   142  	return r.MatchString(val)
   143  }