vanguard 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|