<#3684 fix: Surface GraphQL errors returned in the...
# meltano-repo-activity
g
#3684 fix: Surface GraphQL errors returned in the response body Pull request opened by Poudel-Sanskriti What does this PR do? This adds a
validate_response
override to
GraphQLStream
so that errors reported in a GraphQL response body are no longer silently ignored. After the existing HTTP status-code checks run, the method inspects the response body for an
errors
key. If errors are present and
data
is null, it raises
FatalAPIError
with the GraphQL error messages. If errors are present but
data
is also returned (partial success), it logs a warning and allows the sync to continue so usable records are not discarded. The validation is also defensive about malformed responses: • A body that is not valid JSON raises
FatalAPIError
(chained from the original decode error) instead of propagating a raw
JSONDecodeError
. • A body that is JSON but not an object raises
FatalAPIError
instead of being treated as valid. • Nonstandard
errors
shapes (a non-list value, entries that are not objects, or objects without a
message
key) are stringified into the error message instead of being silently dropped. Why was this PR needed? GraphQL APIs return HTTP 200 even when the request fails, placing the actual error inside the response body under an
errors
key (for example
{"errors": [{"message": "Your token has insufficient scope"}], "data": null}
).
GraphQLStream
inherited
validate_response
from
RESTStream
, which only inspects the HTTP status code. Because the status was 200, these errors passed validation, the stream extracted zero records, and the sync completed with no error or warning. The reporter in the linked issue hit exactly this: a token with insufficient scope produced silent data loss that was difficult to diagnose. This change makes the failure visible at the point it occurs. Design choices: • The override lives in
GraphQLStream
rather than
RESTStream
, because body-level
errors
semantics are specific to GraphQL and should not affect plain REST streams. • Total failure (
data
is null) raises, while partial success (
data
present alongside
errors
) warns and continues, because the GraphQL spec allows responses that carry both errors and usable data. Taps that need stricter or retriable handling can override the method. • Malformed bodies (non-JSON or non-object) raise rather than pass silently, in line with the review feedback given on #3668. What are the relevant issue numbers? Closes #1421 Screenshots / Recordings (if applicable) Tests in
tests/core/rest/test_graphql.py
cover error-only, multi-error, partial-success, clean, empty-errors, nonstandard error shapes, non-JSON body, and non-object body responses: $ uv run pytest tests/core/rest/test_graphql.py -q ........... [100%] 11 passed in 0.06s The behavior was also verified end to end with a minimal tap. Against the live countries GraphQL API, a query requesting an invalid field now aborts the sync with
FatalAPIError: GraphQL errors in response: Cannot query field "bogusField" on type "Country".
, where previously the same sync completed "successfully" with zero records. Does this PR meet the acceptance criteria? • Tests added for new/changed behavior • All tests passing • Follows project style guide • No breaking changes introduced • Documentation updated (if applicable) - not needed; behavior is internal to response validation Summary by Sourcery Surface GraphQL body-level errors during response validation to avoid silent failures when HTTP status is 200. Bug Fixes: • Raise a FatalAPIError when a GraphQL response contains errors and no data, preventing silent data loss. • Log a warning instead of failing when GraphQL responses include both errors and usable data, allowing partial results to be processed. Tests: • Add GraphQLStream response validation tests covering error-only, partial-success, and success scenarios, including logging behavior. meltano/sdk