mini_defender 0.4.1 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56cc42b9b0ce4c44e30dc4bcd8dfcc31bcccc012388f52a076320ee926feec11
4
- data.tar.gz: 7e5aaf99490b42b14956fda14843065d3cd906b6568da7ff4df90059f965b75b
3
+ metadata.gz: 36f0ca2e9d174da94e991c9eed6300ae0f0315ab57b3d9372921726261318c67
4
+ data.tar.gz: a4f29b8f5558008c1f5b992722a8ab9dd07dbc93d51d7ec288bd3be5e9d1d91b
5
5
  SHA512:
6
- metadata.gz: f3a25a2655ed8889345d101bca863cd72a605825ae2dd99a0b6527f73c33572cf5771e8aba28fdb81b1311ac3cc22845a868c5edeb3ddcb1f9ee2572e5e91010
7
- data.tar.gz: 9ee05f959b9289c6ea0d9b1f88278bc9617df76cef267a362163fef02f6056dc8443ee3700c86308b54e6608edfd3c34ad7cfabd01d6b01e86d37772d028abe5
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 "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
- # Specify your gem's dependencies in mini_defender.gemspec
6
5
  gemspec
7
6
 
8
- gem "rake", "~> 13.0"
9
-
10
- gem "minitest", "~> 5.0"
7
+ gem 'rake', '~> 13.0'
8
+ gem 'minitest', '~> 5.0'
@@ -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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'countries'
3
+ require 'money'
4
4
 
5
5
  class MiniDefender::Rules::Currency < MiniDefender::Rule
6
6
  CURRENCIES = Money::Currency.map(&:iso_code).map(&:upcase).uniq
@@ -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
- "The field must be an object."
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
- .map { |k, v| [Regexp.compile('\A' + k.gsub(/\*/, '\d+') + '\Z'), v] }.to_h
11
- .map { |p, set|
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
- return unless @errors.nil?
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, coerced, self)
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
- raise ValidationError.new('Data validation failed', @errors) unless @errors.empty?
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniDefender
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.1"
5
5
  end
data/lib/mini_defender.rb CHANGED
@@ -13,7 +13,6 @@ require_relative 'mini_defender/validation_helpers'
13
13
 
14
14
  # Extensions to Ruby Core
15
15
  require_relative 'mini_defender/extensions/enumerable'
16
- require_relative 'mini_defender/extensions/hash'
17
16
 
18
17
  module MiniDefender
19
18
  end
@@ -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.1
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: 2023-11-16 00:00:00.000000000 Z
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