hash_validator 1.1.1 → 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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/.ruby-version +1 -1
  4. data/README.md +315 -56
  5. data/hash_validator.gemspec +1 -1
  6. data/lib/hash_validator/configuration.rb +16 -0
  7. data/lib/hash_validator/validators/alpha_validator.rb +15 -0
  8. data/lib/hash_validator/validators/alphanumeric_validator.rb +15 -0
  9. data/lib/hash_validator/validators/array_validator.rb +1 -1
  10. data/lib/hash_validator/validators/base.rb +28 -3
  11. data/lib/hash_validator/validators/boolean_validator.rb +3 -5
  12. data/lib/hash_validator/validators/class_validator.rb +1 -1
  13. data/lib/hash_validator/validators/digits_validator.rb +15 -0
  14. data/lib/hash_validator/validators/dynamic_func_validator.rb +26 -0
  15. data/lib/hash_validator/validators/dynamic_pattern_validator.rb +23 -0
  16. data/lib/hash_validator/validators/email_validator.rb +4 -6
  17. data/lib/hash_validator/validators/enumerable_validator.rb +4 -6
  18. data/lib/hash_validator/validators/hash_validator.rb +8 -3
  19. data/lib/hash_validator/validators/hex_color_validator.rb +15 -0
  20. data/lib/hash_validator/validators/ip_validator.rb +22 -0
  21. data/lib/hash_validator/validators/ipv4_validator.rb +18 -0
  22. data/lib/hash_validator/validators/ipv6_validator.rb +22 -0
  23. data/lib/hash_validator/validators/json_validator.rb +21 -0
  24. data/lib/hash_validator/validators/lambda_validator.rb +5 -8
  25. data/lib/hash_validator/validators/many_validator.rb +3 -3
  26. data/lib/hash_validator/validators/multiple_validator.rb +1 -1
  27. data/lib/hash_validator/validators/optional_validator.rb +1 -1
  28. data/lib/hash_validator/validators/presence_validator.rb +4 -6
  29. data/lib/hash_validator/validators/regex_validator.rb +4 -6
  30. data/lib/hash_validator/validators/simple_type_validators.rb +1 -1
  31. data/lib/hash_validator/validators/simple_validator.rb +2 -4
  32. data/lib/hash_validator/validators/url_validator.rb +21 -0
  33. data/lib/hash_validator/validators.rb +46 -4
  34. data/lib/hash_validator/version.rb +1 -1
  35. data/lib/hash_validator.rb +1 -0
  36. data/spec/configuration_spec.rb +189 -0
  37. data/spec/hash_validator_spec.rb +4 -4
  38. data/spec/validators/alpha_validator_spec.rb +93 -0
  39. data/spec/validators/alphanumeric_validator_spec.rb +99 -0
  40. data/spec/validators/base_spec.rb +2 -2
  41. data/spec/validators/digits_validator_spec.rb +99 -0
  42. data/spec/validators/dynamic_func_validator_spec.rb +252 -0
  43. data/spec/validators/dynamic_pattern_validator_spec.rb +150 -0
  44. data/spec/validators/hash_validator_spec.rb +102 -0
  45. data/spec/validators/hex_color_validator_spec.rb +111 -0
  46. data/spec/validators/ip_validator_spec.rb +105 -0
  47. data/spec/validators/ipv4_validator_spec.rb +99 -0
  48. data/spec/validators/ipv6_validator_spec.rb +99 -0
  49. data/spec/validators/json_validator_spec.rb +88 -0
  50. data/spec/validators/url_validator_spec.rb +75 -0
  51. data/spec/validators/user_defined_spec.rb +2 -2
  52. metadata +42 -4
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashValidator::Validator::AlphaValidator do
4
+ let(:validator) { HashValidator::Validator::AlphaValidator.new }
5
+
6
+ context 'valid alpha strings' do
7
+ it 'validates lowercase letters' do
8
+ errors = {}
9
+ validator.validate('key', 'abc', {}, errors)
10
+ expect(errors).to be_empty
11
+ end
12
+
13
+ it 'validates uppercase letters' do
14
+ errors = {}
15
+ validator.validate('key', 'ABC', {}, errors)
16
+ expect(errors).to be_empty
17
+ end
18
+
19
+ it 'validates mixed case letters' do
20
+ errors = {}
21
+ validator.validate('key', 'AbC', {}, errors)
22
+ expect(errors).to be_empty
23
+ end
24
+
25
+ it 'validates single letter' do
26
+ errors = {}
27
+ validator.validate('key', 'a', {}, errors)
28
+ expect(errors).to be_empty
29
+ end
30
+
31
+ it 'validates long strings' do
32
+ errors = {}
33
+ validator.validate('key', 'HelloWorld', {}, errors)
34
+ expect(errors).to be_empty
35
+ end
36
+ end
37
+
38
+ context 'invalid alpha strings' do
39
+ it 'rejects non-string values' do
40
+ errors = {}
41
+ validator.validate('key', 123, {}, errors)
42
+ expect(errors['key']).to eq('must contain only letters')
43
+ end
44
+
45
+ it 'rejects nil values' do
46
+ errors = {}
47
+ validator.validate('key', nil, {}, errors)
48
+ expect(errors['key']).to eq('must contain only letters')
49
+ end
50
+
51
+ it 'rejects strings with numbers' do
52
+ errors = {}
53
+ validator.validate('key', 'abc123', {}, errors)
54
+ expect(errors['key']).to eq('must contain only letters')
55
+ end
56
+
57
+ it 'rejects strings with spaces' do
58
+ errors = {}
59
+ validator.validate('key', 'abc def', {}, errors)
60
+ expect(errors['key']).to eq('must contain only letters')
61
+ end
62
+
63
+ it 'rejects strings with special characters' do
64
+ errors = {}
65
+ validator.validate('key', 'abc!', {}, errors)
66
+ expect(errors['key']).to eq('must contain only letters')
67
+ end
68
+
69
+ it 'rejects strings with hyphens' do
70
+ errors = {}
71
+ validator.validate('key', 'abc-def', {}, errors)
72
+ expect(errors['key']).to eq('must contain only letters')
73
+ end
74
+
75
+ it 'rejects strings with underscores' do
76
+ errors = {}
77
+ validator.validate('key', 'abc_def', {}, errors)
78
+ expect(errors['key']).to eq('must contain only letters')
79
+ end
80
+
81
+ it 'rejects empty strings' do
82
+ errors = {}
83
+ validator.validate('key', '', {}, errors)
84
+ expect(errors['key']).to eq('must contain only letters')
85
+ end
86
+
87
+ it 'rejects strings with periods' do
88
+ errors = {}
89
+ validator.validate('key', 'abc.def', {}, errors)
90
+ expect(errors['key']).to eq('must contain only letters')
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashValidator::Validator::AlphanumericValidator do
4
+ let(:validator) { HashValidator::Validator::AlphanumericValidator.new }
5
+
6
+ context 'valid alphanumeric strings' do
7
+ it 'validates letters only' do
8
+ errors = {}
9
+ validator.validate('key', 'abc', {}, errors)
10
+ expect(errors).to be_empty
11
+ end
12
+
13
+ it 'validates numbers only' do
14
+ errors = {}
15
+ validator.validate('key', '123', {}, errors)
16
+ expect(errors).to be_empty
17
+ end
18
+
19
+ it 'validates mixed letters and numbers' do
20
+ errors = {}
21
+ validator.validate('key', 'abc123', {}, errors)
22
+ expect(errors).to be_empty
23
+ end
24
+
25
+ it 'validates uppercase letters' do
26
+ errors = {}
27
+ validator.validate('key', 'ABC', {}, errors)
28
+ expect(errors).to be_empty
29
+ end
30
+
31
+ it 'validates mixed case letters' do
32
+ errors = {}
33
+ validator.validate('key', 'AbC123', {}, errors)
34
+ expect(errors).to be_empty
35
+ end
36
+
37
+ it 'validates single character' do
38
+ errors = {}
39
+ validator.validate('key', 'a', {}, errors)
40
+ expect(errors).to be_empty
41
+ end
42
+
43
+ it 'validates single digit' do
44
+ errors = {}
45
+ validator.validate('key', '1', {}, errors)
46
+ expect(errors).to be_empty
47
+ end
48
+ end
49
+
50
+ context 'invalid alphanumeric strings' do
51
+ it 'rejects non-string values' do
52
+ errors = {}
53
+ validator.validate('key', 123, {}, errors)
54
+ expect(errors['key']).to eq('must contain only letters and numbers')
55
+ end
56
+
57
+ it 'rejects nil values' do
58
+ errors = {}
59
+ validator.validate('key', nil, {}, errors)
60
+ expect(errors['key']).to eq('must contain only letters and numbers')
61
+ end
62
+
63
+ it 'rejects strings with spaces' do
64
+ errors = {}
65
+ validator.validate('key', 'abc 123', {}, errors)
66
+ expect(errors['key']).to eq('must contain only letters and numbers')
67
+ end
68
+
69
+ it 'rejects strings with special characters' do
70
+ errors = {}
71
+ validator.validate('key', 'abc!123', {}, errors)
72
+ expect(errors['key']).to eq('must contain only letters and numbers')
73
+ end
74
+
75
+ it 'rejects strings with hyphens' do
76
+ errors = {}
77
+ validator.validate('key', 'abc-123', {}, errors)
78
+ expect(errors['key']).to eq('must contain only letters and numbers')
79
+ end
80
+
81
+ it 'rejects strings with underscores' do
82
+ errors = {}
83
+ validator.validate('key', 'abc_123', {}, errors)
84
+ expect(errors['key']).to eq('must contain only letters and numbers')
85
+ end
86
+
87
+ it 'rejects empty strings' do
88
+ errors = {}
89
+ validator.validate('key', '', {}, errors)
90
+ expect(errors['key']).to eq('must contain only letters and numbers')
91
+ end
92
+
93
+ it 'rejects strings with periods' do
94
+ errors = {}
95
+ validator.validate('key', 'abc.123', {}, errors)
96
+ expect(errors['key']).to eq('must contain only letters and numbers')
97
+ end
98
+ end
99
+ 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
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashValidator::Validator::DigitsValidator do
4
+ let(:validator) { HashValidator::Validator::DigitsValidator.new }
5
+
6
+ context 'valid digit strings' do
7
+ it 'validates single digit' do
8
+ errors = {}
9
+ validator.validate('key', '1', {}, errors)
10
+ expect(errors).to be_empty
11
+ end
12
+
13
+ it 'validates multiple digits' do
14
+ errors = {}
15
+ validator.validate('key', '123', {}, errors)
16
+ expect(errors).to be_empty
17
+ end
18
+
19
+ it 'validates zero' do
20
+ errors = {}
21
+ validator.validate('key', '0', {}, errors)
22
+ expect(errors).to be_empty
23
+ end
24
+
25
+ it 'validates long number strings' do
26
+ errors = {}
27
+ validator.validate('key', '1234567890', {}, errors)
28
+ expect(errors).to be_empty
29
+ end
30
+
31
+ it 'validates leading zeros' do
32
+ errors = {}
33
+ validator.validate('key', '0123', {}, errors)
34
+ expect(errors).to be_empty
35
+ end
36
+ end
37
+
38
+ context 'invalid digit strings' do
39
+ it 'rejects non-string values' do
40
+ errors = {}
41
+ validator.validate('key', 123, {}, errors)
42
+ expect(errors['key']).to eq('must contain only digits')
43
+ end
44
+
45
+ it 'rejects nil values' do
46
+ errors = {}
47
+ validator.validate('key', nil, {}, errors)
48
+ expect(errors['key']).to eq('must contain only digits')
49
+ end
50
+
51
+ it 'rejects strings with letters' do
52
+ errors = {}
53
+ validator.validate('key', '123abc', {}, errors)
54
+ expect(errors['key']).to eq('must contain only digits')
55
+ end
56
+
57
+ it 'rejects strings with spaces' do
58
+ errors = {}
59
+ validator.validate('key', '123 456', {}, errors)
60
+ expect(errors['key']).to eq('must contain only digits')
61
+ end
62
+
63
+ it 'rejects strings with special characters' do
64
+ errors = {}
65
+ validator.validate('key', '123!', {}, errors)
66
+ expect(errors['key']).to eq('must contain only digits')
67
+ end
68
+
69
+ it 'rejects strings with hyphens' do
70
+ errors = {}
71
+ validator.validate('key', '123-456', {}, errors)
72
+ expect(errors['key']).to eq('must contain only digits')
73
+ end
74
+
75
+ it 'rejects strings with periods' do
76
+ errors = {}
77
+ validator.validate('key', '123.456', {}, errors)
78
+ expect(errors['key']).to eq('must contain only digits')
79
+ end
80
+
81
+ it 'rejects empty strings' do
82
+ errors = {}
83
+ validator.validate('key', '', {}, errors)
84
+ expect(errors['key']).to eq('must contain only digits')
85
+ end
86
+
87
+ it 'rejects negative signs' do
88
+ errors = {}
89
+ validator.validate('key', '-123', {}, errors)
90
+ expect(errors['key']).to eq('must contain only digits')
91
+ end
92
+
93
+ it 'rejects positive signs' do
94
+ errors = {}
95
+ validator.validate('key', '+123', {}, errors)
96
+ expect(errors['key']).to eq('must contain only digits')
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,252 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashValidator::Validator::DynamicFuncValidator do
4
+ describe 'Function-based validator' do
5
+ let(:odd_func_validator) do
6
+ HashValidator::Validator::DynamicFuncValidator.new(
7
+ 'odd_func',
8
+ ->(n) { n.is_a?(Integer) && n.odd? },
9
+ 'is not an odd integer'
10
+ )
11
+ end
12
+
13
+ let(:range_validator) do
14
+ HashValidator::Validator::DynamicFuncValidator.new(
15
+ 'in_range',
16
+ ->(n) { n.is_a?(Numeric) && n >= 18 && n <= 65 },
17
+ 'must be between 18 and 65'
18
+ )
19
+ end
20
+
21
+ let(:custom_validator) do
22
+ HashValidator::Validator::DynamicFuncValidator.new(
23
+ 'custom',
24
+ proc { |v| v.to_s.length > 5 },
25
+ 'must be longer than 5 characters'
26
+ )
27
+ end
28
+
29
+ let(:errors) { Hash.new }
30
+
31
+ describe '#initialize' do
32
+ it 'requires a callable object' do
33
+ expect {
34
+ HashValidator::Validator::DynamicFuncValidator.new('test', 'not_callable')
35
+ }.to raise_error(ArgumentError, 'Function must be callable (proc or lambda)')
36
+ end
37
+
38
+ it 'accepts a lambda' do
39
+ validator = HashValidator::Validator::DynamicFuncValidator.new(
40
+ 'test',
41
+ ->(v) { true }
42
+ )
43
+ expect(validator.func).to respond_to(:call)
44
+ end
45
+
46
+ it 'accepts a proc' do
47
+ validator = HashValidator::Validator::DynamicFuncValidator.new(
48
+ 'test',
49
+ proc { |v| true }
50
+ )
51
+ expect(validator.func).to respond_to(:call)
52
+ end
53
+
54
+ it 'accepts a custom error message' do
55
+ validator = HashValidator::Validator::DynamicFuncValidator.new(
56
+ 'test',
57
+ ->(v) { true },
58
+ 'custom error'
59
+ )
60
+ expect(validator.error_message).to eq('custom error')
61
+ end
62
+
63
+ it 'uses default error message when none provided' do
64
+ validator = HashValidator::Validator::DynamicFuncValidator.new(
65
+ 'test',
66
+ ->(v) { true }
67
+ )
68
+ expect(validator.error_message).to eq('test required')
69
+ end
70
+ end
71
+
72
+ describe '#should_validate?' do
73
+ it 'validates the correct name' do
74
+ expect(odd_func_validator.should_validate?('odd_func')).to eq true
75
+ end
76
+
77
+ it 'does not validate other names' do
78
+ expect(odd_func_validator.should_validate?('even_func')).to eq false
79
+ expect(odd_func_validator.should_validate?('string')).to eq false
80
+ end
81
+ end
82
+
83
+ describe '#validate' do
84
+ context 'with odd number function' do
85
+ it 'validates odd integers correctly' do
86
+ odd_func_validator.validate(:key, 1, {}, errors)
87
+ expect(errors).to be_empty
88
+
89
+ errors.clear
90
+ odd_func_validator.validate(:key, 13, {}, errors)
91
+ expect(errors).to be_empty
92
+
93
+ errors.clear
94
+ odd_func_validator.validate(:key, -7, {}, errors)
95
+ expect(errors).to be_empty
96
+ end
97
+
98
+ it 'rejects even integers' do
99
+ odd_func_validator.validate(:key, 2, {}, errors)
100
+ expect(errors).to eq({ key: 'is not an odd integer' })
101
+
102
+ errors.clear
103
+ odd_func_validator.validate(:key, 100, {}, errors)
104
+ expect(errors).to eq({ key: 'is not an odd integer' })
105
+ end
106
+
107
+ it 'rejects non-integers' do
108
+ odd_func_validator.validate(:key, 1.5, {}, errors)
109
+ expect(errors).to eq({ key: 'is not an odd integer' })
110
+
111
+ errors.clear
112
+ odd_func_validator.validate(:key, '1', {}, errors)
113
+ expect(errors).to eq({ key: 'is not an odd integer' })
114
+ end
115
+ end
116
+
117
+ context 'with range validator' do
118
+ it 'validates numbers in range' do
119
+ range_validator.validate(:key, 18, {}, errors)
120
+ expect(errors).to be_empty
121
+
122
+ errors.clear
123
+ range_validator.validate(:key, 30, {}, errors)
124
+ expect(errors).to be_empty
125
+
126
+ errors.clear
127
+ range_validator.validate(:key, 65, {}, errors)
128
+ expect(errors).to be_empty
129
+
130
+ errors.clear
131
+ range_validator.validate(:key, 25.5, {}, errors)
132
+ expect(errors).to be_empty
133
+ end
134
+
135
+ it 'rejects numbers outside range' do
136
+ range_validator.validate(:key, 17, {}, errors)
137
+ expect(errors).to eq({ key: 'must be between 18 and 65' })
138
+
139
+ errors.clear
140
+ range_validator.validate(:key, 66, {}, errors)
141
+ expect(errors).to eq({ key: 'must be between 18 and 65' })
142
+
143
+ errors.clear
144
+ range_validator.validate(:key, -5, {}, errors)
145
+ expect(errors).to eq({ key: 'must be between 18 and 65' })
146
+ end
147
+
148
+ it 'rejects non-numeric values' do
149
+ range_validator.validate(:key, 'twenty', {}, errors)
150
+ expect(errors).to eq({ key: 'must be between 18 and 65' })
151
+ end
152
+ end
153
+
154
+ context 'with custom validator using proc' do
155
+ it 'validates strings longer than 5 chars' do
156
+ custom_validator.validate(:key, 'hello world', {}, errors)
157
+ expect(errors).to be_empty
158
+
159
+ errors.clear
160
+ custom_validator.validate(:key, 'testing', {}, errors)
161
+ expect(errors).to be_empty
162
+ end
163
+
164
+ it 'rejects strings 5 chars or shorter' do
165
+ custom_validator.validate(:key, 'hello', {}, errors)
166
+ expect(errors).to eq({ key: 'must be longer than 5 characters' })
167
+
168
+ errors.clear
169
+ custom_validator.validate(:key, 'hi', {}, errors)
170
+ expect(errors).to eq({ key: 'must be longer than 5 characters' })
171
+ end
172
+
173
+ it 'converts values to strings' do
174
+ custom_validator.validate(:key, 123456, {}, errors)
175
+ expect(errors).to be_empty
176
+
177
+ errors.clear
178
+ custom_validator.validate(:key, 12345, {}, errors)
179
+ expect(errors).to eq({ key: 'must be longer than 5 characters' })
180
+ end
181
+ end
182
+
183
+ context 'with function that raises errors' do
184
+ let(:error_func_validator) do
185
+ HashValidator::Validator::DynamicFuncValidator.new(
186
+ 'error_func',
187
+ ->(v) { raise 'intentional error' },
188
+ 'validation failed'
189
+ )
190
+ end
191
+
192
+ it 'treats exceptions as validation failures' do
193
+ error_func_validator.validate(:key, 'any value', {}, errors)
194
+ expect(errors).to eq({ key: 'validation failed' })
195
+ end
196
+ end
197
+ end
198
+
199
+ describe 'Integration with HashValidator.add_validator' do
200
+ before do
201
+ HashValidator.add_validator('adult_age',
202
+ func: ->(age) { age.is_a?(Integer) && age >= 18 },
203
+ error_message: 'must be 18 or older')
204
+
205
+ HashValidator.add_validator('palindrome',
206
+ func: proc { |s| s.to_s == s.to_s.reverse },
207
+ error_message: 'must be a palindrome')
208
+ end
209
+
210
+ after do
211
+ HashValidator.remove_validator('adult_age')
212
+ HashValidator.remove_validator('palindrome')
213
+ end
214
+
215
+ it 'can register lambda-based validators' do
216
+ validator = HashValidator.validate(
217
+ { age: 25 },
218
+ { age: 'adult_age' }
219
+ )
220
+ expect(validator.valid?).to eq true
221
+ expect(validator.errors).to be_empty
222
+ end
223
+
224
+ it 'returns errors for invalid lambda validations' do
225
+ validator = HashValidator.validate(
226
+ { age: 16 },
227
+ { age: 'adult_age' }
228
+ )
229
+ expect(validator.valid?).to eq false
230
+ expect(validator.errors).to eq({ age: 'must be 18 or older' })
231
+ end
232
+
233
+ it 'can register proc-based validators' do
234
+ validator = HashValidator.validate(
235
+ { word: 'racecar' },
236
+ { word: 'palindrome' }
237
+ )
238
+ expect(validator.valid?).to eq true
239
+ expect(validator.errors).to be_empty
240
+ end
241
+
242
+ it 'returns errors for invalid proc validations' do
243
+ validator = HashValidator.validate(
244
+ { word: 'hello' },
245
+ { word: 'palindrome' }
246
+ )
247
+ expect(validator.valid?).to eq false
248
+ expect(validator.errors).to eq({ word: 'must be a palindrome' })
249
+ end
250
+ end
251
+ end
252
+ end