Skip to content

Commit 0ab6586

Browse files
authored
Merge pull request #345 from stokarenko/better-api-errors
Track the real HTTP reason of ApiErrors
2 parents 5c6745c + 60e2a64 commit 0ab6586

File tree

7 files changed

+92
-20
lines changed

7 files changed

+92
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- [#345](https://github.com/JsonApiClient/json_api_client/pull/345) - track the real HTTP reason of ApiErrors
6+
57
## 1.11.0
68

79
- [#344](https://github.com/JsonApiClient/json_api_client/pull/344) - introduce safe singular resource fetching with `raise_on_blank_find_param` resource setting

README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This gem is meant to help you build an API client for interacting with REST APIs as laid out by [http://jsonapi.org](http://jsonapi.org). It attempts to give you a query building framework that is easy to understand (it is similar to ActiveRecord scopes).
44

5-
*Note: master is currently tracking the 1.0.0 specification. If you're looking for the older code, see [0.x branch](https://github.com/chingor13/json_api_client/tree/0.x)*
5+
*Note: master is currently tracking the 1.0.0 specification. If you're looking for the older code, see [0.x branch](https://github.com/JsonApiClient/json_api_client/tree/0.x)*
66

77
## Usage
88

@@ -474,6 +474,14 @@ module MyApi
474474
end
475475
```
476476
477+
##### Server errors handling
478+
479+
Non-success API response will cause the specific `JsonApiClient::Errors::SomeException` raised, depends on responded HTTP status.
480+
Please refer to [JsonApiClient::Middleware::Status#handle_status](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/middleware/status.rb)
481+
method for concrete status-to-exception mapping used out of the box.
482+
483+
JsonApiClient will try determine is failed API response JsonApi-compatible, if so - JsonApi error messages will be parsed from response body, and tracked as a part of particular exception message. In additional, `JsonApiClient::Errors::ServerError` exception will keep the actual HTTP status and message within its message.
484+
477485
##### Custom status handler
478486
479487
You can change handling of response status using `connection_options`. For example you can override 400 status handling.
@@ -569,7 +577,7 @@ end
569577
570578
You can customize how your resources find pagination information from the response.
571579
572-
If the [existing paginator](https://github.com/chingor13/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) fits your requirements but you don't use the default `page` and `per_page` params for pagination, you can customise the param keys as follows:
580+
If the [existing paginator](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) fits your requirements but you don't use the default `page` and `per_page` params for pagination, you can customise the param keys as follows:
573581
574582
```ruby
575583
JsonApiClient::Paginating::Paginator.page_param = "number"
@@ -578,7 +586,7 @@ JsonApiClient::Paginating::Paginator.per_page_param = "size"
578586
579587
Please note that this is a global configuration, so library authors should create a custom paginator that inherits `JsonApiClient::Paginating::Paginator` and configure the custom paginator to avoid modifying global config.
580588
581-
If the [existing paginator](https://github.com/chingor13/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) does not fit your needs, you can create a custom paginator:
589+
If the [existing paginator](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) does not fit your needs, you can create a custom paginator:
582590
583591
```ruby
584592
class MyPaginator
@@ -680,4 +688,4 @@ required. The commits will be squashed into master once accepted.
680688
681689
## Changelog
682690
683-
See [changelog](https://github.com/chingor13/json_api_client/blob/master/CHANGELOG.md)
691+
See [changelog](https://github.com/JsonApiClient/json_api_client/blob/master/CHANGELOG.md)

json_api_client.gemspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ Gem::Specification.new do |s|
1616
s.add_dependency "faraday_middleware", '~> 0.9'
1717
s.add_dependency "addressable", '~> 2.2'
1818
s.add_dependency "activemodel", '>= 3.2.0'
19+
s.add_dependency "rack", '>= 0.2'
1920

20-
s.add_development_dependency "webmock"
21+
s.add_development_dependency "webmock", '~> 3.5.1'
2122
s.add_development_dependency "mocha"
2223

2324
s.license = "MIT"

lib/json_api_client/errors.rb

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
1+
require 'rack'
2+
13
module JsonApiClient
24
module Errors
35
class ApiError < StandardError
46
attr_reader :env
7+
58
def initialize(env, msg = nil)
6-
super msg
79
@env = env
10+
# Try to fetch json_api errors from response
11+
msg = track_json_api_errors(msg)
12+
13+
super msg
14+
end
15+
16+
private
17+
18+
# Try to fetch json_api errors from response
19+
def track_json_api_errors(msg)
20+
return msg unless env.try(:body).kind_of?(Hash) || env.body.key?('errors')
21+
22+
errors_msg = env.body['errors'].map { |e| e['title'] }.compact.join('; ').presence
23+
return msg unless errors_msg
24+
25+
msg.nil? ? errors_msg : "#{msg} (#{errors_msg})"
26+
# Just to be sure that it is back compatible
27+
rescue StandardError
28+
msg
829
end
930
end
1031

@@ -21,7 +42,13 @@ class ConnectionError < ApiError
2142
end
2243

2344
class ServerError < ApiError
24-
def initialize(env, msg = 'Internal server error')
45+
def initialize(env, msg = nil)
46+
msg ||= begin
47+
status = env.status
48+
message = ::Rack::Utils::HTTP_STATUS_CODES[status]
49+
"#{status} #{message}"
50+
end
51+
2552
super env, msg
2653
end
2754
end
@@ -36,20 +63,23 @@ class NotFound < ServerError
3663
attr_reader :uri
3764
def initialize(uri)
3865
@uri = uri
39-
end
40-
def message
41-
"Couldn't find resource at: #{uri.to_s}"
66+
67+
msg = "Couldn't find resource at: #{uri.to_s}"
68+
super nil, msg
4269
end
4370
end
4471

72+
class InternalServerError < ServerError
73+
end
74+
4575
class UnexpectedStatus < ServerError
4676
attr_reader :code, :uri
4777
def initialize(code, uri)
4878
@code = code
4979
@uri = uri
50-
end
51-
def message
52-
"Unexpected response status: #{code} from: #{uri.to_s}"
80+
81+
msg = "Unexpected response status: #{code} from: #{uri.to_s}"
82+
super nil, msg
5383
end
5484
end
5585
end

lib/json_api_client/middleware/status.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ def handle_status(code, env)
4444
# Allow to proceed as resource errors will be populated
4545
when 400..499
4646
raise Errors::ClientError, env
47-
when 500..599
47+
when 500
48+
raise Errors::InternalServerError, env
49+
when 501..599
4850
raise Errors::ServerError, env
4951
else
5052
raise Errors::UnexpectedStatus.new(code, env[:url])

test/unit/errors_test.rb

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,42 @@ def test_timeout_errors
2222
end
2323
end
2424

25-
def test_500_errors
25+
def test_internal_server_error_with_plain_text_response
2626
stub_request(:get, "http://example.com/users")
2727
.to_return(headers: {content_type: "text/plain"}, status: 500, body: "something went wrong")
2828

29-
assert_raises JsonApiClient::Errors::ServerError do
30-
User.all
31-
end
29+
exception = assert_raises(JsonApiClient::Errors::InternalServerError) { User.all }
30+
assert_equal '500 Internal Server Error', exception.message
31+
end
32+
33+
def test_internal_server_error_with_json_api_response
34+
stub_request(:get, "http://example.com/users").to_return(
35+
headers: {content_type: "application/vnd.api+json"},
36+
status: 500,
37+
body: {errors: [{title: "Some special error"}]}.to_json
38+
)
39+
40+
exception = assert_raises(JsonApiClient::Errors::InternalServerError) { User.all }
41+
assert_equal '500 Internal Server Error (Some special error)', exception.message
42+
end
43+
44+
def test_500_errors_with_plain_text_response
45+
stub_request(:get, "http://example.com/users")
46+
.to_return(headers: {content_type: "text/plain"}, status: 503, body: "service unavailable")
47+
48+
exception = assert_raises(JsonApiClient::Errors::ServerError) { User.all }
49+
assert_equal '503 Service Unavailable', exception.message
50+
end
51+
52+
def test_500_errors_with_with_json_api_response
53+
stub_request(:get, "http://example.com/users").to_return(
54+
headers: {content_type: "application/vnd.api+json"},
55+
status: 503,
56+
body: {errors: [{title: "Timeout error"}]}.to_json
57+
)
58+
59+
exception = assert_raises(JsonApiClient::Errors::ServerError) { User.all }
60+
assert_equal '503 Service Unavailable (Timeout error)', exception.message
3261
end
3362

3463
def test_not_found

test/unit/status_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def test_server_responding_with_status_meta
1111
}
1212
}.to_json)
1313

14-
assert_raises JsonApiClient::Errors::ServerError do
14+
assert_raises JsonApiClient::Errors::InternalServerError do
1515
User.find(1)
1616
end
1717
end
@@ -24,7 +24,7 @@ def test_server_responding_with_http_status
2424
status: 500,
2525
body: "something irrelevant")
2626

27-
assert_raises JsonApiClient::Errors::ServerError do
27+
assert_raises JsonApiClient::Errors::InternalServerError do
2828
User.find(1)
2929
end
3030
end

0 commit comments

Comments
 (0)