matchers 0.1.0.pre.1
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/lib/matcher/assertions.rb +19 -0
- data/lib/matcher/autoload.rb +5 -0
- data/lib/matcher/base.rb +183 -0
- data/lib/matcher/compatibility.rb +34 -0
- data/lib/matcher/debug.rb +62 -0
- data/lib/matcher/dsl/builder.rb +99 -0
- data/lib/matcher/dsl/chain.rb +84 -0
- data/lib/matcher/dsl/expression_dsl.rb +306 -0
- data/lib/matcher/dsl/matcher_dsl.rb +5 -0
- data/lib/matcher/dsl/optional.rb +82 -0
- data/lib/matcher/dsl/optional_chain.rb +24 -0
- data/lib/matcher/dsl/others.rb +28 -0
- data/lib/matcher/errors/and_error.rb +88 -0
- data/lib/matcher/errors/boolean_collector.rb +51 -0
- data/lib/matcher/errors/element_error.rb +24 -0
- data/lib/matcher/errors/empty_error.rb +23 -0
- data/lib/matcher/errors/error.rb +39 -0
- data/lib/matcher/errors/error_collector.rb +100 -0
- data/lib/matcher/errors/nested_error.rb +98 -0
- data/lib/matcher/errors/or_error.rb +88 -0
- data/lib/matcher/expression_cache.rb +57 -0
- data/lib/matcher/expression_labeler.rb +96 -0
- data/lib/matcher/expressions/array_expression.rb +45 -0
- data/lib/matcher/expressions/block.rb +189 -0
- data/lib/matcher/expressions/call.rb +307 -0
- data/lib/matcher/expressions/call_error.rb +45 -0
- data/lib/matcher/expressions/constant.rb +53 -0
- data/lib/matcher/expressions/expression.rb +237 -0
- data/lib/matcher/expressions/expression_walker.rb +77 -0
- data/lib/matcher/expressions/hash_expression.rb +59 -0
- data/lib/matcher/expressions/proc_expression.rb +96 -0
- data/lib/matcher/expressions/range_expression.rb +65 -0
- data/lib/matcher/expressions/recorder.rb +136 -0
- data/lib/matcher/expressions/rescue_last_error_expression.rb +49 -0
- data/lib/matcher/expressions/set_expression.rb +45 -0
- data/lib/matcher/expressions/string_expression.rb +53 -0
- data/lib/matcher/expressions/symbol_proc.rb +53 -0
- data/lib/matcher/expressions/variable.rb +87 -0
- data/lib/matcher/hash_stack.rb +52 -0
- data/lib/matcher/list.rb +102 -0
- data/lib/matcher/markers.rb +7 -0
- data/lib/matcher/matcher_cache.rb +18 -0
- data/lib/matcher/matchers/all_matcher.rb +60 -0
- data/lib/matcher/matchers/always_matcher.rb +34 -0
- data/lib/matcher/matchers/any_matcher.rb +70 -0
- data/lib/matcher/matchers/array_matcher.rb +72 -0
- data/lib/matcher/matchers/block_matcher.rb +61 -0
- data/lib/matcher/matchers/boolean_matcher.rb +37 -0
- data/lib/matcher/matchers/dig_matcher.rb +149 -0
- data/lib/matcher/matchers/each_matcher.rb +85 -0
- data/lib/matcher/matchers/each_pair_matcher.rb +119 -0
- data/lib/matcher/matchers/equal_matcher.rb +198 -0
- data/lib/matcher/matchers/equal_set_matcher.rb +112 -0
- data/lib/matcher/matchers/expression_matcher.rb +69 -0
- data/lib/matcher/matchers/filter_matcher.rb +115 -0
- data/lib/matcher/matchers/hash_matcher.rb +315 -0
- data/lib/matcher/matchers/imply_matcher.rb +83 -0
- data/lib/matcher/matchers/imply_some_matcher.rb +116 -0
- data/lib/matcher/matchers/index_by_matcher.rb +177 -0
- data/lib/matcher/matchers/inline_matcher.rb +101 -0
- data/lib/matcher/matchers/keys_matcher.rb +131 -0
- data/lib/matcher/matchers/kind_of_matcher.rb +35 -0
- data/lib/matcher/matchers/lazy_all_matcher.rb +69 -0
- data/lib/matcher/matchers/lazy_any_matcher.rb +69 -0
- data/lib/matcher/matchers/let_matcher.rb +73 -0
- data/lib/matcher/matchers/map_matcher.rb +148 -0
- data/lib/matcher/matchers/negated_array_matcher.rb +38 -0
- data/lib/matcher/matchers/negated_each_matcher.rb +36 -0
- data/lib/matcher/matchers/negated_each_pair_matcher.rb +38 -0
- data/lib/matcher/matchers/negated_imply_some_matcher.rb +46 -0
- data/lib/matcher/matchers/negated_matcher.rb +25 -0
- data/lib/matcher/matchers/negated_project_matcher.rb +31 -0
- data/lib/matcher/matchers/never_matcher.rb +35 -0
- data/lib/matcher/matchers/one_matcher.rb +68 -0
- data/lib/matcher/matchers/optional_matcher.rb +38 -0
- data/lib/matcher/matchers/parse_float_matcher.rb +86 -0
- data/lib/matcher/matchers/parse_integer_matcher.rb +101 -0
- data/lib/matcher/matchers/parse_iso8601_helper.rb +41 -0
- data/lib/matcher/matchers/parse_iso8601_matcher.rb +52 -0
- data/lib/matcher/matchers/parse_json_helper.rb +43 -0
- data/lib/matcher/matchers/parse_json_matcher.rb +59 -0
- data/lib/matcher/matchers/project_matcher.rb +72 -0
- data/lib/matcher/matchers/raises_matcher.rb +131 -0
- data/lib/matcher/matchers/range_matcher.rb +50 -0
- data/lib/matcher/matchers/reference_matcher.rb +213 -0
- data/lib/matcher/matchers/reference_matcher_collection.rb +57 -0
- data/lib/matcher/matchers/regexp_matcher.rb +86 -0
- data/lib/matcher/messages/expected_phrasing.rb +355 -0
- data/lib/matcher/messages/message.rb +104 -0
- data/lib/matcher/messages/message_builder.rb +35 -0
- data/lib/matcher/messages/message_rules.rb +240 -0
- data/lib/matcher/messages/namespaced_message_builder.rb +19 -0
- data/lib/matcher/messages/phrasing.rb +59 -0
- data/lib/matcher/messages/standard_message_builder.rb +105 -0
- data/lib/matcher/patterns/ast_mapping.rb +42 -0
- data/lib/matcher/patterns/capture_hole.rb +33 -0
- data/lib/matcher/patterns/constant_hole.rb +14 -0
- data/lib/matcher/patterns/hole.rb +30 -0
- data/lib/matcher/patterns/method_hole.rb +62 -0
- data/lib/matcher/patterns/pattern.rb +104 -0
- data/lib/matcher/patterns/pattern_building.rb +39 -0
- data/lib/matcher/patterns/pattern_capture.rb +11 -0
- data/lib/matcher/patterns/pattern_match.rb +29 -0
- data/lib/matcher/patterns/variable_hole.rb +14 -0
- data/lib/matcher/reporter.rb +103 -0
- data/lib/matcher/rules/message_factory.rb +26 -0
- data/lib/matcher/rules/message_rule.rb +18 -0
- data/lib/matcher/rules/message_rule_context.rb +26 -0
- data/lib/matcher/rules/rule_builder.rb +29 -0
- data/lib/matcher/rules/rule_set.rb +57 -0
- data/lib/matcher/rules/transform_builder.rb +24 -0
- data/lib/matcher/rules/transform_mapping.rb +5 -0
- data/lib/matcher/rules/transform_rule.rb +21 -0
- data/lib/matcher/state.rb +40 -0
- data/lib/matcher/testing/error_builder.rb +62 -0
- data/lib/matcher/testing/error_checker.rb +514 -0
- data/lib/matcher/testing/error_testing.rb +37 -0
- data/lib/matcher/testing/pattern_testing.rb +11 -0
- data/lib/matcher/testing/pattern_testing_scope.rb +34 -0
- data/lib/matcher/testing.rb +107 -0
- data/lib/matcher/undefined.rb +10 -0
- data/lib/matcher/utils/mapping_utils.rb +61 -0
- data/lib/matcher/utils.rb +72 -0
- data/lib/matcher/version.rb +5 -0
- data/lib/matcher.rb +346 -0
- metadata +174 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
module PatternBuilding
|
|
5
|
+
include ExpressionDsl
|
|
6
|
+
|
|
7
|
+
def pattern_of(value)
|
|
8
|
+
Pattern.new(expression_of(value))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def capture(key, pattern)
|
|
12
|
+
pattern = expression_of(pattern)
|
|
13
|
+
hole = CaptureHole.new(key, pattern)
|
|
14
|
+
|
|
15
|
+
expr(hole)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def hole(key, &filter)
|
|
19
|
+
expr(Hole.new(key, filter))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def var(key)
|
|
23
|
+
expr(VariableHole.new(key))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def const(key)
|
|
27
|
+
expr(ConstantHole.new(key))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def method_hole(key, receiver, method, *args, **kwargs)
|
|
31
|
+
receiver = expression_of(receiver)
|
|
32
|
+
args = args.map { expression_of(_1) }
|
|
33
|
+
kwargs = kwargs.transform_values { expression_of(_1) }
|
|
34
|
+
hole = MethodHole.new(key, receiver, method, args, kwargs)
|
|
35
|
+
|
|
36
|
+
expr(hole)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class PatternMatch
|
|
5
|
+
def initialize
|
|
6
|
+
@captures = {}
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def [](key)
|
|
10
|
+
@captures[key]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def include?(key)
|
|
14
|
+
@captures.include?(key)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def capture(key, expression, mapping)
|
|
18
|
+
@captures[key] = PatternCapture.new(expression, mapping)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def value_paths
|
|
22
|
+
@captures.transform_values(&:value_path)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def expressions
|
|
26
|
+
@captures.transform_values(&:expression)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
5
|
+
module Matcher
|
|
6
|
+
class Reporter
|
|
7
|
+
##
|
|
8
|
+
# Formats an error tree as a human-readable string
|
|
9
|
+
# @example
|
|
10
|
+
# errors = Matcher.build { Integer }.match("foo")
|
|
11
|
+
# puts Reporter.report(errors)
|
|
12
|
+
# # > root: expected a kind of Integer but got "foo"
|
|
13
|
+
# @param error [Error] the error tree from {Base#match}
|
|
14
|
+
# @return [String]
|
|
15
|
+
def self.report(error)
|
|
16
|
+
new.report(error)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@level = 0
|
|
21
|
+
@continue_line = false
|
|
22
|
+
@path_stack = ["root"]
|
|
23
|
+
@phrasing = ExpectedPhrasing.phrasing
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def report(error)
|
|
27
|
+
@io = StringIO.new
|
|
28
|
+
|
|
29
|
+
report_error(error)
|
|
30
|
+
|
|
31
|
+
string = @io.string
|
|
32
|
+
@io.close
|
|
33
|
+
@io = nil
|
|
34
|
+
|
|
35
|
+
string
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def report_error(error)
|
|
41
|
+
case error
|
|
42
|
+
when EmptyError
|
|
43
|
+
report_empty
|
|
44
|
+
when ElementError
|
|
45
|
+
report_element(error)
|
|
46
|
+
when NestedError
|
|
47
|
+
report_nested(error)
|
|
48
|
+
when AndError
|
|
49
|
+
report_and(error)
|
|
50
|
+
when OrError
|
|
51
|
+
report_or(error)
|
|
52
|
+
else
|
|
53
|
+
raise "Illegal error: #{error.inspect}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def report_empty
|
|
58
|
+
"no error"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def report_element(element)
|
|
62
|
+
message = element.message
|
|
63
|
+
|
|
64
|
+
if message.is_a?(Message)
|
|
65
|
+
message = @phrasing.call(@path_stack.last, message)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
line("#{@path_stack.last}: #{message}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def report_nested(nested)
|
|
72
|
+
path = NestedError.key_to_s(nested.key, @path_stack.last)
|
|
73
|
+
|
|
74
|
+
@path_stack.push(path)
|
|
75
|
+
report_error(nested.child)
|
|
76
|
+
@path_stack.pop
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def report_and(error)
|
|
80
|
+
error.children.each { report_error(_1) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def report_or(error)
|
|
84
|
+
line("expected at least one error to be absent:")
|
|
85
|
+
|
|
86
|
+
error.children.each do |n|
|
|
87
|
+
line("- ", newline: false)
|
|
88
|
+
|
|
89
|
+
@level += 1
|
|
90
|
+
report_error(n)
|
|
91
|
+
@level -= 1
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def line(message, newline: true)
|
|
96
|
+
message = " " * @level + message unless @continue_line
|
|
97
|
+
message += "\n" if newline
|
|
98
|
+
|
|
99
|
+
@continue_line = !newline
|
|
100
|
+
@io.print(message)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class MessageFactory
|
|
5
|
+
def initialize(value_paths, expressions, block)
|
|
6
|
+
@value_paths = value_paths
|
|
7
|
+
@expressions = expressions
|
|
8
|
+
@negate = false
|
|
9
|
+
@block = block
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def negate!
|
|
13
|
+
@negate = !@negate
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create(context, value_tree)
|
|
17
|
+
# Using reduce instead of dig so it will fail if an unexpected leaf is
|
|
18
|
+
# encountered.
|
|
19
|
+
values = @value_paths.transform_values { _1.reduce(value_tree, :[]) }
|
|
20
|
+
message = context.instance_exec(values, @expressions, &@block)
|
|
21
|
+
message.negate! if @negate
|
|
22
|
+
|
|
23
|
+
message
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class MessageRule
|
|
5
|
+
def initialize(patterns, block)
|
|
6
|
+
@patterns = patterns
|
|
7
|
+
@block = block
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :patterns
|
|
11
|
+
|
|
12
|
+
def apply(match)
|
|
13
|
+
expressions = @block.arity >= 2 ? match.expressions : nil
|
|
14
|
+
|
|
15
|
+
MessageFactory.new(match.value_paths, expressions, @block)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class MessageRuleContext
|
|
5
|
+
extend Forwardable
|
|
6
|
+
|
|
7
|
+
def initialize(matcher, state)
|
|
8
|
+
@matcher = matcher
|
|
9
|
+
@state = state
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def standard_message
|
|
13
|
+
StandardMessageBuilder.new(!@matcher.negated, @state.actual)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def expression_message
|
|
17
|
+
NamespacedMessageBuilder.new(
|
|
18
|
+
!@matcher.negated, @state.actual, :expression
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def given
|
|
23
|
+
@matcher.expression.given_for(@state.values)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class RuleBuilder
|
|
5
|
+
include PatternBuilding
|
|
6
|
+
|
|
7
|
+
def initialize(rules = [], build_session: Matcher.build_session)
|
|
8
|
+
ExpressionDsl.init(self, build_session)
|
|
9
|
+
|
|
10
|
+
@rules = rules
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :rules
|
|
14
|
+
|
|
15
|
+
def transform(*patterns, negate: false, &block)
|
|
16
|
+
patterns.map! { pattern_of(_1) }
|
|
17
|
+
@rules << TransformRule.new(patterns, negate, block)
|
|
18
|
+
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def message(*patterns, &block)
|
|
23
|
+
patterns.map! { pattern_of(_1) }
|
|
24
|
+
@rules << MessageRule.new(patterns, block)
|
|
25
|
+
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class RuleSet
|
|
5
|
+
def initialize(rules = [], &)
|
|
6
|
+
@rules = rules
|
|
7
|
+
|
|
8
|
+
configure(&) if block_given?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def configure(&)
|
|
12
|
+
Matcher.with_build_session do |build_session|
|
|
13
|
+
builder = RuleBuilder.new(@rules, build_session:)
|
|
14
|
+
builder.instance_exec(&)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def apply(expression)
|
|
21
|
+
cur = expression
|
|
22
|
+
mapping = AstMapping.new
|
|
23
|
+
result = nil
|
|
24
|
+
negate = false
|
|
25
|
+
|
|
26
|
+
while (rule, match = find_rule(cur, mapping))
|
|
27
|
+
result = rule.apply(match)
|
|
28
|
+
|
|
29
|
+
if rule.is_a?(MessageRule)
|
|
30
|
+
result.negate! if negate
|
|
31
|
+
|
|
32
|
+
return result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
cur = result.expression
|
|
36
|
+
mapping = result.mapping
|
|
37
|
+
negate = !negate if rule.negate?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def find_rule(expression, mapping)
|
|
46
|
+
@rules.each do |rule|
|
|
47
|
+
rule.patterns.each do |pattern|
|
|
48
|
+
match = pattern.match(expression, mapping)
|
|
49
|
+
|
|
50
|
+
return [rule, match] if match
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class TransformBuilder
|
|
5
|
+
include Singleton
|
|
6
|
+
|
|
7
|
+
def call(match, receiver, method, *args, **kwargs)
|
|
8
|
+
expression = Call.new(
|
|
9
|
+
receiver.expression,
|
|
10
|
+
method,
|
|
11
|
+
args.map(&:expression),
|
|
12
|
+
kwargs.transform_values(&:expression),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
mapping = TransformMapping.new
|
|
16
|
+
mapping.path = match.mapping.path
|
|
17
|
+
mapping.receiver = receiver.mapping
|
|
18
|
+
mapping.args = args.map(&:mapping)
|
|
19
|
+
mapping.kwargs = kwargs.transform_values(&:mapping)
|
|
20
|
+
|
|
21
|
+
PatternCapture.new(expression, mapping)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class TransformRule
|
|
5
|
+
def initialize(patterns, negate, block)
|
|
6
|
+
@patterns = patterns
|
|
7
|
+
@negate = negate
|
|
8
|
+
@block = block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :patterns
|
|
12
|
+
|
|
13
|
+
def negate?
|
|
14
|
+
@negate
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def apply(match)
|
|
18
|
+
TransformBuilder.instance.instance_exec(match, &@block)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class State
|
|
5
|
+
def initialize(values, boolean: false)
|
|
6
|
+
@values = values
|
|
7
|
+
@boolean = boolean
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :values
|
|
11
|
+
|
|
12
|
+
def boolean?
|
|
13
|
+
@boolean
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def actual
|
|
17
|
+
@values[:actual]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def errors
|
|
21
|
+
@errors ||= new_collector
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def new_collector
|
|
25
|
+
@boolean ? BooleanCollector.new : ErrorCollector.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def result
|
|
29
|
+
@errors&.error || EmptyError.instance
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def report(actual = self.actual)
|
|
33
|
+
StandardMessageBuilder.new(false, actual)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def expected(actual = self.actual)
|
|
37
|
+
StandardMessageBuilder.new(true, actual)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matcher
|
|
4
|
+
class ErrorBuilder
|
|
5
|
+
def self.build(use_or: false, &)
|
|
6
|
+
errors = build_errors(&)
|
|
7
|
+
|
|
8
|
+
if use_or
|
|
9
|
+
OrError.from(errors)
|
|
10
|
+
else
|
|
11
|
+
AndError.from(errors)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.build_errors(&)
|
|
16
|
+
builder = ErrorBuilder.new
|
|
17
|
+
builder.instance_exec(&) if block_given?
|
|
18
|
+
builder.errors
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
attr_reader :errors
|
|
22
|
+
|
|
23
|
+
def initialize
|
|
24
|
+
@errors = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def _or(*path, &)
|
|
28
|
+
errors = ErrorBuilder.build_errors(&)
|
|
29
|
+
error = OrError.from(errors)
|
|
30
|
+
|
|
31
|
+
@errors << nest(path, error)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def _and(*path, &)
|
|
35
|
+
errors = ErrorBuilder.build_errors(&)
|
|
36
|
+
error = AndError.from(errors)
|
|
37
|
+
|
|
38
|
+
@errors << nest(path, error)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def error(path_or_message, message = nil)
|
|
42
|
+
if message.nil?
|
|
43
|
+
@errors << ElementError.new(path_or_message)
|
|
44
|
+
else
|
|
45
|
+
path = Array(path_or_message)
|
|
46
|
+
element = ElementError.new(message)
|
|
47
|
+
|
|
48
|
+
@errors << nest(path, element)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def msg(actual)
|
|
53
|
+
StandardMessageBuilder.new(false, actual)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def nest(path, error)
|
|
59
|
+
path.reverse_each.reduce(error) { NestedError.from(_2, _1) }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|