From f68ca4bbbd1318841bd13c6ef226931f0b34ce59 Mon Sep 17 00:00:00 2001 From: Jarek Jakubowski Date: Wed, 16 Jan 2019 10:47:21 +0100 Subject: [PATCH 1/7] Array universal key --- src/Lexer.php | 2 +- src/Matcher/ArrayMatcher.php | 22 ++++++++++----- tests/LexerTest.php | 1 + tests/Matcher/ArrayMatcherTest.php | 45 ++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/Lexer.php b/src/Lexer.php index 5d8877a4..f635f4da 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -32,7 +32,7 @@ protected function getCatchablePatterns() : array '\\-?[0-9]*\\.?[0-9]*', // numbers "'(?:[^']|'')*'", // string between ' character '"(?:[^"]|"")*"', // string between " character, - '@[a-zA-Z0-9\\*]+@', // type pattern + '@[a-zA-Z0-9\\*.]+@', // type pattern ]; } diff --git a/src/Matcher/ArrayMatcher.php b/src/Matcher/ArrayMatcher.php index bbb9fbd4..37990ecd 100644 --- a/src/Matcher/ArrayMatcher.php +++ b/src/Matcher/ArrayMatcher.php @@ -15,6 +15,7 @@ final class ArrayMatcher extends Matcher { const PATTERN = 'array'; const UNBOUNDED_PATTERN = '@...@'; + const UNIVERSAL_KEY = '@*@'; private $propertyMatcher; @@ -70,18 +71,20 @@ private function iterateMatch(array $values, array $patterns, string $parentPath foreach ($values as $key => $value) { $path = $this->formatAccessPath($key); - if ($this->shouldSkippValueMatchingFor($pattern)) { + if ($this->shouldSkipValueMatchingFor($pattern)) { continue; } if ($this->valueExist($path, $patterns)) { $pattern = $this->getValueByPath($patterns, $path); + } elseif (isset($patterns[self::UNIVERSAL_KEY])) { + $pattern = $patterns[self::UNIVERSAL_KEY]; } else { $this->setMissingElementInError('pattern', $this->formatFullPath($parentPath, $path)); return false; } - if ($this->shouldSkippValueMatchingFor($pattern)) { + if ($this->shouldSkipValueMatchingFor($pattern)) { continue; } @@ -127,6 +130,7 @@ function ($item) use ($skipPattern) { $notExistingKeys = $this->findNotExistingKeys($pattern, $values); if (\count($notExistingKeys) > 0) { + dump($notExistingKeys); $keyNames = \array_keys($notExistingKeys); $path = $this->formatFullPath($parentPath, $this->formatAccessPath($keyNames[0])); $this->setMissingElementInError('value', $path); @@ -138,9 +142,13 @@ function ($item) use ($skipPattern) { return true; } - private function findNotExistingKeys(array $pattern, array $values) : array + private function findNotExistingKeys(array $patterns, array $values) : array { - $notExistingKeys = \array_diff_key($pattern, $values); + if (isset($patterns[self::UNIVERSAL_KEY])) { + return []; + } + + $notExistingKeys = \array_diff_key($patterns, $values); return \array_filter($notExistingKeys, function ($pattern) use ($values) { if (\is_array($pattern)) { @@ -194,8 +202,7 @@ private function valueExist(string $path, array $haystack) : bool private function arrayPropertyExists(string $property, array $objectOrArray) : bool { - return ($objectOrArray instanceof \ArrayAccess && isset($objectOrArray[$property])) || - (\is_array($objectOrArray) && \array_key_exists($property, $objectOrArray)); + return ($objectOrArray instanceof \ArrayAccess || \is_array($objectOrArray)) && isset($objectOrArray[$property]); } private function getValueByPath(array $array, string $path) @@ -217,6 +224,7 @@ private function getPropertyAccessor() : PropertyAccessorInterface private function setMissingElementInError(string $place, string $path) { + throw new \Exception(\sprintf('There is no element under path %s in %s.', $path, $place)); $this->error = \sprintf('There is no element under path %s in %s.', $path, $place); } @@ -230,7 +238,7 @@ private function formatFullPath(string $parentPath, string $path) : string return \sprintf('%s%s', $parentPath, $path); } - private function shouldSkippValueMatchingFor($lastPattern) : bool + private function shouldSkipValueMatchingFor($lastPattern) : bool { return $lastPattern === self::UNBOUNDED_PATTERN; } diff --git a/tests/LexerTest.php b/tests/LexerTest.php index 3fca10d5..db72076c 100644 --- a/tests/LexerTest.php +++ b/tests/LexerTest.php @@ -181,6 +181,7 @@ public static function validMatcherTypePatterns() ['@integer@'], ['@number@'], ['@*@'], + ['@...@'], ['@wildcard@'] ]; } diff --git a/tests/Matcher/ArrayMatcherTest.php b/tests/Matcher/ArrayMatcherTest.php index 2cadbff4..ddeb9dc4 100644 --- a/tests/Matcher/ArrayMatcherTest.php +++ b/tests/Matcher/ArrayMatcherTest.php @@ -158,9 +158,39 @@ public static function positiveMatchData() 6.66 ]; + $simpleArrPatternWithUniversalKey = [ + 'users' => [ + [ + 'firstName' => '@string@', + Matcher\ArrayMatcher::UNIVERSAL_KEY => '@*@' + ], + Matcher\ArrayMatcher::UNBOUNDED_PATTERN + ], + true, + false, + 1, + 6.66 + ]; + + $simpleArrPatternWithUniversalKeyAndStringValue = [ + 'users' => [ + [ + 'firstName' => '@string@', + Matcher\ArrayMatcher::UNIVERSAL_KEY => '@string@' + ], + Matcher\ArrayMatcher::UNBOUNDED_PATTERN + ], + true, + false, + 1, + 6.66 + ]; + return [ [$simpleArr, $simpleArr], [$simpleArr, $simpleArrPattern], + [$simpleArr, $simpleArrPatternWithUniversalKey], + [$simpleArr, $simpleArrPatternWithUniversalKeyAndStringValue], [[], []], [['foo' => null], ['foo' => null]], [['foo' => null], ['foo' => '@null@']], @@ -224,8 +254,23 @@ public static function negativeMatchData() 6.66 ]; + $simpleArrPatternWithUniversalKeyAndIntegerValue = [ + 'users' => [ + [ + 'firstName' => '@string@', + Matcher\ArrayMatcher::UNIVERSAL_KEY => '@integer@' + ], + Matcher\ArrayMatcher::UNBOUNDED_PATTERN + ], + true, + false, + 1, + 6.66 + ]; + return [ [$simpleArr, $simpleDiff], + [$simpleArr, $simpleArrPatternWithUniversalKeyAndIntegerValue], [['status' => 'ok', 'data' => [['foo']]], ['status' => 'ok', 'data' => []]], [[1], []], [['key' => 'val'], ['key' => 'val2']], From 6a64e17d7577a9a756ab08baeef47685bef8f40b Mon Sep 17 00:00:00 2001 From: Jarek Jakubowski Date: Wed, 16 Jan 2019 10:53:41 +0100 Subject: [PATCH 2/7] Remove unwanted Exception --- src/Matcher/ArrayMatcher.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Matcher/ArrayMatcher.php b/src/Matcher/ArrayMatcher.php index 37990ecd..c07a4132 100644 --- a/src/Matcher/ArrayMatcher.php +++ b/src/Matcher/ArrayMatcher.php @@ -224,7 +224,6 @@ private function getPropertyAccessor() : PropertyAccessorInterface private function setMissingElementInError(string $place, string $path) { - throw new \Exception(\sprintf('There is no element under path %s in %s.', $path, $place)); $this->error = \sprintf('There is no element under path %s in %s.', $path, $place); } From 4825cf631c48321eab8c325ecce6c38ecf631c29 Mon Sep 17 00:00:00 2001 From: Jarek Jakubowski Date: Wed, 16 Jan 2019 12:14:20 +0100 Subject: [PATCH 3/7] add test and remove dump --- src/Matcher/ArrayMatcher.php | 1 - tests/Matcher/JsonMatcherTest.php | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Matcher/ArrayMatcher.php b/src/Matcher/ArrayMatcher.php index c07a4132..bee1340f 100644 --- a/src/Matcher/ArrayMatcher.php +++ b/src/Matcher/ArrayMatcher.php @@ -130,7 +130,6 @@ function ($item) use ($skipPattern) { $notExistingKeys = $this->findNotExistingKeys($pattern, $values); if (\count($notExistingKeys) > 0) { - dump($notExistingKeys); $keyNames = \array_keys($notExistingKeys); $path = $this->formatFullPath($parentPath, $this->formatAccessPath($keyNames[0])); $this->setMissingElementInError('value', $path); diff --git a/tests/Matcher/JsonMatcherTest.php b/tests/Matcher/JsonMatcherTest.php index 4a13cc16..45a60c49 100644 --- a/tests/Matcher/JsonMatcherTest.php +++ b/tests/Matcher/JsonMatcherTest.php @@ -200,6 +200,10 @@ public static function positiveMatches() '{"users":[{"firstName":"Norbert","lastName":"Orzechowicz","roles":["ROLE_USER", "ROLE_DEVELOPER"]}]}', '{"users":[{"firstName":"Norbert","lastName":"Orzechowicz","roles":"@wildcard@"}]}' ], + [ + '{"users":[{"firstName":"Norbert","lastName":"Orzechowicz","roles":["ROLE_USER", "ROLE_DEVELOPER"]}]}', + '{"users":[{"firstName":"Norbert","@*@":"@*@"}]}' + ], [ '[{"name": "Norbert"},{"name":"Michał"},{"name":"Bob"},{"name":"Martin"}]', '[{"name": "Norbert"},@...@]' From cc4747f91915477856921f6eee0b9dd832da96f7 Mon Sep 17 00:00:00 2001 From: Jarek Jakubowski Date: Wed, 16 Jan 2019 12:36:20 +0100 Subject: [PATCH 4/7] revert invalid array key check --- src/Matcher/ArrayMatcher.php | 3 ++- tests/Matcher/JsonMatcherTest.php | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Matcher/ArrayMatcher.php b/src/Matcher/ArrayMatcher.php index bee1340f..882906d2 100644 --- a/src/Matcher/ArrayMatcher.php +++ b/src/Matcher/ArrayMatcher.php @@ -201,7 +201,8 @@ private function valueExist(string $path, array $haystack) : bool private function arrayPropertyExists(string $property, array $objectOrArray) : bool { - return ($objectOrArray instanceof \ArrayAccess || \is_array($objectOrArray)) && isset($objectOrArray[$property]); + return ($objectOrArray instanceof \ArrayAccess && isset($objectOrArray[$property])) || + (\is_array($objectOrArray) && \array_key_exists($property, $objectOrArray)); } private function getValueByPath(array $array, string $path) diff --git a/tests/Matcher/JsonMatcherTest.php b/tests/Matcher/JsonMatcherTest.php index 45a60c49..40c9feae 100644 --- a/tests/Matcher/JsonMatcherTest.php +++ b/tests/Matcher/JsonMatcherTest.php @@ -188,6 +188,14 @@ public static function positiveMatches() '{"username":null,"some_data":"test"}', '{"username":null, "some_data": @string@}' ], + [ + '{"username":null,"some_data":"test"}', + '{"username":null, "some_data": "@string@"}' + ], + [ + '{"username":null,"some_data":"test"}', + '{"username":null,"some_data":@string@}' + ], [ '{"null":null}', '{"null":null}' From 5ed937f73c529165a6be4bbb0b27a5c063afe342 Mon Sep 17 00:00:00 2001 From: Jarek Jakubowski Date: Wed, 16 Jan 2019 12:39:16 +0100 Subject: [PATCH 5/7] remove not needed code samples --- tests/Matcher/JsonMatcherTest.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/Matcher/JsonMatcherTest.php b/tests/Matcher/JsonMatcherTest.php index 40c9feae..45a60c49 100644 --- a/tests/Matcher/JsonMatcherTest.php +++ b/tests/Matcher/JsonMatcherTest.php @@ -188,14 +188,6 @@ public static function positiveMatches() '{"username":null,"some_data":"test"}', '{"username":null, "some_data": @string@}' ], - [ - '{"username":null,"some_data":"test"}', - '{"username":null, "some_data": "@string@"}' - ], - [ - '{"username":null,"some_data":"test"}', - '{"username":null,"some_data":@string@}' - ], [ '{"null":null}', '{"null":null}' From 70d25b2219029c127ebf1e717b87c1670f089189 Mon Sep 17 00:00:00 2001 From: Jarek Jakubowski Date: Thu, 7 Feb 2019 22:26:22 +0100 Subject: [PATCH 6/7] Add more tests --- tests/Matcher/JsonMatcherTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Matcher/JsonMatcherTest.php b/tests/Matcher/JsonMatcherTest.php index 45a60c49..d9cd76bc 100644 --- a/tests/Matcher/JsonMatcherTest.php +++ b/tests/Matcher/JsonMatcherTest.php @@ -204,9 +204,17 @@ public static function positiveMatches() '{"users":[{"firstName":"Norbert","lastName":"Orzechowicz","roles":["ROLE_USER", "ROLE_DEVELOPER"]}]}', '{"users":[{"firstName":"Norbert","@*@":"@*@"}]}' ], + [ + '{"users":[{"firstName":"Norbert","lastName":"Orzechowicz","roles":["ROLE_USER", "ROLE_DEVELOPER"]},{}]}', + '{"users":[{"firstName":"Norbert","@*@":"@*@"},@...@]}' + ], [ '[{"name": "Norbert"},{"name":"Michał"},{"name":"Bob"},{"name":"Martin"}]', '[{"name": "Norbert"},@...@]' + ], + [ + '[{"name": "Norbert","lastName":"Orzechowicz"},{"name":"Michał"},{"name":"Bob"},{"name":"Martin"}]', + '[{"name": "Norbert","@*@":"@*@"},@...@]' ] ]; } From 6761bbffdb134dd50bf87d4b8359dd628ecd9bc2 Mon Sep 17 00:00:00 2001 From: Jarek Jakubowski Date: Thu, 7 Feb 2019 23:16:29 +0100 Subject: [PATCH 7/7] Update Readme with Universal Array Key example --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/README.md b/README.md index ec6a6bda..be0ae53e 100644 --- a/README.md +++ b/README.md @@ -334,6 +334,64 @@ $matcher->match( ); ``` +### Json matching with unbounded arrays and objects + +```php +createMatcher(); + +$matcher->match( + '{ + "users":[ + { + "firstName": "Norbert", + "lastName": "Orzechowicz", + "created": "2014-01-01", + "roles":["ROLE_USER", "ROLE_DEVELOPER"]}, + "attributes": { + "isAdmin": false, + "dateOfBirth" null, + "hasEmailVerified": true + } + }, + { + "firstName": "Michał", + "lastName": "Dąbrowski", + "created": "2014-01-01", + "roles":["ROLE_USER", "ROLE_DEVELOPER", "ROLE_ADMIN"]}, + "attributes": { + "isAdmin": true, + "dateOfBirth" null, + "hasEmailVerified": true + } + } + ] + }', + '{ + "users":[ + { + "firstName": @string@, + "lastName": @string@, + "created": "@string@.isDateTime()", + "roles": [ + "ROLE_USER", + @...@ + ], + "attributes": { + "isAdmin": @boolean@, + "@*@": "@*@" + } + } + ], + @...@ + }' +); +``` + ### Xml matching ```php