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 }