diff --git a/src/JsonPatch.php b/src/JsonPatch.php index c9f679c..3339db4 100644 --- a/src/JsonPatch.php +++ b/src/JsonPatch.php @@ -61,7 +61,7 @@ public static function import(array $data) } if (!is_object($operation)) { - throw new Exception( 'Invalid patch operation - should be a JSON object' ); + throw new Exception('Invalid patch operation - should be a JSON object'); } if (!isset($operation->op)) { @@ -145,32 +145,32 @@ public function apply(&$original, $stopOnError = true) $errors = array(); foreach ($this->operations as $operation) { try { - $pathItems = JsonPointer::splitPath($operation->path); + $pathItems = $this->splitPath($operation, 'path'); switch (true) { case $operation instanceof Add: - JsonPointer::add($original, $pathItems, $operation->value, $this->flags); + $this->add($operation, $original, $pathItems, $operation->value); break; case $operation instanceof Copy: - $fromItems = JsonPointer::splitPath($operation->from); - $value = JsonPointer::get($original, $fromItems); - JsonPointer::add($original, $pathItems, $value, $this->flags); + $fromItems = $this->splitPath($operation, 'from'); + $value = $this->get($operation, 'from', $original, $fromItems); + $this->add($operation, $original, $pathItems, $value); break; case $operation instanceof Move: - $fromItems = JsonPointer::splitPath($operation->from); - $value = JsonPointer::get($original, $fromItems); - JsonPointer::remove($original, $fromItems, $this->flags); - JsonPointer::add($original, $pathItems, $value, $this->flags); + $fromItems = $this->splitPath($operation, 'from'); + $value = $this->get($operation, 'from', $original, $fromItems); + $this->remove($operation, 'from', $original, $fromItems); + $this->add($operation, $original, $pathItems, $value); break; case $operation instanceof Remove: - JsonPointer::remove($original, $pathItems, $this->flags); + $this->remove($operation, 'path', $original, $pathItems); break; case $operation instanceof Replace: - JsonPointer::get($original, $pathItems); - JsonPointer::remove($original, $pathItems, $this->flags); - JsonPointer::add($original, $pathItems, $operation->value, $this->flags); + $this->get($operation, 'path', $original, $pathItems); + $this->remove($operation, 'path', $original, $pathItems); + $this->add($operation, $original, $pathItems, $operation->value); break; case $operation instanceof Test: - $value = JsonPointer::get($original, $pathItems); + $value = $this->get($operation, 'path', $original, $pathItems); $diff = new JsonDiff($operation->value, $value, JsonDiff::STOP_ON_DIFF); if ($diff->getDiffCnt() !== 0) { @@ -188,4 +188,41 @@ public function apply(&$original, $stopOnError = true) } return $errors; } + + private function splitPath(OpPath $operation, $field) + { + try { + return JsonPointer::splitPath($operation->$field); + } catch (Exception $exception) { + throw new PathException($exception->getMessage(), $operation, $field, $exception->getCode()); + } + } + + private function add(OpPath $operation, &$original, array $pathItems, $value) + { + try { + JsonPointer::add($original, $pathItems, $value, $this->flags); + } catch (Exception $exception) { + throw new PathException($exception->getMessage(), $operation, 'path', $exception->getCode()); + } + } + + private function get(OpPath $operation, $field, $original, array $pathItems) + { + try { + return JsonPointer::get($original, $pathItems); + } catch (Exception $exception) { + throw new PathException($exception->getMessage(), $operation, $field, $exception->getCode()); + } + } + + private function remove($operation, $field, &$original, array $pathItems) + { + try { + JsonPointer::remove($original, $pathItems, $this->flags); + } catch (Exception $exception) { + throw new PathException($exception->getMessage(), $operation, $field, $exception->getCode()); + } + } + } diff --git a/src/PathException.php b/src/PathException.php new file mode 100644 index 0000000..c96f83f --- /dev/null +++ b/src/PathException.php @@ -0,0 +1,51 @@ +operation = $operation; + $this->field = $field; + } + + /** + * @return object + */ + public function getOperation() + { + return $this->operation; + } + + /** + * @return string + */ + public function getField() + { + return $this->field; + } +} diff --git a/tests/src/JsonPatchTest.php b/tests/src/JsonPatchTest.php index f90a850..3ac0245 100644 --- a/tests/src/JsonPatchTest.php +++ b/tests/src/JsonPatchTest.php @@ -5,8 +5,10 @@ use Swaggest\JsonDiff\Exception; use Swaggest\JsonDiff\JsonDiff; use Swaggest\JsonDiff\JsonPatch; +use Swaggest\JsonDiff\JsonPatch\OpPath; use Swaggest\JsonDiff\MissingFieldException; use Swaggest\JsonDiff\PatchTestOperationFailedException; +use Swaggest\JsonDiff\PathException; use Swaggest\JsonDiff\UnknownOperationException; class JsonPatchTest extends \PHPUnit_Framework_TestCase @@ -174,4 +176,88 @@ public function testTestOperationFailed() $this->assertSame($actualValue, $testError->getActualValue()); } + public function testPathExceptionContinueOnError() + { + $data = new \stdClass(); + $patch = new JsonPatch(); + + $operation1 = new JsonPatch\Add('/some/path', 22); + $patch->op($operation1); + + $operation2 = new JsonPatch\Move('/target', '/source'); + $patch->op($operation2); + + $errors = $patch->apply($data, false); + + $this->assertInstanceOf(PathException::class, $errors[0]); + $this->assertSame($operation1, $errors[0]->getOperation()); + $this->assertSame('path', $errors[0]->getField()); + + $this->assertInstanceOf(PathException::class, $errors[1]); + $this->assertSame($operation2, $errors[1]->getOperation()); + $this->assertSame('from', $errors[1]->getField()); + } + + public function pathExceptionProvider() { + return [ + 'splitPath_path' => [ + new JsonPatch\Copy('invalid/path', '/valid/from'), + 'Path must start with "/": invalid/path', + 'path' + ], + 'splitPath_from' => [ + new JsonPatch\Copy('/valid/path', 'invalid/from'), + 'Path must start with "/": invalid/from', + 'from' + ], + 'add' => [ + new JsonPatch\Add('/some/path', 22), + 'Non-existent path item: some', + 'path' + ], + 'get_from' => [ + new JsonPatch\Copy('/target', '/source'), + 'Key not found: source', + 'from' + ], + 'get_path' => [ + new JsonPatch\Replace('/some/path', 23), + 'Key not found: some', + 'path' + ], + 'remove_from' => [ + new JsonPatch\Move('/target', '/source'), + 'Key not found: source', + 'from' + ], + 'remove_path' => [ + new JsonPatch\Remove('/some/path'), + 'Key not found: some', + 'path' + ] + ]; + } + + /** + * @param OpPath $operation + * @param string $expectedMessage + * @param string $expectedField + * + * @dataProvider pathExceptionProvider + */ + public function testPathException(OpPath $operation, $expectedMessage, $expectedField) { + $data = new \stdClass(); + $patch = new JsonPatch(); + + $patch->op($operation); + + try { + $patch->apply($data ); + $this->fail('PathException expected'); + } catch (Exception $ex) { + $this->assertInstanceOf(PathException::class, $ex); + $this->assertEquals($expectedMessage, $ex->getMessage()); + $this->assertEquals($expectedField, $ex->getField()); + } + } }