Skip to content

Commit

Permalink
Create "KeyExists" rule
Browse files Browse the repository at this point in the history
Signed-off-by: Henrique Moody <[email protected]>
  • Loading branch information
henriquemoody committed Feb 22, 2024
1 parent f1b6f0c commit e99d33f
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 0 deletions.
3 changes: 3 additions & 0 deletions docs/08-list-of-rules-by-category.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [EndsWith](rules/EndsWith.md)
- [In](rules/In.md)
- [Key](rules/Key.md)
- [KeyExists](rules/KeyExists.md)
- [KeyNested](rules/KeyNested.md)
- [KeySet](rules/KeySet.md)
- [Sorted](rules/Sorted.md)
Expand Down Expand Up @@ -240,6 +241,7 @@
## Structures

- [Key](rules/Key.md)
- [KeyExists](rules/KeyExists.md)
- [KeyNested](rules/KeyNested.md)
- [KeySet](rules/KeySet.md)
- [Property](rules/Property.md)
Expand Down Expand Up @@ -337,6 +339,7 @@
- [IterableType](rules/IterableType.md)
- [Json](rules/Json.md)
- [Key](rules/Key.md)
- [KeyExists](rules/KeyExists.md)
- [KeyNested](rules/KeyNested.md)
- [KeySet](rules/KeySet.md)
- [LanguageCode](rules/LanguageCode.md)
Expand Down
1 change: 1 addition & 0 deletions docs/rules/ArrayType.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ See also:
- [FloatType](FloatType.md)
- [IntType](IntType.md)
- [IterableType](IterableType.md)
- [KeyExists](KeyExists.md)
- [NullType](NullType.md)
- [ObjectType](ObjectType.md)
- [ResourceType](ResourceType.md)
Expand Down
1 change: 1 addition & 0 deletions docs/rules/ArrayVal.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ See also:
- [Each](Each.md)
- [IterableType](IterableType.md)
- [Key](Key.md)
- [KeyExists](KeyExists.md)
- [KeySet](KeySet.md)
- [ScalarVal](ScalarVal.md)
- [Sorted](Sorted.md)
Expand Down
1 change: 1 addition & 0 deletions docs/rules/Each.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ See also:
- [Call](Call.md)
- [IterableType](IterableType.md)
- [Key](Key.md)
- [KeyExists](KeyExists.md)
- [NotEmpty](NotEmpty.md)
- [Unique](Unique.md)
1 change: 1 addition & 0 deletions docs/rules/Key.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ See also:

- [ArrayVal](ArrayVal.md)
- [Each](Each.md)
- [KeyExists](KeyExists.md)
- [KeyNested](KeyNested.md)
- [KeySet](KeySet.md)
- [Property](Property.md)
39 changes: 39 additions & 0 deletions docs/rules/KeyExists.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# KeyExists

- `KeyExists(int|string $key)`

Validates if the given key exists in an [array][] or [ArrayAccess][].

```php
v::keyExists('name')->validate(['name' => 'The Respect Panda']); // true
v::keyExists('name')->validate(['email' => '[email protected]']); // false

v::keyExists(0)->validate(['a', 'b', 'c']); // true
v::keyExists(4)->validate(['a', 'b', 'c']); // false

v::keyExists('username')->validate(new ArrayObject(['username' => 'therespectpanda'])); // true
v::keyExists(5)->validate(new ArrayObject(['a', 'b', 'c'])); // false
```

## Categorization

- Arrays
- Structures

## Changelog

| Version | Description |
| ------: | ------------------ |
| 3.0.0 | Created from `Key` |

***
See also:

- [ArrayType](ArrayType.md)
- [ArrayVal](ArrayVal.md)
- [Each](Each.md)
- [Key](Key.md)
- [Property](Property.md)

[array]: https://www.php.net/array
[ArrayAccess]: https://www.php.net/arrayaccess
1 change: 1 addition & 0 deletions docs/rules/Property.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ Version | Description
See also:

- [Key](Key.md)
- [KeyExists](KeyExists.md)
- [KeyNested](KeyNested.md)
- [ObjectType](ObjectType.md)
2 changes: 2 additions & 0 deletions library/ChainedValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ public function iterableType(): ChainedValidator;

