Skip to content

Commit a2cfa75

Browse files
committed
Add proper recursive handling for $ref resolution base (#448)
Fixes #447 Note that this patch does not check whether a given container is actually a schema when recursing into it. In most cases this will not matter, however it does mean that in some edge cases it will attempt to resolve a `$ref` in a context where ref is actually not part of the spec. Limiting resolution to schema-context containers is outside the scope of this patch, but can be added later.
1 parent 1a330df commit a2cfa75

File tree

4 files changed

+85
-9
lines changed

4 files changed

+85
-9
lines changed

src/JsonSchema/SchemaStorage.php

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use JsonSchema\Constraints\BaseConstraint;
66
use JsonSchema\Entity\JsonPointer;
77
use JsonSchema\Exception\UnresolvableJsonPointerException;
8-
use JsonSchema\Iterator\ObjectIterator;
98
use JsonSchema\Uri\UriResolver;
109
use JsonSchema\Uri\UriRetriever;
1110

@@ -69,14 +68,42 @@ public function addSchema($id, $schema = null)
6968
}
7069
}
7170

72-
$objectIterator = new ObjectIterator($schema);
73-
foreach ($objectIterator as $toResolveSchema) {
74-
if (property_exists($toResolveSchema, '$ref') && is_string($toResolveSchema->{'$ref'})) {
75-
$jsonPointer = new JsonPointer($this->uriResolver->resolve($toResolveSchema->{'$ref'}, $id));
76-
$toResolveSchema->{'$ref'} = (string) $jsonPointer;
71+
// resolve references
72+
$this->expandRefs($schema, $id);
73+
74+
$this->schemas[$id] = $schema;
75+
}
76+
77+
/**
78+
* Recursively resolve all references against the provided base
79+
*
80+
* @param mixed $schema
81+
* @param string $base
82+
*/
83+
private function expandRefs(&$schema, $base = null)
84+
{
85+
if (!is_object($schema)) {
86+
if (is_array($schema)) {
87+
foreach ($schema as &$member) {
88+
$this->expandRefs($member, $base);
89+
}
7790
}
91+
92+
return;
93+
}
94+
95+
if (property_exists($schema, 'id') && is_string($schema->id)) {
96+
$base = $this->uriResolver->resolve($schema->id, $base);
97+
}
98+
99+
if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) {
100+
$refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $base));
101+
$schema->{'$ref'} = (string) $refPointer;
102+
}
103+
104+
foreach ($schema as &$member) {
105+
$this->expandRefs($member, $base);
78106
}
79-
$this->schemas[$id] = $schema;
80107
}
81108

82109
/**

src/JsonSchema/Uri/UriResolver.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ public function generate(array $components)
7676
*/
7777
public function resolve($uri, $baseUri = null)
7878
{
79+
// treat non-uri base as local file path
80+
if (!is_null($baseUri) && !filter_var($baseUri, \FILTER_VALIDATE_URL)) {
81+
if (is_file($baseUri)) {
82+
$baseUri = 'file://' . realpath($baseUri);
83+
} elseif (is_dir($baseUri)) {
84+
$baseUri = 'file://' . realpath($baseUri) . '/';
85+
} else {
86+
$baseUri = 'file://' . getcwd() . '/' . $baseUri;
87+
}
88+
}
89+
7990
if ($uri == '') {
8091
return $baseUri;
8192
}

src/JsonSchema/Validator.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,17 @@ public function validate(&$value, $schema = null, $checkMode = null)
5454
}
5555

5656
// add provided schema to SchemaStorage with internal URI to allow internal $ref resolution
57-
$this->factory->getSchemaStorage()->addSchema(SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI, $schema);
57+
if (is_object($schema) && property_exists($schema, 'id')) {
58+
$schemaURI = $schema->id;
59+
} else {
60+
$schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI;
61+
}
62+
$this->factory->getSchemaStorage()->addSchema($schemaURI, $schema);
5863

5964
$validator = $this->factory->createInstanceFor('schema');
6065
$validator->check(
6166
$value,
62-
$this->factory->getSchemaStorage()->getSchema(SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI)
67+
$this->factory->getSchemaStorage()->getSchema($schemaURI)
6368
);
6469

6570
$this->factory->setConfig($initialCheckMode);

tests/Uri/UriResolverTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,37 @@ public function testReversable()
190190
// check that the recombined URI matches the original input
191191
$this->assertEquals($uri, $this->resolver->generate($split));
192192
}
193+
194+
public function testRelativeFileAsRoot()
195+
{
196+
$this->assertEquals(
197+
'file://' . getcwd() . '/src/JsonSchema/Validator.php',
198+
$this->resolver->resolve(
199+
'Validator.php',
200+
'src/JsonSchema/SchemaStorage.php'
201+
)
202+
);
203+
}
204+
205+
public function testRelativeDirectoryAsRoot()
206+
{
207+
$this->assertEquals(
208+
'file://' . getcwd() . '/src/JsonSchema/Validator.php',
209+
$this->resolver->resolve(
210+
'Validator.php',
211+
'src/JsonSchema'
212+
)
213+
);
214+
}
215+
216+
public function testRelativeNonExistentFileAsRoot()
217+
{
218+
$this->assertEquals(
219+
'file://' . getcwd() . '/resolved.file',
220+
$this->resolver->resolve(
221+
'resolved.file',
222+
'test.file'
223+
)
224+
);
225+
}
193226
}

0 commit comments

Comments
 (0)