Skip to content

Commit 51c2cd4

Browse files
committed
Merge pull request #18 from b2beauty/hotfix/fix-type-of-error-codes
Fix code in error responses
2 parents f78b371 + fda1713 commit 51c2cd4

File tree

16 files changed

+274
-87
lines changed

16 files changed

+274
-87
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ env:
66
rvm:
77
- 2.2
88
before_install: gem install bundler -v 1.10.4
9-
script: bundle exec rspec spec/controllers/{user,post}s_controller_spec.rb
9+
script: bundle exec rspec spec/controllers

README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,15 @@ jsonapi_render json: { data: { id: 1, first_name: 'Tiago' } }, options: { model:
6868
jsonapi_render json: { data: [{ id: 1, first_name: 'Tiago' }, { id: 2, first_name: 'Doug' }] }, options: { model: User }
6969
```
7070

71-
### jsonapi_serialize
71+
### jsonapi_format
7272

7373
In the backstage this is the method that actually parses ActiveRecord/Hash objects and builds a new Hash compliant with JSON API. It can be called anywhere in your controllers being very useful whenever you need to work with a JSON API "serialized" version of your object before rendering it.
7474

75+
Note: because of semantic reasons `JSONAPI::Utils#jsonapi_serialize` was renamed being now just an alias to `JSONAPI::Utils#jsonapi_format`.
76+
7577
```ruby
7678
def index
77-
result = do_some_magic(jsonapi_serialize(User.all))
79+
result = do_some_magic(jsonapi_format(User.all))
7880
render json: result
7981
end
8082
```
@@ -228,12 +230,8 @@ Content-Type: application/vnd.api+json
228230
{
229231
"title": "Record not found",
230232
"detail": "The record identified by 3 could not be found.",
231-
"id": null,
232-
"href": null,
233-
"code": 404,
234-
"source": null,
235-
"links": null,
236-
"status": "not_found"
233+
"code": "404",
234+
"status": "404"
237235
}
238236
]
239237
}

lib/jsonapi/utils/exceptions.rb

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,52 @@
11
require 'jsonapi/utils/version'
22

3-
module JSONAPI::Utils::Exceptions
4-
class BadRequest < ::JSONAPI::Exceptions::Error
5-
def code; 400 end
3+
module JSONAPI
4+
module Utils
5+
module Exceptions
6+
class ActiveRecord < ::JSONAPI::Exceptions::Error
7+
attr_accessor :object
68

7-
def errors
8-
[JSONAPI::Error.new(code: 400,
9-
status: :bad_request,
10-
title: 'Bad Request',
11-
detail: 'This request is not supported.')]
12-
end
13-
end
9+
def initialize(object)
10+
@object = object
11+
end
12+
13+
def errors
14+
object.errors.keys.map do |key|
15+
JSONAPI::Error.new(
16+
code: JSONAPI::VALIDATION_ERROR,
17+
status: :unprocessable_entity,
18+
id: key,
19+
title: object.errors.full_messages_for(key).first
20+
)
21+
end
22+
end
23+
end
24+
25+
class BadRequest < ::JSONAPI::Exceptions::Error
26+
def code; '400' end
27+
28+
def errors
29+
[JSONAPI::Error.new(
30+
code: code,
31+
status: :bad_request,
32+
title: 'Bad Request',
33+
detail: 'This request is not supported.'
34+
)]
35+
end
36+
end
1437

15-
class InternalServerError < ::JSONAPI::Exceptions::Error
16-
def code; 500 end
38+
class InternalServerError < ::JSONAPI::Exceptions::Error
39+
def code; '500' end
1740

18-
def errors
19-
[JSONAPI::Error.new(code: 500,
20-
status: :internal_server_error,
21-
title: 'Internal Server Error',
22-
detail: 'An internal error ocurred while processing the request.')]
41+
def errors
42+
[JSONAPI::Error.new(
43+
code: code,
44+
status: :internal_server_error,
45+
title: 'Internal Server Error',
46+
detail: 'An internal error ocurred while processing the request.'
47+
)]
48+
end
49+
end
2350
end
2451
end
2552
end

lib/jsonapi/utils/request.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def setup_request
1212
end
1313

