Skip to content

Commit

Permalink
feat: improves iri_only implementation (#3454)
Browse files Browse the repository at this point in the history
* Issue-#3275 - feat: Improves iri_only implementation (#3454)

* Add back default context

* fix: minor fixes

Co-authored-by: soyuka <soyuka@users.noreply.github.com>
Co-authored-by: Alan Poulain <contact@alanpoulain.eu>
  • Loading branch information
3 people committed Oct 15, 2020
1 parent b322aff commit 1c7c26d
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 13 deletions.
24 changes: 24 additions & 0 deletions features/bootstrap/DoctrineContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\FourthLevel as FourthLevelDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\Greeting as GreetingDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\InitializeInput as InitializeInputDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\IriOnlyDummy as IriOnlyDummyDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\MaxDepthDummy as MaxDepthDummyDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\NetworkPathDummy as NetworkPathDummyDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\NetworkPathRelationDummy as NetworkPathRelationDummyDocument;
Expand Down Expand Up @@ -120,6 +121,7 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Greeting;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\InitializeInput;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\InternalUser;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\IriOnlyDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\MaxDepthDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\NetworkPathDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\NetworkPathRelationDummy;
Expand Down Expand Up @@ -1585,6 +1587,20 @@ public function thereAreDummyMercureObjects(int $nb)
$this->manager->flush();
}

/**
* @Given there are :nb iriOnlyDummies
*/
public function thereAreIriOnlyDummies(int $nb)
{
for ($i = 1; $i <= $nb; ++$i) {
$iriOnlyDummy = $this->buildIriOnlyDummy();
$iriOnlyDummy->setFoo('bar'.$nb);
$this->manager->persist($iriOnlyDummy);
}

$this->manager->flush();
}

/**
* @Given there are :nb absoluteUrlDummy objects with a related absoluteUrlRelationDummy
*/
Expand Down Expand Up @@ -1867,6 +1883,14 @@ private function buildGreeting()
return $this->isOrm() ? new Greeting() : new GreetingDocument();
}

/**
* @return IriOnlyDummy|IriOnlyDummyDocument
*/
private function buildIriOnlyDummy()
{
return $this->isOrm() ? new IriOnlyDummy() : new IriOnlyDummyDocument();
}

/**
* @return MaxDepthDummy|MaxDepthDummyDocument
*/
Expand Down
50 changes: 50 additions & 0 deletions features/jsonld/iri_only.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Feature: JSON-LD using iri_only parameter
In order to improve Vulcain support
As a Vulcain user and as a developer
I should be able to only get an IRI list when I ask a resource.

Scenario: Retrieve Dummy's resource context with iri_only
When I send a "GET" request to "/contexts/IriOnlyDummy"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": {
"@vocab": "http://example.com/docs.jsonld#",
"hydra": "http://www.w3.org/ns/hydra/core#",
"hydra:member": {
"@type": "@id"
}
}
}
"""

@createSchema
Scenario: Retrieve Dummies with iri_only and jsonld_embed_context
Given there are 3 iriOnlyDummies
When I send a "GET" request to "/iri_only_dummies"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": {
"@vocab": "http://example.com/docs.jsonld#",
"hydra": "http://www.w3.org/ns/hydra/core#",
"hydra:member": {
"@type": "@id"
}
},
"@id": "/iri_only_dummies",
"@type": "hydra:Collection",
"hydra:member": [
"/iri_only_dummies/1",
"/iri_only_dummies/2",
"/iri_only_dummies/3"
],
"hydra:totalItems": 3
}
"""
3 changes: 1 addition & 2 deletions src/Hydra/Serializer/CollectionNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,10 @@ public function normalize($object, $format = null, array $context = [])
}

$data['@type'] = 'hydra:Collection';

$data['hydra:member'] = [];
$iriOnly = $context[self::IRI_ONLY] ?? $this->defaultContext[self::IRI_ONLY];
foreach ($object as $obj) {
$data['hydra:member'][] = $iriOnly ? ['@id' => $this->iriConverter->getIriFromItem($obj)] : $this->normalizer->normalize($obj, $format, $context);
$data['hydra:member'][] = $iriOnly ? $this->iriConverter->getIriFromItem($obj) : $this->normalizer->normalize($obj, $format, $context);
}

if ($object instanceof PaginatorInterface) {
Expand Down
7 changes: 7 additions & 0 deletions src/JsonLd/ContextBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ public function getResourceContext(string $resourceClass, int $referenceType = U
return [];
}

if ($resourceMetadata->getAttribute('normalization_context')['iri_only'] ?? false) {
$context = $this->getBaseContext($referenceType);
$context['hydra:member']['@type'] = '@id';

return $context;
}

return $this->getResourceContextWithShortname($resourceClass, $referenceType, $shortName);
}

Expand Down
1 change: 1 addition & 0 deletions src/Serializer/SerializerContextBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public function createFromRequest(Request $request, bool $normalization, array $
}

$context['resource_class'] = $attributes['resource_class'];
$context['iri_only'] = $resourceMetadata->getAttribute('normalization_context')['iri_only'] ?? false;
$context['input'] = $resourceMetadata->getTypedOperationAttribute($operationType, $attributes[$operationKey], 'input', null, true);
$context['output'] = $resourceMetadata->getTypedOperationAttribute($operationType, $attributes[$operationKey], 'output', null, true);
$context['request_uri'] = $request->getRequestUri();
Expand Down
62 changes: 62 additions & 0 deletions tests/Fixtures/TestBundle/Document/IriOnlyDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?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\Core\Tests\Fixtures\TestBundle\Document;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
* Dummy with iri_only.
*
* @author Pierre Thibaudeau <pierre.thibaudeau@les-tilleuls.coop>
*
* @ApiResource(
* normalizationContext={
* "iri_only"=true,
* "jsonld_embed_context"=true
* }
* )
* @ODM\Document
*/
class IriOnlyDummy
{
/**
* @var int The id
*
* @ODM\Id(strategy="INCREMENT", type="integer")
*/
private $id;

/**
* @var string
*
* @ODM\Field(type="string")
*/
private $foo;

public function getId(): int
{
return $this->id;
}

public function getFoo(): string
{
return $this->foo;
}

public function setFoo(string $foo): void
{
$this->foo = $foo;
}
}
64 changes: 64 additions & 0 deletions tests/Fixtures/TestBundle/Entity/IriOnlyDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?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\Core\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;

