jsonapi_parameters 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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