Skip to content

Commit fa5bc98

Browse files
adinauerclaude
andcommitted
feat(graphql): Apply outgoing response body policy
Use the Data Collection outgoing response body decision when attaching GraphQL execution results. Preserve the sendDefaultPii and maxRequestBodySize gate when Data Collection is absent. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 34dcac9 commit fa5bc98

5 files changed

Lines changed: 159 additions & 3 deletions

File tree

sentry-graphql-core/src/main/java/io/sentry/graphql/ExceptionReporter.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ private boolean isAllowedToAttachRequestBody(final @NotNull IScopes scopes) {
6262
}
6363

6464
private boolean isAllowedToAttachResponseBody(final @NotNull IScopes scopes) {
65-
final @NotNull SentryOptions options = scopes.getOptions();
66-
return options.isSendDefaultPii()
67-
&& !SentryOptions.RequestSize.NONE.equals(options.getMaxRequestBodySize());
65+
return scopes
66+
.getOptions()
67+
.getDataCollectionResolver()
68+
.isOutgoingResponseBodyWithLegacyBodyGate();
6869
}
6970

7071
private void setRequestDetailsOnEvent(

sentry-graphql-core/src/test/kotlin/io/sentry/graphql/ExceptionReporterTest.kt

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import graphql.schema.GraphQLObjectType
1212
import graphql.schema.GraphQLScalarType
1313
import graphql.schema.GraphQLSchema
1414
import io.sentry.Hint
15+
import io.sentry.HttpBodyType
1516
import io.sentry.IScope
1617
import io.sentry.IScopes
1718
import io.sentry.KeyValueCollectionBehavior
@@ -253,6 +254,124 @@ class ExceptionReporterTest {
253254
)
254255
}
255256

