core icon indicating copy to clipboard operation
core copied to clipboard

Calling GraphQL DeleteMutation ignores serialization groups and causes error

Open danaki opened this issue 1 year ago • 3 comments

API Plaform: v3.3

Description:

Calling

mutation {
  deleteGreeting(input: {id: "/api/greetings/cd"}) {
    greeting {
      id
      _id
      name
    }
    clientMutationId
  }
}

produces

        "debugMessage": "Cannot return null for non-nullable field \"Greeting.name\".",
        "file": "/mnt/c/work/ap-gql-bug/my-api/api/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php",

while name is defined as non-nullable.

Expected behavior:

An output like this:

{
  "data": {
    "greeting": {
      "id": "/api/greetings/cd",
      "_id": "cd",
      "name": "cd"
    }
  }
}

Entity/Greeting.php

Notice: ApiResource has totaly valid DataPersister and DataProvider defined.

<?php
// Entity/Greeting.php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\DeleteMutation;
use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use App\State\DataProvider;
use App\State\DataPersister;
use Symfony\Component\Validator\Constraints as Assert;


#[ApiResource(
    processor: DataPersister::class,
    provider: DataProvider::class,
    graphQlOperations: [
        new Query(),
        new QueryCollection(),
        new DeleteMutation(
            name: 'delete'
        )
    ]
)]
class Greeting
{
    private ?string $id = null;

    public string $name = '';

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

    public function getId(): ?string
    {
        return $this->id;
    }
}

State/DataProvider.php

<?php
// State/DataProvider.php

namespace App\State;

use App\Entity\Greeting;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use ApiPlatform\State\Pagination\TraversablePaginator;
use ApiPlatform\Metadata\CollectionOperationInterface;


final class DataProvider implements ProviderInterface
{
    private $data;

    function __construct()
    {
        $this->data = [
            'ab' => new Greeting('ab'),
            'cd' => new Greeting('cd'),
        ];
    }

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable|Greeting|null
    {
        if ($operation instanceof CollectionOperationInterface) {
            return new TraversablePaginator(new \ArrayObject($this->data), 0, count($this->data), count($this->data));
        }

        return $this->data[$uriVariables['id']] ?? null;
    }
}

State/DataPersister.php

<?php
// State/DataPersister.php

namespace App\State;

use ApiPlatform\Metadata\DeleteOperationInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;


final class DataPersister implements ProcessorInterface
{
    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
       return $data;
    }
}

Here's the repository reproducing mentioned bug: https://github.com/danaki/api-platform-grapql-delete-mutation-bug-demo

danaki avatar Jun 19 '24 18:06 danaki

Update: it seems it has nothing to do with custom Provider/Persister, but with serialization after DeleteMutation(). Obvious normalizationContext and denormalizationContext defining groups has no effect.

#[ApiResource(
    processor: DataPersister::class,
    provider: DataProvider::class,
    normalizationContext: ['groups' => ['delete']],
    denormalizationContext: ['groups' => ['delete']],
    graphQlOperations: [
        new Query(),
        new QueryCollection(),
        new DeleteMutation(
            name: 'delete',
            //serialize: false
        )
    ]
)]
class Greeting
{
    #[Groups(['delete'])]
    private ?string $id = null;

    #[Groups(['delete'])]
    public string $name = '';

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

    public function getId(): ?string
    {
        return $this->id;
    }
}

fails with ""Cannot return null for non-nullable field "Greeting.name"".

serialize: false (a commented line) helps to avoid error but also gives no output. Issue title updated.

danaki avatar Jun 27 '24 20:06 danaki

don't you need to do something like https://github.com/api-platform/core/issues/6354 ?

soyuka avatar Jun 28 '24 07:06 soyuka

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Aug 27 '24 15:08 stale[bot]