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
@@ -15,21 +15,18 @@ class HashValidator::Validator::LambdaValidator < HashValidator::Validator::Base
15
15
  end
16
16
  end
17
17
 
18
- def presence_error_message
18
+ def error_message
19
19
  'is not valid'
20
20
  end
21
21
 
22
- def validate(key, value, lambda, errors)
23
- unless lambda.call(value)
24
- errors[key] = presence_error_message
25
- end
26
-
22
+ def valid?(value, lambda)
23
+ lambda.call(value)
27
24
  rescue
28
- errors[key] = presence_error_message
25
+ false
29
26
  end
30
27
  end
31
28
 
32
29
  class HashValidator::Validator::LambdaValidator::InvalidArgumentCount < StandardError
33
30
  end
34
31
 
35
- HashValidator.append_validator(HashValidator::Validator::LambdaValidator.new)
32
+ HashValidator.add_validator(HashValidator::Validator::LambdaValidator.new)
@@ -9,13 +9,13 @@ module HashValidator
9
9
  validation.is_a?(Validations::Many)
10
10
  end
11
11
 
12
- def presence_error_message
12
+ def error_message
13
13
  "enumerable required"
14
14
  end
15
15
 
16
16
  def validate(key, value, validations, errors)
17
17
  unless value.is_a?(Enumerable)
18
- errors[key] = presence_error_message
18
+ errors[key] = error_message
19
19
  return
20
20
  end
21
21
 
@@ -37,4 +37,4 @@ module HashValidator
37
37
  end
38
38
  end
39
39
 
40
- HashValidator.append_validator(HashValidator::Validator::ManyValidator.new)
40
+ HashValidator.add_validator(HashValidator::Validator::ManyValidator.new)
@@ -24,4 +24,4 @@ module HashValidator
24
24
  end
25
25
  end
26
26
 
27
- HashValidator.append_validator(HashValidator::Validator::MultipleValidator.new)
27
+ HashValidator.add_validator(HashValidator::Validator::MultipleValidator.new)
@@ -19,4 +19,4 @@ module HashValidator
19
19
  end
20
20
  end
21
21
 
22
- HashValidator.append_validator(HashValidator::Validator::OptionalValidator.new)
22
+ HashValidator.add_validator(HashValidator::Validator::OptionalValidator.new)
@@ -3,15 +3,13 @@ class HashValidator::Validator::PresenceValidator < HashValidator::Validator::Ba
3
3
  super('required')
4
4
  end
5
5
 
6
- def presence_error_message
6
+ def error_message
7
7
  'is required'
8
8
  end
9
9
 
10
- def validate(key, value, _validations, errors)
11
- if value.nil?
12
- errors[key] = presence_error_message
13
- end
10
+ def valid?(value)
11
+ !value.nil?
14
12
  end
15
13
  end
16
14
 
17
- HashValidator.append_validator(HashValidator::Validator::PresenceValidator.new)
15
+ HashValidator.add_validator(HashValidator::Validator::PresenceValidator.new)
@@ -7,15 +7,13 @@ class HashValidator::Validator::RegexpValidator < HashValidator::Validator::Base
7
7
  rhs.is_a?(Regexp)
8
8
  end
9
9
 
10
- def presence_error_message
10
+ def error_message
11
11
  'does not match regular expression'
12
12
  end
13
13
 
14
- def validate(key, value, regexp, errors)
15
- unless regexp.match(value.to_s)
16
- errors[key] = presence_error_message
17
- end
14
+ def valid?(value, regexp)
15
+ regexp.match(value.to_s)
18
16
  end
19
17
  end
20
18
 
21
- HashValidator.append_validator(HashValidator::Validator::RegexpValidator.new)
19
+ HashValidator.add_validator(HashValidator::Validator::RegexpValidator.new)
@@ -13,5 +13,5 @@
13
13
  Time
14
14
  ].each do |type|
15
15
  name = type.to_s.gsub(/(.)([A-Z])/,'\1_\2').downcase # ActiveSupport/Inflector#underscore behaviour
16
- HashValidator.append_validator(HashValidator::Validator::SimpleValidator.new(name, lambda { |v| v.is_a?(type) }))
16
+ HashValidator.add_validator(HashValidator::Validator::SimpleValidator.new(name, lambda { |v| v.is_a?(type) }))
17
17
  end
@@ -12,9 +12,7 @@ class HashValidator::Validator::SimpleValidator < HashValidator::Validator::Base
12
12
  self.lambda = lambda
13
13
  end
14
14
 
15
- def validate(key, value, _validations, errors)
16
- unless lambda.call(value)
17
- errors[key] = presence_error_message
18
- end
15
+ def valid?(value)
16
+ lambda.call(value)
19
17
  end
20
18
  end
@@ -5,19 +5,12 @@ class HashValidator::Validator::UrlValidator < HashValidator::Validator::Base
5
5
  super('url') # The name of the validator
6
6
  end
7
7
 
8
- def presence_error_message
8
+ def error_message
9
9
  'is not a valid URL'
10
10
  end
11
11
 
12
- def validate(key, value, _validations, errors)
13
- unless value.is_a?(String) && valid_url?(value)
14
- errors[key] = presence_error_message
15
- end
16
- end
17
-
18
- private
19
-
20
- def valid_url?(value)
12
+ def valid?(value)
13
+ return false unless value.is_a?(String)
21
14
  uri = URI.parse(value)
22
15
  %w[http https ftp].include?(uri.scheme)
23
16
  rescue URI::InvalidURIError
@@ -25,4 +18,4 @@ class HashValidator::Validator::UrlValidator < HashValidator::Validator::Base
25
18
  end
26
19
  end
27
20
 
28
- HashValidator.append_validator(HashValidator::Validator::UrlValidator.new)
21
+ HashValidator.add_validator(HashValidator::Validator::UrlValidator.new)
@@ -2,9 +2,40 @@ module HashValidator
2
2
  @@validators = []
3
3
 
4
4
 
5
- def self.append_validator(validator)
6
- unless validator.is_a?(HashValidator::Validator::Base)
7
- raise StandardError.new('validators need to inherit from HashValidator::Validator::Base')
5
+ def self.remove_validator(name)
6
+ name = name.to_s
7
+ @@validators.reject! { |v| v.name == name }
8
+ end
9
+
10
+ def self.add_validator(*args)
11
+ validator = case args.length
12
+ when 1
13
+ # Instance-based validator (existing behavior)
14
+ validator_instance = args[0]
15
+ unless validator_instance.is_a?(HashValidator::Validator::Base)
16
+ raise StandardError.new('validators need to inherit from HashValidator::Validator::Base')
17
+ end
18
+ validator_instance
19
+ when 2
20
+ # Dynamic validator with options
21
+ name = args[0]
22
+ options = args[1]
23
+
24
+ if options.is_a?(Hash)
25
+ if options[:pattern]
26
+ # Pattern-based validator
27
+ HashValidator::Validator::DynamicPatternValidator.new(name, options[:pattern], options[:error_message])
28
+ elsif options[:func]
29
+ # Function-based validator
30
+ HashValidator::Validator::DynamicFuncValidator.new(name, options[:func], options[:error_message])
31
+ else
32
+ raise ArgumentError, 'Options hash must contain either :pattern or :func key'
33
+ end
34
+ else
35
+ raise ArgumentError, 'Second argument must be an options hash with :pattern or :func key'
36
+ end
37
+ else
38
+ raise ArgumentError, 'add_validator expects 1 argument (validator instance) or 2 arguments (name, options)'
8
39
  end
9
40
 
10
41
  if @@validators.detect { |v| v.name == validator.name }
@@ -24,6 +55,8 @@ end
24
55
 
25
56
  # Load validators
26
57
  require 'hash_validator/validators/base'
58
+ require 'hash_validator/validators/dynamic_pattern_validator'
59
+ require 'hash_validator/validators/dynamic_func_validator'
27
60
  require 'hash_validator/validators/simple_validator'
28
61
  require 'hash_validator/validators/class_validator'
29
62
  require 'hash_validator/validators/hash_validator'
@@ -43,4 +76,7 @@ require 'hash_validator/validators/json_validator'
43
76
  require 'hash_validator/validators/hex_color_validator'
44
77
  require 'hash_validator/validators/alphanumeric_validator'
45
78
  require 'hash_validator/validators/alpha_validator'
46
- require 'hash_validator/validators/digits_validator'
79
+ require 'hash_validator/validators/digits_validator'
80
+ require 'hash_validator/validators/ipv4_validator'
81
+ require 'hash_validator/validators/ipv6_validator'
82
+ require 'hash_validator/validators/ip_validator'
@@ -1,3 +1,3 @@
1
1
  module HashValidator
