validate-rb 0.1.0.alpha.1 → 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,167 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Validate
4
- module Constraints
5
- class ValidationContext
6
- def self.none
7
- @none ||= None.new
8
- end
9
-
10
- def self.root(value, violations = [])
11
- new(value, Path.new, violations)
12
- end
13
-
14
- attr_reader :value
15
-
16
- def initialize(value, path = Path.new, violations = [])
17
- @value = value
18
- @path = path
19
- @violations = violations
20
- @keys = Hash.new do |hash, key|
21
- unless @value.respond_to?(:[]) || @value.respond_to_missing?(:[])
22
- raise Error::KeyError,
23
- "#{key.inspect}: value doesn't respond to :[]"
24
- end
25
- begin
26
- hash[key] = child_context(@value[key], KeyPath.new(key))
27
- rescue => e
28
- raise Error::KeyError,
29
- "#{key.inspect}: #{e.message}",
30
- cause: e
31
- end
32
- end
33
- @attrs = Hash.new do |hash, attr|
34
- unless @value.respond_to?(attr) || @value.respond_to_missing?(attr)
35
- raise Error::NameError,
36
- "#{attr.inspect}: value doesn't respond to #{attr.inspect}"
37
- end
38
- hash[attr] = child_context(@value.send(attr), AttrPath.new(attr))
39
- end
40
- end
41
-
42
- def [](key)
43
- @keys[key]
44
- end
45
-
46
- def attr(name)
47
- @attrs[name]
48
- end
49
-
50
- def add_violation(constraint)
51
- @violations << create_violation(constraint)
52
- self
53
- end
54
-
55
- def clear_violations
56
- @violations.clear
57
- self
58
- end
59
-
60
- def has_violations?
61
- !@violations.empty?
62
- end
63
-
64
- def to_err
65
- Error::ConstraintViolationError.new(@violations.freeze)
66
- end
67
-
68
- private
69
-
70
- def create_violation(constraint)
71
- Constraint::Violation.new(@value, @path, constraint)
72
- end
73
-
74
- def child_context(value, path)
75
- ValidationContext.new(value, @path.child(path), @violations)
76
- end
77
-
78
- class Path
79
- extend Forwardable
80
-
81
- def_delegators(:@paths, :empty?, :length, :size, :each)
82
-
83
- include Enumerable
84
-
85
- def initialize(paths = [])
86
- @paths = paths
87
- end
88
-
89
- def child(path)
90
- Path.new(@paths.dup << path)
91
- end
92
-
93
- def to_s
94
- return '.' if @paths.empty?
95
-
96
- @paths.join
97
- end
98
-
99
- def at(index)
100
- raise Error::IndexError if index.negative?
101
-
102
- return nil if index.zero?
103
- @paths.fetch(index - 1)
104
- end
105
-
106
- def inspect
107
- return "#<#{self.class.name} <root>>" if @paths.empty?
108
-
109
- "#<#{self.class.name} #{to_s}>"
110
- end
111
- end
112
-
113
- class KeyPath
114
- def initialize(key)
115
- @key = key
116
- end
117
-
118
- def to_s
119
- "[#{@key.inspect}]"
120
- end
121
-
122
- def inspect
123
- "#<#{self.class.name} #{@key.inspect}>"
124
- end
125
- end
126
-
127
- class AttrPath
128
- def initialize(attr)
129
- @attr = attr
130
- end
131
-
132
- def to_s
133
- ".#{@attr}"
134
- end
135
-
136
- def inspect
137
- "#<#{self.class.name} #{@attr.inspect}>"
138
- end
139
- end
140
-
141
- class None < ValidationContext
142
- def initialize
143
- end
144
-
145
- def [](_)
146
- self
147
- end
148
-
149
- def attr(_)
150
- self
151
- end
152
-
153
- def add_violation(_)
154
- self
155
- end
156
-
157
- def clear_violations
158
- self
159
- end
160
-
161
- def has_violations?
162
- false
163
- end
164
- end
165
- end
166
- end
167
- end
@@ -1,337 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Validate
4
- module Constraints
5
- include Validate::Arguments
6
-
7
- @reserved_names = Hash[%i[define validation_context].map { |n| [n, n] }]
8
-
9
- arg(:name) do
10
- not_blank(message: 'constraint name must not be blank')
11
- is_a(Symbol, message: 'constraint name must be a Symbol')
12
- end
13
- arg(:body) do
14
- not_nil(message: 'constraint body is required')
15
- end
16
- def self.define(name, **defaults, &body)
17
- if @reserved_names.include?(name)
18
- raise Error::ArgumentError,
19
- "#{name} is already defined"
20
- end
21
-
22
- @reserved_names[name] = name
23
- constraint_class = Constraint.create_class(name, defaults, &body)
24
- Constraints.const_set(Helpers.camelize(name), constraint_class)
25
- define_method(name, &constraint_class.method(:new))
26
- module_function(name)
27
- end
28
-
29
- define(:not_nil, message: 'not be nil') do
30
- evaluate { |value| fail if value.nil? }
31
- end
32
-
33
- define(:not_blank, message: 'not be blank') do
34
- evaluate do |value|
35
- fail if value.nil? || !value.respond_to?(:empty?) || value.empty?
36
- end
37
- end
38
-
39
- define(:not_empty, message: 'not be empty') do
40
- evaluate { |value| fail if value&.empty? }
41
- end
42
-
43
- define(:is_a, message: 'be a %{constraint.klass}') do
44
- option(:klass) do
45
- not_nil(message: 'klass is required')
46
- end
47
-
48
- initialize { |klass| { klass: klass } }
49
- evaluate do |value|
50
- pass if value.nil?
51
-
52
- klass = options[:klass]
53
- fail unless klass === value
54
- end
55
- end
56
-
57
- define(:respond_to, message: 'respond to %{constraint.method_name.inspect}') do
58
- option(:method_name) do
59
- not_blank(message: 'method_name is required')
60
- is_a(Symbol, message: 'method_name must be a Symbol')
61
- end
62
-
63
- initialize do |method_name|
64
- method_name.nil? ? {} : { method_name: method_name }
65
- end
66
- evaluate do |instance|
67
- pass if instance.nil?
68
- fail unless instance.respond_to?(options[:method_name])
69
- end
70
- key { "respond_to_#{options[:method_name]}" }
71
- end
72
-
73
- define(:length, message: 'have length of %{constraint.describe_length}') do
74
- option(:min) { respond_to(:>, message: 'min must respond to :>') }
75
- option(:max) { respond_to(:<, message: 'max must respond to :<') }
76
-
77
- initialize do |range = nil|
78
- case range
79
- when ::Range
80
- { min: range.min, max: range.max }
81
- else
82
- { min: range, max: range }
83
- end
84
- end
85
- evaluate do |value|
86
- pass if value.nil?
87
- fail unless value.respond_to?(:length)
88
-
89
- length = value.length
90
- fail if (options[:min]&.> length) || (options[:max]&.< length)
91
- end
92
-
93
- def describe_length
94
- if options[:max] == options[:min]
95
- options[:max].to_s
96
- elsif options[:max].nil?
97
- "at least #{options[:min]}"
98
- elsif options[:min].nil?
99
- "at most #{options[:max]}"
100
- else
101
- "at least #{options[:min]} and at most #{options[:max]}"
102
- end
103
- end
104
- key { "length_over_#{options[:min]}_under_#{options[:max]}" }
105
- end
106
-
107
- define(:one_of, message: 'be %{constraint.describe_presence}') do
108
- option(:values) do
109
- respond_to(:include?, message: 'values must respond to :include?')
110
- end
111
-
112
- initialize do |*values|
113
- if values.one? && values.first.respond_to?(:include?)
114
- { values: values.first }
115
- else
116
- { values: values }
117
- end
118
- end
119
- evaluate do |value|
120
- pass if value.nil?
121
-
122
- values = options[:values]
123
- pass if values.respond_to?(:cover?) && values.cover?(value)
124
- fail unless values.include?(value)
125
- end
126
-
127
- def describe_presence
128
- case options[:values]
129
- when ::Hash
130
- "one of #{options[:values].keys}"
131
- when ::Range
132
- "covered by #{options[:values]}"
133
- else
134
- "one of #{options[:values]}"
135
- end
136
- end
137
- end
138
-
139
- define(:validate, message: 'pass validation') do
140
- option(:using) do
141
- not_nil(message: 'using is required')
142
- is_a(Proc, message: 'using must be a Proc')
143
- end
144
-
145
- initialize { |&validate_block| { using: validate_block } }
146
- evaluate do |value|
147
- pass if value.nil?
148
- fail unless instance_exec(value, &options[:using])
149
- end
150
- end
151
-
152
- define(:attr, message: 'have attribute %{constraint.attribute}') do
153
- option(:attribute) do
154
- not_nil(message: 'attribute is required')
155
- is_a(Symbol, message: 'attribute must be a Symbol')
156
- end
157
- option(:constraints) do
158
- is_a(AST::DefinitionContext, message: 'constraints must be a DefinitionContext')
159
- end
160
-
161
- initialize do |attribute, &block|
162
- { attribute: attribute,
163
- constraints: block && AST::DefinitionContext.create(&block) }
164
- end
165
- evaluate do |value, ctx|
166
- pass if value.nil?
167
-
168
- attribute = options[:attribute]
169
- begin
170
- options[:constraints].evaluate(ctx.attr(attribute))
171
- rescue NameError
172
- fail
173
- end
174
- end
175
- key { "attr_#{options[:attribute]}" }
176
- end
177
-
178
- define(:key, message: 'have key %{constraint.key.inspect}') do
179
- option(:key) do
180
- not_nil(message: 'key is required')
181
- end
182
- option(:constraints) do
183
- is_a(AST::DefinitionContext, message: 'constraints must be a DefinitionContext')
184
- end
185
-
186
- initialize do |key, &block|
187
- { key: key,
188
- constraints: block && AST::DefinitionContext.create(&block) }
189
- end
190
-
191
- evaluate do |instance, ctx|
192
- pass if instance.nil?
193
- fail unless instance.respond_to?(:[])
194
-
195
- key = options[:key]
196
- begin
197
- options[:constraints]&.evaluate(ctx[key])
198
- rescue KeyError
199
- fail
200
- end
201
- end
202
- key { "key_#{options[:key]}" }
203
- end
204
-
205
- define(:min, message: 'be at least %{constraint.min}') do
206
- option(:min) do
207
- not_nil(message: 'min is required')
208
- respond_to(:>, message: 'min must respond to :>')
209
- end
210
-
211
- initialize do |min = nil|
212
- min.nil? ? {} : { min: min }
213
- end
214
- evaluate do |value|
215
- pass if value.nil?
216
- fail if options[:min] > value
217
- end
218
- end
219
-
220
- define(:max, message: 'be at most %{constraint.max}') do
221
- option(:max) do
222
- not_nil(message: 'max is required')
223
- respond_to(:<, message: 'max must respond to :<')
224
- end
225
-
226
- initialize do |max = nil|
227
- max.nil? ? {} : { max: max }
228
- end
229
- evaluate do |value|
230
- pass if value.nil?
231
- fail if options[:max] < value
232
- end
233
- end
234
-
235
- define(:equal, message: 'be equal to %{constraint.equal}') do
236
- option(:equal) do
237
- not_nil(message: 'equal is required')
238
- respond_to(:==, message: 'equal must respond to :==')
239
- end
240
-
241
- initialize do |equal = nil|
242
- equal.nil? ? {} : { equal: equal }
243
- end
244
- evaluate do |value|
245
- pass if value.nil?
246
- fail unless options[:equal] == value
247
- end
248
- end
249
-
250
- define(:match, message: 'match %{constraint.regexp}') do
251
- option(:regexp) do
252
- not_nil(message: 'regexp is required')
253
- respond_to(:=~, message: 'regexp must respond to :=~')
254
- end
255
-
256
- initialize do |regexp = nil|
257
- regexp.nil? ? {} : { regexp: regexp }
258
- end
259
- evaluate do |value|
260
- pass if value.nil?
261
- fail unless value.is_a?(String) && options[:regexp] =~ value
262
- end
263
- end
264
-
265
- define(:valid, message: 'be valid %{constraint.validator || value.class}') do
266
- option(:validator, default: nil)
267
-
268
- initialize do |validator = nil|
269
- validator.nil? ? {} : { validator: validator }
270
- end
271
- evaluate do |value, ctx|
272
- pass if value.nil?
273
-
274
- Scope.current
275
- .validator(options[:validator] || value.class)
276
- .validate(ctx)
277
- end
278
- key { options[:validator] && "valid_#{options[:validator]}" || 'valid' }
279
- end
280
-
281
- define(:each_value, message: 'have values') do
282
- option(:constraints) do
283
- not_nil(message: 'constraints are required')
284
- is_a(AST::DefinitionContext, message: 'constraints must be a DefinitionContext')
285
- end
286
-
287
- initialize do |&block|
288
- return {} if block.nil?
289
-
290
- { constraints: AST::DefinitionContext.create(&block) }
291
- end
292
- evaluate do |collection, ctx|
293
- pass if collection.nil?
294
- fail unless collection.respond_to?(:each)
295
-
296
- constraints = options[:constraints]
297
- case collection
298
- when ::Hash
299
- collection.each do |key, value|
300
- constraints.evaluate(ctx[key])
301
- end
302
- else
303
- i = 0
304
- collection.each do |value|
305
- constraints.evaluate(ctx[i])
306
- i += 1
307
- end
308
- end
309
- end
310
- end
311
-
312
- define(
313
- :unique,
314
- message: 'have unique %{constraint.describe_unique_attribute}'
315
- ) do
316
- option(:attribute, default: nil) do
317
- is_a(Symbol, message: 'attribute %{value.inspect} must be a Symbol')
318
- end
319
-
320
- initialize do |attribute = nil|
321
- attribute.nil? ? {} : { attribute: attribute }
322
- end
323
- evaluate do |value|
324
- pass if value.nil?
325
- fail unless value.respond_to?(:uniq) && value.respond_to?(:size)
326
- fail unless value.size == value.uniq(&options[:attribute]).size
327
- end
328
- key do
329
- options[:attribute] && "unique_#{options[:attribute]}" || 'unique'
330
- end
331
-
332
- def describe_unique_attribute
333
- options[:attribute] || 'values'
334
- end
335
- end
336
- end
337
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Validate
4
- module Error
5
- class StandardError < ::StandardError
6
- include Error
7
- end
8
-
9
- class ArgumentError < ::ArgumentError
10
- include Error
11
- end
12
-
13
- class KeyError < ::KeyError
14
- include Error
15
- end
16
-
17
- class IndexError < ::IndexError
18
- include Error
19
- end
20
-
21
- class ValidationRuleError < StandardError
22
- end
23
-
24
- class ConstraintViolationError < StandardError
25
- attr_reader :violations
26
-
27
- def initialize(violations)
28
- @violations = violations
29
- super()
30
- end
31
-
32
- def message
33
- @violations.group_by(&:path)
34
- .transform_values { |violations| violations.map(&:message) }
35
- .map { |path, messages| "#{path}: #{messages.join(' ')}" }
36
- .join("\n")
37
- end
38
- end
39
- end
40
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Validate
4
- module Helpers
5
- module_function
6
-
7
- def camelize(name)
8
- name.to_s.split('_').collect(&:capitalize).join
9
- end
10
- end
11
- end
@@ -1,48 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module Validate
5
- class Scope
6
- def self.current
7
- @current ||= Scope.new
8
- end
9
-
10
- def initialize
11
- @constraints = {}
12
- @validators = {}
13
- end
14
-
15
- def register_validator(name, validator)
16
- if @validators.include?(name)
17
- raise Error::ArgumentError,
18
- "duplicate validator :#{name}"
19
- end
20
-
21
- @validators[name] = validator
22
- end
23
-
24
- def validator?(name)
25
- @validators.include?(name)
26
- end
27
-
28
- def validator(name)
29
- validator_name.assert(name,
30
- message: "invalid validator #{name.inspect}",
31
- error_class: KeyError)
32
-
33
- @validators.fetch(name) { name.validator }
34
- end
35
-
36
- private
37
-
38
- def validator_name
39
- @validator_name ||= Assertions.create(@validators) do |validators|
40
- not_nil(message: 'name must not be nil')
41
- (one_of(values: validators,
42
- message: '%{value.inspect} must be an existing validator name') |
43
- respond_to(:validator,
44
- message: '%{value.inspect} must respond to :validator'))
45
- end
46
- end
47
- end
48
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Validate
4
- module Validators
5
- module DSL
6
- include Arguments
7
-
8
- arg(:name) { is_a(Module) | (is_a(Symbol) & not_blank) }
9
- def define(name, &body)
10
- Scope.current.register_validator(name, create(&body))
11
- end
12
-
13
- def create(&block)
14
- Validator.new(&block)
15
- end
16
-
17
- def none
18
- @none ||= Validator::None.new
19
- end
20
- end
21
-
22
- class Validator
23
- def initialize(&block)
24
- @constraints = AST::DefinitionContext.create(&block)
25
- end
26
-
27
- def validate(ctx)
28
- @constraints.evaluate(ctx)
29
- end
30
-
31
- private
32
-
33
- class None < Validator
34
- NO_VIOLATIONS = [].freeze
35
-
36
- def initialize; end
37
-
38
- def validate(*args)
39
- NO_VIOLATIONS
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Validate
4
- module Validators
5
- extend DSL
6
- end
7
- end