Skip to content

Commit

Permalink
Return exception response (#37)
Browse files Browse the repository at this point in the history
* Introducing exception subscriber to create api error responses

* Remove test parameter

* Apply php-cs-fixer changes

* Fix tests

* Apply php-cs-fixer changes

* Add abstract ApiException

* Apply php-cs-fixer changes

* Declare strict

---------

Co-authored-by: mattamon <[email protected]>
  • Loading branch information
mattamon and mattamon authored Apr 22, 2024
1 parent 1d8bdcb commit f576568
Show file tree
Hide file tree
Showing 26 changed files with 239 additions and 40 deletions.
9 changes: 9 additions & 0 deletions config/event_subscribers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

#Subscriber
Pimcore\Bundle\StudioApiBundle\EventSubscriber\ApiExceptionSubscriber:
tags: [ 'kernel.event_subscriber' ]
35 changes: 35 additions & 0 deletions src/Attributes/Response/Error/BadRequestResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error;

use Attribute;
use OpenApi\Attributes\JsonContent;
use OpenApi\Attributes\Response;
use Pimcore\Bundle\StudioApiBundle\Response\Schema\Error;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class BadRequestResponse extends Response
{
public function __construct()
{
parent::__construct(
response: 400,
description: 'Bad Request',
content: new JsonContent(ref: Error::class, example: ['message' => 'Something bad you did'])
);
}
}
35 changes: 35 additions & 0 deletions src/Attributes/Response/Error/MethodNotAllowedResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error;

use Attribute;
use OpenApi\Attributes\JsonContent;
use OpenApi\Attributes\Response;
use Pimcore\Bundle\StudioApiBundle\Response\Schema\Error;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class MethodNotAllowedResponse extends Response
{
public function __construct()
{
parent::__construct(
response: 405,
description: 'Bad Request',
content: new JsonContent(ref: Error::class, example: ['message' => 'Using the wrong method you are'])
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioApiBundle\Attributes\Response;
namespace Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error;

use Attribute;
use OpenApi\Attributes\JsonContent;
use OpenApi\Attributes\Response;
use Pimcore\Bundle\StudioApiBundle\Response\Schema\Unauthorized;
use Pimcore\Bundle\StudioApiBundle\Response\Schema\Error;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class UnauthorizedResponse extends Response
Expand All @@ -29,7 +29,7 @@ public function __construct()
parent::__construct(
response: 401,
description: 'Unauthorized',
content: new JsonContent(ref: Unauthorized::class)
content: new JsonContent(ref: Error::class, example: ['message' => 'Computer says no'])
);
}
}
6 changes: 5 additions & 1 deletion src/Controller/Api/Assets/CollectionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
use Pimcore\Bundle\StudioApiBundle\Attributes\Parameters\Query\PathIncludeParentParameter;
use Pimcore\Bundle\StudioApiBundle\Attributes\Parameters\Query\PathParameter;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Content\CollectionJson;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\BadRequestResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\MethodNotAllowedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Property\AnyOfAsset;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\SuccessResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Config\Tags;
use Pimcore\Bundle\StudioApiBundle\Controller\Api\AbstractApiController;
use Pimcore\Bundle\StudioApiBundle\Controller\Trait\PaginatedResponseTrait;
Expand Down Expand Up @@ -81,7 +83,9 @@ public function __construct(
description: 'Paginated assets with total count as header param',
content: new CollectionJson(new AnyOfAsset())
)]
#[BadRequestResponse]
#[UnauthorizedResponse]
#[MethodNotAllowedResponse]
public function getAssets(#[MapQueryString] Parameters $parameters): JsonResponse
{
$assetQuery = $this->filterService->applyFilters(
Expand Down
4 changes: 3 additions & 1 deletion src/Controller/Api/Assets/GetController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
use OpenApi\Attributes\Get;
use Pimcore\Bundle\StudioApiBundle\Attributes\Parameters\Path\IdParameter;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Content\OneOfAssetJson;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\MethodNotAllowedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\SuccessResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Config\Tags;
use Pimcore\Bundle\StudioApiBundle\Controller\Api\AbstractApiController;
use Pimcore\Bundle\StudioApiBundle\Service\AssetSearchServiceInterface;
Expand Down Expand Up @@ -56,6 +57,7 @@ public function __construct(
content: new OneOfAssetJson()
)]
#[UnauthorizedResponse]
#[MethodNotAllowedResponse]
public function getAssetById(int $id): JsonResponse
{
return $this->jsonResponse($this->assetSearchService->getAssetById($id));
Expand Down
5 changes: 4 additions & 1 deletion src/Controller/Api/Authorization/AuthorizationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
use OpenApi\Attributes\Post;
use Pimcore\Bundle\StudioApiBundle\Attributes\Request\CredentialsRequestBody;
use Pimcore\Bundle\StudioApiBundle\Attributes\Request\TokenRequestBody;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\MethodNotAllowedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\SuccessResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Config\Tags;
use Pimcore\Bundle\StudioApiBundle\Controller\Api\AbstractApiController;
use Pimcore\Bundle\StudioApiBundle\Request\Credentials;
Expand Down Expand Up @@ -61,6 +62,7 @@ public function __construct(
content: new JsonContent(ref: Token::class)
)]
#[UnauthorizedResponse]
#[MethodNotAllowedResponse]
public function login(#[MapRequestPayload] Credentials $credentials): JsonResponse
{
/** @var User $user */
Expand All @@ -84,6 +86,7 @@ public function login(#[MapRequestPayload] Credentials $credentials): JsonRespon
content: new JsonContent(ref: Token::class)
)]
#[UnauthorizedResponse]
#[MethodNotAllowedResponse]
public function refresh(#[MapRequestPayload] Refresh $refresh): JsonResponse
{
$tokenInfo = $this->tokenService->refreshToken($refresh->getToken());
Expand Down
6 changes: 5 additions & 1 deletion src/Controller/Api/DataObjects/CollectionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
use Pimcore\Bundle\StudioApiBundle\Attributes\Parameters\Query\PathIncludeParentParameter;
use Pimcore\Bundle\StudioApiBundle\Attributes\Parameters\Query\PathParameter;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Content\CollectionJson;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\BadRequestResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\MethodNotAllowedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Property\DataObjectCollection;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\SuccessResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Config\Tags;
use Pimcore\Bundle\StudioApiBundle\Controller\Api\AbstractApiController;
use Pimcore\Bundle\StudioApiBundle\Controller\Trait\PaginatedResponseTrait;
Expand Down Expand Up @@ -80,7 +82,9 @@ public function __construct(
description: 'Paginated data objects with total count as header param',
content: new CollectionJson(new DataObjectCollection())
)]
#[BadRequestResponse]
#[UnauthorizedResponse]
#[MethodNotAllowedResponse]
public function getDataObjects(#[MapQueryString] DataObjectParameters $parameters): JsonResponse
{

Expand Down
4 changes: 3 additions & 1 deletion src/Controller/Api/TranslationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
use OpenApi\Attributes\JsonContent;
use OpenApi\Attributes\Post;
use Pimcore\Bundle\StudioApiBundle\Attributes\Request\TranslationRequestBody;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\MethodNotAllowedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\SuccessResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Config\Tags;
use Pimcore\Bundle\StudioApiBundle\Request\Query\Translation;
use Pimcore\Bundle\StudioApiBundle\Service\TranslatorServiceInterface;
Expand Down Expand Up @@ -59,6 +60,7 @@ public function __construct(
content: new JsonContent(ref: Translation::class)
)]
#[UnauthorizedResponse]
#[MethodNotAllowedResponse]
public function getTranslations(
#[MapRequestPayload] Translation $translation,
): JsonResponse {
Expand Down
1 change: 1 addition & 0 deletions src/DependencyInjection/PimcoreStudioApiExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function load(array $configs, ContainerBuilder $container): void
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../../config'));
$loader->load('services.yaml');
$loader->load('filters.yaml');
$loader->load('event_subscribers.yaml');

$definition = $container->getDefinition(TokenServiceInterface::class);
$definition->setArgument('$tokenLifetime', $config['api_token']['lifetime']);
Expand Down
60 changes: 60 additions & 0 deletions src/EventSubscriber/ApiExceptionSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioApiBundle\EventSubscriber;

use Pimcore\Bundle\StudioApiBundle\Controller\Api\AbstractApiController;
use Pimcore\Bundle\StudioApiBundle\Exception\ApiExceptionInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;

/**
* @internal
*/
final class ApiExceptionSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'kernel.exception' => 'onKernelException',
];
}

public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
$request = $event->getRequest();

if(!$exception instanceof ApiExceptionInterface && !$this->isStudioApiCall($request->getPathInfo())) {
return;
}
/** @var ApiExceptionInterface $exception */
$response = new JsonResponse(
[
'message' => $exception->getMessage(),
],
$exception->getStatusCode()
);

$event->setResponse($response);
}

private function isStudioApiCall(string $path): bool
{
return str_starts_with($path, AbstractApiController::API_PATH);
}
}
23 changes: 23 additions & 0 deletions src/Exception/AbstractApiException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioApiBundle\Exception;

use Symfony\Component\HttpKernel\Exception\HttpException;

abstract class AbstractApiException extends HttpException implements ApiExceptionInterface
{
}
4 changes: 1 addition & 3 deletions src/Exception/AccessDeniedException.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@

namespace Pimcore\Bundle\StudioApiBundle\Exception;

use Symfony\Component\HttpKernel\Exception\HttpException;

/**
* @internal
*/
final class AccessDeniedException extends HttpException
final class AccessDeniedException extends AbstractApiException
{
}
27 changes: 27 additions & 0 deletions src/Exception/ApiExceptionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioApiBundle\Exception;

/**
* @internal
*/
interface ApiExceptionInterface
{
public function getStatusCode(): int;

public function getMessage(): string;
}
4 changes: 1 addition & 3 deletions src/Exception/InvalidFilterTypeException.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@

namespace Pimcore\Bundle\StudioApiBundle\Exception;

use Exception;

/**
* @internal
*/
final class InvalidFilterTypeException extends Exception
final class InvalidFilterTypeException extends AbstractApiException
{
}
4 changes: 1 addition & 3 deletions src/Exception/InvalidQueryTypeException.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@

namespace Pimcore\Bundle\StudioApiBundle\Exception;

use Exception;

/**
* @internal
*/
final class InvalidQueryTypeException extends Exception
final class InvalidQueryTypeException extends AbstractApiException
{
}
4 changes: 1 addition & 3 deletions src/Exception/InvalidSearchException.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

namespace Pimcore\Bundle\StudioApiBundle\Exception;

use Exception;

final class InvalidSearchException extends Exception
final class InvalidSearchException extends AbstractApiException
{
}
Loading

0 comments on commit f576568

Please sign in to comment.