github.com/galdor/go-ejson@v0.0.0-20231201100034-d335379f26b0/validator.go (about) 1 package ejson 2 3 import ( 4 "bytes" 5 "fmt" 6 "net/url" 7 "reflect" 8 "regexp" 9 "strconv" 10 "unicode/utf8" 11 12 "github.com/galdor/go-uuid" 13 ) 14 15 type ValidationError struct { 16 Pointer Pointer `json:"pointer"` 17 Code string `json:"code"` 18 Message string `json:"message"` 19 } 20 21 type ValidationErrors []*ValidationError 22 23 type Validator struct { 24 Pointer Pointer 25 Errors ValidationErrors 26 } 27 28 type Validatable interface { 29 ValidateJSON(v *Validator) 30 } 31 32 func (err ValidationError) Error() string { 33 if len(err.Pointer) == 0 { 34 return err.Message 35 } else { 36 return fmt.Sprintf("%v: %s", err.Pointer, err.Message) 37 } 38 } 39 40 func (errs ValidationErrors) Error() string { 41 var buf bytes.Buffer 42 43 buf.WriteString("invalid data") 44 45 if len(errs) > 0 { 46 buf.WriteByte(':') 47 } 48 49 for _, err := range errs { 50 buf.WriteString("\n ") 51 buf.WriteString(err.Error()) 52 } 53 54 return buf.String() 55 } 56 57 func Validate(value interface{}) error { 58 v := NewValidator() 59 60 if validatableValue, ok := value.(Validatable); ok { 61 validatableValue.ValidateJSON(v) 62 } 63 64 if len(v.Errors) > 0 { 65 return v.Error() 66 } 67 68 return nil 69 } 70 71 func NewValidator() *Validator { 72 return &Validator{} 73 } 74 75 func (v *Validator) Error() error { 76 if len(v.Errors) == 0 { 77 return nil 78 } 79 80 return v.Errors 81 } 82 83 func (v *Validator) Push(token interface{}) { 84 v.Pointer = v.Pointer.Child(token) 85 } 86 87 func (v *Validator) Pop() { 88 v.Pointer = v.Pointer.Parent() 89 } 90 91 func (v *Validator) WithChild(token interface{}, fn func()) { 92 v.Push(token) 93 defer v.Pop() 94 95 fn() 96 } 97 98 func (v *Validator) AddError(token interface{}, code, format string, args ...interface{}) { 99 pointer := v.Pointer.Child(token) 100 101 err := ValidationError{ 102 Pointer: pointer, 103 Code: code, 104 Message: fmt.Sprintf(format, args...), 105 } 106 107 v.Errors = append(v.Errors, &err) 108 } 109 110 func (v *Validator) Check(token interface{}, value bool, code, format string, args ...interface{}) bool { 111 if !value { 112 v.AddError(token, code, format, args...) 113 } 114 115 return value 116 } 117 118 func (v *Validator) CheckIntMin(token interface{}, i int, min int) bool { 119 return v.Check(token, i >= min, "integer_too_small", 120 "integer must be greater or equal to %d", min) 121 } 122 123 func (v *Validator) CheckIntMax(token interface{}, i int, max int) bool { 124 return v.Check(token, i <= max, "integer_too_large", 125 "integer must be lower or equal to %d", max) 126 } 127 128 func (v *Validator) CheckIntMinMax(token interface{}, i int, min, max int) bool { 129 if !v.CheckIntMin(token, i, min) { 130 return false 131 } 132 133 return v.CheckIntMax(token, i, max) 134 } 135 136 func (v *Validator) CheckInt64Min(token interface{}, i, min int64) bool { 137 return v.Check(token, i >= min, "integer_too_small", 138 "integer must be greater or equal to %d", min) 139 } 140 141 func (v *Validator) CheckInt64Max(token interface{}, i, max int64) bool { 142 return v.Check(token, i <= max, "integer_too_large", 143 "integer must be lower or equal to %d", max) 144 } 145 146 func (v *Validator) CheckInt64MinMax(token interface{}, i, min, max int64) bool { 147 if !v.CheckInt64Min(token, i, min) { 148 return false 149 } 150 151 return v.CheckInt64Max(token, i, max) 152 } 153 154 func (v *Validator) CheckFloatMin(token interface{}, i, min float64) bool { 155 return v.Check(token, i >= min, "float_too_small", 156 "float %f must be greater or equal to %f", i, min) 157 } 158 159 func (v *Validator) CheckFloatMax(token interface{}, i, max float64) bool { 160 return v.Check(token, i <= max, "float_too_large", 161 "float %f must be lower or equal to %f", i, max) 162 } 163 164 func (v *Validator) CheckFloatMinMax(token interface{}, i, min, max float64) bool { 165 if !v.CheckFloatMin(token, i, min) { 166 return false 167 } 168 169 return v.CheckFloatMax(token, i, max) 170 } 171 172 func (v *Validator) CheckStringLengthMin(token interface{}, s string, min int) bool { 173 length := utf8.RuneCountInString(s) 174 return v.Check(token, length >= min, "string_too_short", 175 "string length must be greater or equal to %d", min) 176 } 177 178 func (v *Validator) CheckStringLengthMax(token interface{}, s string, max int) bool { 179 length := utf8.RuneCountInString(s) 180 return v.Check(token, length <= max, "string_too_long", 181 "string length must be lower or equal to %d", max) 182 } 183 184 func (v *Validator) CheckStringLengthMinMax(token interface{}, s string, min, max int) bool { 185 if !v.CheckStringLengthMin(token, s, min) { 186 return false 187 } 188 189 return v.CheckStringLengthMax(token, s, max) 190 } 191 192 func (v *Validator) CheckStringNotEmpty(token interface{}, s string) bool { 193 return v.Check(token, s != "", "missing_or_empty_string", 194 "missing or empty string") 195 } 196 197 func (v *Validator) CheckStringValue(token interface{}, value interface{}, values interface{}) bool { 198 valueType := reflect.TypeOf(value) 199 if valueType.Kind() != reflect.String { 200 panic(fmt.Sprintf("value %#v (%T) is not a string", value, value)) 201 } 202 203 s := reflect.ValueOf(value).String() 204 205 valuesType := reflect.TypeOf(values) 206 if valuesType.Kind() != reflect.Slice { 207 panic(fmt.Sprintf("values %#v (%T) are not a slice", values, values)) 208 } 209 if valuesType.Elem().Kind() != reflect.String { 210 panic(fmt.Sprintf("values %#v (%T) are not a slice of strings", 211 values, values)) 212 } 213 214 valuesValue := reflect.ValueOf(values) 215 216 found := false 217 for i := 0; i < valuesValue.Len(); i++ { 218 s2 := valuesValue.Index(i).String() 219 if s == s2 { 220 found = true 221 } 222 } 223 224 if !found { 225 var buf bytes.Buffer 226 227 buf.WriteString("value must be one of the following strings: ") 228 229 for i := 0; i < valuesValue.Len(); i++ { 230 if i > 0 { 231 buf.WriteString(", ") 232 } 233 234 s2 := valuesValue.Index(i).String() 235 buf.WriteString(s2) 236 } 237 238 v.AddError(token, "invalid_value", "%s", buf.String()) 239 } 240 241 return found 242 } 243 244 func (v *Validator) CheckStringMatch(token interface{}, s string, re *regexp.Regexp) bool { 245 return v.CheckStringMatch2(token, s, re, "invalid_string_format", 246 "string must match the following regular expression: %s", 247 re.String()) 248 } 249 250 func (v *Validator) CheckStringMatch2(token interface{}, s string, re *regexp.Regexp, code, format string, args ...interface{}) bool { 251 if !re.MatchString(s) { 252 v.AddError(token, code, format, args...) 253 return false 254 } 255 256 return true 257 } 258 259 func (v *Validator) CheckStringURI(token interface{}, s string) bool { 260 // The url.Parse function parses URI references. Most of the time we are 261 // interested in URIs, so we check that there is a schema. 262 263 uri, err := url.Parse(s) 264 if err != nil { 265 v.AddError(token, "invalid_uri_format", "string must be a valid uri") 266 return false 267 } 268 269 if uri.Scheme == "" { 270 v.AddError(token, "missing_uri_scheme", "uri must have a scheme") 271 return false 272 } 273 274 return true 275 } 276 277 func (v *Validator) CheckUUID(token interface{}, value string) bool { 278 var id uuid.UUID 279 280 if !v.CheckStringNotEmpty(token, value) { 281 return false 282 } 283 284 ok := v.Check(token, id.Parse(value) == nil, "invalid_uuid", 285 "string must be a valid uuid") 286 if !ok { 287 return false 288 } 289 290 return v.Check(token, !id.Equal(uuid.Nil), "invalid_uuid", 291 "string must not be a null uuid") 292 } 293 294 func (v *Validator) CheckArrayLengthMin(token interface{}, value interface{}, min int) bool { 295 var length int 296 297 checkArray(value, &length) 298 299 return v.Check(token, length >= min, "array_too_small", 300 "array must contain %d or more elements", min) 301 } 302 303 func (v *Validator) CheckArrayLengthMax(token interface{}, value interface{}, max int) bool { 304 var length int 305 306 checkArray(value, &length) 307 308 return v.Check(token, length <= max, "array_too_large", 309 "array must contain %d or less elements", max) 310 } 311 312 func (v *Validator) CheckArrayLengthMinMax(token interface{}, value interface{}, min, max int) bool { 313 if !v.CheckArrayLengthMin(token, value, min) { 314 return false 315 } 316 317 return v.CheckArrayLengthMax(token, value, max) 318 } 319 320 func (v *Validator) CheckArrayNotEmpty(token interface{}, value interface{}) bool { 321 var length int 322 323 checkArray(value, &length) 324 325 return v.Check(token, length > 0, "empty_array", "array must not be empty") 326 } 327 328 func checkArray(value interface{}, plen *int) { 329 valueType := reflect.TypeOf(value) 330 331 switch valueType.Kind() { 332 case reflect.Slice: 333 *plen = reflect.ValueOf(value).Len() 334 335 case reflect.Array: 336 *plen = valueType.Len() 337 338 default: 339 panic(fmt.Sprintf("value is not a slice or array")) 340 } 341 } 342 343 func (v *Validator) CheckOptionalObject(token interface{}, value interface{}) bool { 344 if !checkObject(value) { 345 return true 346 } 347 348 return v.doCheckObject(token, value) 349 } 350 351 func (v *Validator) CheckObject(token interface{}, value interface{}) bool { 352 if !checkObject(value) { 353 v.AddError(token, "missing_value", "missing value") 354 return false 355 } 356 357 return v.doCheckObject(token, value) 358 } 359 360 func (v *Validator) CheckObjectArray(token interface{}, value interface{}) bool { 361 valueType := reflect.TypeOf(value) 362 kind := valueType.Kind() 363 364 if kind != reflect.Array && kind != reflect.Slice { 365 panic(fmt.Sprintf("value %#v (%T) is not an array or slice", 366 value, value)) 367 } 368 369 ok := true 370 371 v.WithChild(token, func() { 372 values := reflect.ValueOf(value) 373 374 for i := 0; i < values.Len(); i++ { 375 child := values.Index(i).Interface() 376 childOk := v.CheckObject(strconv.Itoa(i), child) 377 ok = ok && childOk 378 } 379 }) 380 381 return ok 382 } 383 384 func (v *Validator) CheckObjectMap(token interface{}, value interface{}) bool { 385 valueType := reflect.TypeOf(value) 386 if valueType.Kind() != reflect.Map { 387 panic(fmt.Sprintf("value %#v (%T) is not a map", value, value)) 388 } 389 390 ok := true 391 392 v.WithChild(token, func() { 393 values := reflect.ValueOf(value) 394 395 iter := values.MapRange() 396 for iter.Next() { 397 key := iter.Key() 398 if key.Kind() != reflect.String { 399 panic(fmt.Sprintf("value %#v (%T) is a map whose keys are "+ 400 "not strings", value, value)) 401 } 402 keyString := key.Interface().(string) 403 404 value := iter.Value().Interface() 405 406 valueOk := v.CheckObject(keyString, value) 407 ok = ok && valueOk 408 } 409 }) 410 411 return ok 412 } 413 414 func (v *Validator) doCheckObject(token interface{}, value interface{}) bool { 415 nbErrors := len(v.Errors) 416 417 value2, ok := value.(Validatable) 418 if !ok { 419 return true 420 } 421 422 v.Push(token) 423 value2.ValidateJSON(v) 424 v.Pop() 425 426 return len(v.Errors) == nbErrors 427 } 428 429 func checkObject(value interface{}) bool { 430 valueType := reflect.TypeOf(value) 431 if valueType == nil { 432 return false 433 } 434 435 if valueType.Kind() != reflect.Pointer { 436 panic(fmt.Sprintf("value %#v (%T) is not a pointer", value, value)) 437 } 438 439 pointedValueType := valueType.Elem() 440 if pointedValueType.Kind() != reflect.Struct { 441 panic(fmt.Sprintf("value %#v (%T) is not a pointer to a structure", 442 value, value)) 443 } 444 445 return !reflect.ValueOf(value).IsZero() 446 }