jsonapi_parameters 2.1.1 → 2.2.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 +4 -4
- data/README.md +47 -1
- data/lib/jsonapi_parameters.rb +1 -2
- data/lib/jsonapi_parameters/core_ext/action_controller/parameters.rb +2 -2
- data/lib/jsonapi_parameters/default_handlers/to_many_relation_handler.rb +5 -2
- data/lib/jsonapi_parameters/default_handlers/to_one_relation_handler.rb +6 -2
- data/lib/jsonapi_parameters/handlers.rb +1 -1
- data/lib/jsonapi_parameters/stack_limit.rb +45 -0
- data/lib/jsonapi_parameters/translator.rb +60 -22
- data/lib/jsonapi_parameters/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8bcf0cd62169a18d8f485540f7fe27e85d2ed8278121fe19309d149b5c59141a
|
4
|
+
data.tar.gz: eef89bf8b7061bf21add79bd09ce3b164c6b0d91e0d8db2898de99e231ab7d36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fbb1868c672869c4e0b561012dba89ed444271bef9246be22df68b564e397e21d1058860ce5416748c5a662f960bbe5c424db56882e2163bc8ae6516861de00e
|
7
|
+
data.tar.gz: '0940555db68b3b48522dbb33d6f035334ff8b149731b71b597df5e148b0d0d900141dd5213202c76eac4a406b2721c273773a401d90ae94caf44ae90b7a0c34c'
|
data/README.md
CHANGED
@@ -93,7 +93,53 @@ 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
|
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
|
+
```
|
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
|
+
```
|
125
|
+
# config/initializers/jsonapi_parameters.rb
|
126
|
+
|
127
|
+
def create_params
|
128
|
+
params.from_jsonapi(custom_stack_limit: 4).require(:user).permit(
|
129
|
+
entities_attributes: { subentities_attributes: { ... } }
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
# OR
|
134
|
+
|
135
|
+
def create_params
|
136
|
+
params.stack_level = 4
|
137
|
+
|
138
|
+
params.from_jsonapi.require(:user).permit(entities_attributes: { subentities_attributes: { ... } })
|
139
|
+
ensure
|
140
|
+
params.reset_stack_limit!
|
141
|
+
end
|
142
|
+
```
|
97
143
|
|
98
144
|
## Customization
|
99
145
|
|
data/lib/jsonapi_parameters.rb
CHANGED
@@ -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'
|
@@ -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
|
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
|
38
|
-
|
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
|
19
|
-
|
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
|
@@ -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
|
-
|
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
|
77
|
-
|
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
|
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.
|
4
|
+
version: 2.2.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-
|
12
|
+
date: 2020-08-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -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
|