jsonapi-resources 0.9.3 → 0.10.5

Sign up to get free protection for your applications and to get access to all the features.
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 +297 -0
  8. data/lib/jsonapi/active_relation_resource.rb +879 -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 +63 -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 -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 +237 -110
  25. data/lib/jsonapi/relationship.rb +144 -15
  26. data/lib/jsonapi/request_parser.rb +412 -357
  27. data/lib/jsonapi/resource.rb +3 -1263
  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 +47 -17
  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,75 +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
- fail JSONAPI::Exceptions::InvalidResource.new(type)
312
+ fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
174
313
  end
175
314
 
176
315
  if type_resource.nil?
177
- fail JSONAPI::Exceptions::InvalidResource.new(type)
316
+ fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
178
317
  else
179
318
  unless values.nil?
180
319
  valid_fields = type_resource.fields.collect { |key| format_key(key) }
181
320
  values.each do |field|
182
321
  if valid_fields.include?(field)
183
- extracted_fields[type].push unformat_key(field)
322
+ validated_fields[type].push unformat_key(field)
184
323
  else
185
- fail JSONAPI::Exceptions::InvalidField.new(type, field)
324
+ fail JSONAPI::Exceptions::InvalidField.new(type, field, error_object_overrides)
186
325
  end
187
326
  end
188
327
  else
189
- fail JSONAPI::Exceptions::InvalidField.new(type, 'nil')
328
+ fail JSONAPI::Exceptions::InvalidField.new(type, 'nil', error_object_overrides)
190
329
  end
191
330
  end
192
331
  end
193
332
 
194
- @fields = extracted_fields.deep_transform_keys { |key| unformat_key(key) }
333
+ validated_fields.deep_transform_keys { |key| unformat_key(key) }
195
334
  end
196
335
 
197
336
  def check_include(resource_klass, include_parts)
@@ -199,214 +338,144 @@ module JSONAPI
199
338
 
200
339
  relationship = resource_klass._relationship(relationship_name)
201
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
+
202
345
  unless include_parts.last.empty?
203
- 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('.'))
204
348
  end
205
349
  else
206
350
  fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), include_parts.first)
207
351
  end
352
+ true
208
353
  end
209
354
 
210
- def parse_include_directives(raw_include)
211
- return unless raw_include
212
-
213
- unless JSONAPI.configuration.allow_include
214
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(:include)
215
- end
355
+ def parse_include_directives(resource_klass, raw_include)
356
+ raw_include ||= ''
216
357
 
217
358
  included_resources = []
218
359
  begin
219
- included_resources += CSV.parse_line(raw_include)
360
+ included_resources += raw_include.is_a?(Array) ? raw_include : CSV.parse_line(raw_include) || []
220
361
  rescue CSV::MalformedCSVError
221
- 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)
222
363
  end
223
364
 
224
- return if included_resources.empty?
225
-
226
365
  begin
227
366
  result = included_resources.compact.map do |included_resource|
228
- check_include(@resource_klass, included_resource.partition('.'))
367
+ check_include(resource_klass, included_resource.partition('.'))
229
368
  unformat_key(included_resource).to_s
230
369
  end
231
370
 
232
- @include_directives = JSONAPI::IncludeDirectives.new(@resource_klass, result)
371
+ return JSONAPI::IncludeDirectives.new(resource_klass, result)
233
372
  rescue JSONAPI::Exceptions::InvalidInclude => e
234
373
  @errors.concat(e.errors)
235
- @include_directives = {}
374
+ return {}
236
375
  end
237
376
  end
238
377
 
239
- def parse_filters(filters)
240
- return unless filters
378
+ def parse_filters(resource_klass, filters)
379
+ parsed_filters = {}
241
380
 
242
- unless JSONAPI.configuration.allow_filter
243
- 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]
244
385
  end
245
386
 
387
+ return parsed_filters unless filters
388
+
246
389
  unless filters.class.method_defined?(:each)
247
390
  @errors.concat(JSONAPI::Exceptions::InvalidFiltersSyntax.new(filters).errors)
248
- return
391
+ return {}
392
+ end
393
+
394
+ unless JSONAPI.configuration.allow_filter
395
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(:filter)
249
396
  end
