jsonapi_parameters 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|