jsonapionify 0.0.1.pre → 0.9.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 +4 -4
- data/.editorconfig +35 -0
- data/.ruby-version +1 -1
- data/.travis.yml +0 -2
- data/Guardfile +1 -1
- data/README.md +13 -8
- data/Rakefile +10 -0
- data/config.ru +3 -3
- data/index.html +1 -0
- data/jsonapionify.gemspec +13 -8
- data/lib/jsonapionify/api/action.rb +60 -50
- data/lib/jsonapionify/api/attribute.rb +13 -2
- data/lib/jsonapionify/api/base/app_builder.rb +17 -2
- data/lib/jsonapionify/api/base/class_methods.rb +33 -17
- data/lib/jsonapionify/api/base/delegation.rb +4 -1
- data/lib/jsonapionify/api/base/doc_helper.rb +13 -4
- data/lib/jsonapionify/api/base/resource_definitions.rb +13 -2
- data/lib/jsonapionify/api/base.rb +22 -6
- data/lib/jsonapionify/api/context_delegate.rb +2 -2
- data/lib/jsonapionify/api/errors.rb +7 -2
- data/lib/jsonapionify/api/errors_object.rb +1 -1
- data/lib/jsonapionify/api/header_options.rb +6 -5
- data/lib/jsonapionify/api/param_options.rb +49 -7
- data/lib/jsonapionify/api/relationship/many.rb +0 -5
- data/lib/jsonapionify/api/relationship/one.rb +10 -9
- data/lib/jsonapionify/api/relationship.rb +17 -5
- data/lib/jsonapionify/api/resource/builders.rb +39 -10
- data/lib/jsonapionify/api/resource/class_methods.rb +17 -6
- data/lib/jsonapionify/api/resource/defaults/actions.rb +0 -1
- data/lib/jsonapionify/api/resource/defaults/errors.rb +11 -11
- data/lib/jsonapionify/api/resource/defaults/options.rb +53 -0
- data/lib/jsonapionify/api/resource/defaults/params.rb +9 -0
- data/lib/jsonapionify/api/resource/defaults/request_contexts.rb +17 -11
- data/lib/jsonapionify/api/resource/definitions/actions.rb +51 -45
- data/lib/jsonapionify/api/resource/definitions/attributes.rb +2 -2
- data/lib/jsonapionify/api/resource/definitions/helpers.rb +18 -0
- data/lib/jsonapionify/api/resource/definitions/pagination.rb +183 -53
- data/lib/jsonapionify/api/resource/definitions/params.rb +43 -12
- data/lib/jsonapionify/api/resource/definitions/request_headers.rb +1 -67
- data/lib/jsonapionify/api/resource/definitions/scopes.rb +2 -13
- data/lib/jsonapionify/api/resource/definitions/sorting.rb +71 -58
- data/lib/jsonapionify/api/resource/error_handling.rb +2 -2
- data/lib/jsonapionify/api/resource/includer.rb +6 -0
- data/lib/jsonapionify/api/resource.rb +14 -3
- data/lib/jsonapionify/api/response.rb +2 -2
- data/lib/jsonapionify/api/server/mock_response.rb +2 -2
- data/lib/jsonapionify/api/server/request.rb +11 -7
- data/lib/jsonapionify/api/server.rb +1 -1
- data/lib/jsonapionify/api/sort_field.rb +59 -0
- data/lib/jsonapionify/api/sort_field_set.rb +36 -0
- data/lib/jsonapionify/callbacks.rb +3 -3
- data/lib/jsonapionify/continuation.rb +1 -0
- data/lib/jsonapionify/deep_sort_collection.rb +22 -0
- data/lib/jsonapionify/documentation/template.erb +196 -77
- data/lib/jsonapionify/documentation.rb +9 -9
- data/lib/jsonapionify/indented_string.rb +1 -0
- data/lib/jsonapionify/inherited_attributes.rb +4 -3
- data/lib/jsonapionify/structure/collections/base.rb +2 -1
- data/lib/jsonapionify/structure/helpers/errors.rb +1 -1
- data/lib/jsonapionify/structure/helpers/object_defaults.rb +2 -1
- data/lib/jsonapionify/structure/helpers/validations.rb +2 -1
- data/lib/jsonapionify/structure/objects/base.rb +4 -3
- data/lib/jsonapionify/structure/objects/top_level.rb +1 -1
- data/lib/jsonapionify/types/boolean_type.rb +2 -2
- data/lib/jsonapionify/types/date_string_type.rb +1 -1
- data/lib/jsonapionify/types/time_string_type.rb +1 -1
- data/lib/jsonapionify/version.rb +1 -1
- data/lib/jsonapionify.rb +16 -2
- metadata +69 -10
- data/fixtures/documentation.json +0 -364
- data/lib/jsonapionify/api/resource/http.rb +0 -11
- data/lib/jsonapionify/enumerable_observer.rb +0 -91
- data/lib/jsonapionify/unstrict_proc.rb +0 -28
@@ -1,6 +1,11 @@
|
|
1
1
|
module JSONAPIonify::Api
|
2
2
|
module Errors
|
3
|
-
|
4
|
-
|
3
|
+
JSONAPIonifyError = Class.new StandardError
|
4
|
+
ResourceNotFound = Class.new JSONAPIonifyError
|
5
|
+
RelationshipNotFound = Class.new JSONAPIonifyError
|
6
|
+
RequestError = Class.new JSONAPIonifyError
|
7
|
+
CacheHit = Class.new JSONAPIonifyError
|
8
|
+
DoubleCacheError = Class.new JSONAPIonifyError
|
9
|
+
InvalidCursor = Class.new JSONAPIonifyError
|
5
10
|
end
|
6
11
|
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module JSONAPIonify::Api
|
2
2
|
class HeaderOptions
|
3
3
|
|
4
|
-
attr_reader :name, :actions, :required
|
4
|
+
attr_reader :name, :actions, :required, :documented
|
5
5
|
|
6
|
-
def initialize(name, actions: nil, required: false)
|
7
|
-
@name
|
8
|
-
@actions
|
9
|
-
@required
|
6
|
+
def initialize(name, actions: nil, required: false, documented: true)
|
7
|
+
@name = name
|
8
|
+
@actions = Array.wrap(actions)
|
9
|
+
@required = required
|
10
|
+
@documented = documented
|
10
11
|
end
|
11
12
|
|
12
13
|
end
|
@@ -1,14 +1,28 @@
|
|
1
1
|
module JSONAPIonify::Api
|
2
2
|
class ParamOptions
|
3
|
+
extend JSONAPIonify::Structure::Helpers::MemberNames
|
4
|
+
|
5
|
+
def self.reserved?(value)
|
6
|
+
%w{sort include}.include? value
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.valid?(value)
|
10
|
+
super(value) && value =~ /[^\u0061-\u007A]/
|
11
|
+
end
|
3
12
|
|
4
13
|
def self.hash_to_keypaths(hash)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
14
|
+
hash.each_with_object([]) do |(k, v), key_paths|
|
15
|
+
pather = lambda do |v, current_path|
|
16
|
+
if v.is_a? Hash
|
17
|
+
v.each do |k, v|
|
18
|
+
pather.call(v, [*current_path, k])
|
19
|
+
end
|
20
|
+
else
|
21
|
+
key_paths << current_path.map(&:to_sym)
|
22
|
+
end
|
9
23
|
end
|
24
|
+
pather.call(v, [k])
|
10
25
|
end
|
11
|
-
mapper[hash, []].map(&:flatten)
|
12
26
|
end
|
13
27
|
|
14
28
|
def self.keypath_to_string(*paths)
|
@@ -30,14 +44,42 @@ module JSONAPIonify::Api
|
|
30
44
|
end
|
31
45
|
end
|
32
46
|
|
33
|
-
attr_reader :keypath, :actions, :required
|
47
|
+
attr_reader :keypath, :actions, :required, :sticky
|
34
48
|
|
35
|
-
def initialize(*keys, actions: nil, required: false)
|
49
|
+
def initialize(*keys, default: nil, actions: nil, required: false, sticky: false)
|
36
50
|
@keypath = keys
|
51
|
+
@sticky = sticky
|
37
52
|
@actions = Array.wrap(actions)
|
53
|
+
@default = default.to_s
|
38
54
|
@required = required
|
39
55
|
end
|
40
56
|
|
57
|
+
def with_value(value)
|
58
|
+
Hash.new.tap do |hash|
|
59
|
+
keypath[0..-2].reduce(hash) do |current_hash, key|
|
60
|
+
current_hash[key.to_s] = {}
|
61
|
+
end[keypath.last.to_s] = value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def default
|
66
|
+
with_value @default
|
67
|
+
end
|
68
|
+
|
69
|
+
def extract_value(params)
|
70
|
+
keypath.reduce(params) do |p, key|
|
71
|
+
p[key.to_s]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def has_default?
|
76
|
+
@default.present?
|
77
|
+
end
|
78
|
+
|
79
|
+
def default_value?(value)
|
80
|
+
@default == value
|
81
|
+
end
|
82
|
+
|
41
83
|
def string
|
42
84
|
self.class.keypath_to_string(*@keypath)
|
43
85
|
end
|
@@ -12,7 +12,6 @@ module JSONAPIonify::Api
|
|
12
12
|
options[:prepend] = 'relationships'
|
13
13
|
define_action(:show, 'GET', **options, &block).response status: 200 do |context|
|
14
14
|
context.response_object[:data] = build_identifier_collection(context.collection)
|
15
|
-
context.meta[:total_count] = context.collection.count
|
16
15
|
context.response_object.to_json
|
17
16
|
end
|
18
17
|
end
|
@@ -23,7 +22,6 @@ module JSONAPIonify::Api
|
|
23
22
|
context.owner_context.reset(:instance)
|
24
23
|
context.reset(:collection)
|
25
24
|
context.response_object[:data] = build_identifier_collection(context.collection)
|
26
|
-
context.meta[:total_count] = context.collection.count
|
27
25
|
context.response_object.to_json
|
28
26
|
end
|
29
27
|
end
|
@@ -34,7 +32,6 @@ module JSONAPIonify::Api
|
|
34
32
|
context.owner_context.reset(:instance)
|
35
33
|
context.reset(:collection)
|
36
34
|
context.response_object[:data] = build_identifier_collection(context.collection)
|
37
|
-
context.meta[:total_count] = context.collection.count
|
38
35
|
context.response_object.to_json
|
39
36
|
end
|
40
37
|
end
|
@@ -45,7 +42,6 @@ module JSONAPIonify::Api
|
|
45
42
|
context.owner_context.reset(:instance)
|
46
43
|
context.reset(:collection)
|
47
44
|
context.response_object[:data] = build_identifier_collection(context.collection)
|
48
|
-
context.meta[:total_count] = context.collection.count
|
49
45
|
context.response_object.to_json
|
50
46
|
end
|
51
47
|
end
|
@@ -55,7 +51,6 @@ module JSONAPIonify::Api
|
|
55
51
|
end
|
56
52
|
|
57
53
|
show
|
58
|
-
replace { error_now :forbidden }
|
59
54
|
end
|
60
55
|
end
|
61
56
|
end
|
@@ -4,8 +4,9 @@ module JSONAPIonify::Api
|
|
4
4
|
prepend_class do
|
5
5
|
rel = self.rel
|
6
6
|
remove_action :list, :create
|
7
|
+
|
7
8
|
class << self
|
8
|
-
undef_method :list
|
9
|
+
undef_method :list
|
9
10
|
end
|
10
11
|
|
11
12
|
define_singleton_method(:show) do |**options, &block|
|
@@ -16,15 +17,15 @@ module JSONAPIonify::Api
|
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
20
|
+
define_singleton_method(:replace) do |**options, &block|
|
21
|
+
options[:prepend] = 'relationships'
|
22
|
+
define_action(:replace, 'PATCH', '', nil, :resource_identifier, **options, &block).response status: 200 do |context|
|
23
|
+
context.owner_context.reset(:instance)
|
24
|
+
context.reset(:instance)
|
25
|
+
context.response_object[:data] = build_resource_identifier(context.instance)
|
26
|
+
context.response_object.to_json
|
27
27
|
end
|
28
|
+
end
|
28
29
|
|
29
30
|
context :instance do |context|
|
30
31
|
context.owner_context.instance.send(rel.name)
|
@@ -30,6 +30,10 @@ module JSONAPIonify::Api
|
|
30
30
|
owner_context_proc.call(context.request)
|
31
31
|
end
|
32
32
|
|
33
|
+
context(:owner) do |context|
|
34
|
+
context.owner_context.instance
|
35
|
+
end
|
36
|
+
|
33
37
|
context(:id) do
|
34
38
|
nil
|
35
39
|
end
|
@@ -43,9 +47,17 @@ module JSONAPIonify::Api
|
|
43
47
|
end
|
44
48
|
|
45
49
|
define_singleton_method(:build_links) do |base_url|
|
50
|
+
build_url = ->(*paths) {
|
51
|
+
URI.parse(base_url).tap do |uri|
|
52
|
+
uri.path = File.join uri.path, *paths
|
53
|
+
params = sticky_params(Rack::Utils.parse_nested_query(uri.query))
|
54
|
+
uri.query = params.to_param if params.present?
|
55
|
+
end.to_s
|
56
|
+
}
|
57
|
+
|
46
58
|
JSONAPIonify::Structure::Maps::RelationshipLinks.new(
|
47
|
-
self:
|
48
|
-
related:
|
59
|
+
self: build_url['relationships', rel.name.to_s],
|
60
|
+
related: build_url[rel.name.to_s]
|
49
61
|
)
|
50
62
|
end
|
51
63
|
end
|
@@ -59,10 +71,10 @@ module JSONAPIonify::Api
|
|
59
71
|
@resource = resource || name
|
60
72
|
end
|
61
73
|
|
62
|
-
def documentation_object
|
74
|
+
def documentation_object
|
63
75
|
OpenStruct.new(
|
64
|
-
name:
|
65
|
-
|
76
|
+
name: name,
|
77
|
+
resource: resource_class.type
|
66
78
|
)
|
67
79
|
end
|
68
80
|
|
@@ -4,18 +4,26 @@ module JSONAPIonify::Api
|
|
4
4
|
|
5
5
|
module ClassMethods
|
6
6
|
def build_resource(request, instance, fields: api.fields, relationships: true, links: true)
|
7
|
+
relationships = false if JSONAPIonify::FALSEY_STRINGS.include? request.params['include-relationships']
|
7
8
|
return nil unless instance
|
8
9
|
resource_url = build_url(request, instance)
|
9
10
|
id = build_id(instance)
|
10
11
|
JSONAPIonify::Structure::Objects::Resource.new.tap do |resource|
|
11
|
-
resource[:id]
|
12
|
-
resource[:type]
|
13
|
-
|
12
|
+
resource[:id] = id
|
13
|
+
resource[:type] = type
|
14
|
+
|
15
|
+
resource[:attributes] = fields[type.to_sym].each_with_object(JSONAPIonify::Structure::Objects::Attributes.new) do |member, attributes|
|
14
16
|
attributes[member.to_sym] = instance.public_send(member)
|
15
17
|
end
|
16
|
-
|
18
|
+
|
19
|
+
resource[:links] = JSONAPIonify::Structure::Objects::Links.new(
|
17
20
|
self: resource_url
|
18
21
|
) if links
|
22
|
+
|
23
|
+
resource[:meta] = {
|
24
|
+
cursor: build_cursor_from_instance(request, instance)
|
25
|
+
}
|
26
|
+
|
19
27
|
resource[:relationships] = relationship_definitions.each_with_object(JSONAPIonify::Structure::Maps::Relationships.new) do |rel, hash|
|
20
28
|
hash[rel.name] = build_relationship(request, instance, rel.name)
|
21
29
|
end if relationships
|
@@ -29,12 +37,28 @@ module JSONAPIonify::Api
|
|
29
37
|
)
|
30
38
|
end
|
31
39
|
|
32
|
-
def build_collection(request, collection, fields: api.fields
|
40
|
+
def build_collection(request, collection, fields: api.fields)
|
41
|
+
relationships = JSONAPIonify::TRUTHY_STRINGS.include? request.params['include-relationships']
|
33
42
|
collection.each_with_object(JSONAPIonify::Structure::Collections::Resources.new) do |instance, resources|
|
34
43
|
resources << build_resource(request, instance, fields: fields, relationships: relationships)
|
35
44
|
end
|
36
45
|
end
|
37
46
|
|
47
|
+
def build_cursor_from_instance(request, instance)
|
48
|
+
sort_string = request.params['sort']
|
49
|
+
sort_fields = sort_fields_from_sort_string(sort_string)
|
50
|
+
attrs_with_values = sort_fields.each_with_object({}) do |field, hash|
|
51
|
+
hash[field.name] = instance.send(field.name)
|
52
|
+
end
|
53
|
+
Base64.urlsafe_encode64(JSON.dump(
|
54
|
+
{
|
55
|
+
t: type,
|
56
|
+
s: sort_string,
|
57
|
+
a: attrs_with_values
|
58
|
+
}
|
59
|
+
))
|
60
|
+
end
|
61
|
+
|
38
62
|
def build_identifier_collection(collection)
|
39
63
|
collection.each_with_object(JSONAPIonify::Structure::Collections::ResourceIdentifiers.new) do |instance, resource_identifiers|
|
40
64
|
resource_identifiers << build_resource_identifier(instance)
|
@@ -61,11 +85,16 @@ module JSONAPIonify::Api
|
|
61
85
|
end
|
62
86
|
|
63
87
|
def build_url(request, instance = nil)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
88
|
+
URI.parse(request.root_url).tap do |uri|
|
89
|
+
uri.path =
|
90
|
+
if instance
|
91
|
+
File.join(uri.path, type, build_id(instance))
|
92
|
+
else
|
93
|
+
File.join(request.root_url, type)
|
94
|
+
end
|
95
|
+
sticky_params = self.sticky_params(request.params)
|
96
|
+
uri.query = sticky_params.to_param if sticky_params.present?
|
97
|
+
end.to_s
|
69
98
|
end
|
70
99
|
|
71
100
|
def build_id(instance)
|
@@ -36,23 +36,34 @@ module JSONAPIonify::Api
|
|
36
36
|
nil
|
37
37
|
end
|
38
38
|
|
39
|
+
def eager_load
|
40
|
+
relationships.map(&:name).each do |name|
|
41
|
+
relationship name
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
39
45
|
def get_url(base)
|
40
|
-
|
46
|
+
URI.parse(base).tap do |uri|
|
47
|
+
uri.path = File.join(uri.path, type)
|
48
|
+
params = sticky_params(Rack::Utils.parse_nested_query(uri.query))
|
49
|
+
uri.query = params.to_param if params.present?
|
50
|
+
end.to_s
|
41
51
|
end
|
42
52
|
|
43
|
-
def
|
53
|
+
def documented_actions_in_order
|
44
54
|
indexes = %i{list create read update delete add replace remove}
|
45
|
-
|
55
|
+
documented_actions.sort_by { |action, _| indexes.index(action.name) || indexes.length }
|
46
56
|
end
|
47
57
|
|
48
58
|
def documentation_object(base_url)
|
49
|
-
url = File.join(base_url, type)
|
50
59
|
OpenStruct.new(
|
51
60
|
name: type,
|
52
61
|
description: JSONAPIonify::Documentation.render_markdown(@description || ''),
|
53
|
-
relationships: relationships.map { |r| r.documentation_object
|
62
|
+
relationships: relationships.map { |r| r.documentation_object },
|
54
63
|
attributes: attributes.map(&:documentation_object),
|
55
|
-
actions:
|
64
|
+
actions: documented_actions_in_order.map do |action, base, args|
|
65
|
+
action.documentation_object File.join(base_url, base), *args
|
66
|
+
end
|
56
67
|
)
|
57
68
|
end
|
58
69
|
|
@@ -8,7 +8,7 @@ module JSONAPIonify::Api
|
|
8
8
|
rescue_from JSONAPIonify::Structure::ValidationError, error: :jsonapi_validation_error
|
9
9
|
rescue_from Oj::ParseError, error: :json_parse_error
|
10
10
|
|
11
|
-
Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |symbol, code|
|
11
|
+
Rack::Utils::SYMBOL_TO_STATUS_CODE.reject { |_, v| v < 400 }.each do |symbol, code|
|
12
12
|
message = Rack::Utils::HTTP_STATUS_CODES[code]
|
13
13
|
error symbol do
|
14
14
|
title message
|
@@ -51,7 +51,7 @@ module JSONAPIonify::Api
|
|
51
51
|
|
52
52
|
error :include_parameter_invalid do
|
53
53
|
parameter 'sort'
|
54
|
-
title '
|
54
|
+
title 'Include parameter is invalid'
|
55
55
|
status '400'
|
56
56
|
end
|
57
57
|
|
@@ -61,10 +61,10 @@ module JSONAPIonify::Api
|
|
61
61
|
status '400'
|
62
62
|
end
|
63
63
|
|
64
|
-
error :
|
64
|
+
error :parameter_invalid do |param|
|
65
65
|
parameter param
|
66
|
-
title 'Parameter
|
67
|
-
detail "parameter
|
66
|
+
title 'Parameter Invalid'
|
67
|
+
detail "parameter invalid: #{param}"
|
68
68
|
status '400'
|
69
69
|
end
|
70
70
|
|
@@ -74,18 +74,18 @@ module JSONAPIonify::Api
|
|
74
74
|
status '400'
|
75
75
|
end
|
76
76
|
|
77
|
-
error :header_not_permitted do |header|
|
78
|
-
title 'Header Not Permitted'
|
79
|
-
detail "header not permitted: #{header}"
|
80
|
-
status '400'
|
81
|
-
end
|
82
|
-
|
83
77
|
error :sort_parameter_invalid do
|
84
78
|
parameter 'sort'
|
85
79
|
title 'Sort parameter is invalid'
|
86
80
|
status '400'
|
87
81
|
end
|
88
82
|
|
83
|
+
error :page_parameter_invalid do |*paths|
|
84
|
+
parameter ParamOptions.keypath_to_string(*paths)
|
85
|
+
title 'Page parameter invalid'
|
86
|
+
status '400'
|
87
|
+
end
|
88
|
+
|
89
89
|
error :request_object_invalid do |context, request_object|
|
90
90
|
context.errors.set request_object.errors.as_collection
|
91
91
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module JSONAPIonify::Api
|
2
|
+
module Resource::Defaults::Options
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
included do
|
5
|
+
id :id
|
6
|
+
scope { raise NotImplementedError, 'scope not implemented' }
|
7
|
+
collection { raise NotImplementedError, 'collection not implemented' }
|
8
|
+
instance { raise NotImplementedError, 'instance not implemented' }
|
9
|
+
new_instance { raise NotImplementedError, 'new instance not implemented' }
|
10
|
+
|
11
|
+
context :scope_defined? do |context|
|
12
|
+
begin
|
13
|
+
!!context.scope
|
14
|
+
rescue NotImplementedError
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context :collection_defined? do |context|
|
20
|
+
begin
|
21
|
+
!!context.collection
|
22
|
+
rescue NotImplementedError
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context :instance_defined? do |context|
|
28
|
+
begin
|
29
|
+
!!context.instance
|
30
|
+
rescue NotImplementedError
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context :new_instance_defined? do |context|
|
36
|
+
begin
|
37
|
+
!!context.new_instance
|
38
|
+
rescue NotImplementedError
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
before do |context|
|
44
|
+
context.request_headers # pull request_headers so they verify
|
45
|
+
end
|
46
|
+
|
47
|
+
before do |context|
|
48
|
+
context.params # pull params so they verify
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -18,20 +18,22 @@ module JSONAPIonify::Api
|
|
18
18
|
end
|
19
19
|
|
20
20
|
context(:request_attributes, readonly: true) do |context|
|
21
|
-
request_object
|
21
|
+
request_object = context.request_object
|
22
|
+
|
23
|
+
# Validate Request Object
|
24
|
+
request_object.validate
|
25
|
+
error_now(:request_object_invalid, context, request_object) if request_object.errors.present?
|
26
|
+
|
22
27
|
request_attributes = context.request_data.fetch(:attributes) do
|
23
28
|
error_now :attributes_missing
|
24
29
|
end
|
25
30
|
request_attributes.tap do |attributes|
|
31
|
+
# Check Permitted Attributes
|
26
32
|
writable_attributes = context.request_resource.attributes.select(&:write?)
|
27
|
-
|
28
|
-
optional_attributes = writable_attributes.select(&:optional?).map(&:name)
|
29
|
-
if (extra_attributes = attributes.keys - (optional_attributes + required_attributes)).present?
|
33
|
+
if (extra_attributes = attributes.keys - writable_attributes.map(&:name)).present?
|
30
34
|
extra_attributes.each { |attr| error :attribute_not_permitted, attr }
|
31
|
-
raise
|
35
|
+
raise Errors::RequestError
|
32
36
|
end
|
33
|
-
request_object.validate
|
34
|
-
error_now(:request_object_invalid, context, request_object) if request_object.errors.present?
|
35
37
|
end.to_hash
|
36
38
|
end
|
37
39
|
|
@@ -41,11 +43,11 @@ module JSONAPIonify::Api
|
|
41
43
|
instances = data.map.each_with_index do |item, i|
|
42
44
|
begin
|
43
45
|
find_instance item, pointer: "data/#{i}"
|
44
|
-
rescue
|
46
|
+
rescue Errors::RequestError
|
45
47
|
should_error = true
|
46
48
|
end
|
47
49
|
end
|
48
|
-
raise
|
50
|
+
raise Errors::RequestError if should_error
|
49
51
|
instances
|
50
52
|
end
|
51
53
|
|
@@ -63,6 +65,10 @@ module JSONAPIonify::Api
|
|
63
65
|
error_now(:data_missing)
|
64
66
|
}
|
65
67
|
end
|
68
|
+
|
69
|
+
context(:authentication, readonly: true) do
|
70
|
+
OpenStruct.new
|
71
|
+
end
|
66
72
|
end
|
67
73
|
|
68
74
|
def find_instance(item, pointer:)
|
@@ -75,7 +81,7 @@ module JSONAPIonify::Api
|
|
75
81
|
self.detail "could not find resource: `#{item[:type]}` with id: #{item[:id]}"
|
76
82
|
end
|
77
83
|
end
|
78
|
-
raise
|
84
|
+
raise Errors::RequestError if should_error
|
79
85
|
instance
|
80
86
|
end
|
81
87
|
|
@@ -88,7 +94,7 @@ module JSONAPIonify::Api
|
|
88
94
|
self.detail "could not find resource: `#{item[:type]}`"
|
89
95
|
end
|
90
96
|
end
|
91
|
-
raise
|
97
|
+
raise Errors::RequestError if should_error
|
92
98
|
resource
|
93
99
|
end
|
94
100
|
|