rule_based_validator 0.1.0 → 0.2.2

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: 50099dc8687da381fdb6d0603c41d97f7f48d80d497a14342f44e726dd129362
4
- data.tar.gz: 4a1e7283d51a71c214a0fb629cfd1b007a7dc5db75131d15989cb2450eca301b
3
+ metadata.gz: 75608d64d1784b6e164aec104b6b477b627579a67f91bd0ac20f82907502b7fc
4
+ data.tar.gz: 5559c08d7fc5b8582ba5d087b1336885f11a54625a97171a3c79b6b7af1bbb43
5
5
  SHA512:
6
- metadata.gz: b238ecaf9501016b372e28859a370f83acacde6718241c2de1f3728e55fb5fd41ee9666a4caa323aa9602bd2a1a51b470f4cec8e9f120a8baa24babed1fa493d
7
- data.tar.gz: 43dc6631997b46d7e70c6f9e38861649eebf004a6bced9ca0d1d1a18ae74ac662951b7206d8ffbbc2266f0f25a426d202624711a4cbb86379ac4638e78c4fac9
6
+ metadata.gz: d39e79041699f8b1e81069b3f2d79cade490d30b4df811fe6f929688017cca1047ed18cf323c824e48711da6e819fa9f9d1d9c8fafd068247eeb8ee5e4c19848
7
+ data.tar.gz: e8c843118d63bb272ef0ea2c96c3621b34345e7b8fde5ca34597f90e8e7db66940bc7dae309f0b75c2829ada0656f2ee882e26f872d97a410834867c9d2cf061
@@ -0,0 +1,3 @@
1
+ RuleBasedValidator.configure do |config|
2
+ config.default_locale = :en
3
+ end
@@ -0,0 +1,2 @@
1
+ class User < ActiveRecord::Base
2
+ end
@@ -0,0 +1,25 @@
1
+ module RuleBasedValidator
2
+ class InvalidRulesException < StandardError
3
+ attr_reader :code, :status
4
+
5
+ def initialize(message = "No se proporcionaron reglas válidas")
6
+ super(message)
7
+ @code = 17
8
+ @status = 400
9
+ end
10
+ end
11
+
12
+ # Excepción para parámetros inválidos
13
+ class InvalidParametersException < StandardError
14
+ attr_reader :code, :status, :message, :body
15
+
16
+ def initialize(errors)
17
+ super(message)
18
+ @code = 18
19
+ @status = 400
20
+ @message = 'Parámetros inválidos'
21
+ @body = { errors: errors }
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,36 @@
1
+ # config/locales/en.yml
2
+ en:
3
+ errors:
4
+ messages:
5
+ invalid_parameter:
6
+ required: "The parameter %{parameter} is required."
7
+ required_if: "The parameter %{parameter} is required if %{required_if}."
8
+ must_be_string: "The value %{value} of %{parameter} must be a string."
9
+ must_be_integer: "The value %{value} of %{parameter} must be an integer."
10
+ must_be_boolean: "The value %{value} of %{parameter} must be a boolean."
11
+ must_be_numeric: "The value %{value} of %{parameter} must be a number."
12
+ must_be_float: "The value %{value} of %{parameter} must be a float."
13
+ must_be_array: "The value %{value} of %{parameter} must be an array."
14
+ must_be_hash: "The value %{value} of %{parameter} must be a hash."
15
+ must_be_both_same_type: "Both must be the same type to compare"
16
+ must_be_greater_than: "The value %{value} of %{parameter} must be greater than %{greater_than}."
17
+ must_be_greater_than_or_equal_to: "The value %{value} of %{parameter} must be greater than or equal to %{greater_than_or_equal_to}."
18
+ must_be_less_than: "The value %{value} of %{parameter} must be less than %{less_than}."
19
+ must_be_less_than_or_equal_to: "The value %{value} of %{parameter} must be less than or equal to %{less_than_or_equal_to}."
20
+ must_be_between: "The value %{value} of %{parameter} must be between %{min} and %{max}."
21
+ must_be_date_format: "The value %{value} of %{parameter} must be a date in %{date_format} format."
22
+ must_be_date: "The value %{value} of %{parameter} must be a date."
23
+ must_be_before_date: "The value %{value} of %{parameter} must be before %{before_date}."
24
+ must_be_before_than_field: "The value %{value} of %{parameter} must be before than %{before_than_field} field."
25
+ must_be_before_or_equal: "The value %{value} of %{parameter} must be before or equal to %{before_or_equal}."
26
+ must_be_after_date: "The value %{value} of %{parameter} must be after %{after_date}."
27
+ must_be_after_than_field: "The value %{value} of %{parameter} must be after than %{after_than_field} field."
28
+ must_be_after_or_equal: "The value %{value} of %{parameter} must be after or equal to %{after_or_equal}."
29
+ must_be_email: "The parameter %{parameter} must be a valid email."
30
+ max_digits_exceeded: "The value %{value} of %{parameter} exceeds the maximum of %{max_digits} digits."
31
+ min_digits_not_reached: "The value %{value} of %{parameter} does not reach the minimum of %{min_digits} digits."
32
+ one_of: "The value %{value} of %{parameter} must be one of the following values: %{values}."
33
+ subset_of: "The value %{value} of %{parameter} must be a subset of the following values: %{values}."
34
+ uniqueness: "The value %{value} of %{parameter} must be unique in the model."
35
+ exists: "The value %{value} of %{parameter} must exist in the model."
36
+
@@ -0,0 +1,36 @@
1
+ # config/locales/es.yml
2
+ es:
3
+ errors:
4
+ messages:
5
+ invalid_parameter:
6
+ required: "El parámetro %{parameter} es requerido."
7
+ required_if: "El parámetro %{parameter} es requerido si %{required_if}."
8
+ must_be_string: "El valor %{value} de %{parameter} debe ser una cadena."
9
+ must_be_integer: "El valor %{value} de %{parameter} debe ser un entero."
10
+ must_be_boolean: "El valor %{value} de %{parameter} debe ser un boolean."
11
+ must_be_numeric: "El valor %{value} de %{parameter} debe ser un numero."
12
+ must_be_float: "El valor %{value} de %{parameter} debe ser un float."
13
+ must_be_array: "El valor %{value} de %{parameter} debe ser un arreglo."
14
+ must_be_hash: "El valor %{value} de %{parameter} debe ser un hash."
15
+ must_be_both_same_type: "Ambos deben ser del mismo tipo para comparar."
16
+ must_be_greater_than: "El valor %{value} de %{parameter} debe ser mayor que %{greater_than}."
17
+ must_be_greater_than_or_equal_to: "El valor %{value} de %{parameter} debe ser mayor o igual que %{greater_than_or_equal_to}."
18
+ must_be_less_than: "El valor %{value} de %{parameter} debe ser menor que %{less_than}."
19
+ must_be_less_than_or_equal_to: "El valor %{value} de %{parameter} debe ser menor o igual que %{less_than_or_equal_to}."
20
+ must_be_between: "El valor %{value} de %{parameter} debe estar entre %{min} y %{max}."
21
+ must_be_date_format: "El valor %{value} de %{parameter} debe ser una fecha en formato %{date_format}."
22
+ must_be_date: "El valor %{value} de %{parameter} debe ser una fecha."
23
+ must_be_before_date: "El valor %{value} de %{parameter} debe ser antes de %{before_date}."
24
+ must_be_before_than_field: "El valor %{value} de %{parameter} debe ser antes que la fecha del campo %{before_than_field}."
25
+ must_be_before_or_equal: "El valor %{value} de %{parameter} debe ser antes o igual que %{before_or_equal}."
26
+ must_be_after_date: "El valor %{value} de %{parameter} debe ser después de %{after_date}."
27
+ must_be_after_than_field: "El valor %{value} de %{parameter} debe ser después que la fecha del campo %{after_than_field}."
28
+ must_be_after_or_equal: "El valor %{value} de %{parameter} debe ser después o igual que %{after_or_equal}."
29
+ must_be_min: "El valor %{value} de %{parameter} debe ser mayor que %{min}."
30
+ must_be_email: "El parametro %{parameter} debe ser un email válido."
31
+ max_digits_exceeded: "El valor %{value} de %{parameter} excede el máximo de %{max_digits} dígitos."
32
+ min_digits_not_reached: "El valor %{value} de %{parameter} no alcanza el mínimo de %{min_digits} dígitos."
33
+ one_of: "El valor %{value} de %{parameter} debe ser uno de los siguientes valores: %{values}."
34
+ subset_of: "El valor %{value} de %{parameter} debe ser un subconjunto de los siguientes valores: %{values}."
35
+ uniqueness: "El valor %{value} de %{parameter} debe ser único en el modelo."
36
+ exists: "El valor %{value} de %{parameter} debe existir en el modelo."
@@ -0,0 +1,677 @@
1
+
2
+ require 'pry'
3
+ require_relative 'exceptions.rb'
4
+
5
+ module RuleBasedValidator
6
+ class Validator
7
+ def initialize(attributes = {})
8
+ @attributes = attributes.to_hash&.symbolize_keys
9
+ end
10
+
11
+ def verify(rules, opts = {})
12
+ @instance = opts[:instance]
13
+
14
+ raise InvalidRulesException.new if rules.empty?
15
+
16
+ rules_formatted = if @instance.nil? || @instance.id.nil?
17
+ RuleBasedValidator::Validator.parse_rules_for(:create, rules)
18
+ else
19
+ RuleBasedValidator::Validator.parse_rules_for(:update, rules)
20
+ end
21
+
22
+ errors = rules_formatted.map do |rule_formatted|
23
+ begin
24
+ validate_attributes_by_rules({ rule_formatted.first => rule_formatted.second })
25
+ rescue InvalidParametersException => exception
26
+ exception.body[:errors]
27
+ end
28
+ end
29
+
30
+ errors = errors.reduce(:merge)
31
+
32
+ raise InvalidParametersException.new(errors) unless errors.empty?
33
+ end
34
+
35
+ def validate_attributes_by_rules(rules_formatted)
36
+ rules_formatted.each do |rule|
37
+ if (@attributes.keys.include? rule[0]) || (rule[1].include? 'required') || required_if_present?(rule[1])
38
+ if (rule[1].include? 'nullable')
39
+ next if send('nullable?', rule[0])
40
+ end
41
+ if required_if_present?(rule[1])
42
+ rule_parsed = filter_required_if(rule[1]).split(':')
43
+ next if send('required_if?', rule[0], rule_parsed[1].to_sym)
44
+ end
45
+ if (rule[1].include? 'required')
46
+ send('required?', rule[0])
47
+ end
48
+
49
+ rule[1].each do |nested_rules|
50
+ if nested_rules.instance_of? Hash
51
+ if @attributes[rule[0]].instance_of? Array
52
+ @attributes[rule[0]].each do |element|
53
+ RuleBasedValidator::Validator.new(element).verify(nested_rules)
54
+ end
55
+ elsif @attributes[rule[0]].instance_of? Hash
56
+ RuleBasedValidator::Validator.new(@attributes[rule[0]]).verify(nested_rules)
57
+ else
58
+ raise Exceptions::Api::InvalidParametersException.new({ key => 'El campo ' + rule[0].to_s + ' debe ser un objeto o un arreglo de objetos' })
59
+ end
60
+ else
61
+ if !nested_rules.index(':')
62
+ send(nested_rules + '?', rule[0])
63
+ elsif !nested_rules.index(',')
64
+ rule_parsed = nested_rules.split(':')
65
+ send(rule_parsed[0] + '?', rule[0], rule_parsed[1])
66
+ elsif !nested_rules.index('[')
67
+ rule_parsed = nested_rules.split(':')
68
+ comparators_parsed = rule_parsed[1].split(',')
69
+ send(rule_parsed[0] + '?', rule[0], comparators_parsed[0], comparators_parsed[1])
70
+ else
71
+ rule_parsed = nested_rules.split(':')
72
+ send(rule_parsed[0] + '?', rule[0], rule_parsed[1])
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ {}
79
+ end
80
+
81
+ def rule_exists?(rules, rule)
82
+ rules.any? { |element| element.include?(rule) }
83
+ end
84
+
85
+ def find_rule(rules, rule)
86
+ rules.find { |r| r.include?(rule) }
87
+ end
88
+
89
+ # Checks if a parameter is required.
90
+ #
91
+ # @param key [Symbol] The name of the parameter to check.
92
+ # @raise [InvalidParametersException] If the parameter is not present.
93
+ # @return [Boolean] `true` if the parameter is present.
94
+ def required?(key)
95
+ unless @attributes[key].present?
96
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.required', parameter: key) })
97
+ end
98
+
99
+ true
100
+ end
101
+
102
+ # Checks if a parameter is nullable.
103
+ #
104
+ # This method verifies if a given parameter is nullable by checking if it is not present in the attributes hash.
105
+ #
106
+ # @param key [Symbol] The name of the parameter to check.
107
+ # @return [Boolean] `true` if the parameter is nullable (not present), `false` otherwise.
108
+ def nullable?(key)
109
+ !@attributes[key].present?
110
+ end
111
+
112
+ # Checks if a parameter is a string.
113
+ #
114
+ # This method verifies if a given parameter is a string by checking its type.
115
+ #
116
+ # @param key [Symbol] The name of the parameter to check.
117
+ # @param value [String] This is used to verify elements of another element. (Array, Hash)
118
+ # @raise [InvalidParametersException] If the parameter is not a string.
119
+ # @return [Boolean] `true` if the parameter is a string, `false` otherwise.
120
+ def string?(key, value = nil)
121
+ value = @attributes[key] if value.nil?
122
+
123
+ unless value.instance_of?(String)
124
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_string', parameter: key, value: value) })
125
+ end
126
+
127
+ value
128
+ end
129
+
130
+ # Checks if a parameter is an integer.
131
+ #
132
+ # This method verifies if a given parameter is an integer by checking its type or if it's a string containing only digits.
133
+ #
134
+ # @param key [Symbol] The name of the parameter to check.
135
+ # @param value [String] This is used to verify elements of another element. (Array, Hash)
136
+ # @raise [InvalidParametersException] If the parameter is not an integer.
137
+ # @return [Boolean] `true` if the parameter is an integer, `false` otherwise.
138
+ def integer?(key, value = nil)
139
+ value = @attributes[key] if value.nil?
140
+
141
+ unless value.is_a?(Integer) || (value.is_a?(String) && value =~ /\A\d+\z/)
142
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_integer', parameter: key, value: value) })
143
+ end
144
+
145
+ true
146
+ end
147
+
148
+ # Checks if a parameter is a float.
149
+ #
150
+ # This method verifies if a given parameter is a float by checking its type or if it's a string containing a float.
151
+ #
152
+ # @param key [Symbol] The name of the parameter to check.
153
+ # @raise [InvalidParametersException] If the parameter is not a float.
154
+ # @param value [String] This is used to verify elements of another element. (Array, Hash)
155
+ # @return [Boolean] `true` if the parameter is a float, `false` otherwise.
156
+ def float?(key, value = nil)
157
+ value = @attributes[key] if value.nil?
158
+
159
+ value = value.to_s unless value.is_a?(String)
160
+
161
+ if value =~ /\A-?\d+\.\d+\z/
162
+ true
163
+ else
164
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_float', parameter: key, value: value) })
165
+ end
166
+
167
+ true
168
+ end
169
+
170
+ # Checks if a parameter is a numeric.
171
+ #
172
+ # This method verifies if a given parameter is a numeric by checking its type or if it's a string containing a numeric.
173
+ #
174
+ # @param key [Symbol] The name of the parameter to check.
175
+ # @param value [String] This is used to verify elements of another element. (Array, Hash)
176
+ # @raise [InvalidParametersException] If the parameter is not a numeric.
177
+ # @return [Boolean] `true` if the parameter is a numeric, `false` otherwise.
178
+ def numeric?(key, value = nil)
179
+ value = @attributes[key] if value.nil?
180
+
181
+ !Float(value).nil? rescue raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_numeric', parameter: key, value: value) })
182
+ end
183
+
184
+ # Checks if a parameter is a boolean.
185
+ #
186
+ # This method verifies if a given parameter is a boolean by checking its type or if it's a string containing 'true' or 'false'.
187
+ #
188
+ # @param key [Symbol] The name of the parameter to check.
189
+ # @param value [String] This is used to verify elements of another element. (Array, Hash)
190
+ # @raise [InvalidParametersException] If the parameter is not a boolean.
191
+ # @return [Boolean] `true` if the parameter is a boolean, `false` otherwise.
192
+ def boolean?(key, value = nil)
193
+ value = @attributes[key] if value.nil?
194
+
195
+ unless [true, false, "true", "false", 1, 0, '1', '0', ].include?(value)
196
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_boolean', parameter: key, value: value) })
197
+ end
198
+
199
+ return true
200
+ end
201
+
202
+ # Checks if a parameter is an array.
203
+ #
204
+ # This method verifies if a given parameter is an array by checking its type.
205
+ #
206
+ # @param key [Symbol] The name of the parameter to check.
207
+ # @param type [String] The type of the elements of the array.
208
+ # @raise [InvalidParametersException] If the parameter is not an array.
209
+ # @return [Boolean] `true` if the parameter is an array, `false` otherwise.
210
+ def array?(key, type = nil)
211
+ value = @attributes[key]
212
+
213
+ unless value.instance_of?(Array)
214
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_array', parameter: key, value: value) })
215
+ end
216
+
217
+ unless type.nil?
218
+ value.each_with_index do |element, index|
219
+ send(type + '?', key, element)
220
+ end
221
+ end
222
+ return true
223
+ end
224
+
225
+ # Checks if the value of the specified key is a hash.
226
+ #
227
+ # @param key [Symbol] The key to check in the attributes hash.
228
+ # @param type [Class] (optional) The expected class of the value.
229
+ # @raise [InvalidParametersException] If the parameter is not an hash.
230
+ # @return [Boolean] Returns true if the value is a hash.
231
+ def hash?(key, type = nil)
232
+ value = @attributes[key]
233
+ if (!value.instance_of? Hash)
234
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_hash', parameter: key, value: value) })
235
+ end
236
+ return true
237
+ end
238
+
239
+ def gt?(key, comparator)
240
+ value = @attributes[key]
241
+
242
+ value = Float(value) rescue value
243
+
244
+ if comparator !~ /\A\d+(\.\d+)?\z/
245
+ comparator_value = @attributes[comparator.to_sym]
246
+ comparator = Float(comparator_value) rescue comparator_value
247
+ else
248
+ comparator = Float(comparator)
249
+ end
250
+
251
+ if value.class != comparator.class
252
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_both_same_type') })
253
+ elsif (value.is_a?(Hash) || value.is_a?(Array)) && (comparator.is_a?(Float))
254
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_greater_than', parameter: key, value: value, greater_than: comparator) }) unless value.count > comparator
255
+ elsif (value.is_a?(Hash) || value.is_a?(Array)) && (comparator.is_a?(Hash) || comparator.is_a?(Array))
256
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_greater_than', parameter: key, value: value, greater_than: comparator) }) unless value.count > comparator.count
257
+ else
258
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_greater_than', parameter: key, value: value, greater_than: comparator) }) unless value > comparator
259
+ end
260
+
261
+ true
262
+ end
263
+
264
+ def gte?(key, comparator)
265
+ value = @attributes[key]
266
+
267
+ value = Float(value) rescue value
268
+
269
+ if comparator !~ /\A\d+(\.\d+)?\z/
270
+ comparator_value = @attributes[comparator.to_sym]
271
+ comparator = Float(comparator_value) rescue comparator_value
272
+ else
273
+ comparator = Float(comparator)
274
+ end
275
+
276
+ if value.class != comparator.class
277
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_both_same_type') })
278
+ elsif (value.is_a?(Hash) || value.is_a?(Array)) && (comparator.is_a?(Float))
279
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_greater_than_or_equal_to', parameter: key, value: value, greater_than_or_equal_to: comparator) }) unless value.count >= comparator
280
+ elsif (value.is_a?(Hash) || value.is_a?(Array)) && (comparator.is_a?(Hash) || comparator.is_a?(Array))
281
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_greater_than_or_equal_to', parameter: key, value: value, greater_than_or_equal_to: comparator) }) unless value.count >= comparator.count
282
+ else
283
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_greater_than_or_equal_to', parameter: key, value: value, greater_than_or_equal_to: comparator) }) unless value >= comparator
284
+ end
285
+
286
+ true
287
+ end
288
+
289
+ # Checks if the value of a given key is less than a comparator.
290
+ #
291
+ # @param key [Symbol] The key of the attribute to compare.
292
+ # @param comparator [Object] The value or attribute to compare against.
293
+ # @return [Boolean] Returns true if the value is less than the comparator, otherwise raises an InvalidParametersException.
294
+ # @raise [InvalidParametersException] Raises an exception if the value is not less than the comparator or if the types are not compatible.
295
+ def lt?(key, comparator)
296
+ value = @attributes[key]
297
+
298
+ value = Float(value) rescue value
299
+
300
+ if comparator !~ /\A\d+(\.\d+)?\z/
301
+ comparator_value = @attributes[comparator.to_sym]
302
+ comparator = Float(comparator_value) rescue comparator_value
303
+ else
304
+ comparator = Float(comparator)
305
+ end
306
+
307
+ if value.class != comparator.class
308
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_both_same_type') })
309
+ elsif (value.is_a?(Hash) || value.is_a?(Array)) && (comparator.is_a?(Float))
310
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_less_than', parameter: key, value: value, less_than: comparator) }) unless value.count < comparator
311
+ elsif (value.is_a?(Hash) || value.is_a?(Array)) && (comparator.is_a?(Hash) || comparator.is_a?(Array))
312
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_less_than', parameter: key, value: value, less_than: comparator) }) unless value.count < comparator.count
313
+ else
314
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_less_than', parameter: key, value: value, less_than: comparator) }) unless value < comparator
315
+ end
316
+
317
+ true
318
+ end
319
+
320
+ def lte?(key, comparator)
321
+ value = @attributes[key]
322
+
323
+ value = Float(value) rescue value
324
+
325
+ if comparator !~ /\A\d+(\.\d+)?\z/
326
+ comparator_value = @attributes[comparator.to_sym]
327
+ comparator = Float(comparator_value) rescue comparator_value
328
+ else
329
+ comparator = Float(comparator)
330
+ end
331
+
332
+ if value.class != comparator.class
333
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_both_same_type') })
334
+ elsif (value.is_a?(Hash) || value.is_a?(Array)) && (comparator.is_a?(Float))
335
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_less_than_or_equal_to', parameter: key, value: value, less_than_or_equal_to: comparator) }) unless value.count <= comparator
336
+ elsif (value.is_a?(Hash) || value.is_a?(Array)) && (comparator.is_a?(Hash) || comparator.is_a?(Array))
337
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_less_than_or_equal_to', parameter: key, value: value, less_than_or_equal_to: comparator) }) unless value.count <= comparator.count
338
+ else
339
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_less_than_or_equal_to', parameter: key, value: value, less_than_or_equal_to: comparator) }) unless value <= comparator
340
+ end
341
+
342
+ true
343
+ end
344
+
345
+ def between?(key, min, max)
346
+ value = @attributes[key]
347
+
348
+ value = Float(value) rescue value
349
+ min = Float(min) rescue min
350
+ max = Float(max) rescue max
351
+
352
+ if value.class != min.class || value.class != max.class
353
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_both_same_type') })
354
+ end
355
+
356
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_between', value: value, parameter: key, min: min, max: max) }) if !value.between?(min, max)
357
+
358
+ return true
359
+ end
360
+
361
+ def date?(key, value = nil)
362
+ if value.nil?
363
+ value = @attributes[key]
364
+ end
365
+ if (Date.parse(value) rescue false)
366
+ return Date.parse(value)
367
+ elsif (Date.strptime(value, '%d-%m-%Y') rescue false)
368
+ return Date.strptime(value, '%d-%m-%Y')
369
+ elsif value.instance_of? Date
370
+ return value
371
+ else
372
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_date', value: value, parameter: key) })
373
+ end
374
+
375
+ return true
376
+ end
377
+
378
+ def date_format?(key, format)
379
+ value = @attributes[key]
380
+
381
+ if (Date.strptime(value, format) rescue false)
382
+ return Date.strptime(value, format)
383
+ else
384
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_date_format', value: value, parameter: key, date_format: format) })
385
+ end
386
+
387
+ return true
388
+ end
389
+
390
+ def regex_required?(key, date)
391
+ value = @attributes[key]
392
+
393
+ entry_date = date?(key)
394
+ date_to_compare = date?(key, date)
395
+
396
+ if entry_date >= date_to_compare
397
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_regex_required_date', value: value, parameter: key, regex_required_date: date) })
398
+ end
399
+
400
+ return true
401
+ end
402
+
403
+ def regex_required_than_field?(key, key2)
404
+ value = @attributes[key]
405
+
406
+ entry_date = date?(key)
407
+ date_to_compare = date?(key2.to_sym)
408
+
409
+ if entry_date >= date_to_compare
410
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_regex_required_than_field', value: value, parameter: key, regex_required_than_field: key2) })
411
+ end
412
+
413
+ return true
414
+ end
415
+
416
+ def regex_required_or_equal?(key, date)
417
+ value = @attributes[key]
418
+
419
+ entry_date = date?(key)
420
+ date_to_compare = date?(key, date)
421
+
422
+ if entry_date > date_to_compare
423
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_regex_required_or_equal', value: value, parameter: key, regex_required_or_equal: date) })
424
+ end
425
+
426
+ return true
427
+ end
428
+
429
+ def before?(key, date)
430
+ value = @attributes[key]
431
+
432
+ entry_date = date?(key)
433
+ date_to_compare = date?(key, date)
434
+
435
+ if entry_date >= date_to_compare
436
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_before_date', value: value, parameter: key, before_date: date) })
437
+ end
438
+
439
+ return true
440
+ end
441
+
442
+ def before_than_field?(key, key2)
443
+ value = @attributes[key]
444
+
445
+ entry_date = date?(key)
446
+ date_to_compare = date?(key2.to_sym)
447
+
448
+ if entry_date >= date_to_compare
449
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_before_than_field', value: value, parameter: key, before_than_field: key2) })
450
+ end
451
+
452
+ return true
453
+ end
454
+
455
+ def before_or_equal?(key, date)
456
+ value = @attributes[key]
457
+
458
+ entry_date = date?(key)
459
+ date_to_compare = date?(key, date)
460
+
461
+ if entry_date > date_to_compare
462
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_before_or_equal', value: value, parameter: key, before_or_equal: date) })
463
+ end
464
+
465
+ return true
466
+ end
467
+
468
+ def after?(key, date)
469
+ value = @attributes[key]
470
+
471
+ entry_date = date?(key)
472
+ date_to_compare = date?(key, date)
473
+
474
+ if entry_date <= date_to_compare
475
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_after_date', value: value, parameter: key, after_date: date) })
476
+ end
477
+
478
+ return true
479
+ end
480
+
481
+ def after_than_field?(key, key2)
482
+ value = @attributes[key]
483
+
484
+ entry_date = date?(key)
485
+ date_to_compare = date?(key2.to_sym)
486
+
487
+ if entry_date <= date_to_compare
488
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_after_than_field', value: value, parameter: key, after_than_field: key2) })
489
+ end
490
+
491
+ return true
492
+ end
493
+
494
+ def after_or_equal?(key, date)
495
+ value = @attributes[key]
496
+
497
+ entry_date = date?(key)
498
+ date_to_compare = date?(key, date)
499
+
500
+ if entry_date < date_to_compare
501
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_after_or_equal', value: value, parameter: key, after_or_equal: date) })
502
+ end
503
+ return true
504
+ end
505
+
506
+ def filter_required_if(rules)
507
+ rules.find { |r| r.include?('required_if') }
508
+ end
509
+
510
+ def required_if_present?(rules)
511
+ filter_required_if(rules).present?
512
+ end
513
+
514
+ def required_if?(key, field, value = nil)
515
+ if @attributes[key].nil?
516
+ if field == :true
517
+ required?(key)
518
+ return true
519
+ elsif field.to_s.include?(',')
520
+ key_to_compare, value_to_compare = field.to_s.split(',')
521
+
522
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.required_if', parameter: key, required_if: field) }) if @attributes[key_to_compare.to_sym] != value_to_compare
523
+
524
+ elsif !@attributes[field].nil?
525
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.required_if', parameter: key, required_if: field) })
526
+ else
527
+ return true
528
+ end
529
+ end
530
+ return false
531
+ end
532
+
533
+ def email?(key)
534
+ value = @attributes[key]
535
+
536
+ value = string?(key, value)
537
+
538
+ unless value =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
539
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_email', parameter: key, value: value) })
540
+ end
541
+
542
+ return true
543
+ end
544
+
545
+ def max_digits?(key, max_digits)
546
+ value = @attributes[key]
547
+
548
+ unless value.is_a?(Numeric)
549
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_numeric', parameter: key, value: value) })
550
+ end
551
+
552
+ unless value.to_s.length <= max_digits.to_i
553
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.max_digits_exceeded', parameter: key, value: value, max_digits: max_digits) })
554
+ end
555
+
556
+ return true
557
+ end
558
+
559
+ def min_digits?(key, min_digits)
560
+ value = @attributes[key]
561
+
562
+ unless value.is_a?(Numeric)
563
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.must_be_numeric', parameter: key, value: value) })
564
+ end
565
+
566
+ unless value.to_s.length >= min_digits.to_i
567
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.min_digits_not_reached', parameter: key, value: value, min_digits: min_digits) })
568
+ end
569
+
570
+ return true
571
+ end
572
+
573
+ def one_of?(key, array)
574
+ value = @attributes[key]
575
+
576
+ array_parsed = JSON.parse(array).map(&:to_s)
577
+ if !array_parsed.include? value.to_s
578
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.one_of', parameter: key, value: value, values: array) })
579
+ end
580
+ return true
581
+ end
582
+
583
+ def subset_of?(key, array)
584
+ array?(key)
585
+ value = @attributes[key]
586
+ array_parsed = JSON.parse(array)
587
+
588
+ value.each do |element|
589
+ if !array_parsed.include? element
590
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.subset_of', parameter: key, value: value, values: array) })
591
+ end
592
+ end
593
+ end
594
+
595
+ def uniqueness?(key, model)
596
+ value = @attributes[key]
597
+
598
+ duplicated_instances = model.constantize.where(key => value)
599
+ duplicated_instances = duplicated_instances.where.not(key => @instance[key]) if @instance.present?
600
+
601
+ if duplicated_instances.count > 0
602
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.uniqueness', parameter: key, value: value) })
603
+ end
604
+ return true
605
+ end
606
+
607
+ def exists?(key, field, model)
608
+ value = @attributes[key]
609
+ if model.constantize.where(field.to_sym => value).count == 0
610
+ raise InvalidParametersException.new({ key => I18n.t('errors.messages.invalid_parameter.exists', parameter: key, value: value) })
611
+ end
612
+ return true
613
+ end
614
+
615
+
616
+ # This method analyzes the rules for a specific action and returns the modified rules.
617
+ #
618
+ # @param action [Symbol] The action for which the rules are analyzed. Example: :create
619
+ # @param rules [Hash] The original rules received. Example: { name: ['on_create|required', 'string'] }
620
+ #
621
+ # @return [Hash] The modified rules according to the specified action. Example: { name: ['required', 'string'] }
622
+ def self.parse_rules_for(action, rules)
623
+ rules_parsed = rules.deep_dup
624
+ case action
625
+ when :create
626
+ rules.each do |attribute, rule_list|
627
+ if rules_present?(rule_list, 'on_create')
628
+ rules_parsed[attribute] = remove_markers(rule_list, 'on_create|')
629
+ end
630
+ if rules_present?(rule_list, 'on_update')
631
+ rules_parsed[attribute] = remove_rules(rule_list, 'on_update')
632
+ end
633
+ end
634
+ when :update
635
+ rules.each do |attribute, rule_list|
636
+ if rules_present?(rule_list, 'on_update')
637
+ rules_parsed[attribute] = remove_markers(rule_list, 'on_update|')
638
+ end
639
+ if rules_present?(rule_list, 'on_create')
640
+ rules_parsed[attribute] = remove_rules(rule_list, 'on_create')
641
+ end
642
+ end
643
+ end
644
+ return rules_parsed
645
+ end
646
+
647
+ # Checks within a list of rules if any of them contains a specific marker.
648
+ #
649
+ # @param rule_list [Array] The list of rules to check. Example: ['on_create|required', 'string']
650
+ # @param marker [String] Marker to search for. Example: 'on_create'
651
+ #
652
+ # @return [Boolean] True if the rule is present, false otherwise. Example: true
653
+ def self.rules_present?(rule_list, marker) :boolean
654
+ rule_list.any? { |rule| rule.to_s.include?(marker) }
655
+ end
656
+
657
+ # Removes a specific marker from a list of rules.
658
+ #
659
+ # @param rule_list [Array] The list of rules to modify. Example: ['on_create|required', 'string']
660
+ # @param action [String] Marker to remove. Example: 'on_create'
661
+ #
662
+ # @return [Array] The list of rules without the marker. Example: ['required', 'string']
663
+ def self.remove_markers(rule_list, marker) :array
664
+ rule_list.map { |rule| rule.sub(marker, '') }
665
+ end
666
+
667
+ # Removes rules that contain a specific marker.
668
+ #
669
+ # @param rule_list [Array] The list of rules to modify. Example: ['on_create|required', 'string']
670
+ # @param action [String] Marker to search for to remove the rule. Example: 'on_create'
671
+ #
672
+ # @return [Array] The list of rules without the rules containing the marker. Example: ['string']
673
+ def self.remove_rules(rule_list, marker) :array
674
+ rule_list.select {|rule| !rule.index(marker)}
675
+ end
676
+ end
677
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuleBasedValidator
4
- VERSION = "0.1.0"
4
+ VERSION = '0.2.2'
5
5
  end
