ruby-livr 2.0.0 → 2.1.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 +5 -5
- data/.github/workflows/test.yml +23 -0
- data/.gitignore +1 -0
- data/Readme.md +2 -2
- data/lib/livr/aliased_rule.rb +11 -5
- data/lib/livr/rule.rb +13 -0
- data/lib/livr/rules/common.rb +1 -2
- data/lib/livr/rules/meta.rb +43 -12
- data/lib/livr/rules/modifiers.rb +10 -10
- data/lib/livr/rules/numeric.rb +5 -5
- data/lib/livr/rules/special.rb +14 -9
- data/lib/livr/validator.rb +7 -2
- data/lib/livr.rb +2 -0
- data/ruby-livr.gemspec +5 -4
- data/spec/01-test_suite_spec.rb +7 -2
- data/spec/02-aliases_spec.rb +30 -1
- data/spec/04-custom_filters_spec.rb +12 -1
- data/spec/06-primitive_values_spec.rb +133 -0
- data/spec/test_suite/aliases_negative/02-address/aliases.json +2 -2
- data/spec/test_suite/aliases_negative/02-address/input.json +2 -2
- data/spec/test_suite/aliases_positive/01-adult_age/input.json +1 -0
- data/spec/test_suite/aliases_positive/01-adult_age/output.json +1 -0
- data/spec/test_suite/aliases_positive/01-adult_age/rules.json +2 -0
- data/spec/test_suite/aliases_positive/02-address/aliases.json +2 -2
- data/spec/test_suite/aliases_positive/02-address/input.json +1 -1
- data/spec/test_suite/aliases_positive/02-address/output.json +1 -1
- data/spec/test_suite/negative/01-required/errors.json +4 -1
- data/spec/test_suite/negative/01-required/input.json +1 -0
- data/spec/test_suite/negative/01-required/rules.json +5 -1
- data/spec/test_suite/negative/03-one_of/errors.json +2 -0
- data/spec/test_suite/negative/03-one_of/input.json +2 -0
- data/spec/test_suite/negative/03-one_of/rules.json +9 -7
- data/spec/test_suite/negative/12-positive_decimal/errors.json +1 -0
- data/spec/test_suite/negative/12-positive_decimal/input.json +1 -0
- data/spec/test_suite/negative/12-positive_decimal/rules.json +1 -0
- data/spec/test_suite/negative/16-email/errors.json +22 -0
- data/spec/test_suite/negative/16-email/input.json +23 -0
- data/spec/test_suite/negative/16-email/rules.json +23 -0
- data/spec/test_suite/negative/17-equal_to_field/errors.json +4 -0
- data/spec/test_suite/negative/17-equal_to_field/input.json +5 -0
- data/spec/test_suite/negative/17-equal_to_field/rules.json +4 -0
- data/spec/test_suite/negative/18-nested_object/input.json +1 -1
- data/spec/test_suite/negative/21-list_of_different_objects/errors.json +6 -1
- data/spec/test_suite/negative/21-list_of_different_objects/input.json +23 -1
- data/spec/test_suite/negative/21-list_of_different_objects/rules.json +36 -1
- data/spec/test_suite/negative/23-url/errors.json +2 -0
- data/spec/test_suite/negative/23-url/input.json +2 -0
- data/spec/test_suite/negative/23-url/rules.json +2 -0
- data/spec/test_suite/negative/24-iso_date/errors.json +6 -0
- data/spec/test_suite/negative/24-iso_date/input.json +6 -0
- data/spec/test_suite/negative/24-iso_date/rules.json +6 -0
- data/spec/test_suite/negative/25-eq/rules.json +7 -7
- data/spec/test_suite/negative/28-variable_object/errors.json +9 -1
- data/spec/test_suite/negative/28-variable_object/input.json +9 -1
- data/spec/test_suite/negative/28-variable_object/rules.json +57 -1
- data/spec/test_suite/negative/29-or/errors.json +3 -0
- data/spec/test_suite/negative/29-or/input.json +1 -0
- data/spec/test_suite/negative/29-or/rules.json +3 -0
- data/spec/test_suite/positive/01-required/input.json +5 -1
- data/spec/test_suite/positive/01-required/output.json +4 -1
- data/spec/test_suite/positive/01-required/rules.json +4 -1
- data/spec/test_suite/positive/03-one_of/input.json +7 -3
- data/spec/test_suite/positive/03-one_of/output.json +7 -3
- data/spec/test_suite/positive/03-one_of/rules.json +8 -4
- data/spec/test_suite/positive/16-email/input.json +29 -0
- data/spec/test_suite/positive/16-email/output.json +28 -0
- data/spec/test_suite/positive/16-email/rules.json +28 -0
- data/spec/test_suite/positive/17-equal_to_field/input.json +3 -1
- data/spec/test_suite/positive/17-equal_to_field/output.json +2 -1
- data/spec/test_suite/positive/17-equal_to_field/rules.json +2 -1
- data/spec/test_suite/positive/18-nested_object/input.json +9 -1
- data/spec/test_suite/positive/18-nested_object/output.json +5 -0
- data/spec/test_suite/positive/18-nested_object/rules.json +10 -1
- data/spec/test_suite/positive/24-iso_date/input.json +2 -0
- data/spec/test_suite/positive/24-iso_date/output.json +2 -0
- data/spec/test_suite/positive/24-iso_date/rules.json +2 -0
- data/spec/test_suite/positive/25-eq/input.json +3 -3
- data/spec/test_suite/positive/25-eq/output.json +3 -3
- data/spec/test_suite/positive/25-eq/rules.json +4 -4
- data/spec/test_suite/positive/26-string/input.json +2 -2
- data/spec/test_suite/positive/26-string/output.json +2 -2
- data/spec/test_suite/positive/27-any_object/input.json +3 -2
- data/spec/test_suite/positive/27-any_object/output.json +3 -2
- data/spec/test_suite/positive/27-any_object/rules.json +2 -1
- data/spec/test_suite/positive/29-or/input.json +2 -0
- data/spec/test_suite/positive/29-or/output.json +2 -0
- data/spec/test_suite/positive/29-or/rules.json +3 -0
- data/spec/test_suite/positive/35-default/output.json +2 -1
- data/spec/test_suite/positive/35-default/rules.json +2 -1
- metadata +17 -14
- data/.travis.yml +0 -4
- data/Gemfile.lock +0 -45
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f6657ac44abc483ea73c0cdd20e26300ef34bdf9dde0e8e65ca6bd90428b2401
|
|
4
|
+
data.tar.gz: a0fb5076c7de93858bfcd224f25981d6e23d04b96f27c85df8b91b8c0e7f96b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6fea4e70535d7de501da828e551d4d247764cdaec995f4f7e23dfab4191ac026151eee4672ae570bab6a399278af4da8363f2099d5eede23cc4e40dfbd5c7cf1
|
|
7
|
+
data.tar.gz: 84b829178bc3f29fd8e7e2fd47e2ba85c173a34a33667d2782b86170ba6b128ed4ed377342752a0157dde3b925cc720e6a6bc51e2782bf47801fb3aac0d391df
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
# ubuntu-24.04, not ubuntu-latest: setup-ruby has no Ruby 2.7-3.1 builds for ubuntu-26.04
|
|
10
|
+
runs-on: ubuntu-24.04
|
|
11
|
+
# head is the next Ruby in development: its failures are early warnings, not a broken build
|
|
12
|
+
continue-on-error: ${{ matrix.ruby == 'head' }}
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
ruby: ['2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0', 'head']
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v7
|
|
19
|
+
- uses: ruby/setup-ruby@v1
|
|
20
|
+
with:
|
|
21
|
+
ruby-version: ${{ matrix.ruby }}
|
|
22
|
+
bundler-cache: true
|
|
23
|
+
- run: bundle exec rspec
|
data/.gitignore
CHANGED
data/Readme.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://github.com/fuksito/ruby-livr/actions/workflows/test.yml)
|
|
2
2
|
|
|
3
3
|
# LIVR Validator
|
|
4
4
|
LIVR.Validator - Lightweight validator supporting Language Independent Validation Rules Specification (LIVR)
|
|
@@ -41,7 +41,7 @@ validator = LIVR::Validator.new({
|
|
|
41
41
|
|
|
42
42
|
Feel free to register your own rules:
|
|
43
43
|
|
|
44
|
-
You can use aliases(prefferable, syntax covered by the specification) for a lot of cases:
|
|
44
|
+
You can use aliases (prefferable, syntax covered by the specification) for a lot of cases:
|
|
45
45
|
|
|
46
46
|
```ruby
|
|
47
47
|
validator = LIVR::Validator.new({
|
data/lib/livr/aliased_rule.rb
CHANGED
|
@@ -6,20 +6,26 @@ module LIVR
|
|
|
6
6
|
raise "Alias rules required" unless _alias["rules"].present?
|
|
7
7
|
|
|
8
8
|
@alias = _alias
|
|
9
|
-
@validator = Validator.new(value: Array.wrap(@alias["rules"]))
|
|
10
9
|
end
|
|
11
10
|
|
|
11
|
+
# A rule for every use: each one gets the rules of the validator that uses it
|
|
12
12
|
def new(*args)
|
|
13
|
-
|
|
13
|
+
dup
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def rule_builders=(rule_builders)
|
|
17
|
+
@validator = Validator.new(value: Array.wrap(@alias["rules"])).register_rules(rule_builders).prepare
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
def call(value, user_data, field_results)
|
|
17
|
-
|
|
21
|
+
self.rule_builders = {} unless @validator # built outside of a validator: default rules only
|
|
22
|
+
# A missing value stays missing, unless the rules change it
|
|
23
|
+
result = @validator.validate(value.nil? ? {} : { value: value })
|
|
18
24
|
if result
|
|
19
|
-
field_results << result[:value]
|
|
25
|
+
field_results << result[:value] if result.has_key?(:value)
|
|
20
26
|
return
|
|
21
27
|
else
|
|
22
|
-
return @alias
|
|
28
|
+
return @alias["error"] || @validator.get_errors[:value]
|
|
23
29
|
end
|
|
24
30
|
end
|
|
25
31
|
end
|
data/lib/livr/rule.rb
CHANGED
|
@@ -17,5 +17,18 @@ module LIVR
|
|
|
17
17
|
def is_no_value(value)
|
|
18
18
|
value == nil || value == ""
|
|
19
19
|
end
|
|
20
|
+
|
|
21
|
+
# Base 10: Integer() alone reads "010" as an octal and "0x10" as a hexadecimal number.
|
|
22
|
+
# No underscores: Integer() and Float() read "1_000" as 1000, like a Ruby literal.
|
|
23
|
+
def to_integer(value)
|
|
24
|
+
return if value.to_s.include?("_")
|
|
25
|
+
Integer(value.to_s, 10) rescue nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Integers stay integers: "10" is 10, "1.5" is 1.5
|
|
29
|
+
def to_number(value)
|
|
30
|
+
return if value.to_s.include?("_")
|
|
31
|
+
to_integer(value) || (Float(value.to_s) rescue nil)
|
|
32
|
+
end
|
|
20
33
|
end
|
|
21
34
|
end
|
data/lib/livr/rules/common.rb
CHANGED
data/lib/livr/rules/meta.rb
CHANGED
|
@@ -4,10 +4,15 @@ module LIVR
|
|
|
4
4
|
|
|
5
5
|
class NestedObject < Rule
|
|
6
6
|
def initialize(livr)
|
|
7
|
-
@
|
|
7
|
+
@livr = livr
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def rule_builders=(rule_builders)
|
|
11
|
+
@validator = Validator.new(@livr).register_rules(rule_builders).prepare
|
|
8
12
|
end
|
|
9
13
|
|
|
10
14
|
def call(nested_object, user_data, field_results)
|
|
15
|
+
self.rule_builders = {} unless @validator # built outside of a validator: default rules only
|
|
11
16
|
return if is_no_value(nested_object)
|
|
12
17
|
return 'FORMAT_ERROR' unless nested_object.is_a?(Hash)
|
|
13
18
|
|
|
@@ -25,13 +30,18 @@ module LIVR
|
|
|
25
30
|
class VariableObject < Rule
|
|
26
31
|
def initialize(selector_field, livrs)
|
|
27
32
|
@selector_field = selector_field
|
|
33
|
+
@livrs = livrs
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def rule_builders=(rule_builders)
|
|
28
37
|
@validators = {}
|
|
29
|
-
livrs.each do |selector_value, livr|
|
|
30
|
-
@validators[selector_value] = Validator.new(livr).prepare
|
|
38
|
+
@livrs.each do |selector_value, livr|
|
|
39
|
+
@validators[selector_value] = Validator.new(livr).register_rules(rule_builders).prepare
|
|
31
40
|
end
|
|
32
41
|
end
|
|
33
42
|
|
|
34
43
|
def call(object, user_data, field_results)
|
|
44
|
+
self.rule_builders = {} unless @validators # built outside of a validator: default rules only
|
|
35
45
|
return if is_no_value(object)
|
|
36
46
|
if ( !object.is_a?(Hash) || !object[@selector_field] || !@validators[ object[@selector_field] ] )
|
|
37
47
|
return 'FORMAT_ERROR'
|
|
@@ -51,11 +61,15 @@ module LIVR
|
|
|
51
61
|
|
|
52
62
|
class ListOf < Rule
|
|
53
63
|
def initialize(*rules)
|
|
54
|
-
livr = { field: rules.flatten }
|
|
55
|
-
|
|
64
|
+
@livr = { field: rules.flatten }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def rule_builders=(rule_builders)
|
|
68
|
+
@validator = Validator.new(@livr).register_rules(rule_builders).prepare
|
|
56
69
|
end
|
|
57
70
|
|
|
58
71
|
def call(values, user_data, field_results)
|
|
72
|
+
self.rule_builders = {} unless @validator # built outside of a validator: default rules only
|
|
59
73
|
return if is_no_value(values)
|
|
60
74
|
return 'FORMAT_ERROR' unless values.is_a?(Array)
|
|
61
75
|
|
|
@@ -87,10 +101,15 @@ module LIVR
|
|
|
87
101
|
|
|
88
102
|
class ListOfObjects < Rule
|
|
89
103
|
def initialize(livr)
|
|
90
|
-
@
|
|
104
|
+
@livr = livr
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def rule_builders=(rule_builders)
|
|
108
|
+
@validator = Validator.new(@livr).register_rules(rule_builders).prepare
|
|
91
109
|
end
|
|
92
110
|
|
|
93
111
|
def call(objects, user_data, field_results)
|
|
112
|
+
self.rule_builders = {} unless @validator # built outside of a validator: default rules only
|
|
94
113
|
return if is_no_value(objects)
|
|
95
114
|
return 'FORMAT_ERROR' unless objects.is_a?(Array)
|
|
96
115
|
|
|
@@ -124,13 +143,18 @@ module LIVR
|
|
|
124
143
|
class ListOfDifferentObjects < Rule
|
|
125
144
|
def initialize(selector_field, livrs)
|
|
126
145
|
@selector_field = selector_field
|
|
146
|
+
@livrs = livrs
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def rule_builders=(rule_builders)
|
|
127
150
|
@validators = {}
|
|
128
|
-
livrs.each do |selector_value, livr|
|
|
129
|
-
@validators[selector_value] = Validator.new(livr).prepare
|
|
151
|
+
@livrs.each do |selector_value, livr|
|
|
152
|
+
@validators[selector_value] = Validator.new(livr).register_rules(rule_builders).prepare
|
|
130
153
|
end
|
|
131
154
|
end
|
|
132
155
|
|
|
133
156
|
def call(objects, user_data, field_results)
|
|
157
|
+
self.rule_builders = {} unless @validators # built outside of a validator: default rules only
|
|
134
158
|
return if is_no_value(objects)
|
|
135
159
|
return 'FORMAT_ERROR' unless objects.is_a?(Array)
|
|
136
160
|
|
|
@@ -140,6 +164,7 @@ module LIVR
|
|
|
140
164
|
|
|
141
165
|
objects.each do |object|
|
|
142
166
|
if !object.is_a?(Hash) || !object[@selector_field] || !@validators[ object[@selector_field] ]
|
|
167
|
+
has_errors = true
|
|
143
168
|
errors << 'FORMAT_ERROR'
|
|
144
169
|
next
|
|
145
170
|
end
|
|
@@ -170,20 +195,26 @@ module LIVR
|
|
|
170
195
|
class Or < Rule
|
|
171
196
|
def initialize(*rule_sets)
|
|
172
197
|
@rule_sets = rule_sets
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def rule_builders=(rule_builders)
|
|
173
201
|
@validators = @rule_sets.map do |rules|
|
|
174
202
|
livr = { field: rules }
|
|
175
|
-
Validator.new(livr).prepare
|
|
203
|
+
Validator.new(livr).register_rules(rule_builders).prepare
|
|
176
204
|
end
|
|
177
205
|
end
|
|
178
206
|
|
|
179
207
|
def call(value, user_data, field_results)
|
|
180
|
-
|
|
208
|
+
self.rule_builders = {} unless @validators # built outside of a validator: default rules only
|
|
209
|
+
# Empty and missing values go to the rule sets too ("required" can be one of them).
|
|
210
|
+
# A missing value stays missing, unless a rule set changes it.
|
|
211
|
+
input = value.nil? ? {} : { field: value }
|
|
181
212
|
|
|
182
213
|
last_error = nil
|
|
183
214
|
@validators.each do |validator|
|
|
184
|
-
result = validator.validate(
|
|
215
|
+
result = validator.validate(input)
|
|
185
216
|
if result
|
|
186
|
-
field_results << result[:field]
|
|
217
|
+
field_results << result[:field] if result.has_key?(:field)
|
|
187
218
|
return nil
|
|
188
219
|
else
|
|
189
220
|
last_error = validator.get_errors[:field]
|
data/lib/livr/rules/modifiers.rb
CHANGED
|
@@ -16,24 +16,24 @@ module LIVR
|
|
|
16
16
|
|
|
17
17
|
class Trim < Rule
|
|
18
18
|
def call(value, user_data, field_results)
|
|
19
|
-
return if is_no_value(value) || value.is_a?(Hash)
|
|
20
|
-
field_results << value.to_s.
|
|
19
|
+
return if is_no_value(value) || value.is_a?(Hash) || value.is_a?(Array)
|
|
20
|
+
field_results << value.to_s.strip
|
|
21
21
|
return
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
class ToLc < Rule
|
|
26
26
|
def call(value, user_data, field_results)
|
|
27
|
-
return if is_no_value(value) || value.is_a?(Hash)
|
|
28
|
-
field_results << value.to_s.
|
|
27
|
+
return if is_no_value(value) || value.is_a?(Hash) || value.is_a?(Array)
|
|
28
|
+
field_results << value.to_s.downcase
|
|
29
29
|
return
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
class ToUc < Rule
|
|
34
34
|
def call(value, user_data, field_results)
|
|
35
|
-
return if is_no_value(value) || value.is_a?(Hash)
|
|
36
|
-
field_results << value.to_s.
|
|
35
|
+
return if is_no_value(value) || value.is_a?(Hash) || value.is_a?(Array)
|
|
36
|
+
field_results << value.to_s.upcase
|
|
37
37
|
return
|
|
38
38
|
end
|
|
39
39
|
end
|
|
@@ -45,8 +45,8 @@ module LIVR
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def call(value, user_data, field_results)
|
|
48
|
-
return if is_no_value(value) || value.is_a?(Hash)
|
|
49
|
-
field_results << value.to_s.
|
|
48
|
+
return if is_no_value(value) || value.is_a?(Hash) || value.is_a?(Array)
|
|
49
|
+
field_results << value.to_s.gsub(@re, '')
|
|
50
50
|
return
|
|
51
51
|
end
|
|
52
52
|
end
|
|
@@ -58,8 +58,8 @@ module LIVR
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def call(value, user_data, field_results)
|
|
61
|
-
return if is_no_value(value) || value.is_a?(Hash)
|
|
62
|
-
field_results << value.to_s.
|
|
61
|
+
return if is_no_value(value) || value.is_a?(Hash) || value.is_a?(Array)
|
|
62
|
+
field_results << value.to_s.gsub(@re, '')
|
|
63
63
|
return
|
|
64
64
|
end
|
|
65
65
|
end
|
data/lib/livr/rules/numeric.rb
CHANGED
|
@@ -7,7 +7,7 @@ module LIVR
|
|
|
7
7
|
return if is_no_value(value)
|
|
8
8
|
return 'FORMAT_ERROR' if !is_primitive(value)
|
|
9
9
|
|
|
10
|
-
value_int =
|
|
10
|
+
value_int = to_integer(value)
|
|
11
11
|
if value_int.nil?
|
|
12
12
|
return "NOT_INTEGER"
|
|
13
13
|
end
|
|
@@ -23,7 +23,7 @@ module LIVR
|
|
|
23
23
|
return if is_no_value(value)
|
|
24
24
|
return 'FORMAT_ERROR' if !is_primitive(value)
|
|
25
25
|
|
|
26
|
-
value_int =
|
|
26
|
+
value_int = to_integer(value)
|
|
27
27
|
|
|
28
28
|
return "NOT_POSITIVE_INTEGER" if value_int.nil?
|
|
29
29
|
return "NOT_POSITIVE_INTEGER" unless value_int.positive?
|
|
@@ -70,7 +70,7 @@ module LIVR
|
|
|
70
70
|
return if is_no_value(value)
|
|
71
71
|
return 'FORMAT_ERROR' if !is_primitive(value)
|
|
72
72
|
|
|
73
|
-
value =
|
|
73
|
+
value = to_number(value)
|
|
74
74
|
if value.nil?
|
|
75
75
|
return "NOT_NUMBER"
|
|
76
76
|
end
|
|
@@ -93,7 +93,7 @@ module LIVR
|
|
|
93
93
|
return if is_no_value(value)
|
|
94
94
|
return 'FORMAT_ERROR' if !is_primitive(value)
|
|
95
95
|
|
|
96
|
-
value =
|
|
96
|
+
value = to_number(value)
|
|
97
97
|
if value.nil?
|
|
98
98
|
return "NOT_NUMBER"
|
|
99
99
|
end
|
|
@@ -117,7 +117,7 @@ module LIVR
|
|
|
117
117
|
return if is_no_value(value)
|
|
118
118
|
return 'FORMAT_ERROR' if !is_primitive(value)
|
|
119
119
|
|
|
120
|
-
value =
|
|
120
|
+
value = to_number(value)
|
|
121
121
|
if value.nil?
|
|
122
122
|
return "NOT_NUMBER"
|
|
123
123
|
end
|
data/lib/livr/rules/special.rb
CHANGED
|
@@ -3,15 +3,15 @@ module LIVR
|
|
|
3
3
|
module Special
|
|
4
4
|
|
|
5
5
|
class Email < Rule
|
|
6
|
+
# The same expression as in the reference implementation: one "@" only and no "_" in the domain.
|
|
7
|
+
# [:space:] is Unicode-aware, like \s in JavaScript. \A and \z, not ^ and $: they match at line breaks.
|
|
8
|
+
EMAIL_RE = /\A(?:[^<>()\[\]\\.,;:[:space:]@"]+(?:\.[^<>()\[\]\\.,;:[:space:]@"]+)*|"[^@\n\r\u2028\u2029]+")@(?:\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\]|(?:[a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,})\z/
|
|
9
|
+
|
|
6
10
|
def call(value, user_data, field_results)
|
|
7
11
|
return if is_no_value(value)
|
|
8
12
|
return 'FORMAT_ERROR' if !is_primitive(value)
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
email_re = /^([\w\-_+]+(?:\.[\w\-_+]+)*)@((?:[\w\-]+\.)*\w[\w\-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i
|
|
12
|
-
return "WRONG_EMAIL" unless email_re.match(value)
|
|
13
|
-
return "WRONG_EMAIL" if /\@.*\@/.match(value)
|
|
14
|
-
return "WRONG_EMAIL" if /\@.*_/.match(value)
|
|
14
|
+
return "WRONG_EMAIL" unless EMAIL_RE.match(value.to_s)
|
|
15
15
|
return
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -25,12 +25,15 @@ module LIVR
|
|
|
25
25
|
return if is_no_value(value)
|
|
26
26
|
return 'FORMAT_ERROR' if !is_primitive(value)
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
# Compared as strings: "10" and 10 are equal. A missing field is not equal to any value.
|
|
29
|
+
other_value = user_data[@field]
|
|
30
|
+
return "FIELDS_NOT_EQUAL" unless is_primitive(other_value) && value.to_s == other_value.to_s
|
|
29
31
|
end
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
class Url < Rule
|
|
33
|
-
|
|
35
|
+
# \A and \z, not ^ and $: they match at line breaks in Ruby.
|
|
36
|
+
URL_RE_STR = '\\A(?:(?:http|https)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[0-1]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))\\.?|localhost)(?::\\d{2,5})?(?:[/?#]\\S*)?\\z'
|
|
34
37
|
URL_RE = Regexp.compile(URL_RE_STR, "i")
|
|
35
38
|
MAX_URL_LENGTH = 2083
|
|
36
39
|
|
|
@@ -38,7 +41,7 @@ module LIVR
|
|
|
38
41
|
return if is_no_value(value)
|
|
39
42
|
return 'FORMAT_ERROR' if !is_primitive(value)
|
|
40
43
|
|
|
41
|
-
if value.length < MAX_URL_LENGTH && URL_RE.match(value)
|
|
44
|
+
if value.to_s.length < MAX_URL_LENGTH && URL_RE.match(value.to_s)
|
|
42
45
|
return
|
|
43
46
|
else
|
|
44
47
|
'WRONG_URL'
|
|
@@ -51,7 +54,9 @@ module LIVR
|
|
|
51
54
|
return if is_no_value(value)
|
|
52
55
|
return 'FORMAT_ERROR' if !is_primitive(value)
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
value = value.to_s
|
|
58
|
+
# Date.strptime alone accepts "2020-01-1x" and "+020-01-01"
|
|
59
|
+
return "WRONG_DATE" unless value =~ /\A\d{4}-\d{2}-\d{2}\z/
|
|
55
60
|
|
|
56
61
|
date = Date.strptime(value, '%Y-%m-%d') rescue nil
|
|
57
62
|
unless date
|
data/lib/livr/validator.rb
CHANGED
|
@@ -3,7 +3,7 @@ module LIVR
|
|
|
3
3
|
def initialize(rules, is_auto_trim=false)
|
|
4
4
|
@rules = rules
|
|
5
5
|
@is_prepared = false
|
|
6
|
-
@validator_builders = {}
|
|
6
|
+
@validator_builders = {}.with_indifferent_access
|
|
7
7
|
@validators = {}
|
|
8
8
|
@is_auto_trim = is_auto_trim
|
|
9
9
|
|
|
@@ -110,7 +110,12 @@ module LIVR
|
|
|
110
110
|
if @validator_builders[name].blank?
|
|
111
111
|
raise "Rule ['#{name}'] not registered"
|
|
112
112
|
end
|
|
113
|
-
@validator_builders[name].new(*args)
|
|
113
|
+
validator = @validator_builders[name].new(*args)
|
|
114
|
+
|
|
115
|
+
# Meta rules and aliases validate with validators of their own. They get the rules
|
|
116
|
+
# of this validator, so that aliases and custom rules can be used inside them.
|
|
117
|
+
validator.rule_builders = @validator_builders if validator.respond_to?(:rule_builders=)
|
|
118
|
+
validator
|
|
114
119
|
end
|
|
115
120
|
|
|
116
121
|
private
|
data/lib/livr.rb
CHANGED
data/ruby-livr.gemspec
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
Gem::Specification.new do |gem|
|
|
2
2
|
gem.name = "ruby-livr"
|
|
3
|
-
gem.version = "2.
|
|
4
|
-
gem.date = "2017-12-22"
|
|
3
|
+
gem.version = "2.1.0"
|
|
5
4
|
gem.summary = "Implementation of LIVR v2.0"
|
|
6
5
|
gem.description = "Language Independent Validation Rules. Implements 2.0 specification"
|
|
7
6
|
gem.authors = ["Vitaliy Yanchuk"]
|
|
@@ -14,6 +13,8 @@ Gem::Specification.new do |gem|
|
|
|
14
13
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
15
14
|
gem.require_paths = ["lib"]
|
|
16
15
|
|
|
17
|
-
gem.
|
|
18
|
-
|
|
16
|
+
gem.required_ruby_version = ">= 2.7"
|
|
17
|
+
|
|
18
|
+
gem.add_dependency "activesupport", '>= 5', '< 9'
|
|
19
|
+
gem.add_development_dependency "rspec", "~> 3.7"
|
|
19
20
|
end
|
data/spec/01-test_suite_spec.rb
CHANGED
|
@@ -5,9 +5,13 @@ def read_file(dir, filename)
|
|
|
5
5
|
JSON.parse(data)
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
+
def read_aliases(dir)
|
|
9
|
+
dir.join("aliases.json").exist? ? read_file(dir, "aliases") : []
|
|
10
|
+
end
|
|
11
|
+
|
|
8
12
|
RSpec.describe "test_suite " do
|
|
9
13
|
|
|
10
|
-
%w[ positive negative ].each do |spec_dir|
|
|
14
|
+
%w[ positive negative aliases_positive aliases_negative ].each do |spec_dir|
|
|
11
15
|
context spec_dir do
|
|
12
16
|
dirs = Pathname.new("#{SPEC_ROOT}/test_suite/#{spec_dir}").children.select(&:directory?)
|
|
13
17
|
dirs.each do |dir|
|
|
@@ -16,10 +20,11 @@ RSpec.describe "test_suite " do
|
|
|
16
20
|
input = read_file(dir, "input")
|
|
17
21
|
|
|
18
22
|
validator = LIVR::Validator.new(rules)
|
|
23
|
+
read_aliases(dir).each { |_alias| validator.register_aliased_rule(_alias) }
|
|
19
24
|
result = validator.validate(input)
|
|
20
25
|
errors = validator.get_errors
|
|
21
26
|
|
|
22
|
-
if spec_dir
|
|
27
|
+
if spec_dir.end_with?('positive')
|
|
23
28
|
expected_result = read_file(dir, "output")
|
|
24
29
|
expect(result).to eq(expected_result)
|
|
25
30
|
else
|
data/spec/02-aliases_spec.rb
CHANGED
|
@@ -18,4 +18,33 @@ RSpec.describe "aliases" do
|
|
|
18
18
|
expect(validator.get_errors).to eq({"password" => "WEAK_PASSWORD"})
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
it "Validates with aliases and custom rules inside meta rules" do
|
|
22
|
+
weak_password = Class.new(LIVR::Rule) do
|
|
23
|
+
def call(value, user_data, field_results)
|
|
24
|
+
"WEAK_PASSWORD" if value.to_s.length < 6
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
validator = LIVR::Validator.new({
|
|
29
|
+
"user" => "user",
|
|
30
|
+
"passwords" => { "list_of" => "weak_password" }
|
|
31
|
+
})
|
|
32
|
+
validator.register_aliased_rule({
|
|
33
|
+
"name" => "user",
|
|
34
|
+
"rules" => { "nested_object" => { "age" => "adult_age" } }
|
|
35
|
+
})
|
|
36
|
+
validator.register_aliased_rule({
|
|
37
|
+
"name" => "adult_age",
|
|
38
|
+
"rules" => [ "positive_integer", { "min_number" => 18 } ]
|
|
39
|
+
})
|
|
40
|
+
validator.register_rules("weak_password" => weak_password)
|
|
41
|
+
|
|
42
|
+
output = validator.validate({ "user" => { "age" => 10 }, "passwords" => [ "abc", "abcdefg" ] })
|
|
43
|
+
expect(output).to eq(false)
|
|
44
|
+
expect(validator.get_errors).to eq({
|
|
45
|
+
"user" => { "age" => "TOO_LOW" },
|
|
46
|
+
"passwords" => [ "WEAK_PASSWORD", nil ]
|
|
47
|
+
})
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
@@ -50,5 +50,16 @@ RSpec.describe "custom_filters" do
|
|
|
50
50
|
)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
it 'Validate data with rules registered with symbol names' do
|
|
54
|
+
validator = LIVR::Validator.new(
|
|
55
|
+
"word1" => ['my_trim2'],
|
|
56
|
+
"word2" => [{ my_trim2: [] }]
|
|
57
|
+
)
|
|
58
|
+
validator.register_rules(my_trim2: MyTrim)
|
|
59
|
+
|
|
60
|
+
output = validator.validate("word1" => ' wordOne ', "word2" => ' wordTwo ')
|
|
54
61
|
|
|
62
|
+
expect(output).to eq("word1" => 'wordOne', "word2" => 'wordTwo')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|