jsonapi-resources 0.9.12 → 0.10.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +34 -11
  4. data/lib/bug_report_templates/rails_5_latest.rb +125 -0
  5. data/lib/bug_report_templates/rails_5_master.rb +140 -0
  6. data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +27 -0
  7. data/lib/jsonapi/active_relation/join_manager.rb +303 -0
  8. data/lib/jsonapi/active_relation_resource.rb +884 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +122 -106
  10. data/lib/jsonapi/basic_resource.rb +1162 -0
  11. data/lib/jsonapi/cached_response_fragment.rb +127 -0
  12. data/lib/jsonapi/compiled_json.rb +11 -1
  13. data/lib/jsonapi/configuration.rb +57 -8
  14. data/lib/jsonapi/error.rb +27 -0
  15. data/lib/jsonapi/error_codes.rb +2 -0
  16. data/lib/jsonapi/exceptions.rb +63 -40
  17. data/lib/jsonapi/formatter.rb +3 -3
  18. data/lib/jsonapi/include_directives.rb +18 -75
  19. data/lib/jsonapi/link_builder.rb +18 -25
  20. data/lib/jsonapi/operation.rb +16 -5
  21. data/lib/jsonapi/operation_result.rb +73 -15
  22. data/lib/jsonapi/paginator.rb +17 -0
  23. data/lib/jsonapi/path.rb +43 -0
  24. data/lib/jsonapi/path_segment.rb +76 -0
  25. data/lib/jsonapi/processor.rb +246 -111
  26. data/lib/jsonapi/relationship.rb +117 -24
  27. data/lib/jsonapi/request_parser.rb +383 -396
  28. data/lib/jsonapi/resource.rb +3 -1376
  29. data/lib/jsonapi/resource_controller_metal.rb +3 -0
  30. data/lib/jsonapi/resource_fragment.rb +47 -0
  31. data/lib/jsonapi/resource_id_tree.rb +112 -0
  32. data/lib/jsonapi/resource_identity.rb +42 -0
  33. data/lib/jsonapi/resource_serializer.rb +124 -286
  34. data/lib/jsonapi/resource_set.rb +176 -0
  35. data/lib/jsonapi/resources/railtie.rb +9 -0
  36. data/lib/jsonapi/resources/version.rb +1 -1
  37. data/lib/jsonapi/response_document.rb +104 -87
  38. data/lib/jsonapi/routing_ext.rb +19 -21
  39. data/lib/jsonapi-resources.rb +20 -4
  40. data/lib/tasks/check_upgrade.rake +52 -0
  41. metadata +34 -31
  42. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  43. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  44. data/lib/jsonapi/operation_results.rb +0 -35
  45. data/lib/jsonapi/relationship_builder.rb +0 -167
@@ -1,132 +1,261 @@
1
- require 'jsonapi/operation'
2
- require 'jsonapi/paginator'
3
-
4
1
  module JSONAPI
5
2
  class RequestParser
6
- attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :operations,
7
- :resource_klass, :context, :paginator, :source_klass, :source_id,
3
+ attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :controller_module_path,
4
+ :context, :paginator, :source_klass, :source_id,
8
5
  :include_directives, :params, :warnings, :server_error_callbacks
9
6
 
10
7
  def initialize(params = nil, options = {})
11
8
  @params = params
9
+ if params
10
+ controller_path = params.fetch(:controller, '')
11
+ @controller_module_path = controller_path.include?('/') ? controller_path.rpartition('/').first + '/' : ''
12
+ else
13
+ @controller_module_path = ''
14
+ end
15
+
12
16
  @context = options[:context]
13
17
  @key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
14
18
  @errors = []
15
19
  @warnings = []
16
- @operations = []
17
- @fields = {}
18
- @filters = {}
19
- @sort_criteria = nil
20
- @source_klass = nil
21
- @source_id = nil
22
- @include_directives = nil
23
- @paginator = nil
24
- @id = nil
25
20
  @server_error_callbacks = options.fetch(:server_error_callbacks, [])
21
+ end
22
+
23
+ def error_object_overrides
24
+ {}
25
+ end
26
+
27
+ def each(_response_document)
28
+ operation = setup_base_op(params)
29
+ if @errors.any?
30
+ fail JSONAPI::Exceptions::Errors.new(@errors)
31
+ else
32
+ yield operation
33
+ end
34
+ rescue ActionController::ParameterMissing => e
35
+ fail JSONAPI::Exceptions::ParameterMissing.new(e.param, error_object_overrides)
36
+ end
26
37
 
27
- setup_action(@params)
38
+ def transactional?
39
+ case params[:action]
40
+ when 'index', 'show_related_resource', 'index_related_resources', 'show', 'show_relationship'
41
+ return false
42
+ else
43
+ return true
44
+ end
28
45
  end
29
46
 
30
- def setup_action(params)
47
+ def setup_base_op(params)
31
48
  return if params.nil?
32
49
 
33
- @resource_klass ||= Resource.resource_for(params[:controller]) if params[:controller]
50
+ resource_klass = Resource.resource_klass_for(params[:controller]) if params[:controller]
34
51
 
35
52
  setup_action_method_name = "setup_#{params[:action]}_action"
36
53
  if respond_to?(setup_action_method_name)
37
54
  raise params[:_parser_exception] if params[:_parser_exception]
38
- send(setup_action_method_name, params)
55
+ send(setup_action_method_name, params, resource_klass)
39
56
  end
40
57
  rescue ActionController::ParameterMissing => e
41
- @errors.concat(JSONAPI::Exceptions::ParameterMissing.new(e.param).errors)
58
+ @errors.concat(JSONAPI::Exceptions::ParameterMissing.new(e.param, error_object_overrides).errors)
59
+ rescue JSONAPI::Exceptions::Error => e
60
+ e.error_object_overrides.merge! error_object_overrides
61
+ @errors.concat(e.errors)
42
62
  end
43
63
 
44
- def setup_index_action(params)
45
- parse_fields(params[:fields])
46
- parse_include_directives(params[:include])
47
- set_default_filters
48
- parse_filters(params[:filter])
49
- parse_sort_criteria(params[:sort])
50
- parse_pagination(params[:page])
51
- add_find_operation
64
+ def setup_index_action(params, resource_klass)
65
+ fields = parse_fields(resource_klass, params[:fields])
66
+ include_directives = parse_include_directives(resource_klass, params[:include])
67
+ filters = parse_filters(resource_klass, params[:filter])
68
+ sort_criteria = parse_sort_criteria(resource_klass, params[:sort])
69
+ paginator = parse_pagination(resource_klass, params[:page])
70
+
71
+ JSONAPI::Operation.new(
72
+ :find,
73
+ resource_klass,
74
+ context: context,
75
+ filters: filters,
76
+ include_directives: include_directives,
77
+ sort_criteria: sort_criteria,
78
+ paginator: paginator,
79
+ fields: fields
80
+ )
52
81
  end
53
82
 
54
- def setup_get_related_resource_action(params)
55
- resolve_singleton_id(params)
56
- initialize_source(params)
57
- parse_fields(params[:fields])
58
- parse_include_directives(params[:include])
59
- set_default_filters
60
- parse_filters(params[:filter])
61
- parse_sort_criteria(params[:sort])
62
- parse_pagination(params[:page])
63
- add_show_related_resource_operation(params[:relationship])
83
+ def setup_show_related_resource_action(params, resource_klass)
84
+ resolve_singleton_id(params, resource_klass)
85
+ source_klass = Resource.resource_klass_for(params.require(:source))
86
+ source_id = source_klass.verify_key(params.require(source_klass._as_parent_key), @context)
87
+
88
+ fields = parse_fields(resource_klass, params[:fields])
89
+ include_directives = parse_include_directives(resource_klass, params[:include])
90
+
91
+ relationship_type = params[:relationship].to_sym
92
+
93
+ JSONAPI::Operation.new(
94
+ :show_related_resource,
95
+ resource_klass,
96
+ context: @context,
97
+ relationship_type: relationship_type,
98
+ source_klass: source_klass,
99
+ source_id: source_id,
100
+ fields: fields,
101
+ include_directives: include_directives
102
+ )
64
103
  end
65
104
 
66
- def setup_get_related_resources_action(params)
67
- resolve_singleton_id(params)
68
- initialize_source(params)
69
- parse_fields(params[:fields])
70
- parse_include_directives(params[:include])
71
- set_default_filters
72
- parse_filters(params[:filter])
73
- parse_sort_criteria(params[:sort])
74
- parse_pagination(params[:page])
75
- add_show_related_resources_operation(params[:relationship])
105
+ def setup_index_related_resources_action(params, resource_klass)
106
+ resolve_singleton_id(params, resource_klass)
107
+ source_klass = Resource.resource_klass_for(params.require(:source))
108
+ source_id = source_klass.verify_key(params.require(source_klass._as_parent_key), @context)
109
+
110
+ fields = parse_fields(resource_klass, params[:fields])
111
+ include_directives = parse_include_directives(resource_klass, params[:include])
112
+ filters = parse_filters(resource_klass, params[:filter])
113
+ sort_criteria = parse_sort_criteria(resource_klass, params[:sort])
114
+ paginator = parse_pagination(resource_klass, params[:page])
115
+ relationship_type = params[:relationship]
116
+
117
+ JSONAPI::Operation.new(
118
+ :show_related_resources,
119
+ resource_klass,
120
+ context: @context,
121
+ relationship_type: relationship_type,
122
+ source_klass: source_klass,
123
+ source_id: source_id,
124
+ filters: filters,
125
+ sort_criteria: sort_criteria,
126
+ paginator: paginator,
127
+ fields: fields,
128
+ include_directives: include_directives
129
+ )
76
130
  end
77
131
 
78
- def setup_show_action(params)
79
- resolve_singleton_id(params)
80
- parse_fields(params[:fields])
81
- parse_include_directives(params[:include])
82
- parse_filters(params[:filter])
132
+ def setup_show_action(params, resource_klass)
133
+ resolve_singleton_id(params, resource_klass)
134
+ fields = parse_fields(resource_klass, params[:fields])
135
+ include_directives = parse_include_directives(resource_klass, params[:include])
136
+ id = params[:id]
83
137
 
84
- @id = params[:id]
85
- add_show_operation
138
+ JSONAPI::Operation.new(
139
+ :show,
140
+ resource_klass,
141
+ context: @context,
142
+ id: id,
143
+ include_directives: include_directives,
144
+ fields: fields,
145
+ allowed_resources: params[:allowed_resources]
146
+ )
86
147
  end
87
148
 
88
- def setup_show_relationship_action(params)
89
- resolve_singleton_id(params)
90
- add_show_relationship_operation(params[:relationship], params.require(@resource_klass._as_parent_key))
149
+ def setup_show_relationship_action(params, resource_klass)
150
+ resolve_singleton_id(params, resource_klass)
151
+ relationship_type = params[:relationship]
152
+ parent_key = params.require(resource_klass._as_parent_key)
153
+ include_directives = parse_include_directives(resource_klass, params[:include])
154
+ filters = parse_filters(resource_klass, params[:filter])
155
+ sort_criteria = parse_sort_criteria(resource_klass, params[:sort])
156
+ paginator = parse_pagination(resource_klass, params[:page])
157
+
158
+ JSONAPI::Operation.new(
159
+ :show_relationship,
160
+ resource_klass,
161
+ context: @context,
162
+ relationship_type: relationship_type,
163
+ parent_key: resource_klass.verify_key(parent_key),
164
+ filters: filters,
165
+ sort_criteria: sort_criteria,
166
+ paginator: paginator,
167
+ fields: fields,
168
+ include_directives: include_directives
169
+ )
91
170
  end
