home

Validation

Apr 21, 2023

A good validation error is developer-friendly. This means that it is structured and, as a litmus test, should allow a consumer to create a localized error message. Consider:

[
  {
    "code": 1,
    "field": "user.name",
  },
  {
    "code": 14,
    "field": "users.0.dob",
    "data": {"min": 1900, "max": 2023}
  }
]

code tells us the type of error. In the above example code 1 means the field is required and code 14 means the provided number isn't within the allowed range. data is optional and depends on code. A code 1 error never has a data field. A code 14 error always has a data field with a min and a max.

field is the name of the invalid input. (I go back and forth on whether it should be a flattened string, "users.7.name" or an array, ["users", 7, "name"]. I don't know)

A good valiation error is user-safe. In addition to the code, data and field, a simple description should be included:

[
  {
    "code": 1,
    "field": "user.name",
    "error": "is required"
  },
  {
    "code": 14,
    "field": "users.0.dob",
    "data": {"min": 1900, "max": 2023},
    "error": "must be between 1900 and 2023"
  }
]

user-safe means it can be displayed to end-users as-is. It doesn't contain sensitive data or overly technical jargon. But its generic nature means that it isn't necessary "user-friendly". For example, instead of saying "you cannot have more than 10 tags per product", it likely uses a more generic "must have no more than 10 items".

The description may be localized - it depends how/if the rest of the system handles localization. The description uses correct plural form: it's "1 character", not "1 characters".

A good validation library supports custom validators with application-specific context.. The validation of "product.tags" might depend on a customer setting of the user making the request.

A good validation library exposes the full input and current object being validated:

{
  "columns": [
    {"name": "status", "type": "int", "default": 0},
    {"name": "name", "type": "text"}
  ]
}

The validation of columns.0.default depends on the value of columns.0.type. The custom validator must be able to access current["type"].

Finally, validation (and data integrity) are first-class priorities of the whole system. Sure, for many cases, validation is about making sure the user's input is valid. It's a one and done at the start of an HTTP handler. But don't force it. Put validation where it makes the most sense, where there's the most context. Avoid using exceptions or error values to surface validation errors. Create a "validation context" that you pass around to collect validation errors.