Commit graph

2,329 commits

Author SHA1 Message Date
Henrique Moody
305a7d15db
Add FormatterModifier to message rendering and expand test coverage 3.0.1
Integrate FormatterModifier into the validation message modifier chain,
enabling StringFormatter formatters to be used as pipe modifiers in
message templates (e.g., {{input|uppercase}}, {{input|mask:1-4:X}}).
This requires bumping respect/string-formatter to ^1.7.

Add feature tests for both the Formatted validator (uppercase, lowercase,
trim, number, date, creditCard, secureCreditCard, chained formatters) and
the FormatterModifier in templates (all major formatters, custom parameters,
multi-argument modifiers, and chained modifiers).

Assisted-by: Claude Code (Claude Opus 4.6)
2026-02-10 06:07:49 +01:00
copilot-swe-agent[bot]
eedce8fb32 Use Punycode filenames for non-ASCII TLD suffix data files 3.0.0
Some systems and tools (e.g., certain archive extractors, Windows
environments, or CI pipelines) do not properly handle non-ASCII
characters in file paths. The public suffix data files for
internationalized TLDs (such as ישראל, СРБ, 香港, and ไทย) were stored
using their native Unicode names, which caused installation failures
on those systems.

This commit converts those filenames to their Punycode equivalents
(e.g., XN--4DBRK0CE.php instead of ישראל.php) using `idn_to_ascii()`.
Both the data generation command (`UpdateDomainSuffixesCommand`) and the
runtime validator (`PublicDomainSuffix`) are updated to use the same
Punycode-based file lookup, ensuring consistency. A polyfill dependency
(`symfony/polyfill-intl-idn`) is added so that `idn_to_ascii()` is
available even when the `intl` PHP extension is not installed.

Assisted-by: Claude Code (Claude Opus 4.6)
Co-authored-by: Henrique Moody <henriquemoody@gmail.com>
2026-02-09 17:34:56 +01:00
Henrique Moody
ddb5ef6605
Export aliases.php in package
Previously aliases.php was incorrectly excluded from the package export due to
improper gitattributes configuration. This change ensures aliases.php is
included in the distributed package by using the correct -export-ignore syntax.

