Skip to content

Commit

Permalink
refactor(java): resilient to network/http errors
Browse files Browse the repository at this point in the history
Signed-off-by: Roman Dmytrenko <[email protected]>
  • Loading branch information
flipt-release-bot[bot] authored and erka committed Oct 25, 2024
1 parent ef850c7 commit c19d8dd
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 12 deletions.
14 changes: 14 additions & 0 deletions flipt-java/src/main/java/io/flipt/api/FliptClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -18,6 +20,7 @@ private FliptClient(FliptClientBuilder builder) {
.baseURL(builder.baseURL)
.authenticationStrategy(builder.authenticationStrategy)
.headers(builder.headers)
.setUnhandledExceptionProcessor(builder.unhandledExceptionProcessor)
.build();
}

Expand All @@ -31,10 +34,15 @@ public static FliptClientBuilder builder() {

public static final class FliptClientBuilder {

private static final Consumer<EvaluationException> nullProcessor =
new Consumer<EvaluationException>() {
public void accept(EvaluationException e) {}
};
private String baseURL = "http://localhost:8080";
private AuthenticationStrategy authenticationStrategy;
private Map<String, String> headers = new HashMap<>();
private Duration timeout = Duration.ofSeconds(60);
private Consumer<EvaluationException> unhandledExceptionProcessor = nullProcessor;

private FliptClientBuilder() {}

Expand Down Expand Up @@ -63,6 +71,12 @@ public FliptClientBuilder timeout(int timeout) {
return this;
}

public FliptClientBuilder setUnhandledExceptionProcessor(
Consumer<EvaluationException> processor) {
this.unhandledExceptionProcessor = processor;
return this;
}

public FliptClient build() {
return new FliptClient(this);
}
Expand Down
73 changes: 73 additions & 0 deletions flipt-java/src/main/java/io/flipt/api/evaluation/Evaluation.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -21,6 +22,7 @@ public class Evaluation {
private final AuthenticationStrategy authenticationStrategy;
private final Map<String, String> headers;
private final ObjectMapper objectMapper;
private final Consumer<EvaluationException> unhandledExceptionProcessor;

private Evaluation(EvaluationBuilder builder) {
this.httpClient = builder.httpClient;
Expand All @@ -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() {
Expand All @@ -43,6 +46,7 @@ public static class EvaluationBuilder {
private String baseURL;
private AuthenticationStrategy authenticationStrategy;
private Map<String, String> headers;
private Consumer<EvaluationException> unhandledExceptionProcessor;

private EvaluationBuilder() {}

Expand All @@ -66,21 +70,90 @@ public EvaluationBuilder headers(Map<String, String> headers) {
return this;
}

public EvaluationBuilder setUnhandledExceptionProcessor(
Consumer<EvaluationException> 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);
Expand Down
35 changes: 23 additions & 12 deletions flipt-java/src/test/java/TestFliptClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,25 @@ void testVariantEvaluation() {
Map<String, String> 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().evaluateVariantValue(req, "fallback"));

Assertions.assertEquals(
"fallback",
fc.evaluation()
.evaluateVariantValue(
new EvaluationRequest("default", "flag-none", "entity", context, Optional.empty()),
"fallback"));
}

@Test
Expand All @@ -67,17 +74,21 @@ void testBooleanEvaluation() {
Map<String, String> 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().evaluateBooleanValue(req, false));
Assertions.assertTrue(
fc.evaluation()
.evaluateBooleanValue(
new EvaluationRequest("default", "flag-none", "entity", context, Optional.empty()),
true));
}

@Test
Expand Down

0 comments on commit c19d8dd

Please sign in to comment.