diff --git a/framework/core/src/Admin/AdminServiceProvider.php b/framework/core/src/Admin/AdminServiceProvider.php index 493464ba9..ce7a6b462 100644 --- a/framework/core/src/Admin/AdminServiceProvider.php +++ b/framework/core/src/Admin/AdminServiceProvider.php @@ -16,6 +16,10 @@ use Flarum\Extension\Event\Disabled; use Flarum\Extension\Event\Enabled; use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\Application; +use Flarum\Foundation\ErrorHandling\Registry; +use Flarum\Foundation\ErrorHandling\Reporter; +use Flarum\Foundation\ErrorHandling\ViewRenderer; +use Flarum\Foundation\ErrorHandling\WhoopsRenderer; use Flarum\Foundation\Event\ClearingCache; use Flarum\Frontend\AddLocaleAssets; use Flarum\Frontend\AddTranslations; @@ -51,11 +55,11 @@ class AdminServiceProvider extends AbstractServiceProvider $pipe = new MiddlewarePipe; // All requests should first be piped through our global error handler - if ($app->inDebugMode()) { - $pipe->pipe($app->make(HttpMiddleware\HandleErrorsWithWhoops::class)); - } else { - $pipe->pipe($app->make(HttpMiddleware\HandleErrorsWithView::class)); - } + $pipe->pipe(new HttpMiddleware\HandleErrors( + $app->make(Registry::class), + $app->inDebugMode() ? $app->make(WhoopsRenderer::class) : $app->make(ViewRenderer::class), + $app->tagged(Reporter::class) + )); $pipe->pipe($app->make(HttpMiddleware\ParseJsonBody::class)); $pipe->pipe($app->make(HttpMiddleware\StartSession::class)); diff --git a/framework/core/src/Api/ApiServiceProvider.php b/framework/core/src/Api/ApiServiceProvider.php index dd8eb6cd9..abe53ef91 100644 --- a/framework/core/src/Api/ApiServiceProvider.php +++ b/framework/core/src/Api/ApiServiceProvider.php @@ -20,12 +20,13 @@ use Flarum\Event\ConfigureMiddleware; use Flarum\Event\ConfigureNotificationTypes; use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\Application; +use Flarum\Foundation\ErrorHandling\JsonApiRenderer; +use Flarum\Foundation\ErrorHandling\Registry; +use Flarum\Foundation\ErrorHandling\Reporter; use Flarum\Http\Middleware as HttpMiddleware; use Flarum\Http\RouteCollection; use Flarum\Http\RouteHandlerFactory; use Flarum\Http\UrlGenerator; -use Tobscure\JsonApi\ErrorHandler; -use Tobscure\JsonApi\Exception\Handler\InvalidParameterExceptionHandler; use Zend\Stratigility\MiddlewarePipe; class ApiServiceProvider extends AbstractServiceProvider @@ -49,7 +50,11 @@ class ApiServiceProvider extends AbstractServiceProvider $this->app->singleton('flarum.api.middleware', function (Application $app) { $pipe = new MiddlewarePipe; - $pipe->pipe($app->make(Middleware\HandleErrors::class)); + $pipe->pipe(new HttpMiddleware\HandleErrors( + $app->make(Registry::class), + $app->make(JsonApiRenderer::class), + $app->tagged(Reporter::class) + )); $pipe->pipe($app->make(HttpMiddleware\ParseJsonBody::class)); $pipe->pipe($app->make(Middleware\FakeHttpMethods::class)); @@ -68,25 +73,6 @@ class ApiServiceProvider extends AbstractServiceProvider $this->app->afterResolving('flarum.api.middleware', function (MiddlewarePipe $pipe) { $pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.api.routes'))); }); - - $this->app->singleton(ErrorHandler::class, function () { - $handler = new ErrorHandler; - - $handler->registerHandler(new ExceptionHandler\FloodingExceptionHandler); - $handler->registerHandler(new ExceptionHandler\IlluminateValidationExceptionHandler); - $handler->registerHandler(new ExceptionHandler\InvalidAccessTokenExceptionHandler); - $handler->registerHandler(new ExceptionHandler\InvalidConfirmationTokenExceptionHandler); - $handler->registerHandler(new ExceptionHandler\MethodNotAllowedExceptionHandler); - $handler->registerHandler(new ExceptionHandler\ModelNotFoundExceptionHandler); - $handler->registerHandler(new ExceptionHandler\PermissionDeniedExceptionHandler); - $handler->registerHandler(new ExceptionHandler\RouteNotFoundExceptionHandler); - $handler->registerHandler(new ExceptionHandler\TokenMismatchExceptionHandler); - $handler->registerHandler(new ExceptionHandler\ValidationExceptionHandler); - $handler->registerHandler(new InvalidParameterExceptionHandler); - $handler->registerHandler(new ExceptionHandler\FallbackExceptionHandler($this->app->inDebugMode(), $this->app->make('log'))); - - return $handler; - }); } /** diff --git a/framework/core/src/Api/Client.php b/framework/core/src/Api/Client.php index beaed329d..684a00a55 100644 --- a/framework/core/src/Api/Client.php +++ b/framework/core/src/Api/Client.php @@ -12,11 +12,14 @@ namespace Flarum\Api; use Exception; +use Flarum\Foundation\ErrorHandling\JsonApiRenderer; +use Flarum\Foundation\ErrorHandling\Registry; use Flarum\User\User; use Illuminate\Contracts\Container\Container; use InvalidArgumentException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Server\RequestHandlerInterface; +use Throwable; use Zend\Diactoros\ServerRequestFactory; class Client @@ -27,18 +30,18 @@ class Client protected $container; /** - * @var ErrorHandler + * @var Registry */ - protected $errorHandler; + protected $registry; /** * @param Container $container - * @param ErrorHandler $errorHandler + * @param Registry $registry */ - public function __construct(Container $container, ErrorHandler $errorHandler = null) + public function __construct(Container $container, Registry $registry) { $this->container = $container; - $this->errorHandler = $errorHandler; + $this->registry = $registry; } /** @@ -69,23 +72,14 @@ class Client try { return $controller->handle($request); - } catch (Exception $e) { - if (! $this->errorHandler) { + } catch (Throwable $e) { + $error = $this->registry->handle($e); + + if ($error->shouldBeReported()) { throw $e; } - return $this->errorHandler->handle($e); + return (new JsonApiRenderer)->format($error, $request); } } - - /** - * @param ErrorHandler $errorHandler - * @return Client - */ - public function setErrorHandler(?ErrorHandler $errorHandler): self - { - $this->errorHandler = $errorHandler; - - return $this; - } } diff --git a/framework/core/src/Api/ErrorHandler.php b/framework/core/src/Api/ErrorHandler.php deleted file mode 100644 index 21ea56e3b..000000000 --- a/framework/core/src/Api/ErrorHandler.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api; - -use Exception; -use Throwable; -use Tobscure\JsonApi\Document; -use Tobscure\JsonApi\ErrorHandler as JsonApiErrorHandler; - -class ErrorHandler -{ - /** - * @var JsonApiErrorHandler - */ - protected $errorHandler; - - /** - * @param JsonApiErrorHandler $errorHandler - */ - public function __construct(JsonApiErrorHandler $errorHandler) - { - $this->errorHandler = $errorHandler; - } - - /** - * @param Exception $e - * @return JsonApiResponse - */ - public function handle(Throwable $e) - { - if (! $e instanceof Exception) { - $e = new Exception($e->getMessage(), $e->getCode(), $e); - } - - $response = $this->errorHandler->handle($e); - - $document = new Document; - $document->setErrors($response->getErrors()); - - return new JsonApiResponse($document, $response->getStatus()); - } -} diff --git a/framework/core/src/Api/Exception/InvalidAccessTokenException.php b/framework/core/src/Api/Exception/InvalidAccessTokenException.php index dafd75a0b..c79e99e03 100644 --- a/framework/core/src/Api/Exception/InvalidAccessTokenException.php +++ b/framework/core/src/Api/Exception/InvalidAccessTokenException.php @@ -12,7 +12,12 @@ namespace Flarum\Api\Exception; use Exception; +use Flarum\Foundation\KnownError; -class InvalidAccessTokenException extends Exception +class InvalidAccessTokenException extends Exception implements KnownError { + public function getType(): string + { + return 'invalid_access_token'; + } } diff --git a/framework/core/src/Api/ExceptionHandler/FallbackExceptionHandler.php b/framework/core/src/Api/ExceptionHandler/FallbackExceptionHandler.php deleted file mode 100644 index 1087dbe89..000000000 --- a/framework/core/src/Api/ExceptionHandler/FallbackExceptionHandler.php +++ /dev/null @@ -1,77 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\ExceptionHandler; - -use Exception; -use Psr\Log\LoggerInterface; -use Tobscure\JsonApi\Exception\Handler\ExceptionHandlerInterface; -use Tobscure\JsonApi\Exception\Handler\ResponseBag; - -class FallbackExceptionHandler implements ExceptionHandlerInterface -{ - /** - * @var bool - */ - protected $debug; - - /** - * @var LoggerInterface - */ - protected $logger; - - /** - * @param bool $debug - * @param LoggerInterface $logger - */ - public function __construct($debug, LoggerInterface $logger) - { - $this->debug = $debug; - $this->logger = $logger; - } - - /** - * {@inheritdoc} - */ - public function manages(Exception $e) - { - return true; - } - - /** - * {@inheritdoc} - */ - public function handle(Exception $e) - { - $status = 500; - $error = $this->constructError($e, $status); - - $this->logger->error($e); - - return new ResponseBag($status, [$error]); - } - - /** - * @param Exception $e - * @param $status - * @return array - */ - private function constructError(Exception $e, $status) - { - $error = ['code' => $status, 'title' => 'Internal server error']; - - if ($this->debug) { - $error['detail'] = (string) $e; - } - - return $error; - } -} diff --git a/framework/core/src/Api/ExceptionHandler/FloodingExceptionHandler.php b/framework/core/src/Api/ExceptionHandler/FloodingExceptionHandler.php deleted file mode 100644 index 0a55870dc..000000000 --- a/framework/core/src/Api/ExceptionHandler/FloodingExceptionHandler.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\ExceptionHandler; - -use Exception; -use Flarum\Post\Exception\FloodingException; -use Tobscure\JsonApi\Exception\Handler\ExceptionHandlerInterface; -use Tobscure\JsonApi\Exception\Handler\ResponseBag; - -class FloodingExceptionHandler implements ExceptionHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function manages(Exception $e) - { - return $e instanceof FloodingException; - } - - /** - * {@inheritdoc} - */ - public function handle(Exception $e) - { - $status = 429; - $error = [ - 'status' => (string) $status, - 'code' => 'too_many_requests' - ]; - - return new ResponseBag($status, [$error]); - } -} diff --git a/framework/core/src/Api/ExceptionHandler/IlluminateValidationExceptionHandler.php b/framework/core/src/Api/ExceptionHandler/IlluminateValidationExceptionHandler.php deleted file mode 100644 index afc496c1d..000000000 --- a/framework/core/src/Api/ExceptionHandler/IlluminateValidationExceptionHandler.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\ExceptionHandler; - -use Exception; -use Illuminate\Validation\ValidationException; -use Tobscure\JsonApi\Exception\Handler\ExceptionHandlerInterface; -use Tobscure\JsonApi\Exception\Handler\ResponseBag; - -class IlluminateValidationExceptionHandler implements ExceptionHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function manages(Exception $e) - { - return $e instanceof ValidationException; - } - - /** - * {@inheritdoc} - */ - public function handle(Exception $e) - { - $status = 422; - - $errors = $this->formatErrors($e->errors()); - - return new ResponseBag($status, $errors); - } - - /** - * @param array $errors - * @return array - */ - protected function formatErrors(array $errors) - { - $errors = array_map(function ($field, $messages) { - return [ - 'status' => '422', - 'code' => 'validation_error', - 'detail' => implode("\n", $messages), - 'source' => ['pointer' => "/data/attributes/$field"] - ]; - }, array_keys($errors), $errors); - - return $errors; - } -} diff --git a/framework/core/src/Api/ExceptionHandler/InvalidAccessTokenExceptionHandler.php b/framework/core/src/Api/ExceptionHandler/InvalidAccessTokenExceptionHandler.php deleted file mode 100644 index c91964f17..000000000 --- a/framework/core/src/Api/ExceptionHandler/InvalidAccessTokenExceptionHandler.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\ExceptionHandler; - -use Exception; -use Flarum\Api\Exception\InvalidAccessTokenException; -use Tobscure\JsonApi\Exception\Handler\ExceptionHandlerInterface; -use Tobscure\JsonApi\Exception\Handler\ResponseBag; - -class InvalidAccessTokenExceptionHandler implements ExceptionHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function manages(Exception $e) - { - return $e instanceof InvalidAccessTokenException; - } - - /** - * {@inheritdoc} - */ - public function handle(Exception $e) - { - $status = 401; - $error = [ - 'status' => (string) $status, - 'code' => 'invalid_access_token' - ]; - - return new ResponseBag($status, [$error]); - } -} diff --git a/framework/core/src/Api/ExceptionHandler/InvalidConfirmationTokenExceptionHandler.php b/framework/core/src/Api/ExceptionHandler/InvalidConfirmationTokenExceptionHandler.php deleted file mode 100644 index 541b620f7..000000000 --- a/framework/core/src/Api/ExceptionHandler/InvalidConfirmationTokenExceptionHandler.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\ExceptionHandler; - -use Exception; -use Flarum\User\Exception\InvalidConfirmationTokenException; -use Tobscure\JsonApi\Exception\Handler\ExceptionHandlerInterface; -use Tobscure\JsonApi\Exception\Handler\ResponseBag; - -class InvalidConfirmationTokenExceptionHandler implements ExceptionHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function manages(Exception $e) - { - return $e instanceof InvalidConfirmationTokenException; - } - - /** - * {@inheritdoc} - */ - public function handle(Exception $e) - { - $status = 403; - $error = [ - 'status' => (string) $status, - 'code' => 'invalid_confirmation_token' - ]; - - return new ResponseBag($status, [$error]); - } -} diff --git a/framework/core/src/Api/ExceptionHandler/MethodNotAllowedExceptionHandler.php b/framework/core/src/Api/ExceptionHandler/MethodNotAllowedExceptionHandler.php deleted file mode 100644 index 729815e95..000000000 --- a/framework/core/src/Api/ExceptionHandler/MethodNotAllowedExceptionHandler.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\ExceptionHandler; - -use Exception; -use Flarum\Http\Exception\MethodNotAllowedException; -use Tobscure\JsonApi\Exception\Handler\ExceptionHandlerInterface; -use Tobscure\JsonApi\Exception\Handler\ResponseBag; - -class MethodNotAllowedExceptionHandler implements ExceptionHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function manages(Exception $e) - { - return $e instanceof MethodNotAllowedException; - } - - /** - * {@inheritdoc} - */ - public function handle(Exception $e) - { - $status = 405; - $error = [ - 'status' => (string) $status, - 'code' => 'method_not_allowed' - ]; - - return new ResponseBag($status, [$error]); - } -} diff --git a/framework/core/src/Api/ExceptionHandler/ModelNotFoundExceptionHandler.php b/framework/core/src/Api/ExceptionHandler/ModelNotFoundExceptionHandler.php deleted file mode 100644 index e921eb166..000000000 --- a/framework/core/src/Api/ExceptionHandler/ModelNotFoundExceptionHandler.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\ExceptionHandler; - -use Exception; -use Illuminate\Database\Eloquent\ModelNotFoundException; -use Tobscure\JsonApi\Exception\Handler\ExceptionHandlerInterface; -use Tobscure\JsonApi\Exception\Handler\ResponseBag; - -class ModelNotFoundExceptionHandler implements ExceptionHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function manages(Exception $e) - { - return $e instanceof ModelNotFoundException; - } - - /** - * {@inheritdoc} - */ - public function handle(Exception $e) - { - $status = 404; - $error = [ - 'status' => (string) $status, - 'code' => 'resource_not_found' - ]; - - return new ResponseBag($status, [$error]); - } -} diff --git a/framework/core/src/Api/ExceptionHandler/PermissionDeniedExceptionHandler.php b/framework/core/src/Api/ExceptionHandler/PermissionDeniedExceptionHandler.php deleted file mode 100644 index a4b4f9d53..000000000 --- a/framework/core/src/Api/ExceptionHandler/PermissionDeniedExceptionHandler.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\ExceptionHandler; - -use Exception; -use Flarum\User\Exception\PermissionDeniedException; -use Tobscure\JsonApi\Exception\Handler\ExceptionHandlerInterface; -use Tobscure\JsonApi\Exception\Handler\ResponseBag; - -class PermissionDeniedExceptionHandler implements ExceptionHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function manages(Exception $e) - { - return $e instanceof PermissionDeniedException; - } - - /** - * {@inheritdoc} - */ - public function handle(Exception $e) - { - $status = 401; - $error = [ - 'status' => (string) $status, - 'code' => 'permission_denied' - ]; - - return new ResponseBag($status, [$error]); - } -} diff --git a/framework/core/src/Api/ExceptionHandler/RouteNotFoundExceptionHandler.php b/framework/core/src/Api/ExceptionHandler/RouteNotFoundExceptionHandler.php deleted file mode 100644 index 67c25d6a1..000000000 --- a/framework/core/src/Api/ExceptionHandler/RouteNotFoundExceptionHandler.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\ExceptionHandler; - -use Exception; -use Flarum\Http\Exception\RouteNotFoundException; -use Tobscure\JsonApi\Exception\Handler\ExceptionHandlerInterface; -use Tobscure\JsonApi\Exception\Handler\ResponseBag; - -class RouteNotFoundExceptionHandler implements ExceptionHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function manages(Exception $e) - { - return $e instanceof RouteNotFoundException; - } - - /** - * {@inheritdoc} - */ - public function handle(Exception $e) - { - $status = 404; - $error = [ - 'status' => (string) $status, - 'code' => 'route_not_found' - ]; - - return new ResponseBag($status, [$error]); - } -} diff --git a/framework/core/src/Api/ExceptionHandler/TokenMismatchExceptionHandler.php b/framework/core/src/Api/ExceptionHandler/TokenMismatchExceptionHandler.php deleted file mode 100644 index 2258b4cd6..000000000 --- a/framework/core/src/Api/ExceptionHandler/TokenMismatchExceptionHandler.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\ExceptionHandler; - -use Exception; -use Flarum\Http\Exception\TokenMismatchException; -use Tobscure\JsonApi\Exception\Handler\ExceptionHandlerInterface; -use Tobscure\JsonApi\Exception\Handler\ResponseBag; - -class TokenMismatchExceptionHandler implements ExceptionHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function manages(Exception $e) - { - return $e instanceof TokenMismatchException; - } - - /** - * {@inheritdoc} - */ - public function handle(Exception $e) - { - $status = 400; - $error = [ - 'status' => (string) $status, - 'code' => 'csrf_token_mismatch' - ]; - - return new ResponseBag($status, [$error]); - } -} diff --git a/framework/core/src/Api/ExceptionHandler/ValidationExceptionHandler.php b/framework/core/src/Api/ExceptionHandler/ValidationExceptionHandler.php deleted file mode 100644 index f36b253fd..000000000 --- a/framework/core/src/Api/ExceptionHandler/ValidationExceptionHandler.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\ExceptionHandler; - -use Exception; -use Flarum\Foundation\ValidationException; -use Tobscure\JsonApi\Exception\Handler\ExceptionHandlerInterface; -use Tobscure\JsonApi\Exception\Handler\ResponseBag; - -class ValidationExceptionHandler implements ExceptionHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function manages(Exception $e) - { - return $e instanceof ValidationException; - } - - /** - * {@inheritdoc} - */ - public function handle(Exception $e) - { - $errors = array_merge( - $this->buildErrors($e->getAttributes(), '/data/attributes'), - $this->buildErrors($e->getRelationships(), '/data/relationships') - ); - - return new ResponseBag(422, $errors); - } - - private function buildErrors(array $messages, $pointer) - { - return array_map(function ($path, $detail) use ($pointer) { - return [ - 'status' => '422', - 'code' => 'validation_error', - 'detail' => $detail, - 'source' => ['pointer' => $pointer.'/'.$path] - ]; - }, array_keys($messages), $messages); - } -} diff --git a/framework/core/src/Api/Middleware/HandleErrors.php b/framework/core/src/Api/Middleware/HandleErrors.php deleted file mode 100644 index cfcc54068..000000000 --- a/framework/core/src/Api/Middleware/HandleErrors.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Api\Middleware; - -use Flarum\Api\ErrorHandler; -use Psr\Http\Message\ResponseInterface as Response; -use Psr\Http\Message\ServerRequestInterface as Request; -use Psr\Http\Server\MiddlewareInterface as Middleware; -use Psr\Http\Server\RequestHandlerInterface as Handler; -use Throwable; - -class HandleErrors implements Middleware -{ - /** - * @var ErrorHandler - */ - protected $errorHandler; - - /** - * @param ErrorHandler $errorHandler - */ - public function __construct(ErrorHandler $errorHandler) - { - $this->errorHandler = $errorHandler; - } - - /** - * Catch all errors that happen during further middleware execution. - */ - public function process(Request $request, Handler $handler): Response - { - try { - return $handler->handle($request); - } catch (Throwable $e) { - return $this->errorHandler->handle($e); - } - } -} diff --git a/framework/core/src/Forum/ForumServiceProvider.php b/framework/core/src/Forum/ForumServiceProvider.php index 969edb06d..a6f6faf1e 100644 --- a/framework/core/src/Forum/ForumServiceProvider.php +++ b/framework/core/src/Forum/ForumServiceProvider.php @@ -18,6 +18,10 @@ use Flarum\Extension\Event\Enabled; use Flarum\Formatter\Formatter; use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\Application; +use Flarum\Foundation\ErrorHandling\Registry; +use Flarum\Foundation\ErrorHandling\Reporter; +use Flarum\Foundation\ErrorHandling\ViewRenderer; +use Flarum\Foundation\ErrorHandling\WhoopsRenderer; use Flarum\Foundation\Event\ClearingCache; use Flarum\Frontend\AddLocaleAssets; use Flarum\Frontend\AddTranslations; @@ -61,11 +65,11 @@ class ForumServiceProvider extends AbstractServiceProvider $pipe = new MiddlewarePipe; // All requests should first be piped through our global error handler - if ($app->inDebugMode()) { - $pipe->pipe($app->make(HttpMiddleware\HandleErrorsWithWhoops::class)); - } else { - $pipe->pipe($app->make(HttpMiddleware\HandleErrorsWithView::class)); - } + $pipe->pipe(new HttpMiddleware\HandleErrors( + $app->make(Registry::class), + $app->inDebugMode() ? $app->make(WhoopsRenderer::class) : $app->make(ViewRenderer::class), + $app->tagged(Reporter::class) + )); $pipe->pipe($app->make(HttpMiddleware\ParseJsonBody::class)); $pipe->pipe($app->make(HttpMiddleware\CollectGarbage::class)); diff --git a/framework/core/src/Foundation/ErrorHandling/ExceptionHandler/IlluminateValidationExceptionHandler.php b/framework/core/src/Foundation/ErrorHandling/ExceptionHandler/IlluminateValidationExceptionHandler.php new file mode 100644 index 000000000..922a8cdd4 --- /dev/null +++ b/framework/core/src/Foundation/ErrorHandling/ExceptionHandler/IlluminateValidationExceptionHandler.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation\ErrorHandling\ExceptionHandler; + +use Flarum\Foundation\ErrorHandling\HandledError; +use Illuminate\Validation\ValidationException; + +class IlluminateValidationExceptionHandler +{ + public function handle(ValidationException $e): HandledError + { + return (new HandledError( + $e, 'validation_error', 422 + ))->withDetails($this->errorDetails($e)); + } + + protected function errorDetails(ValidationException $e): array + { + $errors = $e->errors(); + + return array_map(function ($field, $messages) { + return [ + 'detail' => implode("\n", $messages), + 'source' => ['pointer' => "/data/attributes/$field"] + ]; + }, array_keys($errors), $errors); + } +} diff --git a/framework/core/src/Foundation/ErrorHandling/ExceptionHandler/ValidationExceptionHandler.php b/framework/core/src/Foundation/ErrorHandling/ExceptionHandler/ValidationExceptionHandler.php new file mode 100644 index 000000000..224143766 --- /dev/null +++ b/framework/core/src/Foundation/ErrorHandling/ExceptionHandler/ValidationExceptionHandler.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation\ErrorHandling\ExceptionHandler; + +use Flarum\Foundation\ErrorHandling\HandledError; +use Flarum\Foundation\ValidationException; + +class ValidationExceptionHandler +{ + public function handle(ValidationException $e) + { + return (new HandledError( + $e, 'validation_error', 422 + ))->withDetails(array_merge( + $this->buildDetails($e->getAttributes(), '/data/attributes'), + $this->buildDetails($e->getRelationships(), '/data/relationships') + )); + } + + private function buildDetails(array $messages, $pointer): array + { + return array_map(function ($path, $detail) use ($pointer) { + return [ + 'detail' => $detail, + 'source' => ['pointer' => $pointer.'/'.$path] + ]; + }, array_keys($messages), $messages); + } +} diff --git a/framework/core/src/Foundation/ErrorHandling/Formatter.php b/framework/core/src/Foundation/ErrorHandling/Formatter.php new file mode 100644 index 000000000..ec7eb4756 --- /dev/null +++ b/framework/core/src/Foundation/ErrorHandling/Formatter.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation\ErrorHandling; + +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; + +interface Formatter +{ + public function format(HandledError $error, Request $request): Response; +} diff --git a/framework/core/src/Foundation/ErrorHandling/HandledError.php b/framework/core/src/Foundation/ErrorHandling/HandledError.php new file mode 100644 index 000000000..738ba6da5 --- /dev/null +++ b/framework/core/src/Foundation/ErrorHandling/HandledError.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation\ErrorHandling; + +use Throwable; + +class HandledError +{ + private $error; + private $type; + private $statusCode; + + private $details = []; + + public static function unknown(Throwable $error) + { + return new static($error, 'unknown', 500); + } + + public function __construct(Throwable $error, $type, $statusCode) + { + $this->error = $error; + $this->type = $type; + $this->statusCode = $statusCode; + } + + public function withDetails(array $details): self + { + $this->details = $details; + + return $this; + } + + public function getError(): Throwable + { + return $this->error; + } + + public function getType(): string + { + return $this->type; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function shouldBeReported(): bool + { + return $this->type === 'unknown'; + } + + public function getDetails(): array + { + return $this->details; + } +} diff --git a/framework/core/src/Foundation/ErrorHandling/JsonApiRenderer.php b/framework/core/src/Foundation/ErrorHandling/JsonApiRenderer.php new file mode 100644 index 000000000..948f8ae8f --- /dev/null +++ b/framework/core/src/Foundation/ErrorHandling/JsonApiRenderer.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation\ErrorHandling; + +use Flarum\Api\JsonApiResponse; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Tobscure\JsonApi\Document; + +class JsonApiRenderer implements Formatter +{ + public function format(HandledError $error, Request $request): Response + { + $document = new Document; + $document->setErrors([ + [ + 'status' => (string) $error->getStatusCode(), + 'code' => $error->getType(), + ], + ]); + + return new JsonApiResponse($document, $error->getStatusCode()); + } +} diff --git a/framework/core/src/Foundation/ErrorHandling/LogReporter.php b/framework/core/src/Foundation/ErrorHandling/LogReporter.php new file mode 100644 index 000000000..edff300d1 --- /dev/null +++ b/framework/core/src/Foundation/ErrorHandling/LogReporter.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation\ErrorHandling; + +use Psr\Log\LoggerInterface; + +class LogReporter implements Reporter +{ + /** + * @var LoggerInterface + */ + protected $logger; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + public function report(HandledError $error) + { + $this->logger->error($error->getError()); + } +} diff --git a/framework/core/src/Foundation/ErrorHandling/Registry.php b/framework/core/src/Foundation/ErrorHandling/Registry.php new file mode 100644 index 000000000..45ed5c4ec --- /dev/null +++ b/framework/core/src/Foundation/ErrorHandling/Registry.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation\ErrorHandling; + +use Flarum\Foundation\KnownError; +use Throwable; + +class Registry +{ + private $statusMap; + private $classMap; + private $handlerMap; + + public function __construct(array $statusMap, array $classMap, array $handlerMap) + { + $this->statusMap = $statusMap; + $this->classMap = $classMap; + $this->handlerMap = $handlerMap; + } + + public function handle(Throwable $error): HandledError + { + return $this->handleKnownTypes($error) + ?? $this->handleCustomTypes($error) + ?? HandledError::unknown($error); + } + + private function handleKnownTypes(Throwable $error): ?HandledError + { + $errorType = null; + + if ($error instanceof KnownError) { + $errorType = $error->getType(); + } else { + $errorClass = get_class($error); + if (isset($this->classMap[$errorClass])) { + $errorType = $this->classMap[$errorClass]; + } + } + + if ($errorType) { + return new HandledError( + $error, + $errorType, + $this->statusMap[$errorType] ?? 500 + ); + } + + return null; + } + + private function handleCustomTypes(Throwable $error): ?HandledError + { + $errorClass = get_class($error); + + if (isset($this->handlerMap[$errorClass])) { + $handler = new $this->handlerMap[$errorClass]; + + return $handler->handle($error); + } + + return null; + } +} diff --git a/framework/core/src/Foundation/ErrorHandling/Reporter.php b/framework/core/src/Foundation/ErrorHandling/Reporter.php new file mode 100644 index 000000000..2e52d289b --- /dev/null +++ b/framework/core/src/Foundation/ErrorHandling/Reporter.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation\ErrorHandling; + +interface Reporter +{ + public function report(HandledError $error); +} diff --git a/framework/core/src/Foundation/ErrorHandling/ViewRenderer.php b/framework/core/src/Foundation/ErrorHandling/ViewRenderer.php new file mode 100644 index 000000000..4ac50ffd7 --- /dev/null +++ b/framework/core/src/Foundation/ErrorHandling/ViewRenderer.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation\ErrorHandling; + +use Flarum\Settings\SettingsRepositoryInterface; +use Illuminate\Contracts\View\Factory as ViewFactory; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Symfony\Component\Translation\TranslatorInterface; +use Zend\Diactoros\Response\HtmlResponse; + +class ViewRenderer implements Formatter +{ + /** + * @var ViewFactory + */ + protected $view; + + /** + * @var TranslatorInterface + */ + protected $translator; + + /** + * @var SettingsRepositoryInterface + */ + protected $settings; + + public function __construct(ViewFactory $view, TranslatorInterface $translator, SettingsRepositoryInterface $settings) + { + $this->view = $view; + $this->translator = $translator; + $this->settings = $settings; + } + + public function format(HandledError $error, Request $request): Response + { + $view = $this->view->make($this->determineView($error)) + ->with('error', $error->getError()) + ->with('message', $this->getMessage($error)); + + return new HtmlResponse($view->render(), $error->getStatusCode()); + } + + private function determineView(HandledError $error): string + { + $view = [ + 'route_not_found' => '404', + 'csrf_token_mismatch' => '419', + ][$error->getType()] ?? 'default'; + + return "flarum.forum::error.$view"; + } + + private function getMessage(HandledError $error) + { + return $this->getTranslationIfExists($error->getStatusCode()) + ?? $this->getTranslationIfExists('unknown') + ?? 'An error occurred while trying to load this page.'; + } + + private function getTranslationIfExists(string $errorType) + { + $key = "core.views.error.${errorType}_message"; + $translation = $this->translator->trans($key, ['{forum}' => $this->settings->get('forum_title')]); + + return $translation === $key ? null : $translation; + } +} diff --git a/framework/core/src/Foundation/ErrorHandling/WhoopsRenderer.php b/framework/core/src/Foundation/ErrorHandling/WhoopsRenderer.php new file mode 100644 index 000000000..7be647558 --- /dev/null +++ b/framework/core/src/Foundation/ErrorHandling/WhoopsRenderer.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation\ErrorHandling; + +use Franzl\Middleware\Whoops\WhoopsRunner; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; + +class WhoopsRenderer implements Formatter +{ + public function format(HandledError $error, Request $request): Response + { + return WhoopsRunner::handle($error->getError(), $request) + ->withStatus($error->getStatusCode()); + } +} diff --git a/framework/core/src/Foundation/ErrorServiceProvider.php b/framework/core/src/Foundation/ErrorServiceProvider.php new file mode 100644 index 000000000..ec82370ee --- /dev/null +++ b/framework/core/src/Foundation/ErrorServiceProvider.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +use Flarum\Foundation\ErrorHandling\ExceptionHandler; +use Flarum\Foundation\ErrorHandling\LogReporter; +use Flarum\Foundation\ErrorHandling\Registry; +use Flarum\Foundation\ErrorHandling\Reporter; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Validation\ValidationException as IlluminateValidationException; +use Tobscure\JsonApi\Exception\InvalidParameterException; + +class ErrorServiceProvider extends AbstractServiceProvider +{ + public function register() + { + $this->app->singleton('flarum.error.statuses', function () { + return [ + // 400 Bad Request + 'csrf_token_mismatch' => 400, + 'invalid_parameter' => 400, + + // 401 Unauthorized + 'invalid_access_token' => 401, + + // 403 Forbidden + 'forbidden' => 403, + 'invalid_confirmation_token' => 403, + 'permission_denied' => 403, + + // 404 Not Found + 'model_not_found' => 404, + 'route_not_found' => 404, + + // 405 Method Not Allowed + 'method_not_allowed' => 405, + + // 429 Too Many Requests + 'too_many_requests' => 429, + ]; + }); + + $this->app->singleton('flarum.error.classes', function () { + return [ + InvalidParameterException::class => 'invalid_parameter', + ModelNotFoundException::class => 'model_not_found', + ]; + }); + + $this->app->singleton('flarum.error.handlers', function () { + return [ + IlluminateValidationException::class => ExceptionHandler\IlluminateValidationExceptionHandler::class, + ValidationException::class => ExceptionHandler\ValidationExceptionHandler::class, + ]; + }); + + $this->app->singleton(Registry::class, function () { + return new Registry( + $this->app->make('flarum.error.statuses'), + $this->app->make('flarum.error.classes'), + $this->app->make('flarum.error.handlers') + ); + }); + + $this->app->tag(LogReporter::class, Reporter::class); + } +} diff --git a/framework/core/src/Foundation/InstalledSite.php b/framework/core/src/Foundation/InstalledSite.php index 614cb1119..ffdfcc3d7 100644 --- a/framework/core/src/Foundation/InstalledSite.php +++ b/framework/core/src/Foundation/InstalledSite.php @@ -117,6 +117,7 @@ class InstalledSite implements SiteInterface $laravel->register(DatabaseServiceProvider::class); $laravel->register(DiscussionServiceProvider::class); $laravel->register(ExtensionServiceProvider::class); + $laravel->register(ErrorServiceProvider::class); $laravel->register(FilesystemServiceProvider::class); $laravel->register(FormatterServiceProvider::class); $laravel->register(ForumServiceProvider::class); diff --git a/framework/core/src/Foundation/KnownError.php b/framework/core/src/Foundation/KnownError.php new file mode 100644 index 000000000..bb7e4591a --- /dev/null +++ b/framework/core/src/Foundation/KnownError.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +interface KnownError +{ + public function getType(): string; +} diff --git a/framework/core/src/Foundation/UninstalledSite.php b/framework/core/src/Foundation/UninstalledSite.php index d363959b2..92aca5142 100644 --- a/framework/core/src/Foundation/UninstalledSite.php +++ b/framework/core/src/Foundation/UninstalledSite.php @@ -69,6 +69,7 @@ class UninstalledSite implements SiteInterface $this->registerLogger($laravel); + $laravel->register(ErrorServiceProvider::class); $laravel->register(LocaleServiceProvider::class); $laravel->register(FilesystemServiceProvider::class); $laravel->register(SessionServiceProvider::class); diff --git a/framework/core/src/Frontend/Frontend.php b/framework/core/src/Frontend/Frontend.php index 791d1feb2..acf0564cc 100644 --- a/framework/core/src/Frontend/Frontend.php +++ b/framework/core/src/Frontend/Frontend.php @@ -70,8 +70,6 @@ class Frontend { $actor = $request->getAttribute('actor'); - $this->api->setErrorHandler(null); - return $this->getResponseBody( $this->api->send(ShowForumController::class, $actor) ); diff --git a/framework/core/src/Http/Exception/ForbiddenException.php b/framework/core/src/Http/Exception/ForbiddenException.php index 55cc50557..903ae98cb 100644 --- a/framework/core/src/Http/Exception/ForbiddenException.php +++ b/framework/core/src/Http/Exception/ForbiddenException.php @@ -12,11 +12,12 @@ namespace Flarum\Http\Exception; use Exception; +use Flarum\Foundation\KnownError; -class ForbiddenException extends Exception +class ForbiddenException extends Exception implements KnownError { - public function __construct($message = null, $code = 403, Exception $previous = null) + public function getType(): string { - parent::__construct($message, $code, $previous); + return 'forbidden'; } } diff --git a/framework/core/src/Http/Exception/MethodNotAllowedException.php b/framework/core/src/Http/Exception/MethodNotAllowedException.php index 0b7cd3f8e..62e0ae07f 100644 --- a/framework/core/src/Http/Exception/MethodNotAllowedException.php +++ b/framework/core/src/Http/Exception/MethodNotAllowedException.php @@ -12,11 +12,12 @@ namespace Flarum\Http\Exception; use Exception; +use Flarum\Foundation\KnownError; -class MethodNotAllowedException extends Exception +class MethodNotAllowedException extends Exception implements KnownError { - public function __construct($message = null, $code = 405, Exception $previous = null) + public function getType(): string { - parent::__construct($message, $code, $previous); + return 'method_not_allowed'; } } diff --git a/framework/core/src/Http/Exception/RouteNotFoundException.php b/framework/core/src/Http/Exception/RouteNotFoundException.php index 6e7b6aef9..820da488e 100644 --- a/framework/core/src/Http/Exception/RouteNotFoundException.php +++ b/framework/core/src/Http/Exception/RouteNotFoundException.php @@ -12,11 +12,12 @@ namespace Flarum\Http\Exception; use Exception; +use Flarum\Foundation\KnownError; -class RouteNotFoundException extends Exception +class RouteNotFoundException extends Exception implements KnownError { - public function __construct($message = null, $code = 404, Exception $previous = null) + public function getType(): string { - parent::__construct($message, $code, $previous); + return 'route_not_found'; } } diff --git a/framework/core/src/Http/Exception/TokenMismatchException.php b/framework/core/src/Http/Exception/TokenMismatchException.php index 87871937a..3c2afd209 100644 --- a/framework/core/src/Http/Exception/TokenMismatchException.php +++ b/framework/core/src/Http/Exception/TokenMismatchException.php @@ -12,11 +12,12 @@ namespace Flarum\Http\Exception; use Exception; +use Flarum\Foundation\KnownError; -class TokenMismatchException extends Exception +class TokenMismatchException extends Exception implements KnownError { - public function __construct($message = null, $code = 419, Exception $previous = null) + public function getType(): string { - parent::__construct($message, $code, $previous); + return 'csrf_token_mismatch'; } } diff --git a/framework/core/src/Http/Middleware/HandleErrorsWithWhoops.php b/framework/core/src/Http/Middleware/HandleErrors.php similarity index 50% rename from framework/core/src/Http/Middleware/HandleErrorsWithWhoops.php rename to framework/core/src/Http/Middleware/HandleErrors.php index 317d3dd2d..94d749f8b 100644 --- a/framework/core/src/Http/Middleware/HandleErrorsWithWhoops.php +++ b/framework/core/src/Http/Middleware/HandleErrors.php @@ -11,27 +11,36 @@ namespace Flarum\Http\Middleware; -use Franzl\Middleware\Whoops\WhoopsRunner; +use Flarum\Foundation\ErrorHandling\Formatter; +use Flarum\Foundation\ErrorHandling\Registry; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\MiddlewareInterface as Middleware; use Psr\Http\Server\RequestHandlerInterface as Handler; -use Psr\Log\LoggerInterface; use Throwable; -class HandleErrorsWithWhoops implements Middleware +class HandleErrors implements Middleware { /** - * @var LoggerInterface + * @var Registry */ - protected $logger; + protected $registry; /** - * @param LoggerInterface $logger + * @var Formatter */ - public function __construct(LoggerInterface $logger) + protected $formatter; + + /** + * @var \Flarum\Foundation\ErrorHandling\Reporter[] + */ + protected $reporters; + + public function __construct(Registry $registry, Formatter $formatter, array $reporters) { - $this->logger = $logger; + $this->registry = $registry; + $this->formatter = $formatter; + $this->reporters = $reporters; } /** @@ -42,19 +51,15 @@ class HandleErrorsWithWhoops implements Middleware try { return $handler->handle($request); } catch (Throwable $e) { - $status = 500; - $errorCode = $e->getCode(); + $error = $this->registry->handle($e); - if ($errorCode !== 404) { - $this->logger->error($e); + if ($error->shouldBeReported()) { + foreach ($this->reporters as $reporter) { + $reporter->report($error); + } } - if (is_int($errorCode) && $errorCode >= 400 && $errorCode < 600) { - $status = $errorCode; - } - - return WhoopsRunner::handle($e, $request) - ->withStatus($status); + return $this->formatter->format($error, $request); } } } diff --git a/framework/core/src/Http/Middleware/HandleErrorsWithView.php b/framework/core/src/Http/Middleware/HandleErrorsWithView.php deleted file mode 100644 index 960074f39..000000000 --- a/framework/core/src/Http/Middleware/HandleErrorsWithView.php +++ /dev/null @@ -1,111 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Http\Middleware; - -use Flarum\Settings\SettingsRepositoryInterface; -use Illuminate\Contracts\View\Factory as ViewFactory; -use Psr\Http\Message\ResponseInterface as Response; -use Psr\Http\Message\ServerRequestInterface as Request; -use Psr\Http\Server\MiddlewareInterface as Middleware; -use Psr\Http\Server\RequestHandlerInterface as Handler; -use Psr\Log\LoggerInterface; -use Symfony\Component\Translation\TranslatorInterface; -use Throwable; -use Zend\Diactoros\Response\HtmlResponse; - -class HandleErrorsWithView implements Middleware -{ - /** - * @var ViewFactory - */ - protected $view; - - /** - * @var LoggerInterface - */ - protected $logger; - - /** - * @var TranslatorInterface - */ - protected $translator; - - /** - * @var SettingsRepositoryInterface - */ - protected $settings; - - /** - * @param ViewFactory $view - * @param LoggerInterface $logger - * @param TranslatorInterface $translator - * @param SettingsRepositoryInterface $settings - */ - public function __construct(ViewFactory $view, LoggerInterface $logger, TranslatorInterface $translator, SettingsRepositoryInterface $settings) - { - $this->view = $view; - $this->logger = $logger; - $this->translator = $translator; - $this->settings = $settings; - } - - /** - * Catch all errors that happen during further middleware execution. - */ - public function process(Request $request, Handler $handler): Response - { - try { - return $handler->handle($request); - } catch (Throwable $e) { - return $this->formatException($e); - } - } - - protected function formatException(Throwable $error) - { - $status = 500; - $errorCode = $error->getCode(); - - // If it seems to be a valid HTTP status code, we pass on the - // exception's status. - if (is_int($errorCode) && $errorCode >= 400 && $errorCode < 600) { - $status = $errorCode; - } - - if (! $this->view->exists($name = "flarum.forum::error.$status")) { - $name = 'flarum.forum::error.default'; - - $this->logger->error($error); - } - - $view = $this->view->make($name) - ->with('error', $error) - ->with('message', $this->getMessage($status)); - - return new HtmlResponse($view->render(), $status); - } - - private function getMessage($status) - { - return $this->getTranslationIfExists($status) - ?? $this->getTranslationIfExists(500) - ?? 'An error occurred while trying to load this page.'; - } - - private function getTranslationIfExists($status) - { - $key = "core.views.error.${status}_message"; - $translation = $this->translator->trans($key, ['{forum}' => $this->settings->get('forum_title')]); - - return $translation === $key ? null : $translation; - } -} diff --git a/framework/core/src/Install/Installer.php b/framework/core/src/Install/Installer.php index 514c8e364..ba4e2c098 100644 --- a/framework/core/src/Install/Installer.php +++ b/framework/core/src/Install/Installer.php @@ -12,8 +12,11 @@ namespace Flarum\Install; use Flarum\Foundation\AppInterface; +use Flarum\Foundation\ErrorHandling\Registry; +use Flarum\Foundation\ErrorHandling\Reporter; +use Flarum\Foundation\ErrorHandling\WhoopsRenderer; use Flarum\Http\Middleware\DispatchRoute; -use Flarum\Http\Middleware\HandleErrorsWithWhoops; +use Flarum\Http\Middleware\HandleErrors; use Flarum\Http\Middleware\StartSession; use Flarum\Install\Console\InstallCommand; use Illuminate\Contracts\Container\Container; @@ -37,7 +40,11 @@ class Installer implements AppInterface public function getRequestHandler() { $pipe = new MiddlewarePipe; - $pipe->pipe($this->container->make(HandleErrorsWithWhoops::class)); + $pipe->pipe(new HandleErrors( + $this->container->make(Registry::class), + $this->container->make(WhoopsRenderer::class), + $this->container->tagged(Reporter::class) + )); $pipe->pipe($this->container->make(StartSession::class)); $pipe->pipe( new DispatchRoute($this->container->make('flarum.install.routes')) diff --git a/framework/core/src/Post/Exception/FloodingException.php b/framework/core/src/Post/Exception/FloodingException.php index f9221b934..2f147c3e5 100644 --- a/framework/core/src/Post/Exception/FloodingException.php +++ b/framework/core/src/Post/Exception/FloodingException.php @@ -12,7 +12,12 @@ namespace Flarum\Post\Exception; use Exception; +use Flarum\Foundation\KnownError; -class FloodingException extends Exception +class FloodingException extends Exception implements KnownError { + public function getType(): string + { + return 'too_many_requests'; + } } diff --git a/framework/core/src/User/Exception/InvalidConfirmationTokenException.php b/framework/core/src/User/Exception/InvalidConfirmationTokenException.php index c8e9240b2..7ee294802 100644 --- a/framework/core/src/User/Exception/InvalidConfirmationTokenException.php +++ b/framework/core/src/User/Exception/InvalidConfirmationTokenException.php @@ -12,7 +12,12 @@ namespace Flarum\User\Exception; use Exception; +use Flarum\Foundation\KnownError; -class InvalidConfirmationTokenException extends Exception +class InvalidConfirmationTokenException extends Exception implements KnownError { + public function getType(): string + { + return 'invalid_confirmation_token'; + } } diff --git a/framework/core/src/User/Exception/PermissionDeniedException.php b/framework/core/src/User/Exception/PermissionDeniedException.php index 1bbeb766c..02814ac25 100644 --- a/framework/core/src/User/Exception/PermissionDeniedException.php +++ b/framework/core/src/User/Exception/PermissionDeniedException.php @@ -12,11 +12,12 @@ namespace Flarum\User\Exception; use Exception; +use Flarum\Foundation\KnownError; -class PermissionDeniedException extends Exception +class PermissionDeniedException extends Exception implements KnownError { - public function __construct($message = null, $code = 403, Exception $previous = null) + public function getType(): string { - parent::__construct($message, $code, $previous); + return 'permission_denied'; } } diff --git a/framework/core/tests/integration/api/Auth/AuthenticateWithApiKeyTest.php b/framework/core/tests/integration/api/Auth/AuthenticateWithApiKeyTest.php index d4a5fadde..1ba3ce5a1 100644 --- a/framework/core/tests/integration/api/Auth/AuthenticateWithApiKeyTest.php +++ b/framework/core/tests/integration/api/Auth/AuthenticateWithApiKeyTest.php @@ -17,7 +17,6 @@ use Flarum\Api\Client; use Flarum\Api\Controller\CreateGroupController; use Flarum\Tests\integration\RetrievesAuthorizedUsers; use Flarum\Tests\integration\TestCase; -use Flarum\User\Exception\PermissionDeniedException; use Flarum\User\Guest; use Flarum\User\User; use Illuminate\Support\Str; @@ -63,11 +62,10 @@ class AuthenticateWithApiKeyTest extends TestCase { /** @var Client $api */ $api = $this->app()->getContainer()->make(Client::class); - $api->setErrorHandler(null); - $this->expectException(PermissionDeniedException::class); + $response = $api->send(CreateGroupController::class, new Guest); - $api->send(CreateGroupController::class, new Guest); + $this->assertEquals(403, $response->getStatusCode()); } /** diff --git a/framework/core/tests/integration/api/Controller/ApiControllerTestCase.php b/framework/core/tests/integration/api/Controller/ApiControllerTestCase.php index 4f0b1296f..05085b801 100644 --- a/framework/core/tests/integration/api/Controller/ApiControllerTestCase.php +++ b/framework/core/tests/integration/api/Controller/ApiControllerTestCase.php @@ -53,8 +53,6 @@ abstract class ApiControllerTestCase extends TestCase /** @var Client $api */ $api = $this->app()->getContainer()->make(Client::class); - $api->setErrorHandler(null); - return $api->send($controller, $actor ?? new Guest, $queryParams, $body); } } diff --git a/framework/core/tests/integration/api/Controller/CreateDiscussionControllerTest.php b/framework/core/tests/integration/api/Controller/CreateDiscussionControllerTest.php index eec919d94..7e23f319d 100644 --- a/framework/core/tests/integration/api/Controller/CreateDiscussionControllerTest.php +++ b/framework/core/tests/integration/api/Controller/CreateDiscussionControllerTest.php @@ -15,7 +15,6 @@ use Flarum\Api\Controller\CreateDiscussionController; use Flarum\Discussion\Discussion; use Flarum\User\User; use Illuminate\Support\Arr; -use Illuminate\Validation\ValidationException; class CreateDiscussionControllerTest extends ApiControllerTestCase { @@ -72,11 +71,9 @@ class CreateDiscussionControllerTest extends ApiControllerTestCase $this->actor = User::find(1); $data = Arr::except($this->data, 'content'); + $response = $this->callWith($data); - $this->expectException(ValidationException::class); - $this->expectExceptionMessage('The given data was invalid.'); - - $this->callWith($data); + $this->assertEquals(422, $response->getStatusCode()); } /** @@ -87,10 +84,8 @@ class CreateDiscussionControllerTest extends ApiControllerTestCase $this->actor = User::find(1); $data = Arr::except($this->data, 'title'); + $response = $this->callWith($data); - $this->expectException(ValidationException::class); - $this->expectExceptionMessage('The given data was invalid.'); - - $this->callWith($data); + $this->assertEquals(422, $response->getStatusCode()); } } diff --git a/framework/core/tests/integration/api/Controller/CreateGroupControllerTest.php b/framework/core/tests/integration/api/Controller/CreateGroupControllerTest.php index 7ab771fa6..d2edcada1 100644 --- a/framework/core/tests/integration/api/Controller/CreateGroupControllerTest.php +++ b/framework/core/tests/integration/api/Controller/CreateGroupControllerTest.php @@ -13,11 +13,9 @@ namespace Flarum\Tests\integration\api\Controller; use Flarum\Api\Controller\CreateGroupController; use Flarum\Group\Group; -use Flarum\User\Exception\PermissionDeniedException; use Flarum\User\User; use Illuminate\Support\Arr; use Illuminate\Support\Str; -use Illuminate\Validation\ValidationException; class CreateGroupControllerTest extends ApiControllerTestCase { @@ -55,10 +53,7 @@ class CreateGroupControllerTest extends ApiControllerTestCase { $this->actor = User::find(1); - $this->expectException(ValidationException::class); - $this->expectExceptionMessage('The given data was invalid.'); - - $this->callWith(); + $this->assertEquals(422, $this->callWith()->getStatusCode()); } /** @@ -89,8 +84,6 @@ class CreateGroupControllerTest extends ApiControllerTestCase { $this->actor = User::find(2); - $this->expectException(PermissionDeniedException::class); - - $this->callWith($this->data); + $this->assertEquals(403, $this->callWith($this->data)->getStatusCode()); } } diff --git a/framework/core/tests/integration/api/Controller/CreateUserControllerTest.php b/framework/core/tests/integration/api/Controller/CreateUserControllerTest.php index f437ec699..d2caad23e 100644 --- a/framework/core/tests/integration/api/Controller/CreateUserControllerTest.php +++ b/framework/core/tests/integration/api/Controller/CreateUserControllerTest.php @@ -13,10 +13,8 @@ namespace Flarum\Tests\integration\api\Controller; use Flarum\Api\Controller\CreateUserController; use Flarum\Settings\SettingsRepositoryInterface; -use Flarum\User\Exception\PermissionDeniedException; use Flarum\User\User; use Illuminate\Support\Arr; -use Illuminate\Validation\ValidationException; class CreateUserControllerTest extends ApiControllerTestCase { @@ -53,10 +51,9 @@ class CreateUserControllerTest extends ApiControllerTestCase */ public function cannot_create_user_without_data() { - $this->expectException(ValidationException::class); - $this->expectExceptionMessage('The given data was invalid.'); + $response = $this->callWith(); - $this->callWith(); + $this->assertEquals(422, $response->getStatusCode()); } /** @@ -106,12 +103,9 @@ class CreateUserControllerTest extends ApiControllerTestCase $settings = app(SettingsRepositoryInterface::class); $settings->set('allow_sign_up', false); - $this->expectException(PermissionDeniedException::class); + $response = $this->callWith($this->data); + $this->assertEquals(403, $response->getStatusCode()); - try { - $this->callWith($this->data); - } finally { - $settings->set('allow_sign_up', true); - } + $settings->set('allow_sign_up', true); } } diff --git a/framework/core/tests/integration/api/Controller/ListNotificationsControllerTest.php b/framework/core/tests/integration/api/Controller/ListNotificationsControllerTest.php index a7a64924a..9f301607b 100644 --- a/framework/core/tests/integration/api/Controller/ListNotificationsControllerTest.php +++ b/framework/core/tests/integration/api/Controller/ListNotificationsControllerTest.php @@ -12,7 +12,6 @@ namespace Flarum\Tests\integration\api\Controller; use Flarum\Api\Controller\ListNotificationsController; -use Flarum\User\Exception\PermissionDeniedException; use Flarum\User\User; class ListNotificationsControllerTest extends ApiControllerTestCase @@ -35,9 +34,9 @@ class ListNotificationsControllerTest extends ApiControllerTestCase */ public function disallows_index_for_guest() { - $this->expectException(PermissionDeniedException::class); + $response = $this->callWith(); - $this->callWith(); + $this->assertEquals(403, $response->getStatusCode()); } /** diff --git a/framework/core/tests/integration/api/Controller/ListUsersControllerTest.php b/framework/core/tests/integration/api/Controller/ListUsersControllerTest.php index 919f01ddc..35eee7504 100644 --- a/framework/core/tests/integration/api/Controller/ListUsersControllerTest.php +++ b/framework/core/tests/integration/api/Controller/ListUsersControllerTest.php @@ -12,7 +12,6 @@ namespace Flarum\Tests\integration\api\Controller; use Flarum\Api\Controller\ListUsersController; -use Flarum\User\Exception\PermissionDeniedException; use Flarum\User\User; class ListUsersControllerTest extends ApiControllerTestCase @@ -41,9 +40,9 @@ class ListUsersControllerTest extends ApiControllerTestCase */ public function disallows_index_for_guest() { - $this->expectException(PermissionDeniedException::class); + $response = $this->callWith(); - $this->callWith(); + $this->assertEquals(403, $response->getStatusCode()); } /** diff --git a/framework/core/tests/integration/api/Controller/ShowDiscussionControllerTest.php b/framework/core/tests/integration/api/Controller/ShowDiscussionControllerTest.php index 50619c789..db9da7c67 100644 --- a/framework/core/tests/integration/api/Controller/ShowDiscussionControllerTest.php +++ b/framework/core/tests/integration/api/Controller/ShowDiscussionControllerTest.php @@ -15,7 +15,6 @@ use Carbon\Carbon; use Flarum\Api\Controller\ShowDiscussionController; use Flarum\Discussion\Discussion; use Flarum\User\User; -use Illuminate\Database\Eloquent\ModelNotFoundException; class ShowDiscussionControllerTest extends ApiControllerTestCase { @@ -73,11 +72,9 @@ class ShowDiscussionControllerTest extends ApiControllerTestCase */ public function guest_cannot_see_empty_discussion() { - $this->expectException(ModelNotFoundException::class); - $response = $this->callWith([], ['id' => 1]); - $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(404, $response->getStatusCode()); } /** @@ -95,8 +92,8 @@ class ShowDiscussionControllerTest extends ApiControllerTestCase */ public function guests_cannot_see_private_discussion() { - $this->expectException(ModelNotFoundException::class); + $response = $this->callWith([], ['id' => 3]); - $this->callWith([], ['id' => 3]); + $this->assertEquals(404, $response->getStatusCode()); } } diff --git a/framework/core/tests/unit/Api/ExceptionHandler/FloodingExceptionHandlerTest.php b/framework/core/tests/unit/Api/ExceptionHandler/FloodingExceptionHandlerTest.php deleted file mode 100644 index e2043b493..000000000 --- a/framework/core/tests/unit/Api/ExceptionHandler/FloodingExceptionHandlerTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Tests\unit\Api\ExceptionHandler; - -use Exception; -use Flarum\Api\ExceptionHandler\FloodingExceptionHandler; -use Flarum\Post\Exception\FloodingException; -use PHPUnit\Framework\TestCase; - -class FloodingExceptionHandlerTest extends TestCase -{ - private $handler; - - public function setUp() - { - $this->handler = new FloodingExceptionHandler; - } - - public function test_it_handles_recognisable_exceptions() - { - $this->assertFalse($this->handler->manages(new Exception)); - $this->assertTrue($this->handler->manages(new FloodingException)); - } - - public function test_it_provides_expected_output() - { - $result = $this->handler->handle(new FloodingException); - - $this->assertEquals(429, $result->getStatus()); - $this->assertEquals([ - [ - 'status' => '429', - 'code' => 'too_many_requests' - ] - ], $result->getErrors()); - } -} diff --git a/framework/core/tests/unit/Api/ExceptionHandler/InvalidAccessTokenExceptionHandlerTest.php b/framework/core/tests/unit/Api/ExceptionHandler/InvalidAccessTokenExceptionHandlerTest.php deleted file mode 100644 index 3d7355eac..000000000 --- a/framework/core/tests/unit/Api/ExceptionHandler/InvalidAccessTokenExceptionHandlerTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Tests\unit\Api\ExceptionHandler; - -use Exception; -use Flarum\Api\Exception\InvalidAccessTokenException; -use Flarum\Api\ExceptionHandler\InvalidAccessTokenExceptionHandler; -use PHPUnit\Framework\TestCase; - -class InvalidAccessTokenExceptionHandlerTest extends TestCase -{ - private $handler; - - public function setUp() - { - $this->handler = new InvalidAccessTokenExceptionHandler; - } - - public function test_it_handles_recognisable_exceptions() - { - $this->assertFalse($this->handler->manages(new Exception)); - $this->assertTrue($this->handler->manages(new InvalidAccessTokenException)); - } - - public function test_output() - { - $response = $this->handler->handle(new InvalidAccessTokenException); - - $this->assertEquals(401, $response->getStatus()); - $this->assertEquals([ - [ - 'status' => '401', - 'code' => 'invalid_access_token' - ] - ], $response->getErrors()); - } -} diff --git a/framework/core/tests/unit/Api/ExceptionHandler/InvalidConfirmationTokenExceptionHandlerTest.php b/framework/core/tests/unit/Api/ExceptionHandler/InvalidConfirmationTokenExceptionHandlerTest.php deleted file mode 100644 index 1f289fe73..000000000 --- a/framework/core/tests/unit/Api/ExceptionHandler/InvalidConfirmationTokenExceptionHandlerTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Tests\unit\Api\ExceptionHandler; - -use Exception; -use Flarum\Api\ExceptionHandler\InvalidConfirmationTokenExceptionHandler; -use Flarum\User\Exception\InvalidConfirmationTokenException; -use PHPUnit\Framework\TestCase; - -class InvalidConfirmationTokenExceptionHandlerTest extends TestCase -{ - private $handler; - - public function setUp() - { - $this->handler = new InvalidConfirmationTokenExceptionHandler; - } - - public function test_it_handles_recognisable_exceptions() - { - $this->assertFalse($this->handler->manages(new Exception)); - $this->assertTrue($this->handler->manages(new InvalidConfirmationTokenException)); - } - - public function test_output() - { - $response = $this->handler->handle(new InvalidConfirmationTokenException); - - $this->assertEquals(403, $response->getStatus()); - $this->assertEquals([ - [ - 'status' => '403', - 'code' => 'invalid_confirmation_token' - ] - ], $response->getErrors()); - } -} diff --git a/framework/core/tests/unit/Api/ExceptionHandler/MethodNotAllowedExceptionHandlerTest.php b/framework/core/tests/unit/Api/ExceptionHandler/MethodNotAllowedExceptionHandlerTest.php deleted file mode 100644 index 23ce0761f..000000000 --- a/framework/core/tests/unit/Api/ExceptionHandler/MethodNotAllowedExceptionHandlerTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Tests\unit\Api\ExceptionHandler; - -use Exception; -use Flarum\Api\ExceptionHandler\MethodNotAllowedExceptionHandler; -use Flarum\Http\Exception\MethodNotAllowedException; -use PHPUnit\Framework\TestCase; - -class MethodNotAllowedExceptionHandlerTest extends TestCase -{ - private $handler; - - public function setUp() - { - $this->handler = new MethodNotAllowedExceptionHandler(); - } - - public function test_it_handles_recognisable_exceptions() - { - $this->assertFalse($this->handler->manages(new Exception)); - $this->assertTrue($this->handler->manages(new MethodNotAllowedException())); - } - - public function test_managing_exceptions() - { - $response = $this->handler->handle(new MethodNotAllowedException); - - $this->assertEquals(405, $response->getStatus()); - $this->assertEquals([ - [ - 'status' => '405', - 'code' => 'method_not_allowed' - ] - ], $response->getErrors()); - } -} diff --git a/framework/core/tests/unit/Api/ExceptionHandler/ModelNotFoundExceptionHandlerTest.php b/framework/core/tests/unit/Api/ExceptionHandler/ModelNotFoundExceptionHandlerTest.php deleted file mode 100644 index c1eb2efcc..000000000 --- a/framework/core/tests/unit/Api/ExceptionHandler/ModelNotFoundExceptionHandlerTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Tests\unit\Api\ExceptionHandler; - -use Exception; -use Flarum\Api\ExceptionHandler\ModelNotFoundExceptionHandler; -use Illuminate\Database\Eloquent\ModelNotFoundException; -use PHPUnit\Framework\TestCase; - -class ModelNotFoundExceptionHandlerTest extends TestCase -{ - private $handler; - - public function setUp() - { - $this->handler = new ModelNotFoundExceptionHandler; - } - - public function test_it_handles_recognisable_exceptions() - { - $this->assertFalse($this->handler->manages(new Exception)); - $this->assertTrue($this->handler->manages(new ModelNotFoundException)); - } - - public function test_managing_exceptions() - { - $response = $this->handler->handle(new ModelNotFoundException); - - $this->assertEquals(404, $response->getStatus()); - $this->assertEquals([ - [ - 'status' => '404', - 'code' => 'resource_not_found' - ] - ], $response->getErrors()); - } -} diff --git a/framework/core/tests/unit/Api/ExceptionHandler/PermissionDeniedExceptionHandlerTest.php b/framework/core/tests/unit/Api/ExceptionHandler/PermissionDeniedExceptionHandlerTest.php deleted file mode 100644 index dcfdb33f8..000000000 --- a/framework/core/tests/unit/Api/ExceptionHandler/PermissionDeniedExceptionHandlerTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Tests\unit\Api\ExceptionHandler; - -use Exception; -use Flarum\Api\ExceptionHandler\PermissionDeniedExceptionHandler; -use Flarum\User\Exception\PermissionDeniedException; -use PHPUnit\Framework\TestCase; - -class PermissionDeniedExceptionHandlerTest extends TestCase -{ - private $handler; - - public function setUp() - { - $this->handler = new PermissionDeniedExceptionHandler; - } - - public function test_it_handles_recognisable_exceptions() - { - $this->assertFalse($this->handler->manages(new Exception)); - $this->assertTrue($this->handler->manages(new PermissionDeniedException)); - } - - public function test_managing_exceptions() - { - $response = $this->handler->handle(new PermissionDeniedException); - - $this->assertEquals(401, $response->getStatus()); - $this->assertEquals([ - [ - 'status' => '401', - 'code' => 'permission_denied' - ] - ], $response->getErrors()); - } -} diff --git a/framework/core/tests/unit/Api/ExceptionHandler/RouteNotFoundExceptionHandlerTest.php b/framework/core/tests/unit/Api/ExceptionHandler/RouteNotFoundExceptionHandlerTest.php deleted file mode 100644 index 51c82cb70..000000000 --- a/framework/core/tests/unit/Api/ExceptionHandler/RouteNotFoundExceptionHandlerTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Tests\unit\Api\ExceptionHandler; - -use Exception; -use Flarum\Api\ExceptionHandler\RouteNotFoundExceptionHandler; -use Flarum\Http\Exception\RouteNotFoundException; -use PHPUnit\Framework\TestCase; - -class RouteNotFoundExceptionHandlerTest extends TestCase -{ - private $handler; - - public function setUp() - { - $this->handler = new RouteNotFoundExceptionHandler(); - } - - public function test_it_handles_recognisable_exceptions() - { - $this->assertFalse($this->handler->manages(new Exception)); - $this->assertTrue($this->handler->manages(new RouteNotFoundException())); - } - - public function test_managing_exceptions() - { - $response = $this->handler->handle(new RouteNotFoundException); - - $this->assertEquals(404, $response->getStatus()); - $this->assertEquals([ - [ - 'status' => '404', - 'code' => 'route_not_found' - ] - ], $response->getErrors()); - } -} diff --git a/framework/core/tests/unit/Api/ExceptionHandler/TokenMismatchExceptionHandlerTest.php b/framework/core/tests/unit/Api/ExceptionHandler/TokenMismatchExceptionHandlerTest.php deleted file mode 100644 index eebb1bff8..000000000 --- a/framework/core/tests/unit/Api/ExceptionHandler/TokenMismatchExceptionHandlerTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Tests\unit\Api\ExceptionHandler; - -use Exception; -use Flarum\Api\ExceptionHandler\TokenMismatchExceptionHandler; -use Flarum\Http\Exception\TokenMismatchException; -use PHPUnit\Framework\TestCase; - -class TokenMismatchExceptionHandlerTest extends TestCase -{ - private $handler; - - public function setUp() - { - $this->handler = new TokenMismatchExceptionHandler; - } - - public function test_it_handles_recognisable_exceptions() - { - $this->assertFalse($this->handler->manages(new Exception)); - $this->assertTrue($this->handler->manages(new TokenMismatchException())); - } - - public function test_managing_exceptions() - { - $response = $this->handler->handle(new TokenMismatchException); - - $this->assertEquals(400, $response->getStatus()); - $this->assertEquals([ - [ - 'status' => '400', - 'code' => 'csrf_token_mismatch' - ] - ], $response->getErrors()); - } -} diff --git a/framework/core/tests/unit/Api/ExceptionHandler/IlluminateValidationExceptionHandlerTest.php b/framework/core/tests/unit/Foundation/ErrorHandling/ExceptionHandler/IlluminateValidationExceptionHandlerTest.php similarity index 65% rename from framework/core/tests/unit/Api/ExceptionHandler/IlluminateValidationExceptionHandlerTest.php rename to framework/core/tests/unit/Foundation/ErrorHandling/ExceptionHandler/IlluminateValidationExceptionHandlerTest.php index ed3d799f6..028fa5e33 100644 --- a/framework/core/tests/unit/Api/ExceptionHandler/IlluminateValidationExceptionHandlerTest.php +++ b/framework/core/tests/unit/Foundation/ErrorHandling/ExceptionHandler/IlluminateValidationExceptionHandlerTest.php @@ -9,10 +9,9 @@ * file that was distributed with this source code. */ -namespace Flarum\Tests\unit\Api\ExceptionHandler; +namespace Flarum\Tests\unit\Foundation\ErrorHandling\ExceptionHandler; -use Exception; -use Flarum\Api\ExceptionHandler\IlluminateValidationExceptionHandler; +use Flarum\Foundation\ErrorHandling\ExceptionHandler\IlluminateValidationExceptionHandler; use Illuminate\Translation\ArrayLoader; use Illuminate\Translation\Translator; use Illuminate\Validation\Factory; @@ -28,29 +27,20 @@ class IlluminateValidationExceptionHandlerTest extends TestCase $this->handler = new IlluminateValidationExceptionHandler; } - public function test_it_handles_familiar_exceptions() - { - $validException = new ValidationException($this->makeValidator()); - - $this->assertFalse($this->handler->manages(new Exception)); - $this->assertTrue($this->handler->manages($validException)); - } - public function test_it_creates_the_desired_output() { $exception = new ValidationException($this->makeValidator(['foo' => ''], ['foo' => 'required'])); - $response = $this->handler->handle($exception); + $error = $this->handler->handle($exception); - $this->assertEquals(422, $response->getStatus()); + $this->assertEquals(422, $error->getStatusCode()); + $this->assertEquals('validation_error', $error->getType()); $this->assertEquals([ [ - 'status' => '422', - 'code' => 'validation_error', 'detail' => 'validation.required', 'source' => ['pointer' => '/data/attributes/foo'] ] - ], $response->getErrors()); + ], $error->getDetails()); } private function makeValidator($data = [], $rules = []) diff --git a/framework/core/tests/unit/Api/ExceptionHandler/ValidationExceptionHandlerTest.php b/framework/core/tests/unit/Foundation/ErrorHandling/ExceptionHandler/ValidationExceptionHandlerTest.php similarity index 58% rename from framework/core/tests/unit/Api/ExceptionHandler/ValidationExceptionHandlerTest.php rename to framework/core/tests/unit/Foundation/ErrorHandling/ExceptionHandler/ValidationExceptionHandlerTest.php index a014a96e5..32062249d 100644 --- a/framework/core/tests/unit/Api/ExceptionHandler/ValidationExceptionHandlerTest.php +++ b/framework/core/tests/unit/Foundation/ErrorHandling/ExceptionHandler/ValidationExceptionHandlerTest.php @@ -9,10 +9,9 @@ * file that was distributed with this source code. */ -namespace Flarum\Tests\unit\Api\ExceptionHandler; +namespace Flarum\Tests\unit\Foundation\ErrorHandling\ExceptionHandler; -use Exception; -use Flarum\Api\ExceptionHandler\ValidationExceptionHandler; +use Flarum\Foundation\ErrorHandling\ExceptionHandler\ValidationExceptionHandler; use Flarum\Foundation\ValidationException; use PHPUnit\Framework\TestCase; @@ -25,33 +24,24 @@ class ValidationExceptionHandlerTest extends TestCase $this->handler = new ValidationExceptionHandler; } - public function test_it_handles_recognisable_exceptions() - { - $this->assertFalse($this->handler->manages(new Exception)); - $this->assertTrue($this->handler->manages(new ValidationException([]))); - } - public function test_managing_exceptions() { - $response = $this->handler->handle(new ValidationException( + $error = $this->handler->handle(new ValidationException( ['foo' => 'Attribute error'], ['bar' => 'Relationship error'] )); - $this->assertEquals(422, $response->getStatus()); + $this->assertEquals(422, $error->getStatusCode()); + $this->assertEquals('validation_error', $error->getType()); $this->assertEquals([ [ - 'status' => '422', - 'code' => 'validation_error', 'detail' => 'Attribute error', 'source' => ['pointer' => '/data/attributes/foo'] ], [ - 'status' => '422', - 'code' => 'validation_error', 'detail' => 'Relationship error', 'source' => ['pointer' => '/data/relationships/bar'] ] - ], $response->getErrors()); + ], $error->getDetails()); } }