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

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.
@@ -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