input_sanitizer 0.1.9 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/gempush.yml +28 -0
  3. data/.gitignore +2 -1
  4. data/.travis.yml +4 -8
  5. data/CHANGELOG +96 -0
  6. data/LICENSE +201 -22
  7. data/README.md +22 -3
  8. data/input_sanitizer.gemspec +10 -4
  9. data/lib/input_sanitizer.rb +5 -2
  10. data/lib/input_sanitizer/errors.rb +142 -0
  11. data/lib/input_sanitizer/extended_converters.rb +5 -52
  12. data/lib/input_sanitizer/extended_converters/comma_joined_integers_converter.rb +15 -0
  13. data/lib/input_sanitizer/extended_converters/comma_joined_strings_converter.rb +15 -0
  14. data/lib/input_sanitizer/extended_converters/positive_integer_converter.rb +12 -0
  15. data/lib/input_sanitizer/extended_converters/specific_values_converter.rb +19 -0
  16. data/lib/input_sanitizer/restricted_hash.rb +49 -8
  17. data/lib/input_sanitizer/v1.rb +22 -0
  18. data/lib/input_sanitizer/v1/clean_field.rb +38 -0
  19. data/lib/input_sanitizer/{default_converters.rb → v1/default_converters.rb} +30 -13
  20. data/lib/input_sanitizer/v1/sanitizer.rb +166 -0
  21. data/lib/input_sanitizer/v2.rb +13 -0
  22. data/lib/input_sanitizer/v2/clean_field.rb +36 -0
  23. data/lib/input_sanitizer/v2/clean_payload_collection_field.rb +41 -0
  24. data/lib/input_sanitizer/v2/clean_query_collection_field.rb +40 -0
  25. data/lib/input_sanitizer/v2/error_collection.rb +49 -0
  26. data/lib/input_sanitizer/v2/nested_sanitizer_factory.rb +19 -0
  27. data/lib/input_sanitizer/v2/payload_sanitizer.rb +130 -0
  28. data/lib/input_sanitizer/v2/payload_transform.rb +42 -0
  29. data/lib/input_sanitizer/v2/query_sanitizer.rb +33 -0
  30. data/lib/input_sanitizer/v2/types.rb +213 -0
  31. data/lib/input_sanitizer/version.rb +1 -1
  32. data/spec/extended_converters/comma_joined_integers_converter_spec.rb +18 -0
  33. data/spec/extended_converters/comma_joined_strings_converter_spec.rb +18 -0
  34. data/spec/extended_converters/positive_integer_converter_spec.rb +18 -0
  35. data/spec/extended_converters/specific_values_converter_spec.rb +27 -0
  36. data/spec/restricted_hash_spec.rb +37 -7
  37. data/spec/sanitizer_spec.rb +129 -26
  38. data/spec/spec_helper.rb +17 -2
  39. data/spec/v1/default_converters_spec.rb +141 -0
  40. data/spec/v2/converters_spec.rb +174 -0
  41. data/spec/v2/payload_sanitizer_spec.rb +460 -0
  42. data/spec/v2/payload_transform_spec.rb +98 -0
  43. data/spec/v2/query_sanitizer_spec.rb +300 -0
  44. data/v2.md +52 -0
  45. metadata +105 -40
  46. data/lib/input_sanitizer/sanitizer.rb +0 -152
  47. data/spec/default_converters_spec.rb +0 -101
  48. 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
@@ -1,3 +1,3 @@
1
1
  module InputSanitizer
2
- VERSION = "0.1.9"
2
+ VERSION = "0.4.0"
3
3
  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 "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe InputSanitizer::RestrictedHash do
4
- let(:hash) { InputSanitizer::RestrictedHash.new([:a, :b]) }
5
- subject { hash }
4
+ let(:hash) { InputSanitizer::RestrictedHash.new([:a]) }
6
5
 
7
- it "does not allow bad keys" do
8
- lambda{hash[:c]}.should raise_error(InputSanitizer::KeyNotAllowedError)
6
+ it 'does not allow bad keys' do
7
+ lambda{ hash[:b] }.should raise_error(InputSanitizer::KeyNotAllowedError)
9
8
  end
10
9
 
11
- it "does allow correct keys" do
10
+ it 'does allow correct keys' do
12
11
  hash[:a].should be_nil
13
12
  end
14
13
 
15
- it "returns value for correct key" do
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
@@ -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 = mock()
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 be_false
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
- expect do
195
+ lambda do
124
196
  BrokenCustomSanitizer.custom(:x)
125
- end.to raise_error
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