validate-rb 0.1.0.alpha.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec9996734d51898a4f003a56c2cb45748c14d58b138ddb03d1291d9cf6a4ed28
4
- data.tar.gz: 144941aa76f0db9224309d7b3614bc41e5e5fd59b37b7419f4785dd77498abf6
3
+ metadata.gz: 2bac2b6ec6ca8d71becc3849cc26401c63a7939c2d9ee7618ccc022f5845080a
4
+ data.tar.gz: 995c743d5e16aa7a4239d0cd4e6eb500b6cd1541184eab44b7605c4f61494751
5
5
  SHA512:
6
- metadata.gz: 3145f4f69e4eb2969d4eafc9c4f92faa4c3a63c2b15061bef8abea109fab30648ace5104c8e8c81314c7b63a1ee0452899d0074cb1ee8fef15ec000443a95f87
7
- data.tar.gz: 7c2f705b8a0da5e05652491060462a783554429a5ea32ecc08e25364ec675e31aac769f23a4f6ec9fbca07e5317cfa9224e99a06820c051b60a8e761890beaea
6
+ metadata.gz: 80bcb6b3ef21a26dd8d39f0dd4666b1185c65011a9dc3cc4e57af6cb9f6329abe7dc9a83fd8aa415e2490d920f7dbdd2b1e950c9838b93b879ca655888944ff0
7
+ data.tar.gz: d687a04ae9a0c0b8a40f3ec4f851583b0edbe2014003f25196b7463beded4c970d6060b0aa79b767e3bb84d46c777103410de5331292220ce026cdb5c08d9b22
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'forwardable'
4
4
  require 'monitor'
5
+ require 'ipaddr'
5
6
 
6
7
  require_relative 'validate/version'
7
8
  require_relative 'validate/errors'
@@ -3,32 +3,12 @@ module Validate
3
3
  module ClassMethods
4
4
  def method_added(method_name)
5
5
  super
6
- return if @args.empty?
7
-
8
- method = instance_method(method_name)
9
- guard = ArgumentsGuard.new(method, @args.dup)
10
-
11
- @methods_guard.__send__(:define_method, method_name) do |*args, &block|
12
- guard.send(method_name, *args, &block)
13
- super(*args, &block)
14
- end
15
- ensure
16
- @args.clear
6
+ guard_method(instance_method(method_name), @methods_guard)
17
7
  end
18
8
 
19
9
  def singleton_method_added(method_name)
20
10
  super
21
- return if @args.empty?
22
-
23
- method = singleton_method(method_name)
24
- guard = ArgumentsGuard.new(method, @args.dup)
25
-
26
- @methods_guard.__send__(:define_singleton_method, method_name) do |*args, &block|
27
- guard.send(method_name, *args, &block)
28
- super(*args, &block)
29
- end
30
- ensure
31
- @args.clear
11
+ guard_method(singleton_method(method_name), @singleton_methods_guard)
32
12
  end
33
13
 
34
14
  def arg(name, &body)
@@ -39,6 +19,24 @@ module Validate
39
19
  @args[name] = Assertions.create(&body)
40
20
  self
41
21
  end
22
+
23
+ private
24
+
25
+ def guard_method(method, guard_module)
26
+ return if @args.empty?
27
+ guard = ArgumentsGuard.new(method, @args)
28
+ guard_module.__send__(:define_method, method.name) do |*args, **kwargs, &block|
29
+ if kwargs.empty?
30
+ guard.enforce!(*args, &block)
31
+ super(*args, &block)
32
+ else
33
+ guard.enforce!(*args, **kwargs, &block)
34
+ super(*args, **kwargs, &block)
35
+ end
36
+ end
37
+ ensure
38
+ @args = {}
39
+ end
42
40
  end
43
41
 
44
42
  def self.included(base)
@@ -46,6 +44,7 @@ module Validate
46
44
  base.instance_exec do
47
45
  @args = {}
48
46
  prepend(@methods_guard = Module.new)
47
+ singleton_class.prepend(@singleton_methods_guard = Module.new)
49
48
  end
50
49
  end
51
50
 
@@ -73,17 +72,19 @@ module Validate
73
72
  when :block
74
73
  signature << "&#{name}"
75
74
  else
76
- raise Error::ArgumentError, "unsupported parameter type #{kind}"
75
+ raise Error::ArgumentError,
76
+ "unsupported parameter type #{kind}"
77
77
  end
