diff --git a/CHANGELOG.md b/CHANGELOG.md index b53b404..43f33e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip * [BC] DefaultHandler response code 404 instead 400 * [BC] Added Container to API Decider * [BC] Output Configurator, Allows different methods for output configuration. Needs to be added to config services. +* [BC] Error handler, Allows for custom error handling of handle method. Needs to be added to config services. * Query configurator rework #### Added diff --git a/README.md b/README.md index f164740..4ea800e 100644 --- a/README.md +++ b/README.md @@ -40,13 +40,20 @@ application: Api: Tomaj\NetteApi\Presenters\*Presenter ``` -Then register your preffered output configurator in *config.neon* services: +Register your preferred output configurator in *config.neon* services: ```neon services: apiOutputConfigurator: Tomaj\NetteApi\Output\Configurator\DebuggerConfigurator ``` +Register your preferred error handler in *config.neon* services: + +```neon +services: + apiErrorHandler: Tomaj\NetteApi\Error\DefaultErrorHandler +``` + And add route to you RouterFactory: ```php diff --git a/src/Error/DefaultErrorHandler.php b/src/Error/DefaultErrorHandler.php new file mode 100644 index 0000000..9dcb512 --- /dev/null +++ b/src/Error/DefaultErrorHandler.php @@ -0,0 +1,66 @@ +outputConfigurator = $outputConfigurator; + } + + public function handle(Throwable $exception, array $params): JsonApiResponse + { + Debugger::log($exception, Debugger::EXCEPTION); + if ($this->outputConfigurator->showErrorDetail()) { + $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $exception->getMessage()]); + } else { + $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error']); + } + return $response; + } + + public function handleInputParams(array $errors): JsonApiResponse + { + if ($this->outputConfigurator->showErrorDetail()) { + $response = new JsonApiResponse(Response::S400_BAD_REQUEST, ['status' => 'error', 'message' => 'wrong input', 'detail' => $errors]); + } else { + $response = new JsonApiResponse(Response::S400_BAD_REQUEST, ['status' => 'error', 'message' => 'wrong input']); + } + return $response; + } + + public function handleSchema(array $errors, array $params): JsonApiResponse + { + Debugger::log($errors, Debugger::ERROR); + + if ($this->outputConfigurator->showErrorDetail()) { + $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $errors]); + } else { + $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error']); + } + return $response; + } + + public function handleAuthorization(ApiAuthorizationInterface $auth, array $params): JsonApiResponse + { + return new JsonApiResponse(Response::S401_UNAUTHORIZED, ['status' => 'error', 'message' => $auth->getErrorMessage()]); + } + + public function handleAuthorizationException(Throwable $exception, array $params): JsonApiResponse + { + return new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => $exception->getMessage()]); + } +} diff --git a/src/Error/ErrorHandlerInterface.php b/src/Error/ErrorHandlerInterface.php new file mode 100644 index 0000000..91974e4 --- /dev/null +++ b/src/Error/ErrorHandlerInterface.php @@ -0,0 +1,39 @@ + $params + */ + public function handle(Throwable $exception, array $params): JsonApiResponse; + + /** + * @param array $errors + * @param array $params + */ + public function handleInputParams(array $errors): JsonApiResponse; + + /** + * @param array $errors + * @param array $params + */ + public function handleSchema(array $errors, array $params): JsonApiResponse; + + /** + * @param array $params + */ + public function handleAuthorization(ApiAuthorizationInterface $auth, array $params): JsonApiResponse; + + /** + * @param array $params + */ + public function handleAuthorizationException(Throwable $exception, array $params): JsonApiResponse; +} diff --git a/src/Presenters/ApiPresenter.php b/src/Presenters/ApiPresenter.php index 254bec9..ea32b33 100644 --- a/src/Presenters/ApiPresenter.php +++ b/src/Presenters/ApiPresenter.php @@ -14,14 +14,13 @@ use Tomaj\NetteApi\Api; use Tomaj\NetteApi\ApiDecider; use Tomaj\NetteApi\Authorization\ApiAuthorizationInterface; +use Tomaj\NetteApi\Error\ErrorHandlerInterface; use Tomaj\NetteApi\Logger\ApiLoggerInterface; use Tomaj\NetteApi\Misc\IpDetectorInterface; +use Tomaj\NetteApi\Output\Configurator\ConfiguratorInterface; use Tomaj\NetteApi\Output\OutputInterface; use Tomaj\NetteApi\Params\ParamsProcessor; use Tomaj\NetteApi\RateLimit\RateLimitInterface; -use Tomaj\NetteApi\Response\JsonApiResponse; -use Tracy\Debugger; -use Tomaj\NetteApi\Output\Configurator\ConfiguratorInterface; final class ApiPresenter implements IPresenter { @@ -37,6 +36,9 @@ final class ApiPresenter implements IPresenter /** @var ConfiguratorInterface @inject */ public $outputConfigurator; + /** @var ErrorHandlerInterface @inject */ + public $errorHandler; + /** * CORS header settings * @@ -71,14 +73,10 @@ public function run(Request $request): IResponse $api = $this->getApi($request); $handler = $api->getHandler(); + $authorization = $api->getAuthorization(); $rateLimit = $api->getRateLimit(); - $authResponse = $this->checkAuth($authorization); - if ($authResponse !== null) { - return $authResponse; - } - $rateLimitResponse = $this->checkRateLimit($rateLimit); if ($rateLimitResponse !== null) { return $rateLimitResponse; @@ -86,15 +84,17 @@ public function run(Request $request): IResponse $paramsProcessor = new ParamsProcessor($handler->params()); if ($paramsProcessor->isError()) { - $this->response->setCode(Response::S400_BAD_REQUEST); - if ($this->outputConfigurator->showErrorDetail()) { - $response = new JsonResponse(['status' => 'error', 'message' => 'wrong input', 'detail' => $paramsProcessor->getErrors()]); - } else { - $response = new JsonResponse(['status' => 'error', 'message' => 'wrong input']); - } + $response = $this->errorHandler->handleInputParams($paramsProcessor->getErrors()); + $this->response->setCode($response->getCode()); return $response; } $params = $paramsProcessor->getValues(); + + $authResponse = $this->checkAuth($authorization, $params); + if ($authResponse !== null) { + return $authResponse; + } + try { $response = $handler->handle($params); $code = $response->getCode(); @@ -116,18 +116,13 @@ public function run(Request $request): IResponse $outputValidatorErrors[] = $validationResult->getErrors(); } if (!$outputValid) { - Debugger::log($outputValidatorErrors, Debugger::ERROR); - $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error', 'details' => $outputValidatorErrors]); + $response = $this->errorHandler->handleSchema($outputValidatorErrors, $params); + $code = $response->getCode(); } } } catch (Throwable $exception) { - if ($this->outputConfigurator->showErrorDetail()) { - $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $exception->getMessage()]); - } else { - $response = new JsonApiResponse(Response::S500_INTERNAL_SERVER_ERROR, ['status' => 'error', 'message' => 'Internal server error']); - } + $response = $this->errorHandler->handle($exception, $params); $code = $response->getCode(); - Debugger::log($exception, Debugger::EXCEPTION); } $end = microtime(true); @@ -153,11 +148,18 @@ private function getApi(Request $request): Api ); } - private function checkAuth(ApiAuthorizationInterface $authorization): ?IResponse + private function checkAuth(ApiAuthorizationInterface $authorization, array $params): ?IResponse { - if (!$authorization->authorized()) { - $this->response->setCode(Response::S403_FORBIDDEN); - return new JsonResponse(['status' => 'error', 'message' => $authorization->getErrorMessage()]); + try { + if (!$authorization->authorized()) { + $response = $this->errorHandler->handleAuthorization($authorization, $params); + $this->response->setCode($response->getCode()); + return $response; + } + } catch (Throwable $exception) { + $response = $this->errorHandler->handleAuthorizationException($exception, $params); + $this->response->setCode($response->getCode()); + return $response; } return null; } diff --git a/tests/Presenters/ApiPresenterTest.php b/tests/Presenters/ApiPresenterTest.php index 804ace4..2d194d5 100644 --- a/tests/Presenters/ApiPresenterTest.php +++ b/tests/Presenters/ApiPresenterTest.php @@ -12,6 +12,7 @@ use Tomaj\NetteApi\Authorization\BearerTokenAuthorization; use Tomaj\NetteApi\Authorization\NoAuthorization; use Tomaj\NetteApi\EndpointIdentifier; +use Tomaj\NetteApi\Error\DefaultErrorHandler; use Tomaj\NetteApi\Handlers\AlwaysOkHandler; use Tomaj\NetteApi\Handlers\EchoHandler; use Tomaj\NetteApi\Misc\IpDetector; @@ -41,6 +42,7 @@ public function testSimpleResponse() $presenter->response = new HttpResponse(); $presenter->context = new Container(); $presenter->outputConfigurator = new DebuggerConfigurator(); + $presenter->errorHandler = new DefaultErrorHandler($presenter->outputConfigurator); $request = new Request('Api:Api:default', 'GET', ['version' => '1', 'package' => 'test', 'apiAction' => 'api']); $result = $presenter->run($request); @@ -65,6 +67,7 @@ public function testWithAuthorization() $presenter->response = new HttpResponse(); $presenter->context = new Container(); $presenter->outputConfigurator = new DebuggerConfigurator(); + $presenter->errorHandler = new DefaultErrorHandler($presenter->outputConfigurator); $request = new Request('Api:Api:default', 'GET', ['version' => '1', 'package' => 'test', 'apiAction' => 'api']); $result = $presenter->run($request); @@ -87,6 +90,7 @@ public function testWithParams() $presenter->response = new HttpResponse(); $presenter->context = new Container(); $presenter->outputConfigurator = new DebuggerConfigurator(); + $presenter->errorHandler = new DefaultErrorHandler($presenter->outputConfigurator); Debugger::$productionMode = Debugger::PRODUCTION; @@ -117,6 +121,7 @@ public function testWithOutputs() $presenter->response = new HttpResponse(); $presenter->context = new Container(); $presenter->outputConfigurator = new DebuggerConfigurator(); + $presenter->errorHandler = new DefaultErrorHandler($presenter->outputConfigurator); $request = new Request('Api:Api:default', 'GET', ['version' => '1', 'package' => 'test', 'apiAction' => 'api']); $result = $presenter->run($request);