input_sanitizer 0.1.9 → 0.4.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.
- checksums.yaml +7 -0
- data/.github/workflows/gempush.yml +28 -0
- data/.gitignore +2 -1
- data/.travis.yml +4 -8
- data/CHANGELOG +96 -0
- data/LICENSE +201 -22
- data/README.md +22 -3
- data/input_sanitizer.gemspec +10 -4
- data/lib/input_sanitizer.rb +5 -2
- data/lib/input_sanitizer/errors.rb +142 -0
- data/lib/input_sanitizer/extended_converters.rb +5 -52
- data/lib/input_sanitizer/extended_converters/comma_joined_integers_converter.rb +15 -0
- data/lib/input_sanitizer/extended_converters/comma_joined_strings_converter.rb +15 -0
- data/lib/input_sanitizer/extended_converters/positive_integer_converter.rb +12 -0
- data/lib/input_sanitizer/extended_converters/specific_values_converter.rb +19 -0
- data/lib/input_sanitizer/restricted_hash.rb +49 -8
- data/lib/input_sanitizer/v1.rb +22 -0
- data/lib/input_sanitizer/v1/clean_field.rb +38 -0
- data/lib/input_sanitizer/{default_converters.rb → v1/default_converters.rb} +30 -13
- data/lib/input_sanitizer/v1/sanitizer.rb +166 -0
- data/lib/input_sanitizer/v2.rb +13 -0
- data/lib/input_sanitizer/v2/clean_field.rb +36 -0
- data/lib/input_sanitizer/v2/clean_payload_collection_field.rb +41 -0
- data/lib/input_sanitizer/v2/clean_query_collection_field.rb +40 -0
- data/lib/input_sanitizer/v2/error_collection.rb +49 -0
- data/lib/input_sanitizer/v2/nested_sanitizer_factory.rb +19 -0
- data/lib/input_sanitizer/v2/payload_sanitizer.rb +130 -0
- data/lib/input_sanitizer/v2/payload_transform.rb +42 -0
- data/lib/input_sanitizer/v2/query_sanitizer.rb +33 -0
- data/lib/input_sanitizer/v2/types.rb +213 -0
- data/lib/input_sanitizer/version.rb +1 -1
- data/spec/extended_converters/comma_joined_integers_converter_spec.rb +18 -0
- data/spec/extended_converters/comma_joined_strings_converter_spec.rb +18 -0
- data/spec/extended_converters/positive_integer_converter_spec.rb +18 -0
- data/spec/extended_converters/specific_values_converter_spec.rb +27 -0
- data/spec/restricted_hash_spec.rb +37 -7
- data/spec/sanitizer_spec.rb +129 -26
- data/spec/spec_helper.rb +17 -2
- data/spec/v1/default_converters_spec.rb +141 -0
- data/spec/v2/converters_spec.rb +174 -0
- data/spec/v2/payload_sanitizer_spec.rb +460 -0
- data/spec/v2/payload_transform_spec.rb +98 -0
- data/spec/v2/query_sanitizer_spec.rb +300 -0
- data/v2.md +52 -0
- metadata +105 -40
- data/lib/input_sanitizer/sanitizer.rb +0 -152
- data/spec/default_converters_spec.rb +0 -101
- data/spec/extended_converters_spec.rb +0 -62
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
|
3
|
+
class InputSanitizer::V2::PayloadTransform
|
4
|
+
attr_reader :original_payload, :context
|
5
|
+
|
6
|
+
def self.call(original_payload, context = {})
|
7
|
+
new(original_payload, context).call
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(original_payload, context = {})
|
11
|
+
fail "#{self.class} is missing #transform method" unless respond_to?(:transform)
|
12
|
+
@original_payload, @context = original_payload, context
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
transform
|
17
|
+
payload
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def rename(from, to)
|
22
|
+
if has?(from)
|
23
|
+
data = payload.delete(from)
|
24
|
+
payload[to] = data
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def merge_in(field, options = {})
|
29
|
+
if source = payload.delete(field)
|
30
|
+
source = options[:using].call(source) if options[:using]
|
31
|
+
payload.merge!(source)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def has?(key)
|
36
|
+
payload.has_key?(key)
|
37
|
+
end
|
38
|
+
|
39
|
+
def payload
|
40
|
+
@payload ||= original_payload.with_indifferent_access
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class InputSanitizer::V2::QuerySanitizer < InputSanitizer::V2::PayloadSanitizer
|
2
|
+
def self.converters
|
3
|
+
{
|
4
|
+
:integer => InputSanitizer::V2::Types::CoercingIntegerCheck.new,
|
5
|
+
:float => InputSanitizer::V2::Types::CoercingFloatCheck.new,
|
6
|
+
:string => InputSanitizer::V2::Types::StringCheck.new,
|
7
|
+
:boolean => InputSanitizer::V2::Types::CoercingBooleanCheck.new,
|
8
|
+
:datetime => InputSanitizer::V2::Types::DatetimeCheck.new,
|
9
|
+
:date => InputSanitizer::V2::Types::DatetimeCheck.new(:check_date => true),
|
10
|
+
:url => InputSanitizer::V2::Types::URLCheck.new,
|
11
|
+
}
|
12
|
+
end
|
13
|
+
initialize_types_dsl
|
14
|
+
|
15
|
+
def self.sort_by(allowed_values, options = {})
|
16
|
+
set_keys_to_converter([:sort_by, { :allow => allowed_values }.merge(options)], InputSanitizer::V2::Types::SortByCheck.new)
|
17
|
+
end
|
18
|
+
|
19
|
+
# allow underscore cache buster by default
|
20
|
+
string :_
|
21
|
+
|
22
|
+
private
|
23
|
+
def perform_clean
|
24
|
+
super
|
25
|
+
@errors.each do |error|
|
26
|
+
error.field = error.field[1..-1] if error.field.start_with?('/')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def sanitizer_type
|
31
|
+
:query
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
|
3
|
+
module InputSanitizer::V2::Types
|
4
|
+
class IntegerCheck
|
5
|
+
def call(value, options = {})
|
6
|
+
if value == nil && (options[:allow_nil] == false || options[:allow_blank] == false || options[:required] == true)
|
7
|
+
raise InputSanitizer::BlankValueError
|
8
|
+
elsif value == nil
|
9
|
+
value
|
10
|
+
else
|
11
|
+
Integer(value).tap do |integer|
|
12
|
+
raise InputSanitizer::TypeMismatchError.new(value, :integer) unless integer == value
|
13
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:minimum] && integer < options[:minimum]
|
14
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:maximum] && integer > options[:maximum]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
rescue ArgumentError, TypeError
|
18
|
+
raise InputSanitizer::TypeMismatchError.new(value, :integer)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class CoercingIntegerCheck
|
23
|
+
def call(value, options = {})
|
24
|
+
if value == nil || value == 'null'
|
25
|
+
if options[:allow_nil] == false || options[:allow_blank] == false || options[:required] == true
|
26
|
+
raise InputSanitizer::BlankValueError
|
27
|
+
else
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
else
|
31
|
+
Integer(value).tap do |integer|
|
32
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:minimum] && integer < options[:minimum]
|
33
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:maximum] && integer > options[:maximum]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
rescue ArgumentError
|
37
|
+
raise InputSanitizer::TypeMismatchError.new(value, :integer)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class FloatCheck
|
42
|
+
def call(value, options = {})
|
43
|
+
if value == nil && (options[:allow_nil] == false || options[:allow_blank] == false || options[:required] == true)
|
44
|
+
raise InputSanitizer::BlankValueError
|
45
|
+
elsif value == nil
|
46
|
+
value
|
47
|
+
else
|
48
|
+
Float(value).tap do |float|
|
49
|
+
raise InputSanitizer::TypeMismatchError.new(value, :float) unless float == value
|
50
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:minimum] && float < options[:minimum]
|
51
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:maximum] && float > options[:maximum]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
rescue ArgumentError, TypeError
|
55
|
+
raise InputSanitizer::TypeMismatchError.new(value, :float)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class CoercingFloatCheck
|
60
|
+
def call(value, options = {})
|
61
|
+
if value == nil || value == 'null'
|
62
|
+
if options[:allow_nil] == false || options[:allow_blank] == false || options[:required] == true
|
63
|
+
raise InputSanitizer::BlankValueError
|
64
|
+
else
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
else
|
68
|
+
Float(value).tap do |float|
|
69
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:minimum] && float < options[:minimum]
|
70
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:maximum] && float > options[:maximum]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
rescue ArgumentError
|
74
|
+
raise InputSanitizer::TypeMismatchError.new(value, :float)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class StringCheck
|
79
|
+
def call(value, options = {})
|
80
|
+
if options[:allow] && !options[:allow].include?(value)
|
81
|
+
raise InputSanitizer::ValueNotAllowedError.new(value)
|
82
|
+
elsif value.blank? && (options[:allow_blank] == false || options[:required] == true)
|
83
|
+
raise InputSanitizer::BlankValueError
|
84
|
+
elsif options[:regexp] && options[:regexp].match(value).nil?
|
85
|
+
raise InputSanitizer::RegexpMismatchError.new
|
86
|
+
elsif value == nil && options[:allow_nil] == false
|
87
|
+
raise InputSanitizer::BlankValueError
|
88
|
+
elsif value.blank?
|
89
|
+
value
|
90
|
+
else
|
91
|
+
value.to_s.tap do |string|
|
92
|
+
raise InputSanitizer::TypeMismatchError.new(value, :string) unless string == value
|
93
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:minimum] && string.length < options[:minimum]
|
94
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:maximum] && string.length > options[:maximum]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class BooleanCheck
|
101
|
+
def call(value, options = {})
|
102
|
+
if value == nil
|
103
|
+
raise InputSanitizer::BlankValueError
|
104
|
+
elsif [true, false].include?(value)
|
105
|
+
value
|
106
|
+
else
|
107
|
+
raise InputSanitizer::TypeMismatchError.new(value, :boolean)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class CoercingBooleanCheck
|
113
|
+
def call(value, options = {})
|
114
|
+
if [true, 'true'].include?(value)
|
115
|
+
true
|
116
|
+
elsif [false, 'false'].include?(value)
|
117
|
+
false
|
118
|
+
else
|
119
|
+
raise InputSanitizer::TypeMismatchError.new(value, :boolean)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class DatetimeCheck
|
125
|
+
def initialize(options = {})
|
126
|
+
@check_date = options && options[:check_date]
|
127
|
+
@klass = @check_date ? Date : DateTime
|
128
|
+
end
|
129
|
+
|
130
|
+
def call(value, options = {})
|
131
|
+
raise InputSanitizer::TypeMismatchError.new(value, @check_date ? :date : :datetime) unless value == nil || value.is_a?(String)
|
132
|
+
|
133
|
+
if value.blank? && (options[:allow_blank] == false || options[:required] == true)
|
134
|
+
raise InputSanitizer::BlankValueError
|
135
|
+
elsif value == nil && options[:allow_nil] == false
|
136
|
+
raise InputSanitizer::BlankValueError
|
137
|
+
elsif value.blank?
|
138
|
+
value
|
139
|
+
else
|
140
|
+
@klass.parse(value).tap do |datetime|
|
141
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:minimum] && datetime < options[:minimum]
|
142
|
+
raise InputSanitizer::ValueError.new(value, options[:minimum], options[:maximum]) if options[:maximum] && datetime > options[:maximum]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
rescue ArgumentError, TypeError
|
146
|
+
raise InputSanitizer::TypeMismatchError.new(value, @check_date ? :date : :datetime)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class URLCheck
|
151
|
+
def call(value, options = {})
|
152
|
+
if value.blank? && (options[:allow_blank] == false || options[:required] == true)
|
153
|
+
raise InputSanitizer::BlankValueError
|
154
|
+
elsif value == nil && options[:allow_nil] == false
|
155
|
+
raise InputSanitizer::BlankValueError
|
156
|
+
elsif value.blank?
|
157
|
+
value
|
158
|
+
else
|
159
|
+
unless /\A#{URI.regexp(%w(http https)).to_s}\z/.match(value)
|
160
|
+
raise InputSanitizer::TypeMismatchError.new(value, :url)
|
161
|
+
end
|
162
|
+
value
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class SortByCheck
|
168
|
+
def call(value, options = {})
|
169
|
+
check_options!(options)
|
170
|
+
|
171
|
+
key, direction = split(value)
|
172
|
+
direction = 'asc' if direction.blank?
|
173
|
+
|
174
|
+
# special case when fallback takes care of separator sanitization e.g. custom fields
|
175
|
+
if options[:fallback] && !allowed_directions.include?(direction)
|
176
|
+
direction = 'asc'
|
177
|
+
key = value
|
178
|
+
end
|
179
|
+
|
180
|
+
unless valid?(key, direction, options)
|
181
|
+
raise InputSanitizer::ValueNotAllowedError.new(value)
|
182
|
+
end
|
183
|
+
|
184
|
+
[key, direction]
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
def valid?(key, direction, options)
|
189
|
+
allowed_keys = options[:allow]
|
190
|
+
fallback = options[:fallback]
|
191
|
+
|
192
|
+
allowed_directions.include?(direction) &&
|
193
|
+
((allowed_keys && allowed_keys.include?(key)) ||
|
194
|
+
(fallback && fallback.call(key, direction, options)))
|
195
|
+
end
|
196
|
+
|
197
|
+
def split(value)
|
198
|
+
head, _, tail = value.to_s.rpartition(':')
|
199
|
+
head.empty? ? [tail, head] : [head, tail]
|
200
|
+
end
|
201
|
+
|
202
|
+
def check_options!(options)
|
203
|
+
fallback = options[:fallback]
|
204
|
+
if fallback && !fallback.respond_to?(:call)
|
205
|
+
raise ArgumentError, ":fallback option must respond to method :call (proc, lambda etc)"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def allowed_directions
|
210
|
+
['asc', 'desc']
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'input_sanitizer/extended_converters/comma_joined_integers_converter'
|
3
|
+
|
4
|
+
describe InputSanitizer::CommaJoinedIntegersConverter do
|
5
|
+
let(:converter) { described_class.new }
|
6
|
+
|
7
|
+
it "parses to array of ids" do
|
8
|
+
converter.call("1,2,3,5").should eq([1, 2, 3, 5])
|
9
|
+
end
|
10
|
+
|
11
|
+
it "converts to array if given an integer" do
|
12
|
+
converter.call(7).should eq([7])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raises on invalid character" do
|
16
|
+
lambda { converter.call(":") }.should raise_error(InputSanitizer::ConversionError)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'input_sanitizer/extended_converters/comma_joined_strings_converter'
|
3
|
+
|
4
|
+
describe InputSanitizer::CommaJoinedStringsConverter do
|
5
|
+
let(:converter) { described_class.new }
|
6
|
+
|
7
|
+
it "parses to array of ids" do
|
8
|
+
converter.call("input,Sanitizer,ROCKS").should eq(["input", "Sanitizer", "ROCKS"])
|
9
|
+
end
|
10
|
+
|
11
|
+
it "allows underscores" do
|
12
|
+
converter.call("input_sanitizer,rocks").should eq(["input_sanitizer", "rocks"])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raises on invalid character" do
|
16
|
+
lambda { converter.call(":") }.should raise_error(InputSanitizer::ConversionError)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'input_sanitizer/extended_converters/positive_integer_converter'
|
3
|
+
|
4
|
+
describe InputSanitizer::PositiveIntegerConverter do
|
5
|
+
let(:converter) { described_class.new }
|
6
|
+
|
7
|
+
it "casts string to integer" do
|
8
|
+
converter.call("3").should == 3
|
9
|
+
end
|
10
|
+
|
11
|
+
it "raises error if integer less than zero" do
|
12
|
+
lambda { converter.call("-3") }.should raise_error(InputSanitizer::ConversionError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raises error if integer equals zero" do
|
16
|
+
lambda { converter.call("0") }.should raise_error(InputSanitizer::ConversionError)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'input_sanitizer/extended_converters/specific_values_converter'
|
3
|
+
|
4
|
+
describe InputSanitizer::SpecificValuesConverter do
|
5
|
+
let(:converter) { described_class.new(values) }
|
6
|
+
let(:values) { [:a, :b] }
|
7
|
+
|
8
|
+
it "converts valid value to symbol" do
|
9
|
+
converter.call("b").should eq(:b)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "raises on invalid value" do
|
13
|
+
lambda { converter.call("c") }.should raise_error(InputSanitizer::ConversionError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "raises on nil value" do
|
17
|
+
lambda { converter.call(nil) }.should raise_error(InputSanitizer::ConversionError)
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when specific values are strings" do
|
21
|
+
let(:values) { ["a", "b"] }
|
22
|
+
|
23
|
+
it "keeps given value as string" do
|
24
|
+
converter.call("a").should eq("a")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,19 +1,49 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe InputSanitizer::RestrictedHash do
|
4
|
-
let(:hash) { InputSanitizer::RestrictedHash.new([:a
|
5
|
-
subject { hash }
|
4
|
+
let(:hash) { InputSanitizer::RestrictedHash.new([:a]) }
|
6
5
|
|
7
|
-
it
|
8
|
-
lambda{hash[:
|
6
|
+
it 'does not allow bad keys' do
|
7
|
+
lambda{ hash[:b] }.should raise_error(InputSanitizer::KeyNotAllowedError)
|
9
8
|
end
|
10
9
|
|
11
|
-
it
|
10
|
+
it 'does allow correct keys' do
|
12
11
|
hash[:a].should be_nil
|
13
12
|
end
|
14
13
|
|
15
|
-
it
|
14
|
+
it 'returns value for correct key' do
|
16
15
|
hash[:a] = 'stuff'
|
17
16
|
hash[:a].should == 'stuff'
|
18
17
|
end
|
18
|
+
|
19
|
+
it 'allows to set value for a new key' do
|
20
|
+
hash[:b] = 'other stuff'
|
21
|
+
hash[:b].should == 'other stuff'
|
22
|
+
hash.key_allowed?(:b).should be_truthy
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'adds new allowed keys after `transform_keys` is done' do
|
26
|
+
hash[:a] = 'stuff'
|
27
|
+
hash.transform_keys! { |key| key.to_s }
|
28
|
+
hash['a'].should == 'stuff'
|
29
|
+
hash.key_allowed?('a').should be_truthy
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'removes previous allowed keys after `transform_keys` is done' do
|
33
|
+
hash[:a] = 'stuff'
|
34
|
+
hash.transform_keys! { |key| key.to_s }
|
35
|
+
lambda{ hash[:a] }.should raise_error(InputSanitizer::KeyNotAllowedError)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'updates allowed keys on merge!' do
|
39
|
+
merge_hash = { merged: '1' }
|
40
|
+
hash.merge!(merge_hash)
|
41
|
+
hash.key_allowed?(:merged).should be_truthy
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'updates allowed keys on merge' do
|
45
|
+
merge_hash = { merged: '1' }
|
46
|
+
new_hash = hash.merge(some_key: merge_hash)
|
47
|
+
new_hash.key_allowed?(:some_key).should be_truthy
|
48
|
+
end
|
19
49
|
end
|
data/spec/sanitizer_spec.rb
CHANGED
@@ -1,11 +1,22 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
class NestedSanitizer < InputSanitizer::Sanitizer
|
4
|
+
integer :foo
|
5
|
+
end
|
6
|
+
|
3
7
|
class BasicSanitizer < InputSanitizer::Sanitizer
|
4
8
|
string :x, :y, :z
|
9
|
+
integer :namespaced, :namespace => :value
|
10
|
+
integer :array, :collection => true
|
11
|
+
integer :namespaced_array, :collection => true, :namespace => :value
|
5
12
|
integer :num
|
6
13
|
date :birthday
|
7
14
|
time :updated_at
|
8
15
|
custom :cust1, :cust2, :converter => lambda { |v| v.reverse }
|
16
|
+
nested :stuff, :sanitizer => NestedSanitizer, :collection => true, :namespace => :nested
|
17
|
+
custom :custom3, :provide => :num, :converter => lambda { |v, num|
|
18
|
+
num == 1 ? v.reverse : v
|
19
|
+
}
|
9
20
|
end
|
10
21
|
|
11
22
|
class BrokenCustomSanitizer < InputSanitizer::Sanitizer
|
@@ -28,12 +39,17 @@ class RequiredCustom < BasicSanitizer
|
|
28
39
|
custom :c1, :required => true, :converter => lambda { |v| v }
|
29
40
|
end
|
30
41
|
|
42
|
+
class DefaultParameters < BasicSanitizer
|
43
|
+
integer :funky_number, :default => 5
|
44
|
+
custom :fixed_stuff, :converter => lambda {|v| v }, :default => "default string"
|
45
|
+
end
|
46
|
+
|
31
47
|
describe InputSanitizer::Sanitizer do
|
32
48
|
let(:sanitizer) { BasicSanitizer.new(@params) }
|
33
49
|
|
34
50
|
describe ".clean" do
|
35
51
|
it "returns cleaned data" do
|
36
|
-
clean_data =
|
52
|
+
clean_data = double
|
37
53
|
BasicSanitizer.any_instance.should_receive(:cleaned).and_return(clean_data)
|
38
54
|
BasicSanitizer.clean({}).should be(clean_data)
|
39
55
|
end
|
@@ -57,6 +73,12 @@ describe InputSanitizer::Sanitizer do
|
|
57
73
|
cleaned.should_not have_key(:d)
|
58
74
|
end
|
59
75
|
|
76
|
+
it "does not include ommited fields" do
|
77
|
+
@params = { "x" => 1, "z" => 3 }
|
78
|
+
|
79
|
+
cleaned.should_not have_key(:y)
|
80
|
+
end
|
81
|
+
|
60
82
|
it "freezes cleaned hash" do
|
61
83
|
@params = {}
|
62
84
|
|
@@ -83,6 +105,30 @@ describe InputSanitizer::Sanitizer do
|
|
83
105
|
cleaned.should_not have_key(:d)
|
84
106
|
end
|
85
107
|
|
108
|
+
it "preserves namespace" do
|
109
|
+
value = { :value => 5 }
|
110
|
+
@params = { :namespaced => value }
|
111
|
+
|
112
|
+
cleaned[:namespaced].should eq(value)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "maps values for collection fields" do
|
116
|
+
numbers = [3, 5, 6]
|
117
|
+
@params = { :array => numbers }
|
118
|
+
|
119
|
+
cleaned[:array].should eq(numbers)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "maps values for collection fields with namespace" do
|
123
|
+
numbers = [
|
124
|
+
{ :value => 2 },
|
125
|
+
{ :value => 5 }
|
126
|
+
]
|
127
|
+
@params = { :namespaced_array => numbers }
|
128
|
+
|
129
|
+
cleaned[:namespaced_array].should eq(numbers)
|
130
|
+
end
|
131
|
+
|
86
132
|
it "silently discards cast errors" do
|
87
133
|
@params = {:num => "f"}
|
88
134
|
|
@@ -95,7 +141,7 @@ describe InputSanitizer::Sanitizer do
|
|
95
141
|
|
96
142
|
cleaned.should have_key(:num)
|
97
143
|
cleaned[:num].should == 23
|
98
|
-
cleaned[:is_nice].should
|
144
|
+
cleaned[:is_nice].should eq(false)
|
99
145
|
end
|
100
146
|
|
101
147
|
it "overrides inherited fields" do
|
@@ -106,6 +152,32 @@ describe InputSanitizer::Sanitizer do
|
|
106
152
|
cleaned[:is_nice].should == 42
|
107
153
|
end
|
108
154
|
|
155
|
+
context "when sanitizer is initialized with default values" do
|
156
|
+
context "when paremeters are not overwriten" do
|
157
|
+
let(:sanitizer) { DefaultParameters.new({}) }
|
158
|
+
|
159
|
+
it "returns default value for non custom key" do
|
160
|
+
sanitizer.cleaned[:funky_number].should == 5
|
161
|
+
end
|
162
|
+
|
163
|
+
it "returns default value for custom key" do
|
164
|
+
sanitizer.cleaned[:fixed_stuff].should == "default string"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context "when parameters are overwriten" do
|
169
|
+
let(:sanitizer) { DefaultParameters.new({ :funky_number => 2, :fixed_stuff => "fixed" }) }
|
170
|
+
|
171
|
+
it "returns default value for non custom key" do
|
172
|
+
sanitizer.cleaned[:funky_number].should == 2
|
173
|
+
end
|
174
|
+
|
175
|
+
it "returns default value for custom key" do
|
176
|
+
sanitizer.cleaned[:fixed_stuff].should == "fixed"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
109
181
|
end
|
110
182
|
|
111
183
|
describe ".custom" do
|
@@ -120,9 +192,53 @@ describe InputSanitizer::Sanitizer do
|
|
120
192
|
end
|
121
193
|
|
122
194
|
it "raises an error when converter is not defined" do
|
123
|
-
|
195
|
+
lambda do
|
124
196
|
BrokenCustomSanitizer.custom(:x)
|
125
|
-
end.
|
197
|
+
end.should raise_error
|
198
|
+
end
|
199
|
+
|
200
|
+
it "provides the converter with requested value" do
|
201
|
+
@params = { :custom3 => 'three', :num => 1 }
|
202
|
+
cleaned.should have_key(:custom3)
|
203
|
+
cleaned.should have_key(:num)
|
204
|
+
cleaned[:custom3].should eq('eerht')
|
205
|
+
cleaned[:num].should eq(1)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe ".nested" do
|
210
|
+
let(:sanitizer) { BasicSanitizer.new(@params) }
|
211
|
+
let(:cleaned) { sanitizer.cleaned }
|
212
|
+
|
213
|
+
it "sanitizes nested values" do
|
214
|
+
nested = [
|
215
|
+
{ :nested => { :foo => "5" } },
|
216
|
+
{ :nested => { :foo => 8 } },
|
217
|
+
]
|
218
|
+
@params = { :stuff => nested }
|
219
|
+
|
220
|
+
expected = [
|
221
|
+
{ :nested => { :foo => 5 } },
|
222
|
+
{ :nested => { :foo => 8 } },
|
223
|
+
]
|
224
|
+
cleaned[:stuff].should eq(expected)
|
225
|
+
end
|
226
|
+
|
227
|
+
it "propagates errors" do
|
228
|
+
nested = [ { :nested => { :foo => "INVALID" } } ]
|
229
|
+
@params = { :stuff => nested }
|
230
|
+
|
231
|
+
sanitizer.should_not be_valid
|
232
|
+
end
|
233
|
+
|
234
|
+
it "returns an error when given a nil for a nested value" do
|
235
|
+
@params = { :stuff => nil }
|
236
|
+
sanitizer.should_not be_valid
|
237
|
+
end
|
238
|
+
|
239
|
+
it "returns an error when given a string for a nested value" do
|
240
|
+
@params = { :stuff => 'nope' }
|
241
|
+
sanitizer.should_not be_valid
|
126
242
|
end
|
127
243
|
end
|
128
244
|
|
@@ -131,43 +247,25 @@ describe InputSanitizer::Sanitizer do
|
|
131
247
|
|
132
248
|
it "includes :integer type" do
|
133
249
|
sanitizer.converters.should have_key(:integer)
|
134
|
-
sanitizer.converters[:integer].should be_a(InputSanitizer::IntegerConverter)
|
250
|
+
sanitizer.converters[:integer].should be_a(InputSanitizer::V1::IntegerConverter)
|
135
251
|
end
|
136
252
|
|
137
253
|
it "includes :string type" do
|
138
254
|
sanitizer.converters.should have_key(:string)
|
139
|
-
sanitizer.converters[:string].should be_a(InputSanitizer::StringConverter)
|
255
|
+
sanitizer.converters[:string].should be_a(InputSanitizer::V1::StringConverter)
|
140
256
|
end
|
141
257
|
|
142
258
|
it "includes :date type" do
|
143
259
|
sanitizer.converters.should have_key(:date)
|
144
|
-
sanitizer.converters[:date].should be_a(InputSanitizer::DateConverter)
|
260
|
+
sanitizer.converters[:date].should be_a(InputSanitizer::V1::DateConverter)
|
145
261
|
end
|
146
262
|
|
147
263
|
it "includes :boolean type" do
|
148
264
|
sanitizer.converters.should have_key(:boolean)
|
149
|
-
sanitizer.converters[:boolean].should be_a(InputSanitizer::BooleanConverter)
|
265
|
+
sanitizer.converters[:boolean].should be_a(InputSanitizer::V1::BooleanConverter)
|
150
266
|
end
|
151
267
|
end
|
152
268
|
|
153
|
-
describe '.extract_options' do
|
154
|
-
|
155
|
-
it "extracts hash from array if is last" do
|
156
|
-
options = { :a => 1}
|
157
|
-
array = [1,2, options]
|
158
|
-
BasicSanitizer.extract_options(array).should == options
|
159
|
-
array.should == [1,2, options]
|
160
|
-
end
|
161
|
-
|
162
|
-
it "does not extract the last element if not a hash and returns default empty hash" do
|
163
|
-
array = [1,2]
|
164
|
-
BasicSanitizer.extract_options(array).should_not == 2
|
165
|
-
BasicSanitizer.extract_options(array).should == {}
|
166
|
-
array.should == [1,2]
|
167
|
-
end
|
168
|
-
|
169
|
-
end
|
170
|
-
|
171
269
|
describe '.extract_options!' do
|
172
270
|
|
173
271
|
it "extracts hash from array if is last" do
|
@@ -234,4 +332,9 @@ describe InputSanitizer::Sanitizer do
|
|
234
332
|
error[:field].should == :c1
|
235
333
|
end
|
236
334
|
end
|
335
|
+
|
336
|
+
it "is valid when given extra params" do
|
337
|
+
@params = { :extra => 'test' }
|
338
|
+
sanitizer.should be_valid
|
339
|
+
end
|
237
340
|
end
|