github.com/shogo82148/goa-v1@v1.6.2/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/shogo82148/goa-v1/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 }