hash_validator 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +27 -0
- data/.ruby-version +1 -1
- data/Plan.md +309 -0
- data/README.md +157 -60
- data/hash_validator.gemspec +5 -5
- data/lib/hash_validator/validators/alpha_validator.rb +23 -0
- data/lib/hash_validator/validators/alphanumeric_validator.rb +23 -0
- data/lib/hash_validator/validators/array_validator.rb +15 -1
- data/lib/hash_validator/validators/digits_validator.rb +23 -0
- data/lib/hash_validator/validators/hash_validator.rb +6 -1
- data/lib/hash_validator/validators/hex_color_validator.rb +23 -0
- data/lib/hash_validator/validators/json_validator.rb +28 -0
- data/lib/hash_validator/validators/url_validator.rb +28 -0
- data/lib/hash_validator/validators.rb +7 -1
- data/lib/hash_validator/version.rb +1 -1
- data/spec/spec_helper.rb +1 -4
- data/spec/validators/alpha_validator_spec.rb +93 -0
- data/spec/validators/alphanumeric_validator_spec.rb +99 -0
- data/spec/validators/digits_validator_spec.rb +99 -0
- data/spec/validators/hash_validator_spec.rb +102 -0
- data/spec/validators/hex_color_validator_spec.rb +111 -0
- data/spec/validators/json_validator_spec.rb +88 -0
- data/spec/validators/url_validator_spec.rb +75 -0
- metadata +26 -19
- data/.travis.yml +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30fa62bd65cac6823fc1de4b4039397e3926d9a141d758523db6cec2c6837042
|
4
|
+
data.tar.gz: 444275199c9e1a25cc7966ea32776923b2d988d1a881dcfb1f2d7bebea62641c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 937942a775e75724c7e04eeecabd15ce1fb796ce25838de469508120e0fc78d7890d3bb3210f4bfa7c2ee74a528cb7a1b501e02b587a5b35a565b510b9d1072b
|
7
|
+
data.tar.gz: 1a894031114c6a2a72bc6ca4f03e33190e6466355b053a08b0728a28d89ceda98ceb65c77d948a148bef99bf55cbf8025a554d90a1cc3e691d24417a8c176301
|
@@ -0,0 +1,27 @@
|
|
1
|
+
name: Test
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ "master" ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ "master" ]
|
8
|
+
|
9
|
+
permissions:
|
10
|
+
contents: read
|
11
|
+
|
12
|
+
jobs:
|
13
|
+
spec:
|
14
|
+
name: "RSpec / Ruby ${{ matrix.ruby }}"
|
15
|
+
runs-on: ubuntu-24.04
|
16
|
+
strategy:
|
17
|
+
fail-fast: false
|
18
|
+
matrix:
|
19
|
+
ruby: ["3.0", "3.1", "3.2", "3.3", "3.4"]
|
20
|
+
steps:
|
21
|
+
- run: sudo apt-get install libcurl4-openssl-dev
|
22
|
+
- uses: actions/checkout@v4
|
23
|
+
- uses: ruby/setup-ruby@v1
|
24
|
+
with:
|
25
|
+
ruby-version: ${{ matrix.ruby }}
|
26
|
+
bundler-cache: true
|
27
|
+
- run: bundle exec rake spec
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.0.7
|
data/Plan.md
ADDED
@@ -0,0 +1,309 @@
|
|
1
|
+
# Hash Validator Enhancement Plan - Living Document
|
2
|
+
|
3
|
+
## Document Instructions
|
4
|
+
**This is a living plan document.** It should be updated throughout the implementation process to track:
|
5
|
+
- Current progress and completion status
|
6
|
+
- Any deviations or improvements discovered during implementation
|
7
|
+
- Lessons learned that may benefit subsequent phases
|
8
|
+
- Test results and quality check outcomes
|
9
|
+
|
10
|
+
**Update Protocol:**
|
11
|
+
1. Mark tasks as `[x]` when completed
|
12
|
+
2. Add notes in **Implementation Notes** sections for important discoveries
|
13
|
+
3. Update status indicators for each phase
|
14
|
+
4. Document any blockers or changes in approach
|
15
|
+
|
16
|
+
## Overall Objective
|
17
|
+
Add 11 new validators to the hash_validator gem to expand its validation capabilities for common use cases.
|
18
|
+
|
19
|
+
## Phase Structure
|
20
|
+
Between each major phase:
|
21
|
+
1. Run full test suite: `rake spec`
|
22
|
+
2. Verify no breaking changes to existing functionality
|
23
|
+
3. Provide summary to user
|
24
|
+
4. Request approval before proceeding to next phase
|
25
|
+
|
26
|
+
---
|
27
|
+
|
28
|
+
## Phase 1: Simple String Pattern Validators
|
29
|
+
**Status:** [ ] Not Started | [ ] In Progress | [x] Completed
|
30
|
+
**Validators:** URL, JSON, Hex Color, Alphanumeric, Alpha, Digits
|
31
|
+
|
32
|
+
### Tasks:
|
33
|
+
- [x] Create URL validator (`lib/hash_validator/validators/url_validator.rb`)
|
34
|
+
- Validation: Use `URI.parse` and check for valid scheme
|
35
|
+
- Error message: "is not a valid URL"
|
36
|
+
- Accept: http, https, ftp schemes
|
37
|
+
|
38
|
+
- [x] Create JSON validator (`lib/hash_validator/validators/json_validator.rb`)
|
39
|
+
- Validation: `JSON.parse(value)` with rescue
|
40
|
+
- Error message: "is not valid JSON"
|
41
|
+
- Must be string type first
|
42
|
+
|
43
|
+
- [x] Create Hex Color validator (`lib/hash_validator/validators/hex_color_validator.rb`)
|
44
|
+
- Validation: Regex `/\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/`
|
45
|
+
- Error message: "is not a valid hex color"
|
46
|
+
- Accept: #RGB and #RRGGBB formats
|
47
|
+
|
48
|
+
- [x] Create Alphanumeric validator (`lib/hash_validator/validators/alphanumeric_validator.rb`)
|
49
|
+
- Validation: Regex `/\A[a-zA-Z0-9]+\z/`
|
50
|
+
- Error message: "must contain only letters and numbers"
|
51
|
+
|
52
|
+
- [x] Create Alpha validator (`lib/hash_validator/validators/alpha_validator.rb`)
|
53
|
+
- Validation: Regex `/\A[a-zA-Z]+\z/`
|
54
|
+
- Error message: "must contain only letters"
|
55
|
+
|
56
|
+
- [x] Create Digits validator (`lib/hash_validator/validators/digits_validator.rb`)
|
57
|
+
- Validation: Regex `/\A\d+\z/`
|
58
|
+
- Error message: "must contain only digits"
|
59
|
+
|
60
|
+
### Tests:
|
61
|
+
- [x] Write specs for each validator in `spec/validators/[validator_name]_spec.rb`
|
62
|
+
- [x] Test valid inputs, invalid inputs, nil values, wrong types
|
63
|
+
- [x] Run test suite and ensure all pass
|
64
|
+
|
65
|
+
### Registration:
|
66
|
+
- [x] Add requires to `lib/hash_validator/validators.rb`
|
67
|
+
- [x] Ensure validators auto-register via `HashValidator.append_validator`
|
68
|
+
|
69
|
+
**Implementation Notes:**
|
70
|
+
- **Ruby Version Updated:** Changed minimum Ruby requirement from 2.0.0 to 3.0.0 in gemspec
|
71
|
+
- **URI Library:** Confirmed URI standard library is available in Ruby 3.0+ and works perfectly for URL validation
|
72
|
+
- **JSON Library:** Confirmed JSON standard library is available in Ruby 3.0+ and works perfectly for JSON validation
|
73
|
+
- **All Tests Pass:** 74 new tests added (70 validator tests + 4 Rails integration tests), all passing (451 total)
|
74
|
+
- **Regex Patterns:** All regex patterns use `\A` and `\z` anchors for exact string matching
|
75
|
+
- **Error Messages:** Follow established pattern ("is not a valid [type]" vs "must contain only [constraint]")
|
76
|
+
- **Performance:** All validators use simple regex patterns for optimal performance
|
77
|
+
- **Rails Integration:** Added ActionController::Parameters support - no more need for `.to_unsafe_h`
|
78
|
+
- **README Updated:** Comprehensive table format with validation configs and example payloads, Rails controller example
|
79
|
+
- **Documentation:** Added Requirements section, Quick Start, better Examples section structure
|
80
|
+
|
81
|
+
---
|
82
|
+
|
83
|
+
## Additional Improvements (Beyond Original Plan)
|
84
|
+
**Status:** [x] Completed
|
85
|
+
**Description:** Enhancements made during Phase 1 implementation
|
86
|
+
|
87
|
+
### Rails Integration Enhancement:
|
88
|
+
- [x] **ActionController::Parameters Support**: Modified hash validator to automatically detect and handle Rails params
|
89
|
+
- [x] **Seamless Conversion**: Internally calls `.to_unsafe_h` when `ActionController::Parameters` detected
|
90
|
+
- [x] **Backward Compatibility**: Regular Hash validation continues to work unchanged
|
91
|
+
- [x] **Integration Tests**: Added 4 comprehensive tests for Rails params support
|
92
|
+
|
93
|
+
### Documentation Improvements:
|
94
|
+
- [x] **README Restructuring**: Better flow from Installation → Quick Start → Examples → Usage Table → Advanced Features
|
95
|
+
- [x] **Usage Table Enhancement**: Three-column table with Validator, Configuration, and Example Payload
|
96
|
+
- [x] **Rails Controller Example**: Practical example showing API parameter validation
|
97
|
+
- [x] **Requirements Section**: Added Ruby 3.0+ requirement documentation
|
98
|
+
- [x] **Quick Start Guide**: Simple 4-line example to get users started immediately
|
99
|
+
|
100
|
+
### Quality Improvements:
|
101
|
+
- [x] **Test Coverage**: Increased from 447 to 451 total tests
|
102
|
+
- [x] **Error Handling**: Verified all new validators provide clear, consistent error messages
|
103
|
+
- [x] **Performance**: All new validators use efficient regex patterns
|
104
|
+
- [x] **Integration**: Full compatibility testing with existing codebase
|
105
|
+
|
106
|
+
**Implementation Notes:**
|
107
|
+
- **Rails Support**: Uses `defined?(ActionController::Parameters)` for safe detection without requiring Rails
|
108
|
+
- **User Experience**: Rails developers can now use `HashValidator.validate(params, validations)` directly
|
109
|
+
- **Maintenance**: No breaking changes introduced, all existing APIs remain unchanged
|
110
|
+
|
111
|
+
---
|
112
|
+
|
113
|
+
## Phase 2: IP Address Validators
|
114
|
+
**Status:** [ ] Not Started | [ ] In Progress | [ ] Completed
|
115
|
+
**Validators:** IP (generic), IPv4, IPv6
|
116
|
+
|
117
|
+
### Tasks:
|
118
|
+
- [ ] Create IPv4 validator (`lib/hash_validator/validators/ipv4_validator.rb`)
|
119
|
+
- Validation: Check 4 octets, each 0-255
|
120
|
+
- 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/`
|
121
|
+
- Error message: "is not a valid IPv4 address"
|
122
|
+
|
123
|
+
- [ ] Create IPv6 validator (`lib/hash_validator/validators/ipv6_validator.rb`)
|
124
|
+
- Validation: Standard IPv6 format including compressed notation
|
125
|
+
- Consider using `IPAddr` class for validation
|
126
|
+
- Error message: "is not a valid IPv6 address"
|
127
|
+
|
128
|
+
- [ ] Create IP validator (`lib/hash_validator/validators/ip_validator.rb`)
|
129
|
+
- Validation: Accept either IPv4 or IPv6
|
130
|
+
- Can leverage IPv4 and IPv6 validators
|
131
|
+
- Error message: "is not a valid IP address"
|
132
|
+
|
133
|
+
### Tests:
|
134
|
+
- [ ] Test standard IPv4 addresses (192.168.1.1)
|
135
|
+
- [ ] Test IPv6 addresses (standard and compressed)
|
136
|
+
- [ ] Test edge cases (0.0.0.0, 255.255.255.255, ::1)
|
137
|
+
- [ ] Test invalid formats
|
138
|
+
|
139
|
+
### Registration:
|
140
|
+
- [ ] Add requires to `lib/hash_validator/validators.rb`
|
141
|
+
|
142
|
+
**Implementation Notes:**
|
143
|
+
<!-- Add discoveries and changes here during implementation -->
|
144
|
+
|
145
|
+
---
|
146
|
+
|
147
|
+
## Phase 3: Parameterized Validators - Length
|
148
|
+
**Status:** [ ] Not Started | [ ] In Progress | [ ] Completed
|
149
|
+
**Validators:** Length with min/max/exactly options
|
150
|
+
|
151
|
+
### Tasks:
|
152
|
+
- [ ] Create Length validation class (`lib/hash_validator/validations/length.rb`)
|
153
|
+
- Similar structure to `Optional`, `Many`, `Multiple` classes
|
154
|
+
- Store min, max, exactly options
|
155
|
+
|
156
|
+
- [ ] Create Length validator (`lib/hash_validator/validators/length_validator.rb`)
|
157
|
+
- Handle strings, arrays, hashes
|
158
|
+
- Support options: `min`, `max`, `exactly`
|
159
|
+
- Dynamic error messages based on constraints
|
160
|
+
|
161
|
+
- [ ] Add factory method to `lib/hash_validator.rb`:
|
162
|
+
```ruby
|
163
|
+
def self.length(options)
|
164
|
+
Validations::Length.new(options)
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
### Usage Examples:
|
169
|
+
```ruby
|
170
|
+
HashValidator.length(min: 5, max: 10)
|
171
|
+
HashValidator.length(exactly: 8)
|
172
|
+
HashValidator.length(min: 5)
|
173
|
+
```
|
174
|
+
|
175
|
+
### Tests:
|
176
|
+
- [ ] Test all option combinations
|
177
|
+
- [ ] Test with strings, arrays, hashes
|
178
|
+
- [ ] Test boundary conditions
|
179
|
+
- [ ] Test invalid option combinations
|
180
|
+
|
181
|
+
**Implementation Notes:**
|
182
|
+
<!-- Add discoveries and changes here during implementation -->
|
183
|
+
|
184
|
+
---
|
185
|
+
|
186
|
+
## Phase 4: Parameterized Validators - Numeric Ranges
|
187
|
+
**Status:** [ ] Not Started | [ ] In Progress | [ ] Completed
|
188
|
+
**Validators:** Between, GreaterThan, LessThan, GreaterThanOrEqual, LessThanOrEqual
|
189
|
+
|
190
|
+
### Tasks:
|
191
|
+
- [ ] Create Range validation classes in `lib/hash_validator/validations/`:
|
192
|
+
- `between.rb` - Between two values (inclusive)
|
193
|
+
- `greater_than.rb` - Greater than value
|
194
|
+
- `less_than.rb` - Less than value
|
195
|
+
- `greater_than_or_equal.rb` - Greater than or equal
|
196
|
+
- `less_than_or_equal.rb` - Less than or equal
|
197
|
+
|
198
|
+
- [ ] Create corresponding validators in `lib/hash_validator/validators/`
|
199
|
+
|
200
|
+
- [ ] Add factory methods to `lib/hash_validator.rb`:
|
201
|
+
```ruby
|
202
|
+
def self.between(min, max)
|
203
|
+
Validations::Between.new(min, max)
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.greater_than(value)
|
207
|
+
Validations::GreaterThan.new(value)
|
208
|
+
end
|
209
|
+
# ... etc
|
210
|
+
```
|
211
|
+
|
212
|
+
### Usage Examples:
|
213
|
+
```ruby
|
214
|
+
HashValidator.between(1, 100)
|
215
|
+
HashValidator.greater_than(0)
|
216
|
+
HashValidator.less_than_or_equal(100)
|
217
|
+
```
|
218
|
+
|
219
|
+
### Tests:
|
220
|
+
- [ ] Test numeric types (Integer, Float, Rational)
|
221
|
+
- [ ] Test boundary values
|
222
|
+
- [ ] Test with non-numeric types
|
223
|
+
- [ ] Test with nil
|
224
|
+
|
225
|
+
**Implementation Notes:**
|
226
|
+
<!-- Add discoveries and changes here during implementation -->
|
227
|
+
|
228
|
+
---
|
229
|
+
|
230
|
+
## Phase 5: Documentation and Final Testing
|
231
|
+
**Status:** [ ] Not Started | [ ] In Progress | [ ] Completed
|
232
|
+
|
233
|
+
### Tasks:
|
234
|
+
- [ ] Update README.md with all new validators
|
235
|
+
- Add to simple types list
|
236
|
+
- Add to "Additional validations" section
|
237
|
+
- Provide usage examples for each
|
238
|
+
|
239
|
+
- [ ] Update CLAUDE.md if needed
|
240
|
+
|
241
|
+
- [ ] Run full test suite
|
242
|
+
|
243
|
+
- [ ] Check for any performance impacts
|
244
|
+
|
245
|
+
- [ ] Verify backward compatibility
|
246
|
+
|
247
|
+
### Documentation Sections to Update:
|
248
|
+
- [ ] Usage section - add new simple validators
|
249
|
+
- [ ] Additional validations - add parameterized validators
|
250
|
+
- [ ] Examples section - comprehensive examples
|
251
|
+
|
252
|
+
**Implementation Notes:**
|
253
|
+
<!-- Add discoveries and changes here during implementation -->
|
254
|
+
|
255
|
+
---
|
256
|
+
|
257
|
+
## Quality Checklist (Run after each phase)
|
258
|
+
- [ ] All new tests pass
|
259
|
+
- [ ] All existing tests still pass
|
260
|
+
- [ ] No rubocop/style violations (if configured)
|
261
|
+
- [ ] Documentation is updated
|
262
|
+
- [ ] Code follows existing patterns
|
263
|
+
|
264
|
+
## Technical Decisions & Rationale
|
265
|
+
|
266
|
+
### Pattern Consistency
|
267
|
+
All validators follow the existing pattern:
|
268
|
+
1. Inherit from `HashValidator::Validator::Base`
|
269
|
+
2. Initialize with validator name
|
270
|
+
3. Implement `validate` method
|
271
|
+
4. Auto-register using `HashValidator.append_validator`
|
272
|
+
|
273
|
+
### Error Message Style
|
274
|
+
Consistent with existing validators:
|
275
|
+
- Simple validators: "is not a valid [type]"
|
276
|
+
- Constraint validators: "must be [constraint]"
|
277
|
+
- Type validators: "[type] required"
|
278
|
+
|
279
|
+
### Validation Approach
|
280
|
+
- **URL**: Use Ruby's built-in `URI.parse` for robustness
|
281
|
+
- **IP Addresses**: Regex for IPv4, consider `IPAddr` for IPv6
|
282
|
+
- **JSON**: Use `JSON.parse` with exception handling
|
283
|
+
- **Patterns**: Simple regex patterns for performance
|
284
|
+
|
285
|
+
### File Organization
|
286
|
+
- Simple validators: `lib/hash_validator/validators/[name]_validator.rb`
|
287
|
+
- Parameterized validations: `lib/hash_validator/validations/[name].rb`
|
288
|
+
- Tests mirror structure: `spec/validators/[name]_spec.rb`
|
289
|
+
|
290
|
+
## Notes for Future Sessions
|
291
|
+
When resuming work on this plan:
|
292
|
+
1. Check this document's status indicators
|
293
|
+
2. Review Implementation Notes for each completed phase
|
294
|
+
3. Run test suite to verify current state
|
295
|
+
4. Continue from the next uncompleted task
|
296
|
+
|
297
|
+
## Completion Criteria
|
298
|
+
- [x] Phase 1: All 6 string pattern validators implemented and tested
|
299
|
+
- [ ] Phase 2: All 3 IP address validators implemented and tested
|
300
|
+
- [ ] Phase 3: Length validator implemented and tested
|
301
|
+
- [ ] Phase 4: All 5 numeric range validators implemented and tested
|
302
|
+
- [x] README fully updated with new validators and Rails integration
|
303
|
+
- [x] All tests passing (451/451)
|
304
|
+
- [x] Rails ActionController::Parameters support added
|
305
|
+
- [x] Ruby 3.0+ minimum version requirement updated
|
306
|
+
- [ ] Gem builds successfully
|
307
|
+
- [ ] Version bump considered
|
308
|
+
|
309
|
+
**Progress: Phase 1 Complete (6/11 validators) + Major Rails Integration Enhancement**
|
data/README.md
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
# Hash Validator
|
2
2
|
|
3
3
|
[](https://rubygems.org/gems/hash_validator)
|
4
|
-
|
5
|
-
[](https://coveralls.io/r/jamesbrooks/hash_validator)
|
4
|
+

|
6
5
|
[](https://codeclimate.com/github/JamesBrooks/hash_validator/maintainability)
|
7
6
|
|
8
7
|
Ruby library to validate hashes (Hash) against user-defined requirements
|
9
8
|
|
9
|
+
## Requirements
|
10
|
+
|
11
|
+
* Ruby 3.0+
|
12
|
+
|
10
13
|
## Installation
|
11
14
|
|
12
15
|
Add this line to your application's Gemfile:
|
@@ -21,76 +24,183 @@ Or install it yourself as:
|
|
21
24
|
|
22
25
|
$ gem install hash_validator
|
23
26
|
|
24
|
-
##
|
27
|
+
## Quick Start
|
25
28
|
|
26
29
|
```ruby
|
27
|
-
|
28
|
-
|
30
|
+
require 'hash_validator'
|
31
|
+
|
32
|
+
# Define validation rules
|
33
|
+
validations = { name: 'string', age: 'integer', email: 'email' }
|
34
|
+
|
35
|
+
# Validate a hash
|
36
|
+
validator = HashValidator.validate(
|
37
|
+
{ name: 'John', age: 30, email: 'john@example.com' },
|
38
|
+
validations
|
39
|
+
)
|
40
|
+
|
41
|
+
validator.valid? # => true
|
42
|
+
```
|
43
|
+
|
44
|
+
## Examples
|
45
|
+
|
46
|
+
### Successful Validation
|
47
|
+
```ruby
|
48
|
+
validations = { name: 'string', active: 'boolean', tags: 'array' }
|
49
|
+
hash = { name: 'Product', active: true, tags: ['new', 'featured'] }
|
50
|
+
|
51
|
+
validator = HashValidator.validate(hash, validations)
|
52
|
+
validator.valid? # => true
|
53
|
+
validator.errors # => {}
|
54
|
+
```
|
55
|
+
|
56
|
+
### Failed Validation
|
57
|
+
```ruby
|
58
|
+
validations = {
|
29
59
|
user: {
|
30
|
-
first_name:
|
31
|
-
|
32
|
-
|
33
|
-
likes: 'array'
|
60
|
+
first_name: 'string',
|
61
|
+
age: 'integer',
|
62
|
+
email: 'email'
|
34
63
|
}
|
35
64
|
}
|
36
65
|
|
37
|
-
|
38
|
-
hash = {
|
39
|
-
foo: 1,
|
40
|
-
bar: 'baz',
|
66
|
+
hash = {
|
41
67
|
user: {
|
42
68
|
first_name: 'James',
|
43
|
-
|
69
|
+
age: 'thirty', # Should be integer
|
70
|
+
# email missing
|
44
71
|
}
|
45
72
|
}
|
46
73
|
|
47
74
|
validator = HashValidator.validate(hash, validations)
|
75
|
+
validator.valid? # => false
|
76
|
+
validator.errors # => { user: { age: "integer required", email: "email required" } }
|
77
|
+
```
|
48
78
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
:
|
55
|
-
|
56
|
-
|
57
|
-
|
79
|
+
### Rails Controller Example
|
80
|
+
```ruby
|
81
|
+
class UsersController < ApplicationController
|
82
|
+
def create
|
83
|
+
validations = {
|
84
|
+
user: {
|
85
|
+
name: 'string',
|
86
|
+
email: 'email',
|
87
|
+
age: 'integer',
|
88
|
+
website: 'url',
|
89
|
+
preferences: {
|
90
|
+
theme: ['light', 'dark'],
|
91
|
+
notifications: 'boolean'
|
92
|
+
}
|
58
93
|
}
|
59
94
|
}
|
95
|
+
|
96
|
+
validator = HashValidator.validate(params, validations)
|
97
|
+
|
98
|
+
if validator.valid?
|
99
|
+
user = User.create(params[:user])
|
100
|
+
render json: { user: user }, status: :created
|
101
|
+
else
|
102
|
+
render json: { errors: validator.errors }, status: :unprocessable_entity
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Example request that would pass validation:
|
108
|
+
# POST /users
|
109
|
+
# {
|
110
|
+
# "user": {
|
111
|
+
# "name": "John Doe",
|
112
|
+
# "email": "john@example.com",
|
113
|
+
# "age": 30,
|
114
|
+
# "website": "https://johndoe.com",
|
115
|
+
# "preferences": {
|
116
|
+
# "theme": "dark",
|
117
|
+
# "notifications": true
|
118
|
+
# }
|
119
|
+
# }
|
120
|
+
# }
|
60
121
|
```
|
61
122
|
|
62
123
|
## Usage
|
63
124
|
|
64
|
-
Define a validation hash which will be used to validate. This
|
125
|
+
Define a validation hash which will be used to validate. This hash can be nested as deeply as required using the following validators:
|
126
|
+
|
127
|
+
| Validator | Validation Configuration | Example Valid Payload |
|
128
|
+
|-----------|-------------------------|----------------------|
|
129
|
+
| `alpha` | `{ name: 'alpha' }` | `{ name: 'James' }` |
|
130
|
+
| `alphanumeric` | `{ username: 'alphanumeric' }` | `{ username: 'user123' }` |
|
131
|
+
| `array` | `{ tags: 'array' }` | `{ tags: ['ruby', 'rails'] }` |
|
132
|
+
| `boolean` | `{ active: 'boolean' }` | `{ active: true }` |
|
133
|
+
| `complex` | `{ number: 'complex' }` | `{ number: Complex(1, 2) }` |
|
134
|
+
| `digits` | `{ zip_code: 'digits' }` | `{ zip_code: '12345' }` |
|
135
|
+
| `email` | `{ contact: 'email' }` | `{ contact: 'user@example.com' }` |
|
136
|
+
| `enumerable` | `{ status: ['active', 'inactive'] }` | `{ status: 'active' }` |
|
137
|
+
| `float` | `{ price: 'float' }` | `{ price: 19.99 }` |
|
138
|
+
| `hex_color` | `{ color: 'hex_color' }` | `{ color: '#ff0000' }` |
|
139
|
+
| `integer` | `{ age: 'integer' }` | `{ age: 25 }` |
|
140
|
+
| `json` | `{ config: 'json' }` | `{ config: '{"theme": "dark"}' }` |
|
141
|
+
| `numeric` | `{ score: 'numeric' }` | `{ score: 95.5 }` |
|
142
|
+
| `range` | `{ priority: 1..10 }` | `{ priority: 5 }` |
|
143
|
+
| `rational` | `{ ratio: 'rational' }` | `{ ratio: Rational(3, 4) }` |
|
144
|
+
| `regexp` | `{ code: /^[A-Z]{3}$/ }` | `{ code: 'ABC' }` |
|
145
|
+
| `required` | `{ id: 'required' }` | `{ id: 'any_value' }` |
|
146
|
+
| `string` | `{ title: 'string' }` | `{ title: 'Hello World' }` |
|
147
|
+
| `symbol` | `{ type: 'symbol' }` | `{ type: :user }` |
|
148
|
+
| `time` | `{ created_at: 'time' }` | `{ created_at: Time.now }` |
|
149
|
+
| `url` | `{ website: 'url' }` | `{ website: 'https://example.com' }` |
|
150
|
+
|
151
|
+
For hash validation, use nested validations or `{}` to just require a hash to be present.
|
152
|
+
|
153
|
+
## Advanced Features
|
154
|
+
|
155
|
+
### Class Validation
|
156
|
+
On top of the pre-defined validators, classes can be used directly to validate the presence of a value of a specific class:
|
65
157
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
* hashes are validates by nesting validations, or if just the presence of a hash is required `{}` can be used.
|
158
|
+
```ruby
|
159
|
+
validations = { created_at: Time, user_id: Integer }
|
160
|
+
hash = { created_at: Time.now, user_id: 123 }
|
161
|
+
HashValidator.validate(hash, validations).valid? # => true
|
162
|
+
```
|
163
|
+
|
164
|
+
### Enumerable Validation
|
165
|
+
Validate that a value is contained within a supplied enumerable:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
validations = { status: ['active', 'inactive', 'pending'] }
|
169
|
+
hash = { status: 'active' }
|
170
|
+
HashValidator.validate(hash, validations).valid? # => true
|
171
|
+
```
|
81
172
|
|
82
|
-
|
173
|
+
### Lambda/Proc Validation
|
174
|
+
Use custom logic with lambdas or procs (must accept only one argument):
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
validations = { age: ->(value) { value.is_a?(Integer) && value >= 18 } }
|
178
|
+
hash = { age: 25 }
|
179
|
+
HashValidator.validate(hash, validations).valid? # => true
|
180
|
+
```
|
181
|
+
|
182
|
+
### Regular Expression Validation
|
183
|
+
Validate that a string matches a regex pattern:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
validations = { product_code: /^[A-Z]{2}\d{4}$/ }
|
187
|
+
hash = { product_code: 'AB1234' }
|
188
|
+
HashValidator.validate(hash, validations).valid? # => true
|
189
|
+
```
|
83
190
|
|
84
|
-
|
191
|
+
### Multiple Validators
|
192
|
+
Apply multiple validators to a single key:
|
85
193
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
194
|
+
```ruby
|
195
|
+
HashValidator.validate(
|
196
|
+
{ score: 85 },
|
197
|
+
{ score: HashValidator.multiple('numeric', 1..100) }
|
198
|
+
).valid? # => true
|
199
|
+
```
|
90
200
|
|
91
|
-
|
201
|
+
This is particularly useful when combining built-in validators with custom validation logic.
|
92
202
|
|
93
|
-
## Custom
|
203
|
+
## Custom Validations
|
94
204
|
|
95
205
|
Allows custom defined validations (must inherit from `HashValidator::Validator::Base`). Example:
|
96
206
|
|
@@ -117,19 +227,6 @@ validator.valid? # => true
|
|
117
227
|
validator.errors # => {}
|
118
228
|
```
|
119
229
|
|
120
|
-
## Multiple validators
|
121
|
-
|
122
|
-
Multiple validators can be applied to a single key, e.g.
|
123
|
-
|
124
|
-
```ruby
|
125
|
-
HashValidator.validate(
|
126
|
-
{ foo: 73 },
|
127
|
-
{ foo: HashValidator.multiple('numeric', 1..100) }
|
128
|
-
)
|
129
|
-
```
|
130
|
-
|
131
|
-
This is particularly useful when defining custom validators.
|
132
|
-
|
133
230
|
## Contributing
|
134
231
|
|
135
232
|
1. Fork it
|
data/hash_validator.gemspec
CHANGED
@@ -13,13 +13,13 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.homepage = "https://github.com/JamesBrooks/hash_validator"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
|
-
spec.
|
17
|
-
spec.
|
18
|
-
spec.
|
19
|
-
spec.
|
16
|
+
spec.required_ruby_version = ">= 3.0.0"
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
20
21
|
|
21
22
|
spec.add_development_dependency 'bundler', '~> 2.1'
|
22
23
|
spec.add_development_dependency 'rake'
|
23
24
|
spec.add_development_dependency 'rspec', '~> 3.10'
|
24
|
-
spec.add_development_dependency 'coveralls'
|
25
25
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class HashValidator::Validator::AlphaValidator < HashValidator::Validator::Base
|
2
|
+
def initialize
|
3
|
+
super('alpha') # The name of the validator
|
4
|
+
end
|
5
|
+
|
6
|
+
def presence_error_message
|
7
|
+
'must contain only letters'
|
8
|
+
end
|
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)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
HashValidator.append_validator(HashValidator::Validator::AlphaValidator.new)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class HashValidator::Validator::AlphanumericValidator < HashValidator::Validator::Base
|
2
|
+
def initialize
|
3
|
+
super('alphanumeric') # The name of the validator
|
4
|
+
end
|
5
|
+
|
6
|
+
def presence_error_message
|
7
|
+
'must contain only letters and numbers'
|
8
|
+
end
|
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)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
HashValidator.append_validator(HashValidator::Validator::AlphanumericValidator.new)
|