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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +35 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +0 -2
  5. data/Guardfile +1 -1
  6. data/README.md +13 -8
  7. data/Rakefile +10 -0
  8. data/config.ru +3 -3
  9. data/index.html +1 -0
  10. data/jsonapionify.gemspec +13 -8
  11. data/lib/jsonapionify/api/action.rb +60 -50
  12. data/lib/jsonapionify/api/attribute.rb +13 -2
  13. data/lib/jsonapionify/api/base/app_builder.rb +17 -2
  14. data/lib/jsonapionify/api/base/class_methods.rb +33 -17
  15. data/lib/jsonapionify/api/base/delegation.rb +4 -1
  16. data/lib/jsonapionify/api/base/doc_helper.rb +13 -4
  17. data/lib/jsonapionify/api/base/resource_definitions.rb +13 -2
  18. data/lib/jsonapionify/api/base.rb +22 -6
  19. data/lib/jsonapionify/api/context_delegate.rb +2 -2
  20. data/lib/jsonapionify/api/errors.rb +7 -2
  21. data/lib/jsonapionify/api/errors_object.rb +1 -1
  22. data/lib/jsonapionify/api/header_options.rb +6 -5
  23. data/lib/jsonapionify/api/param_options.rb +49 -7
  24. data/lib/jsonapionify/api/relationship/many.rb +0 -5
  25. data/lib/jsonapionify/api/relationship/one.rb +10 -9
  26. data/lib/jsonapionify/api/relationship.rb +17 -5
  27. data/lib/jsonapionify/api/resource/builders.rb +39 -10
  28. data/lib/jsonapionify/api/resource/class_methods.rb +17 -6
  29. data/lib/jsonapionify/api/resource/defaults/actions.rb +0 -1
  30. data/lib/jsonapionify/api/resource/defaults/errors.rb +11 -11
  31. data/lib/jsonapionify/api/resource/defaults/options.rb +53 -0
  32. data/lib/jsonapionify/api/resource/defaults/params.rb +9 -0
  33. data/lib/jsonapionify/api/resource/defaults/request_contexts.rb +17 -11
  34. data/lib/jsonapionify/api/resource/definitions/actions.rb +51 -45
  35. data/lib/jsonapionify/api/resource/definitions/attributes.rb +2 -2
  36. data/lib/jsonapionify/api/resource/definitions/helpers.rb +18 -0
  37. data/lib/jsonapionify/api/resource/definitions/pagination.rb +183 -53
  38. data/lib/jsonapionify/api/resource/definitions/params.rb +43 -12
  39. data/lib/jsonapionify/api/resource/definitions/request_headers.rb +1 -67
  40. data/lib/jsonapionify/api/resource/definitions/scopes.rb +2 -13
  41. data/lib/jsonapionify/api/resource/definitions/sorting.rb +71 -58
  42. data/lib/jsonapionify/api/resource/error_handling.rb +2 -2
  43. data/lib/jsonapionify/api/resource/includer.rb +6 -0
  44. data/lib/jsonapionify/api/resource.rb +14 -3
  45. data/lib/jsonapionify/api/response.rb +2 -2
  46. data/lib/jsonapionify/api/server/mock_response.rb +2 -2
  47. data/lib/jsonapionify/api/server/request.rb +11 -7
  48. data/lib/jsonapionify/api/server.rb +1 -1
  49. data/lib/jsonapionify/api/sort_field.rb +59 -0
  50. data/lib/jsonapionify/api/sort_field_set.rb +36 -0
  51. data/lib/jsonapionify/callbacks.rb +3 -3
  52. data/lib/jsonapionify/continuation.rb +1 -0
  53. data/lib/jsonapionify/deep_sort_collection.rb +22 -0
  54. data/lib/jsonapionify/documentation/template.erb +196 -77
  55. data/lib/jsonapionify/documentation.rb +9 -9
  56. data/lib/jsonapionify/indented_string.rb +1 -0
  57. data/lib/jsonapionify/inherited_attributes.rb +4 -3
  58. data/lib/jsonapionify/structure/collections/base.rb +2 -1
  59. data/lib/jsonapionify/structure/helpers/errors.rb +1 -1
  60. data/lib/jsonapionify/structure/helpers/object_defaults.rb +2 -1
  61. data/lib/jsonapionify/structure/helpers/validations.rb +2 -1
  62. data/lib/jsonapionify/structure/objects/base.rb +4 -3
  63. data/lib/jsonapionify/structure/objects/top_level.rb +1 -1
  64. data/lib/jsonapionify/types/boolean_type.rb +2 -2
  65. data/lib/jsonapionify/types/date_string_type.rb +1 -1
  66. data/lib/jsonapionify/types/time_string_type.rb +1 -1
  67. data/lib/jsonapionify/version.rb +1 -1
  68. data/lib/jsonapionify.rb +16 -2
  69. metadata +69 -10
  70. data/fixtures/documentation.json +0 -364
  71. data/lib/jsonapionify/api/resource/http.rb +0 -11
  72. data/lib/jsonapionify/enumerable_observer.rb +0 -91
  73. data/lib/jsonapionify/unstrict_proc.rb +0 -28
