Skip to content

Commit 3673d47

Browse files
feat: add support for request transformers (#72)
1 parent 189b44f commit 3673d47

File tree

19 files changed

+211
-13
lines changed

19 files changed

+211
-13
lines changed

hypertrace-core-graphql-impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies {
2222
implementation(project(":hypertrace-core-graphql-attribute-scope"))
2323
implementation(project(":hypertrace-core-graphql-rx-utils"))
2424
implementation(project(":hypertrace-core-graphql-log-event-schema"))
25+
implementation(project(":hypertrace-core-graphql-request-transformation"))
2526

2627
implementation("org.slf4j:slf4j-api")
2728
implementation("com.google.inject:guice")

hypertrace-core-graphql-impl/src/main/java/org/hypertrace/core/graphql/impl/GraphQlModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.hypertrace.core.graphql.deserialization.GraphQlDeserializationRegistryModule;
1010
import org.hypertrace.core.graphql.log.event.LogEventSchemaModule;
1111
import org.hypertrace.core.graphql.metadata.MetadataSchemaModule;
12+
import org.hypertrace.core.graphql.request.transformation.RequestTransformationModule;
1213
import org.hypertrace.core.graphql.rx.RxUtilModule;
1314
import org.hypertrace.core.graphql.schema.registry.GraphQlSchemaRegistryModule;
1415
import org.hypertrace.core.graphql.span.SpanSchemaModule;
@@ -48,5 +49,6 @@ protected void configure() {
4849
install(new TraceSchemaModule());
4950
install(new RxUtilModule());
5051
install(new LogEventSchemaModule());
52+
install(new RequestTransformationModule());
5153
}
5254
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
plugins {
2+
`java-library`
3+
jacoco
4+
id("org.hypertrace.jacoco-report-plugin")
5+
}
6+
7+
dependencies {
8+
api("com.google.inject:guice")
9+
api(project(":hypertrace-core-graphql-common-schema"))
10+
11+
testAnnotationProcessor("org.projectlombok:lombok")
12+
testCompileOnly("org.projectlombok:lombok")
13+
testImplementation("org.junit.jupiter:junit-jupiter")
14+
testImplementation("org.mockito:mockito-core")
15+
}
16+
17+
tasks.test {
18+
useJUnitPlatform()
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.hypertrace.core.graphql.request.transformation;
2+
3+
import io.reactivex.rxjava3.core.Single;
4+
import org.hypertrace.core.graphql.common.request.ContextualRequest;
5+
6+
public interface RequestTransformation {
7+
8+
/**
9+
* Any request may be provided to this method, which should return true if this transformation
10+
* supports the request and false otherwise.
11+
*/
12+
boolean supportsRequest(ContextualRequest request);
13+
14+
/**
15+
* Applies a transformation to the request. This will only receive requests that have previously
16+
* returned true from {@link #supportsRequest(ContextualRequest)}, and must always return a
17+
* request, potentially the same request passed in if no transformation is necessary.
18+
*/
19+
<T extends ContextualRequest> Single<T> transform(T request);
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.hypertrace.core.graphql.request.transformation;
2+
3+
import com.google.inject.AbstractModule;
4+
import com.google.inject.multibindings.Multibinder;
5+
6+
public class RequestTransformationModule extends AbstractModule {
7+
8+
@Override
9+
protected void configure() {
10+
bind(RequestTransformer.class).to(RequestTransformerImpl.class);
11+
Multibinder.newSetBinder(binder(), RequestTransformation.class);
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.hypertrace.core.graphql.request.transformation;
2+
3+
import io.reactivex.rxjava3.core.Single;
4+
import org.hypertrace.core.graphql.common.request.ContextualRequest;
5+
6+
/**
7+
* A request transformer that can receive any request and will apply all applicable transformations
8+
* that have been registered (currently, the order is undefined), returning the result.
9+
*/
10+
public interface RequestTransformer {
11+
<T extends ContextualRequest> Single<T> transform(T request);
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.hypertrace.core.graphql.request.transformation;
2+
3+
import io.reactivex.rxjava3.core.Observable;
4+
import io.reactivex.rxjava3.core.Single;
5+
import java.util.Set;
6+
import javax.inject.Inject;
7+
import org.hypertrace.core.graphql.common.request.ContextualRequest;
8+
9+
class RequestTransformerImpl implements RequestTransformer {
10+
private final Set<RequestTransformation> requestTransformations;
11+
12+
@Inject
13+
RequestTransformerImpl(Set<RequestTransformation> requestTransformations) {
14+
this.requestTransformations = requestTransformations;
15+
}
16+
17+
@Override
18+
public <T extends ContextualRequest> Single<T> transform(T request) {
19+
return Observable.fromIterable(requestTransformations)
20+
.filter(requestTransformation -> requestTransformation.supportsRequest(request))
21+
.reduce(
22+
Single.just(request), // Work in singles since there's no flat reduce, then unwrap later
23+
(currentRequestSingle, requestTransformation) ->
24+
currentRequestSingle.flatMap(requestTransformation::transform))
25+
.flatMap(wrapped -> wrapped);
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.hypertrace.core.graphql.request.transformation;
2+
3+
import static org.mockito.Mockito.mock;
4+
5+
import io.reactivex.rxjava3.core.Single;
6+
import io.reactivex.rxjava3.observers.TestObserver;
7+
import java.util.Set;
8+
import lombok.Value;
9+
import lombok.experimental.Accessors;
10+
import org.hypertrace.core.graphql.common.request.ContextualRequest;
11+
import org.hypertrace.core.graphql.context.GraphQlRequestContext;
12+
import org.junit.jupiter.api.Test;
13+
14+
class RequestTransformerImplTest {
15+
16+
@Test
17+
void noApplicableTransformerReturnsInput() {
18+
RequestTransformer transformer = new RequestTransformerImpl(Set.of());
19+
20+
ContextualRequest request = mock(ContextualRequest.class);
21+
22+
TestObserver<ContextualRequest> testObserver = new TestObserver<>();
23+
transformer.transform(request).subscribe(testObserver);
24+
25+
testObserver.assertResult(request);
26+
}
27+
28+
@Test
29+
void skipsUnsupportedTransformers() {
30+
GraphQlRequestContext context = mock(GraphQlRequestContext.class);
31+
RequestTransformer transformer =
32+
new RequestTransformerImpl(
33+
Set.of(
34+
new TestTransformation(true, "prefix-", ""),
35+
new TestTransformation(false, "", "-suffix")));
36+
37+
TestObserver<ContextualRequest> testObserver = new TestObserver<>();
38+
transformer.transform(new TestContextualRequest(context, "original")).subscribe(testObserver);
39+
40+
testObserver.assertResult(new TestContextualRequest(context, "prefix-original"));
41+
}
42+
43+
@Test
44+
void appliesAllTransformers() {
45+
GraphQlRequestContext context = mock(GraphQlRequestContext.class);
46+
RequestTransformer transformer =
47+
new RequestTransformerImpl(
48+
Set.of(
49+
new TestTransformation(true, "prefix-", ""),
50+
new TestTransformation(true, "", "-suffix")));
51+
52+
TestObserver<ContextualRequest> testObserver = new TestObserver<>();
53+
transformer.transform(new TestContextualRequest(context, "original")).subscribe(testObserver);
54+
55+
testObserver.assertResult(new TestContextualRequest(context, "prefix-original-suffix"));
56+
}
57+
58+
@Value
59+
private static class TestTransformation implements RequestTransformation {
60+
boolean supported;
61+
String prefixToAdd;
62+
String suffixToAdd;
63+
64+
@Override
65+
public boolean supportsRequest(ContextualRequest request) {
66+
return supported;
67+
}
68+
69+
@Override
70+
@SuppressWarnings("unchecked")
71+
public <T extends ContextualRequest> Single<T> transform(T request) {
72+
String previousValue = ((TestContextualRequest) request).value();
73+
TestContextualRequest newRequest =
74+
new TestContextualRequest(request.context(), prefixToAdd + previousValue + suffixToAdd);
75+
return Single.just((T) newRequest);
76+
}
77+
}
78+
79+
@Value
80+
@Accessors(fluent = true)
81+
private static class TestContextualRequest implements ContextualRequest {
82+
GraphQlRequestContext context;
83+
String value;
84+
}
85+
}

hypertrace-core-graphql-span-schema/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies {
2828
implementation(project(":hypertrace-core-graphql-deserialization"))
2929
implementation(project(":hypertrace-core-graphql-schema-utils"))
3030
implementation(project(":hypertrace-core-graphql-attribute-scope-constants"))
31+
implementation(project(":hypertrace-core-graphql-request-transformation"))
3132

3233
testImplementation("org.junit.jupiter:junit-jupiter")
3334
testImplementation("com.fasterxml.jackson.core:jackson-databind")

hypertrace-core-graphql-span-schema/src/main/java/org/hypertrace/core/graphql/span/dao/GatewayServiceSpanDao.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import javax.inject.Inject;
77
import javax.inject.Singleton;
88
import org.hypertrace.core.graphql.context.GraphQlRequestContext;
9+
import org.hypertrace.core.graphql.request.transformation.RequestTransformer;
910
import org.hypertrace.core.graphql.span.request.SpanRequest;
1011
import org.hypertrace.core.graphql.span.schema.SpanResultSet;
1112
import org.hypertrace.core.graphql.spi.config.GraphQlServiceConfig;
@@ -23,6 +24,7 @@ class GatewayServiceSpanDao implements SpanDao {
2324
private final GatewayServiceSpanConverter spanConverter;
2425
private final SpanLogEventDao spanLogEventDao;
2526
private final GraphQlServiceConfig serviceConfig;
27+
private final RequestTransformer requestTransformer;
2628

2729
@Inject
2830
GatewayServiceSpanDao(
@@ -31,19 +33,22 @@ class GatewayServiceSpanDao implements SpanDao {
3133
GrpcContextBuilder grpcContextBuilder,
3234
GatewayServiceSpanRequestBuilder requestBuilder,
3335
GatewayServiceSpanConverter spanConverter,
34-
SpanLogEventDao spanLogEventDao) {
36+
SpanLogEventDao spanLogEventDao,
37+
RequestTransformer requestTransformer) {
3538
this.grpcContextBuilder = grpcContextBuilder;
3639
this.requestBuilder = requestBuilder;
3740
this.spanConverter = spanConverter;
3841
this.spanLogEventDao = spanLogEventDao;
3942
this.gatewayServiceStub = gatewayServiceFutureStub;
4043
this.serviceConfig = serviceConfig;
44+
this.requestTransformer = requestTransformer;
4145
}
4246

4347
@Override
4448
public Single<SpanResultSet> getSpans(SpanRequest request) {
45-
return this.requestBuilder
46-
.buildRequest(request)
49+
return this.requestTransformer
50+
.transform(request)
51+
.flatMap(this.requestBuilder::buildRequest)
4752
.flatMap(
4853
serverRequest -> this.makeRequest(request.spanEventsRequest().context(), serverRequest))
4954
.flatMap(serverResponse -> spanLogEventDao.fetchLogEvents(request, serverResponse))

0 commit comments

Comments
 (0)