78
78
  next unless rules.include?(name)
79
79
 
80
- assertions << "@rules[:#{name}].assert(#{name}, message: 'invalid argument #{name}')"
80
+ assertions <<
81
+ "@rules[:#{name}].assert(#{name}, message: '#{name}') unless #{name}.eql?(DEFAULT_VALUE)"
81
82
  end
82
83
 
83
- singleton_class.class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
84
- def #{method.name}(#{signature.join(', ')})
85
- #{assertions.join("\n ")}
86
- end
84
+ singleton_class.class_eval(<<~RUBY, __FILE__, __LINE__)
85
+ def enforce!(#{signature.join(', ')})
86
+ #{assertions.join("\n ")}
87
+ end
87
88
  RUBY
88
89
 
89
90
  @rules = rules
@@ -5,8 +5,8 @@ module Validate
5
5
  module AST
6
6
  def self.build(*args, &block)
7
7
  Generator.new
8
- .generate(*args, &block)
9
- .freeze
8
+ .generate(*args, &block)
9
+ .freeze
10
10
  end
11
11
 
12
12
  class DefinitionContext
@@ -65,8 +65,8 @@ module Validate
65
65
 
66
66
  def evaluate(ctx)
67
67
  @constraints.each_value
68
- .reject { |c| catch(:pending) { c.valid?(ctx.value, ctx) } }
69
- .each { |c| ctx.add_violation(c) }
68
+ .reject { |c| catch(:pending) { c.valid?(ctx.value, ctx) } }
69
+ .each { |c| ctx.add_violation(c) }
70
70
  ctx
71
71
  end
72
72
  end
@@ -156,7 +156,7 @@ module Validate
156
156
  args.map { |arg| [:value, arg] },
157
157
  block,
158
158
  ::Kernel.caller
159
- .reject { |line| line.include?(__FILE__) }
159
+ .reject { |line| line.include?(__FILE__) }
160
160
  ]
161
161
  self
162
162
  end
@@ -286,10 +286,10 @@ module Validate
286
286
 
287
287
  def message
288
288
  'both ' + @constraints
289
- .size
290
- .times
291
- .map { |i| "[#{constraint_message(i)}]" }
292
- .join(', and ')
289
+ .size
290
+ .times
291
+ .map { |i| "[#{constraint_message(i)}]" }
292
+ .join(', and ')
293
293
  end
294
294
  end
295
295
 
@@ -327,10 +327,10 @@ module Validate
327
327
 
328
328
  def message
329
329
  'either ' + @constraints
330
- .size
331
- .times
332
- .map { |i| "[#{constraint_message(i)}]" }
333
- .join(', or ')
330
+ .size
331
+ .times
332
+ .map { |i| "[#{constraint_message(i)}]" }
333
+ .join(', or ')
334
334
  end
335
335
  end
336
336
 
@@ -364,10 +364,10 @@ module Validate
364
364
  return "not [#{constraint_message(0)}]" if @constraints.one?
365
365
 
366
366
  'neither ' + @constraints
367
- .size
368
- .times
369
- .map { |i| "[#{constraint_message(i)}]" }
370
- .join(', nor ')
367
+ .size
368
+ .times
369
+ .map { |i| "[#{constraint_message(i)}]" }
370
+ .join(', nor ')
371
371
  end
372
372
 
373
373
  def inspect
@@ -2,16 +2,34 @@
2
2
 
3
3
  module Validate
4
4
  module Compare
5
- class ByAttributes
5
+ module TransformUsing
6
+ def using(&transform_block)
7
+ @transform_block = transform_block
8
+ self
9
+ end
10
+
11
+ def <=>(other)
12
+ return super if @transform_block.nil?
13
+
14
+ super(@transform_block.call(other))
15
+ end
16
+ end
17
+
18
+ class WithAttributes
6
19
  include Comparable
20
+ prepend TransformUsing
7
21
 
8
22
  def initialize(attributes)
9
23
  @attributes = attributes
10
24
  end
11
25
 
12
26
  def <=>(other)
13
- @attributes.map { |attribute, value| value <=> other.send(attribute) }
14
- .find { |result| !result.zero? } || 0
27
+ @attributes.each do |attribute, value|
28
+ result = value <=> other.send(attribute)
29
+ return result unless result.zero?
30
+ end
31
+
32
+ 0
15
33
  end
