Skip to content

Create "Patterned" validator#1654

Closed
henriquemoody wants to merge 1 commit intoRespect:mainfrom
henriquemoody:validator/patterned
Closed

Create "Patterned" validator#1654
henriquemoody wants to merge 1 commit intoRespect:mainfrom
henriquemoody:validator/patterned

Conversation

@henriquemoody
Copy link
Member

The Patterned validator formats input values using a pattern before displaying them in error messages, while still validating the original unformatted input. This is the complement to the Masked validator:

  • Masked: Hides sensitive parts of the input (e.g., "4111****1111")
  • Patterned: Adds formatting to raw input (e.g., "4111 1111 1111 1111")

This validator solves a common UX problem: when users submit data without formatting characters (like spaces or punctuation), error messages show the raw input, which can be harder to read and verify. For example:

  • Credit card "4111111111111112" → "4111 1111 1111 1112"
  • CPF "12345678900" → "123.456.789-00"
  • Phone "11987654321" → "(11) 98765-4321"

The validator is particularly useful in scenarios where:

  1. Form inputs strip formatting characters before submission
  2. Data comes from external systems without formatting
  3. Users need to visually verify the value they entered
  4. Error messages should match the expected display format

It uses the PatternFormatter from respect/string-formatter, which supports digit placeholders (0), quantifiers ({n}), and transformations (\u for uppercase, \l for lowercase, etc.).

Assisted-by: Claude Code (Opus 4.5)

@codecov
Copy link

codecov bot commented Jan 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.30%. Comparing base (b352f17) to head (cd34498).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##               main    #1654   +/-   ##
=========================================
  Coverage     99.29%   99.30%           
- Complexity      920      924    +4     
=========================================
  Files           190      191    +1     
  Lines          2141     2151   +10     
=========================================
+ Hits           2126     2136   +10     
  Misses           15       15           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new Patterned validator that formats input values using patterns in error messages while still validating the original unformatted input. This complements the existing Masked validator by adding formatting instead of hiding sensitive data.

Changes:

  • Adds Patterned validator that applies pattern-based formatting to input values in error messages
  • Integrates the new validator into all mixin interfaces (Chain, Builder, and their variants)
  • Provides comprehensive test coverage including unit tests, feature tests, and smoke tests

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Validators/Patterned.php Implements the core Patterned validator using PatternFormatter from respect/string-formatter
tests/unit/Validators/PatternedTest.php Unit tests covering invalid patterns, non-string inputs, and formatting behavior
tests/feature/Validators/PatternedTest.php Feature tests for error message formatting with real validators (CPF)
tests/src/SmokeTestProvider.php Adds smoke test case demonstrating uppercase transformation
src/Mixins/*.php Updates all 12 mixin interfaces to include patterned() methods
docs/validators/Patterned.md Comprehensive documentation with examples, behavior description, and categorization
docs/validators.md Adds Patterned to Display and Miscellaneous categories and alphabetical list
docs/migrating-from-v2-to-v3.md Documents Patterned as a new v3.0 validator with examples

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 723 to 727
v::patterned('0{3}.0{3}.0{3}-0{2}', v::cpf())->assert('12345678900');
// → "123.456.789-00" must be a valid CPF number

v::patterned('(0{2}) 0{5}-0{4}', v::phone())->assert('11987654321');
// → "(11) 98765-4321" must be a valid telephone number
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if we could also document some common number patterns:

Thousands separation: 1000000 -> 1.000.000
Comma separation: 10.00 => 10,00

I think those are by far the most popular use cases for adding patterns to some display value.

The Patterned validator formats input values using a pattern before
displaying them in error messages, while still validating the original
unformatted input. This is the complement to the Masked validator:

- Masked: Hides sensitive parts of the input (e.g., "4111****1111")
- Patterned: Adds formatting to raw input (e.g., "4111 1111 1111 1111")

This validator solves a common UX problem: when users submit data without
formatting characters (like spaces or punctuation), error messages show
the raw input, which can be harder to read and verify. For example:

  - Credit card "4111111111111112" → "4111 1111 1111 1112"
  - CPF "12345678900" → "123.456.789-00"
  - Phone "11987654321" → "(11) 98765-4321"

The validator is particularly useful in scenarios where:

1. Form inputs strip formatting characters before submission
2. Data comes from external systems without formatting
3. Users need to visually verify the value they entered
4. Error messages should match the expected display format

It uses the PatternFormatter from respect/string-formatter, which supports
digit placeholders (0), quantifiers ({n}), and transformations (\u for
uppercase, \l for lowercase, etc.).

Assisted-by: Claude Code (Opus 4.5)
* SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
*/

declare(strict_types=1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add at least one test that showcases the behavior when using this together with named() and templated()?

This is also true for the Masked #1652 (already closed, I have not noticed back then) and the Formatted #1657 PRs.

Comment on lines +730 to +731
v::patterned('$00.00', v::phone())->assert(1297);
// → "$12.97" must be a valid telephone number
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is misleading. The use of $ implies a monterary value, but the validator used is a phone number.

Suggestion: Keep the phone number, and instead add examples for the most popular use cases:

  • Thousands separator
  • Decimals separator
  • Dates

@henriquemoody
Copy link
Member Author

henriquemoody commented Feb 1, 2026

Noted the comments, but I will close this one in favor of #1657 with some aliases.

I will implemente your comments there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants