Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(java): resilient to network/http errors #320

Merged
merged 1 commit into from
Oct 25, 2024
Merged
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
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
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().variantValue(req, "fallback"));

Assertions.assertEquals(
"fallback",
fc.evaluation()
.variantValue(
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().booleanValue(req, false));
Assertions.assertTrue(
fc.evaluation()
.booleanValue(
new EvaluationRequest("default", "flag-none", "entity", context, Optional.empty()),
true));
}

@Test
Expand Down
Loading