From 7b2cb77019783e90b1e684cf96e96d3b6150c5d7 Mon Sep 17 00:00:00 2001 From: Leakless Date: Sun, 15 Oct 2017 19:32:36 +0200 Subject: [PATCH] 2.3 - Add repeat expander feature (#109) * RepeatExpander : feature + tests * RepeatExpander: update README * RepeatExpander : Fix readme and new failure test --- README.md | 1 + src/Matcher/Pattern/Expander/Repeat.php | 147 ++++++++++++++++++ src/Parser/ExpanderInitializer.php | 1 + tests/Matcher/Pattern/Expander/RepeatTest.php | 79 ++++++++++ 4 files changed, 228 insertions(+) create mode 100644 src/Matcher/Pattern/Expander/Repeat.php create mode 100644 tests/Matcher/Pattern/Expander/RepeatTest.php diff --git a/README.md b/README.md index 9cbfe5ff..e0cd2f96 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ $matcher->getError(); // returns null or error message * ``oneOf(...$expanders)`` - example usage ``"@string@.oneOf(contains('foo'), contains('bar'), contains('baz'))"`` * ``matchRegex($regex)`` - example usage ``"@string@.matchRegex('/^lorem.+/')"`` * ``optional()`` - work's only with ``ArrayMatcher``, ``JsonMatcher`` and ``XmlMatcher`` +* ``repeat($pattern, $isStrict = true)`` - example usage ``'@array@.repeat({"name": "foe"})'`` or ``"@array@.repeat('@string@')"`` ##Example usage diff --git a/src/Matcher/Pattern/Expander/Repeat.php b/src/Matcher/Pattern/Expander/Repeat.php new file mode 100644 index 00000000..0005a1ea --- /dev/null +++ b/src/Matcher/Pattern/Expander/Repeat.php @@ -0,0 +1,147 @@ +pattern = $pattern; + $this->isStrict = $isStrict; + $this->isScalar = true; + + $json = json_decode($pattern, true); + + if ($json !== null && json_last_error() === JSON_ERROR_NONE) { + $this->pattern = $json; + $this->isScalar = false; + } + } + + /** + * @param $values + * @return bool + */ + public function match($values) + { + if (!is_array($values)) { + $this->error = sprintf("Repeat expander require \"array\", got \"%s\".", new StringConverter($values)); + return false; + } + + $factory = new SimpleFactory(); + $matcher = $factory->createMatcher(); + + if ($this->isScalar) { + return $this->matchScalar($values, $matcher); + } + + return $this->matchJson($values, $matcher); + } + + /** + * @return string|null + */ + public function getError() + { + return $this->error; + } + + /** + * @param array $values + * @param Matcher $matcher + * @return bool + */ + private function matchScalar(array $values, Matcher $matcher) + { + foreach ($values as $index => $value) { + $match = $matcher->match($value, $this->pattern); + + if (!$match) { + $this->error = sprintf("Repeat expander, entry n°%d, find error : %s", $index, $matcher->getError()); + return false; + } + } + + return true; + } + + /** + * @param array $values + * @param Matcher $matcher + * @return bool + */ + private function matchJson(array $values, Matcher $matcher) + { + $patternKeys = array_keys($this->pattern); + $patternKeysLength = count($patternKeys); + + foreach ($values as $index => $value) { + $valueKeys = array_keys($value); + $valueKeysLength = count($valueKeys); + + if ($this->isStrict && $patternKeysLength !== $valueKeysLength) { + $this->error = sprintf("Repeat expander expect to have %d keys in array but get : %d", $patternKeysLength, $valueKeysLength); + return false; + } + + foreach ($patternKeys as $key) { + if (!array_key_exists($key, $value)) { + $this->error = sprintf("Repeat expander, entry n°%d, require \"array\" to have key \"%s\".", $index, $key); + return false; + } + + $match = $matcher->match($value[$key], $this->pattern[$key]); + + if (!$match) { + $this->error = sprintf("Repeat expander, entry n°%d, key \"%s\", find error : %s", $index, $key, $matcher->getError()); + return false; + } + } + } + + return true; + } +} diff --git a/src/Parser/ExpanderInitializer.php b/src/Parser/ExpanderInitializer.php index cc9b3dcb..b55a3578 100644 --- a/src/Parser/ExpanderInitializer.php +++ b/src/Parser/ExpanderInitializer.php @@ -31,6 +31,7 @@ final class ExpanderInitializer Expander\OneOf::NAME => Expander\OneOf::class, Expander\Optional::NAME => Expander\Optional::class, Expander\StartsWith::NAME => Expander\StartsWith::class, + Expander\Repeat::NAME => Expander\Repeat::class, ]; /** diff --git a/tests/Matcher/Pattern/Expander/RepeatTest.php b/tests/Matcher/Pattern/Expander/RepeatTest.php new file mode 100644 index 00000000..1b5585ac --- /dev/null +++ b/tests/Matcher/Pattern/Expander/RepeatTest.php @@ -0,0 +1,79 @@ +assertEquals($expectedResult, $expander->match($haystack)); + } + + public static function examplesProvider() + { + $jsonPattern = '{"name": "@string@", "activated": "@boolean@"}'; + + $jsonTest = array( + array("name" => "toto", "activated" => true), + array("name" => "titi", "activated" => false), + array("name" => "tate", "activated" => true) + ); + + $scalarPattern = "@string@"; + $scalarTest = array( + "toto", + "titi", + "tata" + ); + + $strictTest = array( + array("name" => "toto", "activated" => true, "offset" => "offset") + ); + + return array( + array($jsonPattern, $jsonTest, true), + array($scalarPattern, $scalarTest, true), + array($jsonPattern, $strictTest, true, false) + ); + } + + /** + * @dataProvider invalidCasesProvider + */ + public function test_error_when_matching_fail($boundary, $value, $errorMessage) + { + $expander = new Repeat($boundary); + $this->assertFalse($expander->match($value)); + $this->assertEquals($errorMessage, $expander->getError()); + } + + public static function invalidCasesProvider() + { + $pattern = '{"name": "@string@", "activated": "@boolean@"}'; + + $valueTest = array( + array("name" => 1, "activated" => "yes") + ); + + $keyTest = array( + array("offset" => true, "foe" => "bar") + ); + + $strictTest = array( + array("name" => 1, "activated" => "yes", "offset" => true) + ); + + return array( + array($pattern, $valueTest, 'Repeat expander, entry n°0, key "name", find error : integer "1" is not a valid string.'), + array($pattern, $keyTest, 'Repeat expander, entry n°0, require "array" to have key "name".'), + array($pattern, $strictTest, 'Repeat expander expect to have 2 keys in array but get : 3'), + array($pattern, "", 'Repeat expander require "array", got "".') + ); + } +}