JSON:API: relations set through the constructor reject spec resource linkage (`relationships.<name>.data`)
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
調査の方向性
Start with JsonApi\Serializer\ItemNormalizerTrait::setAttributeValue(), which strips the data wrapper, and compare it with AbstractItemNormalizer::createConstructorArgument(), which calls createAndValidateAttributeValue() without that step. Then check denormalizeRelation() in the JSON:API serializer. Done when a constructor-argument relation accepts relationships.<name>.data for to-one (including null) and to-many, and the flat shape is rejected, with a test modelled on the Book/Author fixture.
索引モデルが issue の本文から書いたものです。
説明
API Platform version(s) affected
4.4.4
Description
When a relation is set through a constructor argument, the JSON:API denormalizer does not accept the resource linkage shape of the spec (relationships.<name>.data).
- To-one with
{"data": {"type": ..., "id": ...}}or{"data": null}: 400 "Only resource linkage supported currently". - To-many with
{"data": [...]}: 400 "The type of the key "data" must be "int", "string" given." - To-one with the flat shape
{"type": ..., "id": ...}, which is not the spec shape: 201.
The same relation as a public property (no constructor) accepts the spec shape.
The cause looks like this: JsonApi\Serializer\ItemNormalizerTrait::setAttributeValue() removes the data wrapper, but AbstractItemNormalizer::createConstructorArgument() calls createAndValidateAttributeValue() directly. The JSON:API normalizer does not override it, so the value still has the data wrapper when it gets to denormalizeRelation().
How to reproduce
#[ApiResource(operations: [new Get(provider: [self::class, 'provide'])])]
final class Author
{
public function __construct(public int $id = 1, public string $name = 'Frank Herbert') {}
public static function provide(): self { return new self(); }
}
#[ApiResource(operations: [
new Get(provider: [self::class, 'provide']),
new Post(processor: [self::class, 'process']),
])]
final class Book
{
/** @param list<Author> $coAuthors */
public function __construct(
public int $id = 1,
public string $title = 'Dune',
public ?Author $author = null,
public array $coAuthors = [],
) {}
public static function provide(): ?self { return null; }
public static function process(self $data, Operation $operation): self { return $data; }
}
// Same relation, set as a property
#[ApiResource(operations: [
new Get(provider: [self::class, 'provide']),
new Post(processor: [self::class, 'process']),
])]
final class Novel
{
public int $id = 1;
public string $title = 'Dune';
public ?Author $author = null;
public static function provide(): self { return new self(); }
public static function process(self $data, Operation $operation): self { return $data; }
}
POST |
relationships |
Status |
|---|---|---|
/books |
{"author":{"data":{"type":"Author","id":"/authors/1"}}} |
400 |
/books |
{"author":{"data":null}} |
400 |
/books |
{"coAuthors":{"data":[{"type":"Author","id":"/authors/1"}]}} |
400 |
/books |
{"author":{"type":"Author","id":"/authors/1"}} (not spec) |
201 |
/novels |
{"author":{"data":{"type":"Author","id":"/authors/1"}}} |
201 |
Full request for the first row:
POST /books
{"data":{"type":"Book","attributes":{"title":"A"},"relationships":{"author":{"data":{"type":"Author","id":"/authors/1"}}}}}
{"errors":[{"id":"/errors/400","title":"An error occurred","detail":"Only resource linkage supported currently, see: http://jsonapi.org/format/#document-resource-object-linkage.","status":400,"type":"/errors/400","links":{"type":"/errors/400"},"code":"400"}]}
Expected: a relation set through the constructor accepts the same relationships.<name>.data shape as a relation set through a property. The flat shape is not accepted.
Possible Solution
Remove the data wrapper for constructor arguments too, for example with a createConstructorArgument() override in ItemNormalizerTrait that does what setAttributeValue() does.
Additional Context
Checked on api-platform/core v4.4.4, Symfony 7.4, PHP 8.5, in an empty Symfony app. The related code in src/JsonApi/Serializer is the same on main, except for the error status cast (#8366).
The app has only FrameworkBundle, TwigBundle and ApiPlatformBundle, with this config:
$c->extension('api_platform', [
'mapping' => ['paths' => ['%kernel.project_dir%/src/ApiResource']],
'formats' => ['jsonapi' => ['application/vnd.api+json']],
'error_formats' => ['jsonapi' => ['application/vnd.api+json'], 'jsonproblem' => ['application/problem+json']],
'doctrine' => ['enabled' => false],
]);
All requests send Accept: application/vnd.api+json and, when they have a body, Content-Type: application/vnd.api+json. The responses are from the prod environment.
- 主要言語
- PHP
- スター
- 2.6k
- フォーク
- 989
- 平均マージ
- 1日 23時間
- マージ済み PR(30日)
- 102
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
api-platform/core のほかの issue
-
JSON:API: resource schemas (`*.jsonapi`) have `properties` but no `type: object`対応中かも @ousamabenyounes が 1 日前に担当しました。 オープン
難易度 2/5 1時間未満 初心者へのやさしさ 80/100
api-platform/core#8701 ·
メンテナーはふだん 1 日以内に返信
-
JSON:API: `JsonApiCollectionBaseSchemaNoPagination` declares `data` as `type: array` without `items`オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
api-platform/core#8699 ·
メンテナーはふだん 1 日以内に返信
-
api:openapi:export --spec-version=3.0.0 outputs `webhooks`, which is invalid in OpenAPI 3.0対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
api-platform/core#8690 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 85/100
api-platform/core#8680 ·
メンテナーはふだん 1 日以内に返信
-
難易度 3/5 1〜2日 初心者へのやさしさ 68/100
api-platform/core#8725 ·
メンテナーはふだん 1 日以内に返信
api-platform/core の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 92/100
メンテナーはふだん 1 日以内に返信
-
Authentication / Authorization Feature: Volunteer
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
WordPress/mcp-adapter#399 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 80/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
freescout-help-desk/freescout#5750 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信