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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ class FlagdProviderSyncResources {
@Setter
private volatile ProviderEvent previousEvent;

@Setter
private volatile boolean isFatal;

private volatile ProviderEventDetails fatalProviderEventDetails;
Expand Down Expand Up @@ -111,12 +110,22 @@ public void waitForInitialization(long deadline) {
*/
public synchronized void shutdown() {
isShutDown = true;
isInitialized = false;
this.notifyAll();
}

public synchronized void fatalError(ProviderEventDetails providerEventDetails) {
isFatal = true;
isInitialized = false;
fatalProviderEventDetails = providerEventDetails;
Comment on lines 117 to 120

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Preserve cleanup after fatal errors.

Line 119 sets isInitialized to false before FlagdProvider.shutdown() runs. FlagdProvider.onFatal calls fatalError(...) and then shutdown(). FlagdProvider.shutdown() returns when !syncResources.isInitialized(), before it calls flagResolver.shutdown(), shuts down errorExecutor, or marks the resource as shut down. This can leave fatal provider resources running and isShutDown false.

Change the shutdown contract so fatal cleanup does not depend on isInitialized, or use a separate cleanup state. Add a regression test for a fatal error after initialization.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdProviderSyncResources.java`
around lines 117 - 120, Update the fatal-error cleanup flow across
FlagdProviderSyncResources.fatalError and FlagdProvider.shutdown so shutdown
still executes after fatalError marks the provider fatal, rather than returning
solely because isInitialized is false. Use an appropriate cleanup-state check or
adjust the shutdown contract while preserving normal shutdown behavior, and add
a regression test covering a fatal error after initialization that verifies
resolver and executor cleanup and the shut-down state.

this.notifyAll();
}

public synchronized void setFatal(boolean fatal) {
isFatal = fatal;
if (fatal) {
isInitialized = false;
}
this.notifyAll();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps")
@ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory")
@IncludeTags("in-process")
@ExcludeTags({"unixsocket", "fractional-v1", "deprecated"})
@ExcludeTags({"unixsocket", "fractional-v1", "fractional-v2", "deprecated"})
@Testcontainers
public class RunInProcessTest {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import dev.openfeature.sdk.ImmutableStructure;
import dev.openfeature.sdk.MutableContext;
import dev.openfeature.sdk.Value;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Given;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ContextSteps extends AbstractSteps {
Expand All @@ -14,32 +17,24 @@ public ContextSteps(State state) {
super(state);
}

@Given("a context containing a key {string}, with type {string} and with value {string}")
@Given("^a context containing a key \"([^\"]*)\", with type \"([^\"]*)\" and with value \"(.*)\"$")
public void a_context_containing_a_key_with_type_and_with_value(String key, String type, String value)
throws ClassNotFoundException, InstantiationException {
Map<String, Value> map = state.context.asMap();
Value typedValue;
switch (type) {
case "Integer":
long longVal = Long.parseLong(value);
if (longVal >= Integer.MIN_VALUE && longVal <= Integer.MAX_VALUE) {
typedValue = new Value((int) longVal);
} else {
// value exceeds int range; store as string to preserve precision
typedValue = new Value(value);
}
break;
case "Float":
typedValue = new Value(Double.parseDouble(value));
break;
case "Boolean":
typedValue = new Value(Boolean.parseBoolean(value));
break;
default:
typedValue = new Value(value);
break;
throws ClassNotFoundException, IOException {
Map<String, Value> map = new HashMap<>(state.context.asMap());
map.put(key, Value.objectToValue(Utils.convert(value, type)));
state.context = new MutableContext(state.context.getTargetingKey(), map);
}

@Given("a context with the following keys:")
public void a_context_with_the_following_keys(DataTable dataTable) throws ClassNotFoundException, IOException {
List<Map<String, String>> rows = dataTable.asMaps(String.class, String.class);
Map<String, Value> map = new HashMap<>(state.context.asMap());
for (Map<String, String> row : rows) {
String key = row.get("key");
String type = row.get("type");
String value = row.get("value");
map.put(key, Value.objectToValue(Utils.convert(value, type)));
}
map.put(key, typedValue);
state.context = new MutableContext(state.context.getTargetingKey(), map);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@ public final class Utils {
private Utils() {}

public static Object convert(String value, String type) throws ClassNotFoundException, IOException {
if (Objects.equals(value, "null")) return null;
if ("Null".equals(type)) return null;
if (Objects.equals(value, "null") && !"String".equals(type)) return null;
Comment on lines +18 to +19

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg '(^|/)Utils\.java$|EvaluatorUtils\.ja$|flagd' || true

echo "== relevant Utils.java =="
if [ -f providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java ]; then
  cat -n providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java
fi

echo "== compare EvaluatorUtils if present =="
if [ -f tools/flagd-api-testkit/src/main/java/dev/openfeature/contrib/tools/flagd/api/testkit/EvaluatorUtils.java ]; then
  cat -n tools/flagd-api-testkit/src/main/java/dev/openfeature/contrib/tools/flagd/api/testkit/EvaluatorUtils.java
fi

echo "== search for Utils method name/call sites =="
rg -n "public static Object|convert|valueOf|Null|\"null\"" providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java tools/flagd-api-testkit/src/main/java/dev/openfeature/contrib/tools/flagd/api/testkit/EvaluatorUtils.java 2>/dev/null || true

echo "== git diff stat/name status if available =="
git diff --stat HEAD~1..HEAD 2>/dev/null || git status --short 2>/dev/null || true

Repository: open-feature/java-sdk-contrib

Length of output: 1944


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Behavioral probe: model the Java switch expression from utils/evaluator files using Java semantics.
# This avoids modifying repository files and keeps the probe read-only.
python3 - <<'PY'
class JavaLike:
    def convert(self, value, target_type):
        if target_type == "Null":
            return None
        if value == "null" and target_type != "String":
            return None
        if target_type == "Boolean":
            return bool(value.lower() == "true") if isinstance(value, str) else bool(value)
        if target_type == "Integer":
            if isinstance(value, str):
                if value in ("null", "Infinity", "-Infinity"):
                    raise ValueError("badIntFormat")
                return int(value)
            raise TypeError("non-string integer")
        if target_type == "Number":
            if isinstance(value, str):
                return float(value)
            raise TypeError("non-string number")
        if target_type == "String":
            return str(value)
        raise NotImplementedError(target_type)

cases = [
    (None, "Boolean"),
    ("null", "Boolean"),
    (None, "Integer"),
    ("null", "Integer"),
    (None, "Number"),
    ("null", "Number"),
    (None, "Object"),
    ("null", "Object"),
    (None, "String"),
    ("null", "String"),
]
for value, target_type in cases:
    try:
        out = JavaLike().convert(value, target_type)
    except Exception as ex:
        out = f"{type(ex).__name__}: {ex}"
    print(f"({value!r}, {target_type!r}) -> {out!r}")
PY

Repository: open-feature/java-sdk-contrib

Length of output: 1944


🌐 Web query:

Java OpenFeature flagd e2e Utils.java Null value type conversion providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java

💡 Result:

The file providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java in the java-sdk-contrib repository is part of the end-to-end (e2e) test infrastructure for the flagd Java provider [1][2]. These tests utilize Gherkin scenarios to verify correct provider behavior, including type handling and evaluation consistency [3][4]. Regarding null value type conversion and error handling in flagd: 1. Consistency Initiatives: There has been an ongoing effort to standardize how flagd SDKs, including the Java implementation, handle errors and null values during evaluation [5][6]. Specifically, it has been established that custom operators and targeting rules should return null (or the language equivalent) when encountering invalid inputs, parse failures, or error conditions, rather than returning false or throwing exceptions [5][6]. 2. Evaluation Impact: In the Java SDK, returning null from a targeting rule effectively triggers a fallback to the default variant [5][6]. This behavior ensures that evaluations remain robust when encountering malformed targeting logic or edge cases (e.g., in custom operators like fractional or sem_ver) [5]. 3. Null in Targeting Rules: When a targeting rule evaluates to null and no defaultValue is specified, the provider is expected to revert to the code-defined default [7]. Recent development efforts have addressed issues where such evaluations might fail to correctly set the reason or value, ensuring that null propagation correctly triggers the fallback mechanism as intended by the OpenFeature specification [7]. The Utils.java file and related e2e test steps are used to validate these behaviors by simulating various targeting scenarios and asserting that the outcomes—including null-induced fallbacks—match expected results defined in the test harness [2][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg '(^|/)Utils\.java$|EvaluatorUtils\.ja$|flagd' || true

echo "== relevant Utils.java =="
if [ -f providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java ]; then
  cat -n providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java
fi

echo "== compare EvaluatorUtils if present =="
if [ -f tools/flagd-api-testkit/src/main/java/dev/openfeature/contrib/tools/flagd/api/testkit/EvaluatorUtils.java ]; then
  cat -n tools/flagd-api-testkit/src/main/java/dev/openfeature/contrib/tools/flagd/api/testkit/EvaluatorUtils.java
fi

echo "== search for Utils method name/call sites =="
rg -n "public static Object|convert|valueOf|Null|\"null\"" providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java tools/flagd-api-testkit/src/main/java/dev/openfeature/contrib/tools/flagd/api/testkit/EvaluatorUtils.java 2>/dev/null || true

echo "== git diff stat/name status if available =="
git diff --stat HEAD~1..HEAD 2>/dev/null || git status --short 2>/dev/null || true

Repository: open-feature/java-sdk-contrib

Length of output: 1944


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Behavioral probe: model the Java switch expression from utils/evaluator files using Java semantics.
# This avoids modifying repository files and keeps the probe read-only.
python3 - <<'PY'
class JavaLike:
    def convert(self, value, target_type):
        if target_type == "Null":
            return None
        if value == "null" and target_type != "String":
            return None
        if target_type == "Boolean":
            return bool(value.lower() == "true") if isinstance(value, str) else bool(value)
        if target_type == "Integer":
            if isinstance(value, str):
                if value in ("null", "Infinity", "-Infinity"):
                    raise ValueError("badIntFormat")
                return int(value)
            raise TypeError("non-string integer")
        if target_type == "Number":
            if isinstance(value, str):
                return float(value)
            raise TypeError("non-string number")
        if target_type == "String":
            return str(value)
        raise NotImplementedError(target_type)

cases = [
    (None, "Boolean"),
    ("null", "Boolean"),
    (None, "Integer"),
    ("null", "Integer"),
    (None, "Number"),
    ("null", "Number"),
    (None, "Object"),
    ("null", "Object"),
    (None, "String"),
    ("null", "String"),
]
for value, target_type in cases:
    try:
        out = JavaLike().convert(value, target_type)
    except Exception as ex:
        out = f"{type(ex).__name__}: {ex}"
    print(f"({value!r}, {target_type!r}) -> {out!r}")
PY

Repository: open-feature/java-sdk-contrib

Length of output: 1944


🌐 Web query:

openfeature/java-sdk-contrib tools/flagd-api-testkit/src/main/java/dev/openfeature/contrib/tools/flagd/api/testkit/EvaluatorUtils.java value == null

💡 Result:

The class EvaluatorUtils in the open-feature/java-sdk-contrib repository is part of the flagd-api-testkit toolset [1][2][3]. While specific code details regarding a null check on value are not explicitly documented in high-level summaries, the project frequently addresses null pointer exceptions (NPEs) and null handling across its tools and providers to ensure robustness in flag evaluation [4][5][6]. Recent updates in the flagd-related modules have focused on: - Improving null safety: Defensive null checks have been added to prevent NPEs in various evaluation contexts, such as when handling flag metadata [4][5]. - Consistent evaluation: Changes have been made to evaluation logic, such as treating DISABLED flags as successful evaluations [3][7] and fixing custom operator conformance edge cases that previously returned null on errors [6]. If you are investigating a specific null value behavior in EvaluatorUtils.java, it is likely related to these broader efforts to standardize how missing metadata, disabled flags, or evaluation errors are handled within the OpenFeature flagd ecosystem. For the most accurate technical analysis of the code at its current state, please refer to the source file directly on the main branch of the repository [8].

Citations:


🌐 Web query:

github.com/open-feature/java-sdk-contrib providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java

💡 Result:

The file Utils.java located in providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ is a helper class used within the end-to-end (E2E) testing suite for the OpenFeature flagd Java provider [1][2]. These E2E tests are based on the Gherkin feature files provided by the OpenFeature specification and rely on the flagd-testbed as a submodule to orchestrate the flagd environment (e.g., managing containers, dynamic flag updates) [3]. The Utils.java class typically provides common functionality required by the Cucumber step definitions, such as: 1. Interaction with the test-harness: Handling communication with the flagd instances or the launchpad utility [3]. 2. State Management: Assisting in managing the state of the OpenFeature API, such as setting or resetting the provider between test scenarios [4]. 3. Environment/Context Helpers: Providing utilities for configuring or retrieving context-specific information needed for flag evaluations within the E2E tests [5]. Recent changes to the E2E infrastructure in the java-sdk-contrib repository, including performance optimizations like a pre-warmed container pool, have updated how these tests execute in parallel [4]. Consequently, utility classes like Utils.java are essential for ensuring that test resources, such as container connections or OpenFeature SDK states, are handled safely and consistently across concurrent test executions [4].

Citations:


🌐 Web query:

github.com/open-feature/java-sdk-contrib raw providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java

💡 Result:

The file located at providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java is part of the flagd provider test suite within the open-feature/java-sdk-contrib repository [1][2]. This utility class typically contains helper methods used for end-to-end (e2e) testing of the flagd provider [3][2]. Recent development in the java-sdk-contrib repository has involved significant architectural changes to these e2e tests to support parallel execution and performance improvements [4]. Specifically, the repository utilizes Cucumber for its e2e testing, and utility classes like this are instrumental in managing test state, container lifecycles, and environment synchronization across test scenarios [2][4]. Due to the active nature of this repository, the contents of specific test utility files are frequently updated to accommodate new features, refactoring of test runners, and dependency management [2][4][5]. For the most up-to-date and accurate code, please refer directly to the official GitHub repository [6].

Citations:


🌐 Web query:

"EvaluatorUtils.java" "value == null" "flagd-api-testkit"

💡 Result:

The EvaluatorUtils.java file is a component within the OpenFeature flagd Java ecosystem, typically associated with utilities for the flagd-api-testkit or related evaluation logic [1][2][3]. In the context of the flagd-api-testkit and flagd Java evaluators, a check for value == null (often appearing in code like if (value == null)) is used to handle edge cases in flag evaluation [2]. These checks typically ensure that when a resolved flag value is null or missing, the system adheres to OpenFeature specifications regarding default values and error handling [4]. For example: 1. Compliance with Specifications: The flagd-api-testkit is used to verify that flagd evaluators correctly handle various scenarios, including those where values might be null, ensuring consistency across implementations [1][2]. 2. Default Value Handling: Recent developments have focused on allowing null or missing default values, where the system must return a specific result (such as FlagNotFound) if targeting does not resolve a variant and no default value is provided [4]. 3. Type Coercion: Evaluator utilities often include logic to coerce or validate resolved values [2]. Checks for null values are essential before performing type coercion to prevent NullPointerException errors and to correctly identify when a type mismatch or default path should be taken [2]. If you are encountering a specific issue or warning related to value == null in EvaluatorUtils.java within your test suite, it is likely part of the ongoing effort to improve operator conformance, null-handling, and spec compliance within the flagd Java ecosystem [5][6][7].

Citations:


🌐 Web query:

github.com/open-feature/java-sdk-contrib "Convert to" "Tools" "Null" "valueOf"

💡 Result:

There is no standard or known operation in the open-feature/java-sdk-contrib repository to "Convert to Tools Null valueOf." Research into the repository indicates that recent efforts have focused on fixing NullPointerExceptions [1] and improving operator conformance [2] within its tools modules, specifically within flagd-core components [1]. The term "valueOf" is a standard Java method often used for type conversion (e.g., String.valueOf(Object)) [3], but it is not associated with a specific migration or conversion utility in this context. If you are encountering an issue related to null handling or value conversion, it is likely related to general Java programming practices or specific bugs within the providers/tools, rather than a documented library feature. If you are looking to resolve null-related issues in your implementation, common approaches in the OpenFeature Java ecosystem include: 1. Ensuring proper null checks when accessing metadata or evaluation results [1]. 2. Using the standard SDK evaluation methods that support default values, which are designed to handle nulls safely [4]. 3. Reviewing project-specific pull requests (such as PR #1709 or #1778) for examples of how to safely handle nulls in the context of the java-sdk-contrib repository [2][1].

Citations:


Handle actual null values before type conversion.

This guard handles the "Null" type and the text "null", but not value == null. A null Boolean becomes false, while null numeric and object values fail. Add the actual-null check before the string sentinel check.

Proposed guard
 if ("Null".equals(type)) return null;
- if (Objects.equals(value, "null") && !"String".equals(type)) return null;
+ if (value == null || (Objects.equals(value, "null") && !"String".equals(type))) return null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ("Null".equals(type)) return null;
if (Objects.equals(value, "null") && !"String".equals(type)) return null;
if ("Null".equals(type)) return null;
if (value == null || (Objects.equals(value, "null") && !"String".equals(type))) return null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java`
around lines 18 - 19, Update the type-conversion logic in Utils to check value
== null before evaluating the "Null" type or the string "null" sentinel,
returning null immediately for actual null inputs. Preserve the existing
sentinel behavior for non-String types and leave the remaining type conversion
unchanged.

switch (type) {
case "Boolean":
return Boolean.parseBoolean(value);
case "String":
return value;
case "Integer":
return Integer.parseInt(value);
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return Long.parseLong(value);
}
case "Float":
return Double.parseDouble(value);
case "Long":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void we_have_an_option_of_type_with_value(String option, String type, Str
return;
}

Object converted = Utils.convert(value, type);
Object converted = ("null".equals(value) && "String".equals(type)) ? null : Utils.convert(value, type);
Method method = Arrays.stream(state.builder.getClass().getMethods())
.filter(method1 -> method1.getName().equals(mapOptionNames(option)))
.findFirst()
Expand All @@ -87,7 +87,7 @@ public void we_have_an_environment_variable_with_value(String varName, String va

@Then("the option {string} of type {string} should have the value {string}")
public void the_option_of_type_should_have_the_value(String option, String type, String value) throws Throwable {
Object convert = Utils.convert(value, type);
Object convert = ("null".equals(value) && "String".equals(type)) ? null : Utils.convert(value, type);

if (IGNORED_FOR_NOW.contains(option)) {
log.error("option '{}' is not supported", option);
Expand Down
2 changes: 1 addition & 1 deletion providers/flagd/test-harness
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import dev.openfeature.sdk.MutableContext;
import dev.openfeature.sdk.Value;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Given;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
Expand All @@ -25,13 +27,27 @@ public ContextSteps(EvaluatorState state) {
}

/** Adds a typed key/value pair to the evaluation context. */
@Given("a context containing a key {string}, with type {string} and with value {string}")
@Given("^a context containing a key \"([^\"]*)\", with type \"([^\"]*)\" and with value \"(.*)\"$")
public void contextKeyWithTypeAndValue(String key, String type, String value) throws IOException {
Map<String, Value> map = new HashMap<>(state.context.asMap());
map.put(key, Value.objectToValue(EvaluatorUtils.convert(value, type)));
state.context = new MutableContext(state.context.getTargetingKey(), map);
}

/** Adds multiple context keys from a data table. */
@Given("a context with the following keys:")
public void contextWithFollowingKeys(DataTable dataTable) throws IOException {
List<Map<String, String>> rows = dataTable.asMaps(String.class, String.class);
Map<String, Value> map = new HashMap<>(state.context.asMap());
for (Map<String, String> row : rows) {
String key = row.get("key");
String type = row.get("type");
String value = row.get("value");
map.put(key, Value.objectToValue(EvaluatorUtils.convert(value, type)));
}
state.context = new MutableContext(state.context.getTargetingKey(), map);
}

/** Sets the targeting key on the evaluation context. */
@Given("a context containing a targeting key with value {string}")
public void contextTargetingKey(String targetingKey) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public void flagEvaluatedWithDetails() {
state.evaluation = dev.openfeature.sdk.ProviderEvaluation.builder()
.errorCode(ErrorCode.TYPE_MISMATCH)
.errorMessage(e.getMessage())
.reason("ERROR")
.build();
} catch (dev.openfeature.sdk.exceptions.OpenFeatureError e) {
// Mirror the OpenFeature SDK client behaviour: on any provider error, return the
Expand All @@ -84,6 +85,7 @@ public void flagEvaluatedWithDetails() {
.value(state.defaultValue)
.errorCode(e.getErrorCode())
.errorMessage(e.getMessage())
.reason("ERROR")
.build();
}
}
Expand All @@ -94,7 +96,11 @@ public void resolvedValueEquals(String value) throws IOException {
if (state.evaluation.getErrorCode() != null) {
log.warning("Evaluation error: " + state.evaluation.getErrorMessage());
}
assertThat(state.evaluation.getValue()).isEqualTo(EvaluatorUtils.convert(value, state.flagType));
Object actualValue = state.evaluation.getValue();
if (actualValue == null && state.evaluation.getErrorCode() != null) {
actualValue = state.defaultValue;
}
assertThat(actualValue).isEqualTo(EvaluatorUtils.convert(value, state.flagType));
}

/** Asserts the evaluation reason matches the expected value. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ private EvaluatorUtils() {}
* @return the converted value, or {@code null} if {@code value} is "null" or empty for Object
*/
public static Object convert(String value, String type) throws IOException {
if (value == null || value.equals("null")) {
if ("Null".equals(type)) {
return null;
}
if (value == null || (value.equals("null") && !"String".equals(type))) {
Comment on lines +25 to +28

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the convert Javadoc to match the null contract.

The documentation omits the Null type. It also states that "null" and an empty Object value return null. The implementation preserves "null" for String and creates an empty object for an empty Object value.

Proposed documentation update
-     * `@param` type  the flag type name: Boolean, String, Integer, Float, or Object
-     * `@return` the converted value, or {`@code` null} if {`@code` value} is "null" or empty for Object
+     * `@param` type  the flag type name: Null, Boolean, String, Integer, Float, or Object
+     * `@return` the converted value; String preserves the literal "null", and Object
+     *         converts an empty value to an empty object
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tools/flagd-api-testkit/src/main/java/dev/openfeature/contrib/tools/flagd/api/testkit/EvaluatorUtils.java`
around lines 25 - 28, Update the convert method’s Javadoc to document the Null
type and accurately describe null handling: the literal "null" remains the
string value for String, while an empty Object value produces an empty object
rather than null.

return null;
}
switch (type) {
Expand Down
2 changes: 1 addition & 1 deletion tools/flagd-api-testkit/test-harness
6 changes: 6 additions & 0 deletions tools/flagd-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
<version>[1.0.0,2.0.0)</version>
</dependency>

<dependency>
<groupId>com.upokecenter</groupId>
<artifactId>cbor</artifactId>
<version>4.5.6</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
Expand Down
Loading
Loading