From 815578cd50c0c01b7663cbff282950fa0d7280ed Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 11:35:55 +0100 Subject: [PATCH 1/7] build: add rack 3 as a dependency --- jsonapi-resources.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsonapi-resources.gemspec b/jsonapi-resources.gemspec index 7be374b3..52c1f190 100644 --- a/jsonapi-resources.gemspec +++ b/jsonapi-resources.gemspec @@ -31,6 +31,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'database_cleaner' spec.add_dependency 'activerecord', '>= 7.1', '< 9.0' # versions 7.1, 7.2, 8.0, 8.1, and above, but not 9.0 spec.add_dependency 'railties', '>= 7.1', '< 9.0' # versions 7.1, 7.2, 8.0, 8.1, and above, but not 9.0 - spec.add_dependency 'rack', '~> 2.0' + spec.add_dependency 'rack', '~> 3.0' spec.add_dependency 'concurrent-ruby' end From 11bf1fa47ee8e4a8a9535dfcc5a6ab44e1fb0f98 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 11:38:18 +0100 Subject: [PATCH 2/7] fix: unprocessable_entity -> unprocessable_content --- lib/jsonapi/exceptions.rb | 4 ++-- test/controllers/controller_test.rb | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/jsonapi/exceptions.rb b/lib/jsonapi/exceptions.rb index e917118c..a0c437ee 100644 --- a/lib/jsonapi/exceptions.rb +++ b/lib/jsonapi/exceptions.rb @@ -498,7 +498,7 @@ def errors def json_api_error(attr_key, message) create_error_object(code: JSONAPI::VALIDATION_ERROR, - status: :unprocessable_entity, + status: :unprocessable_content, title: message, detail: detail(attr_key, message), source: { pointer: pointer(attr_key) }, @@ -532,7 +532,7 @@ def general_error?(attr_key) class SaveFailed < Error def errors [create_error_object(code: JSONAPI::SAVE_FAILED, - status: :unprocessable_entity, + status: :unprocessable_content, title: I18n.translate('jsonapi-resources.exceptions.save_failed.title', default: 'Save failed or was cancelled'), detail: I18n.translate('jsonapi-resources.exceptions.save_failed.detail', diff --git a/test/controllers/controller_test.rb b/test/controllers/controller_test.rb index e2568f97..a2f22066 100644 --- a/test/controllers/controller_test.rb +++ b/test/controllers/controller_test.rb @@ -761,7 +761,7 @@ def test_create_link_to_missing_object } } - assert_response :unprocessable_entity + assert_response :unprocessable_content # TODO: check if this validation is working assert_match /author - can't be blank/, response.body assert_nil response.location @@ -864,7 +864,7 @@ def test_create_with_invalid_data } } - assert_response :unprocessable_entity + assert_response :unprocessable_content assert_equal "/data/relationships/author", json_response['errors'][0]['source']['pointer'] assert_equal "can't be blank", json_response['errors'][0]['title'] @@ -2019,7 +2019,7 @@ def test_delete_with_validation_error_base assert_equal "can't destroy me", json_response['errors'][0]['title'] assert_equal "/data", json_response['errors'][0]['source']['pointer'] - assert_response :unprocessable_entity + assert_response :unprocessable_content end def test_delete_with_validation_error_attr @@ -2028,7 +2028,7 @@ def test_delete_with_validation_error_attr assert_equal "is locked", json_response['errors'][0]['title'] assert_equal "/data/attributes/title", json_response['errors'][0]['source']['pointer'] - assert_response :unprocessable_entity + assert_response :unprocessable_content end def test_delete_single @@ -2631,7 +2631,7 @@ def test_create_validations_missing_attribute } } - assert_response :unprocessable_entity + assert_response :unprocessable_content assert_equal 2, json_response['errors'].size assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][0]['code'] assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][1]['code'] @@ -2653,7 +2653,7 @@ def test_update_validations_missing_attribute } } - assert_response :unprocessable_entity + assert_response :unprocessable_content assert_equal 1, json_response['errors'].size assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][0]['code'] assert_match /name - can't be blank/, response.body @@ -3183,7 +3183,7 @@ def test_create_with_invalid_data } } - assert_response :unprocessable_entity + assert_response :unprocessable_content assert_equal "/data/attributes/spouse-name", json_response['errors'][0]['source']['pointer'] assert_equal "can't be blank", json_response['errors'][0]['title'] @@ -3779,7 +3779,7 @@ def test_save_model_callbacks_fail } } - assert_response :unprocessable_entity + assert_response :unprocessable_content assert_match /Save failed or was cancelled/, json_response['errors'][0]['detail'] end end @@ -4077,7 +4077,7 @@ def test_delete_with_validation_error_base_on_resource assert_equal "can't destroy me", json_response['errors'][0]['title'] assert_equal "/data/attributes/base", json_response['errors'][0]['source']['pointer'] - assert_response :unprocessable_entity + assert_response :unprocessable_content end end From 40961b68001c57c6e607abc216aa45ff3d4dea6a Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 12:48:17 +0100 Subject: [PATCH 3/7] fix: replace updated Rack status_code function with legacy version --- lib/jsonapi/error.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/jsonapi/error.rb b/lib/jsonapi/error.rb index 8eeb87cf..55ea0537 100644 --- a/lib/jsonapi/error.rb +++ b/lib/jsonapi/error.rb @@ -17,7 +17,7 @@ def initialize(options = {}) @source = options[:source] @links = options[:links] - @status = Rack::Utils.status_code(options[:status]).to_s + @status = status_code(options[:status]).to_s @meta = options[:meta] end @@ -48,11 +48,22 @@ def update_with_overrides(error_object_overrides) if error_object_overrides[:status] # :nocov: - @status = Rack::Utils::SYMBOL_TO_STATUS_CODE[error_object_overrides[:status]].to_s + @status = status_code(error_object_overrides[:status]).to_s # :nocov: end @meta = error_object_overrides[:meta] || @meta end + + private + + # Extracted from Rack 2 + def status_code(status) + if status.is_a?(Symbol) + Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" } + else + status.to_i + end + end end class Warning From 8f9a01c95cf477ab64bd659b281963de3811178e Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 11 Aug 2026 14:42:57 +0100 Subject: [PATCH 4/7] test: add additional unprocessable content tests --- test/integration/requests/request_test.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration/requests/request_test.rb b/test/integration/requests/request_test.rb index 6ce002c7..b48914af 100644 --- a/test/integration/requests/request_test.rb +++ b/test/integration/requests/request_test.rb @@ -453,6 +453,9 @@ def test_post_single_minimal_invalid } assert_jsonapi_response 422 + assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][0]['code'] + assert_equal '422', json_response['errors'][0]['status'] + assert_match "can't be blank", json_response['errors'][0]['title'] end def test_update_relationship_without_content_type From 5f5149f4bfba040fc33f56c85c2a76d5d24d8b2f Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 12 Aug 2026 14:30:10 +0100 Subject: [PATCH 5/7] test: add json-api error tests --- .../jsonapi_request/jsonapi_error_test.rb | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/unit/jsonapi_request/jsonapi_error_test.rb diff --git a/test/unit/jsonapi_request/jsonapi_error_test.rb b/test/unit/jsonapi_request/jsonapi_error_test.rb new file mode 100644 index 00000000..078971d8 --- /dev/null +++ b/test/unit/jsonapi_request/jsonapi_error_test.rb @@ -0,0 +1,41 @@ +require File.expand_path('../../../test_helper', __FILE__) + +class JSONAPIErrorTest < Minitest::Test + def test_status_code_no_status + error = JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST) + + assert_equal('0', error.status) + end + + def test_status_code_accepts_symbol + error = JSONAPI::Error.new(code: JSONAPI::VALIDATION_ERROR, status: :unprocessable_content) + + assert_equal('422', error.status) + end + + def test_status_code_accepts_integer + error = JSONAPI::Error.new(code: JSONAPI::VALIDATION_ERROR, status: 422) + + assert_equal('422', error.status) + end + + def test_status_code_accepts_string + error = JSONAPI::Error.new(code: JSONAPI::VALIDATION_ERROR, status: '422') + + assert_equal('422', error.status) + end + + def test_status_code_rejects_unknown_symbol + error = assert_raises(ArgumentError) do + JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST, status: :not_a_real_status) + end + + assert_equal('Unrecognized status code :not_a_real_status', error.message) + end + + def test_status_code_handles_nil + error = JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST, status: nil) + + assert_equal('0', error.status) + end +end From 2b1943a3a394e983f188d9581b121f6a74835be6 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 12 Aug 2026 14:35:03 +0100 Subject: [PATCH 6/7] fix: raise ArgumentError for nil status --- lib/jsonapi/error.rb | 4 ++++ test/unit/jsonapi_request/jsonapi_error_test.rb | 16 ++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/jsonapi/error.rb b/lib/jsonapi/error.rb index 55ea0537..9e23d30c 100644 --- a/lib/jsonapi/error.rb +++ b/lib/jsonapi/error.rb @@ -58,6 +58,10 @@ def update_with_overrides(error_object_overrides) # Extracted from Rack 2 def status_code(status) + if status.nil? + raise ArgumentError, "Status code is required" + end + if status.is_a?(Symbol) Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" } else diff --git a/test/unit/jsonapi_request/jsonapi_error_test.rb b/test/unit/jsonapi_request/jsonapi_error_test.rb index 078971d8..801b8204 100644 --- a/test/unit/jsonapi_request/jsonapi_error_test.rb +++ b/test/unit/jsonapi_request/jsonapi_error_test.rb @@ -1,10 +1,12 @@ require File.expand_path('../../../test_helper', __FILE__) class JSONAPIErrorTest < Minitest::Test - def test_status_code_no_status - error = JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST) + def test_status_code_requires_status + error = assert_raises(ArgumentError) do + JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST) + end - assert_equal('0', error.status) + assert_equal('Status code is required', error.message) end def test_status_code_accepts_symbol @@ -33,9 +35,11 @@ def test_status_code_rejects_unknown_symbol assert_equal('Unrecognized status code :not_a_real_status', error.message) end - def test_status_code_handles_nil - error = JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST, status: nil) + def test_status_code_rejects_nil + error = assert_raises(ArgumentError) do + JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST, status: nil) + end - assert_equal('0', error.status) + assert_equal('Status code is required', error.message) end end From e2add4f98659d1857b8a5d23a8385b74ca50ca82 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 20 Aug 2026 13:52:59 +0100 Subject: [PATCH 7/7] release: bump patch version --- lib/jsonapi/resources/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jsonapi/resources/version.rb b/lib/jsonapi/resources/version.rb index f54a58b9..32ebf10d 100644 --- a/lib/jsonapi/resources/version.rb +++ b/lib/jsonapi/resources/version.rb @@ -1,5 +1,5 @@ module JSONAPI module Resources - VERSION = '0.4.0' + VERSION = '0.4.1' end end