16
34
 
17
35
  def method_missing(symbol, *args)
@@ -23,12 +41,39 @@ module Validate
23
41
  def respond_to_missing?(attribute, _ = false)
24
42
  @attributes.include?(attribute)
25
43
  end
44
+
45
+ def to_s
46
+ '<attributes ' + @attributes.map { |attribute, value| "#{attribute}: #{value}"}
47
+ .join(', ') + '>'
48
+ end
49
+ end
50
+
51
+ class ToValue
52
+ include Comparable
53
+ prepend TransformUsing
54
+
55
+ def initialize(value_block)
56
+ @value_block = value_block
57
+ end
58
+
59
+ def <=>(other)
60
+ @value_block.call <=> other
61
+ end
62
+
63
+ def to_s
64
+ '<dynamic value>'
65
+ end
26
66
  end
27
67
 
28
68
  module_function
29
69
 
30
70
  def attributes(**attributes)
31
- ByAttributes.new(attributes)
71
+ WithAttributes.new(attributes)
72
+ end
73
+
74
+ def to(value = nil, &value_block)
75
+ value_block ||= value.is_a?(Proc) ? value : proc { value }
76
+ ToValue.new(value_block)
32
77
  end
33
78
  end
34
79
  end
@@ -116,13 +116,27 @@ module Validate
116
116
  expects_kwargs = true if kind == :keyrest
117
117
  end
118
118
 
119
- define_constraint_method(:initialize, initialize_block) do |*args, **kwargs, &block|
120
- known_options, extra_kwargs =
121
- kwargs.partition { |k, _| supported_options.include?(k) }
122
- .map { |h| Hash[h] }
123
- args << extra_kwargs if expects_kwargs || !extra_kwargs.empty?
119
+ define_constraint_method(:initialize, initialize_block) do |*args, &block|
120
+ if args.last.is_a?(Hash)
121
+ known_options, kwargs =
122
+ args.pop
123
+ .partition { |k, _| supported_options.include?(k) }
124
+ .map { |h| Hash[h] }
125
+
126
+ if !expects_kwargs && !kwargs.empty?
127
+ args << kwargs
128
+ kwargs = {}
129
+ end
124
130
 
125
- merged_options = {}.merge!(super(*args, &block), known_options)
131
+ if expects_kwargs
132
+ merged_options = {}.merge!(super(*args, **kwargs, &block), known_options)
133
+ else
134
+ args << kwargs unless kwargs.empty?
135
+ merged_options = {}.merge!(super(*args, &block), known_options)
136
+ end
137
+ else
138
+ merged_options = super(*args, &block)
139
+ end
126
140
 
127
141
  options = supported_options.each_with_object({}) do |(n, opt), opts|
128
142
  opts[n] = opt.get_or_default(merged_options)
@@ -130,7 +144,7 @@ module Validate
130
144
 
131
145
  unless merged_options.empty?
132
146
  raise Error::ArgumentError,
133
- "undefined options #{merged_options.inspect}"
147
+ "unexpected options #{merged_options.inspect}"
134
148
  end
135
149
 
136
150
  @options = options.freeze
@@ -20,7 +20,7 @@ module Validate
20
20
  end
21
21
 
22
22
  @reserved_names[name] = name
23
- constraint_class = Constraint.create_class(name, defaults, &body)
23
+ constraint_class = Constraint.create_class(name, **defaults, &body)
24
24
  Constraints.const_set(Helpers.camelize(name), constraint_class)
25
25
  define_method(name, &constraint_class.method(:new))
26
26
  module_function(name)
@@ -104,6 +104,40 @@ module Validate
104
104
  key { "length_over_#{options[:min]}_under_#{options[:max]}" }
105
105
  end
106
106
 
