diff --git a/flipt-java/src/main/java/io/flipt/api/FliptClient.java b/flipt-java/src/main/java/io/flipt/api/FliptClient.java index 204c252..a684f5e 100644 --- a/flipt-java/src/main/java/io/flipt/api/FliptClient.java +++ b/flipt-java/src/main/java/io/flipt/api/FliptClient.java @@ -2,9 +2,11 @@ import io.flipt.api.authentication.AuthenticationStrategy; import io.flipt.api.evaluation.Evaluation; +import io.flipt.api.evaluation.EvaluationException; import java.time.Duration; import java.util.HashMap; import java.util.Map; +import java.util.function.Consumer; import okhttp3.OkHttpClient; public class FliptClient { @@ -18,6 +20,7 @@ private FliptClient(FliptClientBuilder builder) { .baseURL(builder.baseURL) .authenticationStrategy(builder.authenticationStrategy) .headers(builder.headers) + .setUnhandledExceptionProcessor(builder.unhandledExceptionProcessor) .build(); } @@ -31,10 +34,15 @@ public static FliptClientBuilder builder() { public static final class FliptClientBuilder { + private static final Consumer nullProcessor = + new Consumer() { + public void accept(EvaluationException e) {} + }; private String baseURL = "http://localhost:8080"; private AuthenticationStrategy authenticationStrategy; private Map headers = new HashMap<>(); private Duration timeout = Duration.ofSeconds(60); + private Consumer unhandledExceptionProcessor = nullProcessor; private FliptClientBuilder() {} @@ -63,6 +71,12 @@ public FliptClientBuilder timeout(int timeout) { return this; } + public FliptClientBuilder setUnhandledExceptionProcessor( + Consumer processor) { + this.unhandledExceptionProcessor = processor; + return this; + } + public FliptClient build() { return new FliptClient(this); } diff --git a/flipt-java/src/main/java/io/flipt/api/evaluation/Evaluation.java b/flipt-java/src/main/java/io/flipt/api/evaluation/Evaluation.java index 35d5671..bf68e64 100644 --- a/flipt-java/src/main/java/io/flipt/api/evaluation/Evaluation.java +++ b/flipt-java/src/main/java/io/flipt/api/evaluation/Evaluation.java @@ -13,6 +13,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.Map; +import java.util.function.Consumer; import okhttp3.*; public class Evaluation { @@ -21,6 +22,7 @@ public class Evaluation { private final AuthenticationStrategy authenticationStrategy; private final Map headers; private final ObjectMapper objectMapper; + private final Consumer unhandledExceptionProcessor; private Evaluation(EvaluationBuilder builder) { this.httpClient = builder.httpClient; @@ -32,6 +34,7 @@ private Evaluation(EvaluationBuilder builder) { .addModule(new Jdk8Module()) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); + this.unhandledExceptionProcessor = builder.unhandledExceptionProcessor; } public static EvaluationBuilder builder() { @@ -43,6 +46,7 @@ public static class EvaluationBuilder { private String baseURL; private AuthenticationStrategy authenticationStrategy; private Map headers; + private Consumer unhandledExceptionProcessor; private EvaluationBuilder() {} @@ -66,21 +70,90 @@ public EvaluationBuilder headers(Map headers) { return this; } + public EvaluationBuilder setUnhandledExceptionProcessor( + Consumer processor) { + this.unhandledExceptionProcessor = processor; + return this; + } + public Evaluation build() { return new Evaluation(this); } } + /** + * Evaluates a variant flag based on the {@link EvaluationRequest}. + * + * @param request the {@link EvaluationRequest} + * @return a {@link VariantEvaluationResponse} containing the evaluation result + * @throws EvaluationException if an error occurs during the process, such as a network issue or + * invalid request + */ public VariantEvaluationResponse evaluateVariant(EvaluationRequest request) throws EvaluationException { return this.makeRequest(request, "/evaluate/v1/variant", VariantEvaluationResponse.class); } + /** + * Evaluates a variant flag based on the {@link EvaluationRequest}. If the evaluation fails due to + * an {@link EvaluationException}, the specified fallback string will be returned. In the event of + * an exception, the exception is passed to an unhandled exception processor for further handling + * or logging. + * + * @param request the {@link EvaluationRequest} + * @param fallback the string value to return in case of an evaluation error + * @return the variant key if the evaluation is successful; otherwise, returns the fallback value + */ + public String variantValue(EvaluationRequest request, String fallback) { + try { + return this.evaluateVariant(request).getVariantKey(); + } catch (EvaluationException e) { + this.unhandledExceptionProcessor.accept(e); + } + + return fallback; + } + + /** + * Evaluates a boolean flag based on the {@link EvaluationRequest}. + * + * @param request the {@link EvaluationRequest} + * @return a {@link BooleanEvaluationResponse} containing the evaluation result + * @throws EvaluationException if an error occurs during the process, such as a network issue or + * invalid request + */ public BooleanEvaluationResponse evaluateBoolean(EvaluationRequest request) throws EvaluationException { return this.makeRequest(request, "/evaluate/v1/boolean", BooleanEvaluationResponse.class); } + /** + * Evaluates a boolean value based on the {@link EvaluationRequest}. If the evaluation fails due + * to an {@link EvaluationException}, the specified fallback boolean value will be returned. In + * the event of an exception, the exception is passed to an unhandled exception processor for + * further handling or logging. + * + * @param request the {@link EvaluationRequest} + * @param fallback the boolean value to return in case of an evaluation error + * @return the value if the evaluation is successful; otherwise, returns the fallback value + */ + public boolean booleanValue(EvaluationRequest request, boolean fallback) { + try { + return this.evaluateBoolean(request).isEnabled(); + } catch (EvaluationException e) { + this.unhandledExceptionProcessor.accept(e); + } + return fallback; + } + + /** + * Evaluates a batch of flags based on the {@link BatchEvaluationRequest}. + * + * @param request the {@link BatchEvaluationRequest} + * @return a {@link BatchEvaluationResponse} containing the evaluation results + * @throws EvaluationException if an error occurs during the evaluation process, such as a network + * issue or invalid request + */ public BatchEvaluationResponse evaluateBatch(BatchEvaluationRequest request) throws EvaluationException { return this.makeRequest(request, "/evaluate/v1/batch", BatchEvaluationResponse.class); diff --git a/flipt-java/src/test/java/TestFliptClient.java b/flipt-java/src/test/java/TestFliptClient.java index 6c861ff..52b00f1 100644 --- a/flipt-java/src/test/java/TestFliptClient.java +++ b/flipt-java/src/test/java/TestFliptClient.java @@ -30,18 +30,25 @@ void testVariantEvaluation() { Map context = new HashMap<>(); context.put("fizz", "buzz"); + EvaluationRequest req = + new EvaluationRequest("default", "flag1", "entity", context, Optional.empty()); + VariantEvaluationResponse variant = - Assertions.assertDoesNotThrow( - () -> - fc.evaluation() - .evaluateVariant( - new EvaluationRequest( - "default", "flag1", "entity", context, Optional.empty()))); + Assertions.assertDoesNotThrow(() -> fc.evaluation().evaluateVariant(req)); Assertions.assertTrue(variant.isMatch()); Assertions.assertEquals("flag1", variant.getFlagKey()); Assertions.assertEquals("MATCH_EVALUATION_REASON", variant.getReason().toString()); Assertions.assertEquals("variant1", variant.getVariantKey()); Assertions.assertEquals("segment1", variant.getSegmentKeys().get(0)); + + Assertions.assertEquals("variant1", fc.evaluation().variantValue(req, "fallback")); + + Assertions.assertEquals( + "fallback", + fc.evaluation() + .variantValue( + new EvaluationRequest("default", "flag-none", "entity", context, Optional.empty()), + "fallback")); } @Test @@ -67,17 +74,21 @@ void testBooleanEvaluation() { Map context = new HashMap<>(); context.put("fizz", "buzz"); + EvaluationRequest req = + new EvaluationRequest("default", "flag_boolean", "entity", context, Optional.empty()); BooleanEvaluationResponse booleanEvaluation = - Assertions.assertDoesNotThrow( - () -> - fc.evaluation() - .evaluateBoolean( - new EvaluationRequest( - "default", "flag_boolean", "entity", context, Optional.empty()))); + Assertions.assertDoesNotThrow(() -> fc.evaluation().evaluateBoolean(req)); Assertions.assertTrue(booleanEvaluation.isEnabled()); Assertions.assertEquals("flag_boolean", booleanEvaluation.getFlagKey()); Assertions.assertEquals("MATCH_EVALUATION_REASON", booleanEvaluation.getReason().toString()); + + Assertions.assertTrue(fc.evaluation().booleanValue(req, false)); + Assertions.assertTrue( + fc.evaluation() + .booleanValue( + new EvaluationRequest("default", "flag-none", "entity", context, Optional.empty()), + true)); } @Test