Skip to content

Commit 1617745

Browse files
committed
resolve relative URLs
1 parent 290389b commit 1617745

4 files changed

Lines changed: 136 additions & 20 deletions

File tree

src/Reader.php

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public static function readFromJsonFile(string $fileName, string $baseType = Ope
6565
{
6666
$spec = static::readFromJson(file_get_contents($fileName), $baseType);
6767
if ($resolveReferences) {
68-
$spec->resolveReferences(new ReferenceContext($spec, static::fileName2Uri($fileName)));
68+
$spec->resolveReferences(new ReferenceContext($spec, $fileName));
6969
}
7070
return $spec;
7171
}
@@ -89,19 +89,8 @@ public static function readFromYamlFile(string $fileName, string $baseType = Ope
8989
{
9090
$spec = static::readFromYaml(file_get_contents($fileName), $baseType);
9191
if ($resolveReferences) {
92-
$spec->resolveReferences(new ReferenceContext($spec, static::fileName2Uri($fileName)));
92+
$spec->resolveReferences(new ReferenceContext($spec, $fileName));
9393
}
9494
return $spec;
9595
}
96-
97-
private static function fileName2Uri($fileName)
98-
{
99-
if (strpos($fileName, '://') !== false) {
100-
return $fileName;
101-
}
102-
if (strncmp($fileName, '/', 1) === 0) {
103-
return "file://$fileName";
104-
}
105-
throw new UnresolvableReferenceException('Can not resolve references for a specification given as a relative path.');
106-
}
10796
}

src/ReferenceContext.php

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,30 @@ class ReferenceContext
2323
*/
2424
private $_uri;
2525

26-
26+
/**
27+
* ReferenceContext constructor.
28+
* @param SpecObjectInterface $base the base object of the spec.
29+
* @param string $uri the URI to the base object.
30+
* @throws UnresolvableReferenceException in case an invalid or non-absolute URI is provided.
31+
*/
2732
public function __construct(SpecObjectInterface $base, string $uri)
2833
{
2934
$this->_baseSpec = $base;
30-
$this->_uri = $uri;
35+
$this->_uri = $this->normalizeUri($uri);
36+
}
37+
38+
/**
39+
* @throws UnresolvableReferenceException in case an invalid or non-absolute URI is provided.
40+
*/
41+
private function normalizeUri($uri)
42+
{
43+
if (strpos($uri, '://') !== false) {
44+
return $uri;
45+
}
46+
if (strncmp($uri, '/', 1) === 0) {
47+
return "file://$uri";
48+
}
49+
throw new UnresolvableReferenceException('Can not resolve references for a specification given as a relative path.');
3150
}
3251

3352
/**
@@ -55,11 +74,43 @@ public function getUri(): string
5574
public function resolveRelativeUri(string $uri): string
5675
{
5776
$parts = parse_url($uri);
58-
if (!isset($parts['scheme'], $parts['host'])) {
59-
// TODO resolve relative URL
60-
throw new UnresolvableReferenceException('Relative URLs are currently not supported in Reference.');
77+
if (isset($parts['scheme'])) {
78+
// absolute URL
79+
return $uri;
6180
}
6281

63-
return $uri;
82+
$baseUri = $this->getUri();
83+
if (strncmp($baseUri, 'file://', 7) === 0) {
84+
85+
if (isset($parts['path'][0]) && $parts['path'][0] === '/') {
86+
// absolute path
87+
return 'file://' . $parts['path'];
88+
}
89+
if (isset($parts['path'])) {
90+
// relative path
91+
return dirname($baseUri) . '/' . $parts['path'];
92+
}
93+
94+
throw new UnresolvableReferenceException("Invalid URI: '$uri'");
95+
}
96+
97+
$baseParts = parse_url($baseUri);
98+
$absoluteUri = implode('', [
99+
$baseParts['scheme'],
100+
'://',
101+
isset($baseParts['username']) ? $baseParts['username'] . (
102+
isset($baseParts['password']) ? ':' . $baseParts['password'] : ''
103+
) . '@' : '',
104+
$baseParts['host'] ?? '',
105+
isset($baseParts['port']) ? ':' . $baseParts['port'] : '',
106+
]);
107+
if (isset($parts['path'][0]) && $parts['path'][0] === '/') {
108+
$absoluteUri .= $parts['path'];
109+
} else if (isset($parts['path'])) {
110+
$absoluteUri .= rtrim(dirname($baseParts['path'] ?? ''), '/') . '/' . $parts['path'];
111+
}
112+
return $absoluteUri
113+
. (isset($parts['query']) ? '?' . $parts['query'] : '')
114+
. (isset($parts['fragment']) ? '#' . $parts['fragment'] : '');
64115
}
65116
}

tests/ReferenceContextTest.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
4+
use cebe\openapi\ReferenceContext;
5+
use cebe\openapi\spec\OpenApi;
6+
7+
class ReferenceContextTest extends \PHPUnit\Framework\TestCase
8+
{
9+
public function uriProvider()
10+
{
11+
$data = [
12+
[
13+
'https://example.com/openapi.yaml', // base URI
14+
'definitions.yaml', // referenced URI
15+
'https://example.com/definitions.yaml', // expected result
16+
],
17+
[
18+
'https://example.com/openapi.yaml', // base URI
19+
'/definitions.yaml', // referenced URI
20+
'https://example.com/definitions.yaml', // expected result
21+
],
22+
[
23+
'https://example.com/api/openapi.yaml', // base URI
24+
'definitions.yaml', // referenced URI
25+
'https://example.com/api/definitions.yaml', // expected result
26+
],
27+
[
28+
'https://example.com/api/openapi.yaml', // base URI
29+
'/definitions.yaml', // referenced URI
30+
'https://example.com/definitions.yaml', // expected result
31+
],
32+
[
33+
'https://example.com/api/openapi.yaml', // base URI
34+
'../definitions.yaml', // referenced URI
35+
'https://example.com/api/../definitions.yaml', // expected result
36+
],
37+
[
38+
'/var/www/openapi.yaml', // base URI
39+
'definitions.yaml', // referenced URI
40+
'file:///var/www/definitions.yaml', // expected result
41+
],
42+
[
43+
'/var/www/openapi.yaml', // base URI
44+
'/var/definitions.yaml', // referenced URI
45+
'file:///var/definitions.yaml', // expected result
46+
],
47+
48+
];
49+
50+
// absolute URLs should not be changed
51+
foreach(array_unique(array_map('current', $data)) as $url) {
52+
$data[] = [
53+
$url,
54+
'file:///var/www/definitions.yaml',
55+
'file:///var/www/definitions.yaml',
56+
];
57+
$data[] = [
58+
$url,
59+
'https://example.com/definitions.yaml',
60+
'https://example.com/definitions.yaml',
61+
];
62+
}
63+
64+
return $data;
65+
}
66+
67+
/**
68+
* @dataProvider uriProvider
69+
*/
70+
public function testResolveUri($baseUri, $referencedUri, $expected)
71+
{
72+
$context = new ReferenceContext(new OpenApi([]), $baseUri);
73+
$this->assertEquals($expected, $context->resolveRelativeUri($referencedUri));
74+
}
75+
76+
}

tests/spec/ReferenceTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public function testResolveFile()
145145

146146
public function testResolveFileHttp()
147147
{
148-
$file = 'https://raw.githubusercontent.com/cebe/php-openapi/master/tests/spec/data/reference/base.yaml';
148+
$file = 'https://raw.githubusercontent.com/cebe/php-openapi/290389bbd337cf4d70ecedfd3a3d886715e19552/tests/spec/data/reference/base.yaml';
149149
/** @var $openapi OpenApi */
150150
$openapi = Reader::readFromYaml(str_replace('##ABSOLUTEPATH##', 'https://' . dirname($file), file_get_contents($file)));
151151

0 commit comments

Comments
 (0)