Skip to content

Commit

Permalink
Merge pull request #23 from chadicus/uuid-filter
Browse files Browse the repository at this point in the history
Add filter for UUID values
  • Loading branch information
chadicus authored Nov 15, 2024
2 parents b666aeb + 140f12a commit f6e6fbf
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 1 deletion.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,38 @@ $value = '{ "string": "value", "array": [1, 2, 3] }';
assert($value === ['string' => 'value', 'array' => [1, 2, 3]]);
```

#### UuidFilter::filter

This filter verifies a given string is a valid universally unique identifier.

The second parameter can be set to `true` to allow null values through without an error.

The third parameter can be set to `true` to allow [Nil UUIDs](https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.7) values through without an error.

The fourth parameter determines which UUID version the value will be validated against. By default, the filter will succeed if the values matches version 1, 2, 3, 4, 5, 6, or 7

```php
// Filtering an UUID string
$value = '1a42403c-a29d-11ef-b864-0242ac120002';
$filtered = \TraderInteractive\Filter\UuidFilter::filter($value);
assert($value === $filtered);

// Filtering null values
$value = null;
$filtered = \TraderInteractive\Filter\UuidFilter::filter($value, true);
assert(null === $filtered);

// Filtering a nil UUID
$value = '00000000-0000-0000-0000-000000000000';
$filtered = \TraderInteractive\Filter\UuidFilter::filter($value, false, true);
assert($value === $filtered);