@@ -1,3 +1,4 @@
1
+ require 'possessive'
1
2
  require 'active_support/core_ext/array/wrap'
2
3
 
3
4
  module JSONAPIonify::Api
@@ -7,15 +8,10 @@ module JSONAPIonify::Api
7
8
  def self.extended(klass)
8
9
  klass.class_eval do
9
10
  extend JSONAPIonify::InheritedAttributes
11
+ include JSONAPIonify::Callbacks
12
+ define_callbacks :request, :list, :create, :read, :update, :delete,
13
+ :show, :add, :remove, :replace, :exception
10
14
  inherited_array_attribute :action_definitions
11
- inherited_hash_attribute :callbacks
12
-
13
- def self.inherited(subclass)
14
- super
15
- callbacks.each do |action_name, klass|
16
- subclass.callbacks[action_name] = Class.new klass
17
- end
18
- end
19
15
  end
20
16
  end
21
17
 
@@ -27,7 +23,6 @@ module JSONAPIonify::Api
27
23
  context.response_collection,
28
24
  fields: context.fields
29
25
  )
30
- context.meta[:total_count] = context.collection.count
31
26
  context.response_object.to_json
32
27
  end
33
28
  end
@@ -42,6 +37,7 @@ module JSONAPIonify::Api
42
37
  define_action(:create, 'POST', **options, &block).tap do |action|
43
38
  action.response status: 201 do |context|
44
39
  context.response_object[:data] = build_resource(context.request, context.instance, fields: context.fields)
40
+ response_headers['Location'] = build_url(context.request, context.instance)
45
41
  context.response_object.to_json
46
42
  end
47
43
  end
@@ -74,9 +70,19 @@ module JSONAPIonify::Api
74
70
  def process(request)
75
71
  path_actions = self.path_actions(request)
76
72
  if request.options? && path_actions.present?
77
- Action.stub do
78
- headers['Allow'] = path_actions.map(&:request_method).join(', ')
79
- response(status: 200, accept: '*/*')
73
+ allow = [*path_actions.map(&:request_method), 'OPTIONS']
74
+ requests = allow.each_with_object({}) do |method, h|
75
+ h[method] = options_for_method(method)
76
+ end
77
+ Action.dummy do
78
+ response_headers['Allow'] = allow.join(', ')
79
+ end.response(status: 200, accept: 'application/vnd.api+json') do
80
+ JSONAPIonify.new_object(
81
+ meta: {
82
+ type: self.class.type,
83
+ requests: requests
84
+ }
85
+ ).to_json
80
86
  end.call(self, request)
81
87
  elsif (action = find_supported_action(request))
82
88
  action.call(self, request)
@@ -87,39 +93,28 @@ module JSONAPIonify::Api
87
93
  end
88
94
  end
89
95
 
96
+ def options_for_method(method)
97
+ case method
98
+ when 'GET'
99
+ { attributes: attributes.select(&:read).map(&:options_json) }
100
+ when 'POST', 'PUT', 'PATCH'
101
+ { attributes: attributes.select(&:write).map(&:options_json) }
102
+ else
103
+ {}
104
+ end
105
+ end
106
+
90
107
  def before(action_name = nil, &block)
91
108
  if action_name == :index
92
109
  warn 'the `index` action will soon be deprecated, use `list` instead!'
93
110
  action_name = :list
94
111
  end
95
- return base_callbacks.before_request(&block) if action_name == nil
96
- callbacks_for(action_name).before_request(&block)
97
- end
98
-
99
- def base_callbacks
100
- resource = self
101
- callbacks['*'] ||= Class.new do
102
- def self.context(*)
103
- end
104
-
105
- include Resource::ErrorHandling
106
-
107
- define_singleton_method(:error_definitions) do
108
- resource.error_definitions
109
- end
110
-
111
- include JSONAPIonify::Callbacks
112
- define_callbacks :request
113
- end
114
- end
115
-
116
- def callbacks_for(action_name)
117
- resource = self
118
- callbacks[action_name] ||= Class.new(base_callbacks)
112
+ return before_request &block if action_name == nil
113
+ send("before_#{action_name}", &block)
119
114
  end
120
115
 
121
- def define_action(*args, **options, &block)
122
- Action.new(*args, **options, &block).tap do |new_action|
116
+ def define_action(name, *args, **options, &block)
117
+ Action.new(name, *args, **options, &block).tap do |new_action|
123
118
  action_definitions.delete new_action
124
119
  action_definitions << new_action
125
120
  end
@@ -133,14 +128,11 @@ module JSONAPIonify::Api
133
128
 
134
129
  def no_action_response(request)
135
130
  if request_method_actions(request).present?
136
- Action.stub { error_now :unsupported_media_type }
137
- elsif (path_actions = self.path_actions(request)).present?
138
- Action.stub do
139
- headers['Allow'] = path_actions.map(&:request_method).join(', ')
140
- error_now :method_not_allowed
141
- end
131
+ Action.error :unsupported_media_type
132
+ elsif self.path_actions(request).present?
133
+ Action.error :forbidden
142
134
  else
143
- Action.stub { error_now :not_found }
135
+ Action.error :not_found
144
136
  end
145
137
  end
146
138
 
@@ -173,12 +165,26 @@ module JSONAPIonify::Api
173
165
  end
174
166
 
175
167
  def actions
168
+ return if action_definitions.blank?
176
169
  action_definitions.select do |action|
177
170
  action.only_associated == false ||
178
171
  (respond_to?(:rel) && action.only_associated == true)
179
172
  end
180
173
  end
181
174
 
175
+ def documented_actions
176
+ api.eager_load
177
+ relationships = descendants.select { |descendant| descendant.respond_to? :rel }
178
+ rels = relationships.each_with_object([]) do |rel, ary|
179
+ rel.actions.each do |action|
180
+ ary << [action, "#{rel.rel.owner.type}/:id", [rel, rel.rel.name, false, "#{action.name} #{rel.rel.owner.type.singularize.possessive} #{rel.rel.name}"]]
181
+ end
182
+ end
183
+ actions.map do |action|
184
+ [action, '', [self, type, true, "#{action.name} #{type}"]]
185
+ end + rels
186
+ end
187
+
182
188
  private
183
189
 
184
190
  def base_path
@@ -6,7 +6,7 @@ module JSONAPIonify::Api
6
6
  extend JSONAPIonify::InheritedAttributes
7
7
  extend JSONAPIonify::Types
8
8
  inherited_array_attribute :attributes
9
- delegate :attributes, to: :class
9
+ delegate :id_attribute, :attributes, to: :class
10
10
 
11
11
  context(:fields, readonly: true) do |context|
12
12
  should_error = false
@@ -20,7 +20,7 @@ module JSONAPIonify::Api
20
20
  attribute ? field_list << attribute.name : error(:field_not_permitted, type, field) && (should_error = true)
21
21
  end
22
22
  end
23
- raise error_exception if should_error
23
+ raise Errors::RequestError if should_error
24
24
  fields
25
25
  end
26
26
  end
@@ -5,5 +5,23 @@ module JSONAPIonify::Api
5
5
  define_method(name, &block)
6
6
  end
7
7
 
8
+ def authentication(&block)
9
+ context :authentication, readonly: true do |context|
10
+ OpenStruct.new.tap do |authentication_object|
11
+ if instance_exec(context.request, authentication_object, &block) == false
12
+ error_now :forbidden
13
+ end
14
+ end
15
+ end
16
+
17
+ before do |context|
18
+ context.authentication
19
+ end
20
+ end
21
+
22
+ def on_exception(&block)
23
+ before_exception &block
24
+ end
25
+
8
26
  end
9
27
  end
@@ -1,79 +1,209 @@
1
1
  module JSONAPIonify::Api
2
2
  module Resource::Definitions::Pagination
3
-
4
3
  class PaginationLinksDelegate
