hash_validator 1.2.0 → 2.0.0

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +173 -12
  3. data/lib/hash_validator/configuration.rb +16 -0
  4. data/lib/hash_validator/validators/alpha_validator.rb +4 -12
  5. data/lib/hash_validator/validators/alphanumeric_validator.rb +4 -12
  6. data/lib/hash_validator/validators/array_validator.rb +1 -1
  7. data/lib/hash_validator/validators/base.rb +28 -3
  8. data/lib/hash_validator/validators/boolean_validator.rb +3 -5
  9. data/lib/hash_validator/validators/class_validator.rb +1 -1
  10. data/lib/hash_validator/validators/digits_validator.rb +4 -12
  11. data/lib/hash_validator/validators/dynamic_func_validator.rb +26 -0
  12. data/lib/hash_validator/validators/dynamic_pattern_validator.rb +23 -0
  13. data/lib/hash_validator/validators/email_validator.rb +4 -6
  14. data/lib/hash_validator/validators/enumerable_validator.rb +4 -6
  15. data/lib/hash_validator/validators/hash_validator.rb +2 -2
  16. data/lib/hash_validator/validators/hex_color_validator.rb +4 -12
  17. data/lib/hash_validator/validators/ip_validator.rb +22 -0
  18. data/lib/hash_validator/validators/ipv4_validator.rb +18 -0
  19. data/lib/hash_validator/validators/ipv6_validator.rb +22 -0
  20. data/lib/hash_validator/validators/json_validator.rb +4 -11
  21. data/lib/hash_validator/validators/lambda_validator.rb +5 -8
  22. data/lib/hash_validator/validators/many_validator.rb +3 -3
  23. data/lib/hash_validator/validators/multiple_validator.rb +1 -1
  24. data/lib/hash_validator/validators/optional_validator.rb +1 -1
  25. data/lib/hash_validator/validators/presence_validator.rb +4 -6
  26. data/lib/hash_validator/validators/regex_validator.rb +4 -6
  27. data/lib/hash_validator/validators/simple_type_validators.rb +1 -1
  28. data/lib/hash_validator/validators/simple_validator.rb +2 -4
  29. data/lib/hash_validator/validators/url_validator.rb +4 -11
  30. data/lib/hash_validator/validators.rb +40 -4
  31. data/lib/hash_validator/version.rb +1 -1
  32. data/lib/hash_validator.rb +1 -0
  33. data/spec/configuration_spec.rb +189 -0
  34. data/spec/hash_validator_spec.rb +4 -4
  35. data/spec/validators/base_spec.rb +2 -2
  36. data/spec/validators/dynamic_func_validator_spec.rb +252 -0
  37. data/spec/validators/dynamic_pattern_validator_spec.rb +150 -0
  38. data/spec/validators/ip_validator_spec.rb +105 -0
  39. data/spec/validators/ipv4_validator_spec.rb +99 -0
  40. data/spec/validators/ipv6_validator_spec.rb +99 -0
  41. data/spec/validators/user_defined_spec.rb +2 -2
  42. metadata +20 -3
  43. data/Plan.md +0 -309
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30fa62bd65cac6823fc1de4b4039397e3926d9a141d758523db6cec2c6837042
4
- data.tar.gz: 444275199c9e1a25cc7966ea32776923b2d988d1a881dcfb1f2d7bebea62641c
3
+ metadata.gz: 01246137fa6612aec48d3ebabfb97fa856482f73d0827c520765ad636690cc85
4
+ data.tar.gz: 4e20f8ad39a7a5f1b4f978d5f832ae30a6896687dc3373d7885c41e637efbad4
5
5
  SHA512:
6
- metadata.gz: 937942a775e75724c7e04eeecabd15ce1fb796ce25838de469508120e0fc78d7890d3bb3210f4bfa7c2ee74a528cb7a1b501e02b587a5b35a565b510b9d1072b
7
- data.tar.gz: 1a894031114c6a2a72bc6ca4f03e33190e6466355b053a08b0728a28d89ceda98ceb65c77d948a148bef99bf55cbf8025a554d90a1cc3e691d24417a8c176301
6
+ metadata.gz: a6503553a0ec7c997006ac24dc183ddfbda551c2a94daaefff08d40733fbf474f2e99d181feccde06faa2312b9bb0d6d7c5eaac810a5d776dd928b8f21946253
7
+ data.tar.gz: 7710ce2712a4e67f0e164bd8ca648c4233ecab28e73542fe93b9dbc52cda2a77c1ebb5eee2e22608ca52a398cba0c9c21c99ec744dc155c512a08f751d404d31
data/README.md CHANGED
@@ -55,7 +55,7 @@ validator.errors # => {}
55
55
 
56
56
  ### Failed Validation
57
57
  ```ruby
58
- validations = {
58
+ validations = {
59
59
  user: {
60
60
  first_name: 'string',
61
61
  age: 'integer',
@@ -63,7 +63,7 @@ validations = {
63
63
  }
64
64
  }
65
65
 
66
- hash = {
66
+ hash = {
67
67
  user: {
68
68
  first_name: 'James',
69
69
  age: 'thirty', # Should be integer
@@ -92,9 +92,9 @@ class UsersController < ApplicationController
92
92
  }
93
93
  }
94
94
  }
95
-
95
+
96
96
  validator = HashValidator.validate(params, validations)
97
-
97
+
98
98
  if validator.valid?
99
99
  user = User.create(params[:user])
100
100
  render json: { user: user }, status: :created
@@ -109,7 +109,7 @@ end
109
109
  # {
110
110
  # "user": {
111
111
  # "name": "John Doe",
112
- # "email": "john@example.com",
112
+ # "email": "john@example.com",
113
113
  # "age": 30,
114
114
  # "website": "https://johndoe.com",
115
115
  # "preferences": {
@@ -137,6 +137,9 @@ Define a validation hash which will be used to validate. This hash can be nested
137
137
  | `float` | `{ price: 'float' }` | `{ price: 19.99 }` |
138
138
  | `hex_color` | `{ color: 'hex_color' }` | `{ color: '#ff0000' }` |
139
139
  | `integer` | `{ age: 'integer' }` | `{ age: 25 }` |
140
+ | `ip` | `{ address: 'ip' }` | `{ address: '192.168.1.1' }` |
141
+ | `ipv4` | `{ address: 'ipv4' }` | `{ address: '10.0.0.1' }` |
142
+ | `ipv6` | `{ address: 'ipv6' }` | `{ address: '2001:db8::1' }` |
140
143
  | `json` | `{ config: 'json' }` | `{ config: '{"theme": "dark"}' }` |
141
144
  | `numeric` | `{ score: 'numeric' }` | `{ score: 95.5 }` |
142
145
  | `range` | `{ priority: 1..10 }` | `{ priority: 5 }` |
@@ -202,7 +205,11 @@ This is particularly useful when combining built-in validators with custom valid
202
205
 
203
206
  ## Custom Validations
204
207
 
205
- Allows custom defined validations (must inherit from `HashValidator::Validator::Base`). Example:
208
+ Allows custom defined validations (must inherit from `HashValidator::Validator::Base`).
209
+
210
+ ### Simple Example (using `valid?`)
211
+
212
+ For simple boolean validations, implement the `valid?` method:
206
213
 
207
214
  ```ruby
208
215
  # Define our custom validator
@@ -211,22 +218,176 @@ class HashValidator::Validator::OddValidator < HashValidator::Validator::Base
211
218
  super('odd') # The name of the validator
212
219
  end
213
220
 
214
- def validate(key, value, validations, errors)
215
- unless value.is_a?(Integer) && value.odd?
216
- errors[key] = presence_error_message
217
- end
221
+ def error_message
222
+ 'must be an odd number'
223
+ end
224
+
225
+ def valid?(value)
226
+ value.is_a?(Integer) && value.odd?
218
227
  end
219
228
  end
220
229
 
221
230
  # Add the validator
222
- HashValidator.append_validator(HashValidator::Validator::OddValidator.new)
231
+ HashValidator.add_validator(HashValidator::Validator::OddValidator.new)
223
232
 
224
- # Now the validator can be used! e.g.
233
+ # Now the validator can be used!
225
234
  validator = HashValidator.validate({ age: 27 }, { age: 'odd' })
226
235
  validator.valid? # => true
227
236
  validator.errors # => {}
237
+
238
+ validator = HashValidator.validate({ age: 26 }, { age: 'odd' })
239
+ validator.valid? # => false
240
+ validator.errors # => { age: 'must be an odd number' }
228
241
  ```
229
242
 
243
+ ### Complex Example (using `validate`)
244
+
245
+ For more complex validations that need access to the validation parameters or custom error handling, override the `validate` method:
246
+
247
+ ```ruby
248
+ # Define a validator that checks if a number is within a range
249
+ class HashValidator::Validator::RangeValidator < HashValidator::Validator::Base
250
+ def initialize
251
+ super('_range') # Underscore prefix as it's invoked through the validation parameter
252
+ end
253
+
254
+ def should_validate?(validation)
255
+ validation.is_a?(Range)
256
+ end
257
+
258
+ def error_message
259
+ 'is out of range'
260
+ end
261
+
262
+ def validate(key, value, range, errors)
263
+ unless range.include?(value)
264
+ errors[key] = "must be between #{range.min} and #{range.max}"
265
+ end
266
+ end
267
+ end
268
+
269
+ # Add the validator
270
+ HashValidator.add_validator(HashValidator::Validator::RangeValidator.new)
271
+
272
+ # Now the validator can be used with Range objects!
273
+ validator = HashValidator.validate({ age: 25 }, { age: 18..65 })
274
+ validator.valid? # => true
275
+ validator.errors # => {}
276
+
277
+ validator = HashValidator.validate({ age: 10 }, { age: 18..65 })
278
+ validator.valid? # => false
279
+ validator.errors # => { age: 'must be between 18 and 65' }
280
+ ```
281
+
282
+ ## Simple Custom Validators
283
+
284
+ For simpler use cases, you can define custom validators without creating a full class using pattern matching or custom functions.
285
+
286
+ ### Configuration DSL
287
+
288
+ Use the configuration DSL to define multiple validators at once, similar to a Rails initializer:
289
+
290
+ ```ruby
291
+ # In a Rails app, this would go in config/initializers/hash_validator.rb
292
+ HashValidator.configure do |config|
293
+ # Add instance-based validators
294
+ config.add_validator HashValidator::Validator::CustomValidator.new
295
+
296
+ # Add pattern-based validators
297
+ config.add_validator 'phone',
298
+ pattern: /\A\+?[1-9]\d{1,14}\z/,
299
+ error_message: 'must be a valid international phone number'
300
+
301
+ config.add_validator 'postal_code',
302
+ pattern: /\A[A-Z0-9]{3,10}\z/i,
303
+ error_message: 'must be a valid postal code'
304
+
305
+ # Add function-based validators
306
+ config.add_validator 'adult',
307
+ func: ->(age) { age.is_a?(Integer) && age >= 18 },
308
+ error_message: 'must be 18 or older'
309
+
310
+ config.add_validator 'business_hours',
311
+ func: ->(hour) { hour.between?(9, 17) },
312
+ error_message: 'must be between 9 AM and 5 PM'
313
+ end
314
+ ```
315
+
316
+ ### Pattern-Based Validators
317
+
318
+ Use regular expressions to validate string formats:
319
+
320
+ ```ruby
321
+ # Add a validator for odd numbers using a pattern
322
+ HashValidator.add_validator('odd_string',
323
+ pattern: /\A\d*[13579]\z/,
324
+ error_message: 'must be an odd number string')
325
+
326
+ # Add a validator for US phone numbers
327
+ HashValidator.add_validator('us_phone',
328
+ pattern: /\A\d{3}-\d{3}-\d{4}\z/,
329
+ error_message: 'must be a valid US phone number (XXX-XXX-XXXX)')
330
+
331
+ # Use the validators
332
+ validator = HashValidator.validate(
333
+ { number: '27', phone: '555-123-4567' },
334
+ { number: 'odd_string', phone: 'us_phone' }
335
+ )
336
+ validator.valid? # => true
337
+
338
+ validator = HashValidator.validate(
339
+ { number: '26', phone: '5551234567' },
340
+ { number: 'odd_string', phone: 'us_phone' }
341
+ )
342
+ validator.valid? # => false
343
+ validator.errors # => { number: 'must be an odd number string', phone: 'must be a valid US phone number (XXX-XXX-XXXX)' }
344
+ ```
345
+
346
+ ### Function-Based Validators
347
+
348
+ Use lambdas or procs for custom validation logic:
349
+
350
+ ```ruby
351
+ # Add a validator for adult age using a lambda
352
+ HashValidator.add_validator('adult_age',
353
+ func: ->(age) { age.is_a?(Integer) && age >= 18 },
354
+ error_message: 'must be 18 or older')
355
+
356
+ # Add a validator for palindromes using a proc
357
+ HashValidator.add_validator('palindrome',
358
+ func: proc { |str| str.to_s == str.to_s.reverse },
359
+ error_message: 'must be a palindrome')
360
+
361
+ # Use the validators
362
+ validator = HashValidator.validate(
363
+ { age: 25, word: 'racecar' },
364
+ { age: 'adult_age', word: 'palindrome' }
365
+ )
366
+ validator.valid? # => true
367
+
368
+ validator = HashValidator.validate(
369
+ { age: 16, word: 'hello' },
370
+ { age: 'adult_age', word: 'palindrome' }
371
+ )
372
+ validator.valid? # => false
373
+ validator.errors # => { age: 'must be 18 or older', word: 'must be a palindrome' }
374
+ ```
375
+
376
+ ### Removing Custom Validators
377
+
378
+ You can remove custom validators when they're no longer needed:
379
+
380
+ ```ruby
381
+ # Remove a specific validator
382
+ HashValidator.remove_validator('adult_age')
383
+ ```
384
+
385
+ These simple validators are ideal for:
386
+ - Quick format validation without regex in your main code
387
+ - Reusable validation logic across your application
388
+ - Keeping validation definitions close to your configuration
389
+ - Avoiding the overhead of creating full validator classes for simple rules
390
+
230
391
  ## Contributing
231
392
 
232
393
  1. Fork it
@@ -0,0 +1,16 @@
1
+ module HashValidator
2
+ class Configuration
3
+ def add_validator(*args)
4
+ HashValidator.add_validator(*args)
5
+ end
6
+
7
+ def remove_validator(name)
8
+ HashValidator.remove_validator(name)
9
+ end
10
+ end
11
+
12
+ def self.configure
13
+ config = Configuration.new
14
+ yield(config) if block_given?
15
+ end
16
+ end
@@ -3,21 +3,13 @@ class HashValidator::Validator::AlphaValidator < HashValidator::Validator::Base
3
3
  super('alpha') # The name of the validator
4
4
  end
5
5
 
6
- def presence_error_message
6
+ def error_message
7
7
  'must contain only letters'
8
8
  end
9
9
 
10
- def validate(key, value, _validations, errors)
11
- unless value.is_a?(String) && valid_alpha?(value)
12
- errors[key] = presence_error_message
13
- end
14
- end
15
-
16
- private
17
-
18
- def valid_alpha?(value)
19
- /\A[a-zA-Z]+\z/.match?(value)
10
+ def valid?(value)
11
+ value.is_a?(String) && /\A[a-zA-Z]+\z/.match?(value)
20
12
  end
21
13
  end
22
14
 
23
- HashValidator.append_validator(HashValidator::Validator::AlphaValidator.new)
15
+ HashValidator.add_validator(HashValidator::Validator::AlphaValidator.new)
@@ -3,21 +3,13 @@ class HashValidator::Validator::AlphanumericValidator < HashValidator::Validator
3
3
  super('alphanumeric') # The name of the validator
4
4
  end
5
5
 
6
- def presence_error_message
6
+ def error_message
7
7
  'must contain only letters and numbers'
8
8
  end
9
9
 
10
- def validate(key, value, _validations, errors)
11
- unless value.is_a?(String) && valid_alphanumeric?(value)
12
- errors[key] = presence_error_message
13
- end
14
- end
15
-
16
- private
17
-
18
- def valid_alphanumeric?(value)
19
- /\A[a-zA-Z0-9]+\z/.match?(value)
10
+ def valid?(value)
11
+ value.is_a?(String) && /\A[a-zA-Z0-9]+\z/.match?(value)
20
12
  end
21
13
  end
22
14
 
23
- HashValidator.append_validator(HashValidator::Validator::AlphanumericValidator.new)
15
+ HashValidator.add_validator(HashValidator::Validator::AlphanumericValidator.new)
@@ -77,4 +77,4 @@ class HashValidator::Validator::ArrayValidator < HashValidator::Validator::Base
77
77
  end
78
78
  end
79
79
 
80
- HashValidator.append_validator(HashValidator::Validator::ArrayValidator.new)
80
+ HashValidator.add_validator(HashValidator::Validator::ArrayValidator.new)
@@ -14,11 +14,36 @@ class HashValidator::Validator::Base
14
14
  self.name == name.to_s
15
15
  end
16
16
 
17
- def presence_error_message
17
+ def error_message
18
18
  "#{self.name} required"
19
19
  end
20
20
 
21
- def validate(*)
22
- raise StandardError.new('validate should not be called directly on BaseValidator')
21
+ def validate(key, value, validations, errors)
22
+ # If the subclass implements valid?, use that for simple boolean validation
23
+ if self.class.instance_methods(false).include?(:valid?)
24
+ # Check the arity of the valid? method to determine how many arguments to pass
25
+ valid_result = case method(:valid?).arity
26
+ when 1
27
+ valid?(value)
28
+ when 2
29
+ valid?(value, validations)
30
+ else
31
+ raise StandardError.new("valid? method must accept either 1 argument (value) or 2 arguments (value, validations)")
32
+ end
33
+
34
+ unless valid_result
35
+ errors[key] = error_message
36
+ end
37
+ else
38
+ # Otherwise, subclass must override validate
39
+ raise StandardError.new('Validator must implement either valid? or override validate method')
40
+ end
23
41
  end
42
+
43
+ # Subclasses can optionally implement this for simple boolean validation
44
+ # Return true if valid, false if invalid
45
+ # Either:
46
+ # def valid?(value) # For simple validations
47
+ # def valid?(value, validations) # When validation context is needed
48
+ # end
24
49
  end
@@ -3,11 +3,9 @@ class HashValidator::Validator::BooleanValidator < HashValidator::Validator::Bas
3
3
  super('boolean') # The name of the validator
4
4
  end
5
5
 
6
- def validate(key, value, _validations, errors)
7
- unless [TrueClass, FalseClass].include?(value.class)
8
- errors[key] = presence_error_message
9
- end
6
+ def valid?(value)
7
+ [TrueClass, FalseClass].include?(value.class)
10
8
  end
11
9
  end
12
10
 
13
- HashValidator.append_validator(HashValidator::Validator::BooleanValidator.new)
11
+ HashValidator.add_validator(HashValidator::Validator::BooleanValidator.new)
@@ -14,4 +14,4 @@ class HashValidator::Validator::ClassValidator < HashValidator::Validator::Base
14
14
  end
15
15
  end
16
16
 
17
- HashValidator.append_validator(HashValidator::Validator::ClassValidator.new)
17
+ HashValidator.add_validator(HashValidator::Validator::ClassValidator.new)
@@ -3,21 +3,13 @@ class HashValidator::Validator::DigitsValidator < HashValidator::Validator::Base
3
3
  super('digits') # The name of the validator
4
4
  end
5
5
 
6
- def presence_error_message
6
+ def error_message
7
7
  'must contain only digits'
8
8
  end
9
9
 
10
- def validate(key, value, _validations, errors)
11
- unless value.is_a?(String) && valid_digits?(value)
12
- errors[key] = presence_error_message
13
- end
14
- end
15
-
16
- private
17
-
18
- def valid_digits?(value)
19
- /\A\d+\z/.match?(value)
10
+ def valid?(value)
11
+ value.is_a?(String) && /\A\d+\z/.match?(value)
20
12
  end
21
13
  end
22
14
 
23
- HashValidator.append_validator(HashValidator::Validator::DigitsValidator.new)
15
+ HashValidator.add_validator(HashValidator::Validator::DigitsValidator.new)
@@ -0,0 +1,26 @@
1
+ class HashValidator::Validator::DynamicFuncValidator < HashValidator::Validator::Base
2
+ attr_accessor :func, :custom_error_message
3
+
4
+ def initialize(name, func, error_message = nil)
5
+ super(name)
6
+
7
+ unless func.respond_to?(:call)
8
+ raise ArgumentError, "Function must be callable (proc or lambda)"
9
+ end
10
+
11
+ @func = func
12
+ @custom_error_message = error_message
13
+ end
14
+
15
+ def error_message
16
+ @custom_error_message || super
17
+ end
18
+
19
+ def valid?(value)
20
+ begin
21
+ !!@func.call(value)
22
+ rescue => e
23
+ false
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ class HashValidator::Validator::DynamicPatternValidator < HashValidator::Validator::Base
2
+ attr_accessor :pattern, :custom_error_message
3
+
4
+ def initialize(name, pattern, error_message = nil)
5
+ super(name)
6
+
7
+ unless pattern.is_a?(Regexp)
8
+ raise ArgumentError, "Pattern must be a regular expression"
9
+ end
10
+
11
+ @pattern = pattern
12
+ @custom_error_message = error_message
13
+ end
14
+
15
+ def error_message
16
+ @custom_error_message || super
17
+ end
18
+
19
+ def valid?(value)
20
+ return false unless value.respond_to?(:to_s)
21
+ @pattern.match?(value.to_s)
22
+ end
23
+ end
@@ -3,15 +3,13 @@ class HashValidator::Validator::EmailValidator < HashValidator::Validator::Base
3
3
  super('email') # The name of the validator
4
4
  end
5
5
 
6
- def presence_error_message
6
+ def error_message
7
7
  'is not a valid email'
8
8
  end
9
9
 
10
- def validate(key, value, _validations, errors)
11
- unless value.is_a?(String) && value.include?("@")
12
- errors[key] = presence_error_message
13
- end
10
+ def valid?(value)
11
+ value.is_a?(String) && value.include?("@")
14
12
  end
15
13
  end
16
14
 
17
- HashValidator.append_validator(HashValidator::Validator::EmailValidator.new)
15
+ HashValidator.add_validator(HashValidator::Validator::EmailValidator.new)
@@ -7,15 +7,13 @@ class HashValidator::Validator::EnumerableValidator < HashValidator::Validator::
7
7
  rhs.is_a?(Enumerable)
8
8
  end
9
9
 
10
- def presence_error_message
10
+ def error_message
11
11
  'value from list required'
12
12
  end
13
13
 
14
- def validate(key, value, validations, errors)
15
- unless validations.include?(value)
16
- errors[key] = presence_error_message
17
- end
14
+ def valid?(value, validations)
15
+ validations.include?(value)
18
16
  end
19
17
  end
20
18
 
21
- HashValidator.append_validator(HashValidator::Validator::EnumerableValidator.new)
19
+ HashValidator.add_validator(HashValidator::Validator::EnumerableValidator.new)
@@ -15,7 +15,7 @@ class HashValidator::Validator::HashValidator < HashValidator::Validator::Base
15
15
 
16
16
  # Validate hash
17
17
  unless value.is_a?(Hash)
18
- errors[key] = presence_error_message
18
+ errors[key] = error_message
19
19
  return
20
20
  end
21
21
 
@@ -38,4 +38,4 @@ class HashValidator::Validator::HashValidator < HashValidator::Validator::Base
38
38
  end
39
39
 
40
40
 
41
- HashValidator.append_validator(HashValidator::Validator::HashValidator.new)
41
+ HashValidator.add_validator(HashValidator::Validator::HashValidator.new)
@@ -3,21 +3,13 @@ class HashValidator::Validator::HexColorValidator < HashValidator::Validator::Ba
3
3
  super('hex_color') # The name of the validator
4
4
  end
5
5
 
6
- def presence_error_message
6
+ def error_message
7
7
  'is not a valid hex color'
8
8
  end
9
9
 
10
- def validate(key, value, _validations, errors)
11
- unless value.is_a?(String) && valid_hex_color?(value)
12
- errors[key] = presence_error_message
13
- end
14
- end
15
-
16
- private
17
-
18
- def valid_hex_color?(value)
19
- /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/.match?(value)
10
+ def valid?(value)
11
+ value.is_a?(String) && /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/.match?(value)
20
12
  end
21
13
  end
22
14
 
23
- HashValidator.append_validator(HashValidator::Validator::HexColorValidator.new)
15
+ HashValidator.add_validator(HashValidator::Validator::HexColorValidator.new)
@@ -0,0 +1,22 @@
1
+ require 'ipaddr'
2
+
3
+ class HashValidator::Validator::IpValidator < HashValidator::Validator::Base
4
+ def initialize
5
+ super('ip') # The name of the validator
6
+ end
7
+
8
+ def error_message
9
+ 'is not a valid IP address'
10
+ end
11
+
12
+ def valid?(value)
13
+ return false unless value.is_a?(String)
14
+ # Use IPAddr to validate both IPv4 and IPv6 addresses
15
+ IPAddr.new(value)
16
+ true
17
+ rescue IPAddr::Error, IPAddr::InvalidAddressError
18
+ false
19
+ end
20
+ end
21
+
22
+ HashValidator.add_validator(HashValidator::Validator::IpValidator.new)
@@ -0,0 +1,18 @@
1
+ class HashValidator::Validator::Ipv4Validator < HashValidator::Validator::Base
2
+ def initialize
3
+ super('ipv4') # The name of the validator
4
+ end
5
+
6
+ def error_message
7
+ 'is not a valid IPv4 address'
8
+ end
9
+
10
+ def valid?(value)
11
+ return false unless value.is_a?(String)
12
+ # IPv4 regex: 4 octets, each 0-255
13
+ ipv4_regex = /\A(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\z/
14
+ value.match?(ipv4_regex)
15
+ end
16
+ end
17
+
18
+ HashValidator.add_validator(HashValidator::Validator::Ipv4Validator.new)
@@ -0,0 +1,22 @@
1
+ require 'ipaddr'
2
+
3
+ class HashValidator::Validator::Ipv6Validator < HashValidator::Validator::Base
4
+ def initialize
5
+ super('ipv6') # The name of the validator
6
+ end
7
+
8
+ def error_message
9
+ 'is not a valid IPv6 address'
10
+ end
11
+
12
+ def valid?(value)
13
+ return false unless value.is_a?(String)
14
+ # Use IPAddr to validate IPv6 addresses (handles standard and compressed notation)
15
+ addr = IPAddr.new(value)
16
+ addr.ipv6?
17
+ rescue IPAddr::Error, IPAddr::InvalidAddressError
18
+ false
19
+ end
20
+ end
21
+
22
+ HashValidator.add_validator(HashValidator::Validator::Ipv6Validator.new)
@@ -5,19 +5,12 @@ class HashValidator::Validator::JsonValidator < HashValidator::Validator::Base
5
5
  super('json') # The name of the validator
6
6
  end
7
7
 
8
- def presence_error_message
8
+ def error_message
9
9
  'is not valid JSON'
10
10
  end
11
11
 
12
- def validate(key, value, _validations, errors)
13
- unless value.is_a?(String) && valid_json?(value)
14
- errors[key] = presence_error_message
15
- end
16
- end
17
-
18
- private
19
-
20
- def valid_json?(value)
12
+ def valid?(value)
13
+ return false unless value.is_a?(String)
21
14
  JSON.parse(value)
22
15
  true
23
16
  rescue JSON::ParserError
@@ -25,4 +18,4 @@ class HashValidator::Validator::JsonValidator < HashValidator::Validator::Base
25
18
  end
26
19
  end
27
20
 
28
- HashValidator.append_validator(HashValidator::Validator::JsonValidator.new)
21
+ HashValidator.add_validator(HashValidator::Validator::JsonValidator.new)