2
- VERSION = '1.2.0'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -20,3 +20,4 @@ require 'hash_validator/base'
20
20
  require 'hash_validator/version'
21
21
  require 'hash_validator/validators'
22
22
  require 'hash_validator/validations'
23
+ require 'hash_validator/configuration'
@@ -0,0 +1,189 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'HashValidator.configure' do
4
+ describe 'configuration DSL' do
5
+ after(:each) do
6
+ # Clean up any validators added during tests
7
+ HashValidator.remove_validator('test_pattern')
8
+ HashValidator.remove_validator('test_func')
9
+ HashValidator.remove_validator('bulk_odd')
10
+ HashValidator.remove_validator('bulk_even')
11
+ HashValidator.remove_validator('bulk_palindrome')
12
+ HashValidator.remove_validator('custom_configured')
13
+ end
14
+
15
+ it 'allows adding instance-based validators' do
16
+ class HashValidator::Validator::CustomConfiguredValidator < HashValidator::Validator::Base
17
+ def initialize
18
+ super('custom_configured')
19
+ end
20
+
21
+ def valid?(value)
22
+ value == 'configured'
23
+ end
24
+ end
25
+
26
+ HashValidator.configure do |config|
27
+ config.add_validator HashValidator::Validator::CustomConfiguredValidator.new
28
+ end
29
+
30
+ validator = HashValidator.validate(
31
+ { status: 'configured' },
32
+ { status: 'custom_configured' }
33
+ )
34
+ expect(validator.valid?).to eq true
35
+ end
36
+
37
+ it 'allows adding pattern-based validators' do
38
+ HashValidator.configure do |config|
39
+ config.add_validator 'test_pattern',
40
+ pattern: /\A[A-Z]{3}\z/,
41
+ error_message: 'must be three uppercase letters'
42
+ end
43
+
44
+ validator = HashValidator.validate(
45
+ { code: 'ABC' },
46
+ { code: 'test_pattern' }
47
+ )
48
+ expect(validator.valid?).to eq true
49
+
50
+ validator = HashValidator.validate(
51
+ { code: 'abc' },
52
+ { code: 'test_pattern' }
53
+ )
54
+ expect(validator.valid?).to eq false
55
+ expect(validator.errors).to eq({ code: 'must be three uppercase letters' })
56
+ end
57
+
58
+ it 'allows adding function-based validators' do
59
+ HashValidator.configure do |config|
60
+ config.add_validator 'test_func',
61
+ func: ->(v) { v.is_a?(Integer) && v > 100 },
62
+ error_message: 'must be an integer greater than 100'
63
+ end
64
+
65
+ validator = HashValidator.validate(
66
+ { score: 150 },
67
+ { score: 'test_func' }
68
+ )
69
+ expect(validator.valid?).to eq true
70
+
71
+ validator = HashValidator.validate(
72
+ { score: 50 },
73
+ { score: 'test_func' }
74
+ )
75
+ expect(validator.valid?).to eq false
76
+ expect(validator.errors).to eq({ score: 'must be an integer greater than 100' })
77
+ end
78
+
79
+ it 'allows adding multiple validators in one configure block' do
80
+ HashValidator.configure do |config|
81
+ config.add_validator 'bulk_odd',
82
+ pattern: /\A\d*[13579]\z/,
83
+ error_message: 'must be odd'
84
+
85
+ config.add_validator 'bulk_even',
86
+ pattern: /\A\d*[02468]\z/,
87
+ error_message: 'must be even'
88
+
89
+ config.add_validator 'bulk_palindrome',
90
+ func: proc { |s| s.to_s == s.to_s.reverse },
91
+ error_message: 'must be a palindrome'
92
+ end
93
+
94
+ # Test odd validator
95
+ validator = HashValidator.validate(
96
+ { num: '13' },
97
+ { num: 'bulk_odd' }
98
+ )
99
+ expect(validator.valid?).to eq true
100
+
101
+ # Test even validator
102
+ validator = HashValidator.validate(
103
+ { num: '24' },
104
+ { num: 'bulk_even' }
105
+ )
106
+ expect(validator.valid?).to eq true
107
+
108
+ # Test palindrome validator
109
+ validator = HashValidator.validate(
110
+ { word: 'level' },
111
+ { word: 'bulk_palindrome' }
112
+ )
113
+ expect(validator.valid?).to eq true
114
+ end
115
+
116
+ it 'allows removing validators within configure block' do
117
+ # First add a validator
118
+ HashValidator.add_validator 'test_pattern',
119
+ pattern: /test/,
120
+ error_message: 'test'
121
+
122
+ # Then remove it in configure
123
+ HashValidator.configure do |config|
124
+ config.remove_validator 'test_pattern'
125
+ end
126
+
127
+ # Should raise error as validator no longer exists
128
+ expect {
129
+ HashValidator.validate({ value: 'test' }, { value: 'test_pattern' })
130
+ }.to raise_error(StandardError, /Could not find valid validator/)
131
+ end
132
+
133
+ it 'works without a block' do
134
+ expect {
135
+ HashValidator.configure
136
+ }.not_to raise_error
137
+ end
138
+
139
+ it 'yields a Configuration instance' do
140
+ HashValidator.configure do |config|
141
+ expect(config).to be_a(HashValidator::Configuration)
142
+ end
143
+ end
144
+ end
145
+
146
+ describe 'Rails-style initializer example' do
147
+ after(:each) do
148
+ HashValidator.remove_validator('phone')
149
+ HashValidator.remove_validator('postal_code')
150
+ HashValidator.remove_validator('age_range')
151
+ end
152
+
153
+ it 'can be used like a Rails initializer' do
154
+ # This would typically be in config/initializers/hash_validator.rb
155
+ HashValidator.configure do |config|
156
+ # Add pattern validators
157
+ config.add_validator 'phone',
158
+ pattern: /\A\+?[1-9]\d{1,14}\z/,
159
+ error_message: 'must be a valid international phone number'
160
+
161
+ config.add_validator 'postal_code',
162
+ pattern: /\A[A-Z0-9]{3,10}\z/i,
163
+ error_message: 'must be a valid postal code'
164
+
165
+ # Add function validator
166
+ config.add_validator 'age_range',
167
+ func: ->(age) { age.is_a?(Integer) && age.between?(0, 120) },
168
+ error_message: 'must be between 0 and 120'
169
+ end
170
+
171
+ # Use the configured validators
172
+ validator = HashValidator.validate(
173
+ {
174
+ phone: '+14155551234',
175
+ postal_code: 'SW1A1AA',
176
+ age: 30
177
+ },
178
+ {
179
+ phone: 'phone',
180
+ postal_code: 'postal_code',
181
+ age: 'age_range'
182
+ }
183
+ )
184
+
185
+ expect(validator.valid?).to eq true
186
+ expect(validator.errors).to be_empty
187
+ end
188
+ end
189
+ end
@@ -7,20 +7,20 @@ describe HashValidator do
7
7
 
