diff --git a/framework/core/src/Api/ApiServiceProvider.php b/framework/core/src/Api/ApiServiceProvider.php index 3c8bb7689..f8c4ebcd2 100644 --- a/framework/core/src/Api/ApiServiceProvider.php +++ b/framework/core/src/Api/ApiServiceProvider.php @@ -18,7 +18,9 @@ use Flarum\Event\ConfigureNotificationTypes; use Flarum\Http\GenerateRouteHandlerTrait; use Flarum\Http\RouteCollection; use Flarum\Foundation\AbstractServiceProvider; -use Psr\Http\Message\ServerRequestInterface; +use Tobscure\JsonApi\ErrorHandler; +use Tobscure\JsonApi\Exception\Handler\FallbackExceptionHandler; +use Tobscure\JsonApi\Exception\Handler\InvalidParameterExceptionHandler; class ApiServiceProvider extends AbstractServiceProvider { @@ -36,6 +38,21 @@ class ApiServiceProvider extends AbstractServiceProvider $this->app->singleton('flarum.api.routes', function () { return $this->getRoutes(); }); + + $this->app->singleton(ErrorHandler::class, function () { + $handler = new ErrorHandler; + + $handler->registerHandler(new Handler\FloodingExceptionHandler); + $handler->registerHandler(new Handler\IlluminateValidationExceptionHandler); + $handler->registerHandler(new Handler\InvalidConfirmationTokenExceptionHandler); + $handler->registerHandler(new Handler\ModelNotFoundExceptionHandler); + $handler->registerHandler(new Handler\PermissionDeniedExceptionHandler); + $handler->registerHandler(new Handler\ValidationExceptionHandler); + $handler->registerHandler(new InvalidParameterExceptionHandler); + $handler->registerHandler(new FallbackExceptionHandler($this->app->inDebugMode())); + + return $handler; + }); } /** diff --git a/framework/core/src/Api/Client.php b/framework/core/src/Api/Client.php index 9d0330287..0a2920172 100644 --- a/framework/core/src/Api/Client.php +++ b/framework/core/src/Api/Client.php @@ -25,11 +25,18 @@ class Client protected $container; /** - * @param Container $container + * @var ErrorHandler */ - public function __construct(Container $container) + protected $errorHandler; + + /** + * @param Container $container + * @param ErrorHandler $errorHandler + */ + public function __construct(Container $container, ErrorHandler $errorHandler) { $this->container = $container; + $this->errorHandler = $errorHandler; } /** @@ -55,11 +62,9 @@ class Client } try { - $response = $controller->handle($request); + return $controller->handle($request); } catch (Exception $e) { - $response = $this->container->make('Flarum\Api\Middleware\HandleErrors')->handle($e); + return $this->errorHandler->handle($e); } - - return $response; } } diff --git a/framework/core/src/Api/ErrorHandler.php b/framework/core/src/Api/ErrorHandler.php new file mode 100644 index 000000000..f285998c2 --- /dev/null +++ b/framework/core/src/Api/ErrorHandler.php @@ -0,0 +1,45 @@ + + * + * 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 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(Exception $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/Handler/FloodingExceptionHandler.php b/framework/core/src/Api/Handler/FloodingExceptionHandler.php new file mode 100644 index 000000000..9f5e8440c --- /dev/null +++ b/framework/core/src/Api/Handler/FloodingExceptionHandler.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\Api\Handler; + +use Exception; +use Flarum\Core\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 = []; + + return new ResponseBag($status, [$error]); + } +} diff --git a/framework/core/src/Api/Handler/IlluminateValidationExceptionHandler.php b/framework/core/src/Api/Handler/IlluminateValidationExceptionHandler.php new file mode 100644 index 000000000..c25def581 --- /dev/null +++ b/framework/core/src/Api/Handler/IlluminateValidationExceptionHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Api\Handler; + +use Exception; +use Illuminate\Contracts\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 = $e->errors()->toArray(); + $errors = array_map(function ($field, $messages) { + return [ + 'detail' => implode("\n", $messages), + 'source' => ['pointer' => '/data/attributes/' . $field], + ]; + }, array_keys($errors), $errors); + + return new ResponseBag($status, $errors); + } +} diff --git a/framework/core/src/Api/Handler/InvalidConfirmationTokenExceptionHandler.php b/framework/core/src/Api/Handler/InvalidConfirmationTokenExceptionHandler.php new file mode 100644 index 000000000..9202e487f --- /dev/null +++ b/framework/core/src/Api/Handler/InvalidConfirmationTokenExceptionHandler.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\Api\Handler; + +use Exception; +use Flarum\Core\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 = ['code' => 'invalid_confirmation_token']; + + return new ResponseBag($status, [$error]); + } +} diff --git a/framework/core/src/Api/Handler/ModelNotFoundExceptionHandler.php b/framework/core/src/Api/Handler/ModelNotFoundExceptionHandler.php new file mode 100644 index 000000000..315f71111 --- /dev/null +++ b/framework/core/src/Api/Handler/ModelNotFoundExceptionHandler.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\Api\Handler; + +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 = []; + + return new ResponseBag($status, [$error]); + } +} diff --git a/framework/core/src/Api/Handler/PermissionDeniedExceptionHandler.php b/framework/core/src/Api/Handler/PermissionDeniedExceptionHandler.php new file mode 100644 index 000000000..ac689d837 --- /dev/null +++ b/framework/core/src/Api/Handler/PermissionDeniedExceptionHandler.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\Api\Handler; + +use Exception; +use Flarum\Core\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 = []; + + return new ResponseBag($status, [$error]); + } +} diff --git a/framework/core/src/Api/Handler/ValidationExceptionHandler.php b/framework/core/src/Api/Handler/ValidationExceptionHandler.php new file mode 100644 index 000000000..4e0ad26e8 --- /dev/null +++ b/framework/core/src/Api/Handler/ValidationExceptionHandler.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Api\Handler; + +use Exception; +use Flarum\Core\Exception\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) + { + $status = 422; + + $messages = $e->getMessages(); + $errors = array_map(function ($path, $detail) { + $source = ['pointer' => '/data/attributes/' . $path]; + + return compact('source', 'detail'); + }, array_keys($messages), $messages); + + return new ResponseBag($status, $errors); + } +} diff --git a/framework/core/src/Api/Middleware/HandleErrors.php b/framework/core/src/Api/Middleware/HandleErrors.php index 71e445221..72d322058 100644 --- a/framework/core/src/Api/Middleware/HandleErrors.php +++ b/framework/core/src/Api/Middleware/HandleErrors.php @@ -10,32 +10,24 @@ namespace Flarum\Api\Middleware; -use Flarum\Api\JsonApiResponse; -use Flarum\Foundation\Application; -use Illuminate\Contracts\Validation\ValidationException; -use Illuminate\Database\Eloquent\ModelNotFoundException; +use Flarum\Api\ErrorHandler; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use Tobscure\JsonApi\Document; -use Tobscure\JsonApi\Exception\JsonApiSerializableInterface; -use Zend\Diactoros\Response\JsonResponse; use Zend\Stratigility\ErrorMiddlewareInterface; -use Flarum\Core; -use Exception; class HandleErrors implements ErrorMiddlewareInterface { /** - * @var Application + * @var ErrorHandler */ - private $app; + protected $errorHandler; /** - * @param Application $app + * @param ErrorHandler $errorHandler */ - public function __construct(Application $app) + public function __construct(ErrorHandler $errorHandler) { - $this->app = $app; + $this->errorHandler = $errorHandler; } /** @@ -43,47 +35,6 @@ class HandleErrors implements ErrorMiddlewareInterface */ public function __invoke($e, Request $request, Response $response, callable $out = null) { - return $this->handle($e); - } - - public function handle(Exception $e) - { - if ($e instanceof JsonApiSerializableInterface) { - $status = $e->getStatusCode(); - - $errors = $e->getErrors(); - } elseif ($e instanceof ValidationException) { - $status = 422; - - $errors = $e->errors()->toArray(); - $errors = array_map(function ($field, $messages) { - return [ - 'detail' => implode("\n", $messages), - 'source' => ['pointer' => '/data/attributes/' . $field], - ]; - }, array_keys($errors), $errors); - } elseif ($e instanceof ModelNotFoundException) { - $status = 404; - - $errors = []; - } else { - $status = 500; - - $error = [ - 'code' => $status, - 'title' => 'Internal Server Error' - ]; - - if ($this->app->inDebugMode()) { - $error['detail'] = (string) $e; - } - - $errors = [$error]; - } - - $document = new Document; - $document->setErrors($errors); - - return new JsonApiResponse($document, $status); + return $this->errorHandler->handle($e); } } diff --git a/framework/core/src/Core/Exception/FloodingException.php b/framework/core/src/Core/Exception/FloodingException.php index 8aaf92437..169905e06 100644 --- a/framework/core/src/Core/Exception/FloodingException.php +++ b/framework/core/src/Core/Exception/FloodingException.php @@ -11,23 +11,7 @@ namespace Flarum\Core\Exception; use Exception; -use Tobscure\JsonApi\Exception\JsonApiSerializableInterface; -class FloodingException extends Exception implements JsonApiSerializableInterface +class FloodingException extends Exception { - /** - * {@inheritdoc} - */ - public function getStatusCode() - { - return 429; - } - - /** - * {@inheritdoc} - */ - public function getErrors() - { - return []; - } } diff --git a/framework/core/src/Core/Exception/InvalidConfirmationTokenException.php b/framework/core/src/Core/Exception/InvalidConfirmationTokenException.php index 2bf6d117a..830ffe43c 100644 --- a/framework/core/src/Core/Exception/InvalidConfirmationTokenException.php +++ b/framework/core/src/Core/Exception/InvalidConfirmationTokenException.php @@ -11,23 +11,7 @@ namespace Flarum\Core\Exception; use Exception; -use Tobscure\JsonApi\Exception\JsonApiSerializableInterface; -class InvalidConfirmationTokenException extends Exception implements JsonApiSerializableInterface +class InvalidConfirmationTokenException extends Exception { - /** - * {@inheritdoc} - */ - public function getStatusCode() - { - return 403; - } - - /** - * {@inheritdoc} - */ - public function getErrors() - { - return ['code' => 'invalid_confirmation_token']; - } } diff --git a/framework/core/src/Core/Exception/PermissionDeniedException.php b/framework/core/src/Core/Exception/PermissionDeniedException.php index 60ad8532d..f45c4cfbd 100644 --- a/framework/core/src/Core/Exception/PermissionDeniedException.php +++ b/framework/core/src/Core/Exception/PermissionDeniedException.php @@ -11,28 +11,7 @@ namespace Flarum\Core\Exception; use Exception; -use Tobscure\JsonApi\Exception\JsonApiSerializableInterface; -class PermissionDeniedException extends Exception implements JsonApiSerializableInterface +class PermissionDeniedException extends Exception { - /** - * Return the HTTP status code to be used for this exception. - * - * @return int - */ - public function getStatusCode() - { - return 401; - } - - /** - * Return an array of errors, formatted as JSON-API error objects. - * - * @see http://jsonapi.org/format/#error-objects - * @return array - */ - public function getErrors() - { - return []; - } } diff --git a/framework/core/src/Core/Exception/ValidationException.php b/framework/core/src/Core/Exception/ValidationException.php index 8669199ff..b32959f59 100644 --- a/framework/core/src/Core/Exception/ValidationException.php +++ b/framework/core/src/Core/Exception/ValidationException.php @@ -11,9 +11,8 @@ namespace Flarum\Core\Exception; use Exception; -use Tobscure\JsonApi\Exception\JsonApiSerializableInterface; -class ValidationException extends Exception implements JsonApiSerializableInterface +class ValidationException extends Exception { protected $messages; @@ -28,24 +27,4 @@ class ValidationException extends Exception implements JsonApiSerializableInterf { return $this->messages; } - - /** - * {@inheritdoc} - */ - public function getStatusCode() - { - return 422; - } - - /** - * {@inheritdoc} - */ - public function getErrors() - { - return array_map(function ($path, $detail) { - $source = ['pointer' => '/data/attributes/' . $path]; - - return compact('source', 'detail'); - }, array_keys($this->messages), $this->messages); - } }