92
171
 
93
- def setup_create_action(params)
94
- parse_fields(params[:fields])
95
- parse_include_directives(params[:include])
96
- parse_add_operation(params.require(:data))
172
+ def setup_create_action(params, resource_klass)
173
+ fields = parse_fields(resource_klass, params[:fields])
174
+ include_directives = parse_include_directives(resource_klass, params[:include])
175
+
176
+ data = params.require(:data)
177
+
178
+ unless data.respond_to?(:each_pair)
179
+ fail JSONAPI::Exceptions::InvalidDataFormat.new(error_object_overrides)
180
+ end
181
+
182
+ verify_type(data[:type], resource_klass)
183
+
184
+ data = parse_params(resource_klass, data, resource_klass.creatable_fields(@context))
185
+
186
+ JSONAPI::Operation.new(
187
+ :create_resource,
188
+ resource_klass,
189
+ context: @context,
190
+ data: data,
191
+ fields: fields,
192
+ include_directives: include_directives,
193
+ warnings: @warnings
194
+ )
97
195
  end
98
196
 
99
- def setup_create_relationship_action(params)
100
- resolve_singleton_id(params)
101
- parse_modify_relationship_action(params, :add)
197
+ def setup_create_relationship_action(params, resource_klass)
198
+ resolve_singleton_id(params, resource_klass)
199
+ parse_modify_relationship_action(:add, params, resource_klass)
102
200
  end
103
201
 
104
- def setup_update_relationship_action(params)
105
- resolve_singleton_id(params)
106
- parse_modify_relationship_action(params, :update)
202
+ def setup_update_relationship_action(params, resource_klass)
203
+ parse_modify_relationship_action(:update, params, resource_klass)
107
204
  end
108
205
 
109
- def setup_update_action(params)
110
- resolve_singleton_id(params)
111
- parse_fields(params[:fields])
112
- parse_include_directives(params[:include])
113
- parse_replace_operation(params.require(:data), params[:id])
206
+ def setup_update_action(params, resource_klass)
207
+ resolve_singleton_id(params, resource_klass)
208
+ fields = parse_fields(resource_klass, params[:fields])
209
+ include_directives = parse_include_directives(resource_klass, params[:include])
210
+
211
+ data = params.require(:data)
212
+ key = params[:id]
213
+
214
+ fail JSONAPI::Exceptions::InvalidDataFormat.new(error_object_overrides) unless data.respond_to?(:each_pair)
215
+
216
+ fail JSONAPI::Exceptions::MissingKey.new(error_object_overrides) if data[:id].nil?
217
+
218
+ resource_id = data.require(:id)
219
+ # Singleton resources may not have the ID set in the URL
220
+ if key
221
+ fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(resource_id) if key.to_s != resource_id.to_s
222
+ end
223
+
224
+ data.delete(:id)
225
+
226
+ verify_type(data[:type], resource_klass)
227
+
228
+ JSONAPI::Operation.new(
229
+ :replace_fields,
230
+ resource_klass,
231
+ context: @context,
232
+ resource_id: resource_id,
233
+ data: parse_params(resource_klass, data, resource_klass.updatable_fields(@context)),
234
+ fields: fields,
235
+ include_directives: include_directives,
236
+ warnings: @warnings
237
+ )
114
238
  end
115
239
 
116
- def setup_destroy_action(params)
117
- resolve_singleton_id(params)
118
- parse_remove_operation(params)
240
+ def setup_destroy_action(params, resource_klass)
241
+ resolve_singleton_id(params, resource_klass)
242
+ JSONAPI::Operation.new(
243
+ :remove_resource,
244
+ resource_klass,
245
+ context: @context,
246
+ resource_id: resource_klass.verify_key(params.require(:id), @context))
119
247
  end
120
248
 
121
- def setup_destroy_relationship_action(params)
122
- resolve_singleton_id(params)
123
- parse_modify_relationship_action(params, :remove)
249
+ def setup_destroy_relationship_action(params, resource_klass)
250
+ resolve_singleton_id(params, resource_klass)
251
+ parse_modify_relationship_action(:remove, params, resource_klass)
124
252
  end
125
253
 
126
- def parse_modify_relationship_action(params, modification_type)
254
+ def parse_modify_relationship_action(modification_type, params, resource_klass)
127
255
  relationship_type = params.require(:relationship)
128
- parent_key = params.require(@resource_klass._as_parent_key)
129
- relationship = @resource_klass._relationship(relationship_type)
256
+
257
+ parent_key = params.require(resource_klass._as_parent_key)
258
+ relationship = resource_klass._relationship(relationship_type)
130
259
 
131
260
  # Removals of to-one relationships are done implicitly and require no specification of data
132
261
  data_required = !(modification_type == :remove && relationship.is_a?(JSONAPI::Relationship::ToOne))
@@ -134,75 +263,74 @@ module JSONAPI
134
263
  if data_required
135
264
  data = params.fetch(:data)
136
265
  object_params = { relationships: { format_key(relationship.name) => { data: data } } }
137
- verified_params = parse_params(object_params, @resource_klass.updatable_fields(@context))
138
266
 
139
- parse_arguments = [verified_params, relationship, parent_key]
267
+ verified_params = parse_params(resource_klass, object_params, resource_klass.updatable_fields(@context))
268
+
269
+ parse_arguments = [resource_klass, verified_params, relationship, parent_key]
140
270
  else
141
- parse_arguments = [params, relationship, parent_key]
271
+ parse_arguments = [resource_klass, params, relationship, parent_key]
142
272
  end
143
273
 
144
274
  send(:"parse_#{modification_type}_relationship_operation", *parse_arguments)
145
275
  end
146
276
 
