Skip to content

Commit 0fa2c4f

Browse files
committed
Do not override custom local context in observation instrumentation
Prior to this commit, the `GraphQlObservationInstrumentation` would always assume that local context instances are of type `GraphQLContext`. This means that a custom context type would be overwritten with a `GraphQLContext` that contains the current observation. Instead, this commit ensures that a local context is contributed only if none was present, or that we wrap the existing one only if it is a `GraphQLContext` in the first place. If the parent data fetcher contributes a custom context, the current observation will not be added. Fixes gh-918
1 parent 9d735b9 commit 0fa2c4f

File tree

2 files changed

+60
-10
lines changed

2 files changed

+60
-10
lines changed

spring-graphql/src/main/java/org/springframework/graphql/observation/GraphQlObservationInstrumentation.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,15 +190,19 @@ private static Observation getCurrentObservation(DataFetchingEnvironment environ
190190
}
191191

192192
private static DataFetchingEnvironment wrapDataFetchingEnvironment(DataFetchingEnvironment environment, Observation dataFetcherObservation) {
193-
GraphQLContext.Builder localContextBuilder = GraphQLContext.newContext();
194-
if (environment.getLocalContext() instanceof GraphQLContext localContext) {
195-
localContextBuilder.of(localContext);
193+
if (environment.getLocalContext() == null || environment.getLocalContext() instanceof GraphQLContext) {
194+
GraphQLContext.Builder localContextBuilder = GraphQLContext.newContext();
195+
if (environment.getLocalContext() instanceof GraphQLContext localContext) {
196+
localContextBuilder.of(localContext);
197+
}
198+
localContextBuilder.of(ObservationThreadLocalAccessor.KEY, dataFetcherObservation);
199+
return DataFetchingEnvironmentImpl
200+
.newDataFetchingEnvironment(environment)
201+
.localContext(localContextBuilder.build())
202+
.build();
196203
}
197-
localContextBuilder.of(ObservationThreadLocalAccessor.KEY, dataFetcherObservation);
198-
return DataFetchingEnvironmentImpl
199-
.newDataFetchingEnvironment(environment)
200-
.localContext(localContextBuilder.build())
201-
.build();
204+
// do not wrap environment, there is an existing custom context
205+
return environment;
202206
}
203207

204208

spring-graphql/src/test/java/org/springframework/graphql/observation/GraphQlObservationInstrumentationTests.java

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2023 the original author or authors.
2+
* Copyright 2020-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,7 +31,15 @@
3131
import org.junit.jupiter.params.ParameterizedTest;
3232
import org.junit.jupiter.params.provider.Arguments;
3333
import org.junit.jupiter.params.provider.MethodSource;
34-
import org.springframework.graphql.*;
34+
35+
import org.springframework.graphql.Author;
36+
import org.springframework.graphql.Book;
37+
import org.springframework.graphql.BookSource;
38+
import org.springframework.graphql.ExecutionGraphQlRequest;
39+
import org.springframework.graphql.ExecutionGraphQlResponse;
40+
import org.springframework.graphql.GraphQlSetup;
41+
import org.springframework.graphql.ResponseHelper;
42+
import org.springframework.graphql.TestExecutionRequest;
3543
import org.springframework.graphql.execution.DataFetcherExceptionResolver;
3644
import org.springframework.graphql.execution.ErrorType;
3745
import reactor.core.publisher.Mono;
@@ -319,4 +327,42 @@ void shouldNotOverrideExistingLocalContext() {
319327
ResponseHelper.forResponse(responseMono);
320328
}
321329

330+
@Test
331+
void shouldNotOverrideCustomLocalContext() {
332+
333+
String document = """
334+
{
335+
bookById(id: 1) {
336+
author {
337+
firstName,
338+
lastName
339+
}
340+
}
341+
}
342+
""";
343+
DataFetcher<DataFetcherResult<Object>> bookDataFetcher = environment -> DataFetcherResult.newResult()
344+
.data(BookSource.getBook(1L))
345+
.localContext(new CustomLocalContext())
346+
.build();
347+
DataFetcher<Author> authorDataFetcher = environment -> BookSource.getAuthor(101L);
348+
DataFetcher<String> authorFirstNameDataFetcher = environment -> {
349+
Object context = environment.getLocalContext();
350+
assertThat(context).isInstanceOf(CustomLocalContext.class);
351+
return BookSource.getAuthor(101L).getFirstName();
352+
};
353+
354+
ExecutionGraphQlRequest request = TestExecutionRequest.forDocument(document);
355+
Mono<ExecutionGraphQlResponse> responseMono = graphQlSetup
356+
.queryFetcher("bookById", bookDataFetcher)
357+
.dataFetcher("Book", "author", authorDataFetcher)
358+
.dataFetcher("Author", "firstName", authorFirstNameDataFetcher)
359+
.toGraphQlService()
360+
.execute(request);
361+
ResponseHelper.forResponse(responseMono);
362+
}
363+
364+
static class CustomLocalContext {
365+
366+
}
367+
322368
}

0 commit comments

Comments
 (0)