Skip to content

Commit 4ca6148

Browse files
authored
Merge pull request #324 from senid231/custom-status-handling
add possibility to override status handling
2 parents d5ddf81 + a2dd3d8 commit 4ca6148

File tree

6 files changed

+160
-2
lines changed

6 files changed

+160
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
* remove type from changes on initialize
99
* ensure that query builder doesn't propagate query values to resource attributes via #build method
1010

11+
- [#324](https://github.com/JsonApiClient/json_api_client/pull/324) - add possibility to override status handling
12+
* add status_handlers to JsonApiClient::Resource.connection_options
13+
1114
## 1.8.0
1215

1316
- [#316](https://github.com/JsonApiClient/json_api_client/pull/316) - Allow custom error messages

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,44 @@ module MyApi
474474
end
475475
```
476476
477+
##### Custom status handler
478+
479+
You can change handling of response status using `connection_options`. For example you can override 400 status handling.
480+
By default it raises `JsonApiClient::Errors::ClientError` but you can skip exception if you want to process errors from the server.
481+
You need to provide a `proc` which should call `throw(:handled)` default handler for this status should be skipped.
482+
```ruby
483+
class ApiBadRequestHandler
484+
def self.call(_env)
485+
# do not raise exception
486+
end
487+
end
488+
489+
class CustomUnauthorizedError < StandardError
490+
attr_reader :env
491+
492+
def initialize(env)
493+
@env = env
494+
super('not authorized')
495+
end
496+
end
497+
498+
MyApi::Base.connection_options[:status_handlers] = {
499+
400 => ApiBadRequestHandler,
500+
401 => ->(env) { raise CustomUnauthorizedError, env }
501+
}
502+
503+
module MyApi
504+
class User < Base
505+
# will use the customized status_handlers
506+
end
507+
end
508+
509+
user = MyApi::User.create(name: 'foo')
510+
# server responds with { errors: [ { detail: 'bad request' } ] }
511+
user.errors.messages # { base: ['bad request'] }
512+
# on 401 it will raise CustomUnauthorizedError instead of JsonApiClient::Errors::NotAuthorized
513+
```
514+
477515
##### Specifying an HTTP Proxy
478516

479517
All resources have a class method ```connection_options``` used to pass options to the JsonApiClient::Connection initializer.

lib/json_api_client/connection.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ def initialize(options = {})
77
site = options.fetch(:site)
88
connection_options = options.slice(:proxy, :ssl, :request, :headers, :params)
99
adapter_options = Array(options.fetch(:adapter, Faraday.default_adapter))
10+
status_middleware_options = {}
11+
status_middleware_options[:custom_handlers] = options[:status_handlers] if options[:status_handlers].present?
1012
@faraday = Faraday.new(site, connection_options) do |builder|
1113
builder.request :json
1214
builder.use Middleware::JsonRequest
13-
builder.use Middleware::Status
15+
builder.use Middleware::Status, status_middleware_options
1416
builder.use Middleware::ParseJson
1517
builder.adapter(*adapter_options)
1618
end

lib/json_api_client/middleware/status.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
module JsonApiClient
22
module Middleware
33
class Status < Faraday::Middleware
4+
def initialize(app, options)
5+
super(app)
6+
@options = options
7+
end
8+
49
def call(environment)
510
@app.call(environment).on_complete do |env|
611
handle_status(env[:status], env)
@@ -15,9 +20,16 @@ def call(environment)
1520
raise Errors::ConnectionError.new environment, e.to_s
1621
end
1722

18-
protected
23+
private
24+
25+
def custom_handler_for(code)
26+
@options.fetch(:custom_handlers, {})[code]
27+
end
1928

2029
def handle_status(code, env)
30+
custom_handler = custom_handler_for(code)
31+
return custom_handler.call(env) if custom_handler.present?
32+
2133
case code
2234
when 200..399
2335
when 401

test/test_helper.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,30 @@ class Comment < TestResource
3737
class User < TestResource
3838
end
3939

40+
class ApiBadRequestHandler
41+
def self.call(_env)
42+
# do not raise exception
43+
end
44+
end
45+
46+
class CustomUnauthorizedError < StandardError
47+
attr_reader :env
48+
49+
def initialize(env)
50+
@env = env
51+
super('not authorized')
52+
end
53+
end
54+
55+
class UserWithCustomStatusHandler < TestResource
56+
self.connection_options = {
57+
status_handlers: {
58+
400 => ApiBadRequestHandler,
59+
401 => ->(env) { raise CustomUnauthorizedError, env }
60+
}
61+
}
62+
end
63+
4064
class UserPreference < TestResource
4165
self.primary_key = :user_id
4266
end

test/unit/status_test.rb

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,85 @@ def test_server_responding_with_408_status
7070
end
7171
end
7272

73+
def test_server_responding_with_400_status
74+
stub_request(:get, "http://example.com/users/1")
75+
.to_return(headers: {content_type: "application/vnd.api+json"}, body: {
76+
meta: {
77+
status: 400,
78+
message: "Bad Request"
79+
}
80+
}.to_json)
81+
82+
assert_raises JsonApiClient::Errors::ClientError do
83+
User.find(1)
84+
end
85+
end
86+
87+
def test_server_responding_with_401_status
88+
stub_request(:get, "http://example.com/users/1")
89+
.to_return(headers: {content_type: "application/vnd.api+json"}, body: {
90+
meta: {
91+
status: 401,
92+
message: "Not Authorized"
93+
}
94+
}.to_json)
95+
96+
assert_raises JsonApiClient::Errors::NotAuthorized do
97+
User.find(1)
98+
end
99+
end
100+
101+
def test_server_responding_with_400_status_in_meta_with_custom_status_handler
102+
stub_request(:get, "http://example.com/user_with_custom_status_handlers/1")
103+
.to_return(headers: {content_type: "application/vnd.api+json"}, body: {
104+
meta: {
105+
status: 400,
106+
message: "Bad Request"
107+
}
108+
}.to_json)
109+
110+
UserWithCustomStatusHandler.find(1)
111+
end
112+
113+
def test_server_responding_with_401_status_in_meta_with_custom_status_handler
114+
stub_request(:get, "http://example.com/user_with_custom_status_handlers/1")
115+
.to_return(headers: {content_type: "application/vnd.api+json"}, body: {
116+
meta: {
117+
status: 401,
118+
message: "Not Authorized"
119+
}
120+
}.to_json)
121+
122+
assert_raises CustomUnauthorizedError do
123+
UserWithCustomStatusHandler.find(1)
124+
end
125+
end
126+
127+
def test_server_responding_with_400_status_with_custom_status_handler
128+
stub_request(:post, "http://example.com/user_with_custom_status_handlers")
129+
.with(headers: { content_type: 'application/vnd.api+json', accept: 'application/vnd.api+json' }, body: {
130+
data: {
131+
type: 'user_with_custom_status_handlers',
132+
attributes: {
133+
name: 'foo'
134+
}
135+
}
136+
}.to_json)
137+
.to_return(status: 400, headers: { content_type: "application/vnd.api+json" }, body: {
138+
errors: [
139+
{
140+
status: '400',
141+
detail: 'Bad Request'
142+
}
143+
]
144+
}.to_json)
145+
146+
user = UserWithCustomStatusHandler.create(name: 'foo')
147+
refute user.persisted?
148+
expected_errors = { base: ['Bad Request'] }
149+
assert_equal expected_errors, user.errors.messages
150+
end
151+
73152
def test_server_responding_with_422_status
74153
stub_request(:get, "http://example.com/users/1")
75154
.to_return(headers: {content_type: "application/vnd.api+json"}, body: {

0 commit comments

Comments
 (0)