jsonapi-resources 0.9.0 → 0.10.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/LICENSE.txt +1 -1
- data/README.md +34 -11
- data/lib/bug_report_templates/rails_5_latest.rb +125 -0
- data/lib/bug_report_templates/rails_5_master.rb +140 -0
- data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +27 -0
- data/lib/jsonapi/active_relation/join_manager.rb +303 -0
- data/lib/jsonapi/active_relation_resource.rb +884 -0
- data/lib/jsonapi/acts_as_resource_controller.rb +122 -105
- data/lib/jsonapi/basic_resource.rb +1162 -0
- data/lib/jsonapi/cached_response_fragment.rb +127 -0
- data/lib/jsonapi/compiled_json.rb +11 -1
- data/lib/jsonapi/configuration.rb +71 -8
- data/lib/jsonapi/error.rb +27 -0
- data/lib/jsonapi/error_codes.rb +2 -0
- data/lib/jsonapi/exceptions.rb +80 -50
- data/lib/jsonapi/formatter.rb +3 -3
- data/lib/jsonapi/include_directives.rb +18 -65
- data/lib/jsonapi/link_builder.rb +74 -80
- data/lib/jsonapi/operation.rb +16 -5
- data/lib/jsonapi/operation_result.rb +74 -16
- data/lib/jsonapi/path.rb +43 -0
- data/lib/jsonapi/path_segment.rb +76 -0
- data/lib/jsonapi/processor.rb +239 -111
- data/lib/jsonapi/relationship.rb +153 -15
- data/lib/jsonapi/request_parser.rb +430 -367
- data/lib/jsonapi/resource.rb +3 -1253
- data/lib/jsonapi/resource_controller_metal.rb +5 -2
- data/lib/jsonapi/resource_fragment.rb +47 -0
- data/lib/jsonapi/resource_id_tree.rb +112 -0
- data/lib/jsonapi/resource_identity.rb +42 -0
- data/lib/jsonapi/resource_serializer.rb +143 -285
- data/lib/jsonapi/resource_set.rb +176 -0
- data/lib/jsonapi/resources/railtie.rb +9 -0
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +105 -83
- data/lib/jsonapi/routing_ext.rb +48 -26
- data/lib/jsonapi-resources.rb +20 -4
- data/lib/tasks/check_upgrade.rake +52 -0
- metadata +50 -20
- data/lib/jsonapi/cached_resource_fragment.rb +0 -127
- data/lib/jsonapi/operation_dispatcher.rb +0 -88
- data/lib/jsonapi/operation_results.rb +0 -35
- data/lib/jsonapi/relationship_builder.rb +0 -167
@@ -0,0 +1,127 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
class CachedResponseFragment
|
3
|
+
|
4
|
+
Lookup = Struct.new(:resource_klass, :serializer_config_key, :context, :context_key, :cache_ids) do
|
5
|
+
|
6
|
+
def type
|
7
|
+
resource_klass._type
|
8
|
+
end
|
9
|
+
|
10
|
+
def keys
|
11
|
+
cache_ids.map do |(id, cache_key)|
|
12
|
+
[type, id, cache_key, serializer_config_key, context_key]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Write = Struct.new(:resource_klass, :resource, :serializer, :serializer_config_key, :context, :context_key, :relationship_data) do
|
18
|
+
def to_key_value
|
19
|
+
|
20
|
+
(id, cache_key) = resource.cache_id
|
21
|
+
|
22
|
+
json = serializer.object_hash(resource, relationship_data)
|
23
|
+
|
24
|
+
cr = CachedResponseFragment.new(
|
25
|
+
resource_klass,
|
26
|
+
id,
|
27
|
+
json['type'],
|
28
|
+
context,
|
29
|
+
resource.fetchable_fields,
|
30
|
+
json['relationships'],
|
31
|
+
json['links'],
|
32
|
+
json['attributes'],
|
33
|
+
json['meta']
|
34
|
+
)
|
35
|
+
|
36
|
+
key = [resource_klass._type, id, cache_key, serializer_config_key, context_key]
|
37
|
+
|
38
|
+
[key, cr]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :resource_klass, :id, :type, :context, :fetchable_fields, :relationships,
|
43
|
+
:links_json, :attributes_json, :meta_json
|
44
|
+
|
45
|
+
def initialize(resource_klass, id, type, context, fetchable_fields, relationships,
|
46
|
+
links_json, attributes_json, meta_json)
|
47
|
+
@resource_klass = resource_klass
|
48
|
+
@id = id
|
49
|
+
@type = type
|
50
|
+
@context = context
|
51
|
+
@fetchable_fields = Set.new(fetchable_fields)
|
52
|
+
|
53
|
+
# Relationships left uncompiled because we'll often want to insert included ids on retrieval
|
54
|
+
# Remove the data since that should not be cached
|
55
|
+
@relationships = relationships&.transform_values {|v| v.delete_if {|k, _v| k == 'data'} }
|
56
|
+
@links_json = CompiledJson.of(links_json)
|
57
|
+
@attributes_json = CompiledJson.of(attributes_json)
|
58
|
+
@meta_json = CompiledJson.of(meta_json)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_cache_value
|
62
|
+
{
|
63
|
+
id: id,
|
64
|
+
type: type,
|
65
|
+
fetchable: fetchable_fields,
|
66
|
+
rels: relationships,
|
67
|
+
links: links_json.try(:to_s),
|
68
|
+
attrs: attributes_json.try(:to_s),
|
69
|
+
meta: meta_json.try(:to_s)
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param [Lookup[]] lookups
|
74
|
+
# @return [Hash<Class<Resource>, Hash<ID, CachedResourceFragment>>]
|
75
|
+
def self.lookup(lookups, context)
|
76
|
+
type_to_klass = lookups.map {|l| [l.type, l.resource_klass]}.to_h
|
77
|
+
|
78
|
+
keys = lookups.map(&:keys).flatten(1)
|
79
|
+
|
80
|
+
hits = JSONAPI.configuration.resource_cache.read_multi(*keys).reject {|_, v| v.nil?}
|
81
|
+
|
82
|
+
return keys.inject({}) do |hash, key|
|
83
|
+
(type, id, _, _) = key
|
84
|
+
resource_klass = type_to_klass[type]
|
85
|
+
hash[resource_klass] ||= {}
|
86
|
+
|
87
|
+
if hits.has_key?(key)
|
88
|
+
hash[resource_klass][id] = self.from_cache_value(resource_klass, context, hits[key])
|
89
|
+
else
|
90
|
+
hash[resource_klass][id] = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
hash
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# @param [Write[]] lookups
|
98
|
+
def self.write(writes)
|
99
|
+
key_values = writes.map(&:to_key_value)
|
100
|
+
|
101
|
+
to_write = key_values.map {|(k, v)| [k, v.to_cache_value]}.to_h
|
102
|
+
|
103
|
+
if JSONAPI.configuration.resource_cache.respond_to? :write_multi
|
104
|
+
JSONAPI.configuration.resource_cache.write_multi(to_write)
|
105
|
+
else
|
106
|
+
to_write.each do |key, value|
|
107
|
+
JSONAPI.configuration.resource_cache.write(key, value)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.from_cache_value(resource_klass, context, h)
|
114
|
+
new(
|
115
|
+
resource_klass,
|
116
|
+
h.fetch(:id),
|
117
|
+
h.fetch(:type),
|
118
|
+
context,
|
119
|
+
h.fetch(:fetchable),
|
120
|
+
h.fetch(:rels, nil),
|
121
|
+
h.fetch(:links, nil),
|
122
|
+
h.fetch(:attrs, nil),
|
123
|
+
h.fetch(:meta, nil)
|
124
|
+
)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -5,6 +5,7 @@ module JSONAPI
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def self.of(obj)
|
8
|
+
# :nocov:
|
8
9
|
case obj
|
9
10
|
when NilClass then nil
|
10
11
|
when CompiledJson then obj
|
@@ -12,6 +13,7 @@ module JSONAPI
|
|
12
13
|
when Hash then CompiledJson.compile(obj)
|
13
14
|
else raise "Can't figure out how to turn #{obj.inspect} into CompiledJson"
|
14
15
|
end
|
16
|
+
# :nocov:
|
15
17
|
end
|
16
18
|
|
17
19
|
def initialize(json, h = nil)
|
@@ -19,7 +21,7 @@ module JSONAPI
|
|
19
21
|
@h = h
|
20
22
|
end
|
21
23
|
|
22
|
-
def to_json(*
|
24
|
+
def to_json(*_args)
|
23
25
|
@json
|
24
26
|
end
|
25
27
|
|
@@ -27,9 +29,17 @@ module JSONAPI
|
|
27
29
|
@json
|
28
30
|
end
|
29
31
|
|
32
|
+
# :nocov:
|
30
33
|
def to_h
|
31
34
|
@h ||= JSON.parse(@json)
|
32
35
|
end
|
36
|
+
# :nocov:
|
37
|
+
|
38
|
+
def [](key)
|
39
|
+
# :nocov:
|
40
|
+
to_h[key]
|
41
|
+
# :nocov:
|
42
|
+
end
|
33
43
|
|
34
44
|
undef_method :as_json
|
35
45
|
end
|
@@ -8,13 +8,17 @@ module JSONAPI
|
|
8
8
|
:resource_key_type,
|
9
9
|
:route_format,
|
10
10
|
:raise_if_parameters_not_allowed,
|
11
|
-
:
|
11
|
+
:warn_on_route_setup_issues,
|
12
|
+
:warn_on_missing_routes,
|
13
|
+
:warn_on_performance_issues,
|
14
|
+
:default_allow_include_to_one,
|
15
|
+
:default_allow_include_to_many,
|
12
16
|
:allow_sort,
|
13
17
|
:allow_filter,
|
14
18
|
:default_paginator,
|
15
19
|
:default_page_size,
|
16
20
|
:maximum_page_size,
|
17
|
-
:
|
21
|
+
:default_processor_klass_name,
|
18
22
|
:use_text_errors,
|
19
23
|
:top_level_links_include_pagination,
|
20
24
|
:top_level_meta_include_record_count,
|
@@ -23,6 +27,7 @@ module JSONAPI
|
|
23
27
|
:top_level_meta_page_count_key,
|
24
28
|
:allow_transactions,
|
25
29
|
:include_backtraces_in_errors,
|
30
|
+
:include_application_backtraces_in_errors,
|
26
31
|
:exception_class_whitelist,
|
27
32
|
:whitelist_all_exceptions,
|
28
33
|
:always_include_to_one_linkage_data,
|
@@ -30,9 +35,12 @@ module JSONAPI
|
|
30
35
|
:cache_formatters,
|
31
36
|
:use_relationship_reflection,
|
32
37
|
:resource_cache,
|
38
|
+
:default_caching,
|
33
39
|
:default_resource_cache_field,
|
34
40
|
:resource_cache_digest_function,
|
35
|
-
:resource_cache_usage_report_function
|
41
|
+
:resource_cache_usage_report_function,
|
42
|
+
:default_exclude_links,
|
43
|
+
:use_related_resource_records_for_joins
|
36
44
|
|
37
45
|
def initialize
|
38
46
|
#:underscored_key, :camelized_key, :dasherized_key, or custom
|
@@ -45,12 +53,17 @@ module JSONAPI
|
|
45
53
|
self.resource_key_type = :integer
|
46
54
|
|
47
55
|
# optional request features
|
48
|
-
self.
|
56
|
+
self.default_allow_include_to_one = true
|
57
|
+
self.default_allow_include_to_many = true
|
49
58
|
self.allow_sort = true
|
50
59
|
self.allow_filter = true
|
51
60
|
|
52
61
|
self.raise_if_parameters_not_allowed = true
|
53
62
|
|
63
|
+
self.warn_on_route_setup_issues = true
|
64
|
+
self.warn_on_missing_routes = true
|
65
|
+
self.warn_on_performance_issues = true
|
66
|
+
|
54
67
|
# :none, :offset, :paged, or a custom paginator name
|
55
68
|
self.default_paginator = :none
|
56
69
|
|
@@ -71,8 +84,12 @@ module JSONAPI
|
|
71
84
|
self.use_text_errors = false
|
72
85
|
|
73
86
|
# Whether or not to include exception backtraces in JSONAPI error
|
74
|
-
# responses. Defaults to `false` in
|
75
|
-
self.include_backtraces_in_errors =
|
87
|
+
# responses. Defaults to `false` in anything other than development or test.
|
88
|
+
self.include_backtraces_in_errors = (Rails.env.development? || Rails.env.test?)
|
89
|
+
|
90
|
+
# Whether or not to include exception application backtraces in JSONAPI error
|
91
|
+
# responses. Defaults to `false` in anything other than development or test.
|
92
|
+
self.include_application_backtraces_in_errors = (Rails.env.development? || Rails.env.test?)
|
76
93
|
|
77
94
|
# List of classes that should not be rescued by the operations processor.
|
78
95
|
# For example, if you use Pundit for authorization, you might
|
@@ -94,7 +111,7 @@ module JSONAPI
|
|
94
111
|
|
95
112
|
# The default Operation Processor to use if one is not defined specifically
|
96
113
|
# for a Resource.
|
97
|
-
self.
|
114
|
+
self.default_processor_klass_name = 'JSONAPI::Processor'
|
98
115
|
|
99
116
|
# Allows transactions for creating and updating records
|
100
117
|
# Set this to false if your backend does not support transactions (e.g. Mongodb)
|
@@ -117,6 +134,11 @@ module JSONAPI
|
|
117
134
|
# Rails cache store.
|
118
135
|
self.resource_cache = nil
|
119
136
|
|
137
|
+
# Cache resources by default
|
138
|
+
# Cache resources by default. Individual resources can be excluded from caching by calling:
|
139
|
+
# `caching false`
|
140
|
+
self.default_caching = false
|
141
|
+
|
120
142
|
# Default resource cache field
|
121
143
|
# On Resources with caching enabled, this field will be used to check for out-of-date
|
122
144
|
# cache entries, unless overridden on a specific Resource. Defaults to "updated_at".
|
@@ -131,6 +153,17 @@ module JSONAPI
|
|
131
153
|
# Optionally provide a callable which JSONAPI will call with information about cache
|
132
154
|
# performance. Should accept three arguments: resource name, hits count, misses count.
|
133
155
|
self.resource_cache_usage_report_function = nil
|
156
|
+
|
157
|
+
# Global configuration for links exclusion
|
158
|
+
# Controls whether to generate links like `self`, `related` with all the resources
|
159
|
+
# and relationships. Accepts either `:default`, `:none`, or array containing the
|
160
|
+
# specific default links to exclude, which may be `:self` and `:related`.
|
161
|
+
self.default_exclude_links = :none
|
162
|
+
|
163
|
+
# Use a related resource's `records` when performing joins. This setting allows included resources to account for
|
164
|
+
# permission scopes. It can be overridden explicitly per relationship. Furthermore, specifying a `relation_name`
|
165
|
+
# on a relationship will cause this setting to be ignored.
|
166
|
+
self.use_related_resource_records_for_joins = true
|
134
167
|
end
|
135
168
|
|
136
169
|
def cache_formatters=(bool)
|
@@ -198,10 +231,26 @@ module JSONAPI
|
|
198
231
|
end
|
199
232
|
|
200
233
|
def default_processor_klass=(default_processor_klass)
|
234
|
+
ActiveSupport::Deprecation.warn('`default_processor_klass` has been replaced by `default_processor_klass_name`.')
|
201
235
|
@default_processor_klass = default_processor_klass
|
202
236
|
end
|
203
237
|
|
204
|
-
|
238
|
+
def default_processor_klass
|
239
|
+
@default_processor_klass ||= default_processor_klass_name.safe_constantize
|
240
|
+
end
|
241
|
+
|
242
|
+
def default_processor_klass_name=(default_processor_klass_name)
|
243
|
+
@default_processor_klass = nil
|
244
|
+
@default_processor_klass_name = default_processor_klass_name
|
245
|
+
end
|
246
|
+
|
247
|
+
def allow_include=(allow_include)
|
248
|
+
ActiveSupport::Deprecation.warn('`allow_include` has been replaced by `default_allow_include_to_one` and `default_allow_include_to_many` options.')
|
249
|
+
@default_allow_include_to_one = allow_include
|
250
|
+
@default_allow_include_to_many = allow_include
|
251
|
+
end
|
252
|
+
|
253
|
+
attr_writer :allow_sort, :allow_filter, :default_allow_include_to_one, :default_allow_include_to_many
|
205
254
|
|
206
255
|
attr_writer :default_paginator
|
207
256
|
|
@@ -225,6 +274,8 @@ module JSONAPI
|
|
225
274
|
|
226
275
|
attr_writer :include_backtraces_in_errors
|
227
276
|
|
277
|
+
attr_writer :include_application_backtraces_in_errors
|
278
|
+
|
228
279
|
attr_writer :exception_class_whitelist
|
229
280
|
|
230
281
|
attr_writer :whitelist_all_exceptions
|
@@ -235,15 +286,27 @@ module JSONAPI
|
|
235
286
|
|
236
287
|
attr_writer :raise_if_parameters_not_allowed
|
237
288
|
|
289
|
+
attr_writer :warn_on_route_setup_issues
|
290
|
+
|
291
|
+
attr_writer :warn_on_missing_routes
|
292
|
+
|
293
|
+
attr_writer :warn_on_performance_issues
|
294
|
+
|
238
295
|
attr_writer :use_relationship_reflection
|
239
296
|
|
240
297
|
attr_writer :resource_cache
|
241
298
|
|
299
|
+
attr_writer :default_caching
|
300
|
+
|
242
301
|
attr_writer :default_resource_cache_field
|
243
302
|
|
244
303
|
attr_writer :resource_cache_digest_function
|
245
304
|
|
246
305
|
attr_writer :resource_cache_usage_report_function
|
306
|
+
|
307
|
+
attr_writer :default_exclude_links
|
308
|
+
|
309
|
+
attr_writer :use_related_resource_records_for_joins
|
247
310
|
end
|
248
311
|
|
249
312
|
class << self
|
data/lib/jsonapi/error.rb
CHANGED
@@ -24,6 +24,33 @@ module JSONAPI
|
|
24
24
|
instance_variables.each {|var| hash[var.to_s.delete('@')] = instance_variable_get(var) unless instance_variable_get(var).nil? }
|
25
25
|
hash
|
26
26
|
end
|
27
|
+
|
28
|
+
def update_with_overrides(error_object_overrides)
|
29
|
+
@title = error_object_overrides[:title] || @title
|
30
|
+
@detail = error_object_overrides[:detail] || @detail
|
31
|
+
@id = error_object_overrides[:id] || @id
|
32
|
+
@href = error_object_overrides[:href] || href
|
33
|
+
|
34
|
+
if error_object_overrides[:code]
|
35
|
+
# :nocov:
|
36
|
+
@code = if JSONAPI.configuration.use_text_errors
|
37
|
+
TEXT_ERRORS[error_object_overrides[:code]]
|
38
|
+
else
|
39
|
+
error_object_overrides[:code]
|
40
|
+
end
|
41
|
+
# :nocov:
|
42
|
+
end
|
43
|
+
|
44
|
+
@source = error_object_overrides[:source] || @source
|
45
|
+
@links = error_object_overrides[:links] || @links
|
46
|
+
|
47
|
+
if error_object_overrides[:status]
|
48
|
+
# :nocov:
|
49
|
+
@status = Rack::Utils::SYMBOL_TO_STATUS_CODE[error_object_overrides[:status]].to_s
|
50
|
+
# :nocov:
|
51
|
+
end
|
52
|
+
@meta = error_object_overrides[:meta] || @meta
|
53
|
+
end
|
27
54
|
end
|
28
55
|
|
29
56
|
class Warning
|
data/lib/jsonapi/error_codes.rb
CHANGED
@@ -20,6 +20,7 @@ module JSONAPI
|
|
20
20
|
INVALID_FILTERS_SYNTAX = '120'
|
21
21
|
SAVE_FAILED = '121'
|
22
22
|
INVALID_DATA_FORMAT = '122'
|
23
|
+
INVALID_RELATIONSHIP = '123'
|
23
24
|
BAD_REQUEST = '400'
|
24
25
|
FORBIDDEN = '403'
|
25
26
|
RECORD_NOT_FOUND = '404'
|
@@ -50,6 +51,7 @@ module JSONAPI
|
|
50
51
|
INVALID_FILTERS_SYNTAX => 'INVALID_FILTERS_SYNTAX',
|
51
52
|
SAVE_FAILED => 'SAVE_FAILED',
|
52
53
|
INVALID_DATA_FORMAT => 'INVALID_DATA_FORMAT',
|
54
|
+
INVALID_RELATIONSHIP => 'INVALID_RELATIONSHIP',
|
53
55
|
FORBIDDEN => 'FORBIDDEN',
|
54
56
|
RECORD_NOT_FOUND => 'RECORD_NOT_FOUND',
|
55
57
|
NOT_ACCEPTABLE => 'NOT_ACCEPTABLE',
|
data/lib/jsonapi/exceptions.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
module Exceptions
|
3
3
|
class Error < RuntimeError
|
4
|
-
|
4
|
+
attr_reader :error_object_overrides
|
5
5
|
|
6
6
|
def initialize(error_object_overrides = {})
|
7
7
|
@error_object_overrides = error_object_overrides
|
@@ -18,6 +18,22 @@ module JSONAPI
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
class Errors < Error
|
22
|
+
def initialize(errors, error_object_overrides = {})
|
23
|
+
@errors = errors
|
24
|
+
|
25
|
+
@errors.each do |error|
|
26
|
+
error.update_with_overrides(error_object_overrides)
|
27
|
+
end
|
28
|
+
|
29
|
+
super(error_object_overrides)
|
30
|
+
end
|
31
|
+
|
32
|
+
def errors
|
33
|
+
@errors
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
21
37
|
class InternalServerError < Error
|
22
38
|
attr_accessor :exception
|
23
39
|
|
@@ -33,6 +49,12 @@ module JSONAPI
|
|
33
49
|
meta[:backtrace] = exception.backtrace
|
34
50
|
end
|
35
51
|
|
52
|
+
if JSONAPI.configuration.include_application_backtraces_in_errors
|
53
|
+
meta ||= Hash.new
|
54
|
+
meta[:exception] ||= exception.message
|
55
|
+
meta[:application_backtrace] = exception.backtrace.select{|line| line =~ /#{Rails.root}/}
|
56
|
+
end
|
57
|
+
|
36
58
|
[create_error_object(code: JSONAPI::INTERNAL_SERVER_ERROR,
|
37
59
|
status: :internal_server_error,
|
38
60
|
title: I18n.t('jsonapi-resources.exceptions.internal_server_error.title',
|
@@ -119,49 +141,30 @@ module JSONAPI
|
|
119
141
|
end
|
120
142
|
end
|
121
143
|
|
122
|
-
|
123
|
-
class HasManyRelationExists < Error
|
124
|
-
attr_accessor :id
|
125
|
-
|
126
|
-
def initialize(id, error_object_overrides = {})
|
127
|
-
@id = id
|
128
|
-
super(error_object_overrides)
|
129
|
-
end
|
130
|
-
|
131
|
-
def errors
|
132
|
-
[create_error_object(code: JSONAPI::RELATION_EXISTS,
|
133
|
-
status: :bad_request,
|
134
|
-
title: I18n.translate('jsonapi-resources.exceptions.has_many_relation.title',
|
135
|
-
default: 'Relation exists'),
|
136
|
-
detail: I18n.translate('jsonapi-resources.exceptions.has_many_relation.detail',
|
137
|
-
default: "The relation to #{id} already exists.",
|
138
|
-
id: id))]
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
144
|
class BadRequest < Error
|
143
|
-
def initialize(exception)
|
145
|
+
def initialize(exception, error_object_overrides = {})
|
144
146
|
@exception = exception
|
147
|
+
super(error_object_overrides)
|
145
148
|
end
|
146
149
|
|
147
150
|
def errors
|
148
|
-
[
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
151
|
+
[create_error_object(code: JSONAPI::BAD_REQUEST,
|
152
|
+
status: :bad_request,
|
153
|
+
title: I18n.translate('jsonapi-resources.exceptions.bad_request.title',
|
154
|
+
default: 'Bad Request'),
|
155
|
+
detail: I18n.translate('jsonapi-resources.exceptions.bad_request.detail',
|
156
|
+
default: @exception))]
|
154
157
|
end
|
155
158
|
end
|
156
159
|
|
157
160
|
class InvalidRequestFormat < Error
|
158
161
|
def errors
|
159
|
-
[
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
162
|
+
[create_error_object(code: JSONAPI::BAD_REQUEST,
|
163
|
+
status: :bad_request,
|
164
|
+
title: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.title',
|
165
|
+
default: 'Bad Request'),
|
166
|
+
detail: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.detail',
|
167
|
+
default: 'Request must be a hash'))]
|
165
168
|
end
|
166
169
|
end
|
167
170
|
|
@@ -324,6 +327,26 @@ module JSONAPI
|
|
324
327
|
end
|
325
328
|
end
|
326
329
|
|
330
|
+
class InvalidRelationship < Error
|
331
|
+
attr_accessor :relationship_name, :type
|
332
|
+
|
333
|
+
def initialize(type, relationship_name, error_object_overrides = {})
|
334
|
+
@relationship_name = relationship_name
|
335
|
+
@type = type
|
336
|
+
super(error_object_overrides)
|
337
|
+
end
|
338
|
+
|
339
|
+
def errors
|
340
|
+
[create_error_object(code: JSONAPI::INVALID_RELATIONSHIP,
|
341
|
+
status: :bad_request,
|
342
|
+
title: I18n.translate('jsonapi-resources.exceptions.invalid_relationship.title',
|
343
|
+
default: 'Invalid relationship'),
|
344
|
+
detail: I18n.translate('jsonapi-resources.exceptions.invalid_relationship.detail',
|
345
|
+
default: "#{relationship_name} is not a valid field for #{type}.",
|
346
|
+
relationship_name: relationship_name, type: type))]
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
327
350
|
class InvalidInclude < Error
|
328
351
|
attr_accessor :relationship, :resource
|
329
352
|
|
@@ -339,7 +362,7 @@ module JSONAPI
|
|
339
362
|
title: I18n.translate('jsonapi-resources.exceptions.invalid_include.title',
|
340
363
|
default: 'Invalid field'),
|
341
364
|
detail: I18n.translate('jsonapi-resources.exceptions.invalid_include.detail',
|
342
|
-
default: "#{relationship} is not a valid relationship of #{resource}",
|
365
|
+
default: "#{relationship} is not a valid includable relationship of #{resource}",
|
343
366
|
relationship: relationship, resource: resource))]
|
344
367
|
end
|
345
368
|
end
|
@@ -364,24 +387,21 @@ module JSONAPI
|
|
364
387
|
end
|
365
388
|
end
|
366
389
|
|
367
|
-
class
|
368
|
-
attr_accessor :
|
390
|
+
class ParameterNotAllowed < Error
|
391
|
+
attr_accessor :param
|
369
392
|
|
370
|
-
def initialize(
|
371
|
-
@
|
393
|
+
def initialize(param, error_object_overrides = {})
|
394
|
+
@param = param
|
372
395
|
super(error_object_overrides)
|
373
396
|
end
|
374
397
|
|
375
398
|
def errors
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
default: "#{param} is not allowed.", param: param))
|
383
|
-
|
384
|
-
end
|
399
|
+
[create_error_object(code: JSONAPI::PARAM_NOT_ALLOWED,
|
400
|
+
status: :bad_request,
|
401
|
+
title: I18n.translate('jsonapi-resources.exceptions.parameter_not_allowed.title',
|
402
|
+
default: 'Param not allowed'),
|
403
|
+
detail: I18n.translate('jsonapi-resources.exceptions.parameters_not_allowed.detail',
|
404
|
+
default: "#{param} is not allowed.", param: param))]
|
385
405
|
end
|
386
406
|
end
|
387
407
|
|
@@ -451,11 +471,12 @@ module JSONAPI
|
|
451
471
|
end
|
452
472
|
|
453
473
|
class ValidationErrors < Error
|
454
|
-
attr_reader :error_messages, :error_metadata, :resource_relationships
|
474
|
+
attr_reader :error_messages, :error_metadata, :resource_relationships, :resource_class
|
455
475
|
|
456
476
|
def initialize(resource, error_object_overrides = {})
|
457
477
|
@error_messages = resource.model_error_messages
|
458
478
|
@error_metadata = resource.validation_error_metadata
|
479
|
+
@resource_class = resource.class
|
459
480
|
@resource_relationships = resource.class._relationships.keys
|
460
481
|
@key_formatter = JSONAPI.configuration.key_formatter
|
461
482
|
super(error_object_overrides)
|
@@ -477,7 +498,7 @@ module JSONAPI
|
|
477
498
|
create_error_object(code: JSONAPI::VALIDATION_ERROR,
|
478
499
|
status: :unprocessable_entity,
|
479
500
|
title: message,
|
480
|
-
detail:
|
501
|
+
detail: detail(attr_key, message),
|
481
502
|
source: { pointer: pointer(attr_key) },
|
482
503
|
meta: metadata_for(attr_key, message))
|
483
504
|
end
|
@@ -487,7 +508,12 @@ module JSONAPI
|
|
487
508
|
error_metadata[attr_key] ? error_metadata[attr_key][message] : nil
|
488
509
|
end
|
489
510
|
|
511
|
+
def detail(attr_key, message)
|
512
|
+
general_error?(attr_key) ? message : "#{format_key(attr_key)} - #{message}"
|
513
|
+
end
|
514
|
+
|
490
515
|
def pointer(attr_or_relationship_name)
|
516
|
+
return '/data' if general_error?(attr_or_relationship_name)
|
491
517
|
formatted_attr_or_relationship_name = format_key(attr_or_relationship_name)
|
492
518
|
if resource_relationships.include?(attr_or_relationship_name)
|
493
519
|
"/data/relationships/#{formatted_attr_or_relationship_name}"
|
@@ -495,6 +521,10 @@ module JSONAPI
|
|
495
521
|
"/data/attributes/#{formatted_attr_or_relationship_name}"
|
496
522
|
end
|
497
523
|
end
|
524
|
+
|
525
|
+
def general_error?(attr_key)
|
526
|
+
attr_key.to_sym == :base && !resource_class._has_attribute?(attr_key)
|
527
|
+
end
|
498
528
|
end
|
499
529
|
|
500
530
|
class SaveFailed < Error
|
data/lib/jsonapi/formatter.rb
CHANGED
@@ -108,7 +108,7 @@ end
|
|
108
108
|
|
109
109
|
class DasherizedKeyFormatter < JSONAPI::KeyFormatter
|
110
110
|
class << self
|
111
|
-
def format(
|
111
|
+
def format(_key)
|
112
112
|
super.underscore.dasherize
|
113
113
|
end
|
114
114
|
|
@@ -146,7 +146,7 @@ end
|
|
146
146
|
|
147
147
|
class CamelizedRouteFormatter < JSONAPI::RouteFormatter
|
148
148
|
class << self
|
149
|
-
def format(
|
149
|
+
def format(_route)
|
150
150
|
super.camelize(:lower)
|
151
151
|
end
|
152
152
|
|
@@ -158,7 +158,7 @@ end
|
|
158
158
|
|
159
159
|
class DasherizedRouteFormatter < JSONAPI::RouteFormatter
|
160
160
|
class << self
|
161
|
-
def format(
|
161
|
+
def format(_route)
|
162
162
|
super.dasherize
|
163
163
|
end
|
164
164
|
|