Skip to content

Commit 4eb3442

Browse files
authored
Merge pull request #57 from wmde/master
Improve JsonPatch exceptions
2 parents ccfc713 + 478e618 commit 4eb3442

9 files changed

+344
-34
lines changed

src/JsonPatch.php

+29-8
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,15 @@ public static function import(array $data)
6060
$operation = (object)$operation;
6161
}
6262

63+
if (!is_object($operation)) {
64+
throw new Exception('Invalid patch operation - should be a JSON object');
65+
}
66+
6367
if (!isset($operation->op)) {
64-
throw new Exception('Missing "op" in operation data');
68+
throw new MissingFieldException('op', $operation);
6569
}
6670
if (!isset($operation->path)) {
67-
throw new Exception('Missing "path" in operation data');
71+
throw new MissingFieldException('path', $operation);
6872
}
6973

7074
$op = null;
@@ -88,18 +92,18 @@ public static function import(array $data)
8892
$op = new Test();
8993
break;
9094
default:
91-
throw new Exception('Unknown "op": ' . $operation->op);
95+
throw new UnknownOperationException($operation);
9296
}
9397
$op->path = $operation->path;
9498
if ($op instanceof OpPathValue) {
9599
if (property_exists($operation, 'value')) {
96100
$op->value = $operation->value;
97101
} else {
98-
throw new Exception('Missing "value" in operation data');
102+
throw new MissingFieldException('value', $operation);
99103
}
100104
} elseif ($op instanceof OpPathFrom) {
101105
if (!isset($operation->from)) {
102-
throw new Exception('Missing "from" in operation data');
106+
throw new MissingFieldException('from', $operation);
103107
}
104108
$op->from = $operation->from;
105109
}
@@ -141,20 +145,26 @@ public function apply(&$original, $stopOnError = true)
141145
$errors = array();
142146
foreach ($this->operations as $operation) {
143147
try {
148+
// track the current pointer field so we can use it for a potential PathException
149+
$pointerField = 'path';
144150
$pathItems = JsonPointer::splitPath($operation->path);
145151
switch (true) {
146152
case $operation instanceof Add:
147153
JsonPointer::add($original, $pathItems, $operation->value, $this->flags);
148154
break;
149155
case $operation instanceof Copy:
156+
$pointerField = 'from';
150157
$fromItems = JsonPointer::splitPath($operation->from);
151158
$value = JsonPointer::get($original, $fromItems);
159+
$pointerField = 'path';
152160
JsonPointer::add($original, $pathItems, $value, $this->flags);
153161
break;
154162
case $operation instanceof Move:
163+
$pointerField = 'from';
155164
$fromItems = JsonPointer::splitPath($operation->from);
156165
$value = JsonPointer::get($original, $fromItems);
157166
JsonPointer::remove($original, $fromItems, $this->flags);
167+
$pointerField = 'path';
158168
JsonPointer::add($original, $pathItems, $value, $this->flags);
159169
break;
160170
case $operation instanceof Remove:
@@ -170,11 +180,22 @@ public function apply(&$original, $stopOnError = true)
170180
$diff = new JsonDiff($operation->value, $value,
171181
JsonDiff::STOP_ON_DIFF);
172182
if ($diff->getDiffCnt() !== 0) {
173-
throw new PatchTestOperationFailedException('Test operation ' . json_encode($operation, JSON_UNESCAPED_SLASHES)
174-
. ' failed: ' . json_encode($value));
183+
throw new PatchTestOperationFailedException($operation, $value);
175184
}
176185
break;
177186
}
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+
}
178199
} catch (Exception $exception) {
179200
if ($stopOnError) {
180201
throw $exception;
@@ -185,4 +206,4 @@ public function apply(&$original, $stopOnError = true)
185206
}
186207
return $errors;
187208
}
188-
}
209+
}

src/JsonPointer.php

+15-15
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public static function splitPath($path)
6666
return self::splitPathURIFragment($pathItems);
6767
} else {
6868
if ($first !== '') {
69-
throw new Exception('Path must start with "/": ' . $path);
69+
throw new JsonPointerException('Path must start with "/": ' . $path);
7070
}
7171
return self::splitPathJsonString($pathItems);
7272
}
@@ -105,15 +105,15 @@ public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIV
105105
while (null !== $key = array_shift($pathItems)) {
106106
if ($ref instanceof \stdClass || is_object($ref)) {
107107
if (PHP_VERSION_ID < 70100 && '' === $key) {
108-
throw new Exception('Empty property name is not supported by PHP <7.1',
108+
throw new JsonPointerException('Empty property name is not supported by PHP <7.1',
109109
Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
110110
}
111111

112112
if ($flags & self::RECURSIVE_KEY_CREATION) {
113113
$ref = &$ref->$key;
114114
} else {
115115
if (!isset($ref->$key) && count($pathItems)) {
116-
throw new Exception('Non-existent path item: ' . $key);
116+
throw new JsonPointerException('Non-existent path item: ' . $key);
117117
} else {
118118
$ref = &$ref->$key;
119119
}
@@ -126,7 +126,7 @@ public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIV
126126
$ref = new \stdClass();
127127
$ref = &$ref->{$key};
128128
} else {
129-
throw new Exception('Non-existent path item: ' . $key);
129+
throw new JsonPointerException('Non-existent path item: ' . $key);
130130
}
131131
} elseif ([] === $ref && 0 === ($flags & self::STRICT_MODE) && false === $intKey && '-' !== $key) {
132132
$ref = new \stdClass();
@@ -138,7 +138,7 @@ public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIV
138138
} else {
139139
if (false === $intKey) {
140140
if (0 === ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS)) {
141-
throw new Exception('Invalid key for array operation');
141+
throw new JsonPointerException('Invalid key for array operation');
142142
}
143143
$ref = &$ref[$key];
144144
continue;
@@ -148,9 +148,9 @@ public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIV
148148
}
149149
if (0 === ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS)) {
150150
if ($intKey > count($ref) && 0 === ($flags & self::RECURSIVE_KEY_CREATION)) {
151-
throw new Exception('Index is greater than number of items in array');
151+
throw new JsonPointerException('Index is greater than number of items in array');
152152
} elseif ($intKey < 0) {
153-
throw new Exception('Negative index');
153+
throw new JsonPointerException('Negative index');
154154
}
155155
}
156156

@@ -203,30 +203,30 @@ public static function get($holder, $pathItems)
203203
while (null !== $key = array_shift($pathItems)) {
204204
if ($ref instanceof \stdClass) {
205205
if (PHP_VERSION_ID < 70100 && '' === $key) {
206-
throw new Exception('Empty property name is not supported by PHP <7.1',
206+
throw new JsonPointerException('Empty property name is not supported by PHP <7.1',
207207
Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
208208
}
209209

210210
$vars = (array)$ref;
211211
if (self::arrayKeyExists($key, $vars)) {
212212
$ref = self::arrayGet($key, $vars);
213213
} else {
214-
throw new Exception('Key not found: ' . $key);
214+
throw new JsonPointerException('Key not found: ' . $key);
215215
}
216216
} elseif (is_array($ref)) {
217217
if (self::arrayKeyExists($key, $ref)) {
218218
$ref = $ref[$key];
219219
} else {
220-
throw new Exception('Key not found: ' . $key);
220+
throw new JsonPointerException('Key not found: ' . $key);
221221
}
222222
} elseif (is_object($ref)) {
223223
if (isset($ref->$key)) {
224224
$ref = $ref->$key;
225225
} else {
226-
throw new Exception('Key not found: ' . $key);
226+
throw new JsonPointerException('Key not found: ' . $key);
227227
}
228228
} else {
229-
throw new Exception('Key not found: ' . $key);
229+
throw new JsonPointerException('Key not found: ' . $key);
230230
}
231231
}
232232
return $ref;
@@ -260,19 +260,19 @@ public static function remove(&$holder, $pathItems, $flags = 0)
260260
if (property_exists($ref, $key)) {
261261
$ref = &$ref->$key;
262262
} else {
263-
throw new Exception('Key not found: ' . $key);
263+
throw new JsonPointerException('Key not found: ' . $key);
264264
}
265265
} elseif (is_object($ref)) {
266266
if (isset($ref->$key)) {
267267
$ref = &$ref->$key;
268268
} else {
269-
throw new Exception('Key not found: ' . $key);
269+
throw new JsonPointerException('Key not found: ' . $key);
270270
}
271271
} else {
272272
if (array_key_exists($key, $ref)) {
273273
$ref = &$ref[$key];
274274
} else {
275-
throw new Exception('Key not found: ' . $key);
275+
throw new JsonPointerException('Key not found: ' . $key);
276276
}
277277
}
278278
}

src/JsonPointerException.php

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
namespace Swaggest\JsonDiff;
4+
5+
class JsonPointerException extends Exception {}

src/MissingFieldException.php

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

src/PatchTestOperationFailedException.php

+42-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,47 @@
33
namespace Swaggest\JsonDiff;
44

55

6+
use Throwable;
7+
68
class PatchTestOperationFailedException extends Exception
79
{
8-
}
10+
/** @var object */
11+
private $operation;
12+
/** @var string */
13+
private $actualValue;
14+
15+
/**
16+
* @param object $operation
17+
* @param string $actualValue
18+
* @param int $code
19+
* @param Throwable|null $previous
20+
*/
21+
public function __construct(
22+
$operation,
23+
$actualValue,
24+
$code = 0,
25+
Throwable $previous = null
26+
)
27+
{
28+
parent::__construct('Test operation ' . json_encode($operation, JSON_UNESCAPED_SLASHES)
29+
. ' failed: ' . json_encode($actualValue), $code, $previous);
30+
$this->operation = $operation;
31+
$this->actualValue = $actualValue;
32+
}
33+
34+
/**
35+
* @return object
36+
*/
37+
public function getOperation()
38+
{
39+
return $this->operation;
40+
}
41+
42+
/**
43+
* @return string
44+
*/
45+
public function getActualValue()
46+
{
47+
return $this->actualValue;
48+
}
49+
}

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+
}

0 commit comments

Comments
 (0)