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
@@ -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
|