Skip to content

Commit 478e618

Browse files
Silvan-WMDEjakobw
authored andcommitted
Introduce PathException for JsonPatch
This exception is thrown in JsonPatch::apply whenever a JsonPointer call fails.
1 parent eae1256 commit 478e618

File tree

3 files changed

+156
-1
lines changed

3 files changed

+156
-1
lines changed

src/JsonPatch.php

+19-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public static function import(array $data)
6161
}
6262

6363
if (!is_object($operation)) {
64-
throw new Exception( 'Invalid patch operation - should be a JSON object' );
64+
throw new Exception('Invalid patch operation - should be a JSON object');
6565
}
6666

6767
if (!isset($operation->op)) {
@@ -145,20 +145,26 @@ public function apply(&$original, $stopOnError = true)
145145
$errors = array();
146146
foreach ($this->operations as $operation) {
147147
try {
148+
// track the current pointer field so we can use it for a potential PathException
149+
$pointerField = 'path';
148150
$pathItems = JsonPointer::splitPath($operation->path);
149151
switch (true) {
150152
case $operation instanceof Add:
151153
JsonPointer::add($original, $pathItems, $operation->value, $this->flags);
152154
break;
153155
case $operation instanceof Copy:
156+
$pointerField = 'from';
154157
$fromItems = JsonPointer::splitPath($operation->from);
155158
$value = JsonPointer::get($original, $fromItems);
159+
$pointerField = 'path';
156160
JsonPointer::add($original, $pathItems, $value, $this->flags);
157161
break;
158162
case $operation instanceof Move:
163+
$pointerField = 'from';
159164
$fromItems = JsonPointer::splitPath($operation->from);
160165
$value = JsonPointer::get($original, $fromItems);
161166
JsonPointer::remove($original, $fromItems, $this->flags);
167+
$pointerField = 'path';
162168
JsonPointer::add($original, $pathItems, $value, $this->flags);
163169
break;
164170
case $operation instanceof Remove:
@@ -178,6 +184,18 @@ public function apply(&$original, $stopOnError = true)
178184
}
179185
break;
180186
}
187+
} catch (JsonPointerException $jsonPointerException) {
188+
$pathException = new PathException(
189+
$jsonPointerException->getMessage(),
190+
$operation,
191+
$pointerField,
192+
$jsonPointerException->getCode()
193+
);
194+
if ($stopOnError) {
195+
throw $pathException;
196+
} else {
197+
$errors[] = $pathException;
198+
}
181199
} catch (Exception $exception) {
182200
if ($stopOnError) {
183201
throw $exception;

src/PathException.php

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Swaggest\JsonDiff;
4+
5+
6+
use Throwable;
7+
8+
class PathException extends Exception
9+
{
10+
/** @var object */
11+
private $operation;
12+
13+
/** @var string */
14+
private $field;
15+
16+
/**
17+
* @param string $message
18+
* @param object $operation
19+
* @param string $field
20+
* @param int $code
21+
* @param Throwable|null $previous
22+
*/
23+
public function __construct(
24+
$message,
25+
$operation,
26+
$field,
27+
$code = 0,
28+
Throwable $previous = null
29+
)
30+
{
31+
parent::__construct($message, $code, $previous);
32+
$this->operation = $operation;
33+
$this->field = $field;
34+
}
35+
36+
/**
37+
* @return object
38+
*/
39+
public function getOperation()
40+
{
41+
return $this->operation;
42+
}
43+
44+
/**
45+
* @return string
46+
*/
47+
public function getField()
48+
{
49+
return $this->field;
50+
}
51+
}

tests/src/JsonPatchTest.php

+86
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
use Swaggest\JsonDiff\Exception;
66
use Swaggest\JsonDiff\JsonDiff;
77
use Swaggest\JsonDiff\JsonPatch;
8+
use Swaggest\JsonDiff\JsonPatch\OpPath;
89
use Swaggest\JsonDiff\MissingFieldException;
910
use Swaggest\JsonDiff\PatchTestOperationFailedException;
11+
use Swaggest\JsonDiff\PathException;
1012
use Swaggest\JsonDiff\UnknownOperationException;
1113

1214
class JsonPatchTest extends \PHPUnit_Framework_TestCase
@@ -174,4 +176,88 @@ public function testTestOperationFailed()
174176
$this->assertSame($actualValue, $testError->getActualValue());
175177
}
176178

179+
public function testPathExceptionContinueOnError()
180+
{
181+
$actualValue = 'xyz';
182+
$data = array('abc' => $actualValue);
183+
$patch = new JsonPatch();
184+
185+
$operation1 = new JsonPatch\Test('/abc', 'def');
186+
$patch->op($operation1);
187+
188+
$operation2 = new JsonPatch\Move('/target', '/source');
189+
$patch->op($operation2);
190+
191+
$errors = $patch->apply($data, false);
192+
193+
$this->assertInstanceOf(PatchTestOperationFailedException::class, $errors[0]);
194+
$this->assertSame($operation1, $errors[0]->getOperation());
195+
196+
$this->assertInstanceOf(PathException::class, $errors[1]);
197+
$this->assertSame($operation2, $errors[1]->getOperation());
198+
$this->assertSame('from', $errors[1]->getField());
199+
}
200+
201+
public function pathExceptionProvider() {
202+
return [
203+
'splitPath_path' => [
204+
new JsonPatch\Copy('invalid/path', '/valid/from'),
205+
'Path must start with "/": invalid/path',
206+
'path'
207+
],
208+
'splitPath_from' => [
209+
new JsonPatch\Copy('/valid/path', 'invalid/from'),
210+
'Path must start with "/": invalid/from',
211+
'from'
212+
],
213+
'add' => [
214+
new JsonPatch\Add('/some/path', 22),
215+
'Non-existent path item: some',
216+
'path'
217+
],
218+
'get_from' => [
219+
new JsonPatch\Copy('/target', '/source'),
220+
'Key not found: source',
221+
'from'
222+
],
223+
'get_path' => [
224+
new JsonPatch\Replace('/some/path', 23),
225+
'Key not found: some',
226+
'path'
227+
],
228+
'remove_from' => [
229+
new JsonPatch\Move('/target', '/source'),
230+
'Key not found: source',
231+
'from'
232+
],
233+
'remove_path' => [
234+
new JsonPatch\Remove('/some/path'),
235+
'Key not found: some',
236+
'path'
237+
]
238+
];
239+
}
240+
241+
/**
242+
* @param OpPath $operation
243+
* @param string $expectedMessage
244+
* @param string $expectedField
245+
*
246+
* @dataProvider pathExceptionProvider
247+
*/
248+
public function testPathException(OpPath $operation, $expectedMessage, $expectedField) {
249+
$data = new \stdClass();
250+
$patch = new JsonPatch();
251+
252+
$patch->op($operation);
253+
254+
try {
255+
$patch->apply($data );
256+
$this->fail('PathException expected');
257+
} catch (Exception $ex) {
258+
$this->assertInstanceOf(PathException::class, $ex);
259+
$this->assertEquals($expectedMessage, $ex->getMessage());
260+
$this->assertEquals($expectedField, $ex->getField());
261+
}
262+
}
177263
}

0 commit comments

Comments
 (0)