147
- def initialize_source(params)
148
- @source_klass = Resource.resource_for(params.require(:source))
149
- @source_id = @source_klass.verify_key(params.require(@source_klass._as_parent_key), @context)
150
- end
151
-
152
- def parse_pagination(page)
153
- paginator_name = @resource_klass._paginator
154
- @paginator = JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
155
- rescue JSONAPI::Exceptions::Error => e
156
- @errors.concat(e.errors)
277
+ def parse_pagination(resource_klass, page)
278
+ paginator_name = resource_klass._paginator
279
+ JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
157
280
  end
158
281
 
159
- def parse_fields(fields)
160
- return if fields.nil?
161
-
282
+ def parse_fields(resource_klass, fields)
162
283
  extracted_fields = {}
163
284
 
285
+ return extracted_fields if fields.nil?
286
+
164
287
  # Extract the fields for each type from the fields parameters
165
288
  if fields.is_a?(ActionController::Parameters)
166
289
  fields.each do |field, value|
167
- resource_fields = value.split(',') unless value.nil? || value.empty?
290
+ if value.is_a?(Array)
291
+ resource_fields = value
292
+ else
293
+ resource_fields = value.split(',') unless value.nil? || value.empty?
294
+ end
168
295
  extracted_fields[field] = resource_fields
169
296
  end
170
297
  else
171
- fail JSONAPI::Exceptions::InvalidFieldFormat.new
298
+ fail JSONAPI::Exceptions::InvalidFieldFormat.new(error_object_overrides)
172
299
  end
173
300
 
174
301
  # Validate the fields
302
+ validated_fields = {}
175
303
  extracted_fields.each do |type, values|
176
304
  underscored_type = unformat_key(type)
177
- extracted_fields[type] = []
305
+ validated_fields[type] = []
178
306
  begin
179
307
  if type != format_key(type)
180
- fail JSONAPI::Exceptions::InvalidResource.new(type)
308
+ fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
181
309
  end
182
- type_resource = Resource.resource_for(@resource_klass.module_path + underscored_type.to_s)
310
+ type_resource = Resource.resource_klass_for(resource_klass.module_path + underscored_type.to_s)
183
311
  rescue NameError
184
- fail JSONAPI::Exceptions::InvalidResource.new(type)
312
+ fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
185
313
  end
186
314
 
187
315
  if type_resource.nil?
188
- fail JSONAPI::Exceptions::InvalidResource.new(type)
316
+ fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
189
317
  else
190
318
  unless values.nil?
191
319
  valid_fields = type_resource.fields.collect { |key| format_key(key) }
192
320
  values.each do |field|
193
321
  if valid_fields.include?(field)
194
- extracted_fields[type].push unformat_key(field)
322
+ validated_fields[type].push unformat_key(field)
195
323
  else
196
- fail JSONAPI::Exceptions::InvalidField.new(type, field)
324
+ fail JSONAPI::Exceptions::InvalidField.new(type, field, error_object_overrides)
197
325
  end
198
326
  end
199
327
  else
200
- fail JSONAPI::Exceptions::InvalidField.new(type, 'nil')
328
+ fail JSONAPI::Exceptions::InvalidField.new(type, 'nil', error_object_overrides)
201
329
  end
202
330
  end
203
331
  end
204
332
 
205
- @fields = extracted_fields.deep_transform_keys { |key| unformat_key(key) }
333
+ validated_fields.deep_transform_keys { |key| unformat_key(key) }
206
334
  end
207
335
 
208
336
  def check_include(resource_klass, include_parts)
@@ -210,243 +338,144 @@ module JSONAPI
210
338
 
211
339
  relationship = resource_klass._relationship(relationship_name)
212
340
  if relationship && format_key(relationship_name) == include_parts.first
341
+ unless relationship.allow_include?(context)
342
+ fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), include_parts.first)
343
+ end
344
+
213
345
  unless include_parts.last.empty?
214
- check_include(Resource.resource_for(resource_klass.module_path + relationship.class_name.to_s.underscore), include_parts.last.partition('.'))
346
+ check_include(Resource.resource_klass_for(resource_klass.module_path + relationship.class_name.to_s.underscore),
347
+ include_parts.last.partition('.'))
215
348
  end
216
349
  else
217
350
  fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), include_parts.first)
218
351
  end
352
+ true
219
353
  end
220
354
 
221
- def parse_include_directives(raw_include)
222
- return unless raw_include
223
-
224
- unless JSONAPI.configuration.allow_include
225
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(:include)
226
- end
355
+ def parse_include_directives(resource_klass, raw_include)
356
+ raw_include ||= ''
227
357
 
228
358
  included_resources = []
229
359
  begin
230
- included_resources += Array(CSV.parse_line(raw_include))
360
+ included_resources += raw_include.is_a?(Array) ? raw_include : CSV.parse_line(raw_include) || []
231
361
  rescue CSV::MalformedCSVError
232
- fail JSONAPI::Exceptions::InvalidInclude.new(format_key(@resource_klass._type), raw_include)
362
+ fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), raw_include)
233
363
  end
234
364
 
235
- return if included_resources.empty?
236
-
237
365
  begin
238
366
  result = included_resources.compact.map do |included_resource|
239
- check_include(@resource_klass, included_resource.partition('.'))
367
+ check_include(resource_klass, included_resource.partition('.'))
240
368
  unformat_key(included_resource).to_s
241
369
  end
242
370
 
243
- @include_directives = JSONAPI::IncludeDirectives.new(@resource_klass, result)
371
+ return JSONAPI::IncludeDirectives.new(resource_klass, result)
244
372
  rescue JSONAPI::Exceptions::InvalidInclude => e
245
373
  @errors.concat(e.errors)
246
- @include_directives = JSONAPI::IncludeDirectives.new(@resource_klass, [])
374
+ return {}
247
375
  end
248
376
  end
249
377
 
250
- def parse_filters(filters)
251
- return unless filters
378
+ def parse_filters(resource_klass, filters)
379
+ parsed_filters = {}
252
380
 
253
- unless JSONAPI.configuration.allow_filter
254
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(:filter)
381
+ # apply default filters
382
+ resource_klass._allowed_filters.each do |filter, opts|
383
+ next if opts[:default].nil? || !parsed_filters[filter].nil?
384
+ parsed_filters[filter] = opts[:default]
255
385
  end
256
386
 
387
+ return parsed_filters unless filters
388
+
257
389
  unless filters.class.method_defined?(:each)
258
390
  @errors.concat(JSONAPI::Exceptions::InvalidFiltersSyntax.new(filters).errors)
259
- return
391
+ return {}
260
392
  end
261
393
 
262
- filters.each do |key, value|
263
-
264
- unformatted_key = unformat_key(key)
265
- if resource_klass._allowed_filter?(unformatted_key)
266
- @filters[unformatted_key] = value
267
- elsif unformatted_key.to_s.include?('.')
268
- parse_relationship_filter(unformatted_key, value)
269
- else
270
- return @errors.concat(Exceptions::FilterNotAllowed.new(unformatted_key).errors)
271
- end
394
+ unless JSONAPI.configuration.allow_filter
395
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(:filter)
272
396
  end
273
- end
274
-
275
- def parse_relationship_filter(key, value)
276
- included_resource_name, filter_method = key.to_s.split('.')
277
- filter_method = filter_method.to_sym if filter_method.present?
278
-
279
- if included_resource_name
280
- relationship = resource_klass._relationship(included_resource_name || '')
281
-
282
- unless relationship
283
- return @errors.concat(Exceptions::FilterNotAllowed.new(filter_method).errors)
284
- end
285
-
286
- unless relationship.resource_klass._allowed_filter?(filter_method)
287
- return @errors.concat(Exceptions::FilterNotAllowed.new(filter_method).errors)
288
- end
289
397
 
290
- unless @include_directives.try(:include_config, relationship.name.to_sym).present?
291
- return @errors.concat(Exceptions::FilterNotAllowed.new(filter_method).errors)
398
+ filters.each do |key, value|
399
+ filter = unformat_key(key)
400
+ if resource_klass._allowed_filter?(filter)
401
+ parsed_filters[filter] = value
402
+ else
403
+ fail JSONAPI::Exceptions::FilterNotAllowed.new(key)
292
404
  end
293
-
294
- verified_filter = relationship.resource_klass.verify_filters({ filter_method => value }, @context)
295
- @include_directives.merge_filter(relationship.name, verified_filter)
296
- else
297
- return @errors.concat(Exceptions::FilterNotAllowed.new(filter_method).errors)
298
405
  end
299
- end
300
406
 
301
- def set_default_filters
302
- @resource_klass._allowed_filters.each do |filter, opts|
303
- next if opts[:default].nil? || !@filters[filter].nil?
304
- @filters[filter] = opts[:default]
305
- end
407
+ parsed_filters
306
408
  end
307
409
 
308
- def parse_sort_criteria(sort_criteria)
410
+ def parse_sort_criteria(resource_klass, sort_criteria)
309
411
  return unless sort_criteria.present?
310
412
 
311
413
  unless JSONAPI.configuration.allow_sort
312
414
  fail JSONAPI::Exceptions::ParameterNotAllowed.new(:sort)
313
415
  end
314
416
 
315
- sorts = []
316
- begin
317
- raw = URI::DEFAULT_PARSER.unescape(sort_criteria)
318
- sorts += CSV.parse_line(raw)
319
- rescue CSV::MalformedCSVError
320
- fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(@resource_klass._type), raw)
417
+ if sort_criteria.is_a?(Array)
418
+ sorts = sort_criteria
419
+ elsif sort_criteria.is_a?(String)
420
+ begin
421
+ raw = URI.decode_www_form_component(sort_criteria)
422
+ sorts = CSV.parse_line(raw)
423
+ rescue CSV::MalformedCSVError
424
+ fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(resource_klass._type), raw)
425
+ end
321
426
  end
322
427
 
323
428
  @sort_criteria = sorts.collect do |sort|
324
429
  if sort.start_with?('-')
325
- sort_criteria = { field: unformat_key(sort[1..-1]).to_s }
326
- sort_criteria[:direction] = :desc
430
+ criteria = { field: unformat_key(sort[1..-1]).to_s }
431
+ criteria[:direction] = :desc
327
432
  else
328
- sort_criteria = { field: unformat_key(sort).to_s }
329
- sort_criteria[:direction] = :asc
433
+ criteria = { field: unformat_key(sort).to_s }
434
+ criteria[:direction] = :asc
330
435
  end
331
436
 
332
- check_sort_criteria(@resource_klass, sort_criteria)
333
- sort_criteria
437
+ check_sort_criteria(resource_klass, criteria)
438
+ criteria
334
439
  end
335
440
  end
336
441
 
337
442
  def check_sort_criteria(resource_klass, sort_criteria)
338
443
  sort_field = sort_criteria[:field]
339
- sortable_fields = resource_klass.sortable_fields(context)
340
444
 
341
- unless sortable_fields.include?sort_field.to_sym
445
+ unless resource_klass.sortable_field?(sort_field.to_sym, context)
342
446
  fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(resource_klass._type), sort_field)
343
447
  end
344
448
  end
345
449
 