@@ -1,8 +1,7 @@
1
- # frozen_string_literal: true
1
+ module RuleBasedValidator
2
+ require_relative "rule_based_validator/validator"
2
3
 
3
- require_relative "rule_based_validator/version"
4
+ I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'rule_based_validator', 'locales', '*.yml')]
4
5
 
5
- module RuleBasedValidator
6
- class Error < StandardError; end
7
- # Your code goes here...
8
- end
6
+ I18n.default_locale = :es
7
+ end
metadata CHANGED
@@ -1,37 +1,156 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rule_based_validator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Prambs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-08 00:00:00.000000000 Z
11
+ date: 2024-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '3.4'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
27
- description: Validates a model based on a set of rules.
26
+ version: '3.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.14'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.14'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activemodel
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '7.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '7.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activerecord
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '7.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '7.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: i18n
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.14'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.14'
111
+ - !ruby/object:Gem::Dependency
112
+ name: json
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.7'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.7'
125
+ - !ruby/object:Gem::Dependency
126
+ name: date
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.3'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.3'
139
+ description: Validates a model or a hash based on a set of rules. The rules are defined
140
+ in a JSON file.
28
141
  email:
29
142
  - fprambsarmendariz@gmail.com
30
143
  executables: []
31
144
  extensions: []
32
145
  extra_rdoc_files: []
33
146
  files:
147
+ - config/initializers/rule_based_validator.rb
148
+ - lib/models/user.rb
34
149
  - lib/rule_based_validator.rb
150
+ - lib/rule_based_validator/exceptions.rb
151
+ - lib/rule_based_validator/locales/en.yml
152
+ - lib/rule_based_validator/locales/es.yml
153
+ - lib/rule_based_validator/validator.rb
35
154
  - lib/rule_based_validator/version.rb
36
155
  homepage: https://github.com/FintreeHQ/rule-based-validator
37
156
  licenses: