jsonapi_parameters 2.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 514048af1321f2eb0d1c17e260d8bfc0ce3dbf3b0d8c70fc91f42724fdc6a0c1
4
- data.tar.gz: 46a52ac2fa28952b732d11479b9c1a2a3c2ff88bafd37816a920626b32ceda31
3
+ metadata.gz: 414c6d7e91cd71641096a2fc6dcb6d7055f9154cab1db06be6dbb188fddb3c6a
4
+ data.tar.gz: 483e0f4576c935632ca3cdbe48b10050d232dbb67e202a4db0fca2d4f0004721
5
5
  SHA512:
6
- metadata.gz: 406ea510828fb14bb8b4ba1298ad4a7f1c68a93391b8dce48a0e9132d8b7e05343febc0da9fd8f2e7aa8b6bb81f1a48a5cc23c51f01164d8d13e572b4f2b1c1e
7
- data.tar.gz: b455719c009aae879edf3e457f34222cee455a3583ae9c1c58348f4784efafe92c1b2407732500a35d8f5da026bdce9dff5624482c90950a0d3a8d4430c48d81
6
+ metadata.gz: 5a90b5d5a44fcbe5ee66e13308ab190e73a7a84409727c6c7530d66d5105baf4754987d634b71ebe4f7674d3b491e95a7ed8bb8e5fd399f4a638cb2bb272f087
7
+ data.tar.gz: 89d0c3dc2b6b100c53f1c446b80d9bd8acfc5e9db33587330c5fd57c362d9c92d8b9866f5c2afb95aa86233bb5c40ce72c61baac6cafa2b40d53df37d39dc47d
data/README.md CHANGED
@@ -93,7 +93,51 @@ translator.jsonapify(params)
93
93
 
