Skip to content

fix: errors bc with rfc_7807_compliant_errors false #5974

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 24, 2023

Conversation

soyuka
Copy link
Member

@soyuka soyuka commented Nov 17, 2023

What does this fix:

Try this:

composer.json:

"packages: {"api-platform/core": "dev-error-bc"},
"minimum-stability": "dev",
"repositories": [
  { "type": "vcs", "url": "https://github.com/soyuka/core" }
]

TODO:

  • add a guide

Documentation:

Errors

Backward compatibility with < 3.1

Use the following configuration:

api_platform:
    defaults:
            rfc_7807_compliant_errors: false

This can also be configured on an ApiResource or in an HttpOperation, for example:

#[ApiResource(extraProperties: ['rfc_7807_compliant_errors' => false])

Control your exceptions

With rfc_7807_compliant_errors a few things happen. First Hydra exception are compatible with the JSON Problem specification. Default exception that are handled by API Platform in JSON will be returned as application/problem+json.

To customize the API Platform response, replace the api_platform.state.error_provider with your own provider:

<?php

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ApiResource\Error;
use ApiPlatform\State\ProviderInterface;
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;

#[AsAlias('api_platform.state.error_provider')]
#[AsTaggedItem('api_platform.state.error_provider')]
final class ErrorProvider implements ProviderInterface
{
    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
    {
        $request = $context['request'];
        $format = $request->getRequestFormat();
        $exception = $request->attributes->get('exception');

        /** @var \ApiPlatform\Metadata\HttpOperation $operation */
        $status = $operation->getStatus() ?? 500;
        // You don't have to use this, you can use a Response, an array or any object (preferably a resource that API Platform can handle).
        $error = Error::createFromException($exception, $status);

        // care about hiding informations as this can be a security leak
        if ($status >= 500) {
            $error->setDetail('Something went wrong');
        }
        
        return $error;
    }
}
    api_platform.state.error_provider:
        class: 'App\State\ErrorProvider'
        tags: 
            - key: 'api_platform.state.error_provider'
              name: 'api_platform.state_provider'

Note that our validation exception have their own error provider at:

api_platform.validator.state.error_provider:
    tags: 
        - key: 'api_platform.validator.state.error_provider'
          name: 'api_platform.state_provider'

Domain exceptions

Another way of having full control over domain exceptions is to create your own Error resource:

<?php

namespace App\ApiResource;

use ApiPlatform\Metadata\ErrorResource;
use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;

#[ErrorResource()]
class Error extends \Exception implements ProblemExceptionInterface
{
    public function getType(): string
    {
        return 'teapot';
    }

    public function getTitle(): ?string
    {
        return null;
    }

    public function getStatus(): ?int
    {
        return 418;
    }

    public function getDetail(): ?string
    {
        return 'I am teapot';
    }

    public function getInstance(): ?string
    {
        return null;
    }
}

We recommend using the \ApiPlatform\Metadata\Exception\ProblemExceptionInterface and the \ApiPlatform\Metadata\Exception\HttpExceptionInterface. For security reasons we add: normalizationContext: ['ignored_attributes' => ['trace', 'file', 'line', 'code', 'message', 'traceAsString']] because you usually don't want these. You can override this context value if you want.

Exception status code:

  1. we look at exception_to_status and take one if there's a match
  2. If your exception is a Symfony\Component\HttpKernel\Exception\HttpExceptionInterface we get its status.
  3. If the exception is a ApiPlatform\Metadata\Exception\ProblemExceptionInterface and there is a status we use it
  4. Same for ApiPlatform\Metadata\Exception\HttpExceptionInterface
  5. We have some defaults:
  • Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface => 400
  • ApiPlatform\Validator\Exception\ValidationException => 422
  1. the status defined on an ErrorResource
  2. 500 is the fallback

j-schumann added a commit to j-schumann/symfony-addons that referenced this pull request Nov 17, 2023
@soyuka soyuka force-pushed the error-bc branch 3 times, most recently from 3745547 to 51773bd Compare November 22, 2023 14:16
@soyuka soyuka force-pushed the error-bc branch 2 times, most recently from 9043b1c to 305ccad Compare November 23, 2023 15:32
@soyuka
Copy link
Member Author

soyuka commented Nov 23, 2023

Failing guides is expected as it doesn't test the code from this branch but from main, my local tests work :)

@SherinBloemendaal
Copy link

Thank you for the clarification!

@NikDevPHP
Copy link

@soyuka Hello, could you please help, what is the right way to create a ValidationException error from code now ?

public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): NotificationTemplate
{
    Assert::isInstanceOf($data, CreateSitesNotificationTemplate::class);
    Assert::notEmpty($data->siteIds);

    $siteIds = $this->siteRepository->getSiteIdsWithAnyAssignedUsersByIds($data->siteIds);
    Assert::notEmpty($siteIds);

    /** @var list<int> $sitesWithoutUsersIds */
    $sitesWithoutUsersIds = array_diff($data->siteIds, $siteIds);

    if ($sitesWithoutUsersIds !== []) {
        throw new SitesWithoutUsersException($sitesWithoutUsersIds); // <- here I need ValidationException (422 code)
    }

    return $notificationTemplate;
}

BacLuc added a commit to BacLuc/ecamp3 that referenced this pull request Mar 22, 2025
It was a little tricky, but finally i figured out how to do it:
https://api-platform.com/docs/core/errors/

The big impact is, that the serialization of the errors is now
done with the same infrastructure as serializing entities.
That makes it less format dependent.

It seems a little weird that we use a provider to "replace" the exception path
of the response by extracting the exception to the context,
and then continuing like when we would serialize a response.
But i can also see the benefits.
See api-platform/core#5974

Issue: ecamp#6618
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants