1package gojsonschema 2 3import ( 4 "net" 5 "net/url" 6 "reflect" 7 "regexp" 8 "strings" 9 "time" 10) 11 12type ( 13 // FormatChecker is the interface all formatters added to FormatCheckerChain must implement 14 FormatChecker interface { 15 IsFormat(input string) bool 16 } 17 18 // FormatCheckerChain holds the formatters 19 FormatCheckerChain struct { 20 formatters map[string]FormatChecker 21 } 22 23 // EmailFormatter verifies email address formats 24 EmailFormatChecker struct{} 25 26 // IPV4FormatChecker verifies IP addresses in the ipv4 format 27 IPV4FormatChecker struct{} 28 29 // IPV6FormatChecker verifies IP addresses in the ipv6 format 30 IPV6FormatChecker struct{} 31 32 // DateTimeFormatChecker verifies date/time formats per RFC3339 5.6 33 // 34 // Valid formats: 35 // Partial Time: HH:MM:SS 36 // Full Date: YYYY-MM-DD 37 // Full Time: HH:MM:SSZ-07:00 38 // Date Time: YYYY-MM-DDTHH:MM:SSZ-0700 39 // 40 // Where 41 // YYYY = 4DIGIT year 42 // MM = 2DIGIT month ; 01-12 43 // DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year 44 // HH = 2DIGIT hour ; 00-23 45 // MM = 2DIGIT ; 00-59 46 // SS = 2DIGIT ; 00-58, 00-60 based on leap second rules 47 // T = Literal 48 // Z = Literal 49 // 50 // Note: Nanoseconds are also suported in all formats 51 // 52 // http://tools.ietf.org/html/rfc3339#section-5.6 53 DateTimeFormatChecker struct{} 54 55 // URIFormatCheckers validates a URI with a valid Scheme per RFC3986 56 URIFormatChecker struct{} 57 58 // HostnameFormatChecker validates a hostname is in the correct format 59 HostnameFormatChecker struct{} 60 61 // UUIDFormatChecker validates a UUID is in the correct format 62 UUIDFormatChecker struct{} 63 64 // RegexFormatChecker validates a regex is in the correct format 65 RegexFormatChecker struct{} 66) 67 68var ( 69 // Formatters holds the valid formatters, and is a public variable 70 // so library users can add custom formatters 71 FormatCheckers = FormatCheckerChain{ 72 formatters: map[string]FormatChecker{ 73 "date-time": DateTimeFormatChecker{}, 74 "hostname": HostnameFormatChecker{}, 75 "email": EmailFormatChecker{}, 76 "ipv4": IPV4FormatChecker{}, 77 "ipv6": IPV6FormatChecker{}, 78 "uri": URIFormatChecker{}, 79 "uuid": UUIDFormatChecker{}, 80 "regex": RegexFormatChecker{}, 81 }, 82 } 83 84 // Regex credit: https://github.com/asaskevich/govalidator 85 rxEmail = regexp.MustCompile("^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$") 86 87 // Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname 88 rxHostname = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`) 89 90 rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$") 91) 92 93// Add adds a FormatChecker to the FormatCheckerChain 94// The name used will be the value used for the format key in your json schema 95func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain { 96 c.formatters[name] = f 97 98 return c 99} 100 101// Remove deletes a FormatChecker from the FormatCheckerChain (if it exists) 102func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain { 103 delete(c.formatters, name) 104 105 return c 106} 107 108// Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name 109func (c *FormatCheckerChain) Has(name string) bool { 110 _, ok := c.formatters[name] 111 112 return ok 113} 114 115// IsFormat will check an input against a FormatChecker with the given name 116// to see if it is the correct format 117func (c *FormatCheckerChain) IsFormat(name string, input interface{}) bool { 118 f, ok := c.formatters[name] 119 120 if !ok { 121 return false 122 } 123 124 if !isKind(input, reflect.String) { 125 return false 126 } 127 128 inputString := input.(string) 129 130 return f.IsFormat(inputString) 131} 132 133func (f EmailFormatChecker) IsFormat(input string) bool { 134 return rxEmail.MatchString(input) 135} 136 137// Credit: https://github.com/asaskevich/govalidator 138func (f IPV4FormatChecker) IsFormat(input string) bool { 139 ip := net.ParseIP(input) 140 return ip != nil && strings.Contains(input, ".") 141} 142 143// Credit: https://github.com/asaskevich/govalidator 144func (f IPV6FormatChecker) IsFormat(input string) bool { 145 ip := net.ParseIP(input) 146 return ip != nil && strings.Contains(input, ":") 147} 148 149func (f DateTimeFormatChecker) IsFormat(input string) bool { 150 formats := []string{ 151 "15:04:05", 152 "15:04:05Z07:00", 153 "2006-01-02", 154 time.RFC3339, 155 time.RFC3339Nano, 156 } 157 158 for _, format := range formats { 159 if _, err := time.Parse(format, input); err == nil { 160 return true 161 } 162 } 163 164 return false 165} 166 167func (f URIFormatChecker) IsFormat(input string) bool { 168 u, err := url.Parse(input) 169 if err != nil || u.Scheme == "" { 170 return false 171 } 172 173 return true 174} 175 176func (f HostnameFormatChecker) IsFormat(input string) bool { 177 return rxHostname.MatchString(input) && len(input) < 256 178} 179 180func (f UUIDFormatChecker) IsFormat(input string) bool { 181 return rxUUID.MatchString(input) 182} 183 184// IsFormat implements FormatChecker interface. 185func (f RegexFormatChecker) IsFormat(input string) bool { 186 if input == "" { 187 return true 188 } 189 _, err := regexp.Compile(input) 190 if err != nil { 191 return false 192 } 193 return true 194} 195