sanger-jsonapi-resources 0.1.1 → 0.2.0

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