param_guard 0.0.1

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.
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Levente Bagi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,82 @@
1
+ = Param Guard
2
+
3
+ Simple utility to check the format of request params, e.g. in a Rails controller, but it can be used on any hash-like Ruby structure.
4
+
5
+ == Installation
6
+
7
+ In Gemfile:
8
+
9
+ gem 'param_guard'
10
+
11
+ == Example
12
+
13
+ For example, in your controller, you could do:
14
+
15
+ def create
16
+ @user = User.new(user_params)
17
+ # ...
18
+ end
19
+
20
+ def user_params
21
+ ParamGuard.filter(
22
+ params,
23
+ {
24
+ user: [:required, :hash, {
25
+ email: [:permitted, :scalar],
26
+ password: [:permitted, :scalar],
27
+ password_confirmation: [:permitted, :scalar],
28
+ profile_attributes: [:required, :hash, {
29
+ date_of_birth: [:permitted, [:scalar, :multi]],
30
+ country_id: [:permitted, [:scalar]],
31
+ terms_ok: [:permitted, [:scalar]],
32
+ }]
33
+ }]
34
+ }
35
+ )[:user]
36
+ end
37
+
38
+ == Definition format
39
+
40
+ The definition should be a hash. The key represents a key in the params hash the value is an array of 2 or 3 values.
41
+
42
+ [1st item]
43
+ <code>:required</code> or <code>:permitted</code>.
44
+
45
+ [2nd item]
46
+ The 2nd item: the expected format of the value
47
+
48
+ - <code>:string</code>
49
+ - <code>:integer</code>
50
+ - <code>:float</code>
51
+ - <code>:hash</code>
52
+ - <code>:scalar</code> - non-structured types
53
+ - <code>:array</code>
54
+ - <code>:multi</code> - multi-parameter values handled by Rails, typically dates and times
55
+
56
+ The required type can also be an array, if any of these matches the value the value will be accepted.
57
+
58
+ [3rd item]
59
+
60
+ If the type is a hash, these will be the definition for the nested hash.
61
+
62
+ See the tests for examples.
63
+
64
+ == Return values
65
+
66
+ This will return a duplicate of params, from which parameters that are not mentioned, will be removed.
67
+
68
+ == Exceptions
69
+
70
+ If a key is required, but is not specified, a <code>ParamGuard::ParameterMissing</code> exception will be raised.
71
+
72
+ If the type is not right, <code>ParamGuard::ParameterOfInvalidType</code> will be raised.
73
+
74
+ Both are a sublass of <code>ParamGuard::InvalidParameters</code>.
75
+
76
+ You should catch these in the controller and respond with HTTP 400 Bad Request. E.g. in you +ApplicationController+:
77
+
78
+ rescue_from ParamGuard::InvalidParameters do |exception|
79
+ Rails.logger.info "Bad Request: #{exception.message}"
80
+ render :text => exception.message, :status => :bad_request
81
+ end
82
+
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ require 'bundler/gem_tasks'
5
+ rescue LoadError
6
+ raise 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'ParamGuard'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.rdoc')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ require 'rake/testtask'
20
+
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'lib'
23
+ t.libs << 'test'
24
+ t.pattern = 'test/**/*_test.rb'
25
+ t.verbose = false
26
+ end
27
+
28
+ task :default => :test
@@ -0,0 +1,90 @@
1
+ module ParamGuard
2
+ InvalidParameters = Class.new(StandardError)
3
+ ParameterMissing = Class.new(InvalidParameters)
4
+ ParameterOfInvalidType = Class.new(InvalidParameters)
5
+
6
+ autoload :ParamDefinition, 'param_guard/param_definition'
7
+
8
+ class << self
9
+
10
+ # Sanitiize params based on rules in defs.
11
+ # Returns a duplicate of params, from which all non-declared fields are deleted.
12
+ # Raises errors ParameterMissing or ParameterOfInvalidType.
13
+ def filter(params, defs, parent_keys = [])
14
+ return params if defs.nil?
15
+ keys_to_keep = []
16
+ defs.each do |key, key_def|
17
+ definition = ParamDefinition.new(*key_def)
18
+ structure, value, params_to_keep = get_param(params, key)
19
+ if value
20
+ if definition.types.any?
21
+ unless definition.types.any?{|t| is_of_type?(t, value, structure)}
22
+ raise ParameterOfInvalidType.new(
23
+ "param '#{keys_to_s(parent_keys + [key])}' must be #{definition.types_for_sentence}"
24
+ )
25
+ end
26
+ end
27
+ keys_to_keep.concat params_to_keep.keys.map(&:to_s)
28
+ if structure == :normal && value.kind_of?(Hash)
29
+ params[key] = filter(value, definition.subdef, parent_keys + [key])
30
+ end
31
+ elsif definition.required?
32
+ raise ParameterMissing.new(
33
+ "param '#{keys_to_s(parent_keys + [key])}' is missing"
34
+ )
35
+ end
36
+ end
37
+ filtered = params.dup
38
+ filtered.delete_if{|key, value| ! keys_to_keep.include? key.to_s }
39
+ filtered
40
+ end
41
+
42
+ private
43
+
44
+ def get_param(params, key)
45
+ key, value = if v = params[key.to_sym]
46
+ [key.to_sym, v]
47
+ elsif v = params[key.to_s]
48
+ [key.to_s, v]
49
+ else
50
+ [key, nil]
51
+ end
52
+ if value
53
+ return :normal, value, { key => value }
54
+ elsif (keys = params.keys.grep(/\A#{Regexp.escape(key.to_s)}\(\d+[if]?\)\z/)).any?
55
+ values = keys.sort.map{|k| params[k]}
56
+ return :multi, values, Hash[keys.map{|k| [k, params[k]]}]
57
+ else
58
+ return :none, nil, {}
59
+ end
60
+ end
61
+
62
+ def is_of_type?(type, value, structure)
63
+ case type.to_sym
64
+ when :string
65
+ value.kind_of? String
66
+ when :integer
67
+ value.kind_of? Fixnum
68
+ when :float
69
+ value.kind_of? Float
70
+ when :hash
71
+ value.kind_of? Hash
72
+ when :scalar
73
+ [String, Fixnum, Float, NilClass, FalseClass, TrueClass].any?{|klass| value.kind_of? klass}
74
+ when :array
75
+ value.kind_of? Array
76
+ when :multi
77
+ structure == :multi
78
+ else
79
+ raise "Unknown type: #{type.inspect}"
80
+ end
81
+ end
82
+
83
+ def keys_to_s(keys)
84
+ "#{keys.first}#{keys[1..-1].map{|k| "[#{k}]"}.join}"
85
+ end
86
+
87
+ end
88
+ end
89
+
90
+
@@ -0,0 +1,21 @@
1
+ module ParamGuard
2
+
3
+ # Rules describing the requirements for a parameter.
4
+ class ParamDefinition < Struct.new(:presence, :type_or_types, :subdef)
5
+ def required?
6
+ presence == :required
7
+ end
8
+
9
+ def types
10
+ Array(type_or_types)
11
+ end
12
+
13
+ def types_for_sentence
14
+ a = types.map(&:to_s)
15
+ return nil if a.empty?
16
+ [a[0..-3] + [a.last(2).join(' or ')]].compact.join(', ')
17
+ end
18
+ end
19
+
20
+ end
21
+
@@ -0,0 +1,3 @@
1
+ module ParamGuard
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,21 @@
1
+ $: << File.expand_path("../../lib", __FILE__)
2
+ require 'test/unit'
3
+ require 'param_guard/param_definition'
4
+
5
+ class ParamGuardParamDefinitionTest < Test::Unit::TestCase
6
+ def test_types_for_sentence_no_types
7
+ assert_equal nil, ParamGuard::ParamDefinition.new.types_for_sentence
8
+ end
9
+
10
+ def test_types_for_sentence_one_type
11
+ assert_equal 'string', ParamGuard::ParamDefinition.new(nil, :string).types_for_sentence
12
+ end
13
+
14
+ def test_types_for_sentence_two_types
15
+ assert_equal 'string or integer', ParamGuard::ParamDefinition.new(nil, [:string, :integer]).types_for_sentence
16
+ end
17
+
18
+ def test_types_for_sentence_three_types
19
+ assert_equal 'hash, string or integer', ParamGuard::ParamDefinition.new(nil, [:hash, :string, :integer]).types_for_sentence
20
+ end
21
+ end
@@ -0,0 +1,121 @@
1
+ $: << File.expand_path("../../lib", __FILE__)
2
+ require 'test/unit'
3
+ require 'param_guard'
4
+
5
+ class ParamGuardTest < Test::Unit::TestCase
6
+
7
+ def test_filter_returns_only_required_and_permitted_params
8
+ defs = { :a => [:required], :b => [:permitted] }
9
+ params = { :a => 1, :b => 2, :c => 3 }
10
+ filtered = ParamGuard.filter(params, defs)
11
+ assert_equal({ :a => 1, :b => 2 }, filtered)
12
+ end
13
+
14
+ def test_filter_raises_error_if_required_param_missing
15
+ defs = { :a => [:required], :b => [:permitted] }
16
+ params = { :b => 2, :c => 3 }
17
+ assert_raise ParamGuard::ParameterMissing do
18
+ filtered = ParamGuard.filter(params, defs)
19
+ end
20
+ end
21
+
22
+ def test_filter_raises_error_if_param_of_wrong_type
23
+ defs = { :a => [:required, :string] }
24
+ params = { :a => 1 }
25
+ assert_raise ParamGuard::ParameterOfInvalidType do
26
+ filtered = ParamGuard.filter(params, defs)
27
+ end
28
+ end
29
+
30
+ def test_filter_raises_error_if_param_of_wrong_type_multiple_types_allowed
31
+ defs = { :a => [:required, [:string, :integer]] }
32
+ params = { :a => {} }
33
+ assert_raise ParamGuard::ParameterOfInvalidType do
34
+ filtered = ParamGuard.filter(params, defs)
35
+ end
36
+ end
37
+
38
+ def test_filter_when_param_of_matches_any_type
39
+ defs = { :a => [:required, [:string, :integer]] }
40
+ params = { :a => 1 }
41
+ filtered = ParamGuard.filter(params, defs)
42
+ assert_equal({ :a => 1 }, filtered)
43
+ end
44
+
45
+ def test_filter_raises_error_if_missing_parameter_in_nested_definition
46
+ defs = {
47
+ :user => [:required, :hash, {
48
+ :name => [:required, :string]
49
+ }]
50
+ }
51
+ params = { :user => {} }
52
+ assert_raise ParamGuard::ParameterMissing do
53
+ filtered = ParamGuard.filter(params, defs)
54
+ end
55
+ end
56
+
57
+ def test_filter_returns_only_valid_parameters_in_nested_definition
58
+ defs = {
59
+ :user => [:required, :hash, {
60
+ :name => [:required, :string]
61
+ }]
62
+ }
63
+ params = { :user => { :name => 'Bob', :email => 'bob@example.com' } }
64
+ filtered = ParamGuard.filter(params, defs)
65
+ assert_equal({ :user => { :name => 'Bob' } }, filtered)
66
+ end
67
+
68
+ def test_filter_accept_multiparams
69
+ defs = {
70
+ dob: [:required, :multi]
71
+ }
72
+ params = { "dob(1i)" => 1999, "dob(2i)" => 1, "dob(3i)" => 1 }
73
+ filtered = ParamGuard.filter(params, defs)
74
+ assert_equal params, filtered
75
+ end
76
+
77
+ def test_filter_accept_multiparams_when_scalar_or_multi_expected
78
+ defs = {
79
+ dob: [:required, [:scalar, :multi]]
80
+ }
81
+ params = { "dob(1i)" => "1999", "dob(2i)" => "1", "dob(3i)" => "1" }
82
+ filtered = ParamGuard.filter(params, defs)
83
+ assert_equal params, filtered
84
+ end
85
+
86
+ def test_filter_accept_scalar_when_scalar_or_multi_expected
87
+ defs = {
88
+ dob: [:required, [:scalar, :multi]]
89
+ }
90
+ params = { "dob" => "1999-01-01" }
91
+ filtered = ParamGuard.filter(params, defs)
92
+ assert_equal params, filtered
93
+ end
94
+
95
+ def test_filter_raise_if_not_multiparams_given
96
+ defs = {
97
+ dob: [:required, :multi]
98
+ }
99
+ params = { "dob" => "1999-01-02" }
100
+ assert_raise ParamGuard::ParameterOfInvalidType do
101
+ filtered = ParamGuard.filter(params, defs)
102
+ end
103
+ end
104
+
105
+ def test_filter_accept_multiparams_or_scalar
106
+ defs = {
107
+ dob: [:required, [:scalar, :multi]]
108
+ }
109
+ params = { "dob" => "1999-01-02" }
110
+ filtered = ParamGuard.filter(params, defs)
111
+ assert_equal params, filtered
112
+ end
113
+
114
+ def test_filter_does_not_alter_original_params_object
115
+ defs = { :a => [:required] }
116
+ params = { :a => 1, :b => 2 }
117
+ filtered = ParamGuard.filter(params, defs)
118
+ assert_equal({ :a => 1, :b => 2 }, params)
119
+ assert_equal({ :a => 1 }, filtered)
120
+ end
121
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: param_guard
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Levente Bagi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description:
31
+ email:
32
+ - bagilevi@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/param_guard/param_definition.rb
38
+ - lib/param_guard/version.rb
39
+ - lib/param_guard.rb
40
+ - MIT-LICENSE
41
+ - Rakefile
42
+ - README.rdoc
43
+ - test/param_definition_test.rb
44
+ - test/param_guard_test.rb
45
+ homepage:
46
+ licenses: []
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ segments:
58
+ - 0
59
+ hash: 1412847858866998539
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ segments:
67
+ - 0
68
+ hash: 1412847858866998539
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 1.8.24
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Filter parameters by defining a required structure
75
+ test_files:
76
+ - test/param_definition_test.rb
77
+ - test/param_guard_test.rb