From 068554340305c58821e5b4016381e7f0aa7af4fa Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 21:21:14 +0000 Subject: [PATCH] fix: Enforce numeric flag value types Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com> --- src/Provider.php | 22 ++++++++++++++++------ tests/ProviderTest.php | 22 +++++++++++++++++++++- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/Provider.php b/src/Provider.php index 66ae6b3..a4c1f50 100644 --- a/src/Provider.php +++ b/src/Provider.php @@ -4,6 +4,7 @@ namespace LaunchDarkly\OpenFeature; +use LaunchDarkly\EvaluationDetail; use LaunchDarkly\LDClient; use OpenFeature\implementation\provider\ResolutionDetailsBuilder; use OpenFeature\implementation\provider\ResolutionError; @@ -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) { + $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 diff --git a/tests/ProviderTest.php b/tests/ProviderTest.php index 9c49eff..2252079 100644 --- a/tests/ProviderTest.php +++ b/tests/ProviderTest.php @@ -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'], @@ -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