Skip to content

Instantly share code, notes, and snippets.

@nesl247
Created March 7, 2023 15:16
Show Gist options
  • Save nesl247/3ae6b1f9575e4dcd0960ca4a9016434e to your computer and use it in GitHub Desktop.
Save nesl247/3ae6b1f9575e4dcd0960ca4a9016434e to your computer and use it in GitHub Desktop.
```php
throw new Api\Platform\Errors\Problems\GenericProblem(type: 'urn:some:example', title: 'This title should never change', status: 500);
```
```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 [];
}
}
<?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,
];
}
}
<?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