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