ward 0.1.0
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.
- data/.document +5 -0
- data/.gitignore +28 -0
- data/LICENSE +19 -0
- data/README.markdown +99 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/features/acceptance_matcher.feature +78 -0
- data/features/attribute_keyword.feature +13 -0
- data/features/close_to_matcher.feature +130 -0
- data/features/context_arguments.feature +47 -0
- data/features/equal_to_matcher.feature +25 -0
- data/features/error_messages.feature +69 -0
- data/features/external_validation.feature +15 -0
- data/features/has_matcher.feature +72 -0
- data/features/has_matcher_initialized_with_expectation.feature +94 -0
- data/features/has_matcher_relativities.feature +171 -0
- data/features/include_matcher.feature +28 -0
- data/features/is_keyword.feature +42 -0
- data/features/is_not_keyword.feature +62 -0
- data/features/match_matcher.feature +49 -0
- data/features/multiple_validators.feature +29 -0
- data/features/nil_matcher.feature +25 -0
- data/features/predicate_matcher.feature +23 -0
- data/features/present_matcher.feature +59 -0
- data/features/satisfy_matcher.feature +80 -0
- data/features/scenario_validation.feature +81 -0
- data/features/step_definitions/external_validation_steps.rb +69 -0
- data/features/step_definitions/generic_validation_steps.rb +33 -0
- data/features/step_definitions/object_definition_steps.rb +43 -0
- data/features/support/env.rb +12 -0
- data/features/support/object_builder.rb +33 -0
- data/features/support/struct.rb +38 -0
- data/lang/en.yml +56 -0
- data/lib/ward.rb +26 -0
- data/lib/ward/context.rb +70 -0
- data/lib/ward/context_chain.rb +87 -0
- data/lib/ward/dsl.rb +7 -0
- data/lib/ward/dsl/validation_block.rb +73 -0
- data/lib/ward/dsl/validation_builder.rb +190 -0
- data/lib/ward/errors.rb +213 -0
- data/lib/ward/matchers.rb +97 -0
- data/lib/ward/matchers/acceptance.rb +43 -0
- data/lib/ward/matchers/close_to.rb +60 -0
- data/lib/ward/matchers/equal_to.rb +33 -0
- data/lib/ward/matchers/has.rb +283 -0
- data/lib/ward/matchers/include.rb +54 -0
- data/lib/ward/matchers/match.rb +29 -0
- data/lib/ward/matchers/matcher.rb +68 -0
- data/lib/ward/matchers/nil.rb +30 -0
- data/lib/ward/matchers/predicate.rb +31 -0
- data/lib/ward/matchers/present.rb +56 -0
- data/lib/ward/matchers/satisfy.rb +65 -0
- data/lib/ward/spec.rb +17 -0
- data/lib/ward/spec/matcher_matcher.rb +114 -0
- data/lib/ward/support.rb +7 -0
- data/lib/ward/support/basic_object.rb +55 -0
- data/lib/ward/support/result.rb +49 -0
- data/lib/ward/validator.rb +147 -0
- data/lib/ward/validator_set.rb +115 -0
- data/lib/ward/version.rb +3 -0
- data/spec/lib/has_matcher_relativity_examples.rb +15 -0
- data/spec/lib/have_public_method_defined.rb +22 -0
- data/spec/rcov.opts +8 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/ward/context_chain_spec.rb +178 -0
- data/spec/ward/context_spec.rb +57 -0
- data/spec/ward/dsl/validation_block_spec.rb +27 -0
- data/spec/ward/dsl/validation_builder_spec.rb +212 -0
- data/spec/ward/errors_spec.rb +149 -0
- data/spec/ward/matchers/acceptance_spec.rb +16 -0
- data/spec/ward/matchers/close_to_spec.rb +57 -0
- data/spec/ward/matchers/equal_to_spec.rb +16 -0
- data/spec/ward/matchers/has_spec.rb +175 -0
- data/spec/ward/matchers/include_spec.rb +41 -0
- data/spec/ward/matchers/match_spec.rb +21 -0
- data/spec/ward/matchers/matcher_spec.rb +54 -0
- data/spec/ward/matchers/nil_spec.rb +16 -0
- data/spec/ward/matchers/predicate_spec.rb +19 -0
- data/spec/ward/matchers/present_spec.rb +16 -0
- data/spec/ward/matchers/satisfy_spec.rb +68 -0
- data/spec/ward/matchers_spec.rb +51 -0
- data/spec/ward/spec/have_public_method_defined_spec.rb +31 -0
- data/spec/ward/spec/matcher_matcher_spec.rb +217 -0
- data/spec/ward/validator_set_spec.rb +178 -0
- data/spec/ward/validator_spec.rb +264 -0
- data/tasks/features.rake +15 -0
- data/tasks/rcov.rake +24 -0
- data/tasks/spec.rake +18 -0
- data/tasks/yard.rake +9 -0
- data/ward.gemspec +176 -0
- metadata +239 -0
data/lib/ward/support.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Ward
|
2
|
+
module Support
|
3
|
+
# Used by the DSL classes. Provides a BasicObject implementation for
|
4
|
+
# Ruby versions older than 1.9.
|
5
|
+
#
|
6
|
+
# From Sequel, with modifications to support RSpec.
|
7
|
+
#
|
8
|
+
class BasicObject
|
9
|
+
|
10
|
+
# Lookup missing constants in ::Object
|
11
|
+
def self.const_missing(name)
|
12
|
+
::Object.const_get(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
if RUBY_VERSION < '1.9.0'
|
16
|
+
# If on Ruby 1.8, create a Ward::BasicObject class that is similar to
|
17
|
+
# the Ruby 1.9 BasicObject class. This is used as the basis for the
|
18
|
+
# DSL classes.
|
19
|
+
(instance_methods - %w( __id__ __send__ instance_eval == equal?
|
20
|
+
should should_not )).each do |method|
|
21
|
+
undef_method(method.to_sym)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end # BasicObject
|
26
|
+
end # Support
|
27
|
+
end # Ward
|
28
|
+
|
29
|
+
#
|
30
|
+
# Sequel License:
|
31
|
+
#
|
32
|
+
# Copyright (c) 2007-2008 Sharon Rosner
|
33
|
+
# Copyright (c) 2008-2010 Jeremy Evans
|
34
|
+
#
|
35
|
+
# Permission is hereby granted, free of charge, to any person
|
36
|
+
# obtaining a copy of this software and associated documentation
|
37
|
+
# files (the "Software"), to deal in the Software without
|
38
|
+
# restriction, including without limitation the rights to use,
|
39
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or
|
40
|
+
# sell copies of the Software, and to permit persons to whom the
|
41
|
+
# Software is furnished to do so, subject to the following
|
42
|
+
# conditions:
|
43
|
+
#
|
44
|
+
# The above copyright notice and this permission notice shall be
|
45
|
+
# included in all copies or substantial portions of the Software.
|
46
|
+
#
|
47
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
48
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
49
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
50
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
51
|
+
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
52
|
+
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
53
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
54
|
+
# THE SOFTWARE.
|
55
|
+
#
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Ward
|
2
|
+
module Support
|
3
|
+
# Used when validating objects which don't include the Validation
|
4
|
+
# module. Contains the results of running a ValidatorSet against a record,
|
5
|
+
# and any errors which occurred.
|
6
|
+
#
|
7
|
+
#
|
8
|
+
class Result
|
9
|
+
|
10
|
+
# Returns the errors which were found when validating.
|
11
|
+
#
|
12
|
+
# An Errors instance will always be returned, even if there were no
|
13
|
+
# errors with the record.
|
14
|
+
#
|
15
|
+
# @return [Ward:Errors]
|
16
|
+
#
|
17
|
+
attr_reader :errors
|
18
|
+
|
19
|
+
# Creates a new Result instance.
|
20
|
+
#
|
21
|
+
# @param [Ward::Errors] errors
|
22
|
+
# The errors which were found by the validations.
|
23
|
+
#
|
24
|
+
def initialize(errors)
|
25
|
+
@errors = errors
|
26
|
+
@result = errors.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns true if the validations passed.
|
30
|
+
#
|
31
|
+
# @return [Boolean]
|
32
|
+
#
|
33
|
+
def pass?
|
34
|
+
@result
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method :valid?, :pass?
|
38
|
+
|
39
|
+
# Returns true if the validations failed.
|
40
|
+
#
|
41
|
+
# @return [Boolean]
|
42
|
+
#
|
43
|
+
def fail?
|
44
|
+
not pass?
|
45
|
+
end
|
46
|
+
|
47
|
+
end # Result
|
48
|
+
end # Support
|
49
|
+
end # Ward
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Ward
|
2
|
+
# Pairs a context (or chain) with a matcher.
|
3
|
+
#
|
4
|
+
class Validator
|
5
|
+
|
6
|
+
# Returns the context.
|
7
|
+
#
|
8
|
+
# @return [Ward::ContextChain, Ward::Context]
|
9
|
+
#
|
10
|
+
attr_reader :context
|
11
|
+
|
12
|
+
# Returns the matcher.
|
13
|
+
#
|
14
|
+
# @return [Ward::Matchers::Matcher]
|
15
|
+
#
|
16
|
+
attr_reader :matcher
|
17
|
+
|
18
|
+
# The scenarios under which the validator will be run.
|
19
|
+
#
|
20
|
+
# @return [Enumerable<Symbol>]
|
21
|
+
#
|
22
|
+
attr_reader :scenarios
|
23
|
+
|
24
|
+
# An override to the default message.
|
25
|
+
#
|
26
|
+
# If set, this message will always be used if the validation fails,
|
27
|
+
# regardless of whether it is used as a positive or negative assertion.
|
28
|
+
#
|
29
|
+
# @return [Hash, String, nil]
|
30
|
+
# Returns a Hash if the user wishes to use a different message depending
|
31
|
+
# on the error state of the matcher (certain matchers, like Has, return
|
32
|
+
# a different error depending on what went wrong), a String if an
|
33
|
+
# explicit error message is set, or nil if no error message is set (in
|
34
|
+
# which case the default error for the matcher will be used).
|
35
|
+
#
|
36
|
+
attr_reader :message
|
37
|
+
|
38
|
+
# Creates a new Validator.
|
39
|
+
#
|
40
|
+
# @param [Ward::ContextChain, Ward::Context] context
|
41
|
+
# The context to be used to fetch the value for the matcher.
|
42
|
+
# @param [Ward::Matchers::Matcher] matcher
|
43
|
+
# A matcher to be used to validate the context value.
|
44
|
+
# @param [Hash] options
|
45
|
+
# Extra options used to customise the Validator.
|
46
|
+
#
|
47
|
+
# @option options [Symbol, Array<Symbol>] :scenarios
|
48
|
+
# An array of scenarios in which this validator should be run
|
49
|
+
# @option options [Hash{Symbol => String}, String] :message
|
50
|
+
# The error message to be used if the validator fails (see
|
51
|
+
# DSL::ValidationBuilder#message).
|
52
|
+
# @option options [String] :context_name
|
53
|
+
# The name to be used for the context when generating error messages.
|
54
|
+
#
|
55
|
+
def initialize(context, matcher, options = {})
|
56
|
+
@context, @matcher = context, matcher
|
57
|
+
@scenarios = Array(options[:scenarios] || :default).freeze
|
58
|
+
@negative = options[:negative] || false
|
59
|
+
@message = options[:message].freeze
|
60
|
+
@context_name = options[:context_name].freeze
|
61
|
+
end
|
62
|
+
|
63
|
+
# Determines if the validator is valid for the given record.
|
64
|
+
#
|
65
|
+
# The return value deviates from the ValidatorSet API -- where #valid?
|
66
|
+
# returns only a boolean value -- but that's fine since you shouldn't be
|
67
|
+
# calling Validator#valid? in your applications anyway. :)
|
68
|
+
#
|
69
|
+
# @param [Object] record
|
70
|
+
# The object whose attribute is to be validated.
|
71
|
+
#
|
72
|
+
# @return [Array<Boolean, {nil, String, Symbol}>]
|
73
|
+
# Returns an array with two elements. The first is always either true or
|
74
|
+
# false, indicating whether the validator passed, and the second is the
|
75
|
+
# raw error message returned by the matcher.
|
76
|
+
#
|
77
|
+
def validate(record)
|
78
|
+
# If the matches? method on the matcher takes two arguments, send in the
|
79
|
+
# record as well as the value.
|
80
|
+
result = if @matcher.method(:matches?).arity != 1
|
81
|
+
@matcher.matches?(@context.value(record), record)
|
82
|
+
else
|
83
|
+
@matcher.matches?(@context.value(record))
|
84
|
+
end
|
85
|
+
|
86
|
+
result, error = if defined?(Rubinius)
|
87
|
+
# Rubinius treats any value which responds to #to_a as being
|
88
|
+
# array-like, thus multiple assignment breaks if the matcher returns
|
89
|
+
# such an object.
|
90
|
+
result.is_a?(Array) ? result : [result].flatten
|
91
|
+
else
|
92
|
+
result
|
93
|
+
end
|
94
|
+
|
95
|
+
(!! result) ^ negative? ? [ true, nil ] : [ false, error_for(error) ]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns if the validator should be run as part of the given scenario.
|
99
|
+
#
|
100
|
+
# @param [Symbol] scenario
|
101
|
+
# A name identifying the scenario.
|
102
|
+
#
|
103
|
+
# @return [Boolean]
|
104
|
+
#
|
105
|
+
def scenario?(scenario)
|
106
|
+
@scenarios.include?(scenario)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns if the validator expects that the matcher _not_ match.
|
110
|
+
#
|
111
|
+
# Use with the +is_not+ and +does_not+ DSL keyword.
|
112
|
+
#
|
113
|
+
# @return [Boolean]
|
114
|
+
#
|
115
|
+
def negative?
|
116
|
+
@negative
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# Returns the error message when the matcher fails.
|
122
|
+
#
|
123
|
+
# If the validator has a message defined, it will always be used in
|
124
|
+
# preference over any error returned by the matcher, or defiend in the
|
125
|
+
# language files.
|
126
|
+
#
|
127
|
+
# @param [nil, Symbol, String] key
|
128
|
+
# @return [String]
|
129
|
+
#
|
130
|
+
# @see Ward::Errors.error_for
|
131
|
+
# @see Ward::Matchers::Matcher#format_error
|
132
|
+
#
|
133
|
+
def error_for(key)
|
134
|
+
initial = @message || Ward::Errors.error_for(matcher, negative?, key)
|
135
|
+
|
136
|
+
error = initial % matcher.customise_error_values(
|
137
|
+
:expected => matcher.expected,
|
138
|
+
:context => @context_name || context.natural_name)
|
139
|
+
|
140
|
+
error.strip!
|
141
|
+
error[0] = error[0].chr.upcase
|
142
|
+
|
143
|
+
error
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end # Ward
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Ward
|
2
|
+
# Holds one or more Validators which are associated with a particular class.
|
3
|
+
#
|
4
|
+
# This class is considered part of Ward's semi-public API; which is to say
|
5
|
+
# that it's methods should not be called in end-user applications, but are
|
6
|
+
# available for library and plugin authors who wish to extend it's
|
7
|
+
# functionality.
|
8
|
+
#
|
9
|
+
# @example Retrieving the validators for a class.
|
10
|
+
# MyClass.validators # => #<ValidatorSet [#<Validator>, ...]>
|
11
|
+
#
|
12
|
+
class ValidatorSet
|
13
|
+
|
14
|
+
# Builds a ValidatorSet using the given block.
|
15
|
+
#
|
16
|
+
# NOTE: Providing an existing ValidatorSet will result in a copy of that
|
17
|
+
# set being mutated; the original will not be changed.
|
18
|
+
#
|
19
|
+
# @param [Ward::ValidatorSet] set
|
20
|
+
# A ValidatorSet to which the built validators should be added.
|
21
|
+
#
|
22
|
+
# @return [Ward::ValidatorSet]
|
23
|
+
#
|
24
|
+
def self.build(set = nil, &block)
|
25
|
+
Ward::DSL::ValidationBlock.new(set, &block).to_validator_set
|
26
|
+
end
|
27
|
+
|
28
|
+
include Enumerable
|
29
|
+
|
30
|
+
# Creates a new ValidatorSet.
|
31
|
+
#
|
32
|
+
# @param [Enumerable<Ward::Validator>] validators
|
33
|
+
# An optional collection of validators.
|
34
|
+
#
|
35
|
+
def initialize(validators = [])
|
36
|
+
@validators = validators
|
37
|
+
end
|
38
|
+
|
39
|
+
# Determines if all of the contained validators are valid for the given
|
40
|
+
# record in the given scenario.
|
41
|
+
#
|
42
|
+
# @param [Object] record
|
43
|
+
# The object whose validations are to be run.
|
44
|
+
# @param [Symbol] scenario
|
45
|
+
# A name identifying the scenario.
|
46
|
+
#
|
47
|
+
# @return [Boolean]
|
48
|
+
# Returns true if the record validated, otherwise returns false.
|
49
|
+
#
|
50
|
+
def valid?(record, scenario = :default)
|
51
|
+
inject(true) do |result, validator|
|
52
|
+
(! validator.scenario?(scenario) or
|
53
|
+
validator.validate(record).first) and result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# A more useful version of #valid?
|
58
|
+
#
|
59
|
+
# Whereas #valid? simply returns true or false indicating whether the
|
60
|
+
# validations passed, #validate returns a Ward::Support::Result instance
|
61
|
+
# which describes the outcome of running the validators, and encapsulates
|
62
|
+
# any errors messages which resulted.
|
63
|
+
#
|
64
|
+
# @param [Object] record
|
65
|
+
# The object whose validations are to be run.
|
66
|
+
# @param [Symbol] scenario
|
67
|
+
# A name identifying the scenario.
|
68
|
+
#
|
69
|
+
# @return [Ward::Support::Result]
|
70
|
+
#
|
71
|
+
def validate(record, scenario = :default)
|
72
|
+
errors = Ward::Errors.new
|
73
|
+
|
74
|
+
@validators.each do |validator|
|
75
|
+
next unless validator.scenario?(scenario)
|
76
|
+
result, error = validator.validate(record)
|
77
|
+
errors.add(validator.context, error) unless result == true
|
78
|
+
end
|
79
|
+
|
80
|
+
Support::Result.new(errors)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Iterates through each validator in the set.
|
84
|
+
#
|
85
|
+
# @yield [validator] Yields each validator in turn.
|
86
|
+
# @yieldparam [Ward::Validator]
|
87
|
+
#
|
88
|
+
def each(&block)
|
89
|
+
@validators.each(&block)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Adds a new validator to set.
|
93
|
+
#
|
94
|
+
# @param [Ward::Validator] validator
|
95
|
+
# The validator to be added to the set.
|
96
|
+
#
|
97
|
+
def push(validator)
|
98
|
+
@validators << validator
|
99
|
+
end
|
100
|
+
|
101
|
+
alias_method :<<, :push
|
102
|
+
|
103
|
+
# Adds the validators contained in +other+ to the receiving set.
|
104
|
+
#
|
105
|
+
# @param [Ward::ValidatorSet] set
|
106
|
+
# The validator set whose validators are to be added to the receiver.
|
107
|
+
#
|
108
|
+
#
|
109
|
+
def merge!(other)
|
110
|
+
@validators |= other.to_a
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
end # ValidatorSet
|
115
|
+
end # Ward
|
data/lib/ward/version.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
share_examples_for 'Has matcher relativity method' do
|
2
|
+
before(:all) do
|
3
|
+
@expectation ||= [5]
|
4
|
+
@matcher = Ward::Matchers::Has.new
|
5
|
+
@return = @matcher.__send__(@method, *@expectation)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should return the matcher' do
|
9
|
+
@return.should == @matcher
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should set the matcher relativity" do
|
13
|
+
@matcher.relativity.should == @relativity
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Ensures that a given class has a particular public instance method defined
|
2
|
+
# without instantiation. Works around Ruby 1.8 and 1.9 differences.
|
3
|
+
#
|
4
|
+
::Spec::Matchers.define :have_public_method_defined do |value|
|
5
|
+
match do |klass|
|
6
|
+
klass.public_method_defined?(value.to_sym)
|
7
|
+
end
|
8
|
+
|
9
|
+
description do
|
10
|
+
"should have public instance method defined ##{value.to_s}"
|
11
|
+
end
|
12
|
+
|
13
|
+
failure_message_for_should do |klass|
|
14
|
+
"expected #{klass.inspect} to define public instance method " \
|
15
|
+
"#{value.inspect}, but it didn't"
|
16
|
+
end
|
17
|
+
|
18
|
+
failure_message_for_should_not do |klass|
|
19
|
+
"expected #{klass.inspect} to not define public instance method " \
|
20
|
+
"#{value.inspect}, but it did"
|
21
|
+
end
|
22
|
+
end
|
data/spec/rcov.opts
ADDED
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
require 'date' # Used in the CloseTo matcher spec.
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'spec'
|
8
|
+
require 'spec/autorun'
|
9
|
+
|
10
|
+
require 'ward'
|
11
|
+
require 'ward/spec'
|
12
|
+
|
13
|
+
# Spec libraries.
|
14
|
+
spec_libs = Dir.glob(File.expand_path(File.dirname(__FILE__)) + '/lib/**/*.rb')
|
15
|
+
spec_libs.each { |file| require file }
|
16
|
+
|
17
|
+
Spec::Runner.configure do |config|
|
18
|
+
|
19
|
+
end
|