Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions guides/handling-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,30 @@ The list could also be partial populated and truncated because of a timeout.
<% end %>
```

## HTTP errors

By default, `GraphQL::Client::HTTP` turns HTTP statuses other than 200 and 400
into a GraphQL-style error, so a `503` shows up in `response.errors` like any
other error. Override `parse_response` to handle the HTTP response yourself,
for example to raise your own errors:

```ruby
HTTP = GraphQL::Client::HTTP.new("https://example.com/graphql") do
def parse_response(response)
case response
when Net::HTTPOK, Net::HTTPBadRequest
super
else
raise MyApp::UpstreamError, "HTTP #{response.code} #{response.message}"
end
end
end
```

`parse_response` receives the `Net::HTTPResponse` with status, headers and body
and returns the GraphQL result hash. Network and timeout errors are raised by
`Net::HTTP` before it is called.

## See also

* [graphql-js "path" field](https://github.com/graphql/graphql-js/blob/23592ad16868e06b1c003629759f905a77ab81a0/src/error/GraphQLError.js#L42-L48)
Expand Down
10 changes: 10 additions & 0 deletions lib/graphql/client/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ def execute(document:, operation_name: nil, variables: {}, context: {})

response = connection.request(request)
@last_response = response.to_hash
parse_response(response)
end

# Public: Extension point for subclasses to turn the HTTP response into a
# GraphQL result, e.g. to raise on unexpected statuses.
#
# response - The Net::HTTPResponse
#
# Returns { "data" => ... , "errors" => ... } Hash.
def parse_response(response)
case response
when Net::HTTPOK, Net::HTTPBadRequest
JSON.parse(response.body)
Expand Down
46 changes: 46 additions & 0 deletions test/test_http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require "graphql"
require "graphql/client/http"
require "minitest/autorun"
require "minitest/mock"

class TestHTTP < Minitest::Test
SWAPI = GraphQL::Client::HTTP.new("https://mpjk0plp9.lp.gql.zone/graphql") do
Expand Down Expand Up @@ -35,3 +36,48 @@ def test_execute
assert_equal(expected, actual)
end
end

class TestHTTPParseResponse < Minitest::Test
class UnexpectedStatus < StandardError; end

def http_response(status, body)
response = Net::HTTPResponse::CODE_TO_OBJ.fetch(status).new("1.1", status, "Test response")
response.instance_variable_set(:@read, true)
response.body = body
response
end

def execute(adapter, response)
connection = Minitest::Mock.new
connection.expect(:request, response, [Net::HTTP::Post])
result = adapter.stub(:connection, connection) do
adapter.execute(document: GraphQL.parse("{ name }"))
end
connection.verify
result
end

def test_default_response_handling
adapter = GraphQL::Client::HTTP.new("https://example.com/graphql")

assert_equal({ "data" => { "name" => "Luke" } }, execute(adapter, http_response("200", '{"data":{"name":"Luke"}}')))
assert_equal({ "errors" => [{ "message" => "Rejected" }] }, execute(adapter, http_response("400", '{"errors":[{"message":"Rejected"}]}')))
assert_equal({ "errors" => [{ "message" => "503 Test response" }] }, execute(adapter, http_response("503", "unavailable")))
end

def test_parse_response_can_be_overridden
adapter = GraphQL::Client::HTTP.new("https://example.com/graphql") do
def parse_response(response)
raise UnexpectedStatus, response.code unless response.is_a?(Net::HTTPOK)

super
end
end
response = http_response("503", "<html>Unavailable</html>")

error = assert_raises(UnexpectedStatus) { execute(adapter, response) }
assert_equal("503", error.message)
assert_equal(response.to_hash, adapter.last_response)
assert_equal({ "data" => { "name" => "Luke" } }, execute(adapter, http_response("200", '{"data":{"name":"Luke"}}')))
end
end
Loading