From 0c3651de70afd27bfeec73a3715d2636183a3c9a Mon Sep 17 00:00:00 2001 From: Tom Carrio Date: Mon, 29 Apr 2024 23:18:52 -0400 Subject: [PATCH] feat: initial support for FlagMetadata in resolution --- src/implementation/flags/Attributes.php | 6 +-- src/implementation/flags/FlagMetadata.php | 45 +++++++++++++++++++ .../provider/ResolutionDetails.php | 13 ++++++ .../provider/ResolutionDetailsBuilder.php | 8 ++++ src/interfaces/flags/FlagMetadata.php | 31 +++++++++++++ src/interfaces/provider/ResolutionDetails.php | 12 +++++ tests/unit/ProviderTest.php | 43 ++++++++++++++++++ 7 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 src/implementation/flags/FlagMetadata.php create mode 100644 src/interfaces/flags/FlagMetadata.php diff --git a/src/implementation/flags/Attributes.php b/src/implementation/flags/Attributes.php index 969a1c4..34837f3 100644 --- a/src/implementation/flags/Attributes.php +++ b/src/implementation/flags/Attributes.php @@ -32,11 +32,7 @@ public function keys(): array */ public function get(string $key): bool | string | int | float | DateTime | array | null { - if (isset($this->attributesMap[$key])) { - return $this->attributesMap[$key]; - } - - return null; + return $this->attributesMap[$key] ?? null; } /** diff --git a/src/implementation/flags/FlagMetadata.php b/src/implementation/flags/FlagMetadata.php new file mode 100644 index 0000000..91a61d8 --- /dev/null +++ b/src/implementation/flags/FlagMetadata.php @@ -0,0 +1,45 @@ + $attributesMap + */ + public function __construct(protected array $metadata = []) + { + } + + /** + * Return key-type pairs of the attributes + * + * @return Array + */ + public function keys(): array + { + return ArrayHelper::getStringKeys($this->metadata); + } + + /** + * @return bool|string|int|float|null + */ + public function get(string $key): bool | string | int | float | null + { + return $this->metadata[$key] ?? null; + } + + /** + * @return Array + */ + public function toArray(): array + { + return [...$this->metadata]; + } +} diff --git a/src/implementation/provider/ResolutionDetails.php b/src/implementation/provider/ResolutionDetails.php index ac9b047..7d8f37b 100644 --- a/src/implementation/provider/ResolutionDetails.php +++ b/src/implementation/provider/ResolutionDetails.php @@ -4,6 +4,8 @@ namespace OpenFeature\implementation\provider; +use OpenFeature\implementation\flags\FlagMetadata as FlagMetadata; +use OpenFeature\interfaces\flags\FlagMetadata as FlagMetadataInterface; use OpenFeature\interfaces\provider\ResolutionDetails as ResolutionDetailsInterface; use OpenFeature\interfaces\provider\ResolutionError; @@ -14,6 +16,7 @@ class ResolutionDetails implements ResolutionDetailsInterface private ?ResolutionError $error = null; private ?string $reason = null; private ?string $variant = null; + private FlagMetadataInterface $flagMetadata = new FlagMetadata(); /** * @return bool|string|int|float|mixed[]|null @@ -60,4 +63,14 @@ public function setVariant(?string $variant): void { $this->variant = $variant; } + + public function getFlagMetadata(): FlagMetadataInterface + { + return $this->flagMetadata; + } + + public function setFlagMetadata(FlagMetadataInterface $flagMetadata): void + { + $this->flagMetadata = $flagMetadata; + } } diff --git a/src/implementation/provider/ResolutionDetailsBuilder.php b/src/implementation/provider/ResolutionDetailsBuilder.php index 4028f6b..cab5fdb 100644 --- a/src/implementation/provider/ResolutionDetailsBuilder.php +++ b/src/implementation/provider/ResolutionDetailsBuilder.php @@ -4,6 +4,7 @@ namespace OpenFeature\implementation\provider; +use OpenFeature\interfaces\flags\FlagMetadata; use OpenFeature\interfaces\provider\ResolutionDetails as ResolutionDetailsInterface; use OpenFeature\interfaces\provider\ResolutionError; @@ -47,6 +48,13 @@ public function withVariant(string $variant): ResolutionDetailsBuilder return $this; } + public function withFlagMetadata(FlagMetadata $flagMetadata): ResolutionDetailsBuilder + { + $this->flagMetadata->setFlagMetadata($flagMetadata); + + return $this; + } + public function build(): ResolutionDetailsInterface { return $this->details; diff --git a/src/interfaces/flags/FlagMetadata.php b/src/interfaces/flags/FlagMetadata.php new file mode 100644 index 0000000..5440e68 --- /dev/null +++ b/src/interfaces/flags/FlagMetadata.php @@ -0,0 +1,31 @@ + + */ + public function keys(): array; + + /** + * @return bool|string|int|float|null + */ + public function get(string $key): bool | string | int | float | null; + + /** + * @return Array + */ + public function toArray(): array; +} diff --git a/src/interfaces/provider/ResolutionDetails.php b/src/interfaces/provider/ResolutionDetails.php index c9c8095..c3230a1 100644 --- a/src/interfaces/provider/ResolutionDetails.php +++ b/src/interfaces/provider/ResolutionDetails.php @@ -4,6 +4,8 @@ namespace OpenFeature\interfaces\provider; +use OpenFeature\interfaces\flags\FlagMetadata; + /** * A structure which contains a subset of the fields defined in the evaluation * details, representing the result of the provider's flag resolution process @@ -49,4 +51,14 @@ public function getReason(): ?string; * with a string identifier corresponding to the returned flag value. */ public function getVariant(): ?string; + + + /** + * --------------- + * Requirement 2.2.10 + * --------------- + * `flag metadata` **MUST** be a structure supporting the definition of arbitrary properties, with keys of type + * `string`, and values of type `boolean | string | number`. + */ + public function getFlagMetadata(): FlagMetadata; } diff --git a/tests/unit/ProviderTest.php b/tests/unit/ProviderTest.php index 18a203a..af32400 100644 --- a/tests/unit/ProviderTest.php +++ b/tests/unit/ProviderTest.php @@ -5,6 +5,7 @@ namespace OpenFeature\Test\unit; use Mockery; +use OpenFeature\implementation\flags\FlagMetadata; use OpenFeature\Test\TestCase; use OpenFeature\Test\TestHook; use OpenFeature\Test\TestProvider; @@ -202,6 +203,48 @@ public function testShouldPopulateReasonField(): void $this->assertEquals($expectedReason, $actualResolution->getReason()); } + /** + * Requirement 2.6 + * + * The provider SHOULD populate the flag resolution structure's reason field with "DEFAULT", "TARGETING_MATCH", "SPLIT", "DISABLED", "UNKNOWN", "ERROR" or some other string indicating the semantic reason for the returned flag value. + */ + public function testShouldPopulateFlagMetadataField(): void + { + $expectedFlagMetadata = new FlagMetadata(['str' => 'value', 'num' => 42, 'bool' => true]); + + /** @var Mockery\MockInterface|Provider $mockProvider */ + $mockProvider = $this->mockery(TestProvider::class)->makePartial(); + $mockProvider->shouldReceive('resolveBooleanValue') + ->andReturn((new ResolutionDetailsBuilder()) + ->withValue(true) + ->withFlagMetadata($expectedFlagMetadata) + ->build()); + + + $actualResolution = $mockProvider->resolveBooleanValue('flagKey', false, null); + + $this->assertEquals($expectedFlagMetadata, $actualResolution->getFlagMetadata()); + } + + /** + * Requirement 2.6 + * + * The provider SHOULD populate the flag resolution structure's reason field with "DEFAULT", "TARGETING_MATCH", "SPLIT", "DISABLED", "UNKNOWN", "ERROR" or some other string indicating the semantic reason for the returned flag value. + */ + public function testShouldDefaultAnEmptyFlagMetadataField(): void + { + $expectedFlagMetadata = new FlagMetadata(); + + /** @var Mockery\MockInterface|Provider $mockProvider */ + $mockProvider = $this->mockery(TestProvider::class)->makePartial(); + $mockProvider->shouldReceive('resolveBooleanValue') + ->andReturn(ResolutionDetailsFactory::fromSuccess(true)); + + $actualResolution = $mockProvider->resolveBooleanValue('flagKey', false, null); + + $this->assertEquals($expectedFlagMetadata, $actualResolution->getFlagMetadata()); + } + /** * Requirement 2.7 *