diff --git a/CHANGELOG.md b/CHANGELOG.md index f87a33e2..95ad0a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ * remove type from changes on initialize * ensure that query builder doesn't propagate query values to resource attributes via #build method +- [#324](https://github.com/JsonApiClient/json_api_client/pull/324) - add possibility to override status handling + * add status_handlers to JsonApiClient::Resource.connection_options + ## 1.8.0 - [#316](https://github.com/JsonApiClient/json_api_client/pull/316) - Allow custom error messages diff --git a/README.md b/README.md index d1425d5f..a7b0966b 100644 --- a/README.md +++ b/README.md @@ -474,6 +474,44 @@ module MyApi end ``` +##### Custom status handler + +You can change handling of response status using `connection_options`. For example you can override 400 status handling. +By default it raises `JsonApiClient::Errors::ClientError` but you can skip exception if you want to process errors from the server. +You need to provide a `proc` which should call `throw(:handled)` default handler for this status should be skipped. +```ruby +class ApiBadRequestHandler + def self.call(_env) + # do not raise exception + end +end + +class CustomUnauthorizedError < StandardError + attr_reader :env + + def initialize(env) + @env = env + super('not authorized') + end +end + +MyApi::Base.connection_options[:status_handlers] = { + 400 => ApiBadRequestHandler, + 401 => ->(env) { raise CustomUnauthorizedError, env } +} + +module MyApi + class User < Base + # will use the customized status_handlers + end +end + +user = MyApi::User.create(name: 'foo') +# server responds with { errors: [ { detail: 'bad request' } ] } +user.errors.messages # { base: ['bad request'] } +# on 401 it will raise CustomUnauthorizedError instead of JsonApiClient::Errors::NotAuthorized +``` + ##### Specifying an HTTP Proxy All resources have a class method ```connection_options``` used to pass options to the JsonApiClient::Connection initializer. diff --git a/lib/json_api_client/connection.rb b/lib/json_api_client/connection.rb index 2f207ec4..227628a1 100644 --- a/lib/json_api_client/connection.rb +++ b/lib/json_api_client/connection.rb @@ -7,10 +7,12 @@ def initialize(options = {}) site = options.fetch(:site) connection_options = options.slice(:proxy, :ssl, :request, :headers, :params) adapter_options = Array(options.fetch(:adapter, Faraday.default_adapter)) + status_middleware_options = {} + status_middleware_options[:custom_handlers] = options[:status_handlers] if options[:status_handlers].present? @faraday = Faraday.new(site, connection_options) do |builder| builder.request :json builder.use Middleware::JsonRequest - builder.use Middleware::Status + builder.use Middleware::Status, status_middleware_options builder.use Middleware::ParseJson builder.adapter(*adapter_options) end diff --git a/lib/json_api_client/middleware/status.rb b/lib/json_api_client/middleware/status.rb index ad31de29..71b22a1c 100644 --- a/lib/json_api_client/middleware/status.rb +++ b/lib/json_api_client/middleware/status.rb @@ -1,6 +1,11 @@ module JsonApiClient module Middleware class Status < Faraday::Middleware + def initialize(app, options) + super(app) + @options = options + end + def call(environment) @app.call(environment).on_complete do |env| handle_status(env[:status], env) @@ -15,9 +20,16 @@ def call(environment) raise Errors::ConnectionError.new environment, e.to_s end - protected + private + + def custom_handler_for(code) + @options.fetch(:custom_handlers, {})[code] + end def handle_status(code, env) + custom_handler = custom_handler_for(code) + return custom_handler.call(env) if custom_handler.present? + case code when 200..399 when 401 diff --git a/test/test_helper.rb b/test/test_helper.rb index 28cfdde1..dac3d5cf 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -37,6 +37,30 @@ class Comment < TestResource class User < TestResource end +class ApiBadRequestHandler + def self.call(_env) + # do not raise exception + end +end + +class CustomUnauthorizedError < StandardError + attr_reader :env + + def initialize(env) + @env = env + super('not authorized') + end +end + +class UserWithCustomStatusHandler < TestResource + self.connection_options = { + status_handlers: { + 400 => ApiBadRequestHandler, + 401 => ->(env) { raise CustomUnauthorizedError, env } + } + } +end + class UserPreference < TestResource self.primary_key = :user_id end diff --git a/test/unit/status_test.rb b/test/unit/status_test.rb index 848fa2f9..aedb5951 100644 --- a/test/unit/status_test.rb +++ b/test/unit/status_test.rb @@ -70,6 +70,85 @@ def test_server_responding_with_408_status end end + def test_server_responding_with_400_status + stub_request(:get, "http://example.com/users/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + meta: { + status: 400, + message: "Bad Request" + } + }.to_json) + + assert_raises JsonApiClient::Errors::ClientError do + User.find(1) + end + end + + def test_server_responding_with_401_status + stub_request(:get, "http://example.com/users/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + meta: { + status: 401, + message: "Not Authorized" + } + }.to_json) + + assert_raises JsonApiClient::Errors::NotAuthorized do + User.find(1) + end + end + + def test_server_responding_with_400_status_in_meta_with_custom_status_handler + stub_request(:get, "http://example.com/user_with_custom_status_handlers/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + meta: { + status: 400, + message: "Bad Request" + } + }.to_json) + + UserWithCustomStatusHandler.find(1) + end + + def test_server_responding_with_401_status_in_meta_with_custom_status_handler + stub_request(:get, "http://example.com/user_with_custom_status_handlers/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + meta: { + status: 401, + message: "Not Authorized" + } + }.to_json) + + assert_raises CustomUnauthorizedError do + UserWithCustomStatusHandler.find(1) + end + end + + def test_server_responding_with_400_status_with_custom_status_handler + stub_request(:post, "http://example.com/user_with_custom_status_handlers") + .with(headers: { content_type: 'application/vnd.api+json', accept: 'application/vnd.api+json' }, body: { + data: { + type: 'user_with_custom_status_handlers', + attributes: { + name: 'foo' + } + } + }.to_json) + .to_return(status: 400, headers: { content_type: "application/vnd.api+json" }, body: { + errors: [ + { + status: '400', + detail: 'Bad Request' + } + ] + }.to_json) + + user = UserWithCustomStatusHandler.create(name: 'foo') + refute user.persisted? + expected_errors = { base: ['Bad Request'] } + assert_equal expected_errors, user.errors.messages + end + def test_server_responding_with_422_status stub_request(:get, "http://example.com/users/1") .to_return(headers: {content_type: "application/vnd.api+json"}, body: {