public function json(): ChainedValidator;

public function keyExists(int|string $key): ChainedValidator;

public function key(
string $reference,
?Validatable $referenceValidator = null,
Expand Down
38 changes: 38 additions & 0 deletions library/Rules/KeyExists.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* SPDX-License-Identifier: MIT
*/

declare(strict_types=1);

namespace Respect\Validation\Rules;

use ArrayAccess;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;

use function array_key_exists;

#[Template(
'Key {{name}} must be present',
'Key {{name}} must not be present',
)]
final class KeyExists extends Standard
{
public function __construct(
private readonly int|string $key
) {
}

public function evaluate(mixed $input): Result
{
return new Result($this->hasKey($input), $input, $this, name: (string) $this->key);
}

private function hasKey(mixed $input): bool
{
return $input instanceof ArrayAccess ? $input->offsetExists($this->key) : array_key_exists($this->key, $input);
}
}
2 changes: 2 additions & 0 deletions library/StaticValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ public static function iterableType(): ChainedValidator;

public static function json(): ChainedValidator;

public static function keyExists(int|string $key): ChainedValidator;

public static function key(
string $reference,
?Validatable $referenceValidator = null,
Expand Down
49 changes: 49 additions & 0 deletions tests/integration/rules/keyExists.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
--FILE--
<?php

declare(strict_types=1);

require 'vendor/autoload.php';

use Respect\Validation\Validator as v;

run([
'Default mode' => [v::keyExists('foo'), ['bar' => 'baz']],
'Negative mode' => [v::not(v::keyExists('foo')), ['foo' => 'baz']],
'Custom name' => [v::keyExists('foo')->setName('Custom name'), ['bar' => 'baz']],
'Custom template' => [v::keyExists('foo'), ['bar' => 'baz'], 'Custom template for `{{name}}`'],
]);
?>
--EXPECT--
Default mode
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
Key foo must be present
- Key foo must be present
[
'foo' => 'Key foo must be present',
]

Negative mode
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
Key foo must not be present
- Key foo must not be present
[
'foo' => 'Key foo must not be present',
]

Custom name
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
Key Custom name must be present
- Key Custom name must be present
[
'Custom name' => 'Key Custom name must be present',
]

Custom template
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
Custom template for `foo`
- Custom template for `foo`
[
'foo' => 'Custom template for `foo`',
]

15 changes: 15 additions & 0 deletions tests/library/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,19 @@ public static function providerForFloatValues(): array
'positive float' => [32.890],
];
}

/** @return array<array{mixed}> */
public static function providerForNonArrayValues(): array
{
$scalarValues = self::providerForNonScalarValues();
unset($scalarValues['array']);

return array_merge(
self::providerForIntegerValues(),
self::providerForBooleanValues(),
self::providerForFloatValues(),
self::providerForStringValues(),
$scalarValues,
);
}
}
43 changes: 43 additions & 0 deletions tests/unit/Rules/KeyExistsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* SPDX-License-Identifier: MIT
*/

declare(strict_types=1);

namespace Respect\Validation\Rules;

use ArrayObject;
use PHPUnit\Framework\Attributes\CoversClass;
use Respect\Validation\Test\RuleTestCase;

use function array_map;

#[CoversClass(KeyExists::class)]
final class KeyExistsTest extends RuleTestCase
{
/** @return array<string, array{KeyExists, mixed}> */
public static function providerForValidInput(): array
{
return [
'string key from array' => [new KeyExists('foo'), ['foo' => 'bar']],
'int key from array' => [new KeyExists(0), [0 => 'bar']],
'string key from ArrayAccess' => [new KeyExists('foo'), ['foo' => 'bar']],
'int key from ArrayAccess' => [new KeyExists(0),new ArrayObject([0 => 'bar'])],
];
}

/** @return array<string, array{KeyExists, mixed}> */
public static function providerForInvalidInput(): array
{
return [
'missing string key' => [new KeyExists('foo'), []],
'missing int key' => [new KeyExists(0), []],
] + array_map(
static fn(mixed $input) => [new KeyExists('bar'), $input],
self::providerForNonArrayValues()
);
}
}

0 comments on commit e99d33f

Please sign in to comment.