From bfad9a6200f7e1a7d44282a48d8891672b56f32a Mon Sep 17 00:00:00 2001 From: Michal Stefanak Date: Wed, 4 Aug 2021 10:53:27 +0200 Subject: [PATCH] added error codes constants. added fault tolerant marker. --- README.md | 2 +- src/Deepr.php | 63 +++++++++++++++++++++++++++++++++------------ tests/DeeprTest.php | 29 +++++++++++++++++---- 3 files changed, 72 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c148ad9..0fdd77f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ PHP API library following Deepr specification. -![](https://img.shields.io/badge/phpunit-passed-success) ![](https://img.shields.io/badge/coverage-98%25-green) ![](https://img.shields.io/github/stars/stefanak-michal/deepr-php) ![](https://img.shields.io/packagist/dt/stefanak-michal/deepr-php) ![](https://img.shields.io/github/v/release/stefanak-michal/deepr-php) ![](https://img.shields.io/github/commits-since/stefanak-michal/deepr-php/latest) +![](https://img.shields.io/badge/phpunit-passed-success) ![](https://img.shields.io/badge/coverage-96%25-green) ![](https://img.shields.io/github/stars/stefanak-michal/deepr-php) ![](https://img.shields.io/packagist/dt/stefanak-michal/deepr-php) ![](https://img.shields.io/github/v/release/stefanak-michal/deepr-php) ![](https://img.shields.io/github/commits-since/stefanak-michal/deepr-php/latest) ## Usage diff --git a/src/Deepr.php b/src/Deepr.php index ae2bc07..be89fea 100644 --- a/src/Deepr.php +++ b/src/Deepr.php @@ -55,6 +55,27 @@ final class Deepr */ const OPTION_AUTHORIZER = 6; + /** + * Authorizer + */ + const ERROR_AUTHORIZER = 1; + /** + * Set options + */ + const ERROR_OPTIONS = 2; + /** + * Get class instance + */ + const ERROR_INSTANCE = 3; + /** + * Wrong structure of classes + */ + const ERROR_STRUCTURE = 4; + /** + * Missing property or method + */ + const ERROR_MISSING = 5; + /** * Enable to see query traversal * @var bool @@ -119,7 +140,7 @@ public function setOptions(array $options = []): Deepr if (empty($this->options[$constant->getValue()])) $this->options[$constant->getValue()] = ''; else - throw new Exception($constant->getName() . ' accept value of type string', 2); + throw new Exception($constant->getName() . ' accept value of type string', self::ERROR_OPTIONS); } break; case 'mixed': @@ -129,7 +150,7 @@ public function setOptions(array $options = []): Deepr if (empty($this->options[$constant->getValue()])) $this->options[$constant->getValue()] = []; else - throw new Exception($constant->getName() . ' accept value of type array', 2); + throw new Exception($constant->getName() . ' accept value of type array', self::ERROR_OPTIONS); } break; case 'callable': @@ -137,7 +158,7 @@ public function setOptions(array $options = []): Deepr if (empty($this->options[$constant->getValue()])) $this->options[$constant->getValue()] = null; else - throw new Exception($constant->getName() . ' accept value of type callable or null', 2); + throw new Exception($constant->getName() . ' accept value of type callable or null', self::ERROR_OPTIONS); } break; } @@ -157,11 +178,11 @@ public function setOptions(array $options = []): Deepr private function getInstance(array $args): IComponent { if (!array_key_exists($this->options[self::OPTION_SV_KEY], $args)) - throw new Exception('Source values type key not found in arguments', 3); + throw new Exception('Source values type key not found in arguments', self::ERROR_INSTANCE); $cls = $this->options[self::OPTION_SV_NS] . $args[$this->options[self::OPTION_SV_KEY]]; if (!class_exists($cls)) - throw new Exception('Requested class "' . $cls . '" does not exists', 3); + throw new Exception('Requested class "' . $cls . '" does not exists', self::ERROR_INSTANCE); $reflection = new ReflectionClass($cls); $invokeArgs = []; @@ -174,7 +195,7 @@ private function getInstance(array $args): IComponent $instance = new $cls(...$invokeArgs); if (!($instance instanceof IComponent)) - throw new Exception($cls . ' has to implement IComponent', 3); + throw new Exception($cls . ' has to implement IComponent', self::ERROR_STRUCTURE); return $instance; } @@ -205,7 +226,7 @@ private function recursion(IComponent &$root, string $action, array $values) } elseif ($k === '[]' && !empty($action)) { $this->debug($action . ' []'); if (!($root instanceof ILoadable)) - throw new Exception('To access collection of class it has to implement ILoadable interface', 4); + throw new Exception('To access collection of class it has to implement ILoadable interface', self::ERROR_STRUCTURE); $tmpValues = $values; unset($tmpValues['[]']); @@ -221,6 +242,7 @@ private function recursion(IComponent &$root, string $action, array $values) $root->add($child, $name); } } + return; } elseif ($k === '()') { continue; } elseif (is_array($v) && array_key_exists('()', $v)) { @@ -235,30 +257,38 @@ private function recursion(IComponent &$root, string $action, array $values) if ($root === $data) { $root = new Collection(); } - $this->recursion($data, $key, $v); - if ($data instanceof Collection) { + if ($data instanceof Collection && count($data->getChildren())) { foreach ($data->getChildren() as $child) { $this->recursion($child, '', $v); } + } else { + $this->recursion($data, $key, $v); } $root->add($data, $k); - } elseif ($root instanceof Collection) { + } elseif ($root instanceof Collection && count($root->getChildren()) && method_exists($root->getChildren()[0], $key)) { foreach ($root->getChildren() as $child) { $this->recursion($child, $k, $v); } + } elseif (substr($k, -1) !== '?') { + throw new Exception('Missing method ' . $key, self::ERROR_MISSING); } } elseif ($v === true) { $this->debug($action . ' ' . $k . ' true'); - if (property_exists($root, $key) && $this->checkPropertyKey($key)) { - $this->authorize($key); - $root->add(new Value($root->$key), $k); + if (property_exists($root, $key)) { + if ($this->checkPropertyKey($key)) { + $this->authorize($key); + $root->add(new Value($root->$key), $k); + } + } elseif (substr($k, -1) !== '?') { + throw new Exception('Missing property ' . $key, self::ERROR_MISSING); } } elseif (property_exists($root, $key)) { $this->debug('property ' . $key); $collection = $root->$key; - if (is_string($collection) && class_exists($collection)) { + if (is_string($collection) && class_exists($collection)) $collection = new $collection(); - } + if (!($collection instanceof Collection)) + throw new Exception('Property has to be instance of collection class or class name', self::ERROR_STRUCTURE); $this->recursion($collection, $key, $v); $root->add($collection, $k); } elseif (is_array($v)) { @@ -298,7 +328,7 @@ private function checkPropertyKey(string $key): bool private function authorize(string $key, string $operation = 'get') { if (is_callable($this->options[self::OPTION_AUTHORIZER]) && $this->options[self::OPTION_AUTHORIZER]($key, $operation) === false) { - throw new Exception('Operation not allowed by authorizer', 1); + throw new Exception('Operation not allowed by authorizer', self::ERROR_AUTHORIZER); } } @@ -310,6 +340,7 @@ private function authorize(string $key, string $operation = 'get') */ private function getKey(string $key, bool $alias = true): string { + $key = rtrim($key, '?'); if (strpos($key, '=>') === false) return $key; diff --git a/tests/DeeprTest.php b/tests/DeeprTest.php index 1010b6d..091e596 100644 --- a/tests/DeeprTest.php +++ b/tests/DeeprTest.php @@ -48,6 +48,10 @@ public function testDeepr(): ?Deepr */ public function testInvokeQueries(string $input, string $output, Deepr $deepr) { + echo json_encode(json_decode($input, true), JSON_PRETTY_PRINT) + . PHP_EOL + . json_encode(json_decode($output, true), JSON_PRETTY_PRINT); + try { $root = new Root(); $input = json_decode($input, true); @@ -108,7 +112,7 @@ public function testException(Deepr $deepr) { $root = new Root(); $this->expectException(Exception::class); - $this->expectExceptionCode(4); + $this->expectExceptionCode($deepr::ERROR_STRUCTURE); $deepr->invokeQuery($root, json_decode('{ "[]": [] }', true)); } @@ -191,7 +195,7 @@ public function testOptionAuthorizerException(Deepr $deepr) { $root = new Root(); $this->expectException(Exception::class); - $this->expectExceptionCode(1); + $this->expectExceptionCode($deepr::ERROR_AUTHORIZER); $deepr->invokeQuery($root, json_decode('{"sayHello":{"()":["John"]}}', true), [ $deepr::OPTION_AUTHORIZER => function (string $key, string $operation) { return false; @@ -223,7 +227,7 @@ public function testSetOptions(Deepr $deepr) public function testSetOptionsStringException(Deepr $deepr) { $this->expectException(Exception::class); - $this->expectExceptionCode(2); + $this->expectExceptionCode($deepr::ERROR_OPTIONS); $deepr->setOptions([ $deepr::OPTION_SV_KEY => ['this has to be string and not a array'], ]); @@ -236,7 +240,7 @@ public function testSetOptionsStringException(Deepr $deepr) public function testSetOptionsArrayException(Deepr $deepr) { $this->expectException(Exception::class); - $this->expectExceptionCode(2); + $this->expectExceptionCode($deepr::ERROR_OPTIONS); $deepr->setOptions([ $deepr::OPTION_IGNORE_KEYS => 'has to be array or empty value', ]); @@ -249,9 +253,24 @@ public function testSetOptionsArrayException(Deepr $deepr) public function testSetOptionsCallableException(Deepr $deepr) { $this->expectException(Exception::class); - $this->expectExceptionCode(2); + $this->expectExceptionCode($deepr::ERROR_OPTIONS); $deepr->setOptions([ $deepr::OPTION_AUTHORIZER => 'this has to be callable or null', ]); } + + /** + * @depends testDeepr + * @param Deepr $deepr + */ + public function testFaultTolerant(Deepr $deepr) + { + try { + $root = new Root(); + $result = $deepr->invokeQuery($root, json_decode('{"abc?": true, "method?": {"()": []}}', true)); + $this->assertEquals([], $result); + } catch (Exception $e) { + $this->markTestIncomplete($e->getMessage()); + } + } }