250
397
 
251
398
  filters.each do |key, value|
252
399
  filter = unformat_key(key)
253
- if @resource_klass._allowed_filter?(filter)
254
- @filters[filter] = value
400
+ if resource_klass._allowed_filter?(filter)
401
+ parsed_filters[filter] = value
255
402
  else
256
- fail JSONAPI::Exceptions::FilterNotAllowed.new(filter)
403
+ fail JSONAPI::Exceptions::FilterNotAllowed.new(key)
257
404
  end
258
405
  end
259
- end
260
406
 
261
- def set_default_filters
262
- @resource_klass._allowed_filters.each do |filter, opts|
263
- next if opts[:default].nil? || !@filters[filter].nil?
264
- @filters[filter] = opts[:default]
265
- end
407
+ parsed_filters
266
408
  end
267
409
 
268
- def parse_sort_criteria(sort_criteria)
410
+ def parse_sort_criteria(resource_klass, sort_criteria)
269
411
  return unless sort_criteria.present?
270
412
 
271
413
  unless JSONAPI.configuration.allow_sort
272
414
  fail JSONAPI::Exceptions::ParameterNotAllowed.new(:sort)
273
415
  end
274
416
 
275
- sorts = []
276
- begin
277
- raw = URI.unescape(sort_criteria)
278
- sorts += CSV.parse_line(raw)
279
- rescue CSV::MalformedCSVError
280
- 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
281
426
  end
282
427
 
283
428
  @sort_criteria = sorts.collect do |sort|
284
429
  if sort.start_with?('-')
285
- sort_criteria = { field: unformat_key(sort[1..-1]).to_s }
286
- sort_criteria[:direction] = :desc
430
+ criteria = { field: unformat_key(sort[1..-1]).to_s }
431
+ criteria[:direction] = :desc
287
432
  else
288
- sort_criteria = { field: unformat_key(sort).to_s }
289
- sort_criteria[:direction] = :asc
433
+ criteria = { field: unformat_key(sort).to_s }
434
+ criteria[:direction] = :asc
290
435
  end
291
436
 
292
- check_sort_criteria(@resource_klass, sort_criteria)
293
- sort_criteria
437
+ check_sort_criteria(resource_klass, criteria)
438
+ criteria
294
439
  end
295
440
  end
296
441
 
297
442
  def check_sort_criteria(resource_klass, sort_criteria)
298
443
  sort_field = sort_criteria[:field]
299
- sortable_fields = resource_klass.sortable_fields(context)
300
444
 
301
- unless sortable_fields.include?sort_field.to_sym
445
+ unless resource_klass.sortable_field?(sort_field.to_sym, context)
302
446
  fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(resource_klass._type), sort_field)
303
447
  end
304
448
  end
305
449
 
306
- def add_find_operation
307
- @operations.push JSONAPI::Operation.new(:find,
308
- @resource_klass,
309
- context: @context,
310
- filters: @filters,
311
- include_directives: @include_directives,
312
- sort_criteria: @sort_criteria,
313
- paginator: @paginator,
314
- fields: @fields
315
- )
316
- end
317
-
318
- def add_show_operation
319
- @operations.push JSONAPI::Operation.new(:show,
320
- @resource_klass,
321
- context: @context,
322
- id: @id,
323
- include_directives: @include_directives,
324
- fields: @fields
325
- )
326
- end
327
-
328
- def add_show_relationship_operation(relationship_type, parent_key)
329
- @operations.push JSONAPI::Operation.new(:show_relationship,
330
- @resource_klass,
331
- context: @context,
332
- relationship_type: relationship_type,
333
- parent_key: @resource_klass.verify_key(parent_key)
334
- )
335
- end
336
-
337
- def add_show_related_resource_operation(relationship_type)
338
- @operations.push JSONAPI::Operation.new(:show_related_resource,
339
- @resource_klass,
340
- context: @context,
341
- relationship_type: relationship_type,
342
- source_klass: @source_klass,
343
- source_id: @source_id,
344
- fields: @fields,
345
- include_directives: @include_directives
346
- )
347
- end
348
-
349
- def add_show_related_resources_operation(relationship_type)
350
- @operations.push JSONAPI::Operation.new(:show_related_resources,
351
- @resource_klass,
352
- context: @context,
353
- relationship_type: relationship_type,
354
- source_klass: @source_klass,
355
- source_id: @source_id,
356
- filters: @filters,
357
- sort_criteria: @sort_criteria,
358
- paginator: @paginator,
359
- fields: @fields,
360
- include_directives: @include_directives
361
- )
362
- end
363
-
364
- def parse_add_operation(params)
365
- fail JSONAPI::Exceptions::InvalidDataFormat unless params.respond_to?(:each_pair)
366
-
367
- verify_type(params[:type])
368
-
369
- data = parse_params(params, @resource_klass.creatable_fields(@context))
370
- @operations.push JSONAPI::Operation.new(:create_resource,
371
- @resource_klass,
372
- context: @context,
373
- data: data,
374
- fields: @fields,
375
- include_directives: @include_directives
376
- )
377
- rescue JSONAPI::Exceptions::Error => e
378
- @errors.concat(e.errors)
379
- end
380
-
381
- def verify_type(type)
450
+ def verify_type(type, resource_klass)
382
451
  if type.nil?
383
452
  fail JSONAPI::Exceptions::ParameterMissing.new(:type)
384
- elsif unformat_key(type).to_sym != @resource_klass._type
385
- 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)
386
455
  end
387
456
  end
388
457
 
389
458
  def parse_to_one_links_object(raw)
390
459
  if raw.nil?
391
460
  return {
392
- type: nil,
393
- id: nil
461
+ type: nil,
462
+ id: nil
394
463
  }
395
464
  end
396
465
 
397
466
  if !(raw.is_a?(Hash) || raw.is_a?(ActionController::Parameters)) ||
398
- raw.keys.length != 2 || !(raw.key?('type') && raw.key?('id'))
399
- 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)
400
469
  end
401
470
 
402
471
  {
403
- type: unformat_key(raw['type']).to_s,
404
- id: raw['id']
472
+ type: unformat_key(raw['type']).to_s,
473
+ id: raw['id']
405
474
  }
406
475
  end
407
476
 
408
477
  def parse_to_many_links_object(raw)
409
- fail JSONAPI::Exceptions::InvalidLinksObject.new if raw.nil?
478
+ fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides) if raw.nil?
410
479
 
411
480
  links_object = {}
412
481
  if raw.is_a?(Array)
@@ -416,12 +485,12 @@ module JSONAPI
416
485
  links_object[link_object[:type]].push(link_object[:id])
417
486
  end
418
487
  else
419
- fail JSONAPI::Exceptions::InvalidLinksObject.new
488
+ fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides)
420
489
  end
421
490
  links_object
422
491
  end
423
492
 
424
- def parse_params(params, allowed_fields)
493
+ def parse_params(resource_klass, params, allowed_fields)
425
494
  verify_permitted_params(params, allowed_fields)
426
495
 
427
496
  checked_attributes = {}
@@ -430,37 +499,37 @@ module JSONAPI
430
499
 
431
500
  params.each do |key, value|
432
501
  case key.to_s
433
- when 'relationships'
434
- value.each do |link_key, link_value|
435
- param = unformat_key(link_key)
436
- relationship = @resource_klass._relationship(param)
437
-
438
- if relationship.is_a?(JSONAPI::Relationship::ToOne)
439
- checked_to_one_relationships[param] = parse_to_one_relationship(link_value, relationship)
440
- elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
441
- parse_to_many_relationship(link_value, relationship) do |result_val|
442
- 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
443
513
  end
444
514
  end
445
- end
446
- when 'id'
447
- checked_attributes['id'] = unformat_value(:id, value)
448
- when 'attributes'
449
- value.each do |key, value|
450
- param = unformat_key(key)
451
- checked_attributes[param] = unformat_value(param, value)
452
- 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
453
522
  end
454
523
  end
455
524
 
456
525
  return {
457
- 'attributes' => checked_attributes,
458
- 'to_one' => checked_to_one_relationships,
459
- 'to_many' => checked_to_many_relationships
526
+ 'attributes' => checked_attributes,
527
+ 'to_one' => checked_to_one_relationships,
528
+ 'to_many' => checked_to_many_relationships
460
529
  }.deep_transform_keys { |key| unformat_key(key) }
461
530
  end
462
531
 
463
- def parse_to_one_relationship(link_value, relationship)
532
+ def parse_to_one_relationship(resource_klass, link_value, relationship)
464
533
  if link_value.nil?
465
534
  linkage = nil
466
535
  else
@@ -469,12 +538,12 @@ module JSONAPI
469
538
 
470
539
  links_object = parse_to_one_links_object(linkage)
471
540
  if !relationship.polymorphic? && links_object[:type] && (links_object[:type].to_s != relationship.type.to_s)
472
- fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
541
+ fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type], error_object_overrides)
473
542
  end
474
543
 
475
544
  unless links_object[:id].nil?
476
- resource = self.resource_klass || Resource
477
- 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)
478
547
  relationship_id = relationship_resource.verify_key(links_object[:id], @context)
479
548
  if relationship.polymorphic?
480
549
  { id: relationship_id, type: unformat_key(links_object[:type].to_s) }
@@ -486,37 +555,54 @@ module JSONAPI
486
555
  end
487
556
  end
488
557
 
489
- def parse_to_many_relationship(link_value, relationship, &add_result)
490
- if link_value.is_a?(Array) && link_value.length == 0
491
- linkage = []
492
- 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))
493
560
  linkage = link_value[:data]
494
561
  else
495
- fail JSONAPI::Exceptions::InvalidLinksObject.new
562
+ fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides)
496
563
  end
497
564
 
498
565
  links_object = parse_to_many_links_object(linkage)
499
566
 
500
- # Since we do not yet support polymorphic to_many relationships we will raise an error if the type does not match the
501
- # relationship's type.
502
- # ToDo: Support Polymorphic relationships
503
-
504
567
  if links_object.length == 0
505
568
  add_result.call([])
506
569
  else
507
- if links_object.length > 1 || !links_object.has_key?(unformat_key(relationship.type).to_s)
508
- fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
509
- 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
589
+
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
510
597
 
511
- links_object.each_pair do |type, keys|
512
- relationship_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(type).to_s)
513
- add_result.call relationship_resource.verify_keys(keys, @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)
514
600
  end
515
601
  end
516
602
  end
517
603
 
518
- def unformat_value(attribute, value)
519
- 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])
520
606
  value_formatter.unformat(value)
521
607
  end
522
608
 
@@ -526,45 +612,45 @@ module JSONAPI
526
612
 
527
613
  params.each do |key, value|
528
614
  case key.to_s
529
- when 'relationships'
530
- value.keys.each do |links_key|
531
- unless formatted_allowed_fields.include?(links_key.to_sym)
532
- if JSONAPI.configuration.raise_if_parameters_not_allowed
533
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(links_key)
534
- else
535
- params_not_allowed.push(links_key)
536
- 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
537
624
  end
538
625
  end
539
- end
540
- when 'attributes'
541
- value.each do |attr_key, attr_value|
542
- 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)
543
640
  if JSONAPI.configuration.raise_if_parameters_not_allowed
544
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(attr_key)
641
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(:id, error_object_overrides)
545
642
  else
546
- params_not_allowed.push(attr_key)
547
- value.delete attr_key
643
+ params_not_allowed.push(:id)
644
+ params.delete :id
548
645
  end
549
646
  end
550
- end
551
- when 'type'
552
- when 'id'
553
- unless formatted_allowed_fields.include?(:id)
647
+ else
554
648
  if JSONAPI.configuration.raise_if_parameters_not_allowed
555
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(:id)
649
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(key, error_object_overrides)
556
650
  else
557
- params_not_allowed.push(:id)
558
- params.delete :id
651
+ params_not_allowed.push(key)
652
+ params.delete key
559
653
  end
560
- end
561
- else
562
- if JSONAPI.configuration.raise_if_parameters_not_allowed
563
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(key)
564
- else
565
- params_not_allowed.push(key)
566
- params.delete key
567
- end
568
654
  end
569
655
  end
570
656
 
@@ -578,23 +664,24 @@ module JSONAPI
578
664
  end
579
665
  end
580
666
 
581
- def parse_add_relationship_operation(verified_params, relationship, parent_key)
667
+ def parse_add_relationship_operation(resource_klass, verified_params, relationship, parent_key)
582
668
  if relationship.is_a?(JSONAPI::Relationship::ToMany)
583
- @operations.push JSONAPI::Operation.new(:create_to_many_relationships,
584
- resource_klass,
585
- context: @context,
586
- resource_id: parent_key,
587
- relationship_type: relationship.name,
588
- 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]
589
676
  )
590
677
  end
591
678
  end
592
679
 
593
- def parse_update_relationship_operation(verified_params, relationship, parent_key)
680
+ def parse_update_relationship_operation(resource_klass, verified_params, relationship, parent_key)
594
681
  options = {
595
- context: @context,
596
- resource_id: parent_key,
597
- relationship_type: relationship.name
682
+ context: @context,
683
+ resource_id: parent_key,
684
+ relationship_type: relationship.name
598
685
  }
599
686
 
600
687
  if relationship.is_a?(JSONAPI::Relationship::ToOne)
@@ -615,62 +702,30 @@ module JSONAPI
615
702
  operation_type = :replace_to_many_relationships
616
703
  end
617
704
 
618
- @operations.push JSONAPI::Operation.new(operation_type, resource_klass, options)
705
+ JSONAPI::Operation.new(operation_type, resource_klass, options)
619
706
  end
620
707
 
621
- def parse_single_replace_operation(data, keys, id_key_presence_check_required: true)
622
- fail JSONAPI::Exceptions::InvalidDataFormat unless data.respond_to?(:each_pair)
623
-
624
- fail JSONAPI::Exceptions::MissingKey.new if data[:id].nil?
625
-
626
- key = data[:id].to_s
627
- if id_key_presence_check_required && !keys.include?(key)
628
- fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(key)
629
- end
630
-
631
- data.delete(:id) unless keys.include?(:id)
632
-
633
- verify_type(data[:type])
634
-
635
- @operations.push JSONAPI::Operation.new(:replace_fields,
636
- @resource_klass,
637
- context: @context,
638
- resource_id: key,
639
- data: parse_params(data, @resource_klass.updatable_fields(@context)),
640
- fields: @fields,
641
- include_directives: @include_directives
642
- )
643
- end
644
-
645
- def parse_replace_operation(data, keys)
646
- parse_single_replace_operation(data, [keys], id_key_presence_check_required: keys.present?)
647
- rescue JSONAPI::Exceptions::Error => e
648
- @errors.concat(e.errors)
649
- end
650
-
651
- def parse_remove_operation(params)
652
- @operations.push JSONAPI::Operation.new(:remove_resource,
653
- @resource_klass,
654
- context: @context,
655
- resource_id: @resource_klass.verify_key(params.require(:id), context))
656
- rescue JSONAPI::Exceptions::Error => e
657
- @errors.concat(e.errors)
658
- end
659
-
660
- def parse_remove_relationship_operation(params, relationship, parent_key)
708
+ def parse_remove_relationship_operation(resource_klass, params, relationship, parent_key)
661
709
  operation_base_args = [resource_klass].push(
662
- context: @context,
663
- resource_id: parent_key,
664
- relationship_type: relationship.name
710
+ context: @context,
711
+ resource_id: parent_key,
712
+ relationship_type: relationship.name
665
713
  )
666
714
 
667
715
  if relationship.is_a?(JSONAPI::Relationship::ToMany)
668
716
  operation_args = operation_base_args.dup
669
717
  keys = params[:to_many].values[0]
670
718
  operation_args[1] = operation_args[1].merge(associated_keys: keys)
671
- @operations.push JSONAPI::Operation.new(:remove_to_many_relationships, *operation_args)
719
+ JSONAPI::Operation.new(:remove_to_many_relationships, *operation_args)
672
720
  else
673
- @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
674
729
  end
675
730
  end
676
731