Skip to content

Commit 3018fab

Browse files
committed
Merge pull request #370 from gniquil/remove-extra-params
added option to ignore params that are not allowed in create/update
2 parents 743a229 + 03783f5 commit 3018fab

5 files changed

Lines changed: 192 additions & 6 deletions

File tree

lib/jsonapi/acts_as_resource_controller.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def ensure_correct_media_type
8787

8888
def setup_request
8989
@request = JSONAPI::Request.new(params, context: context, key_formatter: key_formatter)
90+
9091
render_errors(@request.errors) unless @request.errors.empty?
9192
rescue => e
9293
handle_exceptions(e)
@@ -121,6 +122,14 @@ def base_response_meta
121122
{}
122123
end
123124

125+
def base_meta
126+
if @request.nil? || @request.warnings.empty?
127+
base_response_meta
128+
else
129+
base_response_meta.merge(warnings: @request.warnings)
130+
end
131+
end
132+
124133
def base_response_links
125134
{}
126135
end
@@ -147,7 +156,7 @@ def create_response_document(operation_results)
147156
base_url: base_url,
148157
key_formatter: key_formatter,
149158
route_formatter: route_formatter,
150-
base_meta: base_response_meta,
159+
base_meta: base_meta,
151160
base_links: base_response_links,
152161
resource_serializer_klass: resource_serializer_klass,
153162
request: @request

lib/jsonapi/configuration.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class Configuration
88
:key_formatter,
99
:route_format,
1010
:route_formatter,
11+
:raise_if_parameters_not_allowed,
1112
:operations_processor,
1213
:allow_include,
1314
:allow_sort,
@@ -38,6 +39,8 @@ def initialize
3839
self.allow_sort = true
3940
self.allow_filter = true
4041

42+
self.raise_if_parameters_not_allowed = true
43+
4144
# :none, :offset, :paged, or a custom paginator name
4245
self.default_paginator = :none
4346

@@ -105,6 +108,8 @@ def operations_processor=(operations_processor)
105108
attr_writer :always_include_to_one_linkage_data
106109

107110
attr_writer :always_include_to_many_linkage_data
111+
112+
attr_writer :raise_if_parameters_not_allowed
108113
end
109114

110115
class << self

lib/jsonapi/error.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,17 @@ def initialize(options = {})
1717
@status = options[:status]
1818
end
1919
end
20+
21+
class Warning
22+
attr_accessor :title, :detail, :code
23+
def initialize(options = {})
24+
@title = options[:title]
25+
@detail = options[:detail]
26+
@code = if JSONAPI.configuration.use_text_errors
27+
TEXT_ERRORS[options[:code]]
28+
else
29+
options[:code]
30+
end
31+
end
32+
end
2033
end

lib/jsonapi/request.rb

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ module JSONAPI
55
class Request
66
attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :operations,
77
:resource_klass, :context, :paginator, :source_klass, :source_id,
8-
:include_directives, :params
8+
:include_directives, :params, :warnings
99

1010
def initialize(params = nil, options = {})
1111
@params = params
1212
@context = options[:context]
1313
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
1414
@errors = []
15+
@warnings = []
1516
@operations = []
1617
@fields = {}
1718
@filters = {}
@@ -487,19 +488,40 @@ def verify_permitted_params(params, allowed_fields)
487488
case key.to_s
488489
when 'relationships'
489490
value.each_key do |links_key|
490-
params_not_allowed.push(links_key) unless formatted_allowed_fields.include?(links_key.to_sym)
491+
unless formatted_allowed_fields.include?(links_key.to_sym)
492+
params_not_allowed.push(links_key)
493+
unless JSONAPI.configuration.raise_if_parameters_not_allowed
494+
value.delete links_key
495+
end
496+
end
491497
end
492498
when 'attributes'
493-
value.each do |attr_key, _attr_value|
494-
params_not_allowed.push(attr_key) unless formatted_allowed_fields.include?(attr_key.to_sym)
499+
value.each do |attr_key, attr_value|
500+
unless formatted_allowed_fields.include?(attr_key.to_sym)
501+
params_not_allowed.push(attr_key)
502+
unless JSONAPI.configuration.raise_if_parameters_not_allowed
503+
value.delete attr_key
504+
end
505+
end
495506
end
496507
when 'type', 'id'
497508
else
498509
params_not_allowed.push(key)
499510
end
500511
end
501512