94
94
  As [stated in the JSON:API specification](https://jsonapi.org/#mime-types) correct mime type for JSON:API input should be [`application/vnd.api+json`](http://www.iana.org/assignments/media-types/application/vnd.api+json).
95
95
 
96
- This gems intention is to make input consumption as easy as possible. Hence, it [registers this mime type for you](lib/jsonapi_parameters/core_ext/action_dispatch/http/mime_type.rb).
96
+ This gem's intention is to make input consumption as easy as possible. Hence, it [registers this mime type for you](lib/jsonapi_parameters/core_ext/action_dispatch/http/mime_type.rb).
97
+
98
+ ## Stack limit
99
+
100
+ In theory, any payload may consist of infinite amount of relationships (and so each relationship may have its own, included, infinite amount of nested relationships).
101
+ Because of that, it is a potential vector of attack.
102
+
103
+ For this reason we have introduced a default limit of stack levels that JsonApi::Parameters will go down through while parsing the payloads.
104
+
105
+ This default limit is 3, and can be overwritten by specifying the custom limit.
106
+
107
+ #### Ruby
108
+ ```ruby
109
+ class Translator
110
+ include JsonApi::Parameters
111
+ end
112
+
113
+ translator = Translator.new
114
+
115
+ translator.jsonapify(custom_stack_limit: 4)
116
+
117
+ # OR
118
+
119
+ translator.stack_limit = 4
120
+ translator.jsonapify.(...)
121
+ ```
122
+
123
+ #### Rails
124
+ ```ruby
125
+ def create_params
126
+ params.from_jsonapi(custom_stack_limit: 4).require(:user).permit(
127
+ entities_attributes: { subentities_attributes: { ... } }
128
+ )
129
+ end
130
+
131
+ # OR
132
+
133
+ def create_params
134
+ params.stack_level = 4
135
+
136
+ params.from_jsonapi.require(:user).permit(entities_attributes: { subentities_attributes: { ... } })
137
+ ensure
138
+ params.reset_stack_limit!
139
+ end
140
+ ```
97
141
 
98
142
  ## Customization
99
143
 
@@ -101,5 +145,11 @@ If you need custom relationship handling (for instance, if you have a relationsh
101
145
 
102
146
  Read more at [Relationship Handlers](https://github.com/visualitypl/jsonapi_parameters/wiki/Relationship-handlers).
103
147
 
148
+ ## Team
149
+
150
+ Project started by [Jasiek Matusz](https://github.com/Marahin).
151
+
152
+ Currently, jsonapi_parameters is maintained by Visuality's [Open Source Commitee](https://www.visuality.pl/open-source).
153
+
104
154
  ## License
105
155
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -9,7 +9,7 @@ class ActionController::Parameters
9
9
  from_jsonapi(*args)
10
10
  end
11
11
 
12
- def from_jsonapi(naming_convention = :snake)
13
- @from_jsonapi ||= self.class.new jsonapify(self, naming_convention: naming_convention)
12
+ def from_jsonapi(naming_convention = :snake, custom_stack_limit: stack_limit)
13
+ @from_jsonapi ||= self.class.new jsonapify(self, naming_convention: naming_convention, custom_stack_limit: custom_stack_limit)
14
14
  end
15
15
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/inflector'
2
+
1
3
  require_relative './base_handler'
2
4
 
3
5
  module JsonApi
@@ -1,3 +1,5 @@
1
+ require 'active_support/inflector'
2
+
1
3
  module JsonApi
2
4
  module Parameters
3
5
  module Handlers
@@ -34,8 +36,9 @@ module JsonApi
34
36
  @with_inclusion &= !included_object.empty?
35
37
 
36
38
  if with_inclusion
37
- included_object.delete(:type)
38
- included_object[:attributes].merge(id: related_id)
39
+ { **(included_object[:attributes] || {}), id: related_id }.tap do |body|
40
+ body[:relationships] = included_object[:relationships] if included_object.key?(:relationships) # Pass nested relationships
41
+ end
39
42
  else
40
43
  relationship.dig(:id)
41
44
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/inflector'
2
+
1
3
  module JsonApi
2
4
  module Parameters
3
5
  module Handlers
@@ -15,8 +17,10 @@ module JsonApi
15
17
 
16
18
  return ["#{singularize(relationship_key)}_id".to_sym, related_id] if included_object.empty?
17
19
 
18
- included_object.delete(:type)
19
- included_object = included_object[:attributes].merge(id: related_id)
20
+ included_object = { **(included_object[:attributes] || {}), id: related_id }.tap do |body|
21
+ body[:relationships] = included_object[:relationships] if included_object.key?(:relationships) # Pass nested relationships
22
+ end
23
+
20
24
  ["#{singularize(relationship_key)}_attributes".to_sym, included_object]
21
25
  end
22
26
  end
@@ -29,7 +29,7 @@ module JsonApi
29
29
  resource_handlers[resource_key.to_sym] = handler_key.to_sym
30
30
  end
31
31
 
32
- def reset_handlers!
32
+ def reset_handlers
33
33
  @handlers = DEFAULT_HANDLER_SET.dup
34
34
  @resource_handlers = {}
35
35
  end
@@ -0,0 +1,45 @@
1
+ module JsonApi
2
+ module Parameters
3
+ LIMIT = 3
4
+
5
+ class StackLevelTooDeepError < StandardError
6
+ end
7
+
8
+ def stack_limit=(val)
9
+ @stack_limit = val
10
+ end
11
+
12
+ def stack_limit
13
+ @stack_limit || LIMIT
14
+ end
15
+
16
+ def reset_stack_limit
17
+ @stack_limit = LIMIT
18
+ end
19
+
20
+ private
21
+
22
+ def initialize_stack(custom_stack_limit)
23
+ @current_stack_level = 0
24
+ @stack_limit = custom_stack_limit
25
+ end
26
+
27
+ def increment_stack_level!
28
+ @current_stack_level += 1
29
+
30
+ raise StackLevelTooDeepError.new(stack_exception_message) if @current_stack_level > stack_limit
31
+ end
32
+
33
+ def decrement_stack_level
34
+ @current_stack_level -= 1
35
+ end
36
+
37
+ def reset_stack_level
38
+ @current_stack_level = 0
39
+ end
40
+
41
+ def stack_exception_message
42
+ "Stack level of nested payload is too deep: #{@current_stack_level}/#{stack_limit}. Please see the documentation on how to overwrite the limit."
43
+ end
44
+ end
45
+ end
@@ -3,7 +3,9 @@ require 'active_support/inflector'
3
3
  module JsonApi::Parameters
4
4
  include ActiveSupport::Inflector
5
5
 
6
- def jsonapify(params, naming_convention: :snake)
6
+ def jsonapify(params, naming_convention: :snake, custom_stack_limit: stack_limit)
7
+ initialize_stack(custom_stack_limit)
8
+
7
9
  jsonapi_translate(params, naming_convention: naming_convention)
8
10
  end
9
11
 
@@ -38,27 +40,11 @@ module JsonApi::Parameters
38
40
  def jsonapi_main_body
39
41
  jsonapi_unsafe_params.tap do |param|
40
42
  jsonapi_relationships.each do |relationship_key, relationship_value|
41
- relationship_value = relationship_value[:data]
42
- handler_args = [relationship_key, relationship_value, jsonapi_included]
43
- handler = if Handlers.resource_handlers.key?(relationship_key)
44
- Handlers.handlers[Handlers.resource_handlers[relationship_key]]
45
- else
46
- case relationship_value
47
- when Array
48
- Handlers.handlers[:to_many]
49
- when Hash
50
- Handlers.handlers[:to_one]
51
- when nil
52
- Handlers.handlers[:nil]
53
- else
54
- raise NotImplementedError.new('relationship resource linkage has to be a type of Array, Hash or nil')
55
- end
56
- end
57
-
58
- key, val = handler.call(*handler_args)
59
- param[key] = val
43
+ param = handle_relationships(param, relationship_key, relationship_value)
60
44
  end
61
45
  end
46
+ ensure
47
+ reset_stack_level
62
48
  end
63
49
 
64
50
  def jsonapi_unsafe_params
@@ -69,11 +55,63 @@ module JsonApi::Parameters
69
55
  end
70
56
  end
71
57
 
58
+ def jsonapi_relationships
59
+ @jsonapi_relationships ||= @jsonapi_unsafe_hash.dig(:data, :relationships) || []
60
+ end
61
+
72
62
  def jsonapi_included
73
63
  @jsonapi_included ||= @jsonapi_unsafe_hash[:included] || []
74
64
  end
75
65
 
76
- def jsonapi_relationships
77
- @jsonapi_relationships ||= @jsonapi_unsafe_hash.dig(:data, :relationships) || []
66
+ def handle_relationships(param, relationship_key, relationship_value)
67
+ increment_stack_level!
68
+
69
+ relationship_value = relationship_value[:data]
70
+ handler_args = [relationship_key, relationship_value, jsonapi_included]
71
+ handler = if Handlers.resource_handlers.key?(relationship_key)
72
+ Handlers.handlers[Handlers.resource_handlers[relationship_key]]
73
+ else
74
+ case relationship_value
75
+ when Array
76
+ Handlers.handlers[:to_many]
77
+ when Hash
78
+ Handlers.handlers[:to_one]
79
+ when nil
80
+ Handlers.handlers[:nil]
81
+ else
82
+ raise NotImplementedError.new('relationship resource linkage has to be a type of Array, Hash or nil')
83
+ end
84
+ end
85
+
86
+ key, val = handler.call(*handler_args)
87
+
88
+ param[key] = handle_nested_relationships(val)
89
+
90
+ param
91
+ ensure
92
+ decrement_stack_level
93
+ end
94
+
95
+ def handle_nested_relationships(val)
96
+ # We can only consider Hash relationships (which imply to-one relationship) and Array relationships (which imply to-many).
97
+ # Each type has a different handling method, though in both cases we end up passing the nested relationship recursively to handle_relationship
98
+ # (and yes, this may go on indefinitely, basically we're going by the relationship levels, deeper and deeper)
99
+ case val
100
+ when Array
101
+ relationships_with_nested_relationships = val.select { |rel| rel.is_a?(Hash) && rel.dig(:relationships) }
102
+ relationships_with_nested_relationships.each do |relationship_with_nested_relationship|
103
+ relationship_with_nested_relationship.delete(:relationships).each do |rel_key, rel_val|
104
+ relationship_with_nested_relationship = handle_relationships(relationship_with_nested_relationship, rel_key, rel_val)
105
+ end
106
+ end
107
+ when Hash
108
+ if val.key?(:relationships)
109
+ val.delete(:relationships).each do |rel_key, rel_val|
110
+ val = handle_relationships(val, rel_key, rel_val)
111
+ end
112
+ end
113
+ end
114
+
115
+ val
78
116
  end
79
117
  end
@@ -1,5 +1,5 @@
1
1
  module JsonApi
2
2
  module Parameters
3
- VERSION = '2.1.0'.freeze
3
+ VERSION = '2.3.0'.freeze
4
4
  end
5
5
  end
@@ -2,6 +2,5 @@ require 'jsonapi_parameters/parameters'
2
2
  require 'jsonapi_parameters/handlers'
3
3
  require 'jsonapi_parameters/translator'
4
4
  require 'jsonapi_parameters/core_ext'
5
+ require 'jsonapi_parameters/stack_limit'
5
6
  require 'jsonapi_parameters/version'
6
-
7
- require 'active_support/inflector'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi_parameters
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Visuality
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-06-30 00:00:00.000000000 Z
12
+ date: 2021-01-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -45,14 +45,14 @@ dependencies:
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: 1.10.5
48
+ version: '1.11'
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: 1.10.5
55
+ version: '1.11'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: json
58
58
  requirement: !ruby/object:Gem::Requirement
@@ -327,6 +327,7 @@ files:
327
327
  - lib/jsonapi_parameters/default_handlers/to_one_relation_handler.rb
328
328
  - lib/jsonapi_parameters/handlers.rb
329
329
  - lib/jsonapi_parameters/parameters.rb
330
+ - lib/jsonapi_parameters/stack_limit.rb
330
331
  - lib/jsonapi_parameters/translator.rb
331
332
  - lib/jsonapi_parameters/version.rb
332
333
  homepage: https://github.com/visualitypl/jsonapi_parameters
@@ -341,14 +342,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
341
342
  requirements:
342
343
  - - ">="
343
344
  - !ruby/object:Gem::Version
344
- version: 2.3.0
345
+ version: 2.5.0
345
346
  required_rubygems_version: !ruby/object:Gem::Requirement
346
347
  requirements:
347
348
  - - ">="
348
349
  - !ruby/object:Gem::Version
349
350
  version: '0'
350
351
  requirements: []
351
- rubygems_version: 3.0.3
352
+ rubygems_version: 3.1.2
352
353
  signing_key:
353
354
  specification_version: 4
354
355
  summary: Translator for JSON:API compliant parameters