5
4
 
6
- def initialize(request, links)
7
- @request = request
8
- @links = links
5
+ def initialize(url, params, links)
6
+ @url = url
7
+ @params = params
8
+ @links = links
9
9
  end
10
10
 
11
11
  %i{first last next prev}.each do |method|
12
12
  define_method method do |**options|
13
- @links[method] = URI.parse(@request.url).tap do |uri|
13
+ @links[method] = URI.parse(@url).tap do |uri|
14
14
  page_params = { page: options }.deep_stringify_keys
15
- uri.query = @request.params.deep_merge(page_params).to_param
15
+ uri.query = @params.deep_merge(page_params).to_param
16
16
  end.to_s
17
17
  end
18
18
  end
19
19
 
20
20
  end
21
21
 
22
- STRATEGIES = {
23
- active_record: proc do |collection, params, links|
24
- page_number = Integer(params['number'] || 1)
25
- page_number = 1 if page_number < 1
26
- page_size = Integer(params['size'] || 50)
27
- raise PaginationError if page_size > 250
28
- first_page = 1
29
- last_page = (collection.count / page_size).ceil
30
- last_page = 1 if last_page == 0
31
-
32
- links.first number: 1 unless page_number == first_page
33
- links.last number: last_page unless page_number == last_page
34
- links.prev number: page_number - 1 unless page_number <= first_page
35
- links.next number: page_number + 1 unless page_number >= last_page
36
-
37
- slice_start = (page_number - 1) * page_size
38
- collection.limit(page_size).offset(slice_start)
39
- end,
40
- enumerable: proc do |collection, params, links|
41
- page_number = Integer(params['number'] || 1)
42
- page_number = 1 if page_number < 1
43
- page_size = Integer(params['size'] || 50)
44
- first_page = 1
45
- last_page = (collection.count / page_size).ceil
46
- last_page = 1 if last_page == 0
47
-
48
- links.first number: 1 unless page_number == first_page
49
- links.last number: last_page unless page_number == last_page
50
- links.prev number: page_number - 1 unless page_number <= first_page
51
- links.next number: page_number + 1 unless page_number >= last_page
52
-
53
- slice_start = (page_number - 1) * page_size
54
-
55
- collection.slice(slice_start, page_size)
22
+ def self.extended(klass)
23
+ klass.class_eval do
24
+ include InstanceMethods
25
+
26
+ inherited_hash_attribute :pagination_strategies
27
+
28
+ define_pagination_strategy 'Object' do |collection|
29
+ collection
30
+ end
31
+
32
+ define_pagination_strategy 'Enumerable' do |collection, params, links, per, context|
33
+ size = Integer(params['first'] || params['last'] || per)
34
+
35
+ slice =
36
+ if (params['before'] && params['first']) || (params['after'] && params['last'])
37
+ error :forbidden do
38
+ message 'Illegal combination of parameters'
39
+ end
40
+ elsif (after = params['after'])
41
+ key_values = parse_and_validate_cursor(:after, after, context)
42
+ array_select_past_cursor(
43
+ collection,
44
+ context.sort_params,
45
+ key_values
46
+ ).first(size)
47
+ elsif (before = params['before'])
48
+ key_values = parse_and_validate_cursor(:before, before, context)
49
+ array_select_past_cursor(
50
+ collection,
51
+ context.sort_params.reverse,
52
+ key_values
53
+ ).last(size)
54
+ elsif params['last']
55
+ collection.last(size)
56
+ else
57
+ collection.first(size)
58
+ end
59
+
60
+ links.first first: size
61
+ links.last last: size
62
+ links.prev before: build_cursor_from_instance(context.request, slice.first), last: size unless slice.first == collection.first
63
+ links.next after: build_cursor_from_instance(context.request, slice.last), first: size unless slice.last == collection.last
64
+
65
+ slice
66
+ end
67
+
68
+ define_pagination_strategy 'ActiveRecord::Relation' do |collection, params, links, per, context|
69
+ size = Integer(params['first'] || params['last'] || per)
70
+
71
+ slice =
72
+ if (params['before'] && params['first']) || (params['after'] && params['last'])
73
+ error :forbidden do
74
+ message 'Illegal combination of parameters'
75
+ end
76
+ elsif (after = params['after'])
77
+ key_values = parse_and_validate_cursor(:after, after, context)
78
+ arel_select_past_cursor(
79
+ collection,
80
+ context.sort_params,
81
+ key_values
82
+ ).limit(size)
83
+ elsif (before = params['before'])
84
+ key_values = parse_and_validate_cursor(:before, before, context)
85
+ ids = arel_select_past_cursor(
86
+ collection,
87
+ context.sort_params.reverse,
88
+ key_values
89
+ ).reverse_order.limit(size).pluck(:id)
90
+ collection.where(id_attribute => ids)
91
+ elsif params['last']
92
+ ids = collection.reverse_order.limit(size).pluck(id_attribute)
93
+ collection.where(id_attribute => ids).limit(size)
94
+ else
95
+ collection.limit(size)
96
+ end
97
+
98
+ links.first first: size
99
+ links.last last: size
100
+ links.prev before: build_cursor_from_instance(context.request, slice.first), last: size unless slice.first == collection.first
101
+ links.next after: build_cursor_from_instance(context.request, slice.last), first: size unless slice.last == collection.last
102
+
103
+ slice
104
+ end
56
105
  end
57
- }
58
- STRATEGIES[:array] = STRATEGIES[:enumerable]
59
- DEFAULT = STRATEGIES[:enumerable]
106
+ end
107
+
108
+ def define_pagination_strategy(mod, &block)
109
+ pagination_strategies[mod.to_s] = block
110
+ end
60
111
 
