rule_based_validator 0.1.0 → 0.2.2

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