vanguard 0.0.3
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 +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.travis.yml +17 -0
- data/Gemfile +7 -0
- data/Gemfile.devtools +56 -0
- data/Guardfile +18 -0
- data/LICENSE +21 -0
- data/README.md +80 -0
- data/Rakefile +2 -0
- data/TODO +3 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +99 -0
- data/config/roodi.yml +26 -0
- data/config/yardstick.yml +2 -0
- data/lib/vanguard.rb +85 -0
- data/lib/vanguard/builder.rb +14 -0
- data/lib/vanguard/builder/nullary.rb +182 -0
- data/lib/vanguard/dsl.rb +43 -0
- data/lib/vanguard/dsl/evaluator.rb +197 -0
- data/lib/vanguard/evaluator.rb +72 -0
- data/lib/vanguard/instance_methods.rb +76 -0
- data/lib/vanguard/matcher.rb +19 -0
- data/lib/vanguard/matcher/binary.rb +59 -0
- data/lib/vanguard/matcher/binary/and.rb +20 -0
- data/lib/vanguard/matcher/binary/or.rb +20 -0
- data/lib/vanguard/matcher/binary/xor.rb +22 -0
- data/lib/vanguard/matcher/nullary.rb +27 -0
- data/lib/vanguard/matcher/nullary/equality.rb +46 -0
- data/lib/vanguard/matcher/nullary/format.rb +126 -0
- data/lib/vanguard/matcher/nullary/greater_than.rb +45 -0
- data/lib/vanguard/matcher/nullary/identity.rb +50 -0
- data/lib/vanguard/matcher/nullary/inclusion.rb +67 -0
- data/lib/vanguard/matcher/nullary/less_than.rb +48 -0
- data/lib/vanguard/matcher/nullary/primitive.rb +47 -0
- data/lib/vanguard/matcher/nullary/proc.rb +44 -0
- data/lib/vanguard/matcher/nullary/value.rb +32 -0
- data/lib/vanguard/matcher/unary.rb +45 -0
- data/lib/vanguard/matcher/unary/attribute.rb +49 -0
- data/lib/vanguard/matcher/unary/not.rb +25 -0
- data/lib/vanguard/result.rb +93 -0
- data/lib/vanguard/rule.rb +43 -0
- data/lib/vanguard/rule/guard.rb +103 -0
- data/lib/vanguard/rule/nullary.rb +81 -0
- data/lib/vanguard/rule/nullary/attribute.rb +69 -0
- data/lib/vanguard/rule/nullary/attribute/absence.rb +19 -0
- data/lib/vanguard/rule/nullary/attribute/format.rb +46 -0
- data/lib/vanguard/rule/nullary/attribute/inclusion.rb +62 -0
- data/lib/vanguard/rule/nullary/attribute/length.rb +184 -0
- data/lib/vanguard/rule/nullary/attribute/predicate.rb +10 -0
- data/lib/vanguard/rule/nullary/attribute/presence.rb +32 -0
- data/lib/vanguard/rule/nullary/attribute/presence/not_blank.rb +0 -0
- data/lib/vanguard/rule/nullary/attribute/presence/not_nil.rb +0 -0
- data/lib/vanguard/rule/nullary/attribute/primitive.rb +35 -0
- data/lib/vanguard/rule/nullary/confirmation.rb +210 -0
- data/lib/vanguard/support/blank.rb +29 -0
- data/lib/vanguard/validator.rb +116 -0
- data/lib/vanguard/validator/builder.rb +52 -0
- data/lib/vanguard/violation.rb +84 -0
- data/spec/integration/vanguard/dsl/guard_spec.rb +58 -0
- data/spec/integration/vanguard/dsl/validates_absence_of_spec.rb +19 -0
- data/spec/integration/vanguard/dsl/validates_acceptance_of_spec.rb +19 -0
- data/spec/integration/vanguard/dsl/validates_confirmation_of_spec.rb +27 -0
- data/spec/integration/vanguard/dsl/validates_format_of_spec.rb +79 -0
- data/spec/integration/vanguard/dsl/validates_inclusion_of_spec.rb +23 -0
- data/spec/integration/vanguard/dsl/validates_length_of_spec.rb +77 -0
- data/spec/integration/vanguard/dsl/validates_presence_of_spec.rb +19 -0
- data/spec/integration/vanguard/dsl/validates_value_of_spec.rb +30 -0
- data/spec/integration/vanguard/validator_spec.rb +57 -0
- data/spec/rcov.opts +6 -0
- data/spec/shared/dsl_spec.rb +73 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/suite.rb +10 -0
- data/spec/unit/vanguard/support/blank_spec.rb +72 -0
- data/vanguard.gemspec +23 -0
- metadata +190 -0
@@ -0,0 +1,182 @@
|
|
1
|
+
module Vanguard
|
2
|
+
class Builder
|
3
|
+
# Abstract base class for nullary builders
|
4
|
+
class Nullary < self
|
5
|
+
include AbstractType
|
6
|
+
|
7
|
+
OPTIONS = [].freeze
|
8
|
+
REQUIRED_OPTIONS = [].freeze
|
9
|
+
|
10
|
+
# Return options
|
11
|
+
#
|
12
|
+
# @return [Hash]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
#
|
16
|
+
attr_reader :options
|
17
|
+
|
18
|
+
# Return arguments
|
19
|
+
#
|
20
|
+
# @return [Class]
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
#
|
24
|
+
attr_reader :klass
|
25
|
+
|
26
|
+
# Return arguments
|
27
|
+
#
|
28
|
+
# @return [Enumerable<Object>]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
#
|
32
|
+
attr_reader :arguments
|
33
|
+
|
34
|
+
# Return rule for attribute name
|
35
|
+
#
|
36
|
+
# @param [Symbol] attribute_name
|
37
|
+
#
|
38
|
+
# @return [Rule]
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
#
|
42
|
+
def rule(attribute_name)
|
43
|
+
klass.new(attribute_name, matcher)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return matcher
|
47
|
+
#
|
48
|
+
# @return [Matcher]
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
#
|
52
|
+
def matcher
|
53
|
+
klass::MATCHER
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return rules
|
57
|
+
#
|
58
|
+
# @api private
|
59
|
+
#
|
60
|
+
def rules
|
61
|
+
arguments.map do |attribute_name|
|
62
|
+
rule(attribute_name)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
memoize :rules
|
66
|
+
|
67
|
+
# Return allowed options
|
68
|
+
#
|
69
|
+
# @return [Enumerable<Symbol>]
|
70
|
+
#
|
71
|
+
# @api private
|
72
|
+
#
|
73
|
+
def allowed_options
|
74
|
+
self.class::OPTIONS
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return required options
|
78
|
+
#
|
79
|
+
# @return [Enumerable<Symbol>]
|
80
|
+
#
|
81
|
+
# @api private
|
82
|
+
#
|
83
|
+
def required_options
|
84
|
+
self.class::REQUIRED_OPTIONS
|
85
|
+
end
|
86
|
+
|
87
|
+
# Run builder
|
88
|
+
#
|
89
|
+
# @return [Enumerable<Rule>]
|
90
|
+
#
|
91
|
+
# @api private
|
92
|
+
#
|
93
|
+
def self.run(*arguments)
|
94
|
+
new(*arguments).rules
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# Initialize object
|
100
|
+
#
|
101
|
+
# @param [Class] klass
|
102
|
+
# @param [Enumerable<Object>] arguments
|
103
|
+
# @param [Hash] options
|
104
|
+
#
|
105
|
+
# @api private
|
106
|
+
#
|
107
|
+
def initialize(klass, arguments, options)
|
108
|
+
@klass, @arguments, @options = klass, arguments, options
|
109
|
+
assert_known_options
|
110
|
+
assert_required_options
|
111
|
+
assert_arguments
|
112
|
+
end
|
113
|
+
|
114
|
+
# Assert arguments are present
|
115
|
+
#
|
116
|
+
# TODO: Raise more specific expection
|
117
|
+
#
|
118
|
+
# @return [undefined]
|
119
|
+
#
|
120
|
+
# @raise [RuntimeError]
|
121
|
+
# if arguments are empty
|
122
|
+
#
|
123
|
+
# @return [undefined]
|
124
|
+
#
|
125
|
+
# @api private
|
126
|
+
#
|
127
|
+
def assert_arguments
|
128
|
+
if arguments.empty?
|
129
|
+
raise "#{klass} needs at least one attribute name"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Assert no unknown options are present
|
134
|
+
#
|
135
|
+
# TODO: Raise more specific exception
|
136
|
+
#
|
137
|
+
# @raise [RuntimeError]
|
138
|
+
# if unknown options are present
|
139
|
+
#
|
140
|
+
# @return [undefined]
|
141
|
+
#
|
142
|
+
# @api private
|
143
|
+
#
|
144
|
+
def assert_known_options
|
145
|
+
unknown = present_keys - (allowed_options + required_options)
|
146
|
+
|
147
|
+
unless unknown.empty?
|
148
|
+
raise "Unknown options: #{unknown.inspect}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Return present option keys
|
153
|
+
#
|
154
|
+
# @return [Enumerable<Object>]
|
155
|
+
#
|
156
|
+
# @api private
|
157
|
+
#
|
158
|
+
def present_keys
|
159
|
+
options.keys
|
160
|
+
end
|
161
|
+
|
162
|
+
# Assert required options are present
|
163
|
+
#
|
164
|
+
# TODO: Raise more specific exception
|
165
|
+
#
|
166
|
+
# @raise [RuntimeException]
|
167
|
+
# if required options are not present
|
168
|
+
#
|
169
|
+
# @return [undefined]
|
170
|
+
#
|
171
|
+
# @api private
|
172
|
+
#
|
173
|
+
def assert_required_options
|
174
|
+
missing = required_options - present_keys
|
175
|
+
|
176
|
+
unless missing.empty?
|
177
|
+
raise "Missing options: #{missing.inspect}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
data/lib/vanguard/dsl.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module Vanguard
|
4
|
+
# Mixin for the Vanguard dsl
|
5
|
+
module DSL
|
6
|
+
|
7
|
+
REGISTRY = {}
|
8
|
+
|
9
|
+
# Register macro
|
10
|
+
#
|
11
|
+
# @param [Symbol] name
|
12
|
+
#
|
13
|
+
# @param [Class] klass
|
14
|
+
#
|
15
|
+
# @return [self]
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
#
|
19
|
+
def self.register(name, klass)
|
20
|
+
REGISTRY[name] = klass
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# Hook called when method is missing
|
25
|
+
#
|
26
|
+
# @param [Symbol] method_name
|
27
|
+
#
|
28
|
+
# @return [self]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
#
|
32
|
+
def method_missing(method_name, *arguments)
|
33
|
+
klass = REGISTRY.fetch(method_name) { super }
|
34
|
+
|
35
|
+
Evaluator.new(klass, arguments).rules.each do |rule|
|
36
|
+
add(rule)
|
37
|
+
end
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
end # module Macros
|
43
|
+
end # module Vanguard
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module Vanguard
|
2
|
+
module DSL
|
3
|
+
class Evaluator
|
4
|
+
include Adamantium::Flat
|
5
|
+
|
6
|
+
# Return klass
|
7
|
+
#
|
8
|
+
# @return [Class]
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
#
|
12
|
+
attr_reader :klass
|
13
|
+
|
14
|
+
# Return options hash
|
15
|
+
#
|
16
|
+
# @return [Hash]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
#
|
20
|
+
attr_reader :options
|
21
|
+
|
22
|
+
# Return arguments array
|
23
|
+
#
|
24
|
+
# @return [Array]
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
#
|
28
|
+
attr_reader :arguments
|
29
|
+
|
30
|
+
# Return rules
|
31
|
+
#
|
32
|
+
# @return [Enumerable<Rule>]
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
#
|
36
|
+
def rules
|
37
|
+
klass.run(arguments, rule_options).map do |rule|
|
38
|
+
connector.connect(rule)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
memoize :rules
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Initialize object
|
46
|
+
#
|
47
|
+
# @param [Class] klass
|
48
|
+
# @param [Enumerable<Object>] arguments
|
49
|
+
#
|
50
|
+
# @return [undefined]
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
#
|
54
|
+
def initialize(klass, arguments)
|
55
|
+
@klass = klass
|
56
|
+
@options = arguments.last.kind_of?(Hash) ? arguments.pop : {}
|
57
|
+
@arguments = arguments
|
58
|
+
assert_no_conflict
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return connector
|
62
|
+
#
|
63
|
+
# @return [Connector]
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
#
|
67
|
+
def connector
|
68
|
+
if_connector || unless_connector || Connector::NONE
|
69
|
+
end
|
70
|
+
memoize :connector
|
71
|
+
|
72
|
+
class Connector
|
73
|
+
include Adamantium::Flat, Equalizer.new(:klass, :left)
|
74
|
+
|
75
|
+
# Return klass
|
76
|
+
#
|
77
|
+
# @return [Class]
|
78
|
+
#
|
79
|
+
# @api private
|
80
|
+
#
|
81
|
+
attr_reader :klass
|
82
|
+
|
83
|
+
# Return left rule
|
84
|
+
#
|
85
|
+
# @return [Rule]
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
#
|
89
|
+
attr_reader :left
|
90
|
+
|
91
|
+
# Return connected rule
|
92
|
+
#
|
93
|
+
# @return [Rule] right
|
94
|
+
#
|
95
|
+
# @api private
|
96
|
+
#
|
97
|
+
def connect(right)
|
98
|
+
@klass.new(left, right)
|
99
|
+
end
|
100
|
+
|
101
|
+
Vanguard.singleton_constant(self, :NONE) do
|
102
|
+
|
103
|
+
# Return connector
|
104
|
+
#
|
105
|
+
# @param [Rule] right
|
106
|
+
#
|
107
|
+
# @return [Rule]
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
#
|
111
|
+
def connect(right)
|
112
|
+
right
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
# Initialize object
|
118
|
+
#
|
119
|
+
# @return [undefined]
|
120
|
+
#
|
121
|
+
# @api private
|
122
|
+
#
|
123
|
+
def initialize; end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
# Initialize object
|
130
|
+
#
|
131
|
+
# @param [Class] klass
|
132
|
+
# @param [Rule] left
|
133
|
+
#
|
134
|
+
# @return [undefined]
|
135
|
+
#
|
136
|
+
# @api private
|
137
|
+
#
|
138
|
+
def initialize(klass, left)
|
139
|
+
@klass, @left = klass, left
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Return if connector
|
144
|
+
#
|
145
|
+
# @return [Connector]
|
146
|
+
# if :if option is present
|
147
|
+
#
|
148
|
+
# @api private
|
149
|
+
#
|
150
|
+
def if_connector
|
151
|
+
name = options.fetch(:if) { return }
|
152
|
+
predicate = Matcher::Unary::Attribute.new(name, Matcher::Nullary::Identity::TRUE)
|
153
|
+
Connector.new(Rule::Guard, predicate)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Return unless connector
|
157
|
+
#
|
158
|
+
# @return [Connector]
|
159
|
+
# if :unless option is present
|
160
|
+
#
|
161
|
+
# @api private
|
162
|
+
#
|
163
|
+
def unless_connector
|
164
|
+
name = options.fetch(:unless) { return }
|
165
|
+
predicate = Matcher::Unary::Attribute.new(name, Matcher::Nullary::Identity::TRUE)
|
166
|
+
predicate = Matcher::Unary::NOT.new(perdicate)
|
167
|
+
Connector.new(Rule::Guard, rule)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Assert no conflicting connectors are present
|
171
|
+
#
|
172
|
+
# @return [undefined]
|
173
|
+
#
|
174
|
+
# @api private
|
175
|
+
#
|
176
|
+
def assert_no_conflict
|
177
|
+
if if_connector and unless_connector
|
178
|
+
raise "Cannot specify both :if and :unless options"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Return rule options
|
183
|
+
#
|
184
|
+
# @return [Hash]
|
185
|
+
#
|
186
|
+
# @api private
|
187
|
+
#
|
188
|
+
def rule_options
|
189
|
+
options = self.options.dup
|
190
|
+
options.delete(:if)
|
191
|
+
options.delete(:unless)
|
192
|
+
options
|
193
|
+
end
|
194
|
+
memoize :rule_options
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Vanguard
|
2
|
+
# Abstract base class for rule evaluators
|
3
|
+
class Evaluator
|
4
|
+
include Adamantium::Flat, AbstractType, Equalizer.new(:rule, :resource, :valid?)
|
5
|
+
|
6
|
+
# Return validated resource
|
7
|
+
#
|
8
|
+
# @return [Object]
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
#
|
12
|
+
attr_reader :resource
|
13
|
+
|
14
|
+
# Return rule
|
15
|
+
#
|
16
|
+
# @return [Rule]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
#
|
20
|
+
attr_reader :rule
|
21
|
+
|
22
|
+
# Initialize object
|
23
|
+
#
|
24
|
+
# @param [Rule] rule
|
25
|
+
# @param [Object] resource
|
26
|
+
#
|
27
|
+
# @return [undefined]
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
#
|
31
|
+
def initialize(rule, resource)
|
32
|
+
@rule, @resource = rule, resource
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return value to be validated
|
36
|
+
#
|
37
|
+
# @return [Object]
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
#
|
41
|
+
def value
|
42
|
+
resource.public_send(rule.attribute_name)
|
43
|
+
end
|
44
|
+
memoize :value
|
45
|
+
|
46
|
+
# Return violation
|
47
|
+
#
|
48
|
+
# @return [Violation]
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
#
|
52
|
+
def violation
|
53
|
+
Violation.new(rule, resource)
|
54
|
+
end
|
55
|
+
memoize :violation
|
56
|
+
|
57
|
+
# Return violations
|
58
|
+
#
|
59
|
+
# @return [Enumerable<Violation>]
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
#
|
63
|
+
def violations
|
64
|
+
unless valid?
|
65
|
+
[violation]
|
66
|
+
else
|
67
|
+
[]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
memoize :violations
|
71
|
+
end
|
72
|
+
end
|