Skip to content

Commit ac58d3f

Browse files
Fix #827 (#828)
This PR will: - Scan schemas that are being added for `id` properties and register them if valid schemas - Avoid altering the `id` for sibling `ref` properties - Don't register schema or expand refs when found in enum or const This will fix #827
1 parent b209eb8 commit ac58d3f

File tree

5 files changed

+112
-37
lines changed

5 files changed

+112
-37
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99
### Fixed
1010
- Fix objects are non-unique despite key order ([#819](https://github.com/jsonrainbow/json-schema/pull/819))
11+
- Id's not being resolved and id property affects sibling ref which it should not do ([#828](https://github.com/jsonrainbow/json-schema/pull/828))
1112

1213
### Changed
1314
- Added extra breaking change to UPDATE-6.0.md regarding BaseConstraint::addError signature change ([#823](https://github.com/jsonrainbow/json-schema/pull/823))

phpstan-baseline.neon

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -646,22 +646,17 @@ parameters:
646646
path: src/JsonSchema/SchemaStorage.php
647647

648648
-
649-
message: "#^Argument of an invalid type object supplied for foreach, only iterables are supported\\.$#"
650-
count: 1
651-
path: src/JsonSchema/SchemaStorage.php
652-
653-
-
654-
message: "#^Call to function is_array\\(\\) with object will always evaluate to false\\.$#"
649+
message: "#^Argument of an invalid type array\\|stdClass supplied for foreach, only iterables are supported\\.$#"
655650
count: 1
656651
path: src/JsonSchema/SchemaStorage.php
657652

658653
-
659-
message: "#^Method JsonSchema\\\\SchemaStorage\\:\\:addSchema\\(\\) has no return type specified\\.$#"
654+
message: "#^Argument of an invalid type object supplied for foreach, only iterables are supported\\.$#"
660655
count: 1
661656
path: src/JsonSchema/SchemaStorage.php
662657

663658
-
664-
message: "#^Method JsonSchema\\\\SchemaStorage\\:\\:expandRefs\\(\\) has no return type specified\\.$#"
659+
message: "#^Call to function is_array\\(\\) with object will always evaluate to false\\.$#"
665660
count: 1
666661
path: src/JsonSchema/SchemaStorage.php
667662

@@ -695,11 +690,6 @@ parameters:
695690
count: 1
696691
path: src/JsonSchema/SchemaStorage.php
697692

698-
-
699-
message: "#^Method JsonSchema\\\\SchemaStorageInterface\\:\\:addSchema\\(\\) has no return type specified\\.$#"
700-
count: 1
701-
path: src/JsonSchema/SchemaStorageInterface.php
702-
703693
-
704694
message: "#^Method JsonSchema\\\\Uri\\\\Retrievers\\\\Curl\\:\\:fetchMessageBody\\(\\) has no return type specified\\.$#"
705695
count: 1

src/JsonSchema/SchemaStorage.php

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function getUriResolver()
4545
/**
4646
* {@inheritdoc}
4747
*/
48-
public function addSchema($id, $schema = null)
48+
public function addSchema(string $id, $schema = null): void
4949
{
5050
if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) {
5151
// if the schema was user-provided to Validator and is still null, then assume this is
@@ -62,14 +62,16 @@ public function addSchema($id, $schema = null)
6262
// workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format)
6363
// see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367
6464
if (is_object($schema) && property_exists($schema, 'id')) {
65-
if ($schema->id == 'http://json-schema.org/draft-04/schema#') {
65+
if ($schema->id === 'http://json-schema.org/draft-04/schema#') {
6666
$schema->properties->id->format = 'uri-reference';
67-
} elseif ($schema->id == 'http://json-schema.org/draft-03/schema#') {
67+
} elseif ($schema->id === 'http://json-schema.org/draft-03/schema#') {
6868
$schema->properties->id->format = 'uri-reference';
6969
$schema->properties->{'$ref'}->format = 'uri-reference';
7070
}
7171
}
7272

73+
$this->scanForSubschemas($schema, $id);
74+
7375
// resolve references
7476
$this->expandRefs($schema, $id);
7577

@@ -79,39 +81,44 @@ public function addSchema($id, $schema = null)
7981
/**
8082
* Recursively resolve all references against the provided base
8183
*
82-
* @param mixed $schema
83-
* @param string $base
84+
* @param mixed $schema
8485
*/
85-
private function expandRefs(&$schema, $base = null)
86+
private function expandRefs(&$schema, ?string $parentId = null): void
8687
{
8788
if (!is_object($schema)) {
8889
if (is_array($schema)) {
8990
foreach ($schema as &$member) {
90-
$this->expandRefs($member, $base);
91+
$this->expandRefs($member, $parentId);
9192
}
9293
}
9394

9495
return;
9596
}
9697

97-
if (property_exists($schema, 'id') && is_string($schema->id) && $base != $schema->id) {
98-
$base = $this->uriResolver->resolve($schema->id, $base);
99-
}
100-
10198
if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) {
102-
$refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $base));
99+
$refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $parentId));
103100
$schema->{'$ref'} = (string) $refPointer;
104101
}
105102

106-
foreach ($schema as &$member) {
107-
$this->expandRefs($member, $base);
103+
foreach ($schema as $propertyName => &$member) {
104+
if (in_array($propertyName, ['enum', 'const'])) {
105+
// Enum and const don't allow $ref as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/445
106+
continue;
107+
}
108+
109+
$childId = $parentId;
110+
if (property_exists($schema, 'id') && is_string($schema->id) && $childId !== $schema->id) {
111+
$childId = $this->uriResolver->resolve($schema->id, $childId);
112+
}
113+
114+
$this->expandRefs($member, $childId);
108115
}
109116
}
110117

111118
/**
112119
* {@inheritdoc}
113120
*/
114-
public function getSchema($id)
121+
public function getSchema(string $id)
115122
{
116123
if (!array_key_exists($id, $this->schemas)) {
117124
$this->addSchema($id);
@@ -123,7 +130,7 @@ public function getSchema($id)
123130
/**
124131
* {@inheritdoc}
125132
*/
126-
public function resolveRef($ref, $resolveStack = [])
133+
public function resolveRef(string $ref, $resolveStack = [])
127134
{
128135
$jsonPointer = new JsonPointer($ref);
129136

@@ -174,4 +181,32 @@ public function resolveRefSchema($refSchema, $resolveStack = [])
174181

175182
return $refSchema;
176183
}
184+
185+
/**
186+
* @param mixed $schema
187+
*/
188+
private function scanForSubschemas($schema, string $parentId): void
189+
{
190+
if (!$schema instanceof \stdClass && !is_array($schema)) {
191+
return;
192+
}
193+
194+
foreach ($schema as $propertyName => $potentialSubSchema) {
195+
if (!is_object($potentialSubSchema)) {
196+
continue;
197+
}
198+
199+
if (property_exists($potentialSubSchema, 'id') && is_string($potentialSubSchema->id) && property_exists($potentialSubSchema, 'type')) {
200+
// Enum and const don't allow id as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/471
201+
if (in_array($propertyName, ['enum', 'const'])) {
202+
continue;
203+
}
204+
205+
// Found sub schema
206+
$this->addSchema($this->uriResolver->resolve($potentialSubSchema->id, $parentId), $potentialSubSchema);
207+
}
208+
209+
$this->scanForSubschemas($potentialSubSchema, $parentId);
210+
}
211+
}
177212
}

src/JsonSchema/SchemaStorageInterface.php

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,23 @@ interface SchemaStorageInterface
99
/**
1010
* Adds schema with given identifier
1111
*
12-
* @param string $id
1312
* @param object $schema
1413
*/
15-
public function addSchema($id, $schema = null);
14+
public function addSchema(string $id, $schema = null): void;
1615

1716
/**
1817
* Returns schema for given identifier, or null if it does not exist
1918
*
20-
* @param string $id
21-
*
2219
* @return object
2320
*/
24-
public function getSchema($id);
21+
public function getSchema(string $id);
2522

2623
/**
2724
* Returns schema for given reference with all sub-references resolved
2825
*
29-
* @param string $ref
30-
*
3126
* @return object
3227
*/
33-
public function resolveRef($ref);
28+
public function resolveRef(string $ref);
3429

3530
/**
3631
* Returns schema referenced by '$ref' property

tests/Drafts/Draft3Test.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
namespace JsonSchema\Tests\Drafts;
1111

12+
use JsonSchema\Constraints\Factory;
13+
use JsonSchema\SchemaStorage;
14+
use JsonSchema\Validator;
15+
1216
/**
1317
* @package JsonSchema\Tests\Drafts
1418
*/
@@ -17,6 +21,56 @@ class Draft3Test extends BaseDraftTestCase
1721
protected $schemaSpec = 'http://json-schema.org/draft-03/schema#';
1822
protected $validateSchema = true;
1923

24+
/**
25+
* This test is a copy of https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft3/ref.json#L203-L225
26+
*
27+
* @todo cleanup when #821 gets merged
28+
*
29+
* @param mixed $data
30+
* @dataProvider refPreventsASiblingIdFromChangingTheBaseUriProvider
31+
*/
32+
public function testRefPreventsASiblingIdFromChangingTheBaseUriProvider($data, bool $expectedResult): void
33+
{
34+
$schema = json_decode(<<<'JSON'
35+
{
36+
"id": "http://localhost:1234/sibling_id/base/",
37+
"definitions": {
38+
"foo": {
39+
"id": "http://localhost:1234/sibling_id/foo.json",
40+
"type": "string"
41+
},
42+
"base_foo": {
43+
"$comment": "this canonical uri is http://localhost:1234/sibling_id/base/foo.json",
44+
"id": "foo.json",
45+
"type": "number"
46+
}
47+
},
48+
"extends": [
49+
{
50+
"$comment": "$ref resolves to http://localhost:1234/sibling_id/base/foo.json, not http://localhost:1234/sibling_id/foo.json",
51+
"id": "http://localhost:1234/sibling_id/",
52+
"$ref": "foo.json"
53+
}
54+
]
55+
}
56+
JSON
57+
, false);
58+
59+
$schemaStorage = new SchemaStorage();
60+
$schemaStorage->addSchema(property_exists($schema, 'id') ? $schema->id : 'internal://mySchema', $schema);
61+
$validator = new Validator(new Factory($schemaStorage));
62+
$validator->validate($data, $schema);
63+
64+
self::assertEquals($expectedResult, $validator->isValid());
65+
}
66+
67+
public function refPreventsASiblingIdFromChangingTheBaseUriProvider(): \Generator
68+
{
69+
yield '$ref resolves to /definitions/base_foo, data does not validate' => ['data' => 'a', 'valid' => false];
70+
yield '$ref resolves to /definitions/base_foo, data validate' => ['data' => 1, 'valid' => true];
71+
}
72+
73+
2074
/**
2175
* {@inheritdoc}
2276
*/

0 commit comments

Comments
 (0)