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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +82 -0
- data/Rakefile +28 -0
- data/lib/param_guard.rb +90 -0
- data/lib/param_guard/param_definition.rb +21 -0
- data/lib/param_guard/version.rb +3 -0
- data/test/param_definition_test.rb +21 -0
- data/test/param_guard_test.rb +121 -0
- metadata +77 -0
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.rdoc
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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
|
data/lib/param_guard.rb
ADDED
@@ -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,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
|