Skip to content

Commit 37f13e1

Browse files
committed
feat: support for ErrorCode enum, Reason strings with pre-existing consts, v0.5 spec
1 parent 0afe8b2 commit 37f13e1

14 files changed

+118
-14
lines changed

Diff for: composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
],
1111
"require": {
1212
"php": "^7.4 || ^8",
13+
"myclabs/php-enum": "^1.8",
1314
"psr/log": "^1.1"
1415
},
1516
"require-dev": {

Diff for: phpstan.neon.dist

+4
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ parameters:
77
excludePaths:
88
- */tests/fixtures/*
99
- */tests/*/fixtures/*
10+
ignoreErrors:
11+
-
12+
message: '#Constant .+ is unused#'
13+
path: src/interfaces/provider/ErrorCode.php

Diff for: src/OpenFeatureClient.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
use OpenFeature\interfaces\flags\FlagValueType;
2929
use OpenFeature\interfaces\hooks\Hook;
3030
use OpenFeature\interfaces\hooks\HooksAwareTrait;
31+
use OpenFeature\interfaces\provider\ErrorCode;
3132
use OpenFeature\interfaces\provider\Provider;
3233
use OpenFeature\interfaces\provider\ResolutionDetails;
34+
use OpenFeature\interfaces\provider\ThrowableWithErrorCode;
3335
use Psr\Log\LoggerAwareInterface;
3436
use Throwable;
3537

@@ -373,10 +375,12 @@ private function evaluateFlag(
373375
),
374376
);
375377

378+
$errorCode = $err instanceof ThrowableWithErrorCode ? $err->getErrorCode() : ErrorCode::GENERAL();
379+
376380
$details = (new EvaluationDetailsBuilder())
377381
->withValue($defaultValue)
378382
->withReason(Reason::ERROR)
379-
->withErrorCode($err->getMessage())
383+
->withErrorCode($errorCode)
380384
->build();
381385

382386
$hookExecutor->errorHooks($flagValueType, $hookContext, $err, $mergedRemainingHooks, $hookHints);

Diff for: src/implementation/flags/EvaluationDetails.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66

77
use DateTime;
88
use OpenFeature\interfaces\flags\EvaluationDetails as EvaluationDetailsInterface;
9+
use OpenFeature\interfaces\provider\ErrorCode;
910

1011
class EvaluationDetails implements EvaluationDetailsInterface
1112
{
1213
private string $flagKey = '';
1314
/** @var bool|string|int|float|DateTime|mixed[]|null $value */
1415
private $value;
15-
private ?string $errorCode = null;
16+
private ?ErrorCode $errorCode = null;
1617
private ?string $reason = null;
1718
private ?string $variant = null;
1819

@@ -52,12 +53,12 @@ public function setValue($value): void
5253
$this->value = $value;
5354
}
5455

55-
public function getErrorCode(): ?string
56+
public function getErrorCode(): ?ErrorCode
5657
{
5758
return $this->errorCode;
5859
}
5960

60-
public function setErrorCode(?string $errorCode): void
61+
public function setErrorCode(?ErrorCode $errorCode): void
6162
{
6263
$this->errorCode = $errorCode;
6364
}

Diff for: src/implementation/flags/EvaluationDetailsBuilder.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use DateTime;
88
use OpenFeature\interfaces\flags\EvaluationDetails as EvaluationDetailsInterface;
9+
use OpenFeature\interfaces\provider\ErrorCode;
910

1011
class EvaluationDetailsBuilder
1112
{
@@ -33,7 +34,7 @@ public function withValue($value): EvaluationDetailsBuilder
3334
return $this;
3435
}
3536