107
+ define(:bytesize, message: 'have byte length of %{constraint.describe_length}') do
108
+ option(:min) { respond_to(:>, message: 'min must respond to :>') }
109
+ option(:max) { respond_to(:<, message: 'max must respond to :<') }
110
+
111
+ initialize do |range = nil|
112
+ case range
113
+ when ::Range
114
+ { min: range.min, max: range.max }
115
+ else
116
+ { min: range, max: range }
117
+ end
118
+ end
119
+ evaluate do |value|
120
+ pass if value.nil?
121
+ fail unless value.respond_to?(:bytesize)
122
+
123
+ bytesize = value.bytesize
124
+ fail if (options[:min]&.> bytesize) || (options[:max]&.< bytesize)
125
+ end
126
+
127
+ def describe_length
128
+ if options[:max] == options[:min]
129
+ options[:max].to_s
130
+ elsif options[:max].nil?
131
+ "at least #{options[:min]}"
132
+ elsif options[:min].nil?
133
+ "at most #{options[:max]}"
134
+ else
135
+ "at least #{options[:min]} and at most #{options[:max]}"
136
+ end
137
+ end
138
+ key { "bytesize_over_#{options[:min]}_under_#{options[:max]}" }
139
+ end
140
+
107
141
  define(:one_of, message: 'be %{constraint.describe_presence}') do
108
142
  option(:values) do
109
143
  respond_to(:include?, message: 'values must respond to :include?')
@@ -309,6 +343,146 @@ module Validate
309
343
  end
310
344
  end
311
345
 
