Skip to content

Commit

Permalink
add flag
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Jul 19, 2024
1 parent 1033064 commit 0e3d3b2
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 20 deletions.
16 changes: 15 additions & 1 deletion src/JsonSchema/SchemaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ public function buildSchema(string $className, string $format = 'json', string $
return $schema;
}

$isJsonMergePatch = 'json' === $format && 'PATCH' === $operation->getMethod() && Schema::TYPE_INPUT === $type;

Check failure on line 91 in src/JsonSchema/SchemaFactory.php

View workflow job for this annotation

GitHub Actions / PHPStan (PHP 8.3)

Call to an undefined method ApiPlatform\Metadata\Operation::getMethod().
$skipRequiredProperties = false;

if ($isJsonMergePatch) {
if (null === ($skipRequiredProperties = $operation->getExtraProperties()['patch_skip_schema_required_properties'] ?? null)) {
trigger_deprecation('api-platform/core', '3.4', "Set 'patch_skip_schema_required_properties' on extra properties as API Platform 4 will remove required properties from the JSON Schema on Patch request.");
}

if (true === $skipRequiredProperties) {
$definitionName .= self::PATCH_SCHEMA_POSTFIX;

Check failure on line 100 in src/JsonSchema/SchemaFactory.php

View workflow job for this annotation

GitHub Actions / PHPStan (PHP 8.3)

Access to undefined constant ApiPlatform\JsonSchema\SchemaFactory::PATCH_SCHEMA_POSTFIX.
}
}