502-
fail JSONAPI::Exceptions::ParametersNotAllowed.new(params_not_allowed) if params_not_allowed.length > 0
513+
if params_not_allowed.length > 0
514+
if JSONAPI.configuration.raise_if_parameters_not_allowed
515+
fail JSONAPI::Exceptions::ParametersNotAllowed.new(params_not_allowed)
516+
else
517+
params_not_allowed_warnings = params_not_allowed.map do |key|
518+
JSONAPI::Warning.new(code: JSONAPI::PARAM_NOT_ALLOWED,
519+
title: 'Param not allowed',
520+
detail: "#{key} is not allowed.")
521+
end
522+
self.warnings.concat(params_not_allowed_warnings)
523+
end
524+
end
503525
end
504526

505527
# TODO: Please remove after `updateable_fields` is removed

test/controllers/controller_test.rb

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ def set_content_type_header!
55
end
66

77
class PostsControllerTest < ActionController::TestCase
8+
def setup
9+
JSONAPI.configuration.raise_if_parameters_not_allowed = true
10+
end
11+
812
def test_index
913
get :index
1014
assert_response :success
@@ -401,6 +405,38 @@ def test_create_extra_param
401405
assert_match /asdfg is not allowed/, response.body
402406
end
403407

408+
def test_create_extra_param_allow_extra_params
409+
JSONAPI.configuration.raise_if_parameters_not_allowed = false
410+
411+
set_content_type_header!
412+
post :create,
413+
{
414+
data: {
415+
type: 'posts',
416+
attributes: {
417+
asdfg: 'aaaa',
418+
title: 'JR is Great',
419+
body: 'JSONAPIResources is the greatest thing since unsliced bread.'
420+
},
421+
relationships: {
422+
author: {data: {type: 'people', id: '3'}}
423+
}
424+
},
425+
include: 'author'
426+
}
427+
428+
assert_response :created
429+
assert json_response['data'].is_a?(Hash)
430+
assert_equal '3', json_response['data']['relationships']['author']['data']['id']
431+
assert_equal 'JR is Great', json_response['data']['attributes']['title']
432+
assert_equal 'JSONAPIResources is the greatest thing since unsliced bread.', json_response['data']['attributes']['body']
433+
434+
assert_equal 1, json_response['meta']["warnings"].count
435+
assert_equal "Param not allowed", json_response['meta']["warnings"][0]["title"]
436+
assert_equal "asdfg is not allowed.", json_response['meta']["warnings"][0]["detail"]
437+
assert_equal 105, json_response['meta']["warnings"][0]["code"]
438+
end
439+
404440
def test_create_with_invalid_data
405441
set_content_type_header!
406442
post :create,
@@ -575,6 +611,40 @@ def test_create_simple_unpermitted_attributes
575611
assert_match /subject/, json_response['errors'][0]['detail']
576612
end
577613