Assisted-by: OpenCode (ollama-cloud/glm-4.7)
2026-02-09 15:34:55 +01:00
dependabot[bot]
5a114662e4 Bump giggsey/libphonenumber-for-php-lite from 8.13.55 to 9.0.23
Bumps [giggsey/libphonenumber-for-php-lite](https://github.com/giggsey/libphonenumber-for-php-lite) from 8.13.55 to 9.0.23.
- [Release notes](https://github.com/giggsey/libphonenumber-for-php-lite/releases)
- [Commits](https://github.com/giggsey/libphonenumber-for-php-lite/compare/8.13.55...9.0.23)

---
updated-dependencies:
- dependency-name: giggsey/libphonenumber-for-php-lite
  dependency-version: 9.0.23
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 13:52:05 +01:00
dependabot[bot]
a0ecc12049 Bump nette/php-generator from 4.2.0 to 4.2.1
Bumps [nette/php-generator](https://github.com/nette/php-generator) from 4.2.0 to 4.2.1.
- [Release notes](https://github.com/nette/php-generator/releases)
- [Commits](https://github.com/nette/php-generator/compare/v4.2.0...v4.2.1)

---
updated-dependencies:
- dependency-name: nette/php-generator
  dependency-version: 4.2.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 13:47:42 +01:00
dependabot[bot]
4d27c08992 Bump symfony/var-exporter from 7.4.0 to 8.0.0
Bumps [symfony/var-exporter](https://github.com/symfony/var-exporter) from 7.4.0 to 8.0.0.
- [Release notes](https://github.com/symfony/var-exporter/releases)
- [Changelog](https://github.com/symfony/var-exporter/blob/8.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/var-exporter/compare/v7.4.0...v8.0.0)

---
updated-dependencies:
- dependency-name: symfony/var-exporter
  dependency-version: 8.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 13:45:55 +01:00
dependabot[bot]
f869da4883 Bump phpstan/phpstan-phpunit from 2.0.12 to 2.0.13
Bumps [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) from 2.0.12 to 2.0.13.
- [Release notes](https://github.com/phpstan/phpstan-phpunit/releases)
- [Commits](https://github.com/phpstan/phpstan-phpunit/compare/2.0.12...2.0.13)

---
updated-dependencies:
- dependency-name: phpstan/phpstan-phpunit
  dependency-version: 2.0.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 13:43:51 +01:00
Henrique Moody
c783abaf37
Upgrade respect/string-formatter to 1.6.0 and remove redundant alias
- Updated respect/string-formatter from ^1.0 to ^1.6
- Removed manual 'f' alias for FormatterBuilder as the package now handles it
- Simplified aliases.php by delegating aliasing to the dependency itself

Assisted-by: OpenCode (ollama-cloud/glm-4.7)
2026-02-09 13:38:40 +01:00
The Respect Panda
74299fded1
Update Regional Information 2026-02-09 13:10:54 +01:00
Henrique Moody
d4605f4493
Add wildcard support to ResultQuery::findByPath()
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)
2026-02-08 22:43:06 +01:00
Henrique Moody
6abf3a548c
Remove Masked validator in favor of Formatted
The Masked validator was a proxy for what the new Formatted validator
already does, so it is being removed to reduce redundancy. All tests and
documentation have been updated accordingly.

Assisted-by: OpenCode (ollama-cloud/glm-4.7)
2026-02-06 20:44:26 +01:00
Henrique Moody
dc0c0345c9
Create "Formatted" validator
The Formatted validator decorates another validator to transform how
input values appear in error messages, while still validating the
original unmodified input.

This is useful for improving the readability of error messages by
displaying values in a user-friendly formatd.

The validator accepts any Respect\StringFormatter\Formatter implementation,
allowing direct use of StringFormatter's fluent builder. As StringFormatter
expands with more formatters in future releases, users will automatically
benefit from the full range of formatting options.

Assisted-by: Claude Code (Opus 4.5)
2026-02-06 20:44:26 +01:00
Alexandre Gomes Gaigalas
8eaedcfba0 Update composer lifecycle
- Commit composer.lock.
 - The lock is not ignored anymore.
 - Changed to ramsey/composer-install action for cache.
2026-02-06 19:20:10 +00:00
Henrique Moody
e84d15f4e4
Rename Composite to LogicalComposite
Rename the Composite class to LogicalComposite to more accurately reflect
its purpose as a validator that combines child validators using logical
operations (AND, OR, NAND, XOR).

This better naming also opens the door for additional composite patterns
beyond logical operations, enabling future validator compositions.

Assisted-by: OpenCode (GLM 4.5)
2026-02-06 18:15:36 +01:00
Alexandre Gomes Gaigalas
91ceaafd2d Update translation docs
The documents on translation were updated to feature symfony with
an array provider. Duplicated container notes were extracted to
a single configuration.md file.

An API for accessing the messages, so users don't have to copy
and paste them from the source or docs, was provided and
TemplateResolver was refactored to use it.
2026-02-06 17:11:34 +00:00
Henrique Moody
b701fac656
Create ShortCircuit validator and ShortCircuitable interface
This commit introduces a mechanism for validators to return early once
the validation outcome is determined, rather than evaluating all child
validators.

The ShortCircuit validator evaluates validators sequentially and stops
at the first failure, similar to how PHP's && operator works. This is
useful when later validators depend on earlier ones passing, or when
you want only the first error message.

The ShortCircuitCapable interface allows composite validators (AllOf,
AnyOf, OneOf, NoneOf, Each, All) to implement their own short-circuit
logic.

Why "ShortCircuit" instead of "FailFast":

The name "FailFast" was initially considered but proved misleading.
While AllOf stops on failure (fail fast), AnyOf stops on success
(succeed fast), and OneOf stops on the second success. The common
behavior is not about failing quickly, but about returning as soon as
the outcome is determined—which is exactly what short-circuit
evaluation means. This terminology is familiar to developers from
boolean operators (&& and ||), making the behavior immediately
understandable.

Co-authored-by: Alexandre Gomes Gaigalas <alganet@gmail.com>
Assisted-by: Claude Code (Opus 4.5)
2026-02-05 17:32:42 +01:00
Alexandre Gomes Gaigalas
e636b63f14 Add v::format() to check if data is already in a specific format
This validator is not so much about how we present the input
during error messages, but in what format data already is formatted.
2026-02-05 16:19:11 +00:00
Valdeir S
570ba481b1 Add Support for Alphanumeric CNPJ
Due to the continuous increase in the number of companies and the
imminent exhaustion of available CNPJs (Brazilian taxpayer
identification numbers), the Brazilian Federal Revenue Service is
instituting the alphanumeric CNPJ. The initiative aims to
facilitate the identification of all companies and improve the
business environment, contributing to the economic and social
development of Brazil.

The alphanumeric CNPJ will be assigned, starting in July 2026,
exclusively to new registrations.

Changes:

- Add support for alphanumeric CNPJ validation
- Format code according to PHPCS standards
- Simplify CNPJ conversion to uppercase character array
- Add documentation about CNPJ structure
2026-02-04 13:20:30 +01:00
Alexandre Gomes Gaigalas
16148e9593 Standardize and improve validation message templates
- Remove redundant "valid" prefix:
   Date, DateTime, DateTimeDiff, Domain, Email, Iban, Imei, Ip, Isbn, Json, LanguageCode, LeapDate, LeapYear, Luhn, MacAddress, NfeAccessKey, Nif, Nip, Pesel, Phone, Pis, PolishIdCard, PostalCode, Roman, Slug, Tld, Url, Uuid, Version.

 - Remove redundant "value" suffix
   ArrayVal, BoolVal, Countable, FloatVal, IntVal, IterableVal, NumericVal, ScalarVal, StringVal.

 - Standardize "consist only of" phrasing
   Alnum, Alpha, Cntrl, Consonant, Digit, Graph, Lowercase, Printable, Punct, Space, Spaced, Uppercase, Vowel, Xdigit.

 - Improve file accessibility messages
   Directory, Executable, File, Image, Readable, SymbolicLink, Writable.

 - Improve grammar and article usage
   CreditCard, Extension, Mimetype, Regex, Size.
2026-02-03 19:58:55 +00:00
Alexandre Gomes Gaigalas
5f25627e61 Update .gitattributes
- Follows up on the rename from library/ to src/
 - Includes required files for proper SBOM
 - Don't show diffs for generated files
 - Include aliases.php
2026-02-03 16:22:40 +00:00
Henrique Moody
7c681fec66
Fix SPDX headers in all files
I ran the `bin/console spdx --fix` with different strategies for
different files. For most of the core classes, since they've been
drastically rebuilt, I've run it with the `git-blame` strategy, for for
the `src/Validators`, in which the API changed completely but the logic
remains the same, I use the `git-log` strategy.
2026-02-03 15:23:23 +01:00
Henrique Moody
7db3bea8a6
Enhance LintSpdxCommand with contributor tracking and header normalization
Improves SPDX header linting to ensure consistent license metadata across
the codebase.

Key changes:

- Enforce deterministic tag ordering (License-Identifier, FileCopyrightText,
  FileContributor) to ensure consistency, prevent merge conflicts, and
  simplify code reviews

- Add contributor alias mapping to consolidate contributors with multiple
  emails or name variations (e.g., "nickl-" → "Nick Lombard")

- Add --contributions-strategy option with "blame" (current code authors)
  and "log" (all historical contributors) to support different attribution
  philosophies

- Add optional path argument to lint specific files or directories

- Add --fix option to automatically correct header issues

Assisted-by: Claude Code (claude-opus-4-5-20251101)
2026-02-03 15:23:20 +01:00
Alexandre Gomes Gaigalas
1b584d88a3 Handle more collision scenarios on NestedArrayFormatter
See #1668 for more info.

There is a new test "Deep name collision" on AllOfTest that
exemplifies the kind of collision this change solves.

Some extra `__root__` keys needed to be added to a select few
other scenarios for consistency.
2026-02-02 20:21:14 +00:00
Henrique Moody
63f7753e43
Rename "Call" validator to "After"
The previous name was confusing as it focused on the implementation
detail (calling a callable) rather than what the validator actually
does. The new name "After" better conveys the validator's purpose:
validating the input after applying a transformation.

Assisted-by: Claude Code (Opus 4.5)
2026-02-02 01:46:12 +01:00
Henrique Moody
92d53f096d
Rename "Callback" validator to "Satisfies"
The previous name was confusing as it focused on the implementation
detail (receiving a callback) rather than what the validator actually
does. The new name "Satisfies" better conveys the validator's purpose:
checking whether the input satisfies a given condition.

Assisted-by: Claude Code (Opus 4.5)
2026-02-02 01:40:14 +01:00
Henrique Moody
f822ed0298
Rename "Lazy" validator to "Factory"
The name "Factory" better conveys the validator's purpose: dynamically
creating a validator at runtime based on the input value. "Lazy" was
misleading as it suggested simple deferred evaluation rather than
input-dependent validator construction.

Also renamed the constructor argument from `$validatorCreator` to
`$factory` for improved expressiveness.

Assisted-by: Claude Code (Opus 4.5)
2026-02-02 01:40:14 +01:00
Alexandre Gomes Gaigalas
a64e33168f Improve Url validator to be more strict
Previously, the URL validator accepted all kinds of URLs.

Combining it with other validators like `Domain` and `Ip` manually
is clumbersome, since the rules are extensive and require small
tweaks such as understanding bracketed IP addresses.

This change makes that composition built-in. The validator still
uses the FILTER_VALIDATE_URL from PHP, but now it also goes deeper
and validate portions of the URL that other validators support.

Changes to `Call` were made so that it can be serialized under
certain circumstances (when invoking a static method call), making
it more flexible.

A static internal method helper for trimming the IP was added to
the Url validator class and marked as internal, as it cannot be
both private, serializable and accessible to the `Call` instance
all at the same time. The `@internal` annotation should advise
users that it's not a public API according to phpdoc conventions.
2026-02-02 00:26:40 +00:00
Alexandre Gomes Gaigalas
ce527e21ad Improve Documentation
- Added `composer docs-serve` to preview documentation locally.
 - Improved `ValidatorRelatedLinter` to not include invalid related
   entries.
 - Updated `CONTRIBUTING.md`
2026-02-01 22:46:48 +00:00
Alexandre Gomes Gaigalas
0b81dcb4f7 Update README.md and README.md-linked headline docs
The phrase "granularity control for advanced reporting" is not
accurate anymore. Although we offer much better exception handling
now, we are currently not offering granularity control at a global
level (previously implemented by the check() interface).

If that were to be changed, we can restore that headline. I instead
replaced with the phrase "Advanced exception handling", which
accurately describes what we offer.

I also removed the `concrete-api.md`, which was a document
describing how we don't rely on static calls. We do, in fact,
recommend some of them, such as `ValidatorBuilder::init`, and
although they can be avoided, it relies on setting up a custom
container which we do not offer full documentation for yet.
2026-02-01 22:46:02 +00:00
Alexandre Gomes Gaigalas
53b3bded7d Improve performance of Prefix transformer
The Prefix transformer had many loops that could be avoided. This
change replaces them for a compiled PCRE regex, taking advantage
of recent PCRE JIT capabilities introduced in PHP.

These changes offer no performance trade-offs, improving lookup
for all categories of prefixes (property/key with shift, ignore
list and fallback to simple rule).

The most affected is the simple rule (no prefix or no conflict
with any kind of prefix rule), yielding the most gains.
2026-02-01 22:45:51 +00:00
Alexandre Gomes Gaigalas
699f06075c Increase Coverage
- Changed Property so it doesn't have an unreachable line anymore.
 - Rename ValidatorTest to ValidatorBuilderTest to better reflect
   the source.
 - Added missing tests for KeyExists's ArrayAccess support.
 - Added tests for using custom exceptions.
2026-01-31 17:18:53 +00:00
Alexandre Gomes Gaigalas
9def16a19e Containerize Uuid optional dependencies for testability
This change makes the ramsey/uuid optional dependency testable
by using the ContainerRegistry to acquire an instance to its factory
instead of relying on `class_exists` directly.
2026-01-31 17:18:37 +00:00
Henrique Moody
b352f17718
Remove "Wrapper" abstract class
With PHP's promoted properties, there is little reason to have an
abstract class that only provides a constructor and a proxy method.
The greater part of the classes that extended Wrapper had their own
implementation of evaluate(), hence it made very little sense to keep
Wrapper in the codebase.

Assisted-by: Claude Code (Opus 4.5)
2026-01-31 00:36:19 +01:00
Alexandre Gomes Gaigalas
bcc60ec035 Allow empty values in iterables for All, Each, Max, Min
Now empty values are again allowed in FilteredArray-style
validators.

To solve the issue with negation, a Result attribute was
added to signal indeciseveness (when a result cannot be
reliably inverted). On such cases, we consider that result
to be valid.

For example, `v::not(v::min(v::equals(10)))` says "The
lowest value of the iterable input should not be equal 10".

If the input is empty, we cannot decide whether its minimum
is equal to 10 or not, so the validator essentially becomes
a null-op.

Users that want to ensure these validators have a valid
decidable target must use it in combination with `Length`
or other similar validators to achieve the same result.
2026-01-30 21:27:16 +00:00
Henrique Moody
882f24b6b8
Create "Masked" validator
The Masked validator decorates other validators to mask sensitive input
values in error messages while still validating the original unmasked
data.

This validator is essential for applications handling sensitive
information such as passwords, credit cards, or email addresses. Without
it, users would need to implement a custom layer between Validation and
the end user to prevent PII from appearing in error messages or logs.

With Masked, sensitive data protection is built directly into the
validation workflow with no additional abstraction required.

Assisted-by: Claude Code (Opus 4.5)
2026-01-30 21:06:36 +01:00
Henrique Moody
aafa204307
Fix input overwrite not propagating to adjacent results
Adjacent results are results that treat the same input. When overwriting
the input of a result, we should also overwrite the input of its adjacent
result to maintain consistency. Currently, there are no cases where this
has caused issues, but this change prevents potential problems.

Assisted-by: Claude Code (Opus 4.5)
2026-01-30 20:40:12 +01:00
Alexandre Gomes Gaigalas
ec16b3d2df Refactor case sensitiveness support
This is a mid-size refactor that affects several validators.

Most prominently, the ones that had an `$identical` parameter
to deal with case sensitiveness.

This parameter was confusing, effectively making validators such
as `Contains` behave very differently for arrays versus strings.

In arrays, `$identical` meant "the same type", while it in strings
it meant "case sensitive".

That parameter was removed, and the default behavior is now to
always compare **case sensitive** and strict typing.

A document explaining how to combine other validators in order
to achieve case _insensitive_ comparisons was added.

Additionally, the `Call` validator was refactored back to be
suitable to take on the task of being a fast, quick composable
validator.

With the introduction of `Circuit`, we can shift the responsibility
of dealing with possible mismatches to the user. This kind of type
handling is demonstrated in how I refactored `Tld` to account for
the type mismatch without setting error handlers.
2026-01-30 17:11:13 +00:00
Alexandre Gomes Gaigalas
47f8f82d7f Remove mathematical, niche and deprecated validators
This commit removes validators described in #1642, refactoring
to clean up after their removal.

 - Url was refactored to use the function `filter_var` instead.
 - tests/bootstrap.php is no longer needed and was removed.
 - Updated migration guide with recommendations for replacements.
2026-01-30 16:08:27 +00:00
Alexandre Gomes Gaigalas
7876576f08 Change CI Perf to run on schedule, no runs on PRs
See #1635

The goal is to collect data for a while. Until we have enough
benchmarks, the PR check is not very useful, so it was disabled.

We added `--tolerate-failure` to all runs, so this check will
never fail because of a missed performance assertion, but it will
report it and archive it.
2026-01-30 11:45:06 +00:00
Alexandre Gomes Gaigalas
b603ef7b24 Containerize PhoneNumberUtil instance for Phone rule
Similar to d8e31db (commit that containerized iso code dbs), but
this time for PhoneNumberUtil.

This makes the optional dependency testable.

PhoneNumberUtil doesn't have a public constructor, so a factory
was declared instead.
2026-01-29 21:42:32 +00:00
Alexandre Gomes Gaigalas
d8e31dbc3a Containerize sokil databases
The main focus of this change is to make those optional dependencies
more testable.

Unfortunately, some phpstan-ignores had to be included, since ::set
is not a PsrContainer method. We're only using it on tests though,
so it's fine. It targets our php-di container for testing purposes
only. The real implementation only relies on ::get.

This change also has the side effect of improving the performance
of those validators by not instantiating their databases each time
a iso validator is built, achieving massive improvements in those
scenarios. A small benchmark with no assertions was added to track
that improvement.
2026-01-29 19:29:51 +00:00
Alexandre Gomes Gaigalas
7f4a4c2035 Fix misplaced Outer/Inner names when using named() on Each
The Each validator was using "Outer" names (the name applied to
the parent) in child results and vice-versa.

This commit fixes it, and also streamlines the Result class,
introducing helper `mapChildren` and `mapAdjacent` methods and
removing unecessary recursive array_maps.

Named children now also display their names, allowing users to
create more meaningful messages that do not spam nested
`.0`...`.0`...etc numeric keys which could be confusing. If the
user does not name them, the previous behavior is kept.
2026-01-29 18:24:50 +00:00
Alexandre Gomes Gaigalas
03817185b2 Remove now unecessary fallback to json_decode
Since PHP 8.3, we don't need that fallback anymore. Now that the
library is set to a minimum PHP 8.5, we can safely remove it.
2026-01-29 11:14:40 +00:00
Alexandre Gomes Gaigalas
bee97ae6e1 Fixes git workspace after checking out benchmarks
With the introduction of composite actions in the GitHub workflows,
the ci-perf.yml workflow broke.

This happens because the setup-action configures a Post Run (a hook
to be executed after the workflow).

Since the benchmarks checkout a different orphan branch, when that
Post Run executes, it cannot find the action anymore.

To fix it, I introduce a new step that restores the git workspace,
making it available for that hook.
2026-01-29 11:06:19 +00:00
Alexandre Gomes Gaigalas
b69beb1db7 Refactor CI Workflows
- Added a composite action for common setup tasks.
 - Shorter names that fit better GitHub runner displays.
 - Changed ci-perf to only run if src or tests change.
 - Removed redundant step names when they're obvious.
2026-01-28 17:31:37 +00:00
Alexandre Gomes Gaigalas
2a7f345e32 Streamline validators.md index
Makes it so the index looks more like a cheatsheet, condensing
information instead of making long lists that require lots of
scrolling to explore.

Additionally, the happy path for each validator was also
added, providing a quick reference use for comparison.

The direct markdown links were replaced by titled markdown
references, offering mouse-over tooltips over links that
display the validator one-line description.

To ensure a proper source of truth for these new index
goodies, the AssertionMessageLinter was modified to
verify that the first assertion in each doc is a
single-line validator that passes (a happy path), further
making our documentation conventions more solid.
2026-01-28 12:47:08 +00:00
Henrique Moody
68ed5d20d1
Create migration guide from v2 to v3
As we approach the release of version 3.0, it is important to provide our users
with a clear guide to migrating from version 2.x to 3.0. This commit provides
guidance by creating a document that will remain in the repository, so users can
always access it.
2026-01-27 14:40:28 +01:00
Henrique Moody
00e7f2a6ff
Fix ResultQuery::findByPath() for nested paths
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)
2026-01-27 13:25:40 +01:00
Henrique Moody
4390e4feb6
Simplify how we load and save files in data/
We had different ways of saving and loading files from `data/`, so I decided to
unify them to simplify things. I repurposed the `DomainInfo` class and named it
`DataLoader`, so we can use the same class to load anything from the `data/`
directory.
2026-01-26 20:28:29 +01:00
Henrique Moody
819d734a00
Check for mismatches in the mixin classes
When we change the contract of a validator, or create a new one, we need to
ensure that the mixin for the validator is present and matches the validator's
constructor.

This commit changes the current class that generates those mixin classes,
converting it into a linter so we can run it in the GitHub workflow to check for
missing changes.
2026-01-26 20:14:09 +01:00