Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for specification v3 combined matchers #86

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions lib/pact/combined_match.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Pact

# Specifies that the actual object should be considered a match if
# it matches any of the matchers depending on combinator operation.

class CombinedMatch

attr_reader :combiner, :matchers

def initialize combiner, matchers
@combiner = combiner
@matchers = matchers
end
end
end
27 changes: 27 additions & 0 deletions lib/pact/matchers/matchers.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'pact/configuration'
require 'pact/term'
require 'pact/something_like'
require 'pact/combined_match'
require 'pact/array_like'
require 'pact/shared/null_expectation'
require 'pact/shared/key_not_found'
Expand Down Expand Up @@ -54,6 +55,7 @@ def calculate_diff expected, actual, opts = {}
when Pact::SomethingLike then calculate_diff(expected.contents, actual, options.merge(:type => true))
when Pact::ArrayLike then array_like_diff(expected, actual, options)
when Pact::Term then term_diff(expected, actual, options)
when Pact::CombinedMatch then combined_match_diff(expected, actual, options)
else object_diff(expected, actual, options)
end
end
Expand Down Expand Up @@ -156,6 +158,31 @@ def calculate_diff_at_key key, expected_value, actual, difference, options
diff_at_key
end

def combined_match_diff expected, actual, options
if expected.matchers.empty?
return NO_DIFF
end
case expected.combiner
when 'AND' then
expected.matchers.each do |e|
diff = calculate_diff(e, actual, options)
if diff.any?
return diff
end
end
return NO_DIFF
when 'OR' then
diff = nil
expected.matchers.each do |e|
diff = calculate_diff(e, actual, options)
if diff.empty?
return NO_DIFF
end
end
return diff
end
end

def check_for_unexpected_keys expected, actual, options
if options[:allow_unexpected_keys]
NO_DIFF
Expand Down
63 changes: 52 additions & 11 deletions lib/pact/matching_rules/v3/merge.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'pact/array_like'
require 'pact/combined_match'
require 'pact/matching_rules/jsonpath'

module Pact
Expand Down Expand Up @@ -81,14 +82,36 @@ def warn_when_not_one_example_item array, path
end

def wrap object, path
rules = @matching_rules[path] && @matching_rules[path]['matchers'] && @matching_rules[path]['matchers'].first
array_rules = @matching_rules["#{path}[*]*"] && @matching_rules["#{path}[*]*"]['matchers'] && @matching_rules["#{path}[*]*"]['matchers'].first
return object unless rules || array_rules

if rules['match'] == 'type' && !rules.has_key?('min')
handle_match_type(object, path, rules)
elsif rules['regex']
handle_regex(object, path, rules)
rules = @matching_rules[path]
return object unless rules

combiner = rules['combine'] || 'AND'
if ['AND', 'OR'].include?(combiner) then
rules.delete('combine')
else
# unsupported combine will be reported
return object
end

matchers = rules['matchers']
# TODO make it work with array rules
# array_rules = @matching_rules["#{path}[*]*"] && @matching_rules["#{path}[*]*"]['matchers'] && @matching_rules["#{path}[*]*"]['matchers'].first

wrapped_matchers = matchers.map do |rule|
wrap_single(rule, object, path)
end
Pact::CombinedMatch.new(combiner, wrapped_matchers)
end

def wrap_single rule, object, path
if rule['match'] == 'type' && !rule.has_key?('min')
handle_match_type(object, path, rule)
elsif rule['match'] == 'null'
handle_null_type(object, path, rule)
elsif rule['match'] == 'timestamp' && rule.has_key?('timestamp')
handle_timestamp_type(object, path, rule)
elsif rule['regex']
handle_regex(object, path, rule)
else
object
end
Expand All @@ -99,6 +122,26 @@ def handle_match_type object, path, rules
Pact::SomethingLike.new(object)
end

def handle_null_type object, path, rule
rule.delete('match')
Pact::SomethingLike.new(nil)
end

def handle_timestamp_type object, path, rule
# barebone timestamp support
supported_formats = {
"yyyy-MM-dd" => /[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
}
regexp = supported_formats[rule['timestamp']]
if regexp then
rule.delete('match')
rule.delete('timestamp')
Pact::Term.new(generate: object, matcher: Regexp.new(regexp))
else
object
end
end

def handle_regex object, path, rules
rules.delete('match')
regex = rules.delete('regex')
Expand All @@ -109,9 +152,7 @@ def log_ignored_rules
@matching_rules.each do | jsonpath, rules_hash |
rules_array = rules_hash["matchers"]
if rules_array
((rules_array.length - 1)..0).each do | index |
rules_array.delete_at(index) if rules_array[index].empty?
end
rules_hash["matchers"] = rules_array.select { | item | item.any? }
end
end

Expand Down