Skip to content

Commit

Permalink
feat: initial support for FlagMetadata in resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
tcarrio committed Apr 30, 2024
1 parent 1c98999 commit 0c3651d
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 5 deletions.
6 changes: 1 addition & 5 deletions src/implementation/flags/Attributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
45 changes: 45 additions & 0 deletions src/implementation/flags/FlagMetadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace OpenFeature\implementation\flags;

use DateTime;
use OpenFeature\implementation\common\ArrayHelper;
use OpenFeature\interfaces\flags\FlagMetadata as FlagMetadataInterface;

class FlagMetadata implements FlagMetadataInterface
{
/**
* @param Array<array-key, bool|string|int|float|DateTime|mixed[]|null> $attributesMap
*/
public function __construct(protected array $metadata = [])
{
}

/**
* Return key-type pairs of the attributes
*
* @return Array<int, string>
*/
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<array-key, bool|string|int|float|DateTime|mixed[]|null>
*/
public function toArray(): array
{
return [...$this->metadata];
}
}
13 changes: 13 additions & 0 deletions src/implementation/provider/ResolutionDetails.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -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;
}
}
8 changes: 8 additions & 0 deletions src/implementation/provider/ResolutionDetailsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down
31 changes: 31 additions & 0 deletions src/interfaces/flags/FlagMetadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace OpenFeature\interfaces\flags;

use DateTime;

/**
* flag metadata MUST be a structure supporting the definition of arbitrary properties,
* with keys of type string, and values of type boolean | string | number.
*/
interface FlagMetadata
{
/**
* Return key-type pairs of the attributes
*
* @return Array<int, string>
*/
public function keys(): array;

/**
* @return bool|string|int|float|null
*/
public function get(string $key): bool | string | int | float | null;

/**
* @return Array<array-key, bool|string|int|float|null>
*/
public function toArray(): array;
}
12 changes: 12 additions & 0 deletions src/interfaces/provider/ResolutionDetails.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
43 changes: 43 additions & 0 deletions tests/unit/ProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
*
Expand Down

0 comments on commit 0c3651d

Please sign in to comment.