Take-Home · Part 2

Extend the validation library

Add cross-field validation to the field-definition format — so a rule like "the project end date must not be before the project start date" can be declared as data, not written as a one-off if. The format design is the point; here's what I chose and why.

View the code on GitHub → github.com/z1on3/benoz-platform-validation
32 / 32
tests passing
16
original tests, unmodified
2
rule kinds, not a language
0
client names in lib/

The shape

Rules live in an optional top-level rules array, a sibling of fields. A definition with no rules key behaves exactly as before, and rule errors use the same { field, message } shape appended to the same list — so validateRecord(definition, record) → { valid, errors } is unchanged.

There are two rule kinds. That's the central design bet: instead of one general expression language stuffed into JSON, the format has a small vocabulary of rule kinds, the same way the library already has a vocabulary of field types. Two kinds cover the two dominant real families — "field A relates to field B" and "field A is required depending on field B" — and each is trivial to read and to write.

compare — one field against another field or a literal

{
  "rule": "compare",
  "field": "project_end_date",
  "op": ">=",
  "against": { "field": "project_start_date" },
  "report_on": "field",
  "message": "Project end date must not be before the project start date"
}

Operators: == != > >= < <=. against is either { "field": "…" } or { "value": … }. Comparison is driven by the left field's declared type — numbers numerically; dates as ISO strings (whose lexicographic order is already chronological, so no timezone traps); text / choice / boolean by equality only. An ordering operator on a non-orderable type is a config error, not a silent pass.

required_if — a field becomes required on a condition

{
  "rule": "required_if",
  "field": "clinical_notes",
  "when": { "field": "priority_level", "op": "==", "value": "urgent" },
  "message": "Clinical notes are required for urgent referrals"
}

The when condition compares another field to a literal, using the same operators. If it holds and the target field is absent, the error reports on the target field.

The four decisions the brief asks for

1 · How a rule refers to another field

By name, through an explicit operand object: { "field": "<name>" } for a field reference, { "value": … } for a literal. The two are distinguished structurally (which key is present), so there's never ambiguity between "the field named 5" and "the number 5." The field must exist in the same definition.

2 · Which field the error is reported against

Default is the left field — the subject of the sentence the rule expresses ("end date must not be before start date"), and the field a user reads as "the one I got wrong." Overridable with report_on: "against" or "both" (the latter for a symmetric min/max pair where neither field is privileged). required_if always reports on the missing field.

3 · What happens when a dependency is missing or invalid

Cross-field rules are a second layer that only runs when the fields it reads are individually sound — present and not already failing their own per-field validation. If a dependency is missing or invalid, the compare rule is skipped: the field's own error (its required or format error) already tells the user what to fix, and "end must be after start" when start is blank or malformed is noise — or literally meaningless, since you'd be comparing against a non-date. This is a deliberate silent-pass at the cross-field layer, not overall — the underlying field error still makes the record invalid. For required_if, if the condition field is missing or invalid the rule doesn't fire: I won't impose a requirement based on an input I couldn't trust.

4 · Where I stopped, and why

Two fixed rule kinds, one operator each — deliberately not a general expression language. Out of scope, on purpose:

The test is the one the brief sets: everything above can be written by someone who has never opened validate.js. If a needed rule can't be expressed in these two kinds, that's the signal to add a new named kind — with its own documented schema — not to overload the two that exist.

Malformed rules fail closed

A rule that can't be interpreted becomes a visible error, never a silent pass — matching the library's existing "unknown field type produces an error rather than a crash." That covers an unknown rule kind, a reference to a field that doesn't exist, an unknown operator, and an ordering operator on a non-orderable type. A bad rule surfaces in tests, not in production.

Verification & honest notes

CheckResult
Original suite (16 tests)unmodified · all pass
New cross-field suite (16 tests)pass — happy path, missing dep, invalid dep, report_on: both, literals, all four required_if branches, every malformed-rule case
lib/ client-agnosticclean — no client names or client field names