jsonapi-resources 0.9.0 → 0.10.6

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