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  }