Merge pull request #606 from flarum/error-handling

Use exception handlers instead of JsonApiSerializableInterface
This commit is contained in:
Toby Zerner 2015-10-27 11:43:07 +10:30
commit 640c6199ef
14 changed files with 326 additions and 141 deletions

View File

@ -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;
});
}
/**

View File

@ -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;
}
}

View File

@ -0,0 +1,45 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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());
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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]);
}
}

View File

@ -0,0 +1,45 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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);
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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]);
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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]);
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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]);
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* 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);
}
}

View File

@ -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);
}
}

View File

@ -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 [];
}
}

View File

@ -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'];
}
}

View File

@ -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 [];
}
}

View File

@ -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);
}
}