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,166 @@
|
|
1
|
+
class InputSanitizer::V1::Sanitizer
|
2
|
+
def initialize(data)
|
3
|
+
@data = symbolize_keys(data)
|
4
|
+
@performed = false
|
5
|
+
@errors = []
|
6
|
+
@cleaned = InputSanitizer::RestrictedHash.new(self.class.fields.keys)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.clean(data)
|
10
|
+
new(data).cleaned
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](field)
|
14
|
+
cleaned[field]
|
15
|
+
end
|
16
|
+
|
17
|
+
def cleaned
|
18
|
+
return @cleaned if @performed
|
19
|
+
|
20
|
+
perform_clean
|
21
|
+
|
22
|
+
@performed = true
|
23
|
+
@cleaned.freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
def valid?
|
27
|
+
cleaned
|
28
|
+
@errors.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def errors
|
32
|
+
cleaned
|
33
|
+
@errors
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.converters
|
37
|
+
{
|
38
|
+
:integer => InputSanitizer::V1::IntegerConverter.new,
|
39
|
+
:string => InputSanitizer::V1::StringConverter.new,
|
40
|
+
:date => InputSanitizer::V1::DateConverter.new,
|
41
|
+
:time => InputSanitizer::V1::TimeConverter.new,
|
42
|
+
:boolean => InputSanitizer::V1::BooleanConverter.new,
|
43
|
+
:integer_or_blank => InputSanitizer::V1::IntegerConverter.new.extend(InputSanitizer::V1::AllowNil),
|
44
|
+
:string_or_blank => InputSanitizer::V1::StringConverter.new.extend(InputSanitizer::V1::AllowNil),
|
45
|
+
:date_or_blank => InputSanitizer::V1::DateConverter.new.extend(InputSanitizer::V1::AllowNil),
|
46
|
+
:time_or_blank => InputSanitizer::V1::TimeConverter.new.extend(InputSanitizer::V1::AllowNil),
|
47
|
+
:boolean_or_blank => InputSanitizer::V1::BooleanConverter.new.extend(InputSanitizer::V1::AllowNil),
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.inherited(subclass)
|
52
|
+
subclass.fields = self.fields.dup
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.initialize_types_dsl
|
56
|
+
converters.keys.each do |name|
|
57
|
+
class_eval <<-END
|
58
|
+
def self.#{name}(*keys)
|
59
|
+
set_keys_to_converter(keys, :#{name})
|
60
|
+
end
|
61
|
+
END
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
initialize_types_dsl
|
66
|
+
|
67
|
+
def self.custom(*keys)
|
68
|
+
options = keys.pop
|
69
|
+
converter = options.delete(:converter)
|
70
|
+
keys.push(options)
|
71
|
+
raise "You did not define a converter for a custom type" if converter == nil
|
72
|
+
self.set_keys_to_converter(keys, converter)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.nested(*keys)
|
76
|
+
options = keys.pop
|
77
|
+
sanitizer = options.delete(:sanitizer)
|
78
|
+
keys.push(options)
|
79
|
+
raise "You did not define a sanitizer for nested value" if sanitizer == nil
|
80
|
+
converter = lambda { |value|
|
81
|
+
instance = sanitizer.new(value)
|
82
|
+
raise InputSanitizer::ConversionError.new(instance.errors) unless instance.valid?
|
83
|
+
instance.cleaned
|
84
|
+
}
|
85
|
+
|
86
|
+
keys << {} unless keys.last.is_a?(Hash)
|
87
|
+
keys.last[:nested] = true
|
88
|
+
|
89
|
+
self.set_keys_to_converter(keys, converter)
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
def self.fields
|
94
|
+
@fields ||= {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.fields=(new_fields)
|
98
|
+
@fields = new_fields
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
def self.extract_options!(array)
|
103
|
+
array.last.is_a?(Hash) ? array.pop : {}
|
104
|
+
end
|
105
|
+
|
106
|
+
def perform_clean
|
107
|
+
self.class.fields.each { |field, hash| clean_field(field, hash) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def clean_field(field, hash)
|
111
|
+
if hash[:options][:nested] && @data.has_key?(field)
|
112
|
+
if hash[:options][:collection]
|
113
|
+
raise InputSanitizer::ConversionError.new("expected an array") unless @data[field].is_a?(Array)
|
114
|
+
else
|
115
|
+
raise InputSanitizer::ConversionError.new("expected a hash") unless @data[field].is_a?(Hash)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
@cleaned[field] = InputSanitizer::V1::CleanField.call(hash[:options].merge({
|
120
|
+
:has_key => @data.has_key?(field),
|
121
|
+
:data => @data[field],
|
122
|
+
:converter => hash[:converter],
|
123
|
+
:provide => @data[hash[:options][:provide]],
|
124
|
+
}))
|
125
|
+
rescue InputSanitizer::ConversionError => error
|
126
|
+
add_error(field, :invalid_value, @data[field], error.message)
|
127
|
+
rescue InputSanitizer::ValueMissingError => error
|
128
|
+
add_error(field, :missing, nil, nil)
|
129
|
+
rescue InputSanitizer::OptionalValueOmitted
|
130
|
+
end
|
131
|
+
|
132
|
+
def add_error(field, error_type, value, description = nil)
|
133
|
+
@errors << {
|
134
|
+
:field => field,
|
135
|
+
:type => error_type,
|
136
|
+
:value => value,
|
137
|
+
:description => description
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
def symbolize_keys(data)
|
142
|
+
symbolized_hash = {}
|
143
|
+
|
144
|
+
data.each do |key, value|
|
145
|
+
symbolized_hash[key.to_sym] = value
|
146
|
+
end
|
147
|
+
|
148
|
+
symbolized_hash
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.set_keys_to_converter(keys, converter_or_type)
|
152
|
+
options = extract_options!(keys)
|
153
|
+
converter = if converter_or_type.is_a?(Symbol)
|
154
|
+
converters[converter_or_type]
|
155
|
+
else
|
156
|
+
converter_or_type
|
157
|
+
end
|
158
|
+
|
159
|
+
keys.each do |key|
|
160
|
+
fields[key] = {
|
161
|
+
:converter => converter,
|
162
|
+
:options => options
|
163
|
+
}
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module InputSanitizer::V2
|
2
|
+
end
|
3
|
+
|
4
|
+
dir = File.dirname(__FILE__)
|
5
|
+
require File.join(dir, 'v2/types')
|
6
|
+
require File.join(dir, 'v2/clean_payload_collection_field')
|
7
|
+
require File.join(dir, 'v2/clean_query_collection_field')
|
8
|
+
require File.join(dir, 'v2/clean_field')
|
9
|
+
require File.join(dir, 'v2/nested_sanitizer_factory')
|
10
|
+
require File.join(dir, 'v2/error_collection')
|
11
|
+
require File.join(dir, 'v2/payload_sanitizer')
|
12
|
+
require File.join(dir, 'v2/query_sanitizer')
|
13
|
+
require File.join(dir, 'v2/payload_transform')
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class InputSanitizer::V2::CleanField < MethodStruct.new(:data, :has_key, :default, :collection, :type, :converter, :options)
|
2
|
+
def call
|
3
|
+
if has_key
|
4
|
+
convert
|
5
|
+
elsif default
|
6
|
+
converter.call(default, options)
|
7
|
+
elsif options[:required]
|
8
|
+
raise InputSanitizer::ValueMissingError
|
9
|
+
else
|
10
|
+
raise InputSanitizer::OptionalValueOmitted
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def convert
|
16
|
+
if collection
|
17
|
+
collection_clean.call(
|
18
|
+
:data => data,
|
19
|
+
:collection => collection,
|
20
|
+
:converter => converter,
|
21
|
+
:options => options
|
22
|
+
)
|
23
|
+
else
|
24
|
+
converter.call(data, options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def collection_clean
|
29
|
+
case type
|
30
|
+
when :payload
|
31
|
+
InputSanitizer::V2::CleanPayloadCollectionField
|
32
|
+
when :query
|
33
|
+
InputSanitizer::V2::CleanQueryCollectionField
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class InputSanitizer::V2::CleanPayloadCollectionField < MethodStruct.new(:data, :converter, :collection, :options)
|
2
|
+
def call
|
3
|
+
return nil if options[:allow_nil] && data == nil
|
4
|
+
|
5
|
+
validate_type
|
6
|
+
validate_size
|
7
|
+
|
8
|
+
result, errors = [], {}
|
9
|
+
|
10
|
+
data.each_with_index do |value, idx|
|
11
|
+
begin
|
12
|
+
result << converter.call(value, options)
|
13
|
+
rescue InputSanitizer::ValidationError => e
|
14
|
+
errors[idx] = e
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
if errors.any?
|
19
|
+
raise InputSanitizer::CollectionError.new(errors)
|
20
|
+
else
|
21
|
+
result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def validate_type
|
27
|
+
unless data.respond_to?(:to_ary)
|
28
|
+
raise InputSanitizer::TypeMismatchError.new(data, :array)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_size
|
33
|
+
if collection.respond_to?(:fetch)
|
34
|
+
if collection[:minimum] && data.length < collection[:minimum]
|
35
|
+
raise InputSanitizer::CollectionLengthError.new(data.length, collection[:minimum], collection[:maximum])
|
36
|
+
elsif collection[:maximum] && data.length > collection[:maximum]
|
37
|
+
raise InputSanitizer::CollectionLengthError.new(data.length, collection[:minimum], collection[:maximum])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class InputSanitizer::V2::CleanQueryCollectionField < MethodStruct.new(:data, :converter, :collection, :options)
|
2
|
+
def call
|
3
|
+
validate_type
|
4
|
+
validate_size
|
5
|
+
|
6
|
+
result, errors = [], {}
|
7
|
+
items.each_with_index do |value, idx|
|
8
|
+
begin
|
9
|
+
result << converter.call(value, options)
|
10
|
+
rescue InputSanitizer::ValidationError => e
|
11
|
+
errors[idx] = e
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if errors.any?
|
16
|
+
raise InputSanitizer::CollectionError.new(errors)
|
17
|
+
else
|
18
|
+
result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def items
|
24
|
+
@items ||= data.to_s.split(',')
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate_type
|
28
|
+
InputSanitizer::V2::Types::StringCheck.new.call(data)
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate_size
|
32
|
+
if collection.respond_to?(:fetch)
|
33
|
+
if collection[:minimum] && items.length < collection[:minimum]
|
34
|
+
raise InputSanitizer::CollectionLengthError.new(items.length, collection[:minimum], collection[:maximum])
|
35
|
+
elsif collection[:maximum] && items.length > collection[:maximum]
|
36
|
+
raise InputSanitizer::CollectionLengthError.new(items.length, collection[:minimum], collection[:maximum])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class InputSanitizer::V2::ErrorCollection
|
2
|
+
include Enumerable
|
3
|
+
|
4
|
+
attr_reader :error_codes, :messages
|
5
|
+
|
6
|
+
def initialize(errors)
|
7
|
+
@error_codes = {}
|
8
|
+
@messages = {}
|
9
|
+
@error_details = {}
|
10
|
+
|
11
|
+
errors.each do |error|
|
12
|
+
(@error_codes[error.field] ||= []) << error.code
|
13
|
+
(@messages[error.field] ||= []) << error.message
|
14
|
+
error_value_hash = { :value => error.value }
|
15
|
+
(@error_details[error.field] ||= []) << error_value_hash
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](attribute)
|
20
|
+
@error_codes[attribute]
|
21
|
+
end
|
22
|
+
|
23
|
+
def each
|
24
|
+
@error_codes.each_key do |attribute|
|
25
|
+
self[attribute].each { |error| yield attribute, error }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def size
|
30
|
+
@error_codes.size
|
31
|
+
end
|
32
|
+
alias_method :length, :size
|
33
|
+
alias_method :count, :size
|
34
|
+
|
35
|
+
def empty?
|
36
|
+
@error_codes.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
def add(attribute, code = :invalid, options = {})
|
40
|
+
(@error_codes[attribute] ||= []) << code
|
41
|
+
(@messages[attribute] ||= []) << options.delete(:messages)
|
42
|
+
(@error_details[attribute] ||= []) << options
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_hash
|
46
|
+
messages.dup
|
47
|
+
end
|
48
|
+
alias_method :full_messages, :to_hash
|
49
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class InputSanitizer::V2::NestedSanitizerFactory
|
2
|
+
class NilAllowed
|
3
|
+
def cleaned
|
4
|
+
nil
|
5
|
+
end
|
6
|
+
|
7
|
+
def valid?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.for(nested_sanitizer_klass, value, options)
|
13
|
+
if value.nil? && options[:allow_nil] && !options[:collection]
|
14
|
+
NilAllowed.new
|
15
|
+
else
|
16
|
+
nested_sanitizer_klass.new(value, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
class InputSanitizer::V2::PayloadSanitizer < InputSanitizer::Sanitizer
|
2
|
+
attr_reader :validation_context
|
3
|
+
|
4
|
+
def initialize(data, validation_context = {})
|
5
|
+
super data
|
6
|
+
|
7
|
+
self.validation_context = validation_context || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def validation_context=(context)
|
11
|
+
raise ArgumentError, "validation_context must be a Hash" unless context && context.is_a?(Hash)
|
12
|
+
@validation_context = context
|
13
|
+
end
|
14
|
+
|
15
|
+
def error_collection
|
16
|
+
@error_collection ||= InputSanitizer::V2::ErrorCollection.new(errors)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.converters
|
20
|
+
{
|
21
|
+
:integer => InputSanitizer::V2::Types::IntegerCheck.new,
|
22
|
+
:float => InputSanitizer::V2::Types::FloatCheck.new,
|
23
|
+
:string => InputSanitizer::V2::Types::StringCheck.new,
|
24
|
+
:boolean => InputSanitizer::V2::Types::BooleanCheck.new,
|
25
|
+
:datetime => InputSanitizer::V2::Types::DatetimeCheck.new,
|
26
|
+
:date => InputSanitizer::V2::Types::DatetimeCheck.new(:check_date => true),
|
27
|
+
:url => InputSanitizer::V2::Types::URLCheck.new,
|
28
|
+
}
|
29
|
+
end
|
30
|
+
initialize_types_dsl
|
31
|
+
|
32
|
+
def self.nested(*keys)
|
33
|
+
options = keys.pop
|
34
|
+
sanitizer = options.delete(:sanitizer)
|
35
|
+
keys.push(options)
|
36
|
+
raise "You did not define a sanitizer for nested value" if sanitizer == nil
|
37
|
+
converter = lambda { |value, converter_options|
|
38
|
+
instance = InputSanitizer::V2::NestedSanitizerFactory.for(sanitizer, value, converter_options)
|
39
|
+
raise InputSanitizer::NestedError.new(instance.errors) unless instance.valid?
|
40
|
+
instance.cleaned
|
41
|
+
}
|
42
|
+
|
43
|
+
keys << {} unless keys.last.is_a?(Hash)
|
44
|
+
keys.last[:nested] = true
|
45
|
+
|
46
|
+
self.set_keys_to_converter(keys, converter)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def perform_clean
|
51
|
+
super
|
52
|
+
@data.reject { |key, _| self.class.fields.keys.include?(key) }.each { |key, _| @errors << InputSanitizer::ExtraneousParamError.new("/#{key}") }
|
53
|
+
end
|
54
|
+
|
55
|
+
def prepare_options!(options)
|
56
|
+
return options if @validation_context.empty?
|
57
|
+
context = @validation_context.dup
|
58
|
+
context_provided_values = context.delete(:provided)
|
59
|
+
|
60
|
+
intersection = options.keys & context.keys
|
61
|
+
|
62
|
+
unless intersection.empty?
|
63
|
+
message = "validation context and converter options have the same keys: #{intersection}. " \
|
64
|
+
"In order to proceed please fix the configuration. " \
|
65
|
+
"In the meantime aborting ..."
|
66
|
+
raise RuntimeError, message
|
67
|
+
end
|
68
|
+
|
69
|
+
if context_provided_values
|
70
|
+
options[:provided] ||= {}
|
71
|
+
options[:provided] = options[:provided].merge(context_provided_values)
|
72
|
+
end
|
73
|
+
|
74
|
+
options.merge(context)
|
75
|
+
end
|
76
|
+
|
77
|
+
def clean_field(field, hash)
|
78
|
+
options = hash[:options].clone
|
79
|
+
collection = options.delete(:collection)
|
80
|
+
default = options.delete(:default)
|
81
|
+
has_key = @data.has_key?(field)
|
82
|
+
value = @data[field]
|
83
|
+
is_nested = options.delete(:nested)
|
84
|
+
|
85
|
+
provide = options.delete(:provide)
|
86
|
+
provided = Array(provide).inject({}) { |memo, value| memo[value] = @data[value]; memo }
|
87
|
+
options[:provided] = provided
|
88
|
+
|
89
|
+
if is_nested && has_key
|
90
|
+
if collection
|
91
|
+
raise InputSanitizer::TypeMismatchError.new(value, "array") unless value.is_a?(Array)
|
92
|
+
elsif !options[:allow_nil] || (options[:allow_nil] && !value.nil?)
|
93
|
+
raise InputSanitizer::TypeMismatchError.new(value, "hash") unless value.is_a?(Hash)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
@cleaned[field] = InputSanitizer::V2::CleanField.call(
|
98
|
+
:data => value,
|
99
|
+
:has_key => has_key,
|
100
|
+
:default => default,
|
101
|
+
:collection => collection,
|
102
|
+
:type => sanitizer_type,
|
103
|
+
:converter => hash[:converter],
|
104
|
+
:options => prepare_options!(options)
|
105
|
+
)
|
106
|
+
rescue InputSanitizer::OptionalValueOmitted
|
107
|
+
rescue InputSanitizer::ValidationError => error
|
108
|
+
@errors += handle_error(field, error)
|
109
|
+
end
|
110
|
+
|
111
|
+
def handle_error(field, error)
|
112
|
+
case error
|
113
|
+
when InputSanitizer::CollectionError
|
114
|
+
error.collection_errors.map do |index, error|
|
115
|
+
handle_error("#{field}/#{index}", error)
|
116
|
+
end
|
117
|
+
when InputSanitizer::NestedError
|
118
|
+
error.nested_errors.map do |error|
|
119
|
+
handle_error("#{field}#{error.field}", error)
|
120
|
+
end
|
121
|
+
else
|
122
|
+
error.field = "/#{field}"
|
123
|
+
Array(error)
|
124
|
+
end.flatten
|
125
|
+
end
|
126
|
+
|
127
|
+
def sanitizer_type
|
128
|
+
:payload
|
129
|
+
end
|
130
|
+
end
|