Skip to content

feat: add machine-readable Code and Params fields to ErrorDetail #999

@Micaso

Description

@Micaso

Problem

When consuming validation errors from huma, only a human-readable message string is available on each ErrorDetail. This makes it difficult to programmatically map huma errors to custom application error types without fragile string parsing.

For example, an expected_one_of error today looks like:

{
"message": "expected value to be one of \"foo\", \"bar\"",
"location": "body.status",
"value": "baz"
}

To handle this in downstream code you'd have to parse the message string to
recover the allowed values — which breaks if the message format changes or is
customised via ErrorFormatter.

Solution

Add two new omitempty fields to ErrorDetail:

  • Code string — a snake_case machine-readable identifier (e.g. "expected_one_of") that corresponds to one of the new validation.Code* constants, one per existing Msg* variable.
  • Params map[string]any — the constraint parameters relevant to the error, e.g. {"allowed": ["foo","bar"]} for enum errors or {"min": 3} for min-length errors.

The same error now looks like:

{
  "message": "expected value to be one of \"foo\", \"bar\"",
  "location": "body.status",
  "value": "baz",
  "code": "expected_one_of",
  "params": { "allowed": ["foo", "bar"] }
}

This enables downstream consumers to switch on Code without touching Message:

for _, detail := range errModel.Errors {
    switch detail.Code {
    case validation.CodeExpectedOneOf:
        allowed := detail.Params["allowed"].([]any)
        // map to your own enum error with structured allowed values
    case validation.CodeExpectedRequiredProperty:
        prop := detail.Params["property"].(string)
        // map to your own missing-field error
    }
}

Changes

  • validation/messages.go — adds a Code* constant for every Msg* variable
  • error.go — adds Code string and Params map[string]any to ErrorDetail (both omitempty, fully backwards-compatible)
  • validate.go — adds ValidateResult.AddCode() method; migrates all internal res.Add() call sites to res.AddCode() with the appropriate code and params
  • validate_test.go — adds TestValidateErrorCodes with 43 subtests covering every code and verifying params where applicable

Backwards compatibility

  • The two new fields are omitempty — existing JSON consumers see no change unless they opt in to reading the new fields.
  • ValidateResult.Add() and ValidateResult.Addf() are unchanged.
  • Custom NewError implementations continue to work as before.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions