Skip to content

Commit 0f88647

Browse files
authored
Merge pull request #764 from meyerbaptiste/swagger-add-read-only-field
Add the readOnly field for Swagger
2 parents c8d7403 + 467d4b8 commit 0f88647

File tree

2 files changed

+59
-1
lines changed

2 files changed

+59
-1
lines changed

src/Swagger/Serializer/DocumentationNormalizer.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
1717
use ApiPlatform\Core\Api\UrlGeneratorInterface;
1818
use ApiPlatform\Core\Documentation\Documentation;
19+
use ApiPlatform\Core\Exception\RuntimeException;
1920
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2021
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2122
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
@@ -364,6 +365,8 @@ private function getDefinition(\ArrayObject $definitions, bool $collection, bool
364365
* @param ResourceMetadata $resourceMetadata
365366
* @param array|null $serializerContext
366367
*
368+
* @throws RuntimeException
369+
*
367370
* @return \ArrayObject
368371
*/
369372
private function getDefinitionSchema(string $resourceClass, ResourceMetadata $resourceMetadata, array $serializerContext = null) : \ArrayObject
@@ -384,6 +387,10 @@ private function getDefinitionSchema(string $resourceClass, ResourceMetadata $re
384387
$normalizedPropertyName = $this->nameConverter ? $this->nameConverter->normalize($propertyName) : $propertyName;
385388

386389
if ($propertyMetadata->isRequired()) {
390+
if (false === $propertyMetadata->isWritable()) {
391+
throw new RuntimeException(sprintf('The property "%s" of the resource "%s" can not be required and read-only at the same time.', $propertyName, $resourceClass));
392+
}
393+
387394
$definitionSchema['required'][] = $normalizedPropertyName;
388395
}
389396

@@ -406,6 +413,10 @@ private function getPropertySchema(PropertyMetadata $propertyMetadata) : \ArrayO
406413
{
407414
$propertySchema = new \ArrayObject();
408415

416+
if (false === $propertyMetadata->isWritable()) {
417+
$propertySchema['readOnly'] = true;
418+
}
419+
409420
if (null !== $description = $propertyMetadata->getDescription()) {
410421
$propertySchema['description'] = $description;
411422
}

tests/Swagger/Serializer/DocumentationNormalizerTest.php

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ public function testNormalize()
4242
$documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3', ['jsonld' => ['application/ld+json']]);
4343

4444
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
45-
$propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name']));
45+
$propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name']));
4646

4747
$dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', 'http://schema.example.com/Dummy', ['get' => ['method' => 'GET'], 'put' => ['method' => 'PUT']], ['get' => ['method' => 'GET'], 'post' => ['method' => 'POST'], 'custom' => ['method' => 'GET', 'path' => '/foo'], 'custom2' => ['method' => 'POST', 'path' => '/foo']], []);
4848
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
4949
$resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata);
5050

5151
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
52+
$propertyMetadataFactoryProphecy->create(Dummy::class, 'id')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false));
5253
$propertyMetadataFactoryProphecy->create(Dummy::class, 'name')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, []));
5354
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
5455
$resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
@@ -224,6 +225,11 @@ public function testNormalize()
224225
'description' => 'This is a dummy.',
225226
'externalDocs' => ['url' => 'http://schema.example.com/Dummy'],
226227
'properties' => [
228+
'id' => new \ArrayObject([
229+
'type' => 'integer',
230+
'description' => 'This is an id.',
231+
'readOnly' => true,
232+
]),
227233
'name' => new \ArrayObject([
228234
'type' => 'string',
229235
'description' => 'This is a name.',
@@ -236,6 +242,47 @@ public function testNormalize()
236242
$this->assertEquals($expected, $normalizer->normalize($documentation));
237243
}
238244

245+
/**
246+
* @expectedException \ApiPlatform\Core\Exception\RuntimeException
247+
* @expectedExceptionMessage The property "id" of the resource "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy" can not be required and read-only at the same time.
248+
*/
249+
public function testNormalizeThrowsExceptionWithAReadOnlyAndRequiredProperty()
250+
{
251+
$documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Dummy API', 'This is a dummy API', '1.2.3', ['jsonld' => ['application/ld+json']]);
252+
253+
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
254+
$propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['id']));
255+
256+
$dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', null, ['post' => ['method' => 'POST']], [], []);
257+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
258+
$resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata);
259+
260+
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
261+
$propertyMetadataFactoryProphecy->create(Dummy::class, 'id')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is an id.', true, false, null, null, true));
262+
263+
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
264+
$resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
265+
266+
$operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class);
267+
$operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'post')->shouldBeCalled()->willReturn('POST');
268+
269+
$urlGeneratorProphecy = $this->prophesize(UrlGeneratorInterface::class);
270+
271+
$operationPathResolver = new CustomOperationPathResolver(new UnderscoreOperationPathResolver());
272+
273+
$normalizer = new DocumentationNormalizer(
274+
$resourceMetadataFactoryProphecy->reveal(),
275+
$propertyNameCollectionFactoryProphecy->reveal(),
276+
$propertyMetadataFactoryProphecy->reveal(),
277+
$resourceClassResolverProphecy->reveal(),
278+
$operationMethodResolverProphecy->reveal(),
279+
$operationPathResolver,
280+
$urlGeneratorProphecy->reveal()
281+
);
282+
283+
$normalizer->normalize($documentation);
284+
}
285+
239286
public function testNormalizeWithNameConverter()
240287
{
241288
$documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Dummy API', 'This is a dummy API', '1.2.3', ['jsonld' => ['application/ld+json']]);

0 commit comments

Comments
 (0)