api-platform / core

The server component of API Platform: hypermedia and GraphQL APIs in minutes
https://api-platform.com
MIT License
2.39k stars 849 forks source link

Cannot make GET request (Accept: text/html), it's always catched up by the documentation route #6384

Closed pourquoi closed 1 week ago

pourquoi commented 3 years ago

API Platform version(s) affected: 2.5.7

Description
I have a resource with only the "get item" activated. It uses a custom controller. I want it to return either the json serialized resource or a html/twig template depending on the client Accept header/format parameter.

this doesnt work: curl -X GET "http://127.0.0.1:9007/api/email_verifications/my_token -h "Accept: text/html" It returns the documentation html page (but autoscrolled at the correct resource location)

How to reproduce

the resource:


<?php

namespace App\Entity\Api;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Controller\EmailVerificationController;

/**
 * @ApiResource(
 *     formats={"html", "json", "jsonld"},
 *     collectionOperations={},
 *     itemOperations={
 *         "get"={
 *             "method"="GET",
 *             "requirements"={"id"=".+"},
 *             "controller"=EmailVerificationController::class
 *         }
 *     }
 * )
 */
class EmailVerification
{
    /**
     * @var string
     * @ApiProperty(identifier=true)
     */
    public $token;

    public function __construct($token)
    {
        $this->token = $token;
    }
}

the controller :


<?php

namespace App\Controller;

use App\Entity\Api\EmailVerification;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;

class EmailVerificationController
{
    private $JWTEncoder;
    private $em;
    private $twig;

    public function __construct(JWTEncoderInterface $JWTEncoder, EntityManagerInterface $em, Environment $twig)
    {
        $this->JWTEncoder = $JWTEncoder;
        $this->em = $em;
        $this->twig = $twig;
    }

    public function __invoke(EmailVerification $data, Request $request)
    {
        try {
            $data = $this->JWTEncoder->decode($data->token);
        } catch( \Exception $e ) {
            return new Response("", 400);
        }

        /** @var User $user */
        if( isset($data['user_id']) && ($user = $this->em->getRepository(User::class)->find($data['user_id'])) ) {
            $user->setEmailVerificationRequired(false);
            $this->em->flush();

            if (in_array('text/html', $request->getAcceptableContentTypes())) {
                return new Response($this->twig->render('transactions/email_verification.html.twig'));
            }

            return new Response("", 200);
        } else {
            return new Response("", 400);
        }
    }
}

Additional Context

api_platform:
    mapping:
        paths: ['%kernel.project_dir%/src/Entity']
    patch_formats:
        json: ['application/merge-patch+json']
    swagger:
        versions: [3]
        api_keys:
            apiKey:
                name: Authorization
                type: header
    name_converter: 'Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter'
    formats:
        jsonld:   ['application/ld+json']
        json:     ['application/json']
        csv:      ['text/csv']
        html:     ['text/html']
soyuka commented 3 years ago

shouldn't you add a path to the item operation? Does it show up on bin/console debug:router?

pourquoi commented 3 years ago

Yes it shows up.

this works fine : curl -X GET "http://127.0.0.1:9007/api/email_verifications/my_token -h "Accept: application/json" but this returns the swagger html documentation page : curl -X GET "http://127.0.0.1:9007/api/email_verifications/my_token -h "Accept: text/html"

dunglas commented 3 years ago

This is in purpose. Disabling API Platform native support for Swagger UI support will "fix" this. Then you can re-add manually the /docs route in your own routing configuration. Maybe should we provide another option to disable only the listener but not the /docs route.

trittler commented 1 year ago

Is there any news on this? Or some kind of workaround? :)

nickbass72 commented 1 year ago
api_platform:
    mapping:
        paths:
            - '%kernel.project_dir%/src/Entity'
            - '%kernel.project_dir%/config/api_platform'
    patch_formats:
        json: ['application/merge-patch+json']
    formats:
        jsonld: ['application/ld+json']
        json: ['application/json']
        html: ['text/html']
    swagger:
        versions: [3]
        api_keys:
            JWT:
                name: authorization
                type: header
    path_segment_name_generator: api_platform.path_segment_name_generator.dash
    defaults:
        formats: ['jsonld', 'json']

At this time "defaults" section resolves this issue for me.

mwierzbi commented 11 months ago

other simple solution:

decorate the SwaggerUiListener

#[AsDecorator(decorates: 'api_platform.swagger.listener.ui')]
final class SwaggerUiListener
{
    public const DISABLE_SWAGGER = 'disable_swagger';

    public function __construct(
        #[AutowireDecorated]
        private \ApiPlatform\Symfony\Bundle\EventListener\SwaggerUiListener $decorated
    ) {
    }
    public function onKernelRequest(RequestEvent $event): void
    {
        $request = $event->getRequest();
        if (!$request->attributes->get(self::DISABLE_SWAGGER, false)) {
            $this->decorated->onKernelRequest($event);
        }
    }
}

and add variable tu the operation


new Metadata\Get(
  defaults: [SwaggerUiListener::DISABLE_SWAGGER => true],
josuealcalde-medac commented 2 months ago

In version 3.3 there is not SwaggerUiListener, and there is a provider. You can avoid problems not using 'html' as a format. For example, i choose 'htm' as a replace:

        new Get(
            name: 'email_html',
            uriTemplate: '/download/{uuid}/html',
            formats: ['htm' => ['text/html']], // if we use 'html', the swagger UI provider will catch up this request
            ......
soyuka commented 1 month ago

Indeed, not a huge fan but this allows to redirect http errors to the swagger ui when enabled, we can probably add a flag to disable this which would fix this use case?

For now you can decorate this: https://github.com/api-platform/core/blob/main/src/Symfony/Bundle/SwaggerUi/SwaggerUiProvider.php

and just skip when you want.

taophp commented 2 weeks ago

I meet exactly the same problem (trying to enable newly registered user by clicking a link in a email)... @soyuka Could you please elaborate on Swagger UI decoration ? I made a try, but it failed. I'm quite new to service decoration, so I may have done something wrong... Could you please have a look on this commit ?

taophp commented 2 weeks ago

Well, it seems that my decorator works as I can see dumps in the Symfony profiler. But I continue to get 404 from Swagger when calling the url from the browser. Do I do it well here https://github.com/taophp/testapi/blob/swagger-decorator/api/src/Swagger/SwaggerUiProvider.php#L21 @soyuka ?

soyuka commented 2 weeks ago

Mhh this is harder then I thought @taophp you should overwrite the service instead, this service is part of a decoration chain therefore if you don't call the chain you may end up with issues. As this is asked a lot maybe that we can introduce an option to skip the provider? I'll try to reproduce this.

taophp commented 1 week ago

@soyuka Yes, it would be great to have an option to skip the provider. Please, add it and let us know.

soyuka commented 1 week ago

6449 introduces the _api_disable_swagger_provider flag inside extraProperties. Will be available in the next release.

taophp commented 1 week ago

Great ! Thank you, @soyuka !

taophp commented 4 days ago

Thanks for this @soyuka ! Please, could you elaborate on how to use this new _api_disable_swagger_provider ? I give it a try here, but it does not work. Consider it in relation with this class.