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 }