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