// Filtering for only UUID v4
$value = '1a42403c-a29d-41ef-b864-0242ac120002';
$filtered = \TraderInteractive\Filter\UuidFilter::filter($value, false, false, [4]);
assert($value === $filtered);
```

#### XmlFilter::filter

This filter ensures the given string is valid XML.
Expand Down
121 changes: 121 additions & 0 deletions src/Filter/UuidFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace TraderInteractive\Filter;

use InvalidArgumentException;
use TraderInteractive\Exceptions\FilterException;

final class UuidFilter
{
/**
* @var string
*/
const FILTER_ALIAS = 'uuid';

/**
* @var string
*/
const UUID_PATTERN_FORMAT = '^[0-9A-F]{8}-[0-9A-F]{4}-[%d][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$';

/**
* @var string
*/
const FILTER_ERROR_FORMAT = "Value '%s' is not a valid UUID. Versions checked (%s)";

/**
* @var string
*/
const NIL_NOT_ALLOWED_ERROR_FORMAT = "Value '%s' is nil uuid, but nil values are not allowed.";

/**
* @var string
*/
const NULL_NOT_ALLOWED_ERROR = "Value is null, but null values are not allowed.";

/**
* @var string
*/
const UNSUPPORTED_VERSION_ERROR_FORMAT = 'Filter does not support UUID v%d';

/**
* @var string
*/
const NIL_UUID = '00000000-0000-0000-0000-000000000000';

/**
* @var array
* @internal
*/
const VALID_UUID_VERSIONS = [1,2,3,4,5,6,7];


/**
* Filters a given string values to a valid UUID
*
* @param string|null $value The value to be filtered.
* @param bool $allowNull Flag to allow value to be null.
* @param bool $allowNil Flag to allow value to be a NIL UUID.
* @param array $versions List of specific UUID version to validate against.
*
* @return string|null
*
* @throws FilterException Thrown if value cannot be filtered as an UUID.
*/
public static function filter(
string $value = null,
bool $allowNull = false,
bool $allowNil = false,
array $versions = self::VALID_UUID_VERSIONS
) {
if (self::valueIsNullAndValid($allowNull, $value)) {
return null;
}

if (self::valueIsNilAndValid($allowNil, $value)) {
return self::NIL_UUID;
}

self::validateVersions($versions);
foreach ($versions as $version) {
$pattern = sprintf(self::UUID_PATTERN_FORMAT, $version);
if (preg_match("/{$pattern}/i", $value)) {
return $value;
}
}

throw new FilterException(
sprintf(
self::FILTER_ERROR_FORMAT,
$value,
implode(', ', $versions)
)
);
}

private static function valueIsNullAndValid(bool $allowNull, string $value = null): bool
{
if ($allowNull === false && $value === null) {
throw new FilterException(self::NULL_NOT_ALLOWED_ERROR);
}

return $allowNull === true && $value === null;
}

private static function valueIsNilAndValid(bool $allowNil, string $value = null): bool
{
if ($allowNil === false && $value === self::NIL_UUID) {
throw new FilterException(sprintf(self::NIL_NOT_ALLOWED_ERROR_FORMAT, $value));
}

return $allowNil === true && $value === self::NIL_UUID;
}

private static function validateVersions(array $versions)
{
foreach ($versions as $version) {
if (!in_array($version, self::VALID_UUID_VERSIONS)) {
throw new InvalidArgumentException(sprintf(self::UNSUPPORTED_VERSION_ERROR_FORMAT, $version));
}
}
}
}
2 changes: 1 addition & 1 deletion tests/Filter/PhoneFilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public function filterWithAllowNull()
* @param mixed $value The value to filter.
*
* @test
* @covers ::__invoke
* @covers ::filter
* @dataProvider provideFilterThrowsException
*/
public function filterThrowsException($value)
Expand Down
129 changes: 129 additions & 0 deletions tests/Filter/UuidFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

namespace Filter;

use InvalidArgumentException;
use TraderInteractive\Exceptions\FilterException;
use TraderInteractive\Filter\UuidFilter;
use PHPUnit\Framework\TestCase;

/**
* @coversDefaultClass \TraderInteractive\Filter\UuidFilter
* @covers ::<private>
*/
final class UuidFilterTest extends TestCase
{
/**
* @var string
* @internal
*/
const UUID_V1 = '1a42403c-a29d-11ef-b864-0242ac120002';

/**
* @var string
* @internal
*/
const UUID_V4 = 'cc468b36-0b9d-4c93-b8e9-d5e949331ffb';

/**
* @var string
* @internal
*/
const UUID_V7 = '01932b4a-af2b-7093-af59-2fb2044d13d8';

/**
* @test
* @covers ::filter
*/
public function filterUuidV1()
{
$this->assertSame(self::UUID_V1, UuidFilter::filter(self::UUID_V1));
}

/**
* @test
* @covers ::filter
*/
public function filterUuidV4()
{
$this->assertSame(self::UUID_V4, UuidFilter::filter(self::UUID_V4));
}

/**
* @test
* @covers ::filter
*/
public function filterUuidV7()
{
$this->assertSame(self::UUID_V7, UuidFilter::filter(self::UUID_V7));
}

/**
* @test
* @covers ::filter
*/
public function filterNullAllowedNullIsTrue()
{
$this->assertNull(UuidFilter::filter(null, true));
}

/**
* @test
* @covers ::filter
*/
public function filterNullAllowedNullIsFalse()
{
$this->expectException(FilterException::class);
UuidFilter::filter(null, false);
}

/**
* @test
* @covers ::filter
*/
public function filterWithInvalidVersionSpecified()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(sprintf(UuidFilter::UNSUPPORTED_VERSION_ERROR_FORMAT, 0));
UuidFilter::filter(self::UUID_V7, false, false, [0]);
}

/**
* @test
* @covers ::filter
*/
public function filterValueDoesNotMatchGivenVersions()
{
$this->expectException(FilterException::class);
$this->expectExceptionMessage(
sprintf(
UuidFilter::FILTER_ERROR_FORMAT,
self::UUID_V4,
implode(', ', [1,7])
)
);
UuidFilter::filter(self::UUID_V4, false, false, [1,7]);
}

/**
* @test
* @covers ::filter
*/
public function filterNilUuid()
{
$value = UuidFilter::NIL_UUID;
$this->assertSame($value, UuidFilter::filter($value, false, true));
}

/**
* @test
* @covers ::filter
*/
public function filterNilUuidNilNotAllowed()
{
$value = UuidFilter::NIL_UUID;
$this->expectException(FilterException::class);
$this->expectExceptionMessage(sprintf(UuidFilter::NIL_NOT_ALLOWED_ERROR_FORMAT, $value));
UuidFilter::filter($value, false, false);
}
}

0 comments on commit f6e6fbf

Please sign in to comment.