From 62578187a998a7d8d2826813356f1aad8d9334ed Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Wed, 17 Jun 2020 16:49:24 +1000 Subject: [PATCH] refactor: converted Groovy matcher classes to Kotlin --- consumer/groovy/build.gradle | 5 + .../pact/consumer/groovy/AndMatcher.groovy | 8 - .../pact/consumer/groovy/DateMatcher.groovy | 34 - .../consumer/groovy/DateTimeMatcher.groovy | 37 -- .../consumer/groovy/EachLikeMatcher.groovy | 14 - .../pact/consumer/groovy/EqualsMatcher.groovy | 12 - .../consumer/groovy/HexadecimalMatcher.groovy | 25 - .../consumer/groovy/IncludeMatcher.groovy | 12 - .../groovy/InvalidMatcherException.groovy | 11 - .../pact/consumer/groovy/LikeMatcher.groovy | 8 - .../dius/pact/consumer/groovy/Matcher.groovy | 15 - .../dius/pact/consumer/groovy/Matchers.groovy | 371 ----------- .../consumer/groovy/MaxLikeMatcher.groovy | 17 - .../consumer/groovy/MinLikeMatcher.groovy | 17 - .../consumer/groovy/MinMaxLikeMatcher.groovy | 15 - .../pact/consumer/groovy/NullMatcher.groovy | 12 - .../pact/consumer/groovy/OrMatcher.groovy | 8 - .../pact/consumer/groovy/PactBuilder.groovy | 10 +- .../pact/consumer/groovy/RegexpMatcher.groovy | 28 - .../pact/consumer/groovy/TimeMatcher.groovy | 33 - .../consumer/groovy/TimestampMatcher.groovy | 35 - .../pact/consumer/groovy/TypeMatcher.groovy | 26 - .../pact/consumer/groovy/UrlMatcher.groovy | 28 - .../pact/consumer/groovy/UuidMatcher.groovy | 25 - .../com/dius/pact/consumer/groovy/Matchers.kt | 613 ++++++++++++++++++ .../groovy/PactBodyBuilderSpec.groovy | 4 +- .../consumer/groovy/UrlMatcherSpec.groovy | 2 +- 27 files changed, 627 insertions(+), 798 deletions(-) delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/AndMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/DateMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/DateTimeMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/EachLikeMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/EqualsMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/HexadecimalMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/IncludeMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/InvalidMatcherException.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/LikeMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/Matcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/Matchers.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MaxLikeMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MinLikeMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MinMaxLikeMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/NullMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/OrMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/RegexpMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TimeMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TimestampMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TypeMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/UrlMatcher.groovy delete mode 100644 consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/UuidMatcher.groovy create mode 100644 consumer/groovy/src/main/kotlin/au/com/dius/pact/consumer/groovy/Matchers.kt diff --git a/consumer/groovy/build.gradle b/consumer/groovy/build.gradle index 73fdce4668..74cf85f18d 100644 --- a/consumer/groovy/build.gradle +++ b/consumer/groovy/build.gradle @@ -14,3 +14,8 @@ dependencies { groovyDoc "org.codehaus.groovy:groovy-all:${project.groovy2Version}" } + +compileGroovy { + dependsOn compileKotlin + classpath = classpath.plus(files(compileKotlin.destinationDir)) +} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/AndMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/AndMatcher.groovy deleted file mode 100644 index 21c6a3843b..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/AndMatcher.groovy +++ /dev/null @@ -1,8 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -/** - * Matches if all provided matches match - */ -class AndMatcher extends Matcher { - def matchers -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/DateMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/DateMatcher.groovy deleted file mode 100644 index 159ea0fe44..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/DateMatcher.groovy +++ /dev/null @@ -1,34 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.generators.DateGenerator -import au.com.dius.pact.core.model.generators.Generator -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import org.apache.commons.lang3.time.DateFormatUtils - -/** - * Matcher for dates - * - */ -@SuppressWarnings('UnnecessaryGetter') -class DateMatcher extends Matcher { - - String pattern - String expression = null - - String getPattern() { - pattern ?: DateFormatUtils.ISO_DATE_FORMAT.pattern - } - - MatchingRule getMatcher() { - new au.com.dius.pact.core.model.matchingrules.DateMatcher(getPattern()) - } - - Generator getGenerator() { - super.@value == null ? new DateGenerator(getPattern(), expression) : null - } - - def getValue() { - super.@value ?: DateFormatUtils.format(new Date(Matchers.DATE_2000), getPattern()) - } - -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/DateTimeMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/DateTimeMatcher.groovy deleted file mode 100644 index a42a98e48d..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/DateTimeMatcher.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.generators.DateTimeGenerator -import au.com.dius.pact.core.model.generators.Generator -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import org.apache.commons.lang3.time.DateFormatUtils - -import java.time.ZoneId -import java.time.format.DateTimeFormatter - -/** - * Matcher for datetimes - */ -@SuppressWarnings('UnnecessaryGetter') -class DateTimeMatcher extends Matcher { - - String pattern - String expression = null - - String getPattern() { - pattern ?: DateFormatUtils.ISO_DATETIME_FORMAT.pattern - } - - MatchingRule getMatcher() { - new au.com.dius.pact.core.model.matchingrules.TimestampMatcher(getPattern()) - } - - def getValue() { - super.@value ?: DateTimeFormatter.ofPattern(getPattern()).withZone(ZoneId.systemDefault()).format( - new Date(Matchers.DATE_2000).toInstant()) - } - - Generator getGenerator() { - super.@value == null ? new DateTimeGenerator(getPattern(), expression) : null - } - -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/EachLikeMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/EachLikeMatcher.groovy deleted file mode 100644 index eb105fcfef..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/EachLikeMatcher.groovy +++ /dev/null @@ -1,14 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.matchingrules.MatchingRule - -/** - * Each like matcher for arrays - */ -class EachLikeMatcher extends LikeMatcher { - - MatchingRule getMatcher() { - au.com.dius.pact.core.model.matchingrules.TypeMatcher.INSTANCE - } - -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/EqualsMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/EqualsMatcher.groovy deleted file mode 100644 index 7d9f0de8d9..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/EqualsMatcher.groovy +++ /dev/null @@ -1,12 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.matchingrules.MatchingRule - -/** - * Matcher to match using equality - */ -class EqualsMatcher extends Matcher { - MatchingRule getMatcher() { - au.com.dius.pact.core.model.matchingrules.EqualsMatcher.INSTANCE - } -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/HexadecimalMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/HexadecimalMatcher.groovy deleted file mode 100644 index d36ca4df7b..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/HexadecimalMatcher.groovy +++ /dev/null @@ -1,25 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.generators.Generator -import au.com.dius.pact.core.model.generators.RandomHexadecimalGenerator -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import au.com.dius.pact.core.model.matchingrules.RegexMatcher - -/** - * Matcher for hexadecimal values - */ -class HexadecimalMatcher extends Matcher { - - MatchingRule getMatcher() { - new RegexMatcher(Matchers.HEXADECIMAL) - } - - Generator getGenerator() { - super.@value == null ? new RandomHexadecimalGenerator(10) : null - } - - def getValue() { - super.@value ?: '1234a' - } - -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/IncludeMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/IncludeMatcher.groovy deleted file mode 100644 index 3bdfd7dbdc..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/IncludeMatcher.groovy +++ /dev/null @@ -1,12 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.matchingrules.MatchingRule - -/** - * Matcher for string inclusion - */ -class IncludeMatcher extends Matcher { - MatchingRule getMatcher() { - new au.com.dius.pact.core.model.matchingrules.IncludeMatcher(value) - } -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/InvalidMatcherException.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/InvalidMatcherException.groovy deleted file mode 100644 index 809fab116e..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/InvalidMatcherException.groovy +++ /dev/null @@ -1,11 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -/** - * Exception for handling invalid matchers - */ -class InvalidMatcherException extends RuntimeException { - - InvalidMatcherException(String message) { - super(message) - } -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/LikeMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/LikeMatcher.groovy deleted file mode 100644 index fe14488271..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/LikeMatcher.groovy +++ /dev/null @@ -1,8 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -/** - * Base class for like matchers - */ -class LikeMatcher extends Matcher { - Integer numberExamples = 1 -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/Matcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/Matcher.groovy deleted file mode 100644 index af0362c208..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/Matcher.groovy +++ /dev/null @@ -1,15 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.generators.Generator -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import groovy.transform.Canonical - -/** - * Base class for matchers - */ -@Canonical -class Matcher { - def value - MatchingRule matcher = null - Generator generator = null -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/Matchers.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/Matchers.groovy deleted file mode 100644 index f92d78a86d..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/Matchers.groovy +++ /dev/null @@ -1,371 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.generators.RandomBooleanGenerator -import au.com.dius.pact.core.model.generators.RandomDecimalGenerator -import au.com.dius.pact.core.model.generators.RandomIntGenerator -import au.com.dius.pact.core.model.generators.RandomStringGenerator -import groovy.util.logging.Slf4j -import org.apache.commons.lang3.time.DateUtils - -import java.text.ParseException -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeParseException -import java.util.regex.Pattern - -/** - * Base class for DSL matcher methods - */ -@SuppressWarnings(['DuplicateNumberLiteral', 'ConfusingMethodName', 'MethodCount']) -@Slf4j -class Matchers { - - static final String HEXADECIMAL = '[0-9a-fA-F]+' - static final String IP_ADDRESS = '(\\d{1,3}\\.)+\\d{1,3}' - static final String UUID_REGEX = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' - static final int TEN = 10 - static final String TYPE = 'type' - static final String DECIMAL = 'decimal' - static final long DATE_2000 = 949323600000L - static final String INTEGER = 'integer' - - /** - * Match a regular expression - * @param re Regular expression pattern - * @param value Example value, if not provided a random one will be generated - */ - static regexp(Pattern re, String value = null) { - regexp(re.toString(), value) - } - - /** - * Match a regular expression - * @param re Regular expression pattern - * @param value Example value, if not provided a random one will be generated - */ - static regexp(String regexp, String value = null) { - if (value && !value.matches(regexp)) { - throw new InvalidMatcherException("Example \"$value\" does not match regular expression \"$regexp\"") - } - new RegexpMatcher(regex: regexp, value: value) - } - - /** - * Match a hexadecimal value - * @param value Example value, if not provided a random one will be generated - */ - static hexValue(String value = null) { - if (value && !value.matches(HEXADECIMAL)) { - throw new InvalidMatcherException("Example \"$value\" is not a hexadecimal value") - } - new HexadecimalMatcher(value: value) - } - - /** - * Match a numeric identifier (integer) - * @param value Example value, if not provided a random one will be generated - */ - static identifier(def value = null) { - new TypeMatcher(value: value ?: 12345678, type: INTEGER, - generator: value == null ? new RandomIntGenerator(0, Integer.MAX_VALUE) : null) - } - - /** - * Match an IP Address - * @param value Example value, if not provided 127.0.0.1 will be generated - */ - static ipAddress(String value = null) { - if (value && !value.matches(IP_ADDRESS)) { - throw new InvalidMatcherException("Example \"$value\" is not an ip adress") - } - new RegexpMatcher(value: '127.0.0.1', regex: IP_ADDRESS) - } - - /** - * Match a numeric value - * @param value Example value, if not provided a random one will be generated - */ - static numeric(Number value = null) { - new TypeMatcher(type: 'number', value: value ?: 100, - generator: value == null ? new RandomDecimalGenerator(6) : null) - } - - /** - * Match a decimal value - * @param value Example value, if not provided a random one will be generated - */ - static decimal(Number value = null) { - new TypeMatcher(type: DECIMAL, value: value ?: 100.0, - generator: value == null ? new RandomDecimalGenerator(6) : null) - } - - /** - * Match a integer value - * @param value Example value, if not provided a random one will be generated - */ - static integer(Long value = null) { - new TypeMatcher(type: INTEGER, value: value ?: 100, - generator: value == null ? new RandomIntGenerator(0, Integer.MAX_VALUE) : null) - } - - /** - * Match a timestamp - * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. - * @param value Example value, if not provided the current date and time will be used - * @deprecated use datetime instead - */ - @Deprecated - static timestamp(String pattern = null, def value = null) { - validateTimeValue(value, pattern) - new DateTimeMatcher(value: value, pattern: pattern) - } - - /** - * Match a timestamp generated from an expression - * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. - * @param expression Expression to use to generate the timestamp - * @deprecated use datetime instead - */ - @Deprecated - static timestampExpression(String expression, String pattern = null) { - new DateTimeMatcher(pattern: pattern, expression: expression) - } - - /** - * Match a datetime - * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. - * @param value Example value, if not provided the current date and time will be used - */ - static datetime(String pattern = null, def value = null) { - validateDateTimeValue(value, pattern) - new DateTimeMatcher(value: value, pattern: pattern) - } - - /** - * Match a datetime generated from an expression - * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. - * @param expression Expression to use to generate the datetime - */ - static datetimeExpression(String expression, String pattern = null) { - new DateTimeMatcher(pattern: pattern, expression: expression) - } - - private static validateTimeValue(String value, String pattern) { - if (value && pattern) { - try { - DateUtils.parseDateStrictly(value, pattern) - } catch (ParseException e) { - throw new InvalidMatcherException("Example \"$value\" does not match pattern \"$pattern\"") - } - } - } - - private static validateDateTimeValue(String value, String pattern) { - if (value && pattern) { - try { - ZonedDateTime.parse(value, DateTimeFormatter.ofPattern(pattern).withZone(ZoneId.systemDefault())) - } catch (DateTimeParseException e) { - log.error("Example \"$value\" does not match pattern \"$pattern\"", e) - throw new InvalidMatcherException("Example \"$value\" does not match pattern \"$pattern\"") - } - } - } - - /** - * Match a time - * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. - * @param value Example value, if not provided the current time will be used - */ - static time(String pattern = null, def value = null) { - validateTimeValue(value, pattern) - new TimeMatcher(value: value, pattern: pattern) - } - - /** - * Match a time generated from an expression - * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. - * @param expression Expression to use to generate the time - */ - - static timeExpression(String expression, String pattern = null) { - new TimeMatcher(pattern: pattern, expression: expression) - } - - /** - * Match a date - * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. - * @param value Example value, if not provided the current date will be used - */ - - static date(String pattern = null, def value = null) { - validateTimeValue(value, pattern) - new DateMatcher(value: value, pattern: pattern) - } - - /** - * Match a date generated from an expression - * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. - * @param expression Expression to use to generate the date - */ - - static dateExpression(String expression, String pattern = null) { - new DateMatcher(pattern: pattern, expression: expression) - } - - /** - * Match a universally unique identifier (UUID) - * @param value optional value to use for examples - */ - static uuid(String value = null) { - if (value && !value.matches(UUID_REGEX)) { - throw new InvalidMatcherException("Example \"$value\" is not a UUID") - } - new UuidMatcher(value: value) - } - - /** - * Match any string value - * @param value Example value, if not provided a random one will be generated - */ - static string(String value = null) { - if (value != null) { - new TypeMatcher(value: value) - } else { - new TypeMatcher(value: 'string', generator: new RandomStringGenerator(10)) - } - } - - /** - * Match any boolean - * @param value Example value, if not provided a random one will be generated - */ - static bool(Boolean value = null) { - if (value != null) { - new TypeMatcher(value: value) - } else { - new TypeMatcher(value: true, generator: RandomBooleanGenerator.INSTANCE) - } - } - - /** - * Array where each element like the following object - * @param numberExamples Optional number of examples to generate. Defaults to 1. - */ - static eachLike(Integer numberExamples = 1, def arg) { - new EachLikeMatcher(value: arg, numberExamples: numberExamples) - } - - /** - * Array with maximum size and each element like the following object - * @param max The maximum size of the array - * @param numberExamples Optional number of examples to generate. Defaults to 1. - */ - static maxLike(Integer max, Integer numberExamples = 1, def arg) { - if (numberExamples > max) { - throw new InvalidMatcherException("The number of examples you have specified ($numberExamples) is " + - "greater than the maximum ($max)") - } - new MaxLikeMatcher(max: max, value: arg, numberExamples: numberExamples) - } - - /** - * Array with minimum size and each element like the following object - * @param min The minimum size of the array - * @param numberExamples Optional number of examples to generate. Defaults to 1. - */ - static minLike(Integer min, Integer numberExamples = 1, def arg) { - if (numberExamples > 1 && numberExamples < min) { - throw new InvalidMatcherException("The number of examples you have specified ($numberExamples) is " + - "less than the minimum ($min)") - } - new MinLikeMatcher(min: min, value: arg, numberExamples: numberExamples) - } - - /** - * Array with minimum and maximum size and each element like the following object - * @param min The minimum size of the array - * @param max The maximum size of the array - * @param numberExamples Optional number of examples to generate. Defaults to 1. - */ - static minMaxLike(Integer min, Integer max, Integer numberExamples = 1, def arg) { - if (min > max) { - throw new InvalidMatcherException("The minimum you have specified ($min) is " + - "greater than the maximum ($max)") - } else if (numberExamples > 1 && numberExamples < min) { - throw new InvalidMatcherException("The number of examples you have specified ($numberExamples) is " + - "less than the minimum ($min)") - } else if (numberExamples > 1 && numberExamples > max) { - throw new InvalidMatcherException("The number of examples you have specified ($numberExamples) is " + - "greater than the maximum ($max)") - } - new MinMaxLikeMatcher(min: min, max: max, value: arg, numberExamples: numberExamples) - } - - /** - * Match Equality - * @param value Value to match to - */ - static equalTo(def value) { - new EqualsMatcher(value: value) - } - - /** - * Matches if the string is included in the value - * @param value String value that must be present - */ - static includesStr(def value) { - new IncludeMatcher(value: value?.toString()) - } - - /** - * Matches if any of the provided matches match - * @param example Example value to use - */ - static or(def example, Object... values) { - new OrMatcher(value: example, matchers: values.collect { - if (it instanceof Matcher) { - it - } else { - new EqualsMatcher(value: it) - } - }) - } - - /** - * Matches if all of the provided matches match - * @param example Example value to use - */ - static and(def example, Object... values) { - new AndMatcher(value: example, matchers: values.collect { - if (it instanceof Matcher) { - it - } else { - new EqualsMatcher(value: it) - } - }) - } - - /** - * Matches a null value - */ - static nullValue() { - new NullMatcher() - } - - /** - * Matches a URL composed of a base path and a list of path fragments - */ - static url(String basePath, Object... pathFragments) { - new UrlMatcher(basePath, pathFragments as List) - } - - /** - * Array of arrays where each element like the following object - * @param numberExamples Optional number of examples to generate. Defaults to 1. - */ - static eachArrayLike(Integer numberExamples = 1, def arg) { - new EachLikeMatcher(value: new EachLikeMatcher(value: arg, numberExamples: numberExamples), - numberExamples: numberExamples) - } -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MaxLikeMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MaxLikeMatcher.groovy deleted file mode 100644 index 9004376e85..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MaxLikeMatcher.groovy +++ /dev/null @@ -1,17 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher - -/** - * Like matcher with a maximum size - */ -class MaxLikeMatcher extends LikeMatcher { - - Integer max - - MatchingRule getMatcher() { - new MaxTypeMatcher(max) - } - -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MinLikeMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MinLikeMatcher.groovy deleted file mode 100644 index 24874dd231..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MinLikeMatcher.groovy +++ /dev/null @@ -1,17 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher - -/** - * Like matcher with a minimum size - */ -class MinLikeMatcher extends LikeMatcher { - - Integer min = 0 - - MatchingRule getMatcher() { - new MinTypeMatcher(min) - } - -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MinMaxLikeMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MinMaxLikeMatcher.groovy deleted file mode 100644 index 8c5fde4760..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/MinMaxLikeMatcher.groovy +++ /dev/null @@ -1,15 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher - -/** - * Like Matcher with a minimum and maximum size - */ -class MinMaxLikeMatcher extends LikeMatcher { - Integer min, max - - MatchingRule getMatcher() { - new MinMaxTypeMatcher(min, max) - } -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/NullMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/NullMatcher.groovy deleted file mode 100644 index 8d09dfc19e..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/NullMatcher.groovy +++ /dev/null @@ -1,12 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.matchingrules.MatchingRule - -/** - * Matcher to match null values - */ -class NullMatcher extends Matcher { - MatchingRule getMatcher() { - au.com.dius.pact.core.model.matchingrules.NullMatcher.INSTANCE - } -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/OrMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/OrMatcher.groovy deleted file mode 100644 index 189e5d259b..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/OrMatcher.groovy +++ /dev/null @@ -1,8 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -/** - * Matcher that matches if any provided matcher matches - */ -class OrMatcher extends Matcher { - def matchers -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/PactBuilder.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/PactBuilder.groovy index 47396ced87..6dfdaad46b 100644 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/PactBuilder.groovy +++ b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/PactBuilder.groovy @@ -159,7 +159,7 @@ class PactBuilder extends BaseBuilder { matchers.addCategory(header).addRule(key, value.matcher) [key, [value.value]] } else if (value instanceof Pattern) { - def matcher = new RegexpMatcher(regex: value) + def matcher = new RegexpMatcher(value.toString()) matchers.addCategory(header).addRule(key, matcher.matcher) [key, [matcher.value]] } else if (value instanceof GeneratedValue) { @@ -178,7 +178,7 @@ class PactBuilder extends BaseBuilder { matchers.addCategory(category).addRule(path.matcher) path.value } else if (path instanceof Pattern) { - def matcher = new RegexpMatcher(regex: path) + def matcher = new RegexpMatcher(path.toString()) matchers.addCategory(category).addRule(matcher.matcher) matcher.value } else if (path instanceof GeneratedValue) { @@ -197,7 +197,7 @@ class PactBuilder extends BaseBuilder { matchers.addCategory(category).addRule(key, value[0].matcher) [key, [value[0].value]] } else if (value[0] instanceof Pattern) { - def matcher = new RegexpMatcher(regex: value[0].toString()) + def matcher = new RegexpMatcher(value[0].toString()) matchers.addCategory(category).addRule(key, matcher.matcher) [key, [matcher.value]] } else if (value[0] instanceof GeneratedValue) { @@ -240,6 +240,7 @@ class PactBuilder extends BaseBuilder { request.matchers.addCategory(body.matchers) request.generators.addGenerators(body.generators) } else if (body != null && !(body instanceof String)) { + if (requestData.prettyPrint == null && !compactMimeTypes(requestData) || requestData.prettyPrint) { request.body = new JsonBuilder(body).toPrettyString() } else { @@ -393,7 +394,8 @@ class PactBuilder extends BaseBuilder { } /** - * Sets up a file upload request. This will add the correct content type header to the request + * Sets up a file upload request using a multipart FORM POST. This will add the correct content type header to + * the request * @param partName This is the name of the part in the multipart body. * @param fileName This is the name of the file that was uploaded * @param fileContentType This is the content type of the uploaded file diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/RegexpMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/RegexpMatcher.groovy deleted file mode 100644 index 24d79b4f75..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/RegexpMatcher.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.generators.Generator -import au.com.dius.pact.core.model.generators.RegexGenerator -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import au.com.dius.pact.core.model.matchingrules.RegexMatcher -import com.mifmif.common.regex.Generex - -/** - * Regular Expression Matcher - */ -class RegexpMatcher extends Matcher { - - String regex - - MatchingRule getMatcher() { - new RegexMatcher(regex, super.@value) - } - - Generator getGenerator() { - value == null ? new RegexGenerator(regex) : null - } - - def getValue() { - super.@value ?: new Generex(regex).random() - } - -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TimeMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TimeMatcher.groovy deleted file mode 100644 index be3ca43305..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TimeMatcher.groovy +++ /dev/null @@ -1,33 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.generators.Generator -import au.com.dius.pact.core.model.generators.TimeGenerator -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import org.apache.commons.lang3.time.DateFormatUtils - -/** - * Matcher for time values - */ -@SuppressWarnings('UnnecessaryGetter') -class TimeMatcher extends Matcher { - - String pattern - String expression = null - - String getPattern() { - pattern ?: DateFormatUtils.ISO_TIME_FORMAT.pattern - } - - MatchingRule getMatcher() { - new au.com.dius.pact.core.model.matchingrules.TimeMatcher(getPattern()) - } - - Generator getGenerator() { - super.@value == null ? new TimeGenerator(getPattern(), expression) : null - } - - def getValue() { - super.@value ?: DateFormatUtils.format(new Date(Matchers.DATE_2000), getPattern()) - } - -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TimestampMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TimestampMatcher.groovy deleted file mode 100644 index b0a9190489..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TimestampMatcher.groovy +++ /dev/null @@ -1,35 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.generators.DateTimeGenerator -import au.com.dius.pact.core.model.generators.Generator -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import org.apache.commons.lang3.time.DateFormatUtils - -/** - * Matcher for timestamps - * @deprecated use DateTimeMatcher instead - */ -@SuppressWarnings('UnnecessaryGetter') -@Deprecated -class TimestampMatcher extends Matcher { - - String pattern - String expression = null - - String getPattern() { - pattern ?: DateFormatUtils.ISO_DATETIME_FORMAT.pattern - } - - MatchingRule getMatcher() { - new au.com.dius.pact.core.model.matchingrules.TimestampMatcher(getPattern()) - } - - def getValue() { - super.@value ?: DateFormatUtils.format(new Date(Matchers.DATE_2000), getPattern()) - } - - Generator getGenerator() { - super.@value == null ? new DateTimeGenerator(getPattern(), expression) : null - } - -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TypeMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TypeMatcher.groovy deleted file mode 100644 index f446453a46..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/TypeMatcher.groovy +++ /dev/null @@ -1,26 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher - -/** - * Matcher for validating same types - */ -class TypeMatcher extends Matcher { - - String type = 'type' - - MatchingRule getMatcher() { - switch (type) { - case 'integer': - return new NumberTypeMatcher(NumberTypeMatcher.NumberType.INTEGER) - case 'decimal': - return new NumberTypeMatcher(NumberTypeMatcher.NumberType.DECIMAL) - case 'number': - return new NumberTypeMatcher(NumberTypeMatcher.NumberType.NUMBER) - default: - return au.com.dius.pact.core.model.matchingrules.TypeMatcher.INSTANCE - } - } - -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/UrlMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/UrlMatcher.groovy deleted file mode 100644 index 2c1cbaf9aa..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/UrlMatcher.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.matchers.UrlMatcherSupport -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import au.com.dius.pact.core.model.matchingrules.RegexMatcher - -/** - * Match a URL by specifying the base and a series of paths. - */ -class UrlMatcher extends Matcher { - - private final String basePath - private final List pathFragments - private final UrlMatcherSupport urlMatcherSupport - - UrlMatcher(String basePath, List pathFragments) { - this.pathFragments = pathFragments - this.basePath = basePath - this.urlMatcherSupport = new UrlMatcherSupport(basePath, pathFragments.collect { - it instanceof RegexpMatcher ? it.matcher : it - }) - this.value = urlMatcherSupport.exampleValue - } - - MatchingRule getMatcher() { - new RegexMatcher(urlMatcherSupport.regexExpression) - } -} diff --git a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/UuidMatcher.groovy b/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/UuidMatcher.groovy deleted file mode 100644 index 562adfcc5c..0000000000 --- a/consumer/groovy/src/main/groovy/au/com/dius/pact/consumer/groovy/UuidMatcher.groovy +++ /dev/null @@ -1,25 +0,0 @@ -package au.com.dius.pact.consumer.groovy - -import au.com.dius.pact.core.model.generators.Generator -import au.com.dius.pact.core.model.generators.UuidGenerator -import au.com.dius.pact.core.model.matchingrules.MatchingRule -import au.com.dius.pact.core.model.matchingrules.RegexMatcher - -/** - * Matcher for universally unique IDs - */ -class UuidMatcher extends Matcher { - - MatchingRule getMatcher() { - new RegexMatcher(Matchers.UUID_REGEX) - } - - Generator getGenerator() { - super.@value == null ? UuidGenerator.INSTANCE : null - } - - def getValue() { - super.@value ?: 'e2490de5-5bd3-43d5-b7c4-526e33f71304' - } - -} diff --git a/consumer/groovy/src/main/kotlin/au/com/dius/pact/consumer/groovy/Matchers.kt b/consumer/groovy/src/main/kotlin/au/com/dius/pact/consumer/groovy/Matchers.kt new file mode 100644 index 0000000000..d1eb604c12 --- /dev/null +++ b/consumer/groovy/src/main/kotlin/au/com/dius/pact/consumer/groovy/Matchers.kt @@ -0,0 +1,613 @@ +package au.com.dius.pact.consumer.groovy + +import au.com.dius.pact.core.matchers.UrlMatcherSupport +import au.com.dius.pact.core.model.generators.DateGenerator +import au.com.dius.pact.core.model.generators.DateTimeGenerator +import au.com.dius.pact.core.model.generators.Generator +import au.com.dius.pact.core.model.generators.RandomBooleanGenerator +import au.com.dius.pact.core.model.generators.RandomDecimalGenerator +import au.com.dius.pact.core.model.generators.RandomHexadecimalGenerator +import au.com.dius.pact.core.model.generators.RandomIntGenerator +import au.com.dius.pact.core.model.generators.RandomStringGenerator +import au.com.dius.pact.core.model.generators.RegexGenerator +import au.com.dius.pact.core.model.generators.TimeGenerator +import au.com.dius.pact.core.model.generators.UuidGenerator +import au.com.dius.pact.core.model.matchingrules.MatchingRule +import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher +import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher +import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher +import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher +import au.com.dius.pact.core.model.matchingrules.RegexMatcher +import au.com.dius.pact.core.support.isNotEmpty +import com.mifmif.common.regex.Generex +import mu.KLogging +import org.apache.commons.lang3.time.DateFormatUtils +import org.apache.commons.lang3.time.DateUtils +import java.text.ParseException +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeParseException +import java.util.Date +import java.util.regex.Pattern + +const val HEXADECIMAL = "[0-9a-fA-F]+" +const val IP_ADDRESS = "(\\d{1,3}\\.)+\\d{1,3}" +const val UUID_REGEX = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" +const val TEN = 10 +const val TYPE = "type" +const val DECIMAL = "decimal" +const val DATE_2000 = 949323600000L +const val INTEGER = "integer" + +/** + * Exception for handling invalid matchers + */ +class InvalidMatcherException(message: String) : RuntimeException(message) + +/** + * Base class for matchers + */ +open class Matcher @JvmOverloads constructor( + open val value: Any? = null, + open val matcher: MatchingRule? = null, + open val generator: Generator? = null +) + +/** + * Regular Expression Matcher + */ +class RegexpMatcher @JvmOverloads constructor( + val regex: String, + value: String? = null +) : Matcher(value, RegexMatcher(regex, value), if (value == null) RegexGenerator(regex) else null) { + override val value: Any? + get() = super.value ?: Generex(regex).random() +} + +class HexadecimalMatcher @JvmOverloads constructor( + value: String? = null +) : Matcher( + value ?: "1234a", + RegexMatcher(HEXADECIMAL, value), + if (value == null) RandomHexadecimalGenerator(10) else null +) + +/** + * Matcher for validating same types + */ +class TypeMatcher @JvmOverloads constructor( + value: Any? = null, + val type: String = "type", + generator: Generator? = null +) : Matcher( + value, + when (type) { + "integer" -> NumberTypeMatcher(NumberTypeMatcher.NumberType.INTEGER) + "decimal" -> NumberTypeMatcher(NumberTypeMatcher.NumberType.DECIMAL) + "number" -> NumberTypeMatcher(NumberTypeMatcher.NumberType.NUMBER) + else -> au.com.dius.pact.core.model.matchingrules.TypeMatcher + }, + generator +) + +class DateTimeMatcher @JvmOverloads constructor( + val pattern: String = DateFormatUtils.ISO_DATETIME_FORMAT.pattern, + value: String? = null, + expression: String? = null +) : Matcher( + value, + au.com.dius.pact.core.model.matchingrules.TimestampMatcher(pattern), + if (value == null) DateTimeGenerator(pattern, expression) else null +) { + override val value: Any? + get() = super.value ?: DateTimeFormatter.ofPattern(pattern).withZone(ZoneId.systemDefault()).format( + Date(DATE_2000).toInstant()) +} + +class TimeMatcher @JvmOverloads constructor( + val pattern: String = DateFormatUtils.ISO_TIME_FORMAT.pattern, + value: String? = null, + expression: String? = null +) : Matcher( + value, + au.com.dius.pact.core.model.matchingrules.TimeMatcher(pattern), + if (value == null) TimeGenerator(pattern, expression) else null +) { + override val value: Any? + get() = super.value ?: DateFormatUtils.format(Date(DATE_2000), pattern) +} + +class DateMatcher @JvmOverloads constructor( + val pattern: String = DateFormatUtils.ISO_DATE_FORMAT.pattern, + value: String? = null, + expression: String? = null +) : Matcher( + value, + au.com.dius.pact.core.model.matchingrules.DateMatcher(pattern), + if (value == null) DateGenerator(pattern, expression) else null +) { + override val value: Any? + get() = super.value ?: DateFormatUtils.format(Date(DATE_2000), pattern) +} + +/** + * Matcher for universally unique IDs + */ +class UuidMatcher @JvmOverloads constructor( + value: Any? = null +) : Matcher( + value ?: "e2490de5-5bd3-43d5-b7c4-526e33f71304", + RegexMatcher(UUID_REGEX), + if (value == null) UuidGenerator else null +) + +/** + * Base class for like matchers + */ +open class LikeMatcher @JvmOverloads constructor( + value: Any? = null, + val numberExamples: Int = 1, + matcher: MatchingRule? = null +) : Matcher(value, matcher ?: au.com.dius.pact.core.model.matchingrules.TypeMatcher) + +/** + * Each like matcher for arrays + */ +class EachLikeMatcher @JvmOverloads constructor( + value: Any? = null, + numberExamples: Int = 1 +) : LikeMatcher(value, numberExamples) + +/** + * Like matcher with a maximum size + */ +class MaxLikeMatcher @JvmOverloads constructor( + val max: Int, + value: Any? = null, + numberExamples: Int = 1 +) : LikeMatcher(value, numberExamples, MaxTypeMatcher(max)) + +/** + * Like matcher with a minimum size + */ +class MinLikeMatcher @JvmOverloads constructor( + val min: Int = 1, + value: Any? = null, + numberExamples: Int = 1 +) : LikeMatcher(value, numberExamples, MinTypeMatcher(min)) + +/** + * Like Matcher with a minimum and maximum size + */ +class MinMaxLikeMatcher @JvmOverloads constructor( + val min: Int, + val max: Int, + value: Any? = null, + numberExamples: Int = 1 +) : LikeMatcher(value, numberExamples, MinMaxTypeMatcher(min, max)) + +/** + * Matcher to match using equality + */ +class EqualsMatcher(value: Any) : Matcher(value, au.com.dius.pact.core.model.matchingrules.EqualsMatcher) + +/** + * Matcher for string inclusion + */ +class IncludeMatcher(value: String) : Matcher(value, au.com.dius.pact.core.model.matchingrules.IncludeMatcher(value)) + +/** + * Matcher that matches if any provided matcher matches + */ +class OrMatcher(example: Any, val matchers: List) : Matcher(example) + +/** + * Matches if all provided matches match + */ +class AndMatcher(example: Any, val matchers: List) : Matcher(example) + +/** + * Matcher to match null values + */ +class NullMatcher : Matcher(null, au.com.dius.pact.core.model.matchingrules.NullMatcher) + +/** + * Match a URL by specifying the base and a series of paths. + */ +class UrlMatcher @JvmOverloads constructor( + val basePath: String, + val pathFragments: List, + private val urlMatcherSupport: UrlMatcherSupport = UrlMatcherSupport(basePath, pathFragments.map { + if (it is RegexpMatcher) it.matcher!! else it + }) +) : Matcher(urlMatcherSupport.getExampleValue(), RegexMatcher(urlMatcherSupport.getRegexExpression())) + +/** + * Base class for DSL matcher methods + */ +open class Matchers { + + companion object : KLogging() { + + /** + * Match a regular expression + * @param re Regular expression pattern + * @param value Example value, if not provided a random one will be generated + */ + @JvmStatic + @JvmOverloads + fun regexp(re: Pattern, value: String? = null) = regexp(re.toString(), value) + + /** + * Match a regular expression + * @param re Regular expression pattern + * @param value Example value, if not provided a random one will be generated + */ + @JvmStatic + @JvmOverloads + fun regexp(regexp: String, value: String? = null): Matcher { + if (value != null && !value.matches(Regex(regexp))) { + throw InvalidMatcherException("Example \"$value\" does not match regular expression \"$regexp\"") + } + return RegexpMatcher(regexp, value) + } + + /** + * Match a hexadecimal value + * @param value Example value, if not provided a random one will be generated + */ + @JvmStatic + @JvmOverloads + fun hexValue(value: String? = null): Matcher { + if (value != null && !value.matches(Regex(HEXADECIMAL))) { + throw InvalidMatcherException("Example \"$value\" is not a hexadecimal value") + } + return HexadecimalMatcher(value) + } + + /** + * Match a numeric identifier (integer) + * @param value Example value, if not provided a random one will be generated + */ + @JvmStatic + @JvmOverloads + fun identifier(value: Any? = null): Matcher { + return TypeMatcher(value ?: 12345678, INTEGER, + if (value == null) RandomIntGenerator(0, Integer.MAX_VALUE) else null) + } + + /** + * Match an IP Address + * @param value Example value, if not provided 127.0.0.1 will be generated + */ + @JvmStatic + @JvmOverloads + fun ipAddress(value: String? = null): Matcher { + if (value != null && !value.matches(Regex(IP_ADDRESS))) { + throw InvalidMatcherException("Example \"$value\" is not an ip address") + } + return RegexpMatcher(IP_ADDRESS, value ?: "127.0.0.1") + } + + /** + * Match a numeric value + * @param value Example value, if not provided a random one will be generated + */ + @JvmStatic + @JvmOverloads + fun numeric(value: Number? = null): Matcher { + return TypeMatcher(value ?: 100, "number", if (value == null) RandomDecimalGenerator(6) else null) + } + + /** + * Match a decimal value + * @param value Example value, if not provided a random one will be generated + */ + @JvmStatic + @JvmOverloads + fun decimal(value: Number? = null): Matcher { + return TypeMatcher(value ?: 100.0, DECIMAL, if (value == null) RandomDecimalGenerator(6) else null) + } + + /** + * Match a integer value + * @param value Example value, if not provided a random one will be generated + */ + @JvmStatic + @JvmOverloads + fun integer(value: Long? = null): Matcher { + return TypeMatcher(value ?: 100, INTEGER, + if (value == null) RandomIntGenerator(0, Integer.MAX_VALUE) else null) + } + + /** + * Match a timestamp + * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. + * @param value Example value, if not provided the current date and time will be used + */ + @Deprecated("use datetime instead") + @JvmStatic + @JvmOverloads + fun timestamp(pattern: String = DateFormatUtils.ISO_DATETIME_FORMAT.pattern, value: String? = null): Matcher { + return datetime(pattern, value) + } + + /** + * Match a timestamp generated from an expression + * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. + * @param expression Expression to use to generate the timestamp + */ + @Deprecated("use datetime instead") + @JvmStatic + @JvmOverloads + fun timestampExpression(expression: String, pattern: String = DateFormatUtils.ISO_DATETIME_FORMAT.pattern): Matcher { + return datetimeExpression(expression, pattern) + } + + /** + * Match a datetime + * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. + * @param value Example value, if not provided the current date and time will be used + */ + @JvmStatic + @JvmOverloads + fun datetime(pattern: String = DateFormatUtils.ISO_DATETIME_FORMAT.pattern, value: String? = null): Matcher { + validateDateTimeValue(value, pattern) + return DateTimeMatcher(pattern, value) + } + + /** + * Match a datetime generated from an expression + * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. + * @param expression Expression to use to generate the datetime + */ + fun datetimeExpression(expression: String, pattern: String = DateFormatUtils.ISO_DATETIME_FORMAT.pattern): Matcher { + return DateTimeMatcher(pattern, null, expression) + } + + private fun validateTimeValue(value: String?, pattern: String?) { + if (value.isNotEmpty() && pattern.isNotEmpty()) { + try { + DateUtils.parseDateStrictly(value, pattern) + } catch (e: ParseException) { + throw InvalidMatcherException("Example \"$value\" does not match pattern \"$pattern\"") + } + } + } + + private fun validateDateTimeValue(value: String?, pattern: String?) { + if (value.isNotEmpty() && pattern.isNotEmpty()) { + try { + ZonedDateTime.parse(value, DateTimeFormatter.ofPattern(pattern).withZone(ZoneId.systemDefault())) + } catch (e: DateTimeParseException) { + logger.error(e) { "Example \"$value\" does not match pattern \"$pattern\"" } + throw InvalidMatcherException("Example \"$value\" does not match pattern \"$pattern\"") + } + } + } + + /** + * Match a time + * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. + * @param value Example value, if not provided the current time will be used + */ + @JvmStatic + @JvmOverloads + fun time(pattern: String = DateFormatUtils.ISO_TIME_FORMAT.pattern, value: String? = null): Matcher { + validateTimeValue(value, pattern) + return TimeMatcher(pattern, value) + } + + /** + * Match a time generated from an expression + * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. + * @param expression Expression to use to generate the time + */ + @JvmStatic + @JvmOverloads + fun timeExpression(expression: String, pattern: String = DateFormatUtils.ISO_TIME_FORMAT.pattern): Matcher { + return TimeMatcher(pattern, null, expression) + } + + /** + * Match a date + * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. + * @param value Example value, if not provided the current date will be used + */ + @JvmStatic + @JvmOverloads + fun date(pattern: String = DateFormatUtils.ISO_DATE_FORMAT.pattern, value: String? = null): Matcher { + validateTimeValue(value, pattern) + return DateMatcher(pattern, value) + } + + /** + * Match a date generated from an expression + * @param pattern Pattern to use to match. If not provided, an ISO pattern will be used. + * @param expression Expression to use to generate the date + */ + @JvmStatic + @JvmOverloads + fun dateExpression(expression: String, pattern: String = DateFormatUtils.ISO_DATE_FORMAT.pattern): Matcher { + return DateMatcher(pattern, null, expression) + } + + /** + * Match a universally unique identifier (UUID) + * @param value optional value to use for examples + */ + @JvmStatic + @JvmOverloads + fun uuid(value: String? = null): Matcher { + if (value != null && !value.matches(Regex(UUID_REGEX))) { + throw InvalidMatcherException("Example \"$value\" is not a UUID") + } + return UuidMatcher(value) + } + + /** + * Match any string value + * @param value Example value, if not provided a random one will be generated + */ + @JvmStatic + @JvmOverloads + fun string(value: String? = null): Matcher { + return if (value != null) { + TypeMatcher(value) + } else { + TypeMatcher("string", generator = RandomStringGenerator(10)) + } + } + + /** + * Match any boolean + * @param value Example value, if not provided a random one will be generated + */ + @JvmStatic + @JvmOverloads + fun bool(value: Boolean? = null): Matcher { + return if (value != null) { + TypeMatcher(value) + } else { + TypeMatcher(true, generator = RandomBooleanGenerator) + } + } + + /** + * Array where each element like the following object + * @param numberExamples Optional number of examples to generate. Defaults to 1. + */ + @JvmStatic + @JvmOverloads + fun eachLike(numberExamples: Int = 1, arg: Any): Matcher { + return EachLikeMatcher(arg, numberExamples) + } + + /** + * Array with maximum size and each element like the following object + * @param max The maximum size of the array + * @param numberExamples Optional number of examples to generate. Defaults to 1. + */ + @JvmStatic + @JvmOverloads + fun maxLike(max: Int, numberExamples: Int = 1, arg: Any): Matcher { + if (numberExamples > max) { + throw InvalidMatcherException("The number of examples you have specified ($numberExamples) is " + + "greater than the maximum ($max)") + } + return MaxLikeMatcher(max, arg, numberExamples) + } + + /** + * Array with minimum size and each element like the following object + * @param min The minimum size of the array + * @param numberExamples Optional number of examples to generate. Defaults to 1. + */ + @JvmStatic + @JvmOverloads + fun minLike(min: Int, numberExamples: Int = 1, arg: Any): Matcher { + if (numberExamples > 1 && numberExamples < min) { + throw InvalidMatcherException("The number of examples you have specified ($numberExamples) is " + + "less than the minimum ($min)") + } + return MinLikeMatcher(min, arg, numberExamples) + } + + /** + * Array with minimum and maximum size and each element like the following object + * @param min The minimum size of the array + * @param max The maximum size of the array + * @param numberExamples Optional number of examples to generate. Defaults to 1. + */ + @JvmStatic + @JvmOverloads + fun minMaxLike(min: Int, max: Int, numberExamples: Int = 1, arg: Any): Matcher { + if (min > max) { + throw InvalidMatcherException("The minimum you have specified ($min) is " + + "greater than the maximum ($max)") + } else if (numberExamples > 1 && numberExamples < min) { + throw InvalidMatcherException("The number of examples you have specified ($numberExamples) is " + + "less than the minimum ($min)") + } else if (numberExamples > 1 && numberExamples > max) { + throw InvalidMatcherException("The number of examples you have specified ($numberExamples) is " + + "greater than the maximum ($max)") + } + return MinMaxLikeMatcher(min, max, arg, numberExamples) + } + + /** + * Match Equality + * @param value Value to match to + */ + @JvmStatic + fun equalTo(value: Any): Matcher { + return EqualsMatcher(value) + } + + /** + * Matches if the string is included in the value + * @param value String value that must be present + */ + @JvmStatic + fun includesStr(value: String): Matcher { + return IncludeMatcher(value) + } + + /** + * Matches if any of the provided matches match + * @param example Example value to use + */ + @JvmStatic + fun or(example: Any, vararg values: Any): Matcher { + return OrMatcher(example, values.map { + if (it is Matcher) { + it + } else { + EqualsMatcher(it) + } + }) + } + + /** + * Matches if all of the provided matches match + * @param example Example value to use + */ + @JvmStatic + fun and(example: Any, vararg values: Any): Matcher { + return AndMatcher(example, values.map { + if (it is Matcher) { + it + } else { + EqualsMatcher(it) + } + }) + } + + /** + * Matches a null value + */ + @JvmStatic + fun nullValue(): Matcher { + return NullMatcher() + } + + /** + * Matches a URL composed of a base path and a list of path fragments + */ + @JvmStatic + fun url(basePath: String, vararg pathFragments: Any): Matcher { + return UrlMatcher(basePath, pathFragments.toList()) + } + + /** + * Array of arrays where each element like the following object + * @param numberExamples Optional number of examples to generate. Defaults to 1. + */ + @JvmStatic + @JvmOverloads + fun eachArrayLike(numberExamples: Int = 1, arg: Any): Matcher { + return EachLikeMatcher(EachLikeMatcher(arg, numberExamples), numberExamples) + } + } +} diff --git a/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/PactBodyBuilderSpec.groovy b/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/PactBodyBuilderSpec.groovy index d81acbc0f7..2fb1224c14 100644 --- a/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/PactBodyBuilderSpec.groovy +++ b/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/PactBodyBuilderSpec.groovy @@ -102,12 +102,12 @@ class PactBodyBuilderSpec extends Specification { bodyMatchingRules['$.surname'].rules == [new RegexMatcher('\\w+', 'larry')] bodyMatchingRules['$.position'].rules == [new RegexMatcher('staff|contractor', 'staff')] bodyMatchingRules['$.hexCode'].rules == [new RegexMatcher('[0-9a-fA-F]+')] - bodyMatchingRules['$.hexCode2'].rules == [new RegexMatcher('[0-9a-fA-F]+')] + bodyMatchingRules['$.hexCode2'].rules == [new RegexMatcher('[0-9a-fA-F]+', '01234AB')] bodyMatchingRules['$.id'].rules == [new NumberTypeMatcher(INTEGER)] bodyMatchingRules['$.id2'].rules == [new NumberTypeMatcher(INTEGER)] bodyMatchingRules['$.salary'].rules == [new NumberTypeMatcher(NumberTypeMatcher.NumberType.DECIMAL)] bodyMatchingRules['$.localAddress'].rules == [new RegexMatcher('(\\d{1,3}\\.)+\\d{1,3}', '127.0.0.1')] - bodyMatchingRules['$.localAddress2'].rules == [new RegexMatcher('(\\d{1,3}\\.)+\\d{1,3}', '127.0.0.1')] + bodyMatchingRules['$.localAddress2'].rules == [new RegexMatcher('(\\d{1,3}\\.)+\\d{1,3}', '192.169.0.2')] bodyMatchingRules['$.age2'].rules == [new NumberTypeMatcher(INTEGER)] bodyMatchingRules['$.ts'].rules == [new TimestampMatcher('yyyy-MM-dd\'T\'HH:mm:ss')] bodyMatchingRules['$.timestamp'].rules == [new TimestampMatcher('yyyy/MM/dd - HH:mm:ss.S')] diff --git a/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/UrlMatcherSpec.groovy b/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/UrlMatcherSpec.groovy index ce81bb303a..a456fd527e 100644 --- a/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/UrlMatcherSpec.groovy +++ b/consumer/groovy/src/test/groovy/au/com/dius/pact/consumer/groovy/UrlMatcherSpec.groovy @@ -8,7 +8,7 @@ class UrlMatcherSpec extends Specification { def 'converts groovy regex matcher class to matching rule regex class'() { when: def matcher = new UrlMatcher('http://localhost:8080', - ['a', new RegexpMatcher(value: '123', regex: '\\d+'), 'b']) + ['a', new RegexpMatcher('\\d+', '123'), 'b']) then: matcher.matcher.toMap(PactSpecVersion.V3) == [match: 'regex', regex: '.*\\Qa\\E\\/\\d+\\/\\Qb\\E$' ]