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.
- 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
|