Skip to content

Commit a2501e6

Browse files
authoredJan 2, 2023
feat(validators-hook): created new package with validation offering (#30)
* feat(validators-hook): created new package with validation offering for OpenFeature hooks Signed-off-by: Tom Carrio <[email protected]> * ci: enable testing for Validators Signed-off-by: Tom Carrio <[email protected]> * ci: enable testing for DDTrace Signed-off-by: Tom Carrio <[email protected]>
1 parent a326c09 commit a2501e6

23 files changed

+675
-0
lines changed
 

‎.github/workflows/php-ci.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ jobs:
1515
php-version: ['7.4', '8.0', '8.1', '8.2']
1616
project-dir:
1717
- hooks/OpenTelemetry
18+
- hooks/DDTrace
19+
- hooks/Validators
1820
- providers/Flagd
1921
- providers/Split
2022
- providers/CloudBees

‎.gitsplit.yml

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ splits:
1010
target: "https://${GH_TOKEN}@github.com/open-feature-php/otel-hook.git"
1111
- prefix: "hooks/DDTrace"
1212
target: "https://${GH_TOKEN}@github.com/open-feature-php/dd-trace-hook.git"
13+
- prefix: "hooks/Validators"
14+
target: "https://${GH_TOKEN}@github.com/open-feature-php/validators-hook.git"
1315
- prefix: "providers/Flagd"
1416
target: "https://${GH_TOKEN}@github.com/open-feature-php/flagd-provider.git"
1517
- prefix: "providers/Split"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
on:
2+
push:
3+
branches:
4+
- main
5+
6+
name: Run Release Please
7+
jobs:
8+
release-please:
9+
runs-on: ubuntu-latest
10+
11+
# Release-please creates a PR that tracks all changes
12+
steps:
13+
- uses: google-github-actions/release-please-action@v3
14+
id: release
15+
with:
16+
command: manifest
17+
token: ${{secrets.GITHUB_TOKEN}}
18+
default-branch: main

‎hooks/Validators/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/composer.lock
2+
/vendor
3+
/build
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
".": "0.0.1"
3+
}

‎hooks/Validators/README.md

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# OpenFeature Validator Hooks
2+
3+
[![a](https://img.shields.io/badge/slack-%40cncf%2Fopenfeature-brightgreen?style=flat&logo=slack)](https://cloud-native.slack.com/archives/C0344AANLA1)
4+
[![Latest Stable Version](http://poser.pugx.org/open-feature/validators-hook/v)](https://packagist.org/packages/open-feature/validators-hook)
5+
[![Total Downloads](http://poser.pugx.org/open-feature/validators-hook/downloads)](https://packagist.org/packages/open-feature/validators-hook)
6+
![PHP 7.4+](https://img.shields.io/badge/php->=7.4-blue.svg)
7+
[![License](http://poser.pugx.org/open-feature/validators-hook/license)](https://packagist.org/packages/open-feature/validators-hook)
8+
9+
## Overview
10+
11+
Validator Hook constructs that provide means to execute validation against resolved feature flag values.
12+
13+
This package also builds on various PSRs (PHP Standards Recommendations) such as the Logger interfaces (PSR-3) and the Basic and Extended Coding Standards (PSR-1 and PSR-12).
14+
15+
## Installation
16+
17+
```
18+
$ composer require open-feature/validators-hook // installs the latest version
19+
```
20+
21+
## Usage
22+
23+
The following validator hook constructs are available, but more are being worked on over time:
24+
25+
- `RegexpValidatorHoook`
26+
27+
28+
```php
29+
use OpenFeature\Hooks\Validators\RegexpValidatorHook;
30+
31+
$alphanumericValidator = new RegexpValidatorHook('/^[A-Za-z0-9]+$/');
32+
$hexadecimalValidator = new RegexpValidatorHook('/^[0-9a-f]+$/');
33+
$asciiValidator = new RegexpValidatorHook('/^[ -~]$/');
34+
35+
// hooks can be applied to the global API, clients, providers, and resolution invocations
36+
37+
// all feature flag resolutions will use this validator
38+
$api = OpenFeatureAPI::getInstance();
39+
$api->addHooks($asciiValidator);
40+
41+
// invocations from this client will use this validator also
42+
$client = $api->getClient('example');
43+
$client->setHooks([$alphanumericValidator]);
44+
45+
// this specific invocation will use this validator also
46+
$client->resolveBooleanValue('test-flag', 'deadbeef', null, new EvaluationOptions([$hexadecimalValidator]));
47+
```
48+
49+
For more examples, see the [examples](./examples/).
50+
51+
## Development
52+
53+
### PHP Versioning
54+
55+
This library targets PHP version 7.4 and newer. As long as you have any compatible version of PHP on your system you should be able to utilize the OpenFeature SDK.
56+
57+
This package also has a `.tool-versions` file for use with PHP version managers like `asdf`.
58+
59+
### Installation and Dependencies
60+
61+
Install dependencies with `composer install`. `composer install` will update the `composer.lock` with the most recent compatible versions.
62+
63+
We value having as few runtime dependencies as possible. The addition of any dependencies requires careful consideration and review.
64+
65+
### Testing
66+
67+
Run tests with `composer run test`.

‎hooks/Validators/composer.json

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
{
2+
"name": "open-feature/validators-hook",
3+
"description": "A validator hooks package for OpenFeature",
4+
"license": "Apache-2.0",
5+
"type": "library",
6+
"keywords": [
7+
"featureflags",
8+
"featureflagging",
9+
"openfeature",
10+
"validator",
11+
"hook"
12+
],
13+
"authors": [
14+
{
15+
"name": "OpenFeature PHP Maintainers",
16+
"homepage": "https://github.com/orgs/open-feature/teams/php-maintainer"
17+
},
18+
{
19+
"name": "open-feature/php-sdk-contrib Contributors",
20+
"homepage": "https://github.com/open-feature/php-sdk-contrib/graphs/contributors"
21+
}
22+
],
23+
"require": {
24+
"php": "^7.4 || ^8",
25+
"open-feature/sdk": "^1.2.0"
26+
},
27+
"require-dev": {
28+
"ergebnis/composer-normalize": "^2.25",
29+
"friendsofphp/php-cs-fixer": "^3.13",
30+
"hamcrest/hamcrest-php": "^2.0",
31+
"mdwheele/zalgo": "^0.3.1",
32+
"mikey179/vfsstream": "v1.6.11",
33+
"mockery/mockery": "^1.5",
34+
"phan/phan": "^5.4",
35+
"php-parallel-lint/php-console-highlighter": "^1.0",
36+
"php-parallel-lint/php-parallel-lint": "^1.3",
37+
"phpstan/extension-installer": "^1.1",
38+
"phpstan/phpstan": "~1.9.0",
39+
"phpstan/phpstan-mockery": "^1.0",
40+
"phpstan/phpstan-phpunit": "^1.1",
41+
"psalm/plugin-mockery": "^0.9.1",
42+
"psalm/plugin-phpunit": "^0.18.0",
43+
"ramsey/coding-standard": "^2.0.3",
44+
"ramsey/composer-repl": "^1.4",
45+
"ramsey/conventional-commits": "^1.3",
46+
"roave/security-advisories": "dev-latest",
47+
"spatie/phpunit-snapshot-assertions": "^4.2",
48+
"vimeo/psalm": "~4.30.0"
49+
},
50+
"minimum-stability": "dev",
51+
"prefer-stable": true,
52+
"autoload": {
53+
"psr-4": {
54+
"OpenFeature\\Hooks\\Validators\\": "src"
55+
}
56+
},
57+
"autoload-dev": {
58+
"psr-4": {
59+
"OpenFeature\\Hooks\\Validators\\Test\\": "tests"
60+
}
61+
},
62+
"config": {
63+
"allow-plugins": {
64+
"phpstan/extension-installer": true,
65+
"dealerdirect/phpcodesniffer-composer-installer": true,
66+
"ergebnis/composer-normalize": true,
67+
"captainhook/plugin-composer": true,
68+
"ramsey/composer-repl": true
69+
},
70+
"sort-packages": true
71+
},
72+
"extra": {
73+
"captainhook": {
74+
"force-install": false
75+
}
76+
},
77+
"scripts": {
78+
"dev:analyze": [
79+
"@dev:analyze:phpstan",
80+
"@dev:analyze:psalm"
81+
],
82+
"dev:analyze:phpstan": "phpstan analyse --ansi --debug --memory-limit=512M",
83+
"dev:analyze:psalm": "psalm",
84+
"dev:build:clean": "git clean -fX build/",
85+
"dev:lint": [
86+
"@dev:lint:syntax",
87+
"@dev:lint:style"
88+
],
89+
"dev:lint:fix": "phpcbf",
90+
"dev:lint:style": "phpcs --colors",
91+
"dev:lint:syntax": "parallel-lint --colors src/ tests/",
92+
"dev:test": [
93+
"@dev:lint",
94+
"@dev:analyze",
95+
"@dev:test:unit",
96+
"@dev:test:integration"
97+
],
98+
"dev:test:coverage:ci": "phpunit --colors=always --coverage-text --coverage-clover build/coverage/clover.xml --coverage-cobertura build/coverage/cobertura.xml --coverage-crap4j build/coverage/crap4j.xml --coverage-xml build/coverage/coverage-xml --log-junit build/junit.xml",
99+
"dev:test:coverage:html": "phpunit --colors=always --coverage-html build/coverage/coverage-html/",
100+
"dev:test:unit": [
101+
"@dev:test:unit:setup",
102+
"phpunit --colors=always --testdox --testsuite=unit",
103+
"@dev:test:unit:teardown"
104+
],
105+
"dev:test:unit:debug": "phpunit --colors=always --testdox -d xdebug.profiler_enable=on",
106+
"dev:test:unit:setup": "echo 'Setup for unit tests...'",
107+
"dev:test:unit:teardown": "echo 'Tore down for unit tests...'",
108+
"dev:test:integration": [
109+
"@dev:test:integration:setup",
110+
"phpunit --colors=always --testdox --testsuite=integration",
111+
"@dev:test:integration:teardown"
112+
],
113+
"dev:test:integration:debug": "phpunit --colors=always --testdox -d xdebug.profiler_enable=on",
114+
"dev:test:integration:setup": "echo 'Setup for integration tests...'",
115+
"dev:test:integration:teardown": "echo 'Tore down integration tests...'",
116+
"test": "@dev:test"
117+
},
118+
"scripts-descriptions": {
119+
"dev:analyze": "Runs all static analysis checks.",
120+
"dev:analyze:phpstan": "Runs the PHPStan static analyzer.",
121+
"dev:analyze:psalm": "Runs the Psalm static analyzer.",
122+
"dev:build:clean": "Cleans the build/ directory.",
123+
"dev:lint": "Runs all linting checks.",
124+
"dev:lint:fix": "Auto-fixes coding standards issues, if possible.",
125+
"dev:lint:style": "Checks for coding standards issues.",
126+
"dev:lint:syntax": "Checks for syntax errors.",
127+
"dev:test": "Runs linting, static analysis, and unit tests.",
128+
"dev:test:coverage:ci": "Runs unit tests and generates CI coverage reports.",
129+
"dev:test:coverage:html": "Runs unit tests and generates HTML coverage report.",
130+
"dev:test:unit": "Runs unit tests.",
131+
"test": "Runs linting, static analysis, and unit tests."
132+
}
133+
}

‎hooks/Validators/examples/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/*/vendor
2+
/*/composer.lock
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# OpenFeature Validators Hook example
2+
3+
This example provides an example of using the validators hooks for OpenFeature.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "open-feature/validators-hook-example",
3+
"description": "An example of using the validator hooks for OpenFeature",
4+
"type": "project",
5+
"license": "Apache-2.0",
6+
"authors": [
7+
{
8+
"name": "Tom Carrio",
9+
"email": "tom@carrio.dev"
10+
}
11+
],
12+
"require": {
13+
"open-feature/sdk": "^1.2.0",
14+
"open-feature/validators-hook": "dev-main"
15+
},
16+
"repositories": [
17+
{
18+
"type": "path",
19+
"url": "../../",
20+
"options": {
21+
"versions": {
22+
"open-feature/validators-hook": "dev-main"
23+
}
24+
}
25+
}
26+
]
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Examples\OpenFeature\Http;
6+
7+
require __DIR__ . '/../../../vendor/autoload.php';
8+
9+
use OpenFeature\Hooks\Validators\Regexp\RegexpValidatorHook;
10+
use OpenFeature\OpenFeatureAPI;
11+
12+
13+
// retrieve the OpenFeatureAPI instance
14+
$api = OpenFeatureAPI::getInstance();
15+
16+
// retrieve an OpenFeatureClient
17+
$client = $api->getClient('split-example', '1.0');
18+
19+
// create some example hook validators
20+
21+
$alphanumericValidator = new RegexpValidatorHook('/^[A-Za-z0-9]+$/');
22+
$hexadecimalValidator = new RegexpValidatorHook('/^[0-9a-f]+$/');
23+
$asciiValidator = new RegexpValidatorHook('/^[ -~]$/');
24+
25+
$client->setHooks([
26+
$alphanumericValidator,
27+
$hexadecimalValidator,
28+
$asciiValidator
29+
]);
30+
31+
$flagValue = $client->getBooleanDetails('dev.openfeature.example_flag', true, null, null);

‎hooks/Validators/phpcs.xml.dist

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0"?>
2+
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
3+
4+
<arg name="extensions" value="php"/>
5+
<arg name="colors"/>
6+
<arg value="sp"/>
7+
8+
<file>./src</file>
9+
<file>./tests</file>
10+
11+
<exclude-pattern>*/tests/fixtures/*</exclude-pattern>
12+
<exclude-pattern>*/tests/*/fixtures/*</exclude-pattern>
13+
14+
<rule ref="Ramsey">
15+
<exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming"/>
16+
<exclude name="SlevomatCodingStandard.Classes.SuperfluousErrorNaming"/>
17+
<exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming"/>
18+
<exclude name="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming"/>
19+
<exclude name="SlevomatCodingStandard.Classes.SuperfluousTraitNaming"/>
20+
21+
<exclude name="Generic.Files.LineLength.TooLong"/>
22+
<exclude name="Generic.Commenting.Todo.TaskFound"/>
23+
24+
<!-- Ignore this for PHP 7.4 -->
25+
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint"/>
26+
</rule>
27+
28+
</ruleset>

‎hooks/Validators/phpstan.neon.dist

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
parameters:
2+
tmpDir: ./build/cache/phpstan
3+
level: max
4+
paths:
5+
- ./src
6+
- ./tests
7+
excludePaths:
8+
- */tests/fixtures/*
9+
- */tests/*/fixtures/*

‎hooks/Validators/phpunit.xml.dist

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
4+
bootstrap="./vendor/autoload.php"
5+
cacheResultFile="./build/cache/phpunit.result.cache"
6+
colors="true"
7+
verbose="true">
8+
9+
<testsuites>
10+
<testsuite name="unit">
11+
<directory>./tests/unit</directory>
12+
</testsuite>
13+
<testsuite name="integration">
14+
<directory>./tests/integration</directory>
15+
</testsuite>
16+
</testsuites>
17+
18+
<coverage processUncoveredFiles="true">
19+
<include>
20+
<directory suffix=".php">./src</directory>
21+
</include>
22+
</coverage>
23+
24+
<php>
25+
<ini name="date.timezone" value="UTC"/>
26+
</php>
27+
28+
</phpunit>

‎hooks/Validators/psalm-baseline.xml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<files psalm-version="3.9.5@0cfe565d0afbcd31eadcc281b9017b5692911661"/>

‎hooks/Validators/psalm.xml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0"?>
2+
<psalm xmlns="https://getpsalm.org/schema/config"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
5+
errorLevel="1"
6+
cacheDirectory="./build/cache/psalm"
7+
errorBaseline="./psalm-baseline.xml">
8+
9+
<projectFiles>
10+
<directory name="./src"/>
11+
<ignoreFiles>
12+
<directory name="./tests"/>
13+
<directory name="./vendor"/>
14+
</ignoreFiles>
15+
</projectFiles>
16+
17+
</psalm>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"bootstrap-sha": "0a4beadbad2b40f14063f2ba9bcda258595119e9",
3+
"packages": {
4+
".": {
5+
"release-type": "php",
6+
"prerelease": false,
7+
"bump-minor-pre-major": true,
8+
"bump-patch-for-minor-pre-major": true,
9+
"include-v-in-tag": false
10+
}
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenFeature\Hooks\Validators\Exceptions;
6+
7+
use Exception;
8+
use Throwable;
9+
10+
class ValidationException extends Exception
11+
{
12+
private const ERROR_MESSAGE = 'Validation hook failed to validate the provided value';
13+
14+
public function __construct(int $code = 0, ?Throwable $previous = null)
15+
{
16+
parent::__construct(self::ERROR_MESSAGE, $code, $previous);
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenFeature\Hooks\Validators\Regexp;
6+
7+
use Exception;
8+
use Throwable;
9+
10+
use function sprintf;
11+
12+
class InvalidRegularExpressionException extends Exception
13+
{
14+
private const ERROR_MESSAGE_TEMPLATE = 'The provided regular expression was invalid: %s';
15+
16+
public function __construct(string $regexp, int $code = 0, ?Throwable $previous = null)
17+
{
18+
$message = sprintf(self::ERROR_MESSAGE_TEMPLATE, $regexp);
19+
20+
parent::__construct($message, $code, $previous);
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenFeature\Hooks\Validators\Regexp;
6+
7+
use OpenFeature\Hooks\Validators\Exceptions\ValidationException;
8+
use OpenFeature\interfaces\flags\EvaluationContext;
9+
use OpenFeature\interfaces\flags\FlagValueType;
10+
use OpenFeature\interfaces\hooks\Hook;
11+
use OpenFeature\interfaces\hooks\HookContext;
12+
use OpenFeature\interfaces\hooks\HookHints;
13+
use OpenFeature\interfaces\provider\ResolutionDetails;
14+
use Throwable;
15+
16+
use function is_int;
17+
use function preg_match;
18+
19+
class RegexpValidatorHook implements Hook
20+
{
21+
private string $regexp;
22+
23+
public function __construct(string $regexp)
24+
{
25+
$this->regexp = self::validateRegexp($regexp);
26+
}
27+
28+
public function before(HookContext $context, HookHints $hints): ?EvaluationContext
29+
{
30+
return null;
31+
}
32+
33+
public function after(HookContext $context, ResolutionDetails $details, HookHints $hints): void
34+
{
35+
/** @var string $resolvedValue */
36+
$resolvedValue = $details->getValue();
37+
38+
if ($this->testResolvedValue($resolvedValue)) {
39+
return;
40+
}
41+
42+
throw new ValidationException();
43+
}
44+
45+
public function error(HookContext $context, Throwable $error, HookHints $hints): void
46+
{
47+
// no-op
48+
}
49+
50+
public function finally(HookContext $context, HookHints $hints): void
51+
{
52+
// no-op
53+
}
54+
55+
public function supportsFlagValueType(string $flagValueType): bool
56+
{
57+
return $flagValueType === FlagValueType::STRING;
58+
}
59+
60+
private function testResolvedValue(string $resolvedValue): bool
61+
{
62+
return preg_match($this->regexp, $resolvedValue) === 1;
63+
}
64+
65+
private static function validateRegexp(string $regexp): string
66+
{
67+
if (self::isValidRegexp($regexp)) {
68+
return $regexp;
69+
}
70+
71+
throw new InvalidRegularExpressionException($regexp);
72+
}
73+
74+
private static function isValidRegexp(string $regexp): bool
75+
{
76+
return is_int(@preg_match($regexp, ''));
77+
}
78+
}

‎hooks/Validators/tests/TestCase.php

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenFeature\Hooks\Validators\Test;
6+
7+
use Mockery;
8+
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
9+
use Mockery\MockInterface;
10+
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
11+
use Spatie\Snapshots\MatchesSnapshots;
12+
13+
/**
14+
* A base test case for common test functionality
15+
*/
16+
abstract class TestCase extends PHPUnitTestCase
17+
{
18+
use MatchesSnapshots;
19+
use MockeryPHPUnitIntegration;
20+
21+
/**
22+
* Configures and returns a mock object
23+
*
24+
* @param class-string<T> $class
25+
* @param mixed ...$arguments
26+
*
27+
* @return T & MockInterface
28+
*
29+
* @template T
30+
*
31+
* phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
32+
*/
33+
public function mockery(string $class, ...$arguments)
34+
{
35+
/** @var T & MockInterface $mock */
36+
$mock = Mockery::mock($class, ...$arguments);
37+
38+
return $mock;
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenFeature\Hooks\Validators\Test\integration;
6+
7+
use OpenFeature\Hooks\Validators\Regexp\RegexpValidatorHook;
8+
use OpenFeature\Hooks\Validators\Test\TestCase;
9+
use OpenFeature\interfaces\hooks\Hook;
10+
11+
class RegexpValidatorHookTest extends TestCase
12+
{
13+
public function testCanCreateRegexpHook(): void
14+
{
15+
$hook = new RegexpValidatorHook('/^[a-z]+$/');
16+
17+
$this->assertInstanceOf(Hook::class, $hook);
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenFeature\Hooks\Validators\Test\integration;
6+
7+
use OpenFeature\Hooks\Validators\Exceptions\ValidationException;
8+
use OpenFeature\Hooks\Validators\Regexp\InvalidRegularExpressionException;
9+
use OpenFeature\Hooks\Validators\Regexp\RegexpValidatorHook;
10+
use OpenFeature\Hooks\Validators\Test\TestCase;
11+
use OpenFeature\implementation\common\Metadata;
12+
use OpenFeature\implementation\hooks\HookContextFactory;
13+
use OpenFeature\implementation\hooks\HookHints;
14+
use OpenFeature\implementation\provider\ResolutionDetailsFactory;
15+
use OpenFeature\interfaces\flags\FlagValueType;
16+
use OpenFeature\interfaces\hooks\Hook;
17+
18+
class RegexpValidatorHookTest extends TestCase
19+
{
20+
public function testCanCreateValidRegexpForHook(): void
21+
{
22+
$hook = new RegexpValidatorHook('/^[a-z]+$/');
23+
24+
$this->assertInstanceOf(Hook::class, $hook);
25+
}
26+
27+
public function testCannotCreateInvalidRegexpForHook(): void
28+
{
29+
$this->expectException(InvalidRegularExpressionException::class);
30+
31+
new RegexpValidatorHook('/[\\]/');
32+
}
33+
34+
public function testAlphanumericRegexpHookPasses(): void
35+
{
36+
$hook = new RegexpValidatorHook('/^[A-Za-z0-9]+$/');
37+
38+
$this->assertInstanceOf(Hook::class, $hook);
39+
40+
$this->executeHook($hook, 'Abc123');
41+
}
42+
43+
public function testAlphanumericRegexpHookFails(): void
44+
{
45+
$hook = new RegexpValidatorHook('/^[A-Za-z0-9]+$/');
46+
47+
$this->assertInstanceOf(Hook::class, $hook);
48+
49+
$this->expectException(ValidationException::class);
50+
51+
$this->executeHook($hook, 'This, a sentence, has other invalid characters.');
52+
}
53+
54+
public function testHexadecimalRegexpHookPasses(): void
55+
{
56+
$hook = new RegexpValidatorHook('/^[0-9a-f]+$/');
57+
58+
$this->assertInstanceOf(Hook::class, $hook);
59+
60+
$this->executeHook($hook, 'deadbeef007');
61+
}
62+
63+
public function testHexadecimalRegexpHookFails(): void
64+
{
65+
$hook = new RegexpValidatorHook('/^[0-9a-f]+$/');
66+
67+
$this->assertInstanceOf(Hook::class, $hook);
68+
69+
$this->expectException(ValidationException::class);
70+
71+
$this->executeHook($hook, '0123456789abcdefg');
72+
}
73+
74+
public function testAsciiRegexpHookPasses(): void
75+
{
76+
$hook = new RegexpValidatorHook('/^[ -~]+$/');
77+
78+
$this->assertInstanceOf(Hook::class, $hook);
79+
80+
$this->executeHook($hook, 'Only ASCII characters get used here: See?');
81+
}
82+
83+
public function testAsciiRegexpHookFails(): void
84+
{
85+
$hook = new RegexpValidatorHook('/^[ -~]+$/');
86+
87+
$this->assertInstanceOf(Hook::class, $hook);
88+
89+
$this->expectException(ValidationException::class);
90+
91+
$this->executeHook($hook, '');
92+
}
93+
94+
private function executeHook(Hook $hook, string $resolvedValue): void
95+
{
96+
$ctx = HookContextFactory::from(
97+
'any-key',
98+
FlagValueType::STRING,
99+
'default-value',
100+
null,
101+
new Metadata('client'),
102+
new Metadata('provider'),
103+
);
104+
105+
$details = ResolutionDetailsFactory::fromSuccess($resolvedValue);
106+
107+
$nullHints = new HookHints();
108+
109+
$hook->after($ctx, $details, $nullHints);
110+
}
111+
}

0 commit comments

Comments
 (0)
Please sign in to comment.