mini_defender 0.4.1 → 0.5.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.
- checksums.yaml +4 -4
- data/Gemfile +3 -5
- data/lib/mini_defender/rule.rb +6 -0
- data/lib/mini_defender/rules/array.rb +16 -0
- data/lib/mini_defender/rules/currency.rb +1 -1
- data/lib/mini_defender/rules/hash.rb +51 -2
- data/lib/mini_defender/rules_expander.rb +29 -7
- data/lib/mini_defender/validator.rb +71 -8
- data/lib/mini_defender/version.rb +1 -1
- data/lib/mini_defender.rb +0 -1
- data/mini_defender.gemspec +0 -2
- metadata +2 -3
- data/lib/mini_defender/extensions/hash.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36f0ca2e9d174da94e991c9eed6300ae0f0315ab57b3d9372921726261318c67
|
4
|
+
data.tar.gz: a4f29b8f5558008c1f5b992722a8ab9dd07dbc93d51d7ec288bd3be5e9d1d91b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 885a3ff5f6a337246c9b47e6ac21b0e9dc21067a11c49d7af2d9135d66f6ab878a620b9f73cafa5b1316d5edc2d74f709f924e37309bbe74bb596500abe8348b
|
7
|
+
data.tar.gz: 3d6a38389f37816a4b6ebbadd7700b6c628e7011c42dfbe0a07ee907f0d354b9b7aaed18179ad97c8350ea01948966ee47182b40e5630370761a5487c58fbe97
|
data/Gemfile
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
|
5
|
-
# Specify your gem's dependencies in mini_defender.gemspec
|
6
5
|
gemspec
|
7
6
|
|
8
|
-
gem
|
9
|
-
|
10
|
-
gem "minitest", "~> 5.0"
|
7
|
+
gem 'rake', '~> 13.0'
|
8
|
+
gem 'minitest', '~> 5.0'
|
data/lib/mini_defender/rule.rb
CHANGED
@@ -61,6 +61,12 @@ class MiniDefender::Rule
|
|
61
61
|
value
|
62
62
|
end
|
63
63
|
|
64
|
+
# This method is used to change the value
|
65
|
+
# regardless if coercion is required
|
66
|
+
def force_coerce?
|
67
|
+
false
|
68
|
+
end
|
69
|
+
|
64
70
|
# @param [Object] attribute
|
65
71
|
# @param [Object] value
|
66
72
|
# @param [MiniDefender::Validator] validator
|
@@ -5,6 +5,22 @@ class MiniDefender::Rules::Array < MiniDefender::Rule
|
|
5
5
|
'array'
|
6
6
|
end
|
7
7
|
|
8
|
+
def initialize(data_mode = 'none')
|
9
|
+
@data_mode = data_mode
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.make(args)
|
13
|
+
new(args[0] || 'none')
|
14
|
+
end
|
15
|
+
|
16
|
+
def coerce(value)
|
17
|
+
@data_mode == 'all' ? value : []
|
18
|
+
end
|
19
|
+
|
20
|
+
def force_coerce?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
8
24
|
def passes?(attribute, value, validator)
|
9
25
|
value.is_a?(Array)
|
10
26
|
end
|
@@ -5,11 +5,60 @@ class MiniDefender::Rules::Hash < MiniDefender::Rule
|
|
5
5
|
'hash'
|
6
6
|
end
|
7
7
|
|
8
|
+
def initialize(data_mode = 'none', key_type = 'any', value_type = 'any')
|
9
|
+
@data_mode = data_mode
|
10
|
+
@key_type = key_type
|
11
|
+
@value_type = value_type
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.make(args)
|
15
|
+
new(
|
16
|
+
args[0] || 'none',
|
17
|
+
args[1] || 'any',
|
18
|
+
args[2] || 'any',
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def coerce(value)
|
23
|
+
@data_mode == 'all' ? value : {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def force_coerce?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
8
30
|
def passes?(attribute, value, validator)
|
9
|
-
value.is_a?(Hash)
|
31
|
+
passes = value.is_a?(Hash)
|
32
|
+
|
33
|
+
if @key_type == 'string'
|
34
|
+
passes &= value.all? { |k, _| k.is_a?(String) }
|
35
|
+
end
|
36
|
+
|
37
|
+
case @value_type
|
38
|
+
when 'string'
|
39
|
+
passes &= value.all? { |_, v| v.is_a?(String) }
|
40
|
+
when 'integer'
|
41
|
+
passes &= value.all? { |_, v| v.is_a?(Integer) }
|
42
|
+
when 'float'
|
43
|
+
passes &= value.all? { |_, v| v.is_a?(Float) }
|
44
|
+
else
|
45
|
+
# None
|
46
|
+
end
|
47
|
+
|
48
|
+
passes
|
10
49
|
end
|
11
50
|
|
12
51
|
def message(attribute, value, validator)
|
13
|
-
|
52
|
+
additional = ''
|
53
|
+
|
54
|
+
if @key_type != 'any'
|
55
|
+
additional += ", key must be #{@key_type}"
|
56
|
+
end
|
57
|
+
|
58
|
+
if @value_type != 'any'
|
59
|
+
additional += ", value must be #{@value_type}"
|
60
|
+
end
|
61
|
+
|
62
|
+
"The field must be an object#{additional}."
|
14
63
|
end
|
15
64
|
end
|
@@ -6,14 +6,36 @@ module MiniDefender
|
|
6
6
|
# @param [Hash] flat_data
|
7
7
|
# @return [Hash]
|
8
8
|
def expand(rules, flat_data)
|
9
|
+
result = {}
|
10
|
+
|
11
|
+
rules.each do |rule_key, rule_set|
|
12
|
+
unless rule_key.include?('*')
|
13
|
+
result[rule_key] = rule_set
|
14
|
+
next
|
15
|
+
end
|
16
|
+
|
17
|
+
k_pattern = Regexp.compile('\A' + rule_key.gsub(/\*/, '\d+') + '\Z')
|
18
|
+
k_pattern_result = {}
|
19
|
+
|
20
|
+
flat_data.each do |value_key, _|
|
21
|
+
next unless k_pattern.match?(value_key)
|
22
|
+
k_pattern_result[value_key] = rule_set
|
23
|
+
end
|
24
|
+
|
25
|
+
if k_pattern_result.empty?
|
26
|
+
k_pattern_result[k_pattern.source.gsub(/\\[AZ]/, '').gsub('\d+', '0')] = rule_set
|
27
|
+
end
|
28
|
+
|
29
|
+
result.merge!(k_pattern_result)
|
30
|
+
end
|
31
|
+
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def array_patterns(rules)
|
9
36
|
rules
|
10
|
-
.
|
11
|
-
.map { |
|
12
|
-
data_rules = flat_data.filter { |k, _| p.match? k }.map { |k, _| [k, set] }
|
13
|
-
data_rules.length > 0 ? data_rules : [[p.source.gsub(/\\[AZ]/, '').gsub('\d+', '0'), set]]
|
14
|
-
}
|
15
|
-
.flatten(1)
|
16
|
-
.to_h
|
37
|
+
.filter { |key, _| key.include?('*') }
|
38
|
+
.map { |key, _| Regexp.compile('\A' + key.gsub(/\*/, '\d+') + '\Z') }
|
17
39
|
end
|
18
40
|
end
|
19
41
|
end
|
@@ -12,10 +12,13 @@ module MiniDefender
|
|
12
12
|
@coerced = {}
|
13
13
|
@expander = RulesExpander.new
|
14
14
|
@factory = RulesFactory.new
|
15
|
+
@array_patterns = @expander.array_patterns(rules)
|
15
16
|
end
|
16
17
|
|
17
18
|
def validate
|
18
|
-
|
19
|
+
unless @errors.nil?
|
20
|
+
return
|
21
|
+
end
|
19
22
|
|
20
23
|
@errors = {}
|
21
24
|
|
@@ -23,6 +26,7 @@ module MiniDefender
|
|
23
26
|
[k, @factory.init_set(set)]
|
24
27
|
end
|
25
28
|
|
29
|
+
# Set default values for missing data key compared to rules
|
26
30
|
data_rules.each do |k, set|
|
27
31
|
if !@data.key?(k) || @data[k].blank?
|
28
32
|
set.filter{ |r| r.defaults?(self) }.each do |r|
|
@@ -51,33 +55,40 @@ module MiniDefender
|
|
51
55
|
end
|
52
56
|
|
53
57
|
value = coerced = @data[k]
|
58
|
+
force_coerce = false
|
59
|
+
|
54
60
|
rule_set.each do |rule|
|
55
61
|
next unless rule.active?(self)
|
56
62
|
|
57
63
|
value_included &= !rule.excluded?(self)
|
58
64
|
|
59
|
-
if rule.passes?(k,
|
65
|
+
if rule.passes?(k, value, self)
|
60
66
|
coerced = rule.coerce(coerced)
|
67
|
+
force_coerce = rule.force_coerce?
|
61
68
|
else
|
62
69
|
@errors[k] << rule.error_message(k, value, self)
|
63
70
|
end
|
64
71
|
end
|
65
72
|
|
73
|
+
if force_coerce
|
74
|
+
value = coerced
|
75
|
+
end
|
76
|
+
|
66
77
|
if @errors[k].empty? && value_included
|
67
78
|
@validated[k] = value
|
68
79
|
@coerced[k] = coerced
|
69
80
|
end
|
70
81
|
end
|
71
82
|
|
72
|
-
@validated = @validated.expand
|
73
|
-
@coerced = @coerced.expand
|
74
|
-
|
75
83
|
@errors.reject! { |_, v| v.empty? }
|
76
84
|
end
|
77
85
|
|
78
86
|
def validate!
|
79
87
|
validate if @errors.nil?
|
80
|
-
|
88
|
+
|
89
|
+
unless @errors.empty?
|
90
|
+
raise ValidationError.new('Data validation failed', @errors)
|
91
|
+
end
|
81
92
|
end
|
82
93
|
|
83
94
|
def passes?
|
@@ -91,12 +102,12 @@ module MiniDefender
|
|
91
102
|
|
92
103
|
def validated
|
93
104
|
validate! if @errors.nil?
|
94
|
-
@validated
|
105
|
+
@validated_compacted ||= compact_expanded(@validated)
|
95
106
|
end
|
96
107
|
|
97
108
|
def coerced
|
98
109
|
validate! if @errors.nil?
|
99
|
-
@coerced
|
110
|
+
@coerced_compacted = compact_expanded(@coerced)
|
100
111
|
end
|
101
112
|
|
102
113
|
# @return [Hash]
|
@@ -125,5 +136,57 @@ module MiniDefender
|
|
125
136
|
|
126
137
|
Regexp.compile("\\A#{search_key.reverse.join('\.')}\\z")
|
127
138
|
end
|
139
|
+
|
140
|
+
def compact_expanded(hash)
|
141
|
+
expanded = {}
|
142
|
+
|
143
|
+
hash.each do |k, v|
|
144
|
+
keys = k.split('.')
|
145
|
+
node = expanded
|
146
|
+
|
147
|
+
while keys.length > 1
|
148
|
+
next_key = keys.shift
|
149
|
+
next_node = (node[next_key] ||= {})
|
150
|
+
|
151
|
+
if next_node.is_a?(Array)
|
152
|
+
node[next_key] = next_node = next_node.each_with_index.to_h do |value, index|
|
153
|
+
[index.to_s, value]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
node = next_node
|
158
|
+
end
|
159
|
+
|
160
|
+
node[keys.shift] = v
|
161
|
+
end
|
162
|
+
|
163
|
+
compact_keys(expanded)
|
164
|
+
end
|
165
|
+
|
166
|
+
def compact_keys(hash, current_key = '')
|
167
|
+
current_key_suffixed = if current_key == ''
|
168
|
+
''
|
169
|
+
else
|
170
|
+
"#{current_key}."
|
171
|
+
end
|
172
|
+
|
173
|
+
result = hash.to_h do |k, v|
|
174
|
+
[k, v.is_a?(Hash) ? compact_keys(v, current_key_suffixed + k) : v]
|
175
|
+
end
|
176
|
+
|
177
|
+
if result.empty?
|
178
|
+
return result
|
179
|
+
end
|
180
|
+
|
181
|
+
unless @array_patterns.any? { |p| p.match?("#{current_key_suffixed}#{result.keys.first}") }
|
182
|
+
return result
|
183
|
+
end
|
184
|
+
|
185
|
+
if result.all? { |k, _v| k.match?(/\A\d+\z/) }
|
186
|
+
return result.values
|
187
|
+
end
|
188
|
+
|
189
|
+
result
|
190
|
+
end
|
128
191
|
end
|
129
192
|
end
|
data/lib/mini_defender.rb
CHANGED
data/mini_defender.gemspec
CHANGED
@@ -14,8 +14,6 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.license = "MIT"
|
15
15
|
spec.required_ruby_version = ">= 2.6.0"
|
16
16
|
|
17
|
-
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
18
|
-
|
19
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
20
18
|
spec.metadata["source_code_uri"] = spec.homepage
|
21
19
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mini_defender
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ali Alhoshaiyan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -97,7 +97,6 @@ files:
|
|
97
97
|
- Rakefile
|
98
98
|
- lib/mini_defender.rb
|
99
99
|
- lib/mini_defender/extensions/enumerable.rb
|
100
|
-
- lib/mini_defender/extensions/hash.rb
|
101
100
|
- lib/mini_defender/handles_validation_errors.rb
|
102
101
|
- lib/mini_defender/rule.rb
|
103
102
|
- lib/mini_defender/rules.rb
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Hash
|
4
|
-
def expand
|
5
|
-
expanded = {}
|
6
|
-
|
7
|
-
reject{ |_, v| v.is_a?(Hash) || v.is_a?(Array) }.each do |k, v|
|
8
|
-
keys = k.split('.')
|
9
|
-
node = expanded
|
10
|
-
node = (node[keys.shift] ||= {}) while keys.length > 1
|
11
|
-
node[keys.shift] = v
|
12
|
-
end
|
13
|
-
|
14
|
-
expanded.compact_keys
|
15
|
-
end
|
16
|
-
|
17
|
-
def compact_keys
|
18
|
-
result = to_h do |k, v|
|
19
|
-
[k, v.is_a?(Hash) ? v.compact_keys : v]
|
20
|
-
end
|
21
|
-
|
22
|
-
if !result.empty? && result.all? { |k, _v| k.match?(/\A\d+\z/) }
|
23
|
-
result.values
|
24
|
-
else
|
25
|
-
result
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|