Skip to content

Union types for property  #5452

Description

@syl20b

Description

Hi,

It seems that only the first property type is checked in ApiPlatform\Serializer\AbstractItemNormalizer::createAndValidateAttributeValue() method.

It could be interesting to check the multiples types of each property.

How to reproduce

Given the following Resource:

<?php

namespace App\ApiResource;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use App\Dto\FooInput;

#[ApiResource(
    operations: [
        new Post(
            uriTemplate: '/foo',
        ),
    ],
)]
class Foo
{
    public string|int|null $bar;
}

When I execute the request with a string

curl -X 'POST' \
  'http://127.0.0.1:8000/api/foo' \
  -H 'accept: application/ld+json' \
  -H 'Content-Type: application/ld+json' \
  -d '{
  "bar": "string"
}'

then the response is OK

But when I execute the request with an integer

curl -X 'POST' \
  'http://127.0.0.1:8000/api/foo' \
  -H 'accept: application/ld+json' \
  -H 'Content-Type: application/ld+json' \
  -d '{
  "bar": 1
}'

Then I've got the following 400 error:

{
  "@context": "/api/contexts/Error",
  "@type": "hydra:Error",
  "hydra:title": "An error occurred",
  "hydra:description": "The type of the \"bar\" attribute must be \"string\", \"integer\" given.",
  "trace": [
    {
      "namespace": "",
      "short_class": "",
      "class": "",
      "type": "",
      "function": "",
      "file": "/home/Workspace/api-platform/vendor/symfony/serializer/Exception/NotNormalizableValueException.php",
      "line": 31,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\Serializer\\Exception",
      "short_class": "NotNormalizableValueException",
      "class": "Symfony\\Component\\Serializer\\Exception\\NotNormalizableValueException",
      "type": "::",
      "function": "createForUnexpectedDataType",
      "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
      "line": 467,
      "args": []
    },
    {
      "namespace": "ApiPlatform\\Serializer",
      "short_class": "AbstractItemNormalizer",
      "class": "ApiPlatform\\Serializer\\AbstractItemNormalizer",
      "type": "->",
      "function": "validateType",
      "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
      "line": 840,
      "args": []
    },
    {
      "namespace": "ApiPlatform\\Serializer",
      "short_class": "AbstractItemNormalizer",
      "class": "ApiPlatform\\Serializer\\AbstractItemNormalizer",
      "type": "->",
      "function": "createAndValidateAttributeValue",
      "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
      "line": 711,
      "args": []
    },
    {
      "namespace": "ApiPlatform\\Serializer",
      "short_class": "AbstractItemNormalizer",
      "class": "ApiPlatform\\Serializer\\AbstractItemNormalizer",
      "type": "->",
      "function": "createAttributeValue",
      "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
      "line": 443,
      "args": []
    },
    {
      "namespace": "ApiPlatform\\Serializer",
      "short_class": "AbstractItemNormalizer",
      "class": "ApiPlatform\\Serializer\\AbstractItemNormalizer",
      "type": "->",
      "function": "setAttributeValue",
      "file": "/home/Workspace/api-platform/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php",
      "line": 382,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\Serializer\\Normalizer",
      "short_class": "AbstractObjectNormalizer",
      "class": "Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer",
      "type": "->",
      "function": "denormalize",
      "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
      "line": 219,
      "args": []
    },
    {
      "namespace": "ApiPlatform\\Serializer",
      "short_class": "AbstractItemNormalizer",
      "class": "ApiPlatform\\Serializer\\AbstractItemNormalizer",
      "type": "->",
      "function": "denormalize",
      "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/JsonLd/Serializer/ItemNormalizer.php",
      "line": 150,
      "args": []
    },
    {
      "namespace": "ApiPlatform\\JsonLd\\Serializer",
      "short_class": "ItemNormalizer",
      "class": "ApiPlatform\\JsonLd\\Serializer\\ItemNormalizer",
      "type": "->",
      "function": "denormalize",
      "file": "/home/Workspace/api-platform/vendor/symfony/serializer/Serializer.php",
      "line": 227,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\Serializer",
      "short_class": "Serializer",
      "class": "Symfony\\Component\\Serializer\\Serializer",
      "type": "->",
      "function": "denormalize",
      "file": "/home/Workspace/api-platform/vendor/symfony/serializer/Serializer.php",
      "line": 145,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\Serializer",
      "short_class": "Serializer",
      "class": "Symfony\\Component\\Serializer\\Serializer",
      "type": "->",
      "function": "deserialize",
      "file": "/home/Workspace/api-platform/vendor/api-platform/core/src/Symfony/EventListener/DeserializeListener.php",
      "line": 101,
      "args": []
    },
    {
      "namespace": "ApiPlatform\\Symfony\\EventListener",
      "short_class": "DeserializeListener",
      "class": "ApiPlatform\\Symfony\\EventListener\\DeserializeListener",
      "type": "->",
      "function": "onKernelRequest",
      "file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/Debug/WrappedListener.php",
      "line": 115,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher\\Debug",
      "short_class": "WrappedListener",
      "class": "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener",
      "type": "->",
      "function": "__invoke",
      "file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/EventDispatcher.php",
      "line": 206,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher",
      "short_class": "EventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
      "type": "->",
      "function": "callListeners",
      "file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/EventDispatcher.php",
      "line": 56,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher",
      "short_class": "EventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
      "type": "->",
      "function": "dispatch",
      "file": "/home/Workspace/api-platform/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php",
      "line": 127,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher\\Debug",
      "short_class": "TraceableEventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher",
      "type": "->",
      "function": "dispatch",
      "file": "/home/Workspace/api-platform/vendor/symfony/http-kernel/HttpKernel.php",
      "line": 139,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "HttpKernel",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->",
      "function": "handleRaw",
      "file": "/home/Workspace/api-platform/vendor/symfony/http-kernel/HttpKernel.php",
      "line": 74,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "HttpKernel",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->",
      "function": "handle",
      "file": "/home/Workspace/api-platform/vendor/symfony/http-kernel/Kernel.php",
      "line": 184,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "Kernel",
      "class": "Symfony\\Component\\HttpKernel\\Kernel",
      "type": "->",
      "function": "handle",
      "file": "/home/Workspace/api-platform/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php",
      "line": 35,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\Runtime\\Runner\\Symfony",
      "short_class": "HttpKernelRunner",
      "class": "Symfony\\Component\\Runtime\\Runner\\Symfony\\HttpKernelRunner",
      "type": "->",
      "function": "run",
      "file": "/home/Workspace/api-platform/vendor/autoload_runtime.php",
      "line": 29,
      "args": []
    },
    {
      "namespace": "",
      "short_class": "",
      "class": "",
      "type": "",
      "function": "require_once",
      "file": "/home/Workspace/api-platform/public/index.php",
      "line": 5,
      "args": [
        [
          "string",
          "/home/Workspace/api-platform/vendor/autoload_runtime.php"
        ]
      ]
    }
  ]
}

Additional Context

This behaviour does not appear when using a DTO as input class.

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions