sanger-jsonapi-resources 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/lib/generators/jsonapi/USAGE +13 -0
- data/lib/generators/jsonapi/controller_generator.rb +14 -0
- data/lib/generators/jsonapi/resource_generator.rb +14 -0
- data/lib/generators/jsonapi/templates/jsonapi_controller.rb +4 -0
- data/lib/generators/jsonapi/templates/jsonapi_resource.rb +4 -0
- data/lib/jsonapi/acts_as_resource_controller.rb +320 -0
- data/lib/jsonapi/cached_resource_fragment.rb +127 -0
- data/lib/jsonapi/callbacks.rb +51 -0
- data/lib/jsonapi/compiled_json.rb +36 -0
- data/lib/jsonapi/configuration.rb +258 -0
- data/lib/jsonapi/error.rb +47 -0
- data/lib/jsonapi/error_codes.rb +60 -0
- data/lib/jsonapi/exceptions.rb +563 -0
- data/lib/jsonapi/formatter.rb +169 -0
- data/lib/jsonapi/include_directives.rb +100 -0
- data/lib/jsonapi/link_builder.rb +152 -0
- data/lib/jsonapi/mime_types.rb +41 -0
- data/lib/jsonapi/naive_cache.rb +30 -0
- data/lib/jsonapi/operation.rb +24 -0
- data/lib/jsonapi/operation_dispatcher.rb +88 -0
- data/lib/jsonapi/operation_result.rb +65 -0
- data/lib/jsonapi/operation_results.rb +35 -0
- data/lib/jsonapi/paginator.rb +209 -0
- data/lib/jsonapi/processor.rb +328 -0
- data/lib/jsonapi/relationship.rb +94 -0
- data/lib/jsonapi/relationship_builder.rb +167 -0
- data/lib/jsonapi/request_parser.rb +678 -0
- data/lib/jsonapi/resource.rb +1255 -0
- data/lib/jsonapi/resource_controller.rb +5 -0
- data/lib/jsonapi/resource_controller_metal.rb +16 -0
- data/lib/jsonapi/resource_serializer.rb +531 -0
- data/lib/jsonapi/resources/version.rb +5 -0
- data/lib/jsonapi/response_document.rb +135 -0
- data/lib/jsonapi/routing_ext.rb +262 -0
- data/lib/jsonapi-resources.rb +27 -0
- metadata +223 -0
@@ -0,0 +1,678 @@
|
|
1
|
+
require 'jsonapi/operation'
|
2
|
+
require 'jsonapi/paginator'
|
3
|
+
|
4
|
+
module JSONAPI
|
5
|
+
class RequestParser
|
6
|
+
attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :operations,
|
7
|
+
:resource_klass, :context, :paginator, :source_klass, :source_id,
|
8
|
+
:include_directives, :params, :warnings, :server_error_callbacks
|
9
|
+
|
10
|
+
def initialize(params = nil, options = {})
|
11
|
+
@params = params
|
12
|
+
@context = options[:context]
|
13
|
+
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
|
14
|
+
@errors = []
|
15
|
+
@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
|
+
@server_error_callbacks = options.fetch(:server_error_callbacks, [])
|
26
|
+
|
27
|
+
setup_action(@params)
|
28
|
+
end
|
29
|
+
|
30
|
+
def setup_action(params)
|
31
|
+
return if params.nil?
|
32
|
+
|
33
|
+
@resource_klass ||= Resource.resource_for(params[:controller]) if params[:controller]
|
34
|
+
|
35
|
+
setup_action_method_name = "setup_#{params[:action]}_action"
|
36
|
+
if respond_to?(setup_action_method_name)
|
37
|
+
raise params[:_parser_exception] if params[:_parser_exception]
|
38
|
+
send(setup_action_method_name, params)
|
39
|
+
end
|
40
|
+
rescue ActionController::ParameterMissing => e
|
41
|
+
@errors.concat(JSONAPI::Exceptions::ParameterMissing.new(e.param).errors)
|
42
|
+
end
|
43
|
+
|
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
|
52
|
+
end
|
53
|
+
|
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])
|
63
|
+
end
|
64
|
+
|
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])
|
74
|
+
end
|
75
|
+
|
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
|
81
|
+
end
|
82
|
+
|
83
|
+
def setup_show_relationship_action(params)
|
84
|
+
add_show_relationship_operation(params[:relationship], params.require(@resource_klass._as_parent_key))
|
85
|
+
end
|
86
|
+
|
87
|
+
def setup_create_action(params)
|
88
|
+
parse_fields(params[:fields])
|
89
|
+
parse_include_directives(params[:include])
|
90
|
+
parse_add_operation(params.require(:data))
|
91
|
+
end
|
92
|
+
|
93
|
+
def setup_create_relationship_action(params)
|
94
|
+
parse_modify_relationship_action(params, :add)
|
95
|
+
end
|
96
|
+
|
97
|
+
def setup_update_relationship_action(params)
|
98
|
+
parse_modify_relationship_action(params, :update)
|
99
|
+
end
|
100
|
+
|
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])
|
105
|
+
end
|
106
|
+
|
107
|
+
def setup_destroy_action(params)
|
108
|
+
parse_remove_operation(params)
|
109
|
+
end
|
110
|
+
|
111
|
+
def setup_destroy_relationship_action(params)
|
112
|
+
parse_modify_relationship_action(params, :remove)
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_modify_relationship_action(params, modification_type)
|
116
|
+
relationship_type = params.require(:relationship)
|
117
|
+
parent_key = params.require(@resource_klass._as_parent_key)
|
118
|
+
relationship = @resource_klass._relationship(relationship_type)
|
119
|
+
|
120
|
+
# Removals of to-one relationships are done implicitly and require no specification of data
|
121
|
+
data_required = !(modification_type == :remove && relationship.is_a?(JSONAPI::Relationship::ToOne))
|
122
|
+
|
123
|
+
if data_required
|
124
|
+
data = params.fetch(:data)
|
125
|
+
object_params = { relationships: { format_key(relationship.name) => { data: data } } }
|
126
|
+
verified_params = parse_params(object_params, @resource_klass.updatable_fields(@context))
|
127
|
+
|
128
|
+
parse_arguments = [verified_params, relationship, parent_key]
|
129
|
+
else
|
130
|
+
parse_arguments = [params, relationship, parent_key]
|
131
|
+
end
|
132
|
+
|
133
|
+
send(:"parse_#{modification_type}_relationship_operation", *parse_arguments)
|
134
|
+
end
|
135
|
+
|
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)
|
139
|
+
end
|
140
|
+
|
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
|
+
|
151
|
+
extracted_fields = {}
|
152
|
+
|
153
|
+
# Extract the fields for each type from the fields parameters
|
154
|
+
if fields.is_a?(ActionController::Parameters)
|
155
|
+
fields.each do |field, value|
|
156
|
+
resource_fields = value.split(',') unless value.nil? || value.empty?
|
157
|
+
extracted_fields[field] = resource_fields
|
158
|
+
end
|
159
|
+
else
|
160
|
+
fail JSONAPI::Exceptions::InvalidFieldFormat.new
|
161
|
+
end
|
162
|
+
|
163
|
+
# Validate the fields
|
164
|
+
extracted_fields.each do |type, values|
|
165
|
+
underscored_type = unformat_key(type)
|
166
|
+
extracted_fields[type] = []
|
167
|
+
begin
|
168
|
+
if type != format_key(type)
|
169
|
+
fail JSONAPI::Exceptions::InvalidResource.new(type)
|
170
|
+
end
|
171
|
+
type_resource = Resource.resource_for(@resource_klass.module_path + underscored_type.to_s)
|
172
|
+
rescue NameError
|
173
|
+
@errors.concat(JSONAPI::Exceptions::InvalidResource.new(type).errors)
|
174
|
+
rescue JSONAPI::Exceptions::InvalidResource => e
|
175
|
+
@errors.concat(e.errors)
|
176
|
+
end
|
177
|
+
|
178
|
+
if type_resource.nil?
|
179
|
+
@errors.concat(JSONAPI::Exceptions::InvalidResource.new(type).errors)
|
180
|
+
else
|
181
|
+
unless values.nil?
|
182
|
+
valid_fields = type_resource.fields.collect { |key| format_key(key) }
|
183
|
+
values.each do |field|
|
184
|
+
if valid_fields.include?(field)
|
185
|
+
extracted_fields[type].push unformat_key(field)
|
186
|
+
else
|
187
|
+
@errors.concat(JSONAPI::Exceptions::InvalidField.new(type, field).errors)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
else
|
191
|
+
@errors.concat(JSONAPI::Exceptions::InvalidField.new(type, 'nil').errors)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
@fields = extracted_fields.deep_transform_keys { |key| unformat_key(key) }
|
197
|
+
end
|
198
|
+
|
199
|
+
def check_include(resource_klass, include_parts)
|
200
|
+
relationship_name = unformat_key(include_parts.first)
|
201
|
+
|
202
|
+
relationship = resource_klass._relationship(relationship_name)
|
203
|
+
if relationship && format_key(relationship_name) == include_parts.first
|
204
|
+
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('.'))
|
206
|
+
end
|
207
|
+
else
|
208
|
+
@errors.concat(JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type),
|
209
|
+
include_parts.first).errors)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
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
|
219
|
+
|
220
|
+
included_resources = []
|
221
|
+
begin
|
222
|
+
included_resources += CSV.parse_line(raw_include)
|
223
|
+
rescue CSV::MalformedCSVError
|
224
|
+
fail JSONAPI::Exceptions::InvalidInclude.new(format_key(@resource_klass._type), raw_include)
|
225
|
+
end
|
226
|
+
|
227
|
+
return if included_resources.empty?
|
228
|
+
|
229
|
+
result = included_resources.compact.map do |included_resource|
|
230
|
+
check_include(@resource_klass, included_resource.partition('.'))
|
231
|
+
unformat_key(included_resource).to_s
|
232
|
+
end
|
233
|
+
|
234
|
+
@include_directives = JSONAPI::IncludeDirectives.new(@resource_klass, result)
|
235
|
+
end
|
236
|
+
|
237
|
+
def parse_filters(filters)
|
238
|
+
return unless filters
|
239
|
+
|
240
|
+
unless JSONAPI.configuration.allow_filter
|
241
|
+
fail JSONAPI::Exceptions::ParametersNotAllowed.new([:filter])
|
242
|
+
end
|
243
|
+
|
244
|
+
unless filters.class.method_defined?(:each)
|
245
|
+
@errors.concat(JSONAPI::Exceptions::InvalidFiltersSyntax.new(filters).errors)
|
246
|
+
return
|
247
|
+
end
|
248
|
+
|
249
|
+
filters.each do |key, value|
|
250
|
+
filter = unformat_key(key)
|
251
|
+
if @resource_klass._allowed_filter?(filter)
|
252
|
+
@filters[filter] = value
|
253
|
+
else
|
254
|
+
@errors.concat(JSONAPI::Exceptions::FilterNotAllowed.new(filter).errors)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
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
|
264
|
+
end
|
265
|
+
|
266
|
+
def parse_sort_criteria(sort_criteria)
|
267
|
+
return unless sort_criteria.present?
|
268
|
+
|
269
|
+
unless JSONAPI.configuration.allow_sort
|
270
|
+
fail JSONAPI::Exceptions::ParametersNotAllowed.new([:sort])
|
271
|
+
end
|
272
|
+
|
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)
|
279
|
+
end
|
280
|
+
|
281
|
+
@sort_criteria = sorts.collect do |sort|
|
282
|
+
if sort.start_with?('-')
|
283
|
+
sort_criteria = { field: unformat_key(sort[1..-1]).to_s }
|
284
|
+
sort_criteria[:direction] = :desc
|
285
|
+
else
|
286
|
+
sort_criteria = { field: unformat_key(sort).to_s }
|
287
|
+
sort_criteria[:direction] = :asc
|
288
|
+
end
|
289
|
+
|
290
|
+
check_sort_criteria(@resource_klass, sort_criteria)
|
291
|
+
sort_criteria
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def check_sort_criteria(resource_klass, sort_criteria)
|
296
|
+
sort_field = sort_criteria[:field]
|
297
|
+
sortable_fields = resource_klass.sortable_fields(context)
|
298
|
+
|
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)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
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)
|
381
|
+
if type.nil?
|
382
|
+
fail JSONAPI::Exceptions::ParameterMissing.new(:type)
|
383
|
+
elsif unformat_key(type).to_sym != @resource_klass._type
|
384
|
+
fail JSONAPI::Exceptions::InvalidResource.new(type)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
def parse_to_one_links_object(raw)
|
389
|
+
if raw.nil?
|
390
|
+
return {
|
391
|
+
type: nil,
|
392
|
+
id: nil
|
393
|
+
}
|
394
|
+
end
|
395
|
+
|
396
|
+
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
|
399
|
+
end
|
400
|
+
|
401
|
+
{
|
402
|
+
type: unformat_key(raw['type']).to_s,
|
403
|
+
id: raw['id']
|
404
|
+
}
|
405
|
+
end
|
406
|
+
|
407
|
+
def parse_to_many_links_object(raw)
|
408
|
+
fail JSONAPI::Exceptions::InvalidLinksObject.new if raw.nil?
|
409
|
+
|
410
|
+
links_object = {}
|
411
|
+
if raw.is_a?(Array)
|
412
|
+
raw.each do |link|
|
413
|
+
link_object = parse_to_one_links_object(link)
|
414
|
+
links_object[link_object[:type]] ||= []
|
415
|
+
links_object[link_object[:type]].push(link_object[:id])
|
416
|
+
end
|
417
|
+
else
|
418
|
+
fail JSONAPI::Exceptions::InvalidLinksObject.new
|
419
|
+
end
|
420
|
+
links_object
|
421
|
+
end
|
422
|
+
|
423
|
+
def parse_params(params, allowed_fields)
|
424
|
+
verify_permitted_params(params, allowed_fields)
|
425
|
+
|
426
|
+
checked_attributes = {}
|
427
|
+
checked_to_one_relationships = {}
|
428
|
+
checked_to_many_relationships = {}
|
429
|
+
|
430
|
+
params.each do |key, value|
|
431
|
+
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
|
442
|
+
end
|
443
|
+
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
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
return {
|
456
|
+
'attributes' => checked_attributes,
|
457
|
+
'to_one' => checked_to_one_relationships,
|
458
|
+
'to_many' => checked_to_many_relationships
|
459
|
+
}.deep_transform_keys { |key| unformat_key(key) }
|
460
|
+
end
|
461
|
+
|
462
|
+
def parse_to_one_relationship(link_value, relationship)
|
463
|
+
if link_value.nil?
|
464
|
+
linkage = nil
|
465
|
+
else
|
466
|
+
linkage = link_value[:data]
|
467
|
+
end
|
468
|
+
|
469
|
+
links_object = parse_to_one_links_object(linkage)
|
470
|
+
if !relationship.polymorphic? && links_object[:type] && (links_object[:type].to_s != relationship.type.to_s)
|
471
|
+
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
472
|
+
end
|
473
|
+
|
474
|
+
unless links_object[:id].nil?
|
475
|
+
resource = self.resource_klass || Resource
|
476
|
+
relationship_resource = resource.resource_for(unformat_key(links_object[:type]).to_s)
|
477
|
+
relationship_id = relationship_resource.verify_key(links_object[:id], @context)
|
478
|
+
if relationship.polymorphic?
|
479
|
+
{ id: relationship_id, type: unformat_key(links_object[:type].to_s) }
|
480
|
+
else
|
481
|
+
relationship_id
|
482
|
+
end
|
483
|
+
else
|
484
|
+
nil
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
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))
|
492
|
+
linkage = link_value[:data]
|
493
|
+
else
|
494
|
+
fail JSONAPI::Exceptions::InvalidLinksObject.new
|
495
|
+
end
|
496
|
+
|
497
|
+
links_object = parse_to_many_links_object(linkage)
|
498
|
+
|
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
|
+
if links_object.length == 0
|
504
|
+
add_result.call([])
|
505
|
+
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
|
509
|
+
|
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)
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
def unformat_value(attribute, value)
|
518
|
+
value_formatter = JSONAPI::ValueFormatter.value_formatter_for(@resource_klass._attribute_options(attribute)[:format])
|
519
|
+
value_formatter.unformat(value)
|
520
|
+
end
|
521
|
+
|
522
|
+
def verify_permitted_params(params, allowed_fields)
|
523
|
+
formatted_allowed_fields = allowed_fields.collect { |field| format_key(field).to_sym }
|
524
|
+
params_not_allowed = []
|
525
|
+
|
526
|
+
params.each do |key, value|
|
527
|
+
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
|
534
|
+
end
|
535
|
+
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
|
543
|
+
end
|
544
|
+
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
|
552
|
+
end
|
553
|
+
end
|
554
|
+
else
|
555
|
+
params_not_allowed.push(key)
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
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)
|
569
|
+
end
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
def parse_add_relationship_operation(verified_params, relationship, parent_key)
|
574
|
+
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]
|
581
|
+
)
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
def parse_update_relationship_operation(verified_params, relationship, parent_key)
|
586
|
+
options = {
|
587
|
+
context: @context,
|
588
|
+
resource_id: parent_key,
|
589
|
+
relationship_type: relationship.name
|
590
|
+
}
|
591
|
+
|
592
|
+
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
593
|
+
if relationship.polymorphic?
|
594
|
+
options[:key_value] = verified_params[:to_one].values[0][:id]
|
595
|
+
options[:key_type] = verified_params[:to_one].values[0][:type]
|
596
|
+
|
597
|
+
operation_type = :replace_polymorphic_to_one_relationship
|
598
|
+
else
|
599
|
+
options[:key_value] = verified_params[:to_one].values[0]
|
600
|
+
operation_type = :replace_to_one_relationship
|
601
|
+
end
|
602
|
+
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
|
603
|
+
unless relationship.acts_as_set
|
604
|
+
fail JSONAPI::Exceptions::ToManySetReplacementForbidden.new
|
605
|
+
end
|
606
|
+
options[:data] = verified_params[:to_many].values[0]
|
607
|
+
operation_type = :replace_to_many_relationships
|
608
|
+
end
|
609
|
+
|
610
|
+
@operations.push JSONAPI::Operation.new(operation_type, resource_klass, options)
|
611
|
+
end
|
612
|
+
|
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)
|
653
|
+
operation_base_args = [resource_klass].push(
|
654
|
+
context: @context,
|
655
|
+
resource_id: parent_key,
|
656
|
+
relationship_type: relationship.name
|
657
|
+
)
|
658
|
+
|
659
|
+
if relationship.is_a?(JSONAPI::Relationship::ToMany)
|
660
|
+
operation_args = operation_base_args.dup
|
661
|
+
keys = params[:to_many].values[0]
|
662
|
+
operation_args[1] = operation_args[1].merge(associated_keys: keys)
|
663
|
+
@operations.push JSONAPI::Operation.new(:remove_to_many_relationships, *operation_args)
|
664
|
+
else
|
665
|
+
@operations.push JSONAPI::Operation.new(:remove_to_one_relationship, *operation_base_args)
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
def format_key(key)
|
670
|
+
@key_formatter.format(key)
|
671
|
+
end
|
672
|
+
|
673
|
+
def unformat_key(key)
|
674
|
+
unformatted_key = @key_formatter.unformat(key)
|
675
|
+
unformatted_key.nil? ? nil : unformatted_key.to_sym
|
676
|
+
end
|
677
|
+
end
|
678
|
+
end
|