Skip to content

Commit f5fb5cf

Browse files
feat: Add hooks support (#300)
**Requirements** - [x] I have added test coverage for new or changed functionality - [x] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests) - [x] I have validated my changes against all supported platform versions **Describe the solution you've provided** Added hooks support
1 parent 8d92eca commit f5fb5cf

File tree

25 files changed

+1778
-15
lines changed

25 files changed

+1778
-15
lines changed

contract-tests/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies {
2828
// https://mvnrepository.com/artifact/org.nanohttpd/nanohttpd
2929
implementation("org.nanohttpd:nanohttpd:2.3.1")
3030
implementation("com.google.code.gson:gson:2.8.9")
31+
implementation("com.squareup.okhttp3:okhttp:4.9.2")
3132
implementation(project(":launchdarkly-android-client-sdk"))
3233
// Comment the previous line and uncomment this one to depend on the published artifact:
3334
//implementation("com.launchdarkly:launchdarkly-android-client-sdk:3.1.5")
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.launchdarkly.sdktest;
2+
3+
import okhttp3.MediaType;
4+
import okhttp3.Request;
5+
import okhttp3.RequestBody;
6+
import okhttp3.Response;
7+
8+
import java.net.URI;
9+
10+
public class HookCallbackService {
11+
private final URI serviceUri;
12+
13+
public HookCallbackService(URI serviceUri) {
14+
this.serviceUri = serviceUri;
15+
}
16+
17+
public void post(Object params) {
18+
RequestBody body = RequestBody.create(
19+
TestService.gson.toJson(params == null ? "{}" : params),
20+
MediaType.parse("application/json")
21+
);
22+
Request request = new Request.Builder().url(serviceUri.toString())
23+
.method("POST", body)
24+
.build();
25+
try (Response response = TestService.client.newCall(request).execute()) {
26+
assertOk(response);
27+
} catch (Exception e) {
28+
throw new RuntimeException(e);
29+
}
30+
}
31+
32+
private void assertOk(Response response) {
33+
if (!response.isSuccessful()) {
34+
String body = "";
35+
if (response.body() != null) {
36+
try {
37+
body = ": " + response.body().string();
38+
} catch (Exception e) {}
39+
}
40+
throw new RuntimeException("HTTP error " + response.code() + " from callback to " + serviceUri + body);
41+
}
42+
}
43+
}

contract-tests/src/main/java/com/launchdarkly/sdktest/Representations.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package com.launchdarkly.sdktest;
22

33
import com.google.gson.annotations.SerializedName;
4+
import com.launchdarkly.sdk.EvaluationDetail;
45
import com.launchdarkly.sdk.EvaluationReason;
56
import com.launchdarkly.sdk.LDContext;
67
import com.launchdarkly.sdk.LDValue;
8+
import com.launchdarkly.sdk.android.integrations.EvaluationSeriesContext;
9+
import com.launchdarkly.sdk.android.integrations.TrackSeriesContext;
710

11+
import java.net.URI;
12+
import java.util.List;
813
import java.util.Map;
914

1015
/**
@@ -33,6 +38,7 @@ public static class SdkConfigParams {
3338
SdkConfigTagParams tags;
3439
SdkConfigClientSideParams clientSide;
3540
SdkConfigServiceEndpointParams serviceEndpoints;
41+
SdkConfigHookParams hooks;
3642
}
3743

3844
public static class SdkConfigStreamParams {
@@ -74,6 +80,28 @@ public static class SdkConfigClientSideParams {
7480
boolean includeEnvironmentAttributes;
7581
}
7682

83+
public static class SdkConfigHookParams {
84+
List<HookConfig> hooks;
85+
}
86+
87+
public static class HookConfig {
88+
String name;
89+
URI callbackUri;
90+
HookData data;
91+
HookErrors errors;
92+
}
93+
94+
public static class HookData {
95+
Map<String, Object> beforeEvaluation;
96+
Map<String, Object> afterEvaluation;
97+
}
98+
99+
public static class HookErrors {
100+
String beforeEvaluation;
101+
String afterEvaluation;
102+
String afterTrack;
103+
}
104+
77105
public static class CommandParams {
78106
String command;
79107
EvaluateFlagParams evaluate;
@@ -107,6 +135,18 @@ public static class EvaluateAllFlagsResponse {
107135
Map<String, LDValue> state;
108136
}
109137

138+
public static class EvaluationSeriesCallbackParams {
139+
EvaluationSeriesContext evaluationSeriesContext;
140+
Map<String, Object> evaluationSeriesData;
141+
EvaluationDetail<LDValue> evaluationDetail;
142+
String stage;
143+
}
144+
145+
public static class TrackSeriesCallbackParams {
146+
TrackSeriesContext trackSeriesContext;
147+
String stage;
148+
}
149+
110150
public static class IdentifyEventParams {
111151
LDContext context;
112152
}

contract-tests/src/main/java/com/launchdarkly/sdktest/SdkClientEntity.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import com.launchdarkly.sdk.android.integrations.ApplicationInfoBuilder;
1717
import com.launchdarkly.sdk.android.integrations.EventProcessorBuilder;
18+
import com.launchdarkly.sdk.android.integrations.Hook;
1819
import com.launchdarkly.sdk.android.integrations.PollingDataSourceBuilder;
1920
import com.launchdarkly.sdk.android.integrations.StreamingDataSourceBuilder;
2021
import com.launchdarkly.sdk.android.integrations.ServiceEndpointsBuilder;
@@ -31,12 +32,17 @@
3132
import com.launchdarkly.sdktest.Representations.EvaluateAllFlagsResponse;
3233
import com.launchdarkly.sdktest.Representations.EvaluateFlagParams;
3334
import com.launchdarkly.sdktest.Representations.EvaluateFlagResponse;
35+
import com.launchdarkly.sdktest.Representations.HookConfig;
36+
import com.launchdarkly.sdktest.Representations.HookData;
37+
import com.launchdarkly.sdktest.Representations.HookErrors;
3438
import com.launchdarkly.sdktest.Representations.IdentifyEventParams;
3539
import com.launchdarkly.sdktest.Representations.SdkConfigParams;
3640

3741
import android.app.Application;
3842

3943
import java.io.IOException;
44+
import java.util.ArrayList;
45+
import java.util.List;
4046
import java.util.concurrent.ExecutionException;
4147
import java.util.concurrent.Future;
4248
import java.util.Map;
@@ -336,9 +342,34 @@ private LDConfig buildSdkConfig(SdkConfigParams params, LDLogAdapter logAdapter,
336342
endpoints.events(params.serviceEndpoints.events);
337343
}
338344
}
339-
340345
builder.serviceEndpoints(endpoints);
341346

347+
if (params.hooks != null && params.hooks.hooks != null) {
348+
List<Hook> hookList = new ArrayList<>();
349+
for (HookConfig hookConfig : params.hooks.hooks) {
350+
HookCallbackService callbackService = new HookCallbackService(hookConfig.callbackUri);
351+
352+
HookData data = new HookData();
353+
data.beforeEvaluation = hookConfig.data != null ? hookConfig.data.beforeEvaluation : null;
354+
data.afterEvaluation = hookConfig.data != null ? hookConfig.data.afterEvaluation : null;
355+
356+
HookErrors errors = new HookErrors();
357+
errors.beforeEvaluation = hookConfig.errors != null ? hookConfig.errors.beforeEvaluation : null;
358+
errors.afterEvaluation = hookConfig.errors != null ? hookConfig.errors.afterEvaluation : null;
359+
errors.afterTrack = hookConfig.errors != null ? hookConfig.errors.afterTrack : null;
360+
361+
TestHook testHook = new TestHook(
362+
hookConfig.name,
363+
callbackService,
364+
data,
365+
errors
366+
);
367+
368+
hookList.add(testHook);
369+
}
370+
builder.hooks(Components.hooks().setHooks(hookList));
371+
}
372+
342373
return builder.build();
343374
}
344375

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.launchdarkly.sdktest;
2+
3+
import com.launchdarkly.sdk.EvaluationDetail;
4+
import com.launchdarkly.sdk.LDValue;
5+
import com.launchdarkly.sdk.android.integrations.EvaluationSeriesContext;
6+
import com.launchdarkly.sdk.android.integrations.Hook;
7+
8+
import com.launchdarkly.sdk.android.integrations.TrackSeriesContext;
9+
import com.launchdarkly.sdktest.Representations.EvaluationSeriesCallbackParams;
10+
import com.launchdarkly.sdktest.Representations.TrackSeriesCallbackParams;
11+
import com.launchdarkly.sdktest.Representations.HookData;
12+
import com.launchdarkly.sdktest.Representations.HookErrors;
13+
14+
import java.util.Collections;
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
18+
public class TestHook extends Hook {
19+
private final HookCallbackService callbackService;
20+
private final HookData hookData;
21+
private final HookErrors hookErrors;
22+
23+
public TestHook(String name, HookCallbackService callbackService, HookData data, HookErrors errors) {
24+
super(name);
25+
this.callbackService = callbackService;
26+
this.hookData = data;
27+
this.hookErrors = errors;
28+
}
29+
30+
@Override
31+
public Map<String, Object> beforeEvaluation(EvaluationSeriesContext seriesContext, Map<String, Object> data) {
32+
if (hookErrors.beforeEvaluation != null) {
33+
throw new RuntimeException(hookErrors.beforeEvaluation);
34+
}
35+
36+
EvaluationSeriesCallbackParams params = new EvaluationSeriesCallbackParams();
37+
params.evaluationSeriesContext = seriesContext;
38+
params.evaluationSeriesData = data;
39+
params.stage = "beforeEvaluation";
40+
41+
callbackService.post(params);
42+
43+
Map<String, Object> newData = new HashMap<>(data);
44+
if (hookData.beforeEvaluation != null) {
45+
newData.putAll(hookData.beforeEvaluation);
46+
}
47+
48+
return Collections.unmodifiableMap(newData);
49+
}
50+
51+
@Override
52+
public Map<String, Object> afterEvaluation(EvaluationSeriesContext seriesContext, Map<String, Object> data, EvaluationDetail<LDValue> evaluationDetail) {
53+
if (hookErrors.afterEvaluation != null) {
54+
throw new RuntimeException(hookErrors.afterEvaluation);
55+
}
56+
57+
EvaluationSeriesCallbackParams params = new EvaluationSeriesCallbackParams();
58+
params.evaluationSeriesContext = seriesContext;
59+
params.evaluationSeriesData = data;
60+
params.evaluationDetail = evaluationDetail;
61+
params.stage = "afterEvaluation";
62+
63+
callbackService.post(params);
64+
65+
Map<String, Object> newData = new HashMap<>();
66+
if (hookData.afterEvaluation != null) {
67+
newData.putAll(hookData.afterEvaluation);
68+
}
69+
70+
return Collections.unmodifiableMap(newData);
71+
}
72+
73+
@Override
74+
public void afterTrack(TrackSeriesContext seriesContext) {
75+
if (hookErrors.afterTrack != null) {
76+
throw new RuntimeException(hookErrors.afterTrack);
77+
}
78+
79+
TrackSeriesCallbackParams params = new TrackSeriesCallbackParams();
80+
params.trackSeriesContext = seriesContext;
81+
params.stage = "afterTrack";
82+
83+
callbackService.post(params);
84+
}
85+
}

contract-tests/src/main/java/com/launchdarkly/sdktest/TestService.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.regex.Pattern;
2424

2525
import fi.iki.elonen.NanoHTTPD;
26+
import okhttp3.OkHttpClient;
2627

2728
public class TestService extends NanoHTTPD {
2829
private static final int PORT = 8001;
@@ -36,13 +37,17 @@ public class TestService extends NanoHTTPD {
3637
"auto-env-attributes",
3738
"inline-context-all",
3839
"anonymous-redaction",
39-
"client-prereq-events"
40+
"client-prereq-events",
41+
"evaluation-hooks",
42+
"track-hooks"
4043
};
4144
private static final String MIME_JSON = "application/json";
4245
static final Gson gson = new GsonBuilder()
4346
.registerTypeAdapterFactory(LDGson.typeAdapters())
4447
.create();
4548

49+
static final OkHttpClient client = new OkHttpClient();
50+
4651
private final Router router = new Router();
4752
private final Application application;
4853
private final LDLogAdapter logAdapter;

0 commit comments

Comments
 (0)