346
- def add_find_operation
347
- @operations.push JSONAPI::Operation.new(:find,
348
- @resource_klass,
349
- context: @context,
350
- filters: @filters,
351
- include_directives: @include_directives,
352
- sort_criteria: @sort_criteria,
353
- paginator: @paginator,
354
- fields: @fields
355
- )
356
- end
357
-
358
- def add_show_operation
359
- @operations.push JSONAPI::Operation.new(:show,
360
- @resource_klass,
361
- context: @context,
362
- id: @id,
363
- include_directives: @include_directives,
364
- fields: @fields
365
- )
366
- end
367
-
368
- def add_show_relationship_operation(relationship_type, parent_key)
369
- @operations.push JSONAPI::Operation.new(:show_relationship,
370
- @resource_klass,
371
- context: @context,
372
- relationship_type: relationship_type,
373
- parent_key: @resource_klass.verify_key(parent_key)
374
- )
375
- end
376
-
377
- def add_show_related_resource_operation(relationship_type)
378
- @operations.push JSONAPI::Operation.new(:show_related_resource,
379
- @resource_klass,
380
- context: @context,
381
- relationship_type: relationship_type,
382
- source_klass: @source_klass,
383
- source_id: @source_id,
384
- fields: @fields,
385
- include_directives: @include_directives
386
- )
387
- end
388
-
389
- def add_show_related_resources_operation(relationship_type)
390
- @operations.push JSONAPI::Operation.new(:show_related_resources,
391
- @resource_klass,
392
- context: @context,
393
- relationship_type: relationship_type,
394
- source_klass: @source_klass,
395
- source_id: @source_id,
396
- filters: @filters,
397
- sort_criteria: @sort_criteria,
398
- paginator: @paginator,
399
- fields: @fields,
400
- include_directives: @include_directives
401
- )
402
- end
403
-
404
- def parse_add_operation(params)
405
- fail JSONAPI::Exceptions::InvalidDataFormat unless params.respond_to?(:each_pair)
406
-
407
- verify_type(params[:type])
408
-
409
- data = parse_params(params, @resource_klass.creatable_fields(@context))
410
- @operations.push JSONAPI::Operation.new(:create_resource,
411
- @resource_klass,
412
- context: @context,
413
- data: data,
414
- fields: @fields,
415
- include_directives: @include_directives
416
- )
417
- rescue JSONAPI::Exceptions::Error => e
418
- @errors.concat(e.errors)
419
- end
420
-
421
- def verify_type(type)
450
+ def verify_type(type, resource_klass)
422
451
  if type.nil?
423
452
  fail JSONAPI::Exceptions::ParameterMissing.new(:type)
424
- elsif unformat_key(type).to_sym != @resource_klass._type
425
- fail JSONAPI::Exceptions::InvalidResource.new(type)
453
+ elsif unformat_key(type).to_sym != resource_klass._type
454
+ fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
426
455
  end
427
456
  end
428
457
 
429
458
  def parse_to_one_links_object(raw)
430
459
  if raw.nil?
431
460
  return {
432
- type: nil,
433
- id: nil
461
+ type: nil,
462
+ id: nil
434
463
  }
435
464
  end
436
465
 
437
466
  if !(raw.is_a?(Hash) || raw.is_a?(ActionController::Parameters)) ||
438
- raw.keys.length != 2 || !(raw.key?('type') && raw.key?('id'))
439
- fail JSONAPI::Exceptions::InvalidLinksObject.new
467
+ raw.keys.length != 2 || !(raw.key?('type') && raw.key?('id'))
468
+ fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides)
440
469
  end
441
470
 
442
471
  {
443
- type: unformat_key(raw['type']).to_s,
444
- id: raw['id']
472
+ type: unformat_key(raw['type']).to_s,
473
+ id: raw['id']
445
474
  }
446
475
  end
447
476
 
448
477
  def parse_to_many_links_object(raw)
449
- fail JSONAPI::Exceptions::InvalidLinksObject.new if raw.nil?
478
+ fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides) if raw.nil?
450
479
 
451
480
  links_object = {}
452
481
  if raw.is_a?(Array)
@@ -456,12 +485,12 @@ module JSONAPI
456
485
  links_object[link_object[:type]].push(link_object[:id])
457
486
  end
458
487
  else
459
- fail JSONAPI::Exceptions::InvalidLinksObject.new
488
+ fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides)
460
489
  end
461
490
  links_object
462
491
  end
463
492
 
464
- def parse_params(params, allowed_fields)
493
+ def parse_params(resource_klass, params, allowed_fields)
465
494
  verify_permitted_params(params, allowed_fields)
466
495
 
467
496
  checked_attributes = {}
@@ -470,37 +499,37 @@ module JSONAPI
470
499
 
471
500
  params.each do |key, value|
472
501
  case key.to_s
473
- when 'relationships'
474
- value.each do |link_key, link_value|
475
- param = unformat_key(link_key)
476
- relationship = @resource_klass._relationship(param)
477
-
478
- if relationship.is_a?(JSONAPI::Relationship::ToOne)
479
- checked_to_one_relationships[param] = parse_to_one_relationship(link_value, relationship)
480
- elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
481
- parse_to_many_relationship(link_value, relationship) do |result_val|
482
- checked_to_many_relationships[param] = result_val
502
+ when 'relationships'
503
+ value.each do |link_key, link_value|
504
+ param = unformat_key(link_key)
505
+ relationship = resource_klass._relationship(param)
506
+
507
+ if relationship.is_a?(JSONAPI::Relationship::ToOne)
508
+ checked_to_one_relationships[param] = parse_to_one_relationship(resource_klass, link_value, relationship)
509
+ elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
510
+ parse_to_many_relationship(resource_klass, link_value, relationship) do |result_val|
511
+ checked_to_many_relationships[param] = result_val
512
+ end
483
513
  end
484
514
  end
485
- end
486
- when 'id'
487
- checked_attributes['id'] = unformat_value(:id, value)
488
- when 'attributes'
489
- value.each do |key, value|
490
- param = unformat_key(key)
491
- checked_attributes[param] = unformat_value(param, value)
492
- end
515
+ when 'id'
516
+ checked_attributes['id'] = unformat_value(resource_klass, :id, value)
517
+ when 'attributes'
518
+ value.each do |key, value|
519
+ param = unformat_key(key)
520
+ checked_attributes[param] = unformat_value(resource_klass, param, value)
521
+ end
493
522
  end
494
523
  end
495
524
 
496
525
  return {
497
- 'attributes' => checked_attributes,
498
- 'to_one' => checked_to_one_relationships,
499
- 'to_many' => checked_to_many_relationships
526
+ 'attributes' => checked_attributes,
527
+ 'to_one' => checked_to_one_relationships,
528
+ 'to_many' => checked_to_many_relationships
500
529
  }.deep_transform_keys { |key| unformat_key(key) }
501
530
  end
502
531
 
503
- def parse_to_one_relationship(link_value, relationship)
532
+ def parse_to_one_relationship(resource_klass, link_value, relationship)
504
533
  if link_value.nil?
505
534
  linkage = nil
506
535
  else
@@ -509,12 +538,12 @@ module JSONAPI
509
538
 
510
539
  links_object = parse_to_one_links_object(linkage)
511
540
  if !relationship.polymorphic? && links_object[:type] && (links_object[:type].to_s != relationship.type.to_s)
512
- fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
541
+ fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type], error_object_overrides)
513
542
  end
514
543
 
515
544
  unless links_object[:id].nil?
516
- resource = self.resource_klass || Resource
517
- relationship_resource = resource.resource_for(unformat_key(relationship.options[:class_name] || links_object[:type]).to_s)
545
+ resource = resource_klass || Resource
546
+ relationship_resource = resource.resource_klass_for(unformat_key(relationship.options[:class_name] || links_object[:type]).to_s)
518
547
  relationship_id = relationship_resource.verify_key(links_object[:id], @context)
519
548
  if relationship.polymorphic?
520
549
  { id: relationship_id, type: unformat_key(links_object[:type].to_s) }
@@ -526,13 +555,11 @@ module JSONAPI
526
555
  end
527
556
  end
528
557
 
529
- def parse_to_many_relationship(link_value, relationship, &add_result)
530
- if link_value.is_a?(Array) && link_value.length == 0
531
- linkage = []
532
- elsif (link_value.is_a?(Hash) || link_value.is_a?(ActionController::Parameters))
558
+ def parse_to_many_relationship(resource_klass, link_value, relationship, &add_result)
559
+ if (link_value.is_a?(Hash) || link_value.is_a?(ActionController::Parameters))
533
560
  linkage = link_value[:data]
534
561
  else
535
- fail JSONAPI::Exceptions::InvalidLinksObject.new
562
+ fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides)
536
563
  end
537
564
 
538
565
  links_object = parse_to_many_links_object(linkage)
@@ -544,13 +571,12 @@ module JSONAPI
544
571
  polymorphic_results = []
545
572
 
546
573
  links_object.each_pair do |type, keys|
547
- resource = self.resource_klass || Resource
548
574
  type_name = unformat_key(type).to_s
549
575
 
550
- relationship_resource_klass = resource.resource_for(relationship.class_name)
576
+ relationship_resource_klass = resource_klass.resource_klass_for(relationship.class_name)
551
577
  relationship_klass = relationship_resource_klass._model_class
552
578
 
553
- linkage_object_resource_klass = resource.resource_for(type_name)
579
+ linkage_object_resource_klass = resource_klass.resource_klass_for(type_name)
554
580
  linkage_object_klass = linkage_object_resource_klass._model_class
555
581
 
556
582
  unless linkage_object_klass == relationship_klass || linkage_object_klass.in?(relationship_klass.subclasses)
@@ -569,14 +595,14 @@ module JSONAPI
569
595
  fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
570
596
  end
571
597
 
572
- relationship_resource = Resource.resource_for(@resource_klass.module_path + relationship_type)
573
- add_result.call relationship_resource.verify_keys(links_object[relationship_type], @context)
598
+ relationship_resource_klass = Resource.resource_klass_for(resource_klass.module_path + relationship_type)
599
+ add_result.call relationship_resource_klass.verify_keys(links_object[relationship_type], @context)
574
600
  end
575
601
  end
576
602
  end
577
603
 
578
- def unformat_value(attribute, value)
579
- value_formatter = JSONAPI::ValueFormatter.value_formatter_for(@resource_klass._attribute_options(attribute)[:format])
604
+ def unformat_value(resource_klass, attribute, value)
605
+ value_formatter = JSONAPI::ValueFormatter.value_formatter_for(resource_klass._attribute_options(attribute)[:format])
580
606
  value_formatter.unformat(value)
581
607
  end
582
608
 
@@ -586,45 +612,45 @@ module JSONAPI
586
612
 
587
613
  params.each do |key, value|
588
614
  case key.to_s
589
- when 'relationships'
590
- value.keys.each do |links_key|
591
- unless formatted_allowed_fields.include?(links_key.to_sym)
592
- if JSONAPI.configuration.raise_if_parameters_not_allowed
593
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(links_key)
594
- else
595
- params_not_allowed.push(links_key)
596
- value.delete links_key
615
+ when 'relationships'
616
+ value.keys.each do |links_key|
617
+ unless formatted_allowed_fields.include?(links_key.to_sym)
618
+ if JSONAPI.configuration.raise_if_parameters_not_allowed
619
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(links_key, error_object_overrides)
620
+ else
621
+ params_not_allowed.push(links_key)
622
+ value.delete links_key
623
+ end
597
624
  end
598
625
  end
599
- end
600
- when 'attributes'
601
- value.each do |attr_key, attr_value|
602
- unless formatted_allowed_fields.include?(attr_key.to_sym)
626
+ when 'attributes'
627
+ value.each do |attr_key, _attr_value|
628
+ unless formatted_allowed_fields.include?(attr_key.to_sym)
629
+ if JSONAPI.configuration.raise_if_parameters_not_allowed
630
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(attr_key, error_object_overrides)
631
+ else
632
+ params_not_allowed.push(attr_key)
633
+ value.delete attr_key
634
+ end
635
+ end
636
+ end
637
+ when 'type'
638
+ when 'id'
639
+ unless formatted_allowed_fields.include?(:id)
603
640
  if JSONAPI.configuration.raise_if_parameters_not_allowed
