diff --git a/readme.md b/readme.md index 2191367..fdaf648 100644 --- a/readme.md +++ b/readme.md @@ -311,3 +311,18 @@ $schema = Expect::arrayOf('string') $processor->process($schema, ['a', 'b']); // it passes, 2 is even number $processor->process($schema, ['a', 'b', 'c']); // error, 3 is not even number ``` + +You can add custom description for every assert. This description will be part of error message. + +```php +$schema = Expect::arrayOf('string') + ->assert(function ($v) { return count($v) % 2 === 0; }, 'Even items in array'); // count must be even number + +$processor->process($schema, ['a', 'b', 'c']); // Failed assertion "Even items in array" for option with value array. +``` + +PHP build in functions can be used too: + +```php +$schema = Expect::string()->assert('is_file'); +``` diff --git a/src/Schema/Elements/Base.php b/src/Schema/Elements/Base.php index a02b09a..0cec71c 100644 --- a/src/Schema/Elements/Base.php +++ b/src/Schema/Elements/Base.php @@ -62,9 +62,13 @@ public function castTo(string $type): self } - public function assert(callable $handler): self + public function assert(callable $handler, ?string $description = null): self { - $this->asserts[] = $handler; + if ($description === null) { + $this->asserts[] = $handler; + } else { + $this->asserts[$description] = $handler; + } return $this; } @@ -112,7 +116,7 @@ private function doFinalize($value, Context $context) foreach ($this->asserts as $i => $assert) { if (!$assert($value)) { - $expected = is_string($assert) ? "$assert()" : "#$i"; + $expected = !is_numeric($i) ? "\"$i\"" : (is_string($assert) ? "$assert()" : "#$i"); $context->addError("Failed assertion $expected for option %path% with value " . static::formatValue($value) . '.'); return; } diff --git a/tests/Schema/Expect.assert.phpt b/tests/Schema/Expect.assert.phpt index 7d419bc..5679daa 100644 --- a/tests/Schema/Expect.assert.phpt +++ b/tests/Schema/Expect.assert.phpt @@ -20,6 +20,15 @@ test(function () { // single assertion Assert::same(__FILE__, (new Processor)->process($schema, __FILE__)); }); +test(function () { // single assertion with custom description + $schema = Expect::string()->assert('is_file', 'File exists'); + + checkValidationErrors(function () use ($schema) { + (new Processor)->process($schema, 'hello'); + }, ["Failed assertion \"File exists\" for option with value 'hello'."]); + + Assert::same(__FILE__, (new Processor)->process($schema, __FILE__)); +}); test(function () { // multiple assertions $schema = Expect::string()->assert('ctype_digit')->assert(function ($s) { return strlen($s) >= 3; }); @@ -34,3 +43,19 @@ test(function () { // multiple assertions Assert::same('123', (new Processor)->process($schema, '123')); }); + +test(function () { // multiple assertions with custom description + $schema = Expect::string() + ->assert('ctype_digit', 'Is number') + ->assert(function ($s) { return strlen($s) >= 3; }, 'Minimal lenght'); + + checkValidationErrors(function () use ($schema) { + (new Processor)->process($schema, ''); + }, ["Failed assertion \"Is number\" for option with value ''."]); + + checkValidationErrors(function () use ($schema) { + (new Processor)->process($schema, '1'); + }, ["Failed assertion \"Minimal lenght\" for option with value '1'."]); + + Assert::same('123', (new Processor)->process($schema, '123')); +});