1414
def check_request
15-
@request.errors.blank? || render_errors(@request.errors)
15+
@request.errors.blank? || jsonapi_render_errors(json: @request)
1616
end
1717
end
1818
end

lib/jsonapi/utils/response/formatters.rb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ module JSONAPI
22
module Utils
33
module Response
44
module Formatters
5-
def jsonapi_format_errors(exception)
6-
JSONAPI::ErrorsOperationResult.new(exception.errors[0].code, exception.errors)
7-
end
8-
9-
def jsonapi_serialize(records, options = {})
5+
def jsonapi_format(records, options = {})
106
if records.is_a?(Hash)
117
hash = records.with_indifferent_access
128
records = hash_to_active_record(hash[:data], options[:model])
@@ -15,6 +11,16 @@ def jsonapi_serialize(records, options = {})
1511
build_response_document(records, options).contents
1612
end
1713

14+
alias_method :jsonapi_serialize, :jsonapi_format
15+
16+
def jsonapi_format_errors(data)
17+
data = JSONAPI::Utils::Exceptions::ActiveRecord.new(data) if data.is_a?(ActiveRecord::Base)
18+
errors = data.respond_to?(:errors) ? data.errors : data
19+
JSONAPI::Utils::Support::Error.sanitize(errors).uniq
20+
end
21+
22+
protected
23+
1824
def build_response_document(records, options)
1925
results = JSONAPI::OperationResults.new
2026

lib/jsonapi/utils/response/renders.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ module Utils
33
module Response
44
module Renders
55
def jsonapi_render(json:, status: nil, options: {})
6-
body = jsonapi_serialize(json, options)
6+
body = jsonapi_format(json, options)
77
render json: body, status: status || @_response_document.status
88
rescue => e
99
handle_exceptions(e)
1010
ensure
11-
if response.body.size > 0
12-
response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
13-
end
11+
correct_media_type
1412
end
1513

16-
def jsonapi_render_errors(exception)
17-
result = jsonapi_format_errors(exception)
18-
errors = result.errors
19-
render json: { errors: errors }, status: errors.first.status
14+
def jsonapi_render_errors(exception = nil, json: nil, status: nil)
15+
body = jsonapi_format_errors(exception || json)
16+
status = status || body.try(:first).try(:[], :status)
17+
render json: { errors: body }, status: status
18+
ensure
19+
correct_media_type
2020
end
2121

2222
def jsonapi_render_internal_server_error

lib/jsonapi/utils/response/support.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
require 'jsonapi/utils/support/filter'
22
require 'jsonapi/utils/support/pagination'
33
require 'jsonapi/utils/support/sort'
4+
require 'jsonapi/utils/support/error'
45

56
module JSONAPI
67
module Utils
78
module Response
89
module Support
10+
include ::JSONAPI::Utils::Support::Error
911
include ::JSONAPI::Utils::Support::Filter
1012
include ::JSONAPI::Utils::Support::Pagination
1113
include ::JSONAPI::Utils::Support::Sort
14+
15+
protected
16+
17+
def correct_media_type
18+
if response.body.size > 0
19+
response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
20+
end
21+
end
1222
end
1323
end
1424
end

lib/jsonapi/utils/support/error.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module JSONAPI
2+
module Utils
3+
module Support
4+
module Error
5+
MEMBERS = %i(title detail id code source links status meta)
6+
7+
module_function
8+
9+
def sanitize(errors)
10+
Array(errors).map do |error|
11+
MEMBERS.reduce({}) do |sum, key|
12+
value = error.try(key) || error.try(:[], key)
13+
if value.nil?
14+
sum
15+
else
16+
value = value.to_s if key == :code
17+
sum.merge(key => value)
18+
end
19+
end
20+
end
21+
end
22+
end
23+
end
24+
end
25+
end

lib/jsonapi/utils/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module JSONAPI
22
module Utils
3-
VERSION = '0.4.4'
3+
VERSION = '0.4.5'
44
end
55
end

spec/controllers/posts_controller_spec.rb

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
require 'spec_helper'
2-
require 'rspec/expectations'
32

43
describe PostsController, type: :controller do
54
include_context 'JSON API headers'
@@ -8,12 +7,28 @@
87

98
let(:fields) { (PostResource.fetchable_fields - %i(id author)).map(&:to_s) }
109
let(:relationships) { %w(author) }
11-
let(:post) { Post.first }
10+
let(:first_post) { Post.first }
11+
let(:user_id) { first_post.user_id }
12+
13+
let(:attributes) do
14+
{ title: 'Lorem ipsum', body: 'Lorem ipsum dolor sit amet.' }
15+
end
16+
17+
let(:author_params) do
18+
{ data: { type: 'users', id: user_id } }
19+
end
20+
21+
let(:post_params) do
22+
{
23+
data: { type: 'posts', attributes: attributes },
24+
relationships: { author: author_params }
25+
}
26+
end
1227

1328
describe '#index' do
1429
context 'with ActiveRecord::Relation' do
1530
it 'renders a collection of users' do
16-
get :index, user_id: post.user_id
31+
get :index, user_id: user_id
1732
expect(response).to have_http_status :ok
1833
expect(response).to have_primary_data('posts')
1934
expect(response).to have_data_attributes(fields)
@@ -36,12 +51,12 @@
3651
describe '#show' do
3752
context 'with ActiveRecord' do
3853
it 'renders a single post' do
39-
get :show, user_id: post.user_id, id: post.id
54+
get :show, user_id: user_id, id: first_post.id
4055
expect(response).to have_http_status :ok
4156
expect(response).to have_primary_data('posts')
4257
expect(response).to have_data_attributes(fields)
4358
expect(response).to have_relationships(relationships)
44-
expect(data['attributes']['title']).to eq("Title for Post #{post.id}")
59+
expect(data['attributes']['title']).to eq("Title for Post #{first_post.id}")
4560
end
4661
end
4762

@@ -59,37 +74,60 @@
5974
context 'when resource was not found' do
6075
context 'with conventional id' do
6176
it 'renders a 404 response' do
62-
get :show, user_id: post.user_id, id: 999
77+
get :show, user_id: user_id, id: 999
6378
expect(response).to have_http_status :not_found
6479
expect(error['title']).to eq('Record not found')
6580
expect(error['detail']).to include('999')
66-
expect(error['code']).to eq(404)
81+
expect(error['code']).to eq('404')
6782
end
6883
end
6984

7085
context 'with uuid' do
7186
let(:uuid) { SecureRandom.uuid }
7287

7388
it 'renders a 404 response' do
74-
get :show, user_id: post.user_id, id: uuid
89+
get :show, user_id: user_id, id: uuid
7590
expect(response).to have_http_status :not_found
7691
expect(error['title']).to eq('Record not found')
7792
expect(error['detail']).to include(uuid)
78-
expect(error['code']).to eq(404)
93+
expect(error['code']).to eq('404')
7994
end
8095
end
8196

8297
context 'with slug' do
8398
let(:slug) { 'some-awesome-slug' }
8499

85100
it 'renders a 404 response' do
86-
get :show, user_id: post.user_id, id: slug
101+
get :show, user_id: user_id, id: slug
87102
expect(response).to have_http_status :not_found
88103
expect(error['title']).to eq('Record not found')
89104
expect(error['detail']).to include(slug)
90-
expect(error['code']).to eq(404)
105+
expect(error['code']).to eq('404')
91106
end
92107
end
93108
end
94109
end
110+
111+
describe '#create' do
112+
it 'creates a new post' do
113+
expect { post :create, post_params }.to change(Post, :count).by(1)
114+
expect(response).to have_http_status :created
115+
expect(response).to have_primary_data('posts')
116+
expect(response).to have_data_attributes(fields)
117+
expect(data['attributes']['title']).to eq(post_params[:data][:attributes][:title])
118+
end
119+
120+
context 'when validation fails' do
121+
it 'render a 422 response' do
122+
post_params[:data][:attributes].merge!(title: nil)
123+
124+
expect { post :create, post_params }.to change(Post, :count).by(0)
125+
expect(response).to have_http_status :unprocessable_entity
126+
127+
expect(errors[0]['id']).to eq('title')
128+
expect(errors[0]['title']).to eq('Title can\'t be blank')
129+
expect(errors[0]['code']).to eq('100')
130+
end
131+
end
132+
end
95133
end

0 commit comments

Comments
 (0)