61
- def pagination(*params, strategy: nil, &block)
62
- params = %i{number size} unless block
63
- params.each { |p| param :page, p, actions: %i{list} }
112
+ def enable_pagination(per: 50)
113
+ param :page, :after, actions: %i{list}
114
+ param :page, :before, actions: %i{list}
115
+ param :page, :first, actions: %i{list}
116
+ param :page, :last, actions: %i{list}
64
117
  context :paginated_collection do |context|
65
- unless (actual_block = block)
66
- actual_strategy = strategy || self.class.default_strategy
67
- actual_block = actual_strategy ? STRATEGIES[actual_strategy] : DEFAULT
118
+ collection = context.sorted_collection
119
+ _, block = pagination_strategies.to_a.reverse.to_h.find do |mod, _|
120
+ Object.const_defined?(mod, false) && context.collection.class <= Object.const_get(mod, false)
68
121
  end
69
- Object.new.instance_exec(
70
- context.respond_to?(:sorted_collection) ? context.sorted_collection : context.collection,
122
+
123
+ links_delegate = PaginationLinksDelegate.new(
124
+ context.request.url,
125
+ self.class.sticky_params(context.params),
126
+ context.links
127
+ )
128
+
129
+ instance_exec(
130
+ collection,
71
131
  context.request.params['page'] || {},
72
- PaginationLinksDelegate.new(context.request, context.links),
73
- &actual_block
132
+ links_delegate,
133
+ per,
134
+ context,
135
+ &block
74
136
  )
75
137
  end
76
138
  end
77
139
 
140
+ module InstanceMethods
141
+
142
+ def array_select_past_cursor(collection, sort_params, key_values)
143
+ sort_params.length.times.map do |i|
144
+ set = sort_params[0..i]
145
+ *contains_fields, outside_field = set
146
+
147
+ # Collect the contains results
148
+ contains_results = contains_fields.map do |field|
149
+ collection.select do |item|
150
+ value = item.send(field.name)
151
+ expected_value = key_values[field.name]
152
+ value && value.send(field.contains_operator, expected_value)
153
+ end
154
+ end
155
+
156
+ # Collect the outside results
157
+ outside_results = collection.select do |item|
158
+ value = item.send(outside_field.name)
159
+ expected_value = key_values[outside_field.name.to_s]
160
+ value && value.send(outside_field.outside_operator, expected_value)
161
+ end
162
+
163
+ # Finish the query
164
+ [*contains_results, outside_results].reduce(:&)
165
+ end.reduce(:|) || []
166
+ end
167
+
168
+ def arel_select_past_cursor(collection, sort_params, key_values)
169
+ subselect = sort_params.length.times.map do |i|
170
+ set = sort_params[0..i]
171
+ *contains_fields, outside_field = set
172
+ contains_fields.reduce(collection.reorder(nil)) do |relation, field|
173
+ relation.where <<-SQL.strip, value: key_values[field.name.to_s]
174
+ "#{field.name}" #{field.contains_operator} :value
175
+ SQL
176
+ end.where(<<-SQL.strip, key_values[outside_field.name.to_s]).to_sql
177
+ "#{outside_field.name}" #{outside_field.outside_operator} ?
178
+ SQL
179
+ end.join(' UNION ')
180
+ collection.from("(#{subselect}) AS #{collection.table_name}")
181
+ end
182
+
183
+ def parse_and_validate_cursor(param, cursor, context)
184
+ should_error = false
185
+ options = JSON.parse(Base64.urlsafe_decode64(cursor))
186
+
187
+ # Validate Type
188
+ unless options['t'] == self.class.type
189
+ should_error = true
190
+ error(:page_parameter_invalid, :page, param) do
191
+ detail 'The cursor type does not match the resource'
192
+ end
193
+ end
194
+
195
+ # Validate Sort
196
+ unless options['s'] == context.params['sort']
197
+ should_error = true
198
+ error(:page_parameter_invalid, :page, param) do
199
+ detail 'The cursor sort does not match the request sort'
200
+ end
201
+ end
202
+ raise Errors::RequestError if should_error
203
+
204
+ options['a']
205
+ end
206
+ end
207
+
78
208
  end
79
209
  end
@@ -6,24 +6,30 @@ module JSONAPIonify::Api
6
6
  extend JSONAPIonify::InheritedAttributes
7
7
  inherited_hash_attribute :param_definitions
8
8
 
9
- before do |context|
10
- context.params # pull params so they verify
11
- end
12
-
13
9
  context(:params, readonly: true) do |context|
14
- should_error = false
10
+ should_error = false
15
11
 
16
- # Check for validity
17
- params = self.class.param_definitions.select do |_, v|
12
+ params = self.class.param_definitions.select do |_, v|
18
13
  v.actions.blank? || v.actions.include?(action_name)
19
14
  end
15
+
16
+ context.request.params.replace(
17
+ [*params.values.select(&:has_default?).map(&:default), context.request.params].reduce(:deep_merge)
18
+ )
19
+
20
20
  required_params = params.select do |_, v|
21
21
  v.required
22
22
  end
23
- if (invalid_params = ParamOptions.invalid_parameters(context.request.params, params.values.map(&:keypath))).present?
24
- should_error = true
25
- invalid_params.each do |string|
26
- error :parameter_not_permitted, string
23
+
24
+ # Check for validity
25
+ context.request.params.each do |k, v|
26
+ keypath = ParamOptions.hash_to_keypaths(k => v)[0]
27
+ reserved = ParamOptions.reserved?(k)
28
+ allowed = params.keys.include? keypath
29
+ valid = ParamOptions.valid?(k) || v.is_a?(Hash)
30
+ unless reserved || (allowed && valid)
31
+ should_error = true
32
+ error :parameter_invalid, ParamOptions.keypath_to_string(*keypath)
27
33
  end
28
34
  end
29
35
 
@@ -32,7 +38,7 @@ module JSONAPIonify::Api
32
38
  error :parameters_missing, missing_params
33
39
  end
34
40
 
35
- raise error_exception if should_error
41
+ raise Errors::RequestError if should_error
36
42
 
37
43
  # Return the params
38
44
  context.request.params
@@ -45,5 +51,30 @@ module JSONAPIonify::Api
45
51
  param_definitions[keypath] = ParamOptions.new(*keypath, **options)
46
52
  end
47
53
 
54
+ def sticky_params(params)
55
+ sticky_param_definitions = param_definitions.values.select(&:sticky)
56
+ ParamOptions.hash_to_keypaths(params).map do |keypath|
57
+ definition = sticky_param_definitions.find do |definition|
58
+ definition.keypath == keypath
59
+ end
60
+ next {} unless definition
61
+ value = definition.extract_value(params)
62
+ if definition.default_value?(value)
63
+ {}
64
+ else
65
+ definition.with_value(value)
66
+ end
67
+ end.reduce(:deep_merge)
68
+
69
+ # sticky_param_definitions = param_definitions.values.select(&:sticky)
70
+ # params.each_with_object do |k, v|
71
+ # definition = sticky_param_definitions.find do |definition|
72
+ # definition.keypath == ParamOptions.hash_to_keypaths(k => v)[0]
73
+ # end
74
+ # binding.pry
75
+ # definition && !definition.default_value?(v)
76
+ # end
77
+ end
78
+
48
79
  end
49
80
  end
@@ -6,65 +6,6 @@ module JSONAPIonify::Api
6
6
  extend JSONAPIonify::InheritedAttributes
7
7
  inherited_hash_attribute :request_header_definitions
8
8
 
9
- # Standard HTTP Headers
10
- # https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields
11
- request_header 'accept'
12
- request_header 'accept-charset'
13
- request_header 'accept-encoding'
14
- request_header 'accept-language'
15
- request_header 'accept-datetime'
16
- request_header 'authorization'
17
- request_header 'cache-control'
18
- request_header 'connection'
19
- request_header 'cookie'
20
- request_header 'content-length'
21
- request_header 'content-md5'
22
- request_header 'content-type'
23
- request_header 'date'
24
- request_header 'expect'
25
- request_header 'from'
26
- request_header 'host'
27
- request_header 'if-match'
28
- request_header 'if-modified-since'
29
- request_header 'if-none-match'
30
- request_header 'if-range'
31
- request_header 'if-unmodified-since'
32
- request_header 'max-forwards'
33
- request_header 'origin'
34
- request_header 'pragma'
35
- request_header 'proxy-authorization'
36
- request_header 'range'
37
- request_header 'referer'
38
- request_header 'te'
39
- request_header 'user-agent'
40
- request_header 'upgrade'
41
- request_header 'via'
42
- request_header 'warning'
43
-
44
- # Non-Standard, but widely used HTTP headers
45
- request_header 'x-requested-with'
46
- request_header 'dnt'
47
- request_header 'x-forwarded-for'
48
- request_header 'x-forwarded-host'
49
- request_header 'x-forwarded-proto'
50
- request_header 'front-end-https'
51
- request_header 'x-att-device-id'
52
- request_header 'x-wap-profile'
53
- request_header 'proxy-connection'
54
- request_header 'x-uidh'
55
- request_header 'upgrade-insecure-requests'
56
-
57
- # Don't allow method overrides
58
- # request_header 'x-http-method-override'
59
-
60
- # Don't allow CSRF tokens, as they should not be used
61
- # in the api by default
62
- # request_header 'x-csrf-token'
63
-
64
- before do |context|
65
- context.request_headers # pull request_headers so they verify
66
- end
67
-
68
9
  context(:request_headers) do |context|
69
10
  should_error = false
70
11
 
@@ -76,19 +17,12 @@ module JSONAPIonify::Api
76
17
  v.required
77
18
  end
78
19
 
79
- if (invalid_keys = context.request.headers.keys.map(&:downcase) - headers.keys.map(&:downcase)).present?
80
- should_error = true
81
- invalid_keys.each do |key|
82
- error :header_not_permitted, key
83
- end
84
- end
85
-
86
20
  if (missing_keys = required_headers.keys.map(&:downcase) - context.request.headers.keys.map(&:downcase)).present?
87
21
  should_error = true
88
22
  error :headers_missing, missing_keys
89
23
  end
90
24
 
91
- raise error_exception if should_error
25
+ raise Errors::RequestError if should_error
92
26
 
93
27
  context.request.headers
94
28
  end
@@ -1,17 +1,6 @@
1
1
  module JSONAPIonify::Api
2
2
  module Resource::Definitions::Scopes
3
3
 
4
- def self.extended(klass)
5
- klass.class_eval do
6
- id :id
7
- scope { raise NotImplementedError, 'scope not implemented' }
8
- collection { raise NotImplementedError, 'collection not implemented' }
9
- instance { raise NotImplementedError, 'instance not implemented' }
10
- new_instance { raise NotImplementedError, 'new instance not implemented' }
11
- param :include
12
- end
13
- end
14
-
15
4
  def scope(&block)
16
5
  define_singleton_method(:current_scope) do
17
6
  Object.new.instance_eval(&block)
@@ -25,10 +14,10 @@ module JSONAPIonify::Api
25
14
 
26
15
  def instance(&block)
27
16
  define_singleton_method(:find_instance) do |id|
28
- Object.new.instance_exec(current_scope, id, &block)
17
+ instance_exec(current_scope, id, OpenStruct.new, &block)
29
18
  end
30
19
  context :instance do |context|
31
- self.class.find_instance(context.id)
20
+ instance_exec(context.scope, context.id, context, &block)
32
21
  end
33
22
  end
34
23