Created
March 7, 2023 15:16
-
-
Save nesl247/3ae6b1f9575e4dcd0960ca4a9016434e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
```php | |
throw new Api\Platform\Errors\Problems\GenericProblem(type: 'urn:some:example', title: 'This title should never change', status: 500); | |
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
```php | |
<?php | |
declare(strict_types=1); | |
namespace App\Platform\Errors\Problems; | |
use League\Uri\Uri; | |
use Symfony\Component\HttpFoundation\Response; | |
use Webmozart\Assert\Assert; | |
class GenericProblem extends \RuntimeException implements Problem | |
{ | |
private readonly Uri $type; | |
public function __construct( | |
Uri|string $type, | |
private readonly string $title, | |
private readonly int $status, | |
private readonly ?string $detail = null, | |
private readonly ?Uri $instance = null, | |
) { | |
Assert::oneOf($this->status, array_keys(Response::$statusTexts)); | |
$this->type = $type instanceof Uri ? $type : Uri::createFromString($type); | |
parent::__construct($this->title, $this->status); | |
} | |
public function getType(): Uri | |
{ | |
return $this->type; | |
} | |
public function getTitle(): string | |
{ | |
return $this->title; | |
} | |
public function getStatus(): int | |
{ | |
return $this->status; | |
} | |
public function getDetail(): ?string | |
{ | |
return $this->detail; | |
} | |
public function getInstance(): ?Uri | |
{ | |
return $this->instance; | |
} | |
public function getStatusCode(): int | |
{ | |
return $this->status; | |
} | |
public function getHeaders(): array | |
{ | |
return []; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* This file is part of the API Platform project. | |
* | |
* (c) Kévin Dunglas <[email protected]> | |
* | |
* For the full copyright and license information, please view the LICENSE | |
* file that was distributed with this source code. | |
*/ | |
declare(strict_types=1); | |
namespace App\Platform\Errors; | |
use ApiPlatform\Api\UrlGeneratorInterface; | |
use ApiPlatform\Serializer\AbstractConstraintViolationListNormalizer; | |
use Symfony\Component\Serializer\NameConverter\NameConverterInterface; | |
use Symfony\Contracts\Translation\TranslatorInterface; | |
/** | |
* @psalm-suppress InternalClass | |
*/ | |
final class HydraConstraintViolationListNormalizer extends AbstractConstraintViolationListNormalizer | |
{ | |
public const FORMAT = 'jsonld'; | |
public function __construct( | |
private readonly UrlGeneratorInterface $urlGenerator, | |
private readonly TranslatorInterface $translator, | |
array $serializePayloadFields = null, | |
NameConverterInterface $nameConverter = null | |
) { | |
/** @psalm-suppress InternalMethod */ | |
parent::__construct($serializePayloadFields, $nameConverter); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null | |
{ | |
/** @psalm-suppress InternalMethod */ | |
[$messages, $violations] = $this->getMessagesAndViolations($object); | |
return [ | |
'@context' => $this->urlGenerator->generate('api_jsonld_context', ['shortName' => 'ConstraintViolationList']), | |
'@type' => 'ConstraintViolationList', | |
'type' => 'urn:syntage:errors:request:invalid_input', | |
'title' => $this->translator->trans('request.invalid_input'), | |
'violations' => $violations, | |
]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* This file is part of the API Platform project. | |
* | |
* (c) Kévin Dunglas <[email protected]> | |
* | |
* For the full copyright and license information, please view the LICENSE | |
* file that was distributed with this source code. | |
*/ | |
declare(strict_types=1); | |
namespace App\Platform\Errors; | |
use ApiPlatform\Api\UrlGeneratorInterface; | |
use App\Platform\Errors\Problems\Problem; | |
use League\Uri\Uri; | |
use Symfony\Component\DependencyInjection\Attribute\Autowire; | |
use Symfony\Component\ErrorHandler\Exception\FlattenException; | |
use Symfony\Component\HttpFoundation\Response; | |
use Symfony\Component\HttpKernel\Exception\HttpException; | |
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; | |
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; | |
use Symfony\Component\String\UnicodeString; | |
use Symfony\Contracts\Translation\TranslatorInterface; | |
use Webmozart\Assert\Assert; | |
/** | |
* Converts {@see \Exception} or {@see FlattenException} to a Hydra error representation. | |
* | |
* @author Kévin Dunglas <[email protected]> | |
* @author Samuel ROZE <[email protected]> | |
*/ | |
final class HydraErrorNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface | |
{ | |
public const FORMAT = 'jsonld'; | |
public const TITLE = 'title'; | |
public function __construct( | |
private readonly UrlGeneratorInterface $urlGenerator, | |
private readonly TranslatorInterface $translator, | |
#[Autowire(value: '%kernel.debug%')] | |
private readonly bool $debug = false, | |
array $defaultContext = [], | |
) { | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null | |
{ | |
Assert::isInstanceOfAny($object, [\Throwable::class, FlattenException::class]); | |
$data = [ | |
'@context' => $this->urlGenerator->generate('api_jsonld_context', ['shortName' => 'Error']), | |
'@type' => 'hydraError', | |
'type' => $this->getType($object, $this->debug)->toString(), | |
'title' => $this->translator->trans($this->getTitle($object, $context, $this->debug)), | |
]; | |
$detail = $this->getDetail($object); | |
if ($detail) { | |
$data['detail'] = $this->translator->trans($detail); | |
} | |
$instance = $this->getInstance($object); | |
if ($instance) { | |
$data['instance'] = $instance; | |
} | |
if ($this->debug) { | |
if (!$object instanceof FlattenException) { | |
$flattenedException = FlattenException::createFromThrowable($object); | |
} else { | |
$flattenedException = $object; | |
} | |
$trace = $flattenedException->getTrace(); | |
if ($trace) { | |
$data['trace'] = $trace; | |
} | |
} | |
return $data; | |
} | |
private function getType(\Throwable|FlattenException $object, bool $debug = false): Uri | |
{ | |
if ($object instanceof Problem) { | |
return $object->getType(); | |
} | |
if ($object instanceof HttpException) { | |
$statusText = new UnicodeString(Response::$statusTexts[$object->getStatusCode()]); | |
return Uri::createFromString("urn:errors:{$statusText->snake()}"); | |
} | |
if ($debug) { | |
/** @var class-string $errorClass */ | |
$errorClass = $object instanceof FlattenException ? $object->getClass() : $object::class; | |
$type = (new UnicodeString((new \ReflectionClass($errorClass))->getShortName()))->snake(); | |
return Uri::createFromString("urn:errors:{$type}"); | |
} | |
return Uri::createFromString('urn:errors:unknown'); | |
} | |
private function getTitle(\Throwable|FlattenException $object, array $context, bool $debug = false): string | |
{ | |
if ($object instanceof Problem) { | |
return $object->getTitle(); | |
} | |
$message = $object->getMessage(); | |
if ($debug) { | |
return $message; | |
} | |
if ($object instanceof FlattenException) { | |
$statusCode = $context['statusCode'] ?? $object->getStatusCode(); | |
if ($statusCode >= 500 && $statusCode < 600) { | |
$message = Response::$statusTexts[$statusCode] ?? Response::$statusTexts[Response::HTTP_INTERNAL_SERVER_ERROR]; | |
} | |
} | |
return $message; | |
} | |
private function getDetail(\Throwable|FlattenException $object): ?string | |
{ | |
if ($object instanceof Problem) { | |
return $object->getDetail(); | |
} | |
return null; | |
} | |
private function getInstance(\Throwable|FlattenException $object): ?Uri | |
{ | |
if ($object instanceof Problem) { | |
return $object->getInstance(); | |
} | |
return null; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool | |
{ | |
return $format === self::FORMAT && ($data instanceof \Exception || $data instanceof FlattenException); | |
} | |
public function hasCacheableSupportsMethod(): bool | |
{ | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment