Skip to content

Commit 9d168be

Browse files
committed
Improve Reference resolution by using separate JSON Reference implementation
1 parent 6ebb3f7 commit 9d168be

1 file changed

Lines changed: 46 additions & 68 deletions

File tree

src/spec/Reference.php

Lines changed: 46 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
use cebe\openapi\exceptions\TypeErrorException;
1111
use cebe\openapi\exceptions\UnresolvableReferenceException;
12+
use cebe\openapi\json\InvalidJsonPointerSyntaxException;
13+
use cebe\openapi\json\JsonReference;
14+
use cebe\openapi\json\NonexistentJsonPointerReferenceException;
1215
use cebe\openapi\ReferenceContext;
1316
use cebe\openapi\SpecObjectInterface;
1417
use Symfony\Component\Yaml\Yaml;
@@ -25,6 +28,7 @@ class Reference implements SpecObjectInterface
2528
{
2629
private $_to;
2730
private $_ref;
31+
private $_jsonReference;
2832
private $_context;
2933

3034
private $_errors = [];
@@ -39,25 +43,29 @@ public function __construct(array $data, string $to = null)
3943
{
4044
if (!isset($data['$ref'])) {
4145
throw new TypeErrorException(
42-
"Unable to instantiate Reference Object with data '" . print_r($data, true) . "'"
46+
"Unable to instantiate Reference Object with data '" . print_r($data, true) . "'."
4347
);
4448
}
4549
if ($to !== null && !is_subclass_of($to, SpecObjectInterface::class, true)) {
4650
throw new TypeErrorException(
47-
"Unable to instantiate Reference Object, Referenced Class type must implement SpecObjectInterface"
51+
"Unable to instantiate Reference Object, Referenced Class type must implement SpecObjectInterface."
52+
);
53+
}
54+
if (!is_string($data['$ref'])) {
55+
throw new TypeErrorException(
56+
'Unable to instantiate Reference Object, value of $ref must be a string.'
4857
);
4958
}
5059
$this->_to = $to;
60+
$this->_ref = $data['$ref'];
61+
try {
62+
$this->_jsonReference = JsonReference::createFromReference($data['$ref']);
63+
} catch (InvalidJsonPointerSyntaxException $e) {
64+
$this->_errors[] = 'Reference: value of $ref is not a valid JSON pointer: ' . $e->getMessage();
65+
}
5166
if (count($data) !== 1) {
5267
$this->_errors[] = 'Reference: additional properties are given. Only $ref should be set in a Reference Object.';
5368
}
54-
if (!is_string($data['$ref'])) {
55-
$this->_errors[] = 'Reference: value of $ref must be a string.';
56-
}
57-
if (!empty($this->_errors)) {
58-
return;
59-
}
60-
$this->_ref = $data['$ref'];
6169
}
6270

6371
/**
@@ -96,6 +104,14 @@ public function getReference()
96104
return $this->_ref;
97105
}
98106

107+
/**
108+
* @return JsonReference the JSON Reference.
109+
*/
110+
public function getJsonReference(): JsonReference
111+
{
112+
return $this->_jsonReference;
113+
}
114+
99115
/**
100116
* @param ReferenceContext $context
101117
*/
@@ -128,68 +144,30 @@ public function resolve(ReferenceContext $context = null)
128144
throw new UnresolvableReferenceException('No context given for resolving reference.');
129145
}
130146
}
131-
if (($pos = strpos($this->_ref, '#')) === 0) {
132-
// resolve in current document
133-
$jsonPointer = substr($this->_ref, 1);
134-
// TODO type error if resolved object does not match $this->_to ?
135-
return $this->resolveJsonPointer($jsonPointer, $context->getBaseSpec());
136-
}
137-
138-
$file = ($pos === false) ? $this->_ref : substr($this->_ref, 0, $pos);
139-
$file = $context->resolveRelativeUri($file);
140-
$jsonPointer = ($pos === false) ? '' : substr($this->_ref, $pos + 1);
141-
142-
// TODO could be a good idea to cache loaded files in current context to avoid loading the same files over and over again
143-
$fileContent = $this->fetchReferencedFile($file);
144-
$referencedData = $this->resolveJsonPointer($jsonPointer, $fileContent);
145-
146-
/** @var $referencedObject SpecObjectInterface */
147-
$referencedObject = new $this->_to($referencedData);
148-
if ($jsonPointer === '') {
149-
$referencedObject->setReferenceContext(new ReferenceContext($referencedObject, $file));
150-
} else {
151-
// TODO resolving references recursively does not work as we do not know the base type of the file at this point
152-
// $referencedObject->resolveReferences(new ReferenceContext($referencedObject, $file));
153-
}
154-
155-
return $referencedObject;
156-
}
157-
158-
private function resolveJsonPointer($jsonPointer, $currentReference)
159-
{
160-
if ($jsonPointer === '') {
161-
// empty pointer references the whole document
162-
return $currentReference;
163-
}
164-
$pointerParts = explode('/', ltrim($jsonPointer, '/'));
165-
foreach ($pointerParts as $part) {
166-
$part = strtr($part, [
167-
'~1' => '/',
168-
'~0' => '~',
169-
]);
170-
171-
if (is_array($currentReference) || $currentReference instanceof \ArrayAccess) {
172-
if (!isset($currentReference[$part])) {
173-
throw new UnresolvableReferenceException(
174-
"Failed to resolve Reference '$this->_ref' to $this->_to Object: path $jsonPointer does not exist in referenced object."
175-
);
176-
}
177-
$currentReference = $currentReference[$part];
178-
} elseif (is_object($currentReference)) {
179-
if (!isset($currentReference->$part)) {
180-
throw new UnresolvableReferenceException(
181-
"Failed to resolve Reference '$this->_ref' to $this->_to Object: path $jsonPointer does not exist in referenced object."
182-
);
183-
}
184-
$currentReference = $currentReference->$part;
147+
$jsonReference = $this->_jsonReference;
148+
try {
149+
if ($jsonReference->getDocumentUri() === '') {
150+
// TODO type error if resolved object does not match $this->_to ?
151+
return $jsonReference->getJsonPointer()->evaluate($context->getBaseSpec());
152+
}
153+
$file = $context->resolveRelativeUri($jsonReference->getDocumentUri());
154+
// TODO could be a good idea to cache loaded files in current context to avoid loading the same files over and over again
155+
$referencedDocument = $this->fetchReferencedFile($file);
156+
$referencedData = $jsonReference->getJsonPointer()->evaluate($referencedDocument);
157+
158+
/** @var $referencedObject SpecObjectInterface */
159+
$referencedObject = new $this->_to($referencedData);
160+
if ($jsonReference->getJsonPointer()->getPointer() === '') {
161+
$referencedObject->setReferenceContext(new ReferenceContext($referencedObject, $file));
185162
} else {
186-
throw new UnresolvableReferenceException(
187-
"Failed to resolve Reference '$this->_ref' to $this->_to Object: path $jsonPointer does not exist in referenced object."
188-
);
163+
// TODO resolving references recursively does not work as we do not know the base type of the file at this point
164+
// $referencedObject->resolveReferences(new ReferenceContext($referencedObject, $file));
189165
}
190-
}
191166

192-
return $currentReference;
167+
return $referencedObject;
168+
} catch (NonexistentJsonPointerReferenceException $e) {
169+
throw new UnresolvableReferenceException("Failed to resolve Reference '$this->_ref' to $this->_to Object: " . $e->getMessage(), 0, $e);
170+
}
193171
}
194172

195173
/**

0 commit comments

Comments
 (0)