36-
public function withErrorCode(?string $errorCode): EvaluationDetailsBuilder
37+
public function withErrorCode(?ErrorCode $errorCode): EvaluationDetailsBuilder
3738
{
3839
$this->details->setErrorCode($errorCode);
3940

Diff for: src/implementation/provider/ResolutionDetails.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
namespace OpenFeature\implementation\provider;
66

77
use DateTime;
8+
use OpenFeature\interfaces\provider\ErrorCode;
89
use OpenFeature\interfaces\provider\ResolutionDetails as ResolutionDetailsInterface;
910

1011
class ResolutionDetails implements ResolutionDetailsInterface
1112
{
1213
/** @var bool|string|int|float|DateTime|mixed[]|null $value */
1314
private $value = null;
14-
private ?string $errorCode = null;
15+
private ?ErrorCode $errorCode = null;
1516
private ?string $reason = null;
1617
private ?string $variant = null;
1718

@@ -31,12 +32,12 @@ public function setValue($value): void
3132
$this->value = $value;
3233
}
3334

34-
public function getErrorCode(): ?string
35+
public function getErrorCode(): ?ErrorCode
3536
{
3637
return $this->errorCode;
3738
}
3839

39-
public function setErrorCode(?string $errorCode): void
40+
public function setErrorCode(?ErrorCode $errorCode): void
4041
{
4142
$this->errorCode = $errorCode;
4243
}

Diff for: src/implementation/provider/ResolutionDetailsBuilder.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace OpenFeature\implementation\provider;
66

77
use DateTime;
8+
use OpenFeature\interfaces\provider\ErrorCode;
89
use OpenFeature\interfaces\provider\ResolutionDetails as ResolutionDetailsInterface;
910

1011
class ResolutionDetailsBuilder
@@ -26,7 +27,7 @@ public function withValue($value): ResolutionDetailsBuilder
2627
return $this;
2728
}
2829

29-
public function withErrorCode(string $errorCode): ResolutionDetailsBuilder
30+
public function withErrorCode(ErrorCode $errorCode): ResolutionDetailsBuilder
3031
{
3132
$this->details->setErrorCode($errorCode);
3233

Diff for: src/interfaces/flags/EvaluationDetails.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace OpenFeature\interfaces\flags;
66

77
use DateTime;
8+
use OpenFeature\interfaces\provider\ErrorCode;
89

910
/**
1011
* A structure representing the result of the flag evaluation process,
@@ -42,7 +43,7 @@ public function getValue();
4243
* MUST contain a string identifying an error occurred during flag evaluation and the
4344
* nature of the error.
4445
*/
45-
public function getErrorCode(): ?string;
46+
public function getErrorCode(): ?ErrorCode;
4647

4748
/**
4849
* -----------------

Diff for: src/interfaces/provider/ErrorCode.php

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenFeature\interfaces\provider;
6+
7+
use MyCLabs\Enum\Enum;
8+
9+
/**
10+
* Error code
11+
*
12+
* @see https://github.com/open-feature/spec/blob/main/specification/types.md#error-code
13+
*
14+
* @method static ErrorCode PROVIDER_NOT_READY()
15+
* @method static ErrorCode FLAG_NOT_FOUND()
16+
* @method static ErrorCode PARSE_ERROR()
17+
* @method static ErrorCode TYPE_MISMATCH()
18+
* @method static ErrorCode TARGETING_KEY_MISSING()
19+
* @method static ErrorCode INVALID_CONTEXT()
20+
* @method static ErrorCode GENERAL()
21+
*
22+
* @extends Enum<string>
23+
*
24+
* @psalm-immutable
25+
*/
26+
final class ErrorCode extends Enum
27+
{
28+
private const PROVIDER_NOT_READY = 'PROVIDER_NOT_READY';
29+
private const FLAG_NOT_FOUND = 'FLAG_NOT_FOUND';
30+
private const PARSE_ERROR = 'PARSE_ERROR';
31+
private const TYPE_MISMATCH = 'TYPE_MISMATCH';
32+
private const TARGETING_KEY_MISSING = 'TARGETING_KEY_MISSING';
33+
private const INVALID_CONTEXT = 'INVALID_CONTEXT';
34+
private const GENERAL = 'GENERAL';
35+
}

Diff for: src/interfaces/provider/Reason.php

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenFeature\interfaces\provider;
6+
7+
/**
8+
* Resolution reason
9+
*/
10+
final class Reason
11+
{
12+
/**
13+
* The resolved value was configured statically, or otherwise fell back to a pre-configured value.
14+
*/
15+
public const DEFAULT = 'DEFAULT';
16+
17+
/**
18+
* The resolved value was the result of a dynamic evaluation, such as a rule or specific user-targeting.
19+
*/
20+
public const TARGETING_MATCH = 'TARGETING_MATCH';
21+
22+
/**
23+
* The resolved value was the result of pseudorandom assignment.
24+
*/
25+
public const SPLIT = 'SPLIT';
26+
27+
/**
28+
* The resolved value was the result of the flag being disabled in the management system.
29+
*/
30+
public const DISABLED = 'DISABLED';
31+
32+
/**
33+
* The reason for the resolved value could not be determined.
34+
*/
35+
public const UNKNOWN = 'UNKNOWN';
36+
37+
/**
38+
* The resolved value was the result of an error.
39+
*/
40+
public const ERROR = 'ERROR';
41+
}

Diff for: src/interfaces/provider/ResolutionDetails.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function getValue();
3232
* In cases of normal execution, the provider MUST NOT populate the flag resolution structure's error code
3333
* field, or otherwise must populate it with a null or falsy value.
3434
*/
35-
public function getErrorCode(): ?string;
35+
public function getErrorCode(): ?ErrorCode;
3636

3737
/**
3838
* ---------------

Diff for: src/interfaces/provider/ThrowableWithErrorCode.php

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenFeature\interfaces\provider;
6+
7+
use Throwable;
8+
9+
interface ThrowableWithErrorCode extends Throwable
10+
{
11+
public function getErrorCode(): ErrorCode;
12+
}

Diff for: tests/unit/OpenFeatureClientTest.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use OpenFeature\implementation\provider\ResolutionDetailsFactory;
2121
use OpenFeature\interfaces\hooks\Hook;
2222
use OpenFeature\interfaces\hooks\HookContext;
23+
use OpenFeature\interfaces\provider\ErrorCode;
2324
use OpenFeature\interfaces\provider\Provider;
2425
use Psr\Log\LoggerInterface;
2526

@@ -484,7 +485,7 @@ public function testClientEvaluationDetailsAbnormalExecutionHasErrorCodeField():
484485
$api = APITestHelper::new();
485486
$client = new OpenFeatureClient($api, 'test-name', 'test-version');
486487

487-
$expectedErrorCode = 'NETWORK_ERROR';
488+
$expectedErrorCode = ErrorCode::FLAG_NOT_FOUND();
488489

489490
$mockProvider = $this->getDefaultMockProvider();
490491
$mockProvider->shouldReceive('resolveBooleanValue')->andReturns((new ResolutionDetailsBuilder())->withValue(true)->withErrorCode($expectedErrorCode)->build());
@@ -507,7 +508,7 @@ public function testClientEvaluationDetailsAbnormalExecutionHasReasonField(): vo
507508
$api = APITestHelper::new();
508509
$client = new OpenFeatureClient($api, 'test-name', 'test-version');
509510

510-
$expectedErrorCode = 'NETWORK_ERROR';
511+
$expectedErrorCode = ErrorCode::FLAG_NOT_FOUND();
511512
$expectedReason = 'Failed to reach target server';
512513

513514
$mockProvider = $this->getDefaultMockProvider();

Diff for: tests/unit/ProviderTest.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use OpenFeature\Test\TestProvider;
1111
use OpenFeature\implementation\provider\ResolutionDetailsBuilder;
1212
use OpenFeature\implementation\provider\ResolutionDetailsFactory;
13+
use OpenFeature\interfaces\provider\ErrorCode;
1314
use OpenFeature\interfaces\provider\Provider;
1415

1516
class ProviderTest extends TestCase
@@ -230,7 +231,7 @@ public function testMustNotPopulateErrorFieldInNormalExecution(): void
230231
*/
231232
public function testMustPopulateErrorFieldInAbnormalExecution(): void
232233
{
233-
$expectedErrorCode = 'ERROR';
234+
$expectedErrorCode = ErrorCode::GENERAL();
234235

235236
/** @var Mockery\MockInterface|Provider $mockProvider */
236237
$mockProvider = $this->mockery(TestProvider::class)->makePartial();

0 commit comments

Comments
 (0)