Skip to content

Commit ec29205

Browse files
Dishwasharichmolj
authored andcommitted
Add ability to require a filter to be provided (#88)
I should be allowed to require a consumer to provide one or more filters to prevent cart-blanche querying to the API.
1 parent 8f70811 commit ec29205

File tree

5 files changed

+119
-1
lines changed

5 files changed

+119
-1
lines changed

lib/jsonapi_compliable/errors.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,19 @@ def pretty(input)
4747

4848
class RecordNotFound < StandardError
4949
end
50+
51+
class RequiredFilter < StandardError
52+
def initialize(attributes)
53+
@attributes = Array(attributes)
54+
end
55+
56+
def message
57+
if @attributes.length > 1
58+
"The required filters \"#{@attributes.join(', ')}\" were not provided"
59+
else
60+
"The required filter \"#{@attributes[0]}\" was not provided"
61+
end
62+
end
63+
end
5064
end
5165
end

lib/jsonapi_compliable/resource.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ def self.allow_filter(name, *args, &blk)
172172
config[:filters][name.to_sym] = {
173173
aliases: aliases,
174174
if: opts[:if],
175-
filter: blk
175+
filter: blk,
176+
required: opts[:required].respond_to?(:call) ? opts[:required] : !!opts[:required]
176177
}
177178
end
178179

lib/jsonapi_compliable/scoping/filter.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class Scoping::Filter < Scoping::Base
2828
# aliases. If valid, call either the default or custom filtering logic.
2929
# @return the scope we are chaining/modifying
3030
def apply
31+
raise JsonapiCompliable::Errors::RequiredFilter.new(missing_required_filters) unless required_filters_provided?
3132
each_filter do |filter, value|
3233
@scope = filter_scope(filter, value)
3334
end

lib/jsonapi_compliable/scoping/filterable.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,19 @@ def find_filter!(name)
2323
def filter_param
2424
query_hash[:filter]
2525
end
26+
27+
def missing_required_filters
28+
required_filters.keys - filter_param.keys
29+
end
30+
31+
def required_filters
32+
resource.filters.select do |_name, opts|
33+
opts[:required].respond_to?(:call) ? opts[:required].call(resource.context) : opts[:required]
34+
end
35+
end
36+
37+
def required_filters_provided?
38+
missing_required_filters.empty?
39+
end
2640
end
2741
end

spec/filtering_spec.rb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,92 @@
208208
}.to raise_error(JsonapiCompliable::Errors::BadFilter)
209209
end
210210
end
211+
212+
context 'when one or more filters are required' do
213+
before do
214+
author = author1
215+
resource_class.class_eval do
216+
allow_filter :required, required: true do |scope, value|
217+
scope.where(id: author.id)
218+
end
219+
220+
allow_filter :also_required, required: true do |scope, value|
221+
scope.where(first_name: author.first_name)
222+
end
223+
end
224+
end
225+
226+
context 'and all required filter are provided' do
227+
before do
228+
params[:filter] = { required: true, also_required: true }
229+
end
230+
231+
it 'should return results' do
232+
ids = scope.resolve.map(&:id)
233+
expect(ids).to eq([author1.id])
234+
end
235+
end
236+
237+
context 'and at least one required filter is provided but some are missing' do
238+
before do
239+
params[:filter] = { required: true }
240+
end
241+
242+
it 'raises an error' do
243+
expect {
244+
scope.resolve
245+
}.to raise_error(JsonapiCompliable::Errors::RequiredFilter, 'The required filter "also_required" was not provided')
246+
end
247+
end
248+
249+
context 'and no required filters are provided' do
250+
before do
251+
params[:filter] = { }
252+
end
253+
254+
it 'raises an error' do
255+
expect {
256+
scope.resolve
257+
}.to raise_error(JsonapiCompliable::Errors::RequiredFilter, 'The required filters "required, also_required" were not provided')
258+
end
259+
260+
end
261+
262+
context 'and required filter determined by proc' do
263+
context 'when required proc evaluates to true' do
264+
before do
265+
resource_class.class_eval do
266+
allow_filter :required_by_proc, required: Proc.new{|ctx| true} do |scope, value|
267+
scope.where(first_name: author.first_name)
268+
end
269+
end
270+
271+
params[:filter] = { required: true, also_required: true }
272+
end
273+
274+
it 'raises an error' do
275+
expect {
276+
scope.resolve
277+
}.to raise_error(JsonapiCompliable::Errors::RequiredFilter, 'The required filter "required_by_proc" was not provided')
278+
end
279+
end
280+
281+
context 'when required proc evaluates to false' do
282+
before do
283+
resource_class.class_eval do
284+
allow_filter :required_by_proc, required: Proc.new{|ctx| false} do |scope, value|
285+
scope.where(first_name: author.first_name)
286+
end
287+
end
288+
289+
params[:filter] = { required: true, also_required: true }
290+
end
291+
292+
it 'should not be required' do
293+
ids = scope.resolve.map(&:id)
294+
expect(ids).to eq([author1.id])
295+
end
296+
end
297+
end
298+
end
211299
end

0 commit comments

Comments
 (0)