Before this change, querying validation results required knowing the
exact path to a specific result node—including numeric indices for
array elements (e.g., `items.1.email`). This made it impractical to
locate the first failing result in dynamic collections where the
failing index is not known ahead of time.
Wildcard segments (`*`) allow matching any single path component, so
patterns like `items.*`, `*.email`, or `data.*.value` will traverse
the result tree and return the first node whose path matches. This is
particularly valuable when validating arrays of items with `each()`,
because consumers can now ask "give me the first failure under items"
without iterating manually.
The implementation replaces the previous flat `array_find` lookup with
a recursive depth-first traversal that, when a wildcard is present,
compares each node's full path against the pattern using segment-level
matching. Non-wildcard lookups continue to use exact array equality,
so there is no behavioral change for existing callers.
Assisted-by: Claude Code (claude-opus-4-6)
The `findByPath()` method was failing to return results when using nested
dot-notation paths such as `user.email` or `items.1`. However, it’s returning
`null` instead of the expected result in some cases.
The root cause was a mismatch between how paths are stored vs searched:
- Storage: Validators like Key and Each create results where the path is stored
as a linked list. For `user.email`, the "email" result has `path="email"` with
`parent="user"`.
- Search (old): The method expected a tree structure where it would find a child
with `path="user"`, then search that child for `path="email"`. But no child
had `path="user"` - only "email" (with "user" as its parent).
The fix computes each result's full path by walking up the parent chain and
compares it against the search path. Also converts numeric strings to integers
when parsing paths (e.g., `items.1` → `['items', 1]`) since array indices are
stored as integers.
While working on this fix, I also realised that to expose the result's status,
it’s best to use `hasFailed()` instead of `isValid()` in `ResultQuery`, since
users will mostly use results when validation failed, not when it passed.
Assisted-by: Claude Code (Opus 4.5)