Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions src/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace LaunchDarkly\OpenFeature;

use LaunchDarkly\EvaluationDetail;
use LaunchDarkly\LDClient;
use OpenFeature\implementation\provider\ResolutionDetailsBuilder;
use OpenFeature\implementation\provider\ResolutionError;
Expand Down Expand Up @@ -126,20 +127,29 @@ private function resolveValue(string $flagKey, string $flagValueType, mixed $def

$ldContext = $this->contextConverter->toLdContext($context);
$result = $this->client->variationDetail($flagKey, $ldContext, $defaultValue);
$value = $result->getValue();

if ($flagValueType == FlagValueType::BOOLEAN && !is_bool($result->getValue())) {
if ($flagValueType == FlagValueType::BOOLEAN && !is_bool($value)) {
return $this->mismatchedTypeDetails($defaultValue);
} elseif ($flagValueType == FlagValueType::STRING && !is_string($result->getValue())) {
} elseif ($flagValueType == FlagValueType::STRING && !is_string($value)) {
return $this->mismatchedTypeDetails($defaultValue);
} elseif ($flagValueType == FlagValueType::INTEGER && !is_numeric($result->getValue())) {
} elseif ($flagValueType == FlagValueType::INTEGER && (!is_int($value) && !is_float($value))) {
return $this->mismatchedTypeDetails($defaultValue);
} elseif ($flagValueType == FlagValueType::FLOAT && !is_numeric($result->getValue())) {
} elseif ($flagValueType == FlagValueType::FLOAT && (!is_int($value) && !is_float($value))) {
return $this->mismatchedTypeDetails($defaultValue);
} elseif ($flagValueType == FlagValueType::OBJECT && !is_array($result->getValue())) {
} elseif ($flagValueType == FlagValueType::OBJECT && !is_array($value)) {
return $this->mismatchedTypeDetails($defaultValue);
}

return $this->detailsConverter->toResolutionDetails($result);
if ($flagValueType == FlagValueType::INTEGER) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's been a bit since I've looked at this part of the algorithm, but do we typically do this truncation?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — truncation is what the other server providers do, which is why I matched it rather than treating a non-integral float as a type mismatch:

  • Python _LaunchDarklyProvider__validate_and_cast_value: return int(value) # Float decimals are truncated to int for FlagType.INTEGER.
  • Ruby provider.rb integer resolution: evaluation_detail.value.to_i.
  • The JS providers have only resolveNumberEvaluation, so the question does not arise there.

Both accept an int or a float and reject bools and numeric strings, which is exactly the shape of the check here.

Worth being explicit that truncation is not what this PR is really about, though: before it, is_numeric let a non-integral float through uncast, so resolveIntegerValue returned 5.5 and the OpenFeature client raised a TypeError that surfaced as GENERAL rather than anything meaningful. So the choice is between truncating like Python and Ruby, or returning TYPE_MISMATCH with the default value. I went with the former for cross-SDK consistency, but if you would rather a 5.5 integer flag be a hard TYPE_MISMATCH here I am happy to switch it — it is a one-line change plus the test, and it would be a deliberate divergence from Python and Ruby.

$value = (int) $value;
} elseif ($flagValueType == FlagValueType::FLOAT) {
$value = (float) $value;
}

$resolvedResult = new EvaluationDetail($value, $result->getVariationIndex(), $result->getReason());

return $this->detailsConverter->toResolutionDetails($resolvedResult);
}

private function mismatchedTypeDetails(mixed $defaultValue): ResolutionDetails
Expand Down
22 changes: 21 additions & 1 deletion tests/ProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,13 @@ public function checkMethodAndResultMatchTypeProvider(): array
[1, true, 1, 'resolveIntegerValue'],
[1, false, 1, 'resolveIntegerValue'],
[1, "", 1, 'resolveIntegerValue'],
[1, "5", 1, 'resolveIntegerValue'],
[1, 2.5, 2, 'resolveIntegerValue'],

[1.0, 2.0, 2.0, 'resolveFloatValue'],
[1.0, 2, 2.0, 'resolveFloatValue'],
[1.0, true, 1.0, 'resolveFloatValue'],
[1.0, "5", 1.0, 'resolveFloatValue'],

[['default-value'], ['return-string'], ['return-string'], 'resolveObjectValue'],
[['default-value'], true, ['default-value'], 'resolveObjectValue'],
Expand All @@ -124,7 +127,24 @@ public function testCheckMethodAndResultMatchType(mixed $defaultValue, mixed $re
$provider = new Provider('sdk-key', ['feature_requester' => $td]);
$resolutionDetails = $provider->{$methodName}("flag-key", $defaultValue, new EvaluationContext("user-key"));

$this->assertEquals($expectedValue, $resolutionDetails->getValue());
$this->assertSame($expectedValue, $resolutionDetails->getValue());
}

public function testNumericStringsGenerateTypeMismatchErrors(): void
{
$td = new Integrations\TestData();
$td->update($td->flag('flag-key')->valueForAll("5"));

$provider = new Provider('sdk-key', ['feature_requester' => $td]);
$integerDetails = $provider->resolveIntegerValue("flag-key", 1, new EvaluationContext("user-key"));
$floatDetails = $provider->resolveFloatValue("flag-key", 1.0, new EvaluationContext("user-key"));

/** @var ResolutionError */
$integerError = $integerDetails->getError();
/** @var ResolutionError */
$floatError = $floatDetails->getError();
$this->assertEquals(ErrorCode::TYPE_MISMATCH(), $integerError->getResolutionErrorCode());
$this->assertEquals(ErrorCode::TYPE_MISMATCH(), $floatError->getResolutionErrorCode());
}

public function testLoggerChangesShouldCascadeToEvaluationConverter(): void
Expand Down
Loading