346
+ define(:each_key, message: 'have keys') do
347
+ option(:constraints) do
348
+ not_nil(message: 'constraints are required')
349
+ is_a(AST::DefinitionContext, message: 'constraints must be a DefinitionContext')
350
+ end
351
+
352
+ initialize do |&block|
353
+ return {} if block.nil?
354
+
355
+ { constraints: AST::DefinitionContext.create(&block) }
356
+ end
357
+ evaluate do |collection, ctx|
358
+ pass if collection.nil?
359
+ fail unless collection.respond_to?(:each_key)
360
+
361
+ constraints = options[:constraints]
362
+ collection.each_key do |key|
363
+ key_ctx = Constraints::ValidationContext.key(key)
364
+ constraints.evaluate(key_ctx)
365
+ ctx.merge(key_ctx) if key_ctx.has_violations?
366
+ end
367
+ end
368
+ end
369
+
370
+ define(:start_with, message: 'start with %{constraint.prefix}') do
371
+ option(:prefix) do
372
+ not_blank(message: 'prefix is required')
373
+ is_a(String, message: 'prefix must be a String')
374
+ end
375
+
376
+ initialize do |prefix = nil|
377
+ return {} if prefix.nil?
378
+
379
+ { prefix: prefix }
380
+ end
381
+ evaluate do |value|
382
+ pass if value.nil?
383
+ fail unless value.respond_to?(:start_with?) && value.start_with?(options[:prefix])
384
+ end
385
+ key do
386
+ "start_with_#{options[:prefix]}"
387
+ end
388
+ end
389
+
390
+ define(:end_with, message: 'end with %{constraint.suffix}') do
391
+ option(:suffix) do
392
+ not_blank(message: 'suffix is required')
393
+ is_a(String, message: 'suffix must be a String')
394
+ end
395
+
396
+ initialize do |suffix = nil|
397
+ return {} if suffix.nil?
398
+
399
+ { suffix: suffix }
400
+ end
401
+ evaluate do |value|
402
+ pass if value.nil?
403
+ fail unless value.respond_to?(:end_with?) && value.end_with?(options[:suffix])
404
+ end
405
+ key do
406
+ "end_with_#{options[:suffix]}"
407
+ end
408
+ end
409
+
410
+ define(:contain, message: 'contain %{constraint.substring}') do
411
+ option(:substring) do
412
+ not_blank(message: 'substring is required')
413
+ is_a(String, message: 'substring must be a String')
414
+ end
415
+
416
+ initialize do |substring = nil|
417
+ return {} if substring.nil?
418
+
419
+ { substring: substring }
420
+ end
421
+ evaluate do |value|
422
+ pass if value.nil?
423
+ fail unless value.respond_to?(:include?) && value.include?(options[:substring])
424
+ end
425
+ key do
426
+ "contain_#{options[:substring]}"
427
+ end
428
+ end
429
+
430
+ define(:uuid, message: 'be a uuid') do
431
+ UUID_REGEXP = %r{\b\h{8}\b-\h{4}-\h{4}-\h{4}-\b\h{12}\b}
432
+
433
+ evaluate do |value|
434
+ pass if value.nil?
435
+ fail unless UUID_REGEXP.match?(value.to_s)
436
+ end
437
+ end
438
+
439
+ define(:hostname, message: 'be a hostname') do
440
+ HOSTNAME_REGEXP = URI::HOST
441
+
442
+ evaluate do |value|
443
+ pass if value.nil?
444
+ fail unless HOSTNAME_REGEXP.match?(value.to_s)
445
+ end
446
+ end
447
+
448
+ define(:uri, message: 'be a uri') do
449
+ option(:absolute, default: true) do
450
+ one_of([true, false], message: ':absolute must be true or false')
451
+ end
452
+
453
+ evaluate do |value|
454
+ pass if value.nil?
455
+ uri = begin
456
+ URI.parse(value)
457
+ rescue URI::Error
458
+ fail
459
+ end
460
+ fail unless options[:absolute] == uri.absolute?
461
+ end
462
+ end
463
+
464
+ define(:ip_address, message: 'be an ip address') do
465
+ option(:version, default: nil) do
466
+ one_of([:v4, :v6], message: 'must be a valid ip version')
467
+ end
468
+
469
+ initialize do |version = nil|
470
+ return {} if version.nil?
471
+
472
+ { version: version }
473
+ end
474
+ evaluate do |value|
475
+ pass if value.nil?
476
+ addr = begin
477
+ IPAddr.new(value)
478
+ rescue IPAddr::Error
479
+ fail
480
+ end
481
+ version = options[:version]
482
+ fail unless version.nil? || addr.send(:"ip#{version}?")
483
+ end
484
+ end
485
+
312
486
  define(
313
487
  :unique,
314
488
  message: 'have unique %{constraint.describe_unique_attribute}'
@@ -11,7 +11,12 @@ module Validate
11
11
  new(value, Path.new, violations)
12
12
  end
13
13
 
14
- attr_reader :value
14
+ def self.key(key, violations = [])
15
+ new(key, Path.new([KeyPath.new(key)]), violations)
16
+ end
17
+
18
+ attr_reader :value, :violations
19
+ protected :violations
15
20
 
16
21
  def initialize(value, path = Path.new, violations = [])
17
22
  @value = value
@@ -61,8 +66,17 @@ module Validate
61
66
  !@violations.empty?
62
67
  end
63
68
 
64
- def to_err
65
- Error::ConstraintViolationError.new(@violations.freeze)
69
+ def to_err(backtrace = [])
70
+ err = Error::ConstraintViolationError.new(@violations.freeze)
71
+ err.set_backtrace(backtrace)
72
+ err
73
+ end
74
+
75
+ def merge(other)
76
+ other.violations.each do |violation|
77
+ @violations << Constraint::Violation.new(violation.value, @path.child(violation.path), violation.constraint)
78
+ end
79
+ self
66
80
  end
67
81
 
68
82
  private
@@ -87,7 +101,14 @@ module Validate
87
101
  end
88
102
 
89
103
  def child(path)
90
- Path.new(@paths.dup << path)
104
+ case path
105
+ when KeyPath, AttrPath
106
+ Path.new(@paths.dup << path)
107
+ when Path
108
+ Path.new(@paths.dup << path.to_a)
109
+ else
110
+ raise ArgumentError, 'invalid path'
111
+ end
91
112
  end
92
113
 
93
114
  def to_s
@@ -31,9 +31,9 @@ module Validate
31
31
 
32
32
  def message
33
33
  @violations.group_by(&:path)
34
- .transform_values { |violations| violations.map(&:message) }
35
- .map { |path, messages| "#{path}: #{messages.join(' ')}" }
36
- .join("\n")
34
+ .transform_values { |violations| violations.map(&:message) }
35
+ .map { |path, messages| "#{path}: #{messages.join(', ')}" }
36
+ .join("\n")
37
37
  end
38
38
  end
39
39
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Validate
4
- VERSION = '0.1.0.alpha.1'
4
+ VERSION = '1.0.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: validate-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bulat Shakirzyanov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-20 00:00:00.000000000 Z
11
+ date: 2020-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aruba
@@ -150,9 +150,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
150
  version: 2.3.0
151
151
  required_rubygems_version: !ruby/object:Gem::Requirement
152
152
  requirements:
153
- - - ">"
153
+ - - ">="
154
154
  - !ruby/object:Gem::Version
155
- version: 1.3.1
155
+ version: '0'
156
156
  requirements: []
157
157
  rubygems_version: 3.0.3
158
158
  signing_key: