Improve API error handling

- Change 'path' key to 'source.pointer', as per spec
- Add 500 error detail if debug mode is on
This commit is contained in:
Toby Zerner
2015-09-14 15:40:07 +09:30
parent b7d8afe6a4
commit 8cccaaaf6b
5 changed files with 54 additions and 27 deletions

View File

@ -129,7 +129,7 @@ export default class Modal extends Component {
m.redraw(); m.redraw();
if (errors) { if (errors) {
this.$('form [name=' + errors[0].path + ']').select(); this.$('form [name=' + errors[0].source.pointer.replace('/data/attributes/', '') + ']').select();
} else { } else {
this.$('form :input:first').select(); this.$('form :input:first').select();
} }

View File

@ -12,6 +12,8 @@ namespace Flarum\Api;
use Flarum\Core\Users\User; use Flarum\Core\Users\User;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Exception;
use Flarum\Api\Middleware\JsonApiErrors;
class Client class Client
{ {
@ -41,7 +43,13 @@ class Client
/** @var \Flarum\Api\Actions\Action $action */ /** @var \Flarum\Api\Actions\Action $action */
$action = $this->container->make($actionClass); $action = $this->container->make($actionClass);
try {
$response = $action->handle(new Request($input, $actor)); $response = $action->handle(new Request($input, $actor));
} catch (Exception $e) {
$middleware = new JsonApiErrors();
$response = $middleware->handle($e);
}
return new Response($response); return new Response($response);
} }

View File

@ -17,45 +17,52 @@ use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Zend\Diactoros\Response\JsonResponse; use Zend\Diactoros\Response\JsonResponse;
use Zend\Stratigility\ErrorMiddlewareInterface; use Zend\Stratigility\ErrorMiddlewareInterface;
use Flarum\Core;
use Exception;
class JsonApiErrors implements ErrorMiddlewareInterface class JsonApiErrors implements ErrorMiddlewareInterface
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function __invoke($error, Request $request, Response $response, callable $out = null) public function __invoke($e, Request $request, Response $response, callable $out = null)
{ {
if ($error instanceof JsonApiSerializable) { return $this->handle($e);
$status = $error->getStatusCode(); }
$errors = $error->getErrors(); public function handle(Exception $e)
} else if ($error instanceof ValidationException) { {
if ($e instanceof JsonApiSerializable) {
$status = $e->getStatusCode();
$errors = $e->getErrors();
} else if ($e instanceof ValidationException) {
$status = 422; $status = 422;
$errors = $error->errors()->toArray(); $errors = $e->errors()->toArray();
$errors = array_map(function ($field, $messages) { $errors = array_map(function ($field, $messages) {
return [ return [
'detail' => implode("\n", $messages), 'detail' => implode("\n", $messages),
'path' => $field, 'source' => ['pointer' => '/data/attributes/' . $field],
]; ];
}, array_keys($errors), $errors); }, array_keys($errors), $errors);
} else if ($error instanceof ModelNotFoundException) { } else if ($e instanceof ModelNotFoundException) {
$status = 404; $status = 404;
$errors = []; $errors = [];
} else { } else {
$status = 500; $status = 500;
// If it seems to be a valid HTTP status code, we pass on the $error = [
// exception's status. 'code' => $status,
$errorCode = $error->getCode(); 'title' => 'Internal Server Error'
if (is_int($errorCode) && $errorCode >= 400 && $errorCode < 600) { ];
$status = $errorCode;
if (Core::inDebugMode()) {
$error['detail'] = (string) $e;
} }
$errors = [ $errors = [$error];
['title' => $error->getMessage()]
];
} }
// JSON API errors must be collected in an array under the // JSON API errors must be collected in an array under the

View File

@ -12,6 +12,21 @@ namespace Flarum\Core\Exceptions;
use Exception; use Exception;
class InvalidConfirmationTokenException extends Exception class InvalidConfirmationTokenException extends Exception implements JsonApiSerializable
{ {
/**
* {@inheritdoc}
*/
public function getStatusCode()
{
return 403;
}
/**
* {@inheritdoc}
*/
public function getErrors()
{
return ['code' => 'invalid_confirmation_token'];
}
} }

View File

@ -27,9 +27,7 @@ class ValidationException extends Exception implements JsonApiSerializable
} }
/** /**
* Return the HTTP status code to be used for this exception. * {@inheritdoc}
*
* @return int
*/ */
public function getStatusCode() public function getStatusCode()
{ {
@ -37,15 +35,14 @@ class ValidationException extends Exception implements JsonApiSerializable
} }
/** /**
* Return an array of errors, formatted as JSON-API error objects. * {@inheritdoc}
*
* @see http://jsonapi.org/format/#error-objects
* @return array
*/ */
public function getErrors() public function getErrors()
{ {
return array_map(function ($path, $detail) { return array_map(function ($path, $detail) {
return compact('path', 'detail'); $source = ['pointer' => '/data/attributes/' . $path];
return compact('source', 'detail');
}, array_keys($this->messages), $this->messages); }, array_keys($this->messages), $this->messages);
} }
} }