ward 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,54 @@
|
|
1
|
+
module Ward
|
2
|
+
module Matchers
|
3
|
+
# Tests whether the validation value is contained in the expected value.
|
4
|
+
#
|
5
|
+
# The expected value can be anything which responds to +include?+; if it
|
6
|
+
# returns true, the matcher will pass.
|
7
|
+
#
|
8
|
+
# @example Person role is either :admin or :staff
|
9
|
+
#
|
10
|
+
# class Person
|
11
|
+
# validate do |person|
|
12
|
+
# person.role.is.in([:admin, :staff])
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
class Include < Matcher
|
17
|
+
|
18
|
+
# Creates a new Include matcher instance.
|
19
|
+
#
|
20
|
+
# @param [#include?] expected
|
21
|
+
# The expected value for the matcher.
|
22
|
+
#
|
23
|
+
def initialize(expected = nil)
|
24
|
+
raise ArgumentError,
|
25
|
+
'The Include matcher requires that a value which responds ' \
|
26
|
+
'to #include? is supplied' unless expected.respond_to?(:include?)
|
27
|
+
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns whether the given value is included in the expected value.
|
32
|
+
#
|
33
|
+
# @param [#include?] actual
|
34
|
+
# The validation value.
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
37
|
+
#
|
38
|
+
def matches?(actual)
|
39
|
+
@expected.include?(actual)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Adds extra information to the error message.
|
43
|
+
#
|
44
|
+
# @param [String] error
|
45
|
+
# @return [String]
|
46
|
+
#
|
47
|
+
def customise_error_values(values)
|
48
|
+
values[:expected] = Ward::Errors.format_exclusive_list(@expected)
|
49
|
+
values
|
50
|
+
end
|
51
|
+
|
52
|
+
end # Include
|
53
|
+
end # Matchers
|
54
|
+
end # Ward
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Ward
|
2
|
+
module Matchers
|
3
|
+
# Tests whether the validation value matches the expected value with a
|
4
|
+
# regular expression.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
#
|
8
|
+
# class Person
|
9
|
+
# validate do |person|
|
10
|
+
# person.name.matches(/^Michael (Scarn|Scott)$/)
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
class Match < Matcher
|
15
|
+
|
16
|
+
# Returns whether the given value matches the expected value.
|
17
|
+
#
|
18
|
+
# @param [#include?] actual
|
19
|
+
# The validation value.
|
20
|
+
#
|
21
|
+
# @return [Boolean]
|
22
|
+
#
|
23
|
+
def matches?(actual)
|
24
|
+
actual.match(@expected)
|
25
|
+
end
|
26
|
+
|
27
|
+
end # Match
|
28
|
+
end # Matchers
|
29
|
+
end # Ward
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Ward
|
2
|
+
module Matchers
|
3
|
+
# A base class used for creating custom matchers.
|
4
|
+
#
|
5
|
+
# @abstract
|
6
|
+
#
|
7
|
+
class Matcher
|
8
|
+
|
9
|
+
# Returns the expected value.
|
10
|
+
#
|
11
|
+
# @return [Object]
|
12
|
+
#
|
13
|
+
attr_reader :expected
|
14
|
+
|
15
|
+
# Returns any extra arguments given to the matcher.
|
16
|
+
#
|
17
|
+
# @return [Array]
|
18
|
+
#
|
19
|
+
attr_reader :extra_args
|
20
|
+
|
21
|
+
# Creates a new matcher instance.
|
22
|
+
#
|
23
|
+
# @param [Object] expected
|
24
|
+
# The expected value for the matcher.
|
25
|
+
#
|
26
|
+
def initialize(expected = nil, *extra_args)
|
27
|
+
@expected = expected
|
28
|
+
@extra_args = extra_args
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns whether the given value matches the expected value.
|
32
|
+
#
|
33
|
+
# @param [Object] actual
|
34
|
+
# The validation value.
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
37
|
+
#
|
38
|
+
# @abstract
|
39
|
+
#
|
40
|
+
def matches?(actual)
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
# Allows matcher subclasses to change -- or add values to -- the hash
|
45
|
+
# whose values are interpolated with the error string.
|
46
|
+
#
|
47
|
+
# @param [Hash]
|
48
|
+
# The standard values which are interpolated with the error string;
|
49
|
+
# contains :context and :expected keys.
|
50
|
+
#
|
51
|
+
# @return [Hash{Symbol => String}]
|
52
|
+
#
|
53
|
+
def customise_error_values(values)
|
54
|
+
values
|
55
|
+
end
|
56
|
+
|
57
|
+
# Determines the key to be used to find error messages in lang files.
|
58
|
+
#
|
59
|
+
# @return [String]
|
60
|
+
#
|
61
|
+
def self.error_id
|
62
|
+
@error_id ||= ActiveSupport::Inflector.underscore(
|
63
|
+
ActiveSupport::Inflector.demodulize(to_s))
|
64
|
+
end
|
65
|
+
|
66
|
+
end # Matcher
|
67
|
+
end # Matchers
|
68
|
+
end # Ward
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Ward
|
2
|
+
module Matchers
|
3
|
+
# Tests whether the validation value is nil.
|
4
|
+
#
|
5
|
+
# @todo Remove once the predicate matcher DSL is available.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
#
|
9
|
+
# class AverageDogWalker
|
10
|
+
# validate do |walker|
|
11
|
+
# walker.common_sense.is.nil # Sigh.
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
class Nil < Matcher
|
16
|
+
|
17
|
+
# Returns whether the given value is nil.
|
18
|
+
#
|
19
|
+
# @param [Object] actual
|
20
|
+
# The validation value.
|
21
|
+
#
|
22
|
+
# @return [Boolean]
|
23
|
+
#
|
24
|
+
def matches?(actual)
|
25
|
+
actual.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
end # Nil
|
29
|
+
end # Matchers
|
30
|
+
end # Ward
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Ward
|
2
|
+
module Matchers
|
3
|
+
# Tests that a predicate method responds with true.
|
4
|
+
#
|
5
|
+
# @example Validating that the predicate method responds with true.
|
6
|
+
#
|
7
|
+
# class Person
|
8
|
+
# validate do |person|
|
9
|
+
# person.is.important?
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
class Predicate < Matcher
|
14
|
+
|
15
|
+
# Returns whether the given value is responds true to the predicate.
|
16
|
+
#
|
17
|
+
# @param [Object] actual
|
18
|
+
# The validation value.
|
19
|
+
#
|
20
|
+
# @return [Boolean]
|
21
|
+
#
|
22
|
+
def matches?(actual)
|
23
|
+
raise ArgumentError, "#{actual.inspect} does not respond " \
|
24
|
+
"to #{expected}" unless actual.respond_to?(@expected)
|
25
|
+
|
26
|
+
actual.__send__(@expected)
|
27
|
+
end
|
28
|
+
|
29
|
+
end # Predicate
|
30
|
+
end # Matchers
|
31
|
+
end # Ward
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Ward
|
2
|
+
module Matchers
|
3
|
+
# Tests whether the validation value is present.
|
4
|
+
#
|
5
|
+
# A "present" value is one which:
|
6
|
+
#
|
7
|
+
# * is a non-blank string, containing more than just whitespace
|
8
|
+
# * responds to #empty? and returns false
|
9
|
+
# * does not evaluate to false (i.e. not nil or false)
|
10
|
+
#
|
11
|
+
# This is equivalent to ActiveSupport's +blank?+ extension methods but
|
12
|
+
# note that +blank?+ is not actually used; if you define +blank?+ on a
|
13
|
+
# class which is provided to the matcher it will not be called.
|
14
|
+
#
|
15
|
+
# @todo
|
16
|
+
# Once the predicate matcher is available, amend the class documentation
|
17
|
+
# to provide an example of how to call +blank?+ explicitly.
|
18
|
+
#
|
19
|
+
# @example Validating that the name attribute is present
|
20
|
+
#
|
21
|
+
# class Person
|
22
|
+
# validate do |person|
|
23
|
+
# person.name.is.present
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# @example Validating that the job attribute is not present :(
|
28
|
+
#
|
29
|
+
# class Person
|
30
|
+
# validate do |person|
|
31
|
+
# person.job.is_not.present
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
class Present < Matcher
|
36
|
+
|
37
|
+
# Returns whether the given value is present.
|
38
|
+
#
|
39
|
+
# @param [Object] actual
|
40
|
+
# The validation value.
|
41
|
+
#
|
42
|
+
# @return [Boolean]
|
43
|
+
#
|
44
|
+
def matches?(actual)
|
45
|
+
if actual.kind_of?(String)
|
46
|
+
actual.match(/\S/)
|
47
|
+
elsif actual.respond_to?(:empty?)
|
48
|
+
not actual.empty?
|
49
|
+
else
|
50
|
+
actual
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end # Present
|
55
|
+
end # Matchers
|
56
|
+
end # Ward
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Ward
|
2
|
+
module Matchers
|
3
|
+
# Tests whether the validation value satisfies the given block.
|
4
|
+
#
|
5
|
+
# If the block returns any value other than false, the matcher will assume
|
6
|
+
# that the match passed.
|
7
|
+
#
|
8
|
+
# Alternatively, you may pass a Symbol which identifies a method name; the
|
9
|
+
# method will be run with it's return value used to determine if the
|
10
|
+
# matcher passed.
|
11
|
+
#
|
12
|
+
# Adding an explict error message is advised, since the message generated
|
13
|
+
# by Ward isn't very helpful: "... is invalid".
|
14
|
+
#
|
15
|
+
# @example Matching with a block
|
16
|
+
#
|
17
|
+
# class Record
|
18
|
+
# validate do |record|
|
19
|
+
# record.name.satisfies do |value, record|
|
20
|
+
# value == 'Michael Scarn'
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @example With a runtime error message
|
26
|
+
#
|
27
|
+
# class Record
|
28
|
+
# validate do |record|
|
29
|
+
# record.name.satisfies do |value, record|
|
30
|
+
# value == 'Michael Scarn' || [false, "Ooooh noooo"]
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
class Satisfy < Matcher
|
36
|
+
|
37
|
+
# Creates a new matcher instance.
|
38
|
+
#
|
39
|
+
# @param [Object] expected
|
40
|
+
# The expected value for the matcher.
|
41
|
+
#
|
42
|
+
def initialize(expected = nil, *extra_args, &block)
|
43
|
+
super(block, *extra_args)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns whether the given value is satisfied by the expected block.
|
47
|
+
#
|
48
|
+
# @param [Object] actual
|
49
|
+
# The validation value.
|
50
|
+
# @param [Object] record
|
51
|
+
# The full record.
|
52
|
+
#
|
53
|
+
# @return [Boolean]
|
54
|
+
#
|
55
|
+
def matches?(actual, record = nil)
|
56
|
+
if @expected.arity != 1
|
57
|
+
@expected.call(actual, record)
|
58
|
+
else
|
59
|
+
@expected.call(actual)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end # Satisfy
|
64
|
+
end # Matchers
|
65
|
+
end # Ward
|
data/lib/ward/spec.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# The 'spec' directory contains various reuseable helpers for use with RSpec.
|
3
|
+
#
|
4
|
+
# The helpers are _not_ loaded when doing +require 'ward'+; if you wish to
|
5
|
+
# use these helpers -- perhaps for testing your own custom validations -- you
|
6
|
+
# must:
|
7
|
+
#
|
8
|
+
# require 'ward'
|
9
|
+
# require 'ward/spec'
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'ward/spec/matcher_matcher'
|
13
|
+
|
14
|
+
module Ward
|
15
|
+
# Provides helpers and matchers for use in RSpec examples.
|
16
|
+
module Spec; end
|
17
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Ward
|
2
|
+
module Spec
|
3
|
+
# Since Ward matchers are permitted to return either false, or an Array
|
4
|
+
# whose first member is false, to indicate a failure, determining the
|
5
|
+
# status of a match attempt relies in you knowing this in advance:
|
6
|
+
#
|
7
|
+
# result, error = matcher.matches?(value)
|
8
|
+
# result.should be_false
|
9
|
+
#
|
10
|
+
# The helpers within the MatcherMatcher module simplify this situation:
|
11
|
+
#
|
12
|
+
# matcher.should pass_matcher_with(value)
|
13
|
+
# matcher.should fail_matcher_with(value)
|
14
|
+
#
|
15
|
+
# The +fail_matcher_with+ helper also provides the ability to check the
|
16
|
+
# error message returned by the matcher:
|
17
|
+
#
|
18
|
+
# matcher.should fail_matcher_with(value, :too_short)
|
19
|
+
#
|
20
|
+
module MatcherMatcher
|
21
|
+
|
22
|
+
# Formats error messages for spec failures.
|
23
|
+
#
|
24
|
+
# @param [String, Symbol] result
|
25
|
+
# Did you expect a :pass or :fail?
|
26
|
+
# @param [Ward::Matchers::Matcher] matcher
|
27
|
+
# The matcher instance which was used for the expectation.
|
28
|
+
# @param [Object] value
|
29
|
+
# The actual value which was supplied to the Ward matcher.
|
30
|
+
#
|
31
|
+
# @return [String]
|
32
|
+
#
|
33
|
+
def self.error_message(result, matcher, value)
|
34
|
+
status = if result.to_sym == :pass then 'failed' else 'passed' end
|
35
|
+
|
36
|
+
expected = unless matcher.expected.nil?
|
37
|
+
"expected: #{matcher.expected.inspect}, "
|
38
|
+
end
|
39
|
+
|
40
|
+
"expected #{matcher.class.inspect} matcher to #{result}, but it " \
|
41
|
+
"#{status} (#{expected}actual: #{value.inspect})"
|
42
|
+
end
|
43
|
+
|
44
|
+
# An RSpec matcher which tests that the Ward matcher passes with a
|
45
|
+
# particular value.
|
46
|
+
#
|
47
|
+
::Spec::Matchers.define :pass_matcher_with do |value|
|
48
|
+
match do |matcher|
|
49
|
+
case result = matcher.matches?(value)
|
50
|
+
when false, nil then false
|
51
|
+
when Array then !! result.first
|
52
|
+
else true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
failure_message_for_should do |matcher|
|
57
|
+
Ward::Spec::MatcherMatcher.error_message(:pass, matcher, value)
|
58
|
+
end
|
59
|
+
|
60
|
+
failure_message_for_should_not do |matcher|
|
61
|
+
Ward::Spec::MatcherMatcher.error_message(:fail, matcher, value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# An RSpec matcher which tests that the Ward matcher fails with a
|
66
|
+
# particular value.
|
67
|
+
#
|
68
|
+
::Spec::Matchers.define :fail_matcher_with do |value|
|
69
|
+
# Allows further customisation of the matcher, asserting that a
|
70
|
+
# particular error message was returned along with the failure. Using
|
71
|
+
# this with +should_not+ makes little sense.
|
72
|
+
#
|
73
|
+
# @param [Symbol, String] expected_error
|
74
|
+
#
|
75
|
+
def with_error(expected_error)
|
76
|
+
@expected_error = expected_error
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
match do |matcher|
|
81
|
+
result, error = matcher.matches?(value)
|
82
|
+
|
83
|
+
if @expected_error and not error.eql?(@expected_error)
|
84
|
+
@actual_error = error
|
85
|
+
false
|
86
|
+
elsif result == false || result.nil?
|
87
|
+
true
|
88
|
+
else
|
89
|
+
false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
failure_message_for_should do |matcher|
|
94
|
+
if @actual_error
|
95
|
+
expected = unless matcher.expected.nil?
|
96
|
+
"expected: #{matcher.expected.inspect}, "
|
97
|
+
end
|
98
|
+
|
99
|
+
"expected #{matcher.class.inspect} matcher to fail with error " \
|
100
|
+
"#{@expected_error.inspect}, but it failed with " \
|
101
|
+
"#{@actual_error.inspect} (#{expected}actual: #{value.inspect})"
|
102
|
+
else
|
103
|
+
Ward::Spec::MatcherMatcher.error_message(:fail, matcher, value)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
failure_message_for_should_not do |matcher|
|
108
|
+
Ward::Spec::MatcherMatcher.error_message(:pass, matcher, value)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end # MatcherMatcher
|
113
|
+
end # Spec
|
114
|
+
end # Ward
|