github.com/kaptinlin/jsonschema@v0.4.6/docs/error-handling.md (about) 1 # Error Handling Guide 2 3 Complete guide to handling validation errors and error types. 4 5 ## Error Types 6 7 ### Validation Errors 8 9 Returned when data doesn't meet schema requirements: 10 11 ```go 12 result := schema.Validate(data) 13 if !result.IsValid() { 14 for field, err := range result.Errors { 15 fmt.Printf("%s: %s\n", field, err.Message) 16 } 17 } 18 ``` 19 20 ### Unmarshal Errors 21 22 Returned by the `Unmarshal` method with detailed error information: 23 24 ```go 25 var user User 26 err := schema.Unmarshal(&user, data) 27 if err != nil { 28 if unmarshalErr, ok := err.(*jsonschema.UnmarshalError); ok { 29 fmt.Printf("Type: %s\n", unmarshalErr.Type) 30 fmt.Printf("Reason: %s\n", unmarshalErr.Reason) 31 } 32 } 33 ``` 34 35 ## UnmarshalError Types 36 37 ### Destination Errors 38 39 Problems with the target variable: 40 41 ```go 42 // Nil destination 43 err := schema.Unmarshal(nil, data) 44 // Type: "destination" 45 46 // Non-pointer destination 47 var user User 48 err := schema.Unmarshal(user, data) 49 // Type: "destination" 50 51 // Nil pointer destination 52 var user *User 53 err := schema.Unmarshal(user, data) 54 // Type: "destination" 55 ``` 56 57 ### Source Errors 58 59 Problems with the input data: 60 61 ```go 62 // Invalid JSON syntax 63 invalidJSON := []byte(`{"name": "John", "age":}`) 64 err := schema.Unmarshal(&user, invalidJSON) 65 // Type: "source" 66 67 // Unsupported source type 68 err := schema.Unmarshal(&user, 12345) 69 // Type: "source" 70 ``` 71 72 ### Validation Errors 73 74 Schema validation failures: 75 76 ```go 77 // Missing required field 78 incomplete := []byte(`{"age": 25}`) // missing "name" 79 err := schema.Unmarshal(&user, incomplete) 80 // Type: "validation" 81 82 // Type mismatch 83 wrongType := []byte(`{"name": "John", "age": "twenty-five"}`) 84 err := schema.Unmarshal(&user, wrongType) 85 // Type: "validation" 86 87 // Constraint violation 88 outOfRange := []byte(`{"name": "John", "age": -5}`) 89 err := schema.Unmarshal(&user, outOfRange) 90 // Type: "validation" 91 ``` 92 93 --- 94 95 ## Validation Result Errors 96 97 ### Error Structure 98 99 ```go 100 type EvaluationError struct { 101 Keyword string // Schema keyword that failed 102 Code string // Error code 103 Message string // Human-readable message 104 Params map[string]interface{} // Additional parameters 105 } 106 ``` 107 108 ### Common Keywords 109 110 ```go 111 result := schema.Validate(invalidData) 112 if !result.IsValid() { 113 for field, err := range result.Errors { 114 switch err.Keyword { 115 case "required": 116 fmt.Printf("Missing required field: %s\n", field) 117 case "type": 118 fmt.Printf("Wrong type for field: %s\n", field) 119 case "minimum": 120 fmt.Printf("Value too small for field: %s\n", field) 121 case "maximum": 122 fmt.Printf("Value too large for field: %s\n", field) 123 case "minLength": 124 fmt.Printf("String too short for field: %s\n", field) 125 case "maxLength": 126 fmt.Printf("String too long for field: %s\n", field) 127 case "pattern": 128 fmt.Printf("Pattern mismatch for field: %s\n", field) 129 case "format": 130 fmt.Printf("Invalid format for field: %s\n", field) 131 case "enum": 132 fmt.Printf("Value not in allowed list for field: %s\n", field) 133 } 134 } 135 } 136 ``` 137 138 --- 139 140 ## Error Handling Patterns 141 142 ### Simple Error Check 143 144 ```go 145 result := schema.Validate(data) 146 if !result.IsValid() { 147 return fmt.Errorf("validation failed") 148 } 149 ``` 150 151 ### Detailed Error Reporting 152 153 ```go 154 result := schema.Validate(data) 155 if !result.IsValid() { 156 var messages []string 157 for field, err := range result.Errors { 158 messages = append(messages, fmt.Sprintf("%s: %s", field, err.Message)) 159 } 160 return fmt.Errorf("validation errors: %s", strings.Join(messages, ", ")) 161 } 162 ``` 163 164 ### Error Type Handling 165 166 ```go 167 var user User 168 err := schema.Unmarshal(&user, data) 169 if err != nil { 170 switch e := err.(type) { 171 case *jsonschema.UnmarshalError: 172 switch e.Type { 173 case "validation": 174 log.Printf("Data validation failed: %s", e.Reason) 175 case "destination": 176 log.Printf("Invalid destination: %s", e.Reason) 177 case "source": 178 log.Printf("Invalid source data: %s", e.Reason) 179 default: 180 log.Printf("Unmarshal error (%s): %s", e.Type, e.Reason) 181 } 182 default: 183 log.Printf("Unexpected error: %v", err) 184 } 185 } 186 ``` 187 188 ### Custom Error Messages 189 190 ```go 191 func formatValidationError(result *jsonschema.EvaluationResult) string { 192 if result.IsValid() { 193 return "" 194 } 195 196 var parts []string 197 for field, err := range result.Errors { 198 switch err.Keyword { 199 case "required": 200 parts = append(parts, fmt.Sprintf("Field '%s' is required", field)) 201 case "type": 202 parts = append(parts, fmt.Sprintf("Field '%s' has wrong type", field)) 203 case "minimum": 204 min := err.Params["minimum"] 205 parts = append(parts, fmt.Sprintf("Field '%s' must be at least %v", field, min)) 206 case "maximum": 207 max := err.Params["maximum"] 208 parts = append(parts, fmt.Sprintf("Field '%s' must be at most %v", field, max)) 209 default: 210 parts = append(parts, fmt.Sprintf("Field '%s': %s", field, err.Message)) 211 } 212 } 213 214 return strings.Join(parts, "; ") 215 } 216 ``` 217 218 --- 219 220 ## Error Output Formats 221 222 ### Simple Flag 223 224 ```go 225 result := schema.Validate(data) 226 flag := result.ToFlag() 227 if !flag.Valid { 228 fmt.Println("Data is invalid") 229 } 230 ``` 231 232 ### Structured List 233 234 ```go 235 result := schema.Validate(data) 236 list := result.ToList() 237 238 fmt.Printf("Valid: %t\n", list.Valid) 239 if !list.Valid { 240 for field, message := range list.Errors { 241 fmt.Printf("- %s: %s\n", field, message) 242 } 243 } 244 ``` 245 246 ### Hierarchical Structure 247 248 ```go 249 result := schema.Validate(data) 250 251 // With hierarchy (default) 252 hierarchical := result.ToList(true) 253 254 // Flattened structure 255 flat := result.ToList(false) 256 ``` 257 258 --- 259 260 ## Internationalization 261 262 ### Localized Error Messages 263 264 ```go 265 // Get localizer for Chinese 266 i18n, _ := jsonschema.GetI18n() 267 localizer := i18n.NewLocalizer("zh-Hans") 268 269 // Get localized errors 270 result := schema.Validate(data) 271 localizedList := result.ToLocalizeList(localizer) 272 273 for field, message := range localizedList.Errors { 274 fmt.Printf("%s: %s\n", field, message) // Messages in Chinese 275 } 276 ``` 277 278 ### Available Languages 279 280 - English (en) - Default 281 - Chinese Simplified (zh-Hans) 282 - Chinese Traditional (zh-Hant) 283 - Japanese (ja) 284 - Korean (ko) 285 - French (fr) 286 - German (de) 287 - Spanish (es) 288 - Portuguese (pt) 289 290 --- 291 292 ## Error Recovery Patterns 293 294 ### Graceful Degradation 295 296 ```go 297 func processUser(data []byte) (*User, error) { 298 var user User 299 err := schema.Unmarshal(&user, data) 300 if err != nil { 301 if unmarshalErr, ok := err.(*jsonschema.UnmarshalError); ok { 302 if unmarshalErr.Type == "validation" { 303 // Try basic JSON unmarshaling as fallback 304 if fallbackErr := json.Unmarshal(data, &user); fallbackErr == nil { 305 log.Printf("Used fallback for invalid data: %s", unmarshalErr.Reason) 306 return &user, nil 307 } 308 } 309 } 310 return nil, err 311 } 312 return &user, nil 313 } 314 ``` 315 316 ### Error Aggregation 317 318 ```go 319 func validateBatch(users [][]byte) []error { 320 var errors []error 321 322 for i, userData := range users { 323 var user User 324 err := schema.Unmarshal(&user, userData) 325 if err != nil { 326 errors = append(errors, fmt.Errorf("user %d: %w", i, err)) 327 } 328 } 329 330 return errors 331 } 332 ``` 333 334 ### Partial Validation 335 336 ```go 337 func validateUserPartial(data map[string]interface{}) map[string]error { 338 fieldErrors := make(map[string]error) 339 340 // Validate individual fields 341 for field, value := range data { 342 fieldSchema := getFieldSchema(field) // Your field schema logic 343 if fieldSchema != nil { 344 result := fieldSchema.Validate(value) 345 if !result.IsValid() { 346 for _, err := range result.Errors { 347 fieldErrors[field] = fmt.Errorf(err.Message) 348 break 349 } 350 } 351 } 352 } 353 354 return fieldErrors 355 } 356 ``` 357 358 --- 359 360 ## Testing Error Scenarios 361 362 ### Validation Error Tests 363 364 ```go 365 func TestValidationErrors(t *testing.T) { 366 tests := []struct { 367 name string 368 data string 369 expectValid bool 370 expectError string 371 }{ 372 { 373 name: "missing required field", 374 data: `{"age": 25}`, 375 expectValid: false, 376 expectError: "required", 377 }, 378 { 379 name: "invalid type", 380 data: `{"name": "John", "age": "twenty"}`, 381 expectValid: false, 382 expectError: "type", 383 }, 384 { 385 name: "out of range", 386 data: `{"name": "John", "age": -5}`, 387 expectValid: false, 388 expectError: "minimum", 389 }, 390 } 391 392 for _, tt := range tests { 393 t.Run(tt.name, func(t *testing.T) { 394 result := schema.Validate([]byte(tt.data)) 395 if result.IsValid() != tt.expectValid { 396 t.Errorf("expected valid=%t, got %t", tt.expectValid, result.IsValid()) 397 } 398 399 if !tt.expectValid { 400 found := false 401 for _, err := range result.Errors { 402 if err.Keyword == tt.expectError { 403 found = true 404 break 405 } 406 } 407 if !found { 408 t.Errorf("expected error keyword %s not found", tt.expectError) 409 } 410 } 411 }) 412 } 413 } 414 ``` 415 416 ### Unmarshal Error Tests 417 418 ```go 419 func TestUnmarshalErrors(t *testing.T) { 420 tests := []struct { 421 name string 422 dst interface{} 423 src interface{} 424 errorType string 425 }{ 426 { 427 name: "nil destination", 428 dst: nil, 429 src: []byte(`{"name": "John"}`), 430 errorType: "destination", 431 }, 432 { 433 name: "non-pointer destination", 434 dst: User{}, 435 src: []byte(`{"name": "John"}`), 436 errorType: "destination", 437 }, 438 { 439 name: "invalid JSON", 440 dst: &User{}, 441 src: []byte(`{"name": "John",}`), 442 errorType: "source", 443 }, 444 } 445 446 for _, tt := range tests { 447 t.Run(tt.name, func(t *testing.T) { 448 err := schema.Unmarshal(tt.dst, tt.src) 449 if err == nil { 450 t.Fatal("expected error, got nil") 451 } 452 453 unmarshalErr, ok := err.(*jsonschema.UnmarshalError) 454 if !ok { 455 t.Fatalf("expected UnmarshalError, got %T", err) 456 } 457 458 if unmarshalErr.Type != tt.errorType { 459 t.Errorf("expected error type %s, got %s", tt.errorType, unmarshalErr.Type) 460 } 461 }) 462 } 463 }