From ab99fff45c489b09971ce3b713628e14e2001e05 Mon Sep 17 00:00:00 2001 From: Yohann Tilotti Date: Fri, 11 Apr 2025 12:59:13 +0200 Subject: [PATCH 1/2] feat(openapi): manage error resources in global --- src/Metadata/ApiResource.php | 6 ++++- src/Metadata/HttpOperation.php | 21 +++------------- src/Metadata/Metadata.php | 19 ++++++++++++++ src/Metadata/Operation.php | 30 +++++++++++++---------- src/OpenApi/Factory/OpenApiFactory.php | 34 +++++++++----------------- 5 files changed, 56 insertions(+), 54 deletions(-) diff --git a/src/Metadata/ApiResource.php b/src/Metadata/ApiResource.php index 81ba3b004d..bd8e22dc20 100644 --- a/src/Metadata/ApiResource.php +++ b/src/Metadata/ApiResource.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Metadata; +use ApiPlatform\Metadata\Exception\ProblemExceptionInterface; use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; use ApiPlatform\State\OptionsInterface; @@ -45,6 +46,7 @@ class ApiResource extends Metadata * @param mixed|null $messenger * @param mixed|null $input * @param mixed|null $output + * @param array>|null $errors */ public function __construct( /** @@ -966,6 +968,7 @@ public function __construct( protected ?bool $strictQueryParameterValidation = null, protected ?bool $hideHydraOperation = null, protected array $extraProperties = [], + ?array $errors = null, ) { parent::__construct( shortName: $shortName, @@ -1005,13 +1008,14 @@ class: $class, provider: $provider, processor: $processor, stateOptions: $stateOptions, + errors: $errors, parameters: $parameters, rules: $rules, policy: $policy, middleware: $middleware, strictQueryParameterValidation: $strictQueryParameterValidation, hideHydraOperation: $hideHydraOperation, - extraProperties: $extraProperties + extraProperties: $extraProperties, ); $this->operations = null === $operations ? null : new Operations($operations); diff --git a/src/Metadata/HttpOperation.php b/src/Metadata/HttpOperation.php index 531030554f..2489eeeb62 100644 --- a/src/Metadata/HttpOperation.php +++ b/src/Metadata/HttpOperation.php @@ -154,7 +154,6 @@ public function __construct( protected bool|OpenApiOperation|Webhook|null $openapi = null, protected ?array $exceptionToStatus = null, protected ?array $links = null, - protected ?array $errors = null, protected ?bool $strictQueryParameterValidation = null, protected ?bool $hideHydraOperation = null, @@ -202,6 +201,7 @@ public function __construct( $provider = null, $processor = null, ?OptionsInterface $stateOptions = null, + ?array $errors = null, array|Parameters|null $parameters = null, array|string|null $rules = null, ?string $policy = null, @@ -254,6 +254,7 @@ class: $class, provider: $provider, processor: $processor, stateOptions: $stateOptions, + errors: $errors, parameters: $parameters, rules: $rules, policy: $policy, @@ -261,7 +262,7 @@ class: $class, queryParameterValidationEnabled: $queryParameterValidationEnabled, strictQueryParameterValidation: $strictQueryParameterValidation, hideHydraOperation: $hideHydraOperation, - extraProperties: $extraProperties + extraProperties: $extraProperties, ); } @@ -621,20 +622,4 @@ public function withLinks(array $links): static return $self; } - - public function getErrors(): ?array - { - return $this->errors; - } - - /** - * @param class-string[] $errors - */ - public function withErrors(array $errors): static - { - $self = clone $this; - $self->errors = $errors; - - return $self; - } } diff --git a/src/Metadata/Metadata.php b/src/Metadata/Metadata.php index e5edb91cea..018b59a958 100644 --- a/src/Metadata/Metadata.php +++ b/src/Metadata/Metadata.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Metadata; +use ApiPlatform\Metadata\Exception\ProblemExceptionInterface; use ApiPlatform\State\OptionsInterface; /** @@ -34,6 +35,7 @@ abstract class Metadata * @param mixed|null $processor * @param Parameters|array $parameters * @param callable|string|array $rules Laravel rules can be a FormRequest class, a callable or an array of rules + * @param array>|null $errors */ public function __construct( protected ?string $shortName = null, @@ -73,6 +75,7 @@ public function __construct( protected $provider = null, protected $processor = null, protected ?OptionsInterface $stateOptions = null, + protected ?array $errors = null, /* * @experimental */ @@ -694,4 +697,20 @@ public function withHideHydraOperation(bool $hideHydraOperation): static return $self; } + + public function getErrors(): ?array + { + return $this->errors; + } + + /** + * @param class-string[] $errors + */ + public function withErrors(array $errors): static + { + $self = clone $this; + $self->errors = $errors; + + return $self; + } } diff --git a/src/Metadata/Operation.php b/src/Metadata/Operation.php index 185048ca4a..4f5a919dee 100644 --- a/src/Metadata/Operation.php +++ b/src/Metadata/Operation.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Metadata; +use ApiPlatform\Metadata\Exception\ProblemExceptionInterface; use ApiPlatform\State\OptionsInterface; /** @@ -47,19 +48,20 @@ abstract class Operation extends Metadata * class?: string|null, * name?: string, * }|string|false|null $output {@see https://api-platform.com/docs/core/dto/#specifying-an-input-or-an-output-data-representation} - * @param string|array|bool|null $mercure {@see https://api-platform.com/docs/core/mercure} - * @param string|bool|null $messenger {@see https://api-platform.com/docs/core/messenger/#dispatching-a-resource-through-the-message-bus} - * @param bool|null $elasticsearch {@see https://api-platform.com/docs/core/elasticsearch/} - * @param bool|null $read {@see https://api-platform.com/docs/core/events/#the-event-system} - * @param bool|null $deserialize {@see https://api-platform.com/docs/core/events/#the-event-system} - * @param bool|null $validate {@see https://api-platform.com/docs/core/events/#the-event-system} - * @param bool|null $write {@see https://api-platform.com/docs/core/events/#the-event-system} - * @param bool|null $serialize {@see https://api-platform.com/docs/core/events/#the-event-system} - * @param bool|null $fetchPartial {@see https://api-platform.com/docs/core/performance/#fetch-partial} - * @param bool|null $forceEager {@see https://api-platform.com/docs/core/performance/#force-eager} - * @param string|callable|null $provider {@see https://api-platform.com/docs/core/state-providers/#state-providers} - * @param string|callable|null $processor {@see https://api-platform.com/docs/core/state-processors/#state-processors} - * @param array $parameters + * @param string|array|bool|null $mercure {@see https://api-platform.com/docs/core/mercure} + * @param string|bool|null $messenger {@see https://api-platform.com/docs/core/messenger/#dispatching-a-resource-through-the-message-bus} + * @param bool|null $elasticsearch {@see https://api-platform.com/docs/core/elasticsearch/} + * @param bool|null $read {@see https://api-platform.com/docs/core/events/#the-event-system} + * @param bool|null $deserialize {@see https://api-platform.com/docs/core/events/#the-event-system} + * @param bool|null $validate {@see https://api-platform.com/docs/core/events/#the-event-system} + * @param bool|null $write {@see https://api-platform.com/docs/core/events/#the-event-system} + * @param bool|null $serialize {@see https://api-platform.com/docs/core/events/#the-event-system} + * @param bool|null $fetchPartial {@see https://api-platform.com/docs/core/performance/#fetch-partial} + * @param bool|null $forceEager {@see https://api-platform.com/docs/core/performance/#force-eager} + * @param string|callable|null $provider {@see https://api-platform.com/docs/core/state-providers/#state-providers} + * @param string|callable|null $processor {@see https://api-platform.com/docs/core/state-processors/#state-processors} + * @param array $parameters + * @param array>|null $errors */ public function __construct( protected ?string $shortName = null, @@ -806,6 +808,7 @@ public function __construct( protected $provider = null, protected $processor = null, protected ?OptionsInterface $stateOptions = null, + ?array $errors = null, array|Parameters|null $parameters = null, array|string|null $rules = null, ?string $policy = null, @@ -853,6 +856,7 @@ class: $class, provider: $provider, processor: $processor, stateOptions: $stateOptions, + errors: $errors, parameters: $parameters, rules: $rules, policy: $policy, diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index fbdc9823b7..2a12bc5a59 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -364,14 +364,12 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection $existingResponses = $openapiOperation->getResponses() ?: []; $overrideResponses = $operation->getExtraProperties()[self::OVERRIDE_OPENAPI_RESPONSES] ?? $this->openApiOptions->getOverrideResponses(); $errors = null; + /** @var array */ + $errorOperations = []; if ($operation instanceof HttpOperation && null !== ($errors = $operation->getErrors())) { - /** @var array */ - $errorOperations = []; foreach ($errors as $error) { $errorOperations[$error] = $this->getErrorResource($error); } - - $openapiOperation = $this->addOperationErrors($openapiOperation, $errorOperations, $resourceMetadataCollection, $schema, $schemas, $operation); } if ($overrideResponses || !$existingResponses) { @@ -385,24 +383,16 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection $successStatus = (string) $operation->getStatus() ?: 201; $openapiOperation = $this->buildOpenApiResponse($existingResponses, $successStatus, \sprintf('%s resource created', $resourceShortName), $openapiOperation, $operation, $responseMimeTypes, $operationOutputSchemas, $resourceMetadataCollection); - if (null === $errors) { - $openapiOperation = $this->addOperationErrors($openapiOperation, [ - $defaultError->withStatus(400)->withDescription('Invalid input'), - $defaultValidationError, - ], $resourceMetadataCollection, $schema, $schemas, $operation); - } + $errorOperations[] = $defaultError->withStatus(400)->withDescription('Invalid input'); + $errorOperations[] = $defaultValidationError; break; case 'PATCH': case 'PUT': $successStatus = (string) $operation->getStatus() ?: 200; $openapiOperation = $this->buildOpenApiResponse($existingResponses, $successStatus, \sprintf('%s resource updated', $resourceShortName), $openapiOperation, $operation, $responseMimeTypes, $operationOutputSchemas, $resourceMetadataCollection); - if (null === $errors) { - $openapiOperation = $this->addOperationErrors($openapiOperation, [ - $defaultError->withStatus(400)->withDescription('Invalid input'), - $defaultValidationError, - ], $resourceMetadataCollection, $schema, $schemas, $operation); - } + $errorOperations[] = $defaultError->withStatus(400)->withDescription('Invalid input'); + $errorOperations[] = $defaultValidationError; break; case 'DELETE': $successStatus = (string) $operation->getStatus() ?: 204; @@ -412,15 +402,15 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection } if ($overrideResponses && !isset($existingResponses[403]) && $operation->getSecurity()) { - $openapiOperation = $this->addOperationErrors($openapiOperation, [ - $defaultError->withStatus(403)->withDescription('Forbidden'), - ], $resourceMetadataCollection, $schema, $schemas, $operation); + $errorOperations[] = $defaultError->withStatus(403)->withDescription('Forbidden'); } if ($overrideResponses && !$operation instanceof CollectionOperationInterface && 'POST' !== $operation->getMethod() && !isset($existingResponses[404]) && null === $errors) { - $openapiOperation = $this->addOperationErrors($openapiOperation, [ - $defaultError->withStatus(404)->withDescription('Not found'), - ], $resourceMetadataCollection, $schema, $schemas, $operation); + $errorOperations[] = $defaultError->withStatus(404)->withDescription('Not found'); + } + + if ($errorOperations) { + $openapiOperation = $this->addOperationErrors($openapiOperation, $errorOperations, $resourceMetadataCollection, $schema, $schemas, $operation); } if (!$openapiOperation->getResponses()) { From fee97bcd8cdfb68ab372ec85f1d51833115cd1bc Mon Sep 17 00:00:00 2001 From: Yohann Tilotti Date: Fri, 11 Apr 2025 13:08:54 +0200 Subject: [PATCH 2/2] feat(openapi): fix order error in resources --- src/Metadata/Delete.php | 5 ++--- src/Metadata/Error.php | 5 ++--- src/Metadata/Get.php | 5 ++--- src/Metadata/GetCollection.php | 5 ++--- src/Metadata/Operation.php | 28 ++++++++++++++-------------- src/Metadata/Patch.php | 7 +++---- src/Metadata/Post.php | 5 ++--- src/Metadata/Put.php | 7 +++---- 8 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/Metadata/Delete.php b/src/Metadata/Delete.php index 7ee8821841..30a37f5ea0 100644 --- a/src/Metadata/Delete.php +++ b/src/Metadata/Delete.php @@ -48,8 +48,6 @@ public function __construct( ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, - ?array $errors = null, - ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -94,6 +92,7 @@ public function __construct( $provider = null, $processor = null, ?OptionsInterface $stateOptions = null, + ?array $errors = null, array|Parameters|null $parameters = null, mixed $rules = null, ?string $policy = null, @@ -131,7 +130,6 @@ public function __construct( exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, - errors: $errors, shortName: $shortName, class: $class, paginationEnabled: $paginationEnabled, @@ -183,6 +181,7 @@ class: $class, strictQueryParameterValidation: $strictQueryParameterValidation, hideHydraOperation: $hideHydraOperation, stateOptions: $stateOptions, + errors: $errors, ); } } diff --git a/src/Metadata/Error.php b/src/Metadata/Error.php index 43325a6096..bb37c34ecd 100644 --- a/src/Metadata/Error.php +++ b/src/Metadata/Error.php @@ -48,8 +48,6 @@ public function __construct( ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, - ?array $errors = null, - ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -94,6 +92,7 @@ public function __construct( $provider = null, $processor = null, ?OptionsInterface $stateOptions = null, + ?array $errors = null, ?bool $hideHydraOperation = null, array $extraProperties = [], ) { @@ -125,7 +124,6 @@ public function __construct( exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, - errors: $errors, shortName: $shortName, class: $class, paginationEnabled: $paginationEnabled, @@ -170,6 +168,7 @@ class: $class, provider: $provider, processor: $processor, stateOptions: $stateOptions, + errors: $errors, hideHydraOperation: $hideHydraOperation, extraProperties: $extraProperties, ); diff --git a/src/Metadata/Get.php b/src/Metadata/Get.php index c87621fa11..eb22d63448 100644 --- a/src/Metadata/Get.php +++ b/src/Metadata/Get.php @@ -48,8 +48,6 @@ public function __construct( ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, - ?array $errors = null, - ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -94,6 +92,7 @@ public function __construct( $provider = null, $processor = null, ?OptionsInterface $stateOptions = null, + ?array $errors = null, array|Parameters|null $parameters = null, mixed $rules = null, ?string $policy = null, @@ -130,7 +129,6 @@ public function __construct( exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, - errors: $errors, shortName: $shortName, class: $class, paginationEnabled: $paginationEnabled, @@ -175,6 +173,7 @@ class: $class, provider: $provider, processor: $processor, stateOptions: $stateOptions, + errors: $errors, parameters: $parameters, rules: $rules, policy: $policy, diff --git a/src/Metadata/GetCollection.php b/src/Metadata/GetCollection.php index 52398d6282..52ff7aee73 100644 --- a/src/Metadata/GetCollection.php +++ b/src/Metadata/GetCollection.php @@ -48,8 +48,6 @@ public function __construct( ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, - ?array $errors = null, - ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -94,6 +92,7 @@ public function __construct( $provider = null, $processor = null, ?OptionsInterface $stateOptions = null, + ?array $errors = null, array|Parameters|null $parameters = null, array|string|null $rules = null, ?string $policy = null, @@ -131,7 +130,6 @@ public function __construct( exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, - errors: $errors, shortName: $shortName, class: $class, paginationEnabled: $paginationEnabled, @@ -183,6 +181,7 @@ class: $class, strictQueryParameterValidation: $strictQueryParameterValidation, hideHydraOperation: $hideHydraOperation, stateOptions: $stateOptions, + errors: $errors, ); } diff --git a/src/Metadata/Operation.php b/src/Metadata/Operation.php index 4f5a919dee..f1699f1efa 100644 --- a/src/Metadata/Operation.php +++ b/src/Metadata/Operation.php @@ -48,20 +48,20 @@ abstract class Operation extends Metadata * class?: string|null, * name?: string, * }|string|false|null $output {@see https://api-platform.com/docs/core/dto/#specifying-an-input-or-an-output-data-representation} - * @param string|array|bool|null $mercure {@see https://api-platform.com/docs/core/mercure} - * @param string|bool|null $messenger {@see https://api-platform.com/docs/core/messenger/#dispatching-a-resource-through-the-message-bus} - * @param bool|null $elasticsearch {@see https://api-platform.com/docs/core/elasticsearch/} - * @param bool|null $read {@see https://api-platform.com/docs/core/events/#the-event-system} - * @param bool|null $deserialize {@see https://api-platform.com/docs/core/events/#the-event-system} - * @param bool|null $validate {@see https://api-platform.com/docs/core/events/#the-event-system} - * @param bool|null $write {@see https://api-platform.com/docs/core/events/#the-event-system} - * @param bool|null $serialize {@see https://api-platform.com/docs/core/events/#the-event-system} - * @param bool|null $fetchPartial {@see https://api-platform.com/docs/core/performance/#fetch-partial} - * @param bool|null $forceEager {@see https://api-platform.com/docs/core/performance/#force-eager} - * @param string|callable|null $provider {@see https://api-platform.com/docs/core/state-providers/#state-providers} - * @param string|callable|null $processor {@see https://api-platform.com/docs/core/state-processors/#state-processors} - * @param array $parameters - * @param array>|null $errors + * @param string|array|bool|null $mercure {@see https://api-platform.com/docs/core/mercure} + * @param string|bool|null $messenger {@see https://api-platform.com/docs/core/messenger/#dispatching-a-resource-through-the-message-bus} + * @param bool|null $elasticsearch {@see https://api-platform.com/docs/core/elasticsearch/} + * @param bool|null $read {@see https://api-platform.com/docs/core/events/#the-event-system} + * @param bool|null $deserialize {@see https://api-platform.com/docs/core/events/#the-event-system} + * @param bool|null $validate {@see https://api-platform.com/docs/core/events/#the-event-system} + * @param bool|null $write {@see https://api-platform.com/docs/core/events/#the-event-system} + * @param bool|null $serialize {@see https://api-platform.com/docs/core/events/#the-event-system} + * @param bool|null $fetchPartial {@see https://api-platform.com/docs/core/performance/#fetch-partial} + * @param bool|null $forceEager {@see https://api-platform.com/docs/core/performance/#force-eager} + * @param string|callable|null $provider {@see https://api-platform.com/docs/core/state-providers/#state-providers} + * @param string|callable|null $processor {@see https://api-platform.com/docs/core/state-processors/#state-processors} + * @param array $parameters + * @param array>|null $errors */ public function __construct( protected ?string $shortName = null, diff --git a/src/Metadata/Patch.php b/src/Metadata/Patch.php index f3e8774349..e34e6a0396 100644 --- a/src/Metadata/Patch.php +++ b/src/Metadata/Patch.php @@ -48,8 +48,6 @@ public function __construct( ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, - ?array $errors = null, - ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -94,6 +92,7 @@ public function __construct( $provider = null, $processor = null, ?OptionsInterface $stateOptions = null, + ?array $errors = null, array|Parameters|null $parameters = null, mixed $rules = null, ?string $policy = null, @@ -131,7 +130,6 @@ public function __construct( exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, - errors: $errors, shortName: $shortName, class: $class, paginationEnabled: $paginationEnabled, @@ -176,13 +174,14 @@ class: $class, provider: $provider, processor: $processor, stateOptions: $stateOptions, + errors: $errors, parameters: $parameters, rules: $rules, policy: $policy, middleware: $middleware, strictQueryParameterValidation: $strictQueryParameterValidation, hideHydraOperation: $hideHydraOperation, - extraProperties: $extraProperties + extraProperties: $extraProperties, ); } } diff --git a/src/Metadata/Post.php b/src/Metadata/Post.php index ebc6f3edbc..4a788a54b1 100644 --- a/src/Metadata/Post.php +++ b/src/Metadata/Post.php @@ -48,8 +48,6 @@ public function __construct( ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, - ?array $errors = null, - ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -94,6 +92,7 @@ public function __construct( $provider = null, $processor = null, ?OptionsInterface $stateOptions = null, + ?array $errors = null, array|Parameters|null $parameters = null, mixed $rules = null, ?string $policy = null, @@ -132,7 +131,6 @@ public function __construct( exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, - errors: $errors, shortName: $shortName, class: $class, paginationEnabled: $paginationEnabled, @@ -177,6 +175,7 @@ class: $class, provider: $provider, processor: $processor, stateOptions: $stateOptions, + errors: $errors, parameters: $parameters, rules: $rules, policy: $policy, diff --git a/src/Metadata/Put.php b/src/Metadata/Put.php index 2df0c5bf3e..1a3d2024bd 100644 --- a/src/Metadata/Put.php +++ b/src/Metadata/Put.php @@ -48,8 +48,6 @@ public function __construct( ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $links = null, - ?array $errors = null, - ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -94,6 +92,7 @@ public function __construct( $provider = null, $processor = null, ?OptionsInterface $stateOptions = null, + ?array $errors = null, array|Parameters|null $parameters = null, mixed $rules = null, ?string $policy = null, @@ -132,7 +131,6 @@ public function __construct( exceptionToStatus: $exceptionToStatus, queryParameterValidationEnabled: $queryParameterValidationEnabled, links: $links, - errors: $errors, shortName: $shortName, class: $class, paginationEnabled: $paginationEnabled, @@ -177,13 +175,14 @@ class: $class, provider: $provider, processor: $processor, stateOptions: $stateOptions, + errors: $errors, parameters: $parameters, rules: $rules, policy: $policy, middleware: $middleware, strictQueryParameterValidation: $strictQueryParameterValidation, hideHydraOperation: $hideHydraOperation, - extraProperties: $extraProperties + extraProperties: $extraProperties, ); }