604
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(attr_key)
641
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(:id, error_object_overrides)
605
642
  else
606
- params_not_allowed.push(attr_key)
607
- value.delete attr_key
643
+ params_not_allowed.push(:id)
644
+ params.delete :id
608
645
  end
609
646
  end
610
- end
611
- when 'type'
612
- when 'id'
613
- unless formatted_allowed_fields.include?(:id)
647
+ else
614
648
  if JSONAPI.configuration.raise_if_parameters_not_allowed
615
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(:id)
649
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(key, error_object_overrides)
616
650
  else
617
- params_not_allowed.push(:id)
618
- params.delete :id
651
+ params_not_allowed.push(key)
652
+ params.delete key
619
653
  end
620
- end
621
- else
622
- if JSONAPI.configuration.raise_if_parameters_not_allowed
623
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(key)
624
- else
625
- params_not_allowed.push(key)
626
- params.delete key
627
- end
628
654
  end
629
655
  end
630
656
 
@@ -638,23 +664,24 @@ module JSONAPI
638
664
  end
639
665
  end
640
666
 
641
- def parse_add_relationship_operation(verified_params, relationship, parent_key)
667
+ def parse_add_relationship_operation(resource_klass, verified_params, relationship, parent_key)
642
668
  if relationship.is_a?(JSONAPI::Relationship::ToMany)
643
- @operations.push JSONAPI::Operation.new(:create_to_many_relationships,
644
- resource_klass,
645
- context: @context,
646
- resource_id: parent_key,
647
- relationship_type: relationship.name,
648
- data: verified_params[:to_many].values[0]
669
+ return JSONAPI::Operation.new(
670
+ :create_to_many_relationships,
671
+ resource_klass,
672
+ context: @context,
673
+ resource_id: parent_key,
674
+ relationship_type: relationship.name,
675
+ data: verified_params[:to_many].values[0]
649
676
  )
650
677
  end
651
678
  end
652
679
 
653
- def parse_update_relationship_operation(verified_params, relationship, parent_key)
680
+ def parse_update_relationship_operation(resource_klass, verified_params, relationship, parent_key)
654
681
  options = {
655
- context: @context,
656
- resource_id: parent_key,
657
- relationship_type: relationship.name
682
+ context: @context,
683
+ resource_id: parent_key,
684
+ relationship_type: relationship.name
658
685
  }
659
686
 
660
687
  if relationship.is_a?(JSONAPI::Relationship::ToOne)
@@ -675,69 +702,29 @@ module JSONAPI
675
702
  operation_type = :replace_to_many_relationships
676
703
  end
677
704
 
678
- @operations.push JSONAPI::Operation.new(operation_type, resource_klass, options)
679
- end
680
-
681
- def parse_single_replace_operation(data, keys, id_key_presence_check_required: true)
682
- fail JSONAPI::Exceptions::InvalidDataFormat unless data.respond_to?(:each_pair)
683
-
684
- fail JSONAPI::Exceptions::MissingKey.new if data[:id].nil?
685
-
686
- key = data[:id].to_s
687
- if id_key_presence_check_required && !keys.include?(key)
688
- fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(key)
689
- end
690
-
691
- data.delete(:id) unless keys.include?(:id)
692
-
693
- verify_type(data[:type])
694
-
695
- @operations.push JSONAPI::Operation.new(:replace_fields,
696
- @resource_klass,
697
- context: @context,
698
- resource_id: key,
699
- data: parse_params(data, @resource_klass.updatable_fields(@context)),
700
- fields: @fields,
701
- include_directives: @include_directives
702
- )
703
- end
704
-
705
- def parse_replace_operation(data, keys)
706
- parse_single_replace_operation(data, [keys],
707
- id_key_presence_check_required: keys.present? && !@resource_klass.singleton?)
708
- rescue JSONAPI::Exceptions::Error => e
709
- @errors.concat(e.errors)
710
- end
711
-
712
- def parse_remove_operation(params)
713
- @operations.push JSONAPI::Operation.new(:remove_resource,
714
- @resource_klass,
715
- context: @context,
716
- resource_id: @resource_klass.verify_key(params.require(:id), context))
717
- rescue JSONAPI::Exceptions::Error => e
718
- @errors.concat(e.errors)
705
+ JSONAPI::Operation.new(operation_type, resource_klass, options)
719
706
  end
720
707
 
721
- def parse_remove_relationship_operation(params, relationship, parent_key)
708
+ def parse_remove_relationship_operation(resource_klass, params, relationship, parent_key)
722
709
  operation_base_args = [resource_klass].push(
723
- context: @context,
724
- resource_id: parent_key,
725
- relationship_type: relationship.name
710
+ context: @context,
711
+ resource_id: parent_key,
712
+ relationship_type: relationship.name
726
713
  )
727
714
 
728
715
  if relationship.is_a?(JSONAPI::Relationship::ToMany)
729
716
  operation_args = operation_base_args.dup
730
717
  keys = params[:to_many].values[0]
731
718
  operation_args[1] = operation_args[1].merge(associated_keys: keys)
732
- @operations.push JSONAPI::Operation.new(:remove_to_many_relationships, *operation_args)
719
+ JSONAPI::Operation.new(:remove_to_many_relationships, *operation_args)
733
720
  else
734
- @operations.push JSONAPI::Operation.new(:remove_to_one_relationship, *operation_base_args)
721
+ JSONAPI::Operation.new(:remove_to_one_relationship, *operation_base_args)
735
722
  end
736
723
  end
737
724
 
738
- def resolve_singleton_id(params)
739
- if @resource_klass.singleton? && params[:id].nil?
740
- key = @resource_klass.singleton_key(context)
725
+ def resolve_singleton_id(params, resource_klass)
726
+ if resource_klass.singleton? && params[:id].nil?
727
+ key = resource_klass.singleton_key(context)
741
728
  params[:id] = key
742
729
  end
743
730
  end