diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0626077..a94487b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: ['8.1', '8.2', '8.3'] + php-version: ['8.1', '8.2', '8.3', '8.4'] steps: - uses: actions/checkout@v1 diff --git a/composer.json b/composer.json index 0782e89..227e861 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,10 @@ { - "name": "phauthentic/error-response", + "name": "phauthentic/problem-details", "type": "library", "require-dev": { + "infection/infection": "^0.29.10", "nyholm/psr7": "^1.8", + "phpmd/phpmd": "^2.15", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.0", "squizlabs/php_codesniffer": "^3.9" @@ -10,12 +12,12 @@ "license": "MIT", "autoload": { "psr-4": { - "Phauthentic\\ErrorResponse\\": "src/" + "Phauthentic\\ProblemDetails\\": "src/" } }, "autoload-dev": { "psr-4": { - "Phauthentic\\ErrorResponse\\Tests\\": "tests/" + "Phauthentic\\ProblemDetails\\Tests\\": "tests/" } }, "authors": [ @@ -31,6 +33,38 @@ }, "config": { "sort-packages": true, - "bin-dir": "bin" + "bin-dir": "bin", + "allow-plugins": { + "infection/extension-installer": true + } + }, + "scripts": { + "test": [ + "phpunit" + ], + "infection": [ + "infection" + ], + "test-coverage": [ + "phpunit --coverage-text" + ], + "test-coverage-html": [ + "phpunit --coverage-html tmp/coverage/" + ], + "cscheck": [ + "phpcs src/ tests/ -s" + ], + "csfix": [ + "phpcbf src/ tests/" + ], + "analyze": [ + "phpstan analyse src/" + ], + "analyse": [ + "phpstan analyse src/" + ], + "phpmd": [ + "bin/phpmd ./src/ text phpmd.xml" + ] } } diff --git a/infection.json5 b/infection.json5 new file mode 100644 index 0000000..91f5a9c --- /dev/null +++ b/infection.json5 @@ -0,0 +1,14 @@ +{ + "$schema": "vendor/infection/infection/resources/schema.json", + "source": { + "directories": [ + "src" + ] + }, + "logs": { + "text": "." + }, + "mutators": { + "@default": true + } +} \ No newline at end of file diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 0000000..5497422 --- /dev/null +++ b/phpmd.xml @@ -0,0 +1,21 @@ + + + + Phauthentic PHPMD rule set + + + + + + + + + + + diff --git a/phpstan.neon b/phpstan.neon index 31f1e85..c308dcf 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,4 +2,3 @@ parameters: level: 8 paths: - src - checkGenericClassInNonGenericObjectType: false diff --git a/src/ErrorResponse.php b/src/ErrorResponse.php index 97a01b1..0b18d6a 100644 --- a/src/ErrorResponse.php +++ b/src/ErrorResponse.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Phauthentic\ErrorResponse; +namespace Phauthentic\ProblemDetails; use InvalidArgumentException; diff --git a/src/ErrorResponseExceptionBasedFactoryInterface.php b/src/ErrorResponseExceptionBasedFactoryInterface.php index b642cc2..6c52891 100644 --- a/src/ErrorResponseExceptionBasedFactoryInterface.php +++ b/src/ErrorResponseExceptionBasedFactoryInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Phauthentic\ErrorResponse; +namespace Phauthentic\ProblemDetails; use Exception; diff --git a/src/ErrorResponseFactory.php b/src/ErrorResponseFactory.php index 5583453..5692bad 100644 --- a/src/ErrorResponseFactory.php +++ b/src/ErrorResponseFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Phauthentic\ErrorResponse; +namespace Phauthentic\ProblemDetails; use Exception; diff --git a/src/ErrorResponseFactoryInterface.php b/src/ErrorResponseFactoryInterface.php index 3b49c51..fd46daf 100644 --- a/src/ErrorResponseFactoryInterface.php +++ b/src/ErrorResponseFactoryInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Phauthentic\ErrorResponse; +namespace Phauthentic\ProblemDetails; use Exception; diff --git a/src/ErrorResponseInterface.php b/src/ErrorResponseInterface.php index c0efd42..434af19 100644 --- a/src/ErrorResponseInterface.php +++ b/src/ErrorResponseInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Phauthentic\ErrorResponse; +namespace Phauthentic\ProblemDetails; /** * ErrorResponseInterface diff --git a/src/ErrorResponseMiddleware.php b/src/ErrorResponseMiddleware.php index 13d8d87..09777be 100644 --- a/src/ErrorResponseMiddleware.php +++ b/src/ErrorResponseMiddleware.php @@ -2,32 +2,51 @@ declare(strict_types=1); -namespace Phauthentic\ErrorResponse; +namespace Phauthentic\ProblemDetails; use Exception; +use JsonException; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +/** + * + */ class ErrorResponseMiddleware implements MiddlewareInterface { /** * @param ResponseFactoryInterface $responseFactory * @param ErrorResponseExceptionBasedFactoryInterface $errorResponseFactory * @param array $exceptionClasses - * @return void + * @param bool $onlyOnJsonRequests */ public function __construct( protected ResponseFactoryInterface $responseFactory, protected ErrorResponseExceptionBasedFactoryInterface $errorResponseFactory, - protected array $exceptionClasses = [Exception::class] + protected array $exceptionClasses = [Exception::class], + protected bool $onlyOnJsonRequests = true ) { } - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { + /** + * {@inheritDoc} + * + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return ResponseInterface + * @throws Exception + */ + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + if (!$this->isJsonRequest($request)) { + return $handler->handle($request); + } + try { return $handler->handle($request); } catch (Exception $exception) { @@ -41,6 +60,12 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } } + protected function isJsonRequest(ServerRequestInterface $request): bool + { + return $this->onlyOnJsonRequests + && in_array('application/json', $request->getHeader('Accept'), true); + } + protected function isAnInterceptableException(Exception $exception): bool { foreach ($this->exceptionClasses as $class) { @@ -52,6 +77,11 @@ protected function isAnInterceptableException(Exception $exception): bool return false; } + /** + * @param ErrorResponseInterface $errorResponse + * @return string + * @throws JsonException + */ protected function errorResponseToJson(ErrorResponseInterface $errorResponse): string { return json_encode($errorResponse->toArray(), JSON_THROW_ON_ERROR); @@ -60,6 +90,7 @@ protected function errorResponseToJson(ErrorResponseInterface $errorResponse): s public function createResponse(ErrorResponseInterface $errorResponse): ResponseInterface { $response = $this->responseFactory->createResponse($errorResponse->getStatus()); + $body = $response->getBody(); $body->write($this->errorResponseToJson($errorResponse)); diff --git a/src/MultiErrorCollectionResponseBuilder.php b/src/MultiErrorCollectionResponseBuilder.php index 945092f..1088007 100644 --- a/src/MultiErrorCollectionResponseBuilder.php +++ b/src/MultiErrorCollectionResponseBuilder.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Phauthentic\ErrorResponse; +namespace Phauthentic\ProblemDetails; use InvalidArgumentException; diff --git a/tests/CustomException.php b/tests/CustomException.php index 8942dee..8573d74 100644 --- a/tests/CustomException.php +++ b/tests/CustomException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Phauthentic\ErrorResponse\Tests; +namespace Phauthentic\ProblemDetails\Tests; use Exception; diff --git a/tests/ErrorResponseFactoryTest.php b/tests/ErrorResponseFactoryTest.php index 6f97a87..5d8a15f 100644 --- a/tests/ErrorResponseFactoryTest.php +++ b/tests/ErrorResponseFactoryTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Phauthentic\ErrorResponse\Tests; +namespace Phauthentic\ProblemDetails\Tests; use Exception; -use Phauthentic\ErrorResponse\ErrorResponse; -use Phauthentic\ErrorResponse\ErrorResponseFactory; -use Phauthentic\ErrorResponse\ErrorResponseInterface; +use Phauthentic\ProblemDetails\ErrorResponse; +use Phauthentic\ProblemDetails\ErrorResponseFactory; +use Phauthentic\ProblemDetails\ErrorResponseInterface; use PHPUnit\Framework\TestCase; class ErrorResponseFactoryTest extends TestCase diff --git a/tests/ErrorResponseMiddlewareTest.php b/tests/ErrorResponseMiddlewareTest.php index c8058f3..44d04da 100644 --- a/tests/ErrorResponseMiddlewareTest.php +++ b/tests/ErrorResponseMiddlewareTest.php @@ -2,19 +2,19 @@ declare(strict_types=1); -namespace Phauthentic\ErrorResponse\Tests; +namespace Phauthentic\ProblemDetails\Tests; use Exception; use Nyholm\Psr7\Factory\Psr17Factory; -use Phauthentic\ErrorResponse\ErrorResponseFactory; -use Phauthentic\ErrorResponse\ErrorResponseMiddleware; +use Phauthentic\ProblemDetails\ErrorResponseFactory; +use Phauthentic\ProblemDetails\ErrorResponseMiddleware; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; class ErrorResponseMiddlewareTest extends TestCase { - public function testProcessErrorResponse(): void + public function testThatExceptionInHandlerWillReturnErrorResponse(): void { $middleware = new ErrorResponseMiddleware( new Psr17Factory(), @@ -38,6 +38,7 @@ public function testProcessErrorResponse(): void (string)$response->getBody() ); $this->assertSame(500, $response->getStatusCode()); + $this->assertSame('application/problem+json', $response->getHeaderLine('Content-Type')); } public function testProcessHandlesUnhandledException(): void diff --git a/tests/ErrorResponseTest.php b/tests/ErrorResponseTest.php index 14501cc..1355a8a 100644 --- a/tests/ErrorResponseTest.php +++ b/tests/ErrorResponseTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Phauthentic\ErrorResponse\Tests; +namespace Phauthentic\ProblemDetails\Tests; use InvalidArgumentException; -use Phauthentic\ErrorResponse\ErrorResponse; +use Phauthentic\ProblemDetails\ErrorResponse; use PHPUnit\Framework\TestCase; class ErrorResponseTest extends TestCase