diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 0641794..348daa6 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -8,12 +8,14 @@ return (new PhpCsFixer\Config()) ->setRules([ - '@PSR2' => true, + '@PSR12' => true, + 'blank_line_after_opening_tag' => false, '@DoctrineAnnotation' => true, 'array_syntax' => ['syntax' => 'short'], 'no_unused_imports' => true, + 'ordered_imports' => true, 'blank_line_before_statement' => ['statements' => ['return']], - 'visibility_required' => true, + 'modifier_keywords' => true, 'cast_spaces' => ['space' => 'single'], 'concat_space' => ['spacing' => 'one'], 'type_declaration_spaces' => true, @@ -40,10 +42,11 @@ 'trim_array_spaces' => true, 'single_space_around_construct' => true, 'single_line_comment_spacing' => true, - 'fully_qualified_strict_types' => true, + 'fully_qualified_strict_types' => ['import_symbols' => true, 'leading_backslash_in_global_namespace' => true], 'global_namespace_import' => ['import_classes' => false, 'import_constants' => null, 'import_functions' => null], 'nullable_type_declaration_for_default_null_value' => true, + 'attribute_empty_parentheses' => ['use_parentheses' => false], 'no_empty_phpdoc' => true, 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true], @@ -64,6 +67,10 @@ 'phpdoc_no_useless_inheritdoc' => true, 'phpdoc_no_empty_return' => true, 'phpdoc_no_alias_tag' => true, + 'phpdoc_param_order' => true, + 'multiline_comment_opening_closing' => true, + + 'php_unit_attributes' => true, ]) ->setFinder($finder) ; diff --git a/rector.php b/rector.php index ad66646..efd89e3 100644 --- a/rector.php +++ b/rector.php @@ -3,18 +3,22 @@ use Rector\CodeQuality\Rector\Attribute\SortAttributeNamedArgsRector; use Rector\CodeQuality\Rector\FuncCall\SortCallLikeNamedArgsRector; use Rector\Config\RectorConfig; -use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector; use Rector\ValueObject\PhpVersion; return RectorConfig::configure() - ->withRules([ - TypedPropertyFromStrictConstructorRector::class - ]) - ->withPreparedSets(true, true) + ->withSkipPath(__DIR__ . '/tests/Fixtures') ->withSkip([ // reorders the named arguments of swagger-php attributes, which decides the order // of the generated specifications SortAttributeNamedArgsRector::class, SortCallLikeNamedArgsRector::class, ]) + ->withPreparedSets( + deadCode: true, + codeQuality: true, + codingStyle: true, + typeDeclarations: true, + phpunitCodeQuality: true, + ) + ->withAttributesSets(phpunit: true) ->withPhpVersion(PhpVersion::PHP_82); diff --git a/src/Adapters/AbstractOpenApiResponseVerifier.php b/src/Adapters/AbstractOpenApiResponseVerifier.php index d3ac2df..52982ab 100644 --- a/src/Adapters/AbstractOpenApiResponseVerifier.php +++ b/src/Adapters/AbstractOpenApiResponseVerifier.php @@ -2,6 +2,7 @@ namespace Radebatz\OpenApi\Verifier\Adapters; +use Composer\Autoload\ClassLoader; use OpenApi\Generator; trait AbstractOpenApiResponseVerifier @@ -41,9 +42,9 @@ protected function prepareOpenApiSpecificationLoader(string $srcDir, ?string $sp */ protected function getAppRoot(): string { - $classLoader = (new \ReflectionClass(\Composer\Autoload\ClassLoader::class))->getFileName(); + $classLoader = (new \ReflectionClass(ClassLoader::class))->getFileName(); if (false === $classLoader) { - throw new \RuntimeException('Cannot locate the project root: Composer\'s ClassLoader has no file'); + throw new \RuntimeException("Cannot locate the project root: Composer's ClassLoader has no file"); } return dirname($classLoader, 3); diff --git a/src/Adapters/Laravel/OpenApiResponseVerifier.php b/src/Adapters/Laravel/OpenApiResponseVerifier.php index 2b7833a..f240e64 100644 --- a/src/Adapters/Laravel/OpenApiResponseVerifier.php +++ b/src/Adapters/Laravel/OpenApiResponseVerifier.php @@ -14,7 +14,8 @@ */ trait OpenApiResponseVerifier { - use AbstractOpenApiResponseVerifier, VerifiesOpenApi; + use AbstractOpenApiResponseVerifier; + use VerifiesOpenApi; public function registerOpenApiVerifier(?Application $app = null, ?string $specification = null): void { diff --git a/src/Adapters/Laravel/OpenApiVerifierMiddleware.php b/src/Adapters/Laravel/OpenApiVerifierMiddleware.php index d31c979..e3659f7 100644 --- a/src/Adapters/Laravel/OpenApiVerifierMiddleware.php +++ b/src/Adapters/Laravel/OpenApiVerifierMiddleware.php @@ -47,12 +47,12 @@ public function terminate(Request $request, Response $response): void // matched none, a 404 for example, has none $route = ($request->getRouteResolver())(); $this->verifier->verify($psrRequest, $psrResponse, $route instanceof Route ? $route->uri() : null); - } catch (OpenApiSchemaMismatchException $oasme) { + } catch (OpenApiSchemaMismatchException $openApiSchemaMismatchException) { if ($this->onMismatch instanceof \Closure) { - ($this->onMismatch)($oasme, $psrResponse); + ($this->onMismatch)($openApiSchemaMismatchException, $psrResponse); } - throw $oasme; + throw $openApiSchemaMismatchException; } } } diff --git a/src/Adapters/Psr15/OpenApiVerifierMiddleware.php b/src/Adapters/Psr15/OpenApiVerifierMiddleware.php index f562792..6b15312 100644 --- a/src/Adapters/Psr15/OpenApiVerifierMiddleware.php +++ b/src/Adapters/Psr15/OpenApiVerifierMiddleware.php @@ -32,12 +32,12 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface try { $this->verifier->verify($request, $response); - } catch (OpenApiSchemaMismatchException $oasme) { + } catch (OpenApiSchemaMismatchException $openApiSchemaMismatchException) { if ($this->onMismatch instanceof \Closure) { - ($this->onMismatch)($oasme, $response); + ($this->onMismatch)($openApiSchemaMismatchException, $response); } - throw $oasme; + throw $openApiSchemaMismatchException; } return $response; diff --git a/src/Adapters/Slim/OpenApiResponseVerifier.php b/src/Adapters/Slim/OpenApiResponseVerifier.php index f5522c7..59a1410 100644 --- a/src/Adapters/Slim/OpenApiResponseVerifier.php +++ b/src/Adapters/Slim/OpenApiResponseVerifier.php @@ -11,7 +11,8 @@ trait OpenApiResponseVerifier { - use AbstractOpenApiResponseVerifier, VerifiesOpenApi; + use AbstractOpenApiResponseVerifier; + use VerifiesOpenApi; /** * @template TContainer of \Psr\Container\ContainerInterface|null diff --git a/src/VerifiesOpenApi.php b/src/VerifiesOpenApi.php index dd01269..0f44b29 100644 --- a/src/VerifiesOpenApi.php +++ b/src/VerifiesOpenApi.php @@ -3,8 +3,8 @@ namespace Radebatz\OpenApi\Verifier; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; trait VerifiesOpenApi { diff --git a/tests/Adapters/AbstractOpenApiResponseVerifierTest.php b/tests/Adapters/AbstractOpenApiResponseVerifierTest.php index ed54b90..4be77ce 100644 --- a/tests/Adapters/AbstractOpenApiResponseVerifierTest.php +++ b/tests/Adapters/AbstractOpenApiResponseVerifierTest.php @@ -5,9 +5,10 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Radebatz\OpenApi\Verifier\Adapters\AbstractOpenApiResponseVerifier; +use Radebatz\OpenApi\Verifier\OpenApiSpecificationLoader; use Radebatz\OpenApi\Verifier\VerifiesOpenApi; -class AbstractOpenApiResponseVerifierTest extends TestCase +final class AbstractOpenApiResponseVerifierTest extends TestCase { use AbstractOpenApiResponseVerifier; use VerifiesOpenApi; @@ -32,7 +33,7 @@ protected function getAppRoot(): string protected function assertVerifiesUsers(): void { - $this->assertNotNull($this->getOpenApiSpecificationLoader()); + $this->assertInstanceOf(OpenApiSpecificationLoader::class, $this->getOpenApiSpecificationLoader()); $this->assertTrue($this->verifyOpenApiResponseBody( 'get', '/users', diff --git a/tests/Adapters/ClientAdapterTest.php b/tests/Adapters/ClientAdapterTest.php index 73ae048..952c87c 100644 --- a/tests/Adapters/ClientAdapterTest.php +++ b/tests/Adapters/ClientAdapterTest.php @@ -16,7 +16,7 @@ use Radebatz\OpenApi\Verifier\OpenApiSchemaMismatchException; use Radebatz\OpenApi\Verifier\OpenApiVerifier; -class ClientAdapterTest extends TestCase +final class ClientAdapterTest extends TestCase { protected function verifier(): OpenApiVerifier { @@ -45,10 +45,10 @@ protected function send(callable $send, string $body, ?string $error): void try { /** @var ResponseInterface $response */ $response = $send(); - } catch (OpenApiSchemaMismatchException $oasme) { - $this->assertStringContainsString($error, (string) $oasme->getErrorSummary()); + } catch (OpenApiSchemaMismatchException $openApiSchemaMismatchException) { + $this->assertStringContainsString($error, (string) $openApiSchemaMismatchException->getErrorSummary()); - throw $oasme; + throw $openApiSchemaMismatchException; } // verifying must leave the body for the caller to read @@ -61,9 +61,10 @@ public function guzzleMiddleware(string $path, string $body, ?string $error): vo { $stack = HandlerStack::create(new MockHandler([new Response(200, ['Content-Type' => 'application/json'], $body)])); $stack->push(new OpenApiVerifierMiddleware($this->verifier()), 'openapi-verifier'); + $client = new Client(['handler' => $stack, 'base_uri' => 'http://localhost']); - $this->send(fn () => $client->get($path), $body, $error); + $this->send(fn (): ResponseInterface => $client->get($path), $body, $error); } #[Test] @@ -72,6 +73,7 @@ public function guzzleMiddlewareAsync(string $path, string $body, ?string $error { $stack = HandlerStack::create(new MockHandler([new Response(200, ['Content-Type' => 'application/json'], $body)])); $stack->push(new OpenApiVerifierMiddleware($this->verifier()), 'openapi-verifier'); + $client = new Client(['handler' => $stack, 'base_uri' => 'http://localhost']); $this->send(fn () => $client->getAsync($path)->wait(), $body, $error); @@ -84,6 +86,6 @@ public function psr18Client(string $path, string $body, ?string $error): void $inner = new Client(['handler' => HandlerStack::create(new MockHandler([new Response(200, ['Content-Type' => 'application/json'], $body)]))]); $client = new OpenApiVerifyingClient($inner, $this->verifier()); - $this->send(fn () => $client->sendRequest(new Request('GET', 'http://localhost' . $path)), $body, $error); + $this->send(fn (): ResponseInterface => $client->sendRequest(new Request('GET', 'http://localhost' . $path)), $body, $error); } } diff --git a/tests/Adapters/LaravelAdapterTest.php b/tests/Adapters/LaravelAdapterTest.php index b2a2d32..464f71f 100644 --- a/tests/Adapters/LaravelAdapterTest.php +++ b/tests/Adapters/LaravelAdapterTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\Attributes\Test; use Radebatz\OpenApi\Verifier\Adapters\Laravel\OpenApiResponseVerifier; -class LaravelAdapterTest extends TestCase +final class LaravelAdapterTest extends TestCase { use OpenApiResponseVerifier; @@ -25,7 +25,7 @@ protected function setUp(): void public function passVerificationUsers(): void { $this->app - ->get('router')->get('/users', function () { + ->get('router')->get('/users', function (): string { return '{"data":[{"id":1,"name":"joe","email":"joe@cool.com"}]}'; }); @@ -38,7 +38,7 @@ public function passVerificationUsers(): void public function failVerificationUsers(): void { $this->app - ->get('router')->get('/users', function () { + ->get('router')->get('/users', function (): string { return '{"data":[{"id":1,"email":"joe@cool.com"}]}'; }); @@ -51,7 +51,7 @@ public function failVerificationUsers(): void public function passVerificationUser(): void { $this->app - ->get('router')->get('/user/{id}', function () { + ->get('router')->get('/user/{id}', function (): string { return '{"data":{"id":1,"name":"joe","email":"joe@cool.com"}}'; }); @@ -64,7 +64,7 @@ public function passVerificationUser(): void public function failVerificationUser(): void { $this->app - ->get('router')->get('/user/{id}', function () { + ->get('router')->get('/user/{id}', function (): string { return '{"data":{"id":1,"name":"joe"}}'; }); diff --git a/tests/Adapters/Psr15AdapterTest.php b/tests/Adapters/Psr15AdapterTest.php index 7e452c3..acf58e2 100644 --- a/tests/Adapters/Psr15AdapterTest.php +++ b/tests/Adapters/Psr15AdapterTest.php @@ -13,7 +13,7 @@ use Radebatz\OpenApi\Verifier\OpenApiSchemaMismatchException; use Radebatz\OpenApi\Verifier\OpenApiVerifier; -class Psr15AdapterTest extends TestCase +final class Psr15AdapterTest extends TestCase { protected function handle(string $body, ?\Closure $onMismatch = null): ResponseInterface { @@ -22,7 +22,7 @@ protected function handle(string $body, ?\Closure $onMismatch = null): ResponseI $onMismatch ); - $handler = new class($body) implements RequestHandlerInterface { + $handler = new class ($body) implements RequestHandlerInterface { public function __construct(private readonly string $body) { } diff --git a/tests/Adapters/SlimAdapterTest.php b/tests/Adapters/SlimAdapterTest.php index 9c7118f..8826961 100644 --- a/tests/Adapters/SlimAdapterTest.php +++ b/tests/Adapters/SlimAdapterTest.php @@ -12,7 +12,7 @@ use Radebatz\OpenApi\Verifier\Adapters\Slim\OpenApiResponseVerifier; use Slim\Factory\AppFactory; -class SlimAdapterTest extends TestCase +final class SlimAdapterTest extends TestCase { use OpenApiResponseVerifier; @@ -21,7 +21,7 @@ public function passVerificationUsers(): void { $response = $this->runApp('GET', '/users', valid: true); - $this->assertEquals(200, $response->getStatusCode()); + $this->assertSame(200, $response->getStatusCode()); } #[Test] @@ -38,7 +38,7 @@ public function passVerificationUser(): void { $response = $this->runApp('GET', '/user/1', valid: true); - $this->assertEquals(200, $response->getStatusCode()); + $this->assertSame(200, $response->getStatusCode()); } #[Test] @@ -68,23 +68,23 @@ protected function runApp(string $requestMethod, string $requestUri, bool $valid // register test route as we do not have an actual app... if ($valid) { - $app->get('/users', function (RequestInterface $request, ResponseInterface $response) { + $app->get('/users', function (RequestInterface $request, ResponseInterface $response): ResponseInterface { $response->getBody()->write('{"data":[{"id":1,"name":"joe","email":"joe@cool.com"}]}'); return $response; }); - $app->get('/user/{id}', function (RequestInterface $request, ResponseInterface $response) { + $app->get('/user/{id}', function (RequestInterface $request, ResponseInterface $response): ResponseInterface { $response->getBody()->write('{"data":{"id":1,"name":"joe","email":"joe@cool.com"}}'); return $response; }); } else { - $app->get('/users', function (RequestInterface $request, ResponseInterface $response) { + $app->get('/users', function (RequestInterface $request, ResponseInterface $response): ResponseInterface { $response->getBody()->write('{"data":[{"id":1,"email":"joe@cool.com"}]}'); return $response; }); - $app->get('/user/{id}', function (RequestInterface $request, ResponseInterface $response) { + $app->get('/user/{id}', function (RequestInterface $request, ResponseInterface $response): ResponseInterface { $response->getBody()->write('{"data":{"id":1,"name":"joe"}}'); return $response; diff --git a/tests/OpenApiSchemaMismatchExceptionTest.php b/tests/OpenApiSchemaMismatchExceptionTest.php index 69f2ebe..ea15cc9 100644 --- a/tests/OpenApiSchemaMismatchExceptionTest.php +++ b/tests/OpenApiSchemaMismatchExceptionTest.php @@ -8,7 +8,7 @@ use Radebatz\OpenApi\Verifier\OpenApiSchemaMismatchException; use Radebatz\OpenApi\Verifier\Schema\SchemaError; -class OpenApiSchemaMismatchExceptionTest extends TestCase +final class OpenApiSchemaMismatchExceptionTest extends TestCase { #[Test] public function getErrorsReturnsSetData(): void diff --git a/tests/OpenApiSpecificationLoaderTest.php b/tests/OpenApiSpecificationLoaderTest.php index a83b9e1..ce496c5 100644 --- a/tests/OpenApiSpecificationLoaderTest.php +++ b/tests/OpenApiSpecificationLoaderTest.php @@ -7,22 +7,17 @@ use PHPUnit\Framework\TestCase; use Radebatz\OpenApi\Verifier\OpenApiSpecificationLoader; -class OpenApiSpecificationLoaderTest extends TestCase +final class OpenApiSpecificationLoaderTest extends TestCase { - /** - * @return iterable> - */ - public static function specifications(): iterable + public static function specifications(): \Iterator { - return [ - [__DIR__ . '/specifications/users.json', true], - [__DIR__ . '/specifications/users.yaml', true], - [__DIR__ . '/specifications/nope.json', false], - [__DIR__ . '/specifications/nope.yaml', false], - [__DIR__ . '/specifications/nope.yml', false], - [new \stdClass(), true], - [[], false], - ]; + yield [__DIR__ . '/specifications/users.json', true]; + yield [__DIR__ . '/specifications/users.yaml', true]; + yield [__DIR__ . '/specifications/nope.json', false]; + yield [__DIR__ . '/specifications/nope.yaml', false]; + yield [__DIR__ . '/specifications/nope.yml', false]; + yield [new \stdClass(), true]; + yield [[], false]; } #[Test] @@ -39,19 +34,14 @@ public function loadSpecification(mixed $filename, bool $valid): void $this->assertSame('3.0.0', $specificationLoader->getOpenApiVersion()); } - /** - * @return iterable> - */ - public static function responseSchemas(): iterable + public static function responseSchemas(): \Iterator { - return [ - 'ucmethod-json-ok' => [__DIR__ . '/specifications/users.json', 'GET', '/users', 200, true], - 'lcmethod-json-ok' =>[__DIR__ . '/specifications/users.json', 'get', '/users', 200, true], - 'yaml-ok' => [__DIR__ . '/specifications/users.yaml', 'get', '/users', 200, true], - 'path-no-match' => [__DIR__ . '/specifications/users.yaml', 'get', '/foo', 200, false], - 'status-no-match' => [__DIR__ . '/specifications/users.json', 'get', '/users', 404, false], - 'status-no-schema' => [__DIR__ . '/specifications/users.yaml', 'get', '/users', 401, false], - ]; + yield 'ucmethod-json-ok' => [__DIR__ . '/specifications/users.json', 'GET', '/users', 200, true]; + yield 'lcmethod-json-ok' => [__DIR__ . '/specifications/users.json', 'get', '/users', 200, true]; + yield 'yaml-ok' => [__DIR__ . '/specifications/users.yaml', 'get', '/users', 200, true]; + yield 'path-no-match' => [__DIR__ . '/specifications/users.yaml', 'get', '/foo', 200, false]; + yield 'status-no-match' => [__DIR__ . '/specifications/users.json', 'get', '/users', 404, false]; + yield 'status-no-schema' => [__DIR__ . '/specifications/users.yaml', 'get', '/users', 401, false]; } #[Test] @@ -63,7 +53,7 @@ public function getResponseSchemaFor(string $specification, string $method, stri $schema = $specificationLoader->getResponseSchemaFor($method, $path, $statusCode); if ($valid) { - $this->assertNotNull($schema); + $this->assertInstanceOf(\stdClass::class, $schema); // carries the components, so its references resolve $this->assertObjectHasProperty('components', $schema); } else { diff --git a/tests/OpenApiVerifierTest.php b/tests/OpenApiVerifierTest.php index c0b2621..d4351d1 100644 --- a/tests/OpenApiVerifierTest.php +++ b/tests/OpenApiVerifierTest.php @@ -12,7 +12,7 @@ use Radebatz\OpenApi\Verifier\Schema\SchemaError; use Radebatz\OpenApi\Verifier\Schema\SchemaValidator; -class OpenApiVerifierTest extends TestCase +final class OpenApiVerifierTest extends TestCase { /** * @param array $schema @@ -168,7 +168,7 @@ public function translationLeavesTheSpecificationAlone(): void #[Test] public function aValidatorGetsTheSchemaAsWrittenAndTheVersion(): void { - $validator = new class implements SchemaValidator { + $validator = new class () implements SchemaValidator { public ?string $version = null; public ?\stdClass $schema = null; @@ -190,8 +190,8 @@ public function validate(mixed $data, \stdClass $schema, string $openApiVersion) try { OpenApiVerifier::fromSpecification($specification, $validator)->verifyResponseBody('get', '/thing', 200, '"x"'); $this->fail('No exception thrown'); - } catch (OpenApiSchemaMismatchException $oasme) { - $this->assertSame('custom', $oasme->getErrors()[0]->keyword); + } catch (OpenApiSchemaMismatchException $openApiSchemaMismatchException) { + $this->assertSame('custom', $openApiSchemaMismatchException->getErrors()[0]->keyword); } $this->assertSame('3.0.3', $validator->version); diff --git a/tests/VerifiesOpenApiTest.php b/tests/VerifiesOpenApiTest.php index 059c54e..3b36736 100644 --- a/tests/VerifiesOpenApiTest.php +++ b/tests/VerifiesOpenApiTest.php @@ -11,23 +11,18 @@ use Radebatz\OpenApi\Verifier\OpenApiSpecificationLoader; use Radebatz\OpenApi\Verifier\VerifiesOpenApi; -class VerifiesOpenApiTest extends TestCase +final class VerifiesOpenApiTest extends TestCase { use VerifiesOpenApi; - /** - * @return iterable> - */ - public static function responses(): iterable + public static function responses(): \Iterator { - return [ - 'users-invalid-json' => ['get', '/users', 200, 'xxx', false, false], - 'invalid-path' => ['get', '/xxxxx', 200, 'xxx', true, false], - 'users-no-schema-invalid-json' => ['get', '/users', 401, 'xxx', true, false], - 'users-verified-bad' => ['get', '/users', 200, '{"data":[{}]}', false, true], - 'users-verified-ok' => ['get', '/users', 200, '{"data":[{"id":1,"name":"joe","email":"joe@cool.com"}]}', true, true], - 'users-verified-nullable-ok' => ['get', '/users', 200, '{"data":[{"id":1,"name":"joe","email":"joe@cool.com", "dob": null}]}', true, true], - ]; + yield 'users-invalid-json' => ['get', '/users', 200, 'xxx', false, false]; + yield 'invalid-path' => ['get', '/xxxxx', 200, 'xxx', true, false]; + yield 'users-no-schema-invalid-json' => ['get', '/users', 401, 'xxx', true, false]; + yield 'users-verified-bad' => ['get', '/users', 200, '{"data":[{}]}', false, true]; + yield 'users-verified-ok' => ['get', '/users', 200, '{"data":[{"id":1,"name":"joe","email":"joe@cool.com"}]}', true, true]; + yield 'users-verified-nullable-ok' => ['get', '/users', 200, '{"data":[{"id":1,"name":"joe","email":"joe@cool.com", "dob": null}]}', true, true]; } #[Test] @@ -46,7 +41,7 @@ public function verifyBody(string $method, string $path, int $statusCode, string } } - $this->assertEquals($isVerified, $verified); + $this->assertSame($isVerified, $verified); } #[Test] @@ -69,7 +64,7 @@ public function verifyRequestResponse(): void } /** @inheritdoc */ - protected function getOpenApiSpecificationLoader(): ?OpenApiSpecificationLoader + protected function getOpenApiSpecificationLoader(): OpenApiSpecificationLoader { return new OpenApiSpecificationLoader(__DIR__ . '/specifications/users.yaml'); }