Skip to content

Commit ab21240

Browse files
authored
Merge pull request #26 from swaggest/error-inspect
error readability
2 parents d461605 + 7ac9d94 commit ab21240

16 files changed

+394
-29
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,29 @@ $schema = Schema::import(true); // permissive schema, always validates
102102
$schema = Schema::import(false); // restrictive schema, always invalidates
103103
```
104104

105+
### Understanding error cause
106+
107+
With complex schemas it may be hard to find out what's wrong with your data. Exception message can look like:
108+
109+
```
110+
No valid results for oneOf {
111+
0: Enum failed, enum: ["a"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[0]
112+
1: Enum failed, enum: ["b"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[1]
113+
2: No valid results for anyOf {
114+
0: Enum failed, enum: ["c"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[0]
115+
1: Enum failed, enum: ["d"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[1]
116+
2: Enum failed, enum: ["e"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[2]
117+
} at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]
118+
} at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo
119+
```
120+
121+
For ambiguous schemas defined with `oneOf`/`anyOf` message is indented multi-line string.
122+
123+
Processing path is a combination of schema and data pointers. You can use `InvalidValue->getSchemaPointer()`
124+
and `InvalidValue->getDataPointer()` to extract schema/data pointer.
125+
126+
You can build error tree using `InvalidValue->inspect()`.
127+
105128
### PHP structured classes with validation
106129

107130
```php

composer.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Exception/Error.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Swaggest\JsonSchema\Exception;
4+
5+
use Swaggest\JsonSchema\InvalidValue;
6+
7+
class Error
8+
{
9+
/** @var string */
10+
public $error;
11+
/** @var string[] */
12+
public $schemaPointers;
13+
/** @var string */
14+
public $dataPointer;
15+
/** @var string */
16+
public $processingPath;
17+
/** @var Error[] */
18+
public $subErrors;
19+
}

src/Exception/LogicException.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66

77
class LogicException extends InvalidValue
88
{
9-
9+
/** @var InvalidValue[] */
10+
public $subErrors;
1011
}

src/Helper.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public static function resolveURI($parent, $current)
5959
}
6060
} elseif (false !== $pos = strrpos($parentParts[0], '/')) {
6161
$resultParts[0] = substr($parentParts[0], 0, $pos + 1) . $currentParts[0];
62+
} else {
63+
$resultParts[0] = $currentParts[0];
6264
}
6365
}
6466

src/InvalidValue.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,51 @@
22

33
namespace Swaggest\JsonSchema;
44

5+
use Swaggest\JsonSchema\Exception\Error;
6+
use Swaggest\JsonSchema\Exception\LogicException;
7+
use Swaggest\JsonSchema\Path\PointerUtil;
8+
59
class InvalidValue extends Exception
610
{
11+
public $error;
12+
public $path;
13+
714
public function addPath($path)
815
{
16+
if ($this->error === null) {
17+
$this->error = $this->message;
18+
}
19+
$this->path = $path;
920
$this->message .= ' at ' . $path;
1021
}
1122

1223
const INVALID_VALUE = 1;
1324
const NOT_IMPLEMENTED = 2;
25+
26+
27+
public function inspect()
28+
{
29+
$error = new Error();
30+
$error->error = $this->error;
31+
$error->processingPath = $this->path;
32+
$error->dataPointer = PointerUtil::getDataPointer($error->processingPath);
33+
$error->schemaPointers = PointerUtil::getSchemaPointers($error->processingPath);
34+
if ($this instanceof LogicException) {
35+
foreach ($this->subErrors as $subError) {
36+
$error->subErrors[] = $subError->inspect();
37+
}
38+
}
39+
return $error;
40+
}
41+
42+
public function getSchemaPointer()
43+
{
44+
return PointerUtil::getSchemaPointer($this->path);
45+
}
46+
47+
public function getDataPointer()
48+
{
49+
return PointerUtil::getDataPointer($this->path);
50+
}
51+
1452
}

src/Path/PointerUtil.php

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,87 @@
33
namespace Swaggest\JsonSchema\Path;
44

55
use Swaggest\JsonDiff\JsonPointer;
6+
use Swaggest\JsonSchema\Schema;
67

78
class PointerUtil
89
{
10+
/**
11+
* Builds JSON pointer to schema from processing path
12+
* Path example: #->properties:responses->additionalProperties:envvar->properties:schema
13+
* @param string $path
14+
* @param bool $isURIFragmentId
15+
* @return string
16+
*/
17+
public static function getSchemaPointer($path, $isURIFragmentId = false)
18+
{
19+
$result = self::getSchemaPointers($path, $isURIFragmentId);
20+
return array_pop($result);
21+
}
22+
23+
/**
24+
* Builds JSON pointer to schema from processing path
25+
* Path example: #->properties:responses->additionalProperties:envvar->properties:schema
26+
* @param string $path
27+
* @param bool $isURIFragmentId
28+
* @return string[]
29+
*/
30+
public static function getSchemaPointers($path, $isURIFragmentId = false)
31+
{
32+
$items = explode('->', $path);
33+
unset($items[0]);
34+
$result = array();
35+
$pointer = $isURIFragmentId ? '#' : '';
36+
foreach ($items as $item) {
37+
$parts = explode(':', $item);
38+
if (isset($parts[0])) {
39+
$schemaPaths = explode('[', $parts[0], 2);
40+
if ($schemaPaths[0] === Schema::PROP_REF) {
41+
$result[] = $pointer . '/' . JsonPointer::escapeSegment(Schema::PROP_REF, $isURIFragmentId);
42+
$pointer = self::rebuildPointer(substr($schemaPaths[1], 0, -1), $isURIFragmentId);
43+
continue;
44+
}
45+
$pointer .= '/' . JsonPointer::escapeSegment($schemaPaths[0], $isURIFragmentId);
46+
if (isset($schemaPaths[1])) {
47+
$pointer .= '/' . JsonPointer::escapeSegment(substr($schemaPaths[1], 0, -1), $isURIFragmentId);
48+
} elseif ($parts[1]) {
49+
$pointer .= '/' . JsonPointer::escapeSegment($parts[1], $isURIFragmentId);
50+
}
51+
}
52+
}
53+
$result[] = $pointer;
54+
return $result;
55+
}
56+
57+
958
/**
1059
* Builds JSON pointer to data from processing path
1160
* Path example: #->properties:responses->additionalProperties:envvar->properties:schema
1261
* @param string $path
62+
* @param bool $isURIFragmentId
1363
* @return string
14-
* @todo proper path items escaping/moving to native JSON pointers
1564
*/
16-
public static function getDataPointer($path) {
65+
public static function getDataPointer($path, $isURIFragmentId = false)
66+
{
1767
$items = explode('->', $path);
1868
unset($items[0]);
19-
$result = '#';
69+
$result = $isURIFragmentId ? '#' : '';
2070
foreach ($items as $item) {
2171
$parts = explode(':', $item);
2272
if (isset($parts[1])) {
23-
$result .= '/' . JsonPointer::escapeSegment($parts[1], true);
73+
$result .= '/' . JsonPointer::escapeSegment($parts[1], $isURIFragmentId);
2474
}
2575
}
2676
return $result;
2777
}
2878

79+
private static function rebuildPointer($pointer, $isURIFragmentId = false)
80+
{
81+
$parts = JsonPointer::splitPath($pointer);
82+
$result = $isURIFragmentId ? '#' : '';
83+
foreach ($parts as $item) {
84+
$result .= '/' . JsonPointer::escapeSegment($item, $isURIFragmentId);
85+
}
86+
return $result;
87+
}
88+
2989
}

src/RefResolver.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ public function getResolutionScope()
3939
}
4040

4141

42+
/**
43+
* @param string $id
44+
* @return string
45+
*/
4246
public function updateResolutionScope($id)
4347
{
4448
$id = rtrim($id, '#');

0 commit comments

Comments
 (0)