Skip to content

Commit 7a17c8a

Browse files
authored
Merge pull request #57 from jsonapi-rb/charset
Add content negotiation middleware + fix response content type.
2 parents c99bdec + dee01d3 commit 7a17c8a

File tree

5 files changed

+136
-2
lines changed

5 files changed

+136
-2
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require 'rack/media_type'
2+
3+
module JSONAPI
4+
module Rails
5+
class FilterMediaType
6+
JSONAPI_MEDIA_TYPE = 'application/vnd.api+json'.freeze
7+
8+
def initialize(app)
9+
@app = app
10+
end
11+
12+
def call(env)
13+
return [415, {}, []] unless valid_content_type?(env['CONTENT_TYPE'])
14+
return [406, {}, []] unless valid_accept?(env['HTTP_ACCEPT'])
15+
16+
@app.call(env)
17+
end
18+
19+
private
20+
21+
def valid_content_type?(content_type)
22+
Rack::MediaType.type(content_type) != JSONAPI_MEDIA_TYPE ||
23+
Rack::MediaType.params(content_type) == {}
24+
end
25+
26+
def valid_accept?(accept)
27+
return true if accept.nil?
28+
29+
jsonapi_media_types =
30+
accept.split(',')
31+
.map(&:strip)
32+
.select { |m| Rack::MediaType.type(m) == JSONAPI_MEDIA_TYPE }
33+
34+
jsonapi_media_types.empty? ||
35+
jsonapi_media_types.any? { |m| Rack::MediaType.params(m) == {} }
36+
end
37+
end
38+
end
39+
end

lib/jsonapi/rails/railtie.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'rails/railtie'
22

3+
require 'jsonapi/rails/filter_media_type'
34
require 'jsonapi/rails/log_subscriber'
45
require 'jsonapi/rails/renderer'
56

@@ -19,14 +20,16 @@ class Railtie < ::Rails::Railtie
1920
jsonapi_errors: ErrorsRenderer.new
2021
}.freeze
2122

22-
initializer 'jsonapi-rails.init' do
23+
initializer 'jsonapi-rails.init' do |app|
2324
register_mime_type
2425
register_parameter_parser
2526
register_renderers
2627
ActiveSupport.on_load(:action_controller) do
2728
require 'jsonapi/rails/controller'
2829
include ::JSONAPI::Rails::Controller
2930
end
31+
32+
app.middleware.use FilterMediaType
3033
end
3134

3235
private
@@ -49,7 +52,7 @@ def register_renderers
4952
RENDERERS.each do |name, renderer|
5053
::ActionController::Renderers.add(name) do |resources, options|
5154
# Renderer proc is evaluated in the controller context.
52-
self.content_type ||= Mime[:jsonapi]
55+
headers['Content-Type'] = Mime[:jsonapi].to_s
5356

5457
ActiveSupport::Notifications.instrument('render.jsonapi-rails',
5558
resources: resources,

spec/content_negotiation_spec.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
require 'rails_helper'
2+
3+
describe ActionController::Base, type: :controller do
4+
controller do
5+
def index
6+
render jsonapi: nil
7+
end
8+
end
9+
10+
context 'when sending data' do
11+
it 'responds with application/vnd.api+json' do
12+
get :index
13+
14+
expect(response.headers['Content-Type']).to eq('application/vnd.api+json')
15+
end
16+
end
17+
end

spec/filter_media_type_spec.rb

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
require 'rails_helper'
2+
3+
describe JSONAPI::Rails::FilterMediaType do
4+
let(:app) { ->(_) { [200, {}, ['OK']] } }
5+
6+
context 'when not receiving JSON API Content-Type' do
7+
it 'passes through' do
8+
env = { 'CONTENT_TYPE' => 'application/json' }
9+
10+
expect(described_class.new(app).call(env)[0]).to eq(200)
11+
end
12+
end
13+
14+
context 'when receiving JSON API Content-Type without media parameters' do
15+
it 'passes through' do
16+
env = { 'CONTENT_TYPE' => 'application/vnd.api+json' }
17+
18+
expect(described_class.new(app).call(env)[0]).to eq(200)
19+
end
20+
end
21+
22+
context 'when receiving Content-Type with media parameters' do
23+
it 'fails with 415 Unsupported Media Type' do
24+
env = { 'CONTENT_TYPE' => 'application/vnd.api+json; charset=utf-8' }
25+
26+
expect(described_class.new(app).call(env)[0]).to eq(415)
27+
end
28+
end
29+
30+
context 'when not receiving JSON API in Accept' do
31+
it 'passes through' do
32+
env = { 'HTTP_ACCEPT' => 'application/json' }
33+
34+
expect(described_class.new(app).call(env)[0]).to eq(200)
35+
end
36+
end
37+
38+
context 'when receiving JSON API in Accept without media parameters' do
39+
it 'passes through' do
40+
env = { 'HTTP_ACCEPT' => 'application/vnd.api+json' }
41+
42+
expect(described_class.new(app).call(env)[0]).to eq(200)
43+
end
44+
end
45+
46+
context 'when receiving JSON API in Accept without media parameters among others' do
47+
it 'passes through' do
48+
env = { 'HTTP_ACCEPT' => 'application/json, application/vnd.api+json' }
49+
50+
expect(described_class.new(app).call(env)[0]).to eq(200)
51+
end
52+
end
53+
54+
context 'when receiving JSON API in Accept with media parameters' do
55+
it 'fails with 406 Not Acceptable' do
56+
env = { 'HTTP_ACCEPT' => 'application/vnd.api+json; charset=utf-8' }
57+
58+
expect(described_class.new(app).call(env)[0]).to eq(406)
59+
end
60+
end
61+
62+
context 'when receiving JSON API in Accept with media parameters among others' do
63+
it 'fails with 406 Not Acceptable' do
64+
env = { 'HTTP_ACCEPT' => 'application/json, application/vnd.api+json; charset=utf-8' }
65+
66+
expect(described_class.new(app).call(env)[0]).to eq(406)
67+
end
68+
end
69+
end

spec/railtie_spec.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,10 @@
88
it 'registers the params parser for the JSON API MIME type' do
99
expect(::ActionDispatch::Request.parameter_parsers[:jsonapi]).not_to be_nil
1010
end
11+
12+
it 'registers the FilterMediaType middleware' do
13+
expect(
14+
Rails.application.middleware.include?(JSONAPI::Rails::FilterMediaType)
15+
).to be true
16+
end
1117
end

0 commit comments

Comments
 (0)