if (!isset($schema['$ref']) && !isset($schema['type'])) {
$ref = Schema::VERSION_OPENAPI === $version ? '#/components/schemas/'.$definitionName : '#/definitions/'.$definitionName;
if ($forceCollection || ('POST' !== $method && $operation instanceof CollectionOperationInterface)) {
Expand Down Expand Up @@ -129,14 +142,15 @@ public function buildSchema(string $className, string $format = 'json', string $
}

$options = ['schema_type' => $type] + $this->getFactoryOptions($serializerContext, $validationGroups, $operation instanceof HttpOperation ? $operation : null);

foreach ($this->propertyNameCollectionFactory->create($inputOrOutputClass, $options) as $propertyName) {
$propertyMetadata = $this->propertyMetadataFactory->create($inputOrOutputClass, $propertyName, $options);
if (!$propertyMetadata->isReadable() && !$propertyMetadata->isWritable()) {
continue;
}

$normalizedPropertyName = $this->nameConverter ? $this->nameConverter->normalize($propertyName, $inputOrOutputClass, $format, $serializerContext) : $propertyName;
if ($propertyMetadata->isRequired()) {
if ($propertyMetadata->isRequired() && !$skipRequiredProperties) {
$definition['required'][] = $normalizedPropertyName;
}

Expand Down
30 changes: 30 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/PatchRequired/PatchMe.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\PatchRequired;

use ApiPlatform\Metadata\Patch;
use Symfony\Component\Validator\Constraints\NotNull;

#[Patch(uriTemplate: '/patch_required_stuff', provider: [self::class, 'provide'])]
final class PatchMe
{
public ?string $a = null;
#[NotNull]
public ?string $b = null;

public static function provide(): self
{
return new self();
}
}
53 changes: 34 additions & 19 deletions tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy as DocumentDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\ApplicationTester;
Expand All @@ -24,6 +25,7 @@
*/
class JsonSchemaGenerateCommandTest extends KernelTestCase
{
use ExpectDeprecationTrait;
private ApplicationTester $tester;

private string $entityClass;
Expand Down Expand Up @@ -76,9 +78,9 @@ public function testExecuteWithJsonldTypeInput(): void
$this->tester->run(['command' => 'api:json-schema:generate', 'resource' => $this->entityClass, '--operation' => '_api_/dummies{._format}_post', '--format' => 'jsonld', '--type' => 'input']);
$result = $this->tester->getDisplay();

$this->assertStringContainsString('@id', $result);
$this->assertStringContainsString('@context', $result);
$this->assertStringContainsString('@type', $result);
$this->assertStringNotContainsString('@id', $result);
$this->assertStringNotContainsString('@context', $result);
$this->assertStringNotContainsString('@type', $result);
}

/**
Expand All @@ -103,24 +105,24 @@ public function testArraySchemaWithReference(): void
$result = $this->tester->getDisplay();
$json = json_decode($result, associative: true);

$this->assertEquals($json['definitions']['BagOfTests.jsonld-write.input']['properties']['tests'], [
$this->assertEquals($json['definitions']['BagOfTests.jsonld-write']['properties']['tests'], [
'type' => 'string',
'foo' => 'bar',
]);

$this->assertEquals($json['definitions']['BagOfTests.jsonld-write.input']['properties']['nonResourceTests'], [
$this->assertEquals($json['definitions']['BagOfTests.jsonld-write']['properties']['nonResourceTests'], [
'type' => 'array',
'items' => [
'$ref' => '#/definitions/NonResourceTestEntity.jsonld-write.input',
'$ref' => '#/definitions/NonResourceTestEntity.jsonld-write',
],
]);

$this->assertEquals($json['definitions']['BagOfTests.jsonld-write.input']['properties']['description'], [
$this->assertEquals($json['definitions']['BagOfTests.jsonld-write']['properties']['description'], [
'maxLength' => 255,
]);

$this->assertEquals($json['definitions']['BagOfTests.jsonld-write.input']['properties']['type'], [
'$ref' => '#/definitions/TestEntity.jsonld-write.input',
$this->assertEquals($json['definitions']['BagOfTests.jsonld-write']['properties']['type'], [
'$ref' => '#/definitions/TestEntity.jsonld-write',
]);
}

Expand All @@ -130,14 +132,14 @@ public function testArraySchemaWithMultipleUnionTypesJsonLd(): void
$result = $this->tester->getDisplay();
$json = json_decode($result, associative: true);

$this->assertEquals($json['definitions']['Nest.jsonld.output']['properties']['owner']['anyOf'], [
['$ref' => '#/definitions/Wren.jsonld.output'],
['$ref' => '#/definitions/Robin.jsonld.output'],
$this->assertEquals($json['definitions']['Nest.jsonld']['properties']['owner']['anyOf'], [
['$ref' => '#/definitions/Wren.jsonld'],
['$ref' => '#/definitions/Robin.jsonld'],
['type' => 'null'],
]);

$this->assertArrayHasKey('Wren.jsonld.output', $json['definitions']);
$this->assertArrayHasKey('Robin.jsonld.output', $json['definitions']);
$this->assertArrayHasKey('Wren.jsonld', $json['definitions']);
$this->assertArrayHasKey('Robin.jsonld', $json['definitions']);
}

public function testArraySchemaWithMultipleUnionTypesJsonApi(): void
Expand Down Expand Up @@ -183,7 +185,7 @@ public function testArraySchemaWithTypeFactory(): void
$result = $this->tester->getDisplay();
$json = json_decode($result, associative: true);

$this->assertEquals($json['definitions']['Foo.jsonld.output']['properties']['expiration'], ['type' => 'string', 'format' => 'date']);
$this->assertEquals($json['definitions']['Foo.jsonld']['properties']['expiration'], ['type' => 'string', 'format' => 'date']);
}

/**
Expand All @@ -195,7 +197,7 @@ public function testWritableNonResourceRef(): void
$result = $this->tester->getDisplay();
$json = json_decode($result, associative: true);

$this->assertEquals($json['definitions']['SaveProduct.jsonld.input']['properties']['codes']['items']['$ref'], '#/definitions/ProductCode.jsonld.input');
$this->assertEquals($json['definitions']['SaveProduct.jsonld']['properties']['codes']['items']['$ref'], '#/definitions/ProductCode.jsonld');
}

/**
Expand All @@ -207,8 +209,8 @@ public function testOpenApiResourceRefIsNotOverwritten(): void
$result = $this->tester->getDisplay();
$json = json_decode($result, associative: true);

$this->assertEquals('#/definitions/DummyFriend', $json['definitions']['Issue6299.Issue6299OutputDto.jsonld.output']['properties']['itemDto']['$ref']);
$this->assertEquals('#/definitions/DummyDate', $json['definitions']['Issue6299.Issue6299OutputDto.jsonld.output']['properties']['collectionDto']['items']['$ref']);
$this->assertEquals('#/definitions/DummyFriend', $json['definitions']['Issue6299.Issue6299OutputDto.jsonld']['properties']['itemDto']['$ref']);
$this->assertEquals('#/definitions/DummyDate', $json['definitions']['Issue6299.Issue6299OutputDto.jsonld']['properties']['collectionDto']['items']['$ref']);
}

/**
Expand All @@ -220,7 +222,7 @@ public function testSubSchemaJsonLd(): void
$result = $this->tester->getDisplay();
$json = json_decode($result, associative: true);

$this->assertArrayHasKey('@id', $json['definitions']['ThirdLevel.jsonld-friends.output']['properties']);
$this->assertArrayHasKey('@id', $json['definitions']['ThirdLevel.jsonld-friends']['properties']);
}

public function testJsonApiIncludesSchema(): void
Expand Down Expand Up @@ -332,4 +334,17 @@ public function testResourceWithEnumPropertiesSchema(): void
$properties['genders']
);
}

/**
* @group legacy
* TODO: find a way to keep required properties if needed
*/
public function testPatchSchemaRequiredProperties(): void
{
$this->tester->run(['command' => 'api:json-schema:generate', 'resource' => 'ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\PatchRequired\PatchMe', '--format' => 'json']);
$result = $this->tester->getDisplay();
$json = json_decode($result, associative: true);

$this->assertEquals(['b'], $json['definitions']['PatchMe']['required']);
}
}

0 comments on commit 0e3d3b2

Please sign in to comment.