diff --git a/README.md b/README.md index 1c30678..f219d6f 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,12 @@ Strip a parameter named `client_id` from a fuzzy path: use Rack::FilterParam, { param: :client_id, path: /\A\/oauth/ } ``` +Strip a parameter named `client_id` based on arbitrary logic: + +```ruby +use Rack::FilterParam, { param: :client_id, if: -> (value) { ... } } +``` + To filter multiple parameters, an array of parameters or options hashes can also be passed. ## Contributing diff --git a/lib/rack/filter_param.rb b/lib/rack/filter_param.rb index 6a02f93..c6f9180 100644 --- a/lib/rack/filter_param.rb +++ b/lib/rack/filter_param.rb @@ -23,6 +23,8 @@ module Rack def process_param(param) return unless path_matches?(param) + return unless param_exists?(param) + return unless affirmative_conditional?(param) param = param[:param] if param.is_a?(Hash) @@ -43,6 +45,24 @@ module Rack false end + def param_exists?(param) + param = param.is_a?(Hash) ? param[:param] : param + params.has_key?(param.to_s) + end + + def params + action_dispatch_parsed? ? action_dispatch_params : request.params + end + + def affirmative_conditional?(param) + return true unless param.is_a?(Hash) + + callable, param = param[:if], param[:param] + return true if callable.nil? + + callable.call(params[param.to_s]) + end + def delete_from_action_dispatch(param) action_dispatch_parsed? && !!action_dispatch_params.delete(param.to_s) end diff --git a/spec/rack/filter_param_spec.rb b/spec/rack/filter_param_spec.rb index 9939a96..6acd6de 100644 --- a/spec/rack/filter_param_spec.rb +++ b/spec/rack/filter_param_spec.rb @@ -36,6 +36,18 @@ RSpec.describe Rack::FilterParam do } end + shared_context 'middleware with conditional filter' do + let(:app) { + Rack::Builder.new do + use Rack::FilterParam, { + param: :x, + if: ->(value) { value == 'yes' } + } + run -> (env) { [200, {}, ['OK']] } + end.to_app + } + end + shared_examples 'core functionality' do context 'sending a param that is not expected to be filtered' do let(:params) { { 'a' => '1' } } @@ -134,6 +146,35 @@ RSpec.describe Rack::FilterParam do end end + shared_examples 'conditional filtering' do + context 'when the value should be filtered' do + let(:params) { { 'x' => 'yes', 'y' => 'no' } } + + it 'filters the param' do + expect(params_to_test.keys).to eq ['y'] + end + + it 'includes the param in `rack.filtered_params`' do + expect(last_request.env['rack.filtered_params']) + .to eq [['x', nil]] + end + end + + context 'when the value should not be filtered' do + let(:params) { { 'x' => 'no', 'y' => 'no' } } + + it 'does not filter the param' do + expect(params_to_test) + .to eq('x' => 'no', 'y' => 'no') + end + + it 'does not include the param in `rack.filtered_params`' do + expect(last_request.env['rack.filtered_params']) + .to be nil + end + end + end + context 'GET request' do let(:method) { :get } @@ -146,6 +187,11 @@ RSpec.describe Rack::FilterParam do include_context 'middleware with filtered paths' include_examples 'path filtering' end + + describe 'conditional filtering' do + include_context 'middleware with conditional filter' + include_examples 'conditional filtering' + end end context 'POST request' do @@ -166,6 +212,11 @@ RSpec.describe Rack::FilterParam do include_context 'middleware with filtered paths' include_examples 'path filtering' end + + describe 'conditional filtering' do + include_context 'middleware with conditional filter' + include_examples 'conditional filtering' + end end context 'Request previously parsed by ActionDispatch::ParamsParser' do @@ -186,5 +237,10 @@ RSpec.describe Rack::FilterParam do include_context 'middleware with filtered paths' include_examples 'path filtering' end + + describe 'conditional filtering' do + include_context 'middleware with conditional filter' + include_examples 'conditional filtering' + end end end