From fd86f07ecf1f0794ef9e470118c35da5fd8dae9c Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 5 Jun 2026 12:17:34 +0200 Subject: [PATCH] fix(jsonapi): do not require id in input schema for post operations Per the JSON:API spec (https://jsonapi.org/format/#crud-creating), a client MAY supply a client-generated id when creating a resource. The schema factory unconditionally marked id as required for any input schema, which forced clients to invent ids on POST. Mark id as required only for non-POST HTTP operations. Fixes #6738 --- src/JsonApi/JsonSchema/SchemaFactory.php | 6 ++++- .../Tests/JsonSchema/SchemaFactoryTest.php | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/JsonApi/JsonSchema/SchemaFactory.php b/src/JsonApi/JsonSchema/SchemaFactory.php index 3f71cf0e243..f940b44d7db 100644 --- a/src/JsonApi/JsonSchema/SchemaFactory.php +++ b/src/JsonApi/JsonSchema/SchemaFactory.php @@ -20,6 +20,7 @@ use ApiPlatform\JsonSchema\SchemaFactoryAwareInterface; use ApiPlatform\JsonSchema\SchemaFactoryInterface; use ApiPlatform\JsonSchema\SchemaUriPrefixTrait; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -369,11 +370,14 @@ private function buildDefinitionPropertiesSchema(string $key, string $className, ]; } + // https://jsonapi.org/format/#crud-creating — clients MAY supply an id when creating a resource. + $required = $operation instanceof HttpOperation && 'POST' === $operation->getMethod() ? ['type'] : ['type', 'id']; + return [ 'data' => [ 'type' => 'object', 'properties' => $replacement, - 'required' => ['type', 'id'], + 'required' => $required, ], ] + $included; } diff --git a/src/JsonApi/Tests/JsonSchema/SchemaFactoryTest.php b/src/JsonApi/Tests/JsonSchema/SchemaFactoryTest.php index 4adcb6480ce..9ca0d9c1b23 100644 --- a/src/JsonApi/Tests/JsonSchema/SchemaFactoryTest.php +++ b/src/JsonApi/Tests/JsonSchema/SchemaFactoryTest.php @@ -22,6 +22,8 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Operations; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Property\PropertyNameCollection; @@ -49,6 +51,7 @@ protected function setUp(): void ); $propertyNameCollectionFactory = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactory->create(Dummy::class, ['enable_getter_setter_extraction' => true, 'schema_type' => Schema::TYPE_OUTPUT])->willReturn(new PropertyNameCollection()); + $propertyNameCollectionFactory->create(Dummy::class, ['enable_getter_setter_extraction' => true, 'schema_type' => Schema::TYPE_INPUT])->willReturn(new PropertyNameCollection()); $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); $definitionNameFactory = new DefinitionNameFactory(null); @@ -164,4 +167,26 @@ public function testSchemaTypeBuildSchema(): void $forcedCollection = $this->schemaFactory->buildSchema(Dummy::class, 'jsonapi', Schema::TYPE_OUTPUT, forceCollection: true); $this->assertEquals($resultSchema['allOf'][0]['$ref'], $forcedCollection['allOf'][0]['$ref']); } + + public function testPostInputSchemaDoesNotRequireId(): void + { + $resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonapi', Schema::TYPE_INPUT, new Post()); + $definitions = $resultSchema->getDefinitions(); + $rootDefinitionKey = $resultSchema->getRootDefinitionKey(); + + $this->assertTrue(isset($definitions[$rootDefinitionKey]['properties']['data'])); + $data = $definitions[$rootDefinitionKey]['properties']['data']; + $this->assertArrayHasKey('required', $data); + $this->assertSame(['type'], $data['required']); + } + + public function testPatchInputSchemaRequiresId(): void + { + $resultSchema = $this->schemaFactory->buildSchema(Dummy::class, 'jsonapi', Schema::TYPE_INPUT, new Patch()); + $definitions = $resultSchema->getDefinitions(); + $rootDefinitionKey = $resultSchema->getRootDefinitionKey(); + + $data = $definitions[$rootDefinitionKey]['properties']['data']; + $this->assertSame(['type', 'id'], $data['required']); + } }