614+
def test_create_simple_unpermitted_attributes_allow_extra_params
615+
JSONAPI.configuration.raise_if_parameters_not_allowed = false
616+
617+
set_content_type_header!
618+
post :create,
619+
{
620+
data: {
621+
type: 'posts',
622+
attributes: {
623+
title: 'JR is Great',
624+
subject: 'JR is SUPER Great',
625+
body: 'JSONAPIResources is the greatest thing since unsliced bread.'
626+
},
627+
relationships: {
628+
author: {data: {type: 'people', id: '3'}}
629+
}
630+
},
631+
include: 'author'
632+
}
633+
634+
assert_response :created
635+
assert json_response['data'].is_a?(Hash)
636+
assert_equal '3', json_response['data']['relationships']['author']['data']['id']
637+
assert_equal 'JR is Great', json_response['data']['attributes']['title']
638+
assert_equal 'JR is Great', json_response['data']['attributes']['subject']
639+
assert_equal 'JSONAPIResources is the greatest thing since unsliced bread.', json_response['data']['attributes']['body']
640+
641+
642+
assert_equal 1, json_response['meta']["warnings"].count
643+
assert_equal "Param not allowed", json_response['meta']["warnings"][0]["title"]
644+
assert_equal "subject is not allowed.", json_response['meta']["warnings"][0]["detail"]
645+
assert_equal 105, json_response['meta']["warnings"][0]["code"]
646+
end
647+
578648
def test_create_with_links_to_many_type_ids
579649
set_content_type_header!
580650
post :create,
@@ -704,6 +774,46 @@ def test_update_with_internal_server_error
704774
assert_equal title, post_object.title
705775
end
706776

777+
def test_update_with_links_allow_extra_params
778+
JSONAPI.configuration.raise_if_parameters_not_allowed = false
779+
780+
set_content_type_header!
781+
javascript = Section.find_by(name: 'javascript')
782+
783+
put :update,
784+
{
785+
id: 3,
786+
data: {
787+
id: '3',
788+
type: 'posts',
789+
attributes: {
790+
title: 'A great new Post',
791+
subject: 'A great new Post',
792+
},
793+
relationships: {
794+
section: {data: {type: 'sections', id: "#{javascript.id}"}},
795+
tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
796+
}
797+
},
798+
include: 'tags,author,section'
799+
}
800+
801+
assert_response :success
802+
assert json_response['data'].is_a?(Hash)
803+
assert_equal '3', json_response['data']['relationships']['author']['data']['id']
804+
assert_equal javascript.id.to_s, json_response['data']['relationships']['section']['data']['id']
805+
assert_equal 'A great new Post', json_response['data']['attributes']['title']
806+
assert_equal 'AAAA', json_response['data']['attributes']['body']
807+
assert matches_array?([{'type' => 'tags', 'id' => '3'}, {'type' => 'tags', 'id' => '4'}],
808+
json_response['data']['relationships']['tags']['data'])
809+
810+
811+
assert_equal 1, json_response['meta']["warnings"].count
812+
assert_equal "Param not allowed", json_response['meta']["warnings"][0]["title"]
813+
assert_equal "subject is not allowed.", json_response['meta']["warnings"][0]["detail"]
814+
assert_equal 105, json_response['meta']["warnings"][0]["code"]
815+
end
816+
707817
def test_update_remove_links
708818
set_content_type_header!
709819
put :update,
@@ -1150,6 +1260,33 @@ def test_update_extra_param_in_links
11501260
assert_match /asdfg is not allowed/, response.body
11511261
end
11521262

1263+
def test_update_extra_param_in_links_allow_extra_params
1264+
JSONAPI.configuration.raise_if_parameters_not_allowed = false
1265+
1266+
set_content_type_header!
1267+
javascript = Section.find_by(name: 'javascript')
1268+
1269+
put :update,
1270+
{
1271+
id: 3,
1272+
data: {
1273+
type: 'posts',
1274+
id: '3',
1275+
attributes: {
1276+
title: 'A great new Post'
1277+
},
1278+
relationships: {
1279+
asdfg: 'aaaa'
1280+
}
1281+
}
1282+
}
1283+
1284+
assert_response :success
1285+
assert_equal "A great new Post", json_response["data"]["attributes"]["title"]
1286+
assert_equal "Param not allowed", json_response["meta"]["warnings"][0]["title"]
1287+
assert_equal "asdfg is not allowed.", json_response["meta"]["warnings"][0]["detail"]
1288+
end
1289+
11531290
def test_update_missing_param
11541291
set_content_type_header!
11551292
javascript = Section.find_by(name: 'javascript')

0 commit comments

Comments
 (0)