Skip to content

Commit 405249b

Browse files
committed
Add support of (remote) absolute urls containing OpenApi specs
1 parent 4c0f623 commit 405249b

5 files changed

Lines changed: 142 additions & 8 deletions

File tree

src/OpenApiSpecFactory.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Osteel\OpenApi\Testing;
6+
7+
use cebe\openapi\Reader;
8+
use cebe\openapi\SpecObjectInterface;
9+
10+
final class OpenApiSpecFactory implements OpenApiSpecFactoryInterface
11+
{
12+
/** @inheritDoc */
13+
public function readFromJsonFile(string $fileName): SpecObjectInterface
14+
{
15+
return Reader::readFromJsonFile($fileName);
16+
}
17+
18+
/** @inheritDoc */
19+
public function readFromYamlFile(string $fileName): SpecObjectInterface
20+
{
21+
return Reader::readFromYamlFile($fileName);
22+
}
23+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Osteel\OpenApi\Testing;
6+
7+
use cebe\openapi\spec\OpenApi;
8+
use cebe\openapi\SpecObjectInterface;
9+
10+
interface OpenApiSpecFactoryInterface
11+
{
12+
/** @return SpecObjectInterface|OpenApi */
13+
public function readFromJsonFile(string $fileName): SpecObjectInterface;
14+
15+
/** @return SpecObjectInterface|OpenApi */
16+
public function readFromYamlFile(string $fileName): SpecObjectInterface;
17+
}

src/ValidatorBuilder.php

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Osteel\OpenApi\Testing;
66

7+
use Closure;
78
use InvalidArgumentException;
89
use League\OpenAPIValidation\PSR7\ValidatorBuilder as BaseValidatorBuilder;
910
use Osteel\OpenApi\Testing\Adapters\MessageAdapterInterface;
@@ -16,6 +17,9 @@
1617
*/
1718
final class ValidatorBuilder implements ValidatorBuilderInterface
1819
{
20+
/** @var ?Closure():OpenApiSpecFactoryInterface */
21+
private static ?Closure $openApiSpecFactoryResolver = null;
22+
1923
/** @var class-string<MessageAdapterInterface> */
2024
private string $adapter = HttpFoundationAdapter::class;
2125

@@ -33,9 +37,7 @@ public function __construct(private BaseValidatorBuilder $validatorBuilder)
3337
*/
3438
public static function fromYaml(string $definition): ValidatorBuilderInterface
3539
{
36-
$method = is_file($definition) ? 'fromYamlFile' : 'fromYaml';
37-
38-
return self::fromMethod($method, $definition);
40+
return self::fromYamlFile($definition);
3941
}
4042

4143
/**
@@ -45,9 +47,7 @@ public static function fromYaml(string $definition): ValidatorBuilderInterface
4547
*/
4648
public static function fromJson(string $definition): ValidatorBuilderInterface
4749
{
48-
$method = is_file($definition) ? 'fromJsonFile' : 'fromJson';
49-
50-
return self::fromMethod($method, $definition);
50+
return self::fromJsonFile($definition);
5151
}
5252

5353
/**
@@ -57,7 +57,7 @@ public static function fromJson(string $definition): ValidatorBuilderInterface
5757
*/
5858
public static function fromYamlFile(string $definition): ValidatorBuilderInterface
5959
{
60-
return self::fromMethod('fromYamlFile', $definition);
60+
return self::fromMethod(self::determineMethod($definition, 'yaml'), $definition);
6161
}
6262

6363
/**
@@ -67,7 +67,7 @@ public static function fromYamlFile(string $definition): ValidatorBuilderInterfa
6767
*/
6868
public static function fromJsonFile(string $definition): ValidatorBuilderInterface
6969
{
70-
return self::fromMethod('fromJsonFile', $definition);
70+
return self::fromMethod(self::determineMethod($definition, 'json'), $definition);
7171
}
7272

7373
/**
@@ -98,11 +98,51 @@ public static function fromJsonString(string $definition): ValidatorBuilderInter
9898
*/
9999
private static function fromMethod(string $method, string $definition): ValidatorBuilderInterface
100100
{
101+
$openApiFactory = self::getOpenApiSpecFactory();
102+
103+
$definition = match ($method) {
104+
'fromJsonSchema' => $openApiFactory->readFromJsonFile($definition),
105+
'fromYamlSchema' => $openApiFactory->readFromYamlFile($definition),
106+
default => $definition,
107+
};
108+
109+
if (in_array($method, ['fromJsonSchema', 'fromYamlSchema'], true)) {
110+
$method = 'fromSchema';
111+
}
112+
101113
$builder = (new BaseValidatorBuilder())->{$method}($definition);
102114

103115
return new ValidatorBuilder($builder);
104116
}
105117

118+
private static function determineMethod(string $definition, string $format): string
119+
{
120+
if (filter_var($definition, FILTER_VALIDATE_URL) && in_array(parse_url($definition, PHP_URL_SCHEME), ['http', 'https'], true)) {
121+
return sprintf('from%sSchema', ucfirst($format));
122+
}
123+
124+
if (is_file($definition)) {
125+
return sprintf('from%sFile', ucfirst($format));
126+
}
127+
128+
return sprintf('from%s', ucfirst($format));
129+
}
130+
131+
/** @param ?Closure():OpenApiSpecFactoryInterface $resolver */
132+
public static function setOpenApiSpecFactoryResolver(?Closure $resolver = null): void
133+
{
134+
self::$openApiSpecFactoryResolver = $resolver;
135+
}
136+
137+
public static function getOpenApiSpecFactory(): OpenApiSpecFactoryInterface
138+
{
139+
if (self::$openApiSpecFactoryResolver === null) {
140+
return new OpenApiSpecFactory();
141+
}
142+
143+
return (self::$openApiSpecFactoryResolver)();
144+
}
145+
106146
/** @inheritDoc */
107147
public function setCache(object $cache): ValidatorBuilderInterface
108148
{

tests/OpenApiSpecFactoryTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Osteel\OpenApi\Testing\Tests;
6+
7+
use cebe\openapi\spec\OpenApi;
8+
use cebe\openapi\SpecObjectInterface;
9+
use Osteel\OpenApi\Testing\OpenApiSpecFactory;
10+
11+
class OpenApiSpecFactoryTest extends TestCase
12+
{
13+
public function test_it_creates_an_open_api_spec_from_yaml_file(): void
14+
{
15+
$result = (new OpenApiSpecFactory())->readFromYamlFile(self::$yamlDefinition);
16+
17+
$this->assertInstanceOf(SpecObjectInterface::class, $result);
18+
$this->assertInstanceOf(OpenApi::class, $result);
19+
}
20+
21+
public function test_it_creates_an_open_api_spec_from_json_file(): void
22+
{
23+
$result = (new OpenApiSpecFactory())->readFromJsonFile(self::$jsonDefinition);
24+
25+
$this->assertInstanceOf(SpecObjectInterface::class, $result);
26+
$this->assertInstanceOf(OpenApi::class, $result);
27+
}
28+
}

tests/ValidatorBuilderTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
namespace Osteel\OpenApi\Testing\Tests;
66

7+
use cebe\openapi\Reader;
78
use InvalidArgumentException;
89
use Osteel\OpenApi\Testing\Adapters\MessageAdapterInterface;
910
use Osteel\OpenApi\Testing\Cache\CacheAdapterInterface;
11+
use Osteel\OpenApi\Testing\OpenApiSpecFactoryInterface;
1012
use Osteel\OpenApi\Testing\Validator;
1113
use Osteel\OpenApi\Testing\ValidatorBuilder;
1214
use stdClass;
@@ -42,6 +44,30 @@ public function test_it_builds_a_validator(string $method, string $definition)
4244
$this->assertTrue($result->get($response, static::PATH));
4345
}
4446

47+
public function test_it_builds_a_validator_given_a_absolute_url(): void
48+
{
49+
$url = 'https://foobar.localhost/openapi.yaml';
50+
51+
$openApiFactory = $this->createMock(OpenApiSpecFactoryInterface::class);
52+
$openApiFactory->expects($this->once())
53+
->method('readFromYamlFile')
54+
->with($url)
55+
->willReturn(Reader::readFromYamlFile(self::$yamlDefinition));
56+
57+
ValidatorBuilder::setOpenApiSpecFactoryResolver(fn () => $openApiFactory);
58+
59+
$result = ValidatorBuilder::fromYaml($url)->getValidator();
60+
61+
$this->assertInstanceOf(Validator::class, $result);
62+
63+
$request = $this->httpFoundationRequest(static::PATH, 'get', ['foo' => 'bar']);
64+
$response = $this->httpFoundationResponse(['foo' => 'bar']);
65+
66+
// Validate a request and a response to make sure the definition was correctly parsed.
67+
$this->assertTrue($result->get($request, static::PATH));
68+
$this->assertTrue($result->get($response, static::PATH));
69+
}
70+
4571
public function test_it_does_not_set_the_adapter_because_its_type_is_invalid()
4672
{
4773
$this->expectException(InvalidArgumentException::class);

0 commit comments

Comments
 (0)