validate-rb 0.1.0.pre → 1.0.0.alpha.1
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/LICENSE +21 -0
- data/README.md +4 -4
- data/lib/validate.rb +75 -1
- data/lib/validate/arguments.rb +93 -0
- data/lib/validate/assertions.rb +27 -0
- data/lib/validate/ast.rb +381 -0
- data/lib/validate/compare.rb +79 -0
- data/lib/validate/constraint.rb +231 -0
- data/lib/validate/constraints.rb +337 -0
- data/lib/validate/constraints/validation_context.rb +167 -0
- data/lib/validate/errors.rb +40 -0
- data/lib/validate/helpers.rb +11 -0
- data/lib/validate/scope.rb +48 -0
- data/lib/validate/validators.rb +7 -0
- data/lib/validate/validators/dsl.rb +44 -0
- data/lib/validate/version.rb +1 -1
- metadata +122 -21
- data/.gitignore +0 -12
- data/.rspec +0 -3
- data/.ruby-version +0 -1
- data/.travis.yml +0 -6
- data/Gemfile +0 -7
- data/Rakefile +0 -6
- data/bin/console +0 -7
- data/bin/setup +0 -6
- data/lib/validate-rb.rb +0 -3
- data/validate-rb.gemspec +0 -30
@@ -0,0 +1,167 @@
|
|
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
|
@@ -0,0 +1,40 @@
|
|
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
|
@@ -0,0 +1,48 @@
|
|
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
|
@@ -0,0 +1,44 @@
|
|
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
|
data/lib/validate/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,113 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: validate-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0.alpha.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bulat Shakirzyanov
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-07-
|
12
|
-
dependencies:
|
11
|
+
date: 2020-07-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aruba
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: cucumber
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sorbet
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: yard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
13
111
|
description: Simple, powerful, and constraint-based validation
|
14
112
|
email:
|
15
113
|
- bulat.shakirzyanov@gusto.com
|
@@ -17,28 +115,31 @@ executables: []
|
|
17
115
|
extensions: []
|
18
116
|
extra_rdoc_files: []
|
19
117
|
files:
|
20
|
-
-
|
21
|
-
- ".rspec"
|
22
|
-
- ".ruby-version"
|
23
|
-
- ".travis.yml"
|
24
|
-
- Gemfile
|
118
|
+
- LICENSE
|
25
119
|
- README.md
|
26
|
-
- Rakefile
|
27
|
-
- bin/console
|
28
|
-
- bin/setup
|
29
|
-
- lib/validate-rb.rb
|
30
120
|
- lib/validate.rb
|
121
|
+
- lib/validate/arguments.rb
|
122
|
+
- lib/validate/assertions.rb
|
123
|
+
- lib/validate/ast.rb
|
124
|
+
- lib/validate/compare.rb
|
125
|
+
- lib/validate/constraint.rb
|
126
|
+
- lib/validate/constraints.rb
|
127
|
+
- lib/validate/constraints/validation_context.rb
|
128
|
+
- lib/validate/errors.rb
|
129
|
+
- lib/validate/helpers.rb
|
130
|
+
- lib/validate/scope.rb
|
131
|
+
- lib/validate/validators.rb
|
132
|
+
- lib/validate/validators/dsl.rb
|
31
133
|
- lib/validate/version.rb
|
32
|
-
|
33
|
-
homepage: https://github.com/Gusto/validaterb
|
134
|
+
homepage: https://github.com/Gusto/validate-rb
|
34
135
|
licenses:
|
35
136
|
- MIT
|
36
137
|
metadata:
|
37
|
-
homepage_uri: https://github.com/Gusto/
|
38
|
-
changelog_uri: https://github.com/Gusto/
|
39
|
-
source_code_uri: https://github.com/Gusto/
|
40
|
-
bug_tracker_uri: https://github.com/Gusto/
|
41
|
-
post_install_message:
|
138
|
+
homepage_uri: https://github.com/Gusto/validate-rb
|
139
|
+
changelog_uri: https://github.com/Gusto/validate-rb/releases
|
140
|
+
source_code_uri: https://github.com/Gusto/validate-rb
|
141
|
+
bug_tracker_uri: https://github.com/Gusto/validate-rb/issues
|
142
|
+
post_install_message:
|
42
143
|
rdoc_options: []
|
43
144
|
require_paths:
|
44
145
|
- lib
|
@@ -54,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
155
|
version: 1.3.1
|
55
156
|
requirements: []
|
56
157
|
rubygems_version: 3.0.3
|
57
|
-
signing_key:
|
158
|
+
signing_key:
|
58
159
|
specification_version: 4
|
59
160
|
summary: Yummy constraint validations for Ruby
|
60
161
|
test_files: []
|