Commit graph

2,221 commits

Author SHA1 Message Date
Henrique Moody
cd6bcd470b
Enable adding modifiers without changing InterpolationRenderer
The `InterpolationRenderer` was violating the open-closed principle,
because every time we would want to add a new modifier, we would need to
change its implementation.

This commit changes that behaviour by creating a `Modifier` interface.
The classes implementing that interface are using a chain of
responsibility to pass the data to the next one. Using a chain of
responsibility makes a lot of sense, since it's only possible to have
one modifier at a time.
2025-12-30 11:17:11 +01:00
Henrique Moody
562d98d805
Refactor the NotEmpty rule
Since we have the ability to use `not` as a prefix, having rules that
validate negative behaviour makes them a bit inflexible, verbose, and
harder to understand.

This commit will refactor the `NotEmpty`, and rename it to `Falsy`. It
will no longer trim strings, because Blank does a much better job at it;
it only simulates the behaviour of PHP’s native `empty()` function.

Because `Falsy`, `Blank`, and `Undef` have similar behaviour, I created
a page to demonstrate the difference and show when the user should use
one or the other.

Assisted-by: Cursor (claude-4.5-opus-high)
2025-12-29 12:48:35 +01:00
Henrique Moody
951c16e8e6
Refactor the NotEmoji rule
Since we have the ability to use `not` as a prefix, having rules that
validate negative behaviour makes them a bit inflexible, verbose, and
harder to understand.

This commit will refactor the `NotEmoji` and rename it to `Emoji`. It
will no longer check if the string contains emojis, but rather if the
string is an emoji or not. I’m also adding support to more emojis, since
the rule was a bit outdated.

This change will make the validator more strict, but will make it useful
in other scenarios. However, later on, I would like to create a rule
called `has` which, could use a validator like `Emoji` to check if the
input _has_ emojis.

Assisted-by: Cursor (claude-4.5-opus-high)
2025-12-29 11:16:25 +01:00
Henrique Moody
a37cac7142
Invert the behaviour of NoWhitespace
Since we have the ability to use `not` as a prefix, having rules that
validate negative behavior makes them a bit inflexible, verbose, and
harder to understand.

This commit will refactor the `NoWhitespace` rule by inverting its
behaviour and renaming it to `Spaced`. Although this is a breaking
change, users will still be able to have a similar behavior with the
prefix `not` + `Spaced`.
2025-12-29 09:11:01 +01:00
Henrique Moody
7046560f77
Invert the behaviour of NotUndef
Since we have the ability to use `not` as a prefix, having rules that
start with not becomes a bit inflexible, verbose, and harder to
understand.

This commit will refactor the `NotUndef` rule by inverting its behaviour
and renaming it to `Undef`.
2025-12-27 18:49:46 +01:00
Henrique Moody
338c186803
Invert the behaviour of NotBlank
Since we have the ability to use `not` as a prefix, having rules that
start with not becomes a bit inflexible, verbose, and harder to
understand.

This commit will refactor the `NotBlank` rule by inverting its behaviour
and renaming it to `Blank`. Although this is a breaking change, users
will not feel it because "NotBlank" will still be available by using the
`not` prefix followed by the `Blank` rule.
2025-12-27 17:18:30 +01:00
Henrique Moody
8e77021ce7
Reformat documentation files
There was some inconsistent Markdown, and some links were out of sync.
2025-12-27 17:03:25 +01:00
Henrique Moody
7636944ea6
Register v as a class alias to Validator
This is a pattern that we already use everywhere, so it makes sense to
simply turn `v` into a class alias to `Validator`, which will allow
people to use all the examples in the documentation without needing to
import the namespace.

We already do that when we run tests, so I just changed it to be a
global setting.
2025-12-27 00:08:35 +01:00
Henrique Moody
94ddfcd0bd
Create named constructor to create Result
The constructor of `Result` has many arguments, but that's not the
primary reason why I'm making this change. I want to change the
constructor, and it will become more complicated, so having this named
constructor will be useful in the next refactoring.

With this change, I also made the `id` mandatory. That made the
constructor look neater and most to promote almost all properties to the
constructor.

Another change was removing the `fromAdjacent` method, which was quite
confusing. I created the `asAdjacentOf` method, which is a bit clearer.
If anything, it makes all static methods named constructors. It will be
a bit more verbose, but more intuitive.
2025-12-26 22:34:43 +01:00
Henrique Moody
48405271c5
Replace placeholder "name" with "subject"
The `{{name}}` placeholder could represent different things depending on
the state of the Result, and referring to it as `{{name}}` seems
arbitrary. This commit changes it to `{{subject}}`, which is much more
generic and it describes well what that placeholder can mean.
2025-12-26 21:30:01 +01:00
Henrique Moody
8332d28acc
Do not overwrite existing names
This commit addresses the skipped tests by modifying certain core
concepts in the library. The way names work has always been somewhat
confusing, even to me. I’ve established that existing names will never
be overwritten, and if a path is already defined, we will change the
name to also include the path.

I’m not very happy with how I’m solving this problem, because the
`FirstResultStringFormatter` is changing some behaviour in the results
that seems to be something that should always happen before. But, for
the moment, I will keep it as is.
2025-12-26 14:48:23 +01:00
Henrique Moody
0d4a5fe2be
Enable validating private properties of parent classes
All validators related to object properties only consider the properties
of the object being validated, not those of its parent object. That's
because PHP reflection only gets the properties visible to the current
class (public or protected). The problem with that is that when there's
a private property in the parent class, we're completely unaware of it.

This commit will modify those rules by retrieving properties from the
parent class, ensuring we capture all properties that require
validation.
2025-12-23 16:44:04 +01:00
Henrique Moody
16c2a75d00
Enable validating inputs without throwing exceptions
Currently, the only way to handle when a validation fails is by
`assert()`, which either throws an exception or doesn't. That means that
every time a user wants to handle the results, they must use try-catch
blocks, which may add some overhead.

This commit introduces the `ResultQuery` class that wraps a `Result`
object, providing an alternative to exception-based validation. This
allows users to handle results directly without try-catch blocks.

I’m taking a risky move here using the old method `validate()`, but I
can’t think of a better name for this method.
2025-12-22 20:37:11 +01:00
Henrique Moody
5b00d69766
Update how we handle templates
Currently, the templates that a user provides when running `assert()`
can significantly impact how the message is displayed. Because of this,
the formatters become complex as they all need to handle similar
conditions to format results.

This commit changes this behaviour, letting only the
`InterpolationRenderer` handle the templates. This makes the code
simpler and allows people to use the `InterpolationRenderer` directly,
without needing to figure out how to handle templates. Thinking about it
further, I believe handling templates is a concern for the `Renderer`
anyway, and this will open the way to other improvements using the
renderer.

I also removed the exception that is thrown when the template is not a
string, because I think that after validation has failed, we should not
throw any other exceptions, as that could cause unexpected errors for
users.
2025-12-22 14:05:55 +01:00
Henrique Moody
644ecb5190
Change the contract of the formatters
Both `ArrayFormatter` and `StringFormatter` accept an instance of the
`Translator`. Thinking about it a bit better, I realised that a
formatter might not always need a `Translator`, but it will surely need
a `Renderer`.

Besides, the `InterpolationRenderer` needs to take translation into
account, so it seems more natural to me that this is the one that will
get an instance of the `Translator`, as other implementations of the
`Renderer` might not even deal with translations.
2025-12-22 13:43:59 +01:00
Henrique Moody
fd7438f9c7
Use a DI container to create instances of Validator
The `ValidatorDefaults` is cumbersome, and customising it can be
annoying. Most projects use some sort of dependency injection container,
and by integrating the creation of the `Validator` with the PSR-11, we
allow users to easily customise how they create validators.

Some tasks, like overwriting the `Translator`, become a bit more
verbose, if the user is not already using a PSR-11 container, but I
think that’s a good tradeoff.
2025-12-22 13:12:04 +01:00
Henrique Moody
bf9b970e24
Move important value objects to the root namespace
The `Id`, `Name`, and `Path` value objects are not only message-related
concerns, they're part of the core of the library, hence it makes sense
to place them at the root namespace.
2025-12-21 11:14:47 +01:00
Henrique Moody
137c74c5b3
Change how we trace the path of results
Currently, we’re using scalar values to trace paths. The problem with
that approach is that we can’t create a reliable hierarchy with them, as
we can’t know for sure when a path is the same for different rules. By
using an object, we can easily compare and create a parent-child
relationship with it.

While making these changes, I deemed it necessary to also create objects
to handle Name and Id, which makes the code simpler and more robust. By
having Name and Path, we can create specific stringifiers that allow us
to customise how we render those values.

I didn’t manage to make those changes atomically, which is why this
commit makes so many changes. I found myself moving back and forth, and
making all those changes at once was the best solution I found.
2025-12-20 22:19:17 +01:00
Henrique Moody
c0b8baa579
Rename StandardStringifier to ValidationStringifier
The name `StandardStringifier` is ambiguous because it doesn't describe
how that stringifier differs from others. Ideally, we'd have a better
name for it, something that makes its behaviour explicit, but since this
is the stringifier from Validation, naming it as such will make it
easier for users to use it if they want.
2025-12-19 17:21:57 +01:00
Henrique Moody
28390400df
Rename StandardRenderer to InterpolationRenderer
The name `StandardRenderer` is ambiguous because it doesn't describe the
class's behavior, only that it is the "standard" or default
implementation. This follows the same reasoning as the recent
refactoring of `StandardFormatter`.

The renderer's primary function is to process a message template by
replacing placeholders (like `{{name}}`) with their corresponding
values. This process is known as string interpolation.

To better reflect this responsibility, the class has been renamed to
`InterpolationRenderer`.

This new name makes the class's purpose immediately clear, which
improves the overall clarity and maintainability of the rendering
system. It also helps to establish a clearer naming convention for any
future renderers (e.g., `JsonRenderer`, `HtmlRenderer`).
2025-12-19 17:00:34 +01:00
Henrique Moody
423172985b
Remove unused argument from Renderer::render()
Apparently, we were not using the argument anywhere, but I didn't notice
that before.
2025-12-19 17:00:31 +01:00
Henrique Moody
82e0722443
Split the Formatter into different formatters
I've noticed that the `StandardFormatter` was quite bloated, which made
it difficult to maintain. Understanding what each method was doing was
quite complicated. Besides, the name "Standard" doesn't mean anything,
because it doesn't say what the implementation does.

I split the `Formatter` into two different interfaces: `StringFormatter`
and `ArrayFormatter`, and I moved some code around:

*  `StandardFormatter::main()` -> `FirstResultStringFormatter`
* `StandardFormatter::full()` -> `NestedListStringFormatter`
* `StandardFormatter::array()` -> `NestedArrayFormatter`

That opens up new ways of handling error messages, potentially
introducing features like `JsonStringFormatter` or `FlatArrayFormatter`
in the future.

While working on this, I removed a significant amount of unnecessary
code, which also improved my overall understanding of those formatters.

I'm not very happy with all the methods in `ValidatorDefaults`, but I
will refactor that later.
2025-12-19 16:20:28 +01:00
Henrique Moody
cfeb01e89e
Bump respect/coding-standard from 4 to 5 2025-12-18 19:03:39 +01:00
Henrique Moody
7f66bcea10
Bump PHP support from 8.1 to 8.5
We want to release version 3.0 as fresh as possible, without having to
maintain backward compatibility with the previous versions. Because that
version will be on for some time, we decided it will be best to support
only PHP version 8.5 or higher.

Acked-by: Alexandre Gomes Gaigalas <alganet@gmail.com>
2025-12-18 19:03:38 +01:00
Henrique Moody
e2b0185acf
Bump symfony/polyfill-mbstring from 1.28 to 1.33 2025-12-18 18:44:38 +01:00
Henrique Moody
0066786fa7
Remove deprecated methods check() and validate()
We want to release version 3.0 as fresh as possible, without having to
maintain backward compatibility with the previous versions.

Acked-by: Alexandre Gomes Gaigalas <alganet@gmail.com>
2025-12-18 17:29:02 +01:00
Henrique Moody
e56836c4f1
Remove deprecated rules
We want to release version 3.0 as fresh as possible, without having to
maintain backward compatibility with the previous versions. Apart from
that, for some rules, it's impossible to be compatible with older
versions.

Acked-by: Alexandre Gomes Gaigalas <alganet@gmail.com>
2025-12-18 17:22:51 +01:00
Henrique Moody
acf791cc9e
Remove "uopz" as a dependency to run tests
I'm creating a workaround here, which is not exactly happy with, but
adding "uopz" as a dependency to run the tests make it harder for
developers, including myself.
2025-12-18 14:13:09 +01:00
Henrique Moody
d3239e878d
Change how we're writing Pest tests
The problem with the current approach is that the "expect()" calls
happen inside "tests/Pest.php". That means that when something fails, we
can't easily know which exact expectation has failed.

This commit will change the helper functions, and will make the tests
more verbose, but event with that, the developer experience is better.
2025-12-18 14:02:33 +01:00
steven.lewis
2b364764f7 Extend UUID rule support to versions 1 through 8 2025-05-27 14:54:20 +01:00
Henrique Moody
cf0052b188
Fix some errors detected by PHPStan
This also made me realise that we had a bug in that rule.
2025-05-18 14:15:41 +02:00
Fabio Ribeiro
0e8ac1817b
Rename Result->isValid method 2025-03-31 21:29:37 +02:00
Henrique Moody
901774f6b7
Improve naming and delete unnecessary Mode
I don't expect us to have more modes, hence a simple boolean value
should be enough for indicating the mode of the teamplate. Apart from
that, the name "inverted" woudln't always make sense, because if you
invert something that is inverted, it gets back to its original mode.

This commit will remove the `Mode` enum, and also improve the naming of
some methods in the `Result`.
2025-01-17 10:51:18 +01:00
Henrique Moody
c096fb0f63
Improve naming of transformers for deprecations
With those changes, the short-name will be shorter and more descriptive.
2025-01-16 11:14:57 +01:00
Henrique Moody
94d53df8ea
Allow to use certain rules as class attributes
There are a few cases in which we want to validate the object as a
whole, and that validation could be attached to the class as a PHP
attribute. This commit enables that capability and changes a few rules
to be class attributes.
2025-01-16 10:27:16 +01:00
Henrique Moody
848724e451
Create transformer for deprecated composite rules
This transformers will help transition from the current major version to
the next. In the current version, it's possible to call `allOf`,
`anyOf`, `noneOf`, and `oneOf` without any arguments or with only one,
and that doesn't make much sense.
2025-01-07 15:01:58 +01:00
Henrique Moody
1d6d005838
Make Simple::isValid() public
There's no reason not to make this method public. It will actually be
easier for users to test their rules when they extend this class if this
method is public.
2025-01-07 14:19:09 +01:00
Henrique Moody
1915b6fff7
Use paths to identify when a rule fails
When nested-structural validation fails, it's challenging to identify
which rule failed from the main exception message. A great example is
the `Issue796Test.php` file. The exception message says:

host must be a string

But you're left unsure whether it's the `host` key from the `mysql` key
or the `postgresql` key.

This commit changes that behaviour by introducing the concept of "Path."
The `path` represents the path that a rule has taken, and we can use it
in structural rules to identify the path of an array or object.

Here's what it looks like before and after:

```diff
-host must be a string
+`.mysql.host` must be a string
```

Because paths are a specific concept, I added a dot (`.`) at the
beginning of all paths when displaying them. I was inspired by the `jq`
syntax. I also added backticks around paths to distinguish them from any
other value.

I didn't manage to fix a test, and I skipped it instead of fixing it
because I want to make changes in how we display error messages as
arrays, and it will be easier to fix it then.
2024-12-27 23:28:35 +01:00
Henrique Moody
a0d6355980
Update some templates and improve tests
Some templates were a bit confusing, and I would like to favour adding
the `{{name}}` at the beginning of the templates as it helps when
reading nested messages.

