Skip to content

Commit

Permalink
api: set rfc_7807_compliant_errors to true
Browse files Browse the repository at this point in the history
  • Loading branch information
BacLuc committed Jan 9, 2025
1 parent 39a70ab commit ec4dbb5
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 310 deletions.
2 changes: 1 addition & 1 deletion api/config/packages/api_platform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ api_platform:
stateless: true
extra_properties:
standard_put: true
rfc_7807_compliant_errors: false
rfc_7807_compliant_errors: true
pagination_enabled: false
itemOperations: [ 'get', 'patch', 'delete' ]
collection_operations:
Expand Down
6 changes: 3 additions & 3 deletions api/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ services:
App\Serializer\Normalizer\CollectionItemsNormalizer:
decorates: 'api_platform.hal.normalizer.collection'

App\Serializer\Normalizer\TranslationConstraintViolationListNormalizer:
App\State\ValidationErrorProvider:
decorates: 'api_platform.validator.state.error_provider'
arguments:
- '@api_platform.hydra.normalizer.constraint_violation_list'
- '@api_platform.problem.normalizer.constraint_violation_list'
- '@.inner'

App\Serializer\SerializerContextBuilder:
decorates: 'api_platform.serializer.context_builder'
Expand Down
30 changes: 30 additions & 0 deletions api/src/DTO/ValidationError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace App\DTO;

use ApiPlatform\ApiResource\Error;
use ApiPlatform\Metadata\ApiProperty;

class ValidationError extends Error {
public function __construct(
private readonly ?string $title,
private readonly int $status,
private readonly ?string $detail,
private readonly ?string $instance,
private readonly array $violations = [],
private readonly string $type = 'about:blank',
) {
parent::__construct(
type: $this->type,
title: $this->title,
status: $this->status,
detail: $this->detail,
instance: $this->instance,
);
}

#[ApiProperty]
public function getViolations(): array {
return $this->violations;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

namespace App\Serializer\Normalizer\Error;

use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationInterface;

class TranslationInfoOfConstraintViolation {
public function extract(ConstraintViolation $constraintViolation): TranslationInfo {
public function extract(ConstraintViolationInterface $constraintViolation): TranslationInfo {
$constraint = $constraintViolation->getConstraint();
$constraintClass = get_class($constraint);
$key = str_replace('\\', '.', $constraintClass);
Expand Down

This file was deleted.

35 changes: 35 additions & 0 deletions api/src/Serializer/Normalizer/ValidationErrorNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Serializer\Normalizer;

use ApiPlatform\Problem\Serializer\ErrorNormalizerTrait;
use App\DTO\ValidationError;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

class ValidationErrorNormalizer implements NormalizerInterface {
use ErrorNormalizerTrait;

public const TYPE = 'type';
public const TITLE = 'title';
private array $defaultContext = [
self::TYPE => 'https://tools.ietf.org/html/rfc2616#section-10',
self::TITLE => 'An error occurred',
];

public function normalize(mixed $data, ?string $format = null, array $context = []): null|array|\ArrayObject|bool|float|int|string {
return [
'type' => $context[self::TYPE] ?? $this->defaultContext[self::TYPE],
'title' => $context[self::TITLE] ?? $this->defaultContext[self::TITLE],
'detail' => $data->getDetail(),
'violations' => $data->getViolations(),
];
}

public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool {
return $data instanceof ValidationError;
}

public function getSupportedTypes(?string $format): array {
return [ValidationError::class => true];
}
}
74 changes: 74 additions & 0 deletions api/src/State/ValidationErrorProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace App\State;

use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ApiResource\Error;
use ApiPlatform\State\ProviderInterface;
use ApiPlatform\Validator\Exception\ValidationException;
use App\DTO\ValidationError;
use App\Serializer\Normalizer\Error\TranslationInfoOfConstraintViolation;
use App\Service\TranslateToAllLocalesService;

/**
* @psalm-suppress MissingTemplateParam
*/
class ValidationErrorProvider implements ProviderInterface {
public function __construct(
private readonly ProviderInterface $decorated,
private readonly TranslationInfoOfConstraintViolation $translationInfoOfConstraintViolation,
private readonly TranslateToAllLocalesService $translateToAllLocalesService
) {}

/**
* @psalm-suppress InvalidReturnStatement
*/
public function provide(Operation $operation, array $uriVariables = [], array $context = []): null|array|object {
$request = $context['request'];
$exception = $request?->attributes->get('exception');
if (!($request ?? null) || !$operation instanceof HttpOperation || null === $exception) {
throw new \RuntimeException('Not an HTTP request');
}

$status = $operation->getStatus() ?? 500;
$error = Error::createFromException($exception, $status);
if (!$exception instanceof ValidationException) {
return $this->decorated->provide($operation, $uriVariables, $context);
}

/**
* @var ValidationException $exception
*/
$violationInfos = [];
foreach ($exception->getConstraintViolationList() as $violation) {
$violationInfo = $this->translationInfoOfConstraintViolation->extract($violation);
$translations = $this->translateToAllLocalesService->translate(
$violation->getMessageTemplate(),
array_merge(
$violation->getPlural() ? ['%count%' => $violation->getPlural()] : [],
$violation->getParameters()
)
);

$violationInfos[] = [
'code' => $violation->getCode(),
'propertyPath' => $violation->getPropertyPath(),
'message' => $violation->getMessage(),
'i18n' => [
...(array) $violationInfo,
'translations' => $translations,
],
];
}

return new ValidationError(
type: $error->getType(),
title: $error->getTitle(),
status: $status,
detail: $error->getDetail(),
instance: $error->getInstance(),
violations: $violationInfos
);
}
}
Loading

0 comments on commit ec4dbb5

Please sign in to comment.