257+
@Test
258+
fun `legacy options can disable outgoing response data`() {
259+
val options = SentryOptions().also { it.maxRequestBodySize = SentryOptions.RequestSize.ALWAYS }
260+
val exceptionReporter = fixture.getSut(options)
261+
262+
exceptionReporter.captureThrowable(
263+
fixture.exception,
264+
ExceptionReporter.ExceptionDetails(
265+
fixture.scopes,
266+
fixture.instrumentationExecutionParameters,
267+
false,
268+
),
269+
fixture.executionResult,
270+
)
271+
272+
verify(fixture.scopes)
273+
.captureEvent(
274+
org.mockito.kotlin.check { assertNull(it.contexts.response) },
275+
any<Hint>(),
276+
)
277+
}
278+
279+
@Test
280+
fun `legacy request body size can disable outgoing response data`() {
281+
val options = SentryOptions().also { it.isSendDefaultPii = true }
282+
val exceptionReporter = fixture.getSut(options)
283+
284+
exceptionReporter.captureThrowable(
285+
fixture.exception,
286+
ExceptionReporter.ExceptionDetails(
287+
fixture.scopes,
288+
fixture.instrumentationExecutionParameters,
289+
false,
290+
),
291+
fixture.executionResult,
292+
)
293+
294+
verify(fixture.scopes)
295+
.captureEvent(
296+
org.mockito.kotlin.check { assertNull(it.contexts.response) },
297+
any<Hint>(),
298+
)
299+
}
300+
301+
@Test
302+
fun `data collection response ignores legacy request body options`() {
303+
val options =
304+
SentryOptions().also {
305+
it.maxRequestBodySize = SentryOptions.RequestSize.NONE
306+
it.dataCollection.httpBodies = setOf(HttpBodyType.OUTGOING_RESPONSE)
307+
}
308+
val exceptionReporter = fixture.getSut(options)
309+
310+
exceptionReporter.captureThrowable(
311+
fixture.exception,
312+
ExceptionReporter.ExceptionDetails(
313+
fixture.scopes,
314+
fixture.instrumentationExecutionParameters,
315+
false,
316+
),
317+
fixture.executionResult,
318+
)
319+
320+
verify(fixture.scopes)
321+
.captureEvent(
322+
org.mockito.kotlin.check { assertNotNull(it.contexts.response?.data) },
323+
any<Hint>(),
324+
)
325+
}
326+
327+
@Test
328+
fun `data collection can disable outgoing response data`() {
329+
val options = fixture.defaultOptions.also { it.dataCollection.httpBodies = emptySet() }
330+
val exceptionReporter = fixture.getSut(options)
331+
332+
exceptionReporter.captureThrowable(
333+
fixture.exception,
334+
ExceptionReporter.ExceptionDetails(
335+
fixture.scopes,
336+
fixture.instrumentationExecutionParameters,
337+
false,
338+
),
339+
fixture.executionResult,
340+
)
341+
342+
verify(fixture.scopes)
343+
.captureEvent(
344+
org.mockito.kotlin.check { assertNull(it.contexts.response) },
345+
any<Hint>(),
346+
)
347+
}
348+
349+
@Test
350+
fun `data collection namespace default enables outgoing response data`() {
351+
val options =
352+
SentryOptions().also {
353+
it.maxRequestBodySize = SentryOptions.RequestSize.NONE
354+
it.dataCollection.cookies = KeyValueCollectionBehavior.off()
355+
}
356+
val exceptionReporter = fixture.getSut(options)
357+
358+
exceptionReporter.captureThrowable(
359+
fixture.exception,
360+
ExceptionReporter.ExceptionDetails(
361+
fixture.scopes,
362+
fixture.instrumentationExecutionParameters,
363+
false,
364+
),
365+
fixture.executionResult,
366+
)
367+
368+
verify(fixture.scopes)
369+
.captureEvent(
370+
org.mockito.kotlin.check { assertNotNull(it.contexts.response?.data) },
371+
any<Hint>(),
372+
)
373+
}
374+
256375
@Test
257376
fun `does not attach query or variables if sendDefaultPii is false`() {
258377
val exceptionReporter =

sentry/api/sentry.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ public final class io/sentry/DataCollectionResolver {
435435
public fun isIncomingResponseBody ()Z
436436
public fun isOutgoingRequestBody ()Z
437437
public fun isOutgoingResponseBody ()Z
438+
public fun isOutgoingResponseBodyWithLegacyBodyGate ()Z
438439
public fun isUserInfo ()Z
439440
}
440441

sentry/src/main/java/io/sentry/DataCollectionResolver.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ public boolean isOutgoingResponseBody() {
104104
return isHttpBodyEnabled(HttpBodyType.OUTGOING_RESPONSE, options.isSendDefaultPii());
105105
}
106106

107+
public boolean isOutgoingResponseBodyWithLegacyBodyGate() {
108+
return isHttpBodyEnabled(HttpBodyType.OUTGOING_RESPONSE, isLegacyGraphqlBodyEnabled());
109+
}
110+
107111
private boolean isLegacyGraphqlBodyEnabled() {
108112
return options.isSendDefaultPii()
109113
&& !SentryOptions.RequestSize.NONE.equals(options.getMaxRequestBodySize());

sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,37 @@ class DataCollectionResolverTest {
128128
assertThat(options.dataCollectionResolver.isGraphqlVariablesWithLegacyBodyGate).isTrue()
129129
}
130130

131+
@Test
132+
fun `outgoing response legacy body variant preserves the legacy size gate`() {
133+
val options =
134+
SentryOptions().apply {
135+
isSendDefaultPii = true
136+
maxRequestBodySize = SentryOptions.RequestSize.NONE
137+
}
138+
139+
assertThat(options.dataCollectionResolver.isOutgoingResponseBodyWithLegacyBodyGate).isFalse()
140+
141+
options.maxRequestBodySize = SentryOptions.RequestSize.SMALL
142+
143+
assertThat(options.dataCollectionResolver.isOutgoingResponseBodyWithLegacyBodyGate).isTrue()
144+
}
145+
146+
@Test
147+
fun `outgoing response legacy body variant uses data collection when namespace is explicit`() {
148+
val options =
149+
SentryOptions().apply {
150+
isSendDefaultPii = false
151+
maxRequestBodySize = SentryOptions.RequestSize.NONE
152+
dataCollection.graphql.setDocument(true)
153+
}
154+
155+
assertThat(options.dataCollectionResolver.isOutgoingResponseBodyWithLegacyBodyGate).isTrue()
156+
157+
options.dataCollection.httpBodies = emptySet()
158+
159+
assertThat(options.dataCollectionResolver.isOutgoingResponseBodyWithLegacyBodyGate).isFalse()
160+
}
161+
131162
@Test
132163
fun `GraphQL legacy body variants ignore the size option when namespace is explicit`() {
133164
val options =

0 commit comments

Comments
 (0)