I also deleted the regression tests for issue #1348, because it's a
non-issue, actually. The best approach to that problem is indeed using
`When` insteaf of `OneOf`.
2024-12-27 15:55:55 +01:00
Henrique Moody
a8ae57fdef
Create a stringifier for "listed" values
This can be extremely useful when dealing with custom templates.
2024-12-27 04:29:00 +01:00
Henrique Moody
4379f66bec
Create a stringifier for "quoted" values
The `StandardQuoter` adds backticks around strings, which indicates that
it's not a simple string but a code. With this stringifier, we can add
quotes to placeholders directly into templates.
2024-12-27 00:49:12 +01:00
Henrique Moody
b8f494955c
Implement a custom stringifier
The standards `CompositeStringifier` from "respect/stringifier" has lots
of interesting stringifiers. However, this library is not 100% focused
on engineers. Someone could type a string that matches a callable, and
then you will overexpose the system.

This commit makes sure that callables are not interpreted as callables.
2024-12-27 00:15:59 +01:00
Henrique Moody
a3c197f600
Handle names via the Named rule
Because of how the validation engine works now [1], there's no reason to
keep adding names to each rule. Instead, create a single rule that
handles naming rules with a few other accessories. This change is not
necessarily simple, but it shrinks the `Rule` interface, and it's more
aligned with how the library works right now.

Personally, I think this API is much more straightforward than the
`setName()` method, as it's way more explicit about which rule we're
naming. Because of this change, the behaviour changed slightly, but it's
for the best.

Because of this change, I managed to remove a lot of code, but
unfortunately, it's quite a big-bang commit. It would be too complicated
to make it atomic since names are an intrinsic part of the library.

[1]: 238f2d506a
2024-12-26 23:10:19 +01:00
Henrique Moody
9dac855c9e
Customize overwriting file and line in ValidationException
I've already changed the `ValidationException` so as not to let the file
and line from the Validator.php [1]. However, one could go even further
when creating more customizations on top of this library, and allowing
to customize the line could be very useful.

What motivated me making this change because it will be handy when I get
back to work on [Assertion][].

[1]: 75a9b8e94f
[Assertion]: https://github.com/Respect/Assertion
2024-12-26 20:37:54 +01:00
Henrique Moody
1d1da7f099
Create "Templated" rule
Because of how the validation engine works, there's no reason to keep
adding templates to each rule. Instead, creating a single rule that
handles templating rules will simplify the library greatly and shrink
the `Rule` interface.

Personally, I think this API is much more straightforward than the
`setTemplate()` method, as it's way more explicit which rule is being
templated.
2024-12-26 14:40:09 +01:00
Henrique Moody
634a155c63
Always display the input
The "NotBlank", "NotEmpty", and "NotUndef" rules do not display the
input in all cases and instead displays the string "The value". The
problem with that is that one doesn't see which value was passed, which
is not so useful.

This commit will changes those rules to always display the input. If
someone doesn't want the input to show, they can always set a name for
the rule.
2024-12-22 06:55:21 +01:00
Henrique Moody
aa633db46a
Improve format of Pest files
I added some trailing commas to the files.
2024-12-22 06:53:36 +01:00
Henrique Moody
b3541616f7
Rename withPrefixedId to withPrefix
That makes the name shorter and it has the same meaning.
2024-12-21 14:15:23 +01:00
Henrique Moody
33e6341415
Set the IDs of of results of Each as the array keys
This will make the messages easier to understand and map than they are
described now.
2024-12-20 18:01:25 +01:00
Henrique Moody
18d8246fa5
Remove duplication and improve naming of adjacent results
I identified a pattern among rules that create results with adjacent
results, so I created a method that abstracts that. I did have to
compromise with the DateTimeDiff, having to escape the input instead of
using the name itself, but that seems like a good trade-off.

I've also renamed "Subsequent" to "Adjacent" because it sounded better.
This is the second time I've renamed this concept, and I hope it will be
the last.
2024-12-20 17:12:11 +01:00