/**
* Dummy with iri_only.
*
* @author Pierre Thibaudeau <pierre.thibaudeau@les-tilleuls.coop>
*
* @ApiResource(
* normalizationContext={
* "iri_only"=true,
* "jsonld_embed_context"=true
* }
* )
* @ORM\Entity
*/
class IriOnlyDummy
{
/**
* @var int The id
*
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @var string
*
* @ORM\Column(type="string")
*/
private $foo;

public function getId(): int
{
return $this->id;
}

public function getFoo(): string
{
return $this->foo;
}

public function setFoo(string $foo): void
{
$this->foo = $foo;
}
}
67 changes: 63 additions & 4 deletions tests/Hydra/Serializer/CollectionNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,12 @@ public function testNormalizeIriOnlyResourceCollection(): void

$delegateNormalizerProphecy = $this->prophesize(NormalizerInterface::class);

$normalizer = new CollectionNormalizer($contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $iriConverterProphecy->reveal(), [CollectionNormalizer::IRI_ONLY => true]);
$normalizer = new CollectionNormalizer($contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $iriConverterProphecy->reveal());
$normalizer->setNormalizer($delegateNormalizerProphecy->reveal());

$actual = $normalizer->normalize($data, CollectionNormalizer::FORMAT, [
'collection_operation_name' => 'get',
'operation_type' => OperationType::COLLECTION,
'iri_only' => true,
'resource_class' => Foo::class,
]);

Expand All @@ -374,8 +374,67 @@ public function testNormalizeIriOnlyResourceCollection(): void
'@id' => '/foos',
'@type' => 'hydra:Collection',
'hydra:member' => [
['@id' => '/foos/1'],
['@id' => '/foos/3'],
'/foos/1',
'/foos/3',
],
'hydra:totalItems' => 2,
], $actual);
}

public function testNormalizeIriOnlyEmbedContextResourceCollection(): void
{
$fooOne = new Foo();
$fooOne->id = 1;
$fooOne->bar = 'baz';

$fooThree = new Foo();
$fooThree->id = 3;
$fooThree->bar = 'bzz';

$data = [$fooOne, $fooThree];

$contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class);
$contextBuilderProphecy->getResourceContext(Foo::class)->willReturn([
'@vocab' => 'http://localhost:8080/docs.jsonld#',
'hydra' => 'http://www.w3.org/ns/hydra/core#',
'hydra:member' => [
'@type' => '@id',
],
]);

$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$resourceClassResolverProphecy->getResourceClass($data, Foo::class)->willReturn(Foo::class);

$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$iriConverterProphecy->getIriFromResourceClass(Foo::class)->willReturn('/foos');
$iriConverterProphecy->getIriFromItem($fooOne)->willReturn('/foos/1');
$iriConverterProphecy->getIriFromItem($fooThree)->willReturn('/foos/3');

$delegateNormalizerProphecy = $this->prophesize(NormalizerInterface::class);

$normalizer = new CollectionNormalizer($contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $iriConverterProphecy->reveal());
$normalizer->setNormalizer($delegateNormalizerProphecy->reveal());

$actual = $normalizer->normalize($data, CollectionNormalizer::FORMAT, [
'collection_operation_name' => 'get',
'iri_only' => true,
'jsonld_embed_context' => true,
'resource_class' => Foo::class,
]);

$this->assertSame([
'@context' => [
'@vocab' => 'http://localhost:8080/docs.jsonld#',
'hydra' => 'http://www.w3.org/ns/hydra/core#',
'hydra:member' => [
'@type' => '@id',
],
],
'@id' => '/foos',
'@type' => 'hydra:Collection',
'hydra:member' => [
'/foos/1',
'/foos/3',
],
'hydra:totalItems' => 2,
], $actual);
Expand Down
19 changes: 19 additions & 0 deletions tests/JsonLd/ContextBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,25 @@ public function testResourceContext()
$this->assertEquals($expected, $contextBuilder->getResourceContext($this->entityClass));
}

public function testIriOnlyResourceContext()
{
$this->resourceMetadataFactoryProphecy->create($this->entityClass)->willReturn(new ResourceMetadata('DummyEntity', null, null, null, null, ['normalization_context' => ['iri_only' => true]]));
$this->propertyNameCollectionFactoryProphecy->create($this->entityClass)->willReturn(new PropertyNameCollection(['dummyPropertyA']));
$this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA')->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'Dummy property A', true, true, true, true, false, false));

$contextBuilder = new ContextBuilder($this->resourceNameCollectionFactoryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->urlGeneratorProphecy->reveal());

$expected = [
'@vocab' => '#',
'hydra' => 'http://www.w3.org/ns/hydra/core#',
'hydra:member' => [
'@type' => '@id',
],
];

$this->assertEquals($expected, $contextBuilder->getResourceContext($this->entityClass));
}

public function testResourceContextWithJsonldContext()
{
$this->resourceMetadataFactoryProphecy->create($this->entityClass)->willReturn(new ResourceMetadata('DummyEntity'));
Expand Down

0 comments on commit 1c7c26d

Please sign in to comment.