8
8
  it 'allows validators with unique names' do
9
9
  expect {
10
- HashValidator.append_validator(new_validator1)
10
+ HashValidator.add_validator(new_validator1)
11
11
  }.to_not raise_error
12
12
  end
13
13
 
14
14
  it 'does not allow validators with conflicting names' do
15
15
  expect {
16
- HashValidator.append_validator(new_validator2)
17
- HashValidator.append_validator(new_validator2)
16
+ HashValidator.add_validator(new_validator2)
17
+ HashValidator.add_validator(new_validator2)
18
18
  }.to raise_error(StandardError, 'validators need to have unique names')
19
19
  end
20
20
 
21
21
  it 'does not allow validators that do not inherit from the base validator class' do
22
22
  expect {
23
- HashValidator.append_validator('Not a validator')
23
+ HashValidator.add_validator('Not a validator')
24
24
  }.to raise_error(StandardError, 'validators need to inherit from HashValidator::Validator::Base')
25
25
  end
26
26
  end
@@ -16,8 +16,8 @@ describe HashValidator::Validator::Base do
16
16
  describe '#validate' do
17
17
  let(:validator) { HashValidator::Validator::Base.new('test') }
18
18
 
19
- it 'throws an exception as base validators cant actually validate' do
20
- expect { validator.validate('key', 'value', {}, {}) }.to raise_error(StandardError, 'validate should not be called directly on BaseValidator')
19
+ it 'throws an exception as base validators must implement valid? or override validate' do
20
+ expect { validator.validate('key', 'value', {}, {}) }.to raise_error(StandardError, 'Validator must implement either valid? or override validate method')
21
21
  end
22
22
  end
23
23
  end