Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@
"symfony/form": "^3.4 || ^4.4 || ^5.1",
"symfony/framework-bundle": "^4.4 || ^5.1",
"symfony/http-client": "^4.4 || ^5.1",
"symfony/maker-bundle": "^1.24",
"symfony/intl": "^4.4 || ^5.3",
"symfony/maker-bundle": "^1.24",
"symfony/mercure-bundle": "*",
"symfony/messenger": "^4.4 || ^5.1",
"symfony/phpunit-bridge": "^5.1.7",
Expand Down
90 changes: 90 additions & 0 deletions features/hydra/collection.feature
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,96 @@ Feature: Collections support
}
"""

@createSchema
Scenario: Cursor-based pagination with ranged items and set cursor
Given there are 10 of these so many objects
When I send a "GET" request to "/so_manies?order%5Bid%5D=desc&id%5Blt%5D=7"
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 valid according to this schema:
"""
{
"type": "object",
"properties": {
"@context": {"pattern": "^/contexts/SoMany$"},
"@id": {"pattern": "^/so_manies$"},
"@type": {"pattern": "^hydra:Collection"},
"hydra:view": {
"type": "object",
"properties": {
"@id": {"pattern": "^/so_manies\\?order%5Bid%5D=desc&id%5Blt%5D=7$"},
"@type": {"pattern": "^hydra:PartialCollectionView$"},
"hydra:previous": {"pattern": "^/so_manies\\?order%5Bid%5D=desc&id%5Bgt%5D=6$"},
"hydra:next": {"pattern": "^/so_manies\\?order%5Bid%5D=desc&id%5Blt%5D=4$"}
},
"additionalProperties": false
},
"hydra:member": {
"type": "array",
"items": {
"type": "object",
"properties": {
"@id": {
"oneOf": [
{"pattern": "^/so_manies/6$"},
{"pattern": "^/so_manies/5$"},
{"pattern": "^/so_manies/4$"}
]
}
}
},
"minItems": 3
}
}
}
"""

@createSchema
Scenario: Cursor-based pagination with ranged items on uids
Given there are 10 of these so many uid objects
When I send a "GET" request to "/so_many_uids?order%5Bid%5D=desc&id%5Blt%5D=1ec5c128-f3d4-62d0-b528-68fef707f0bd"
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 valid according to this schema:
"""
{
"type": "object",
"properties": {
"@context": {"pattern": "^/contexts/SoManyUids"},
"@id": {"pattern": "^/so_many_uids"},
"@type": {"pattern": "^hydra:Collection"},
"hydra:view": {
"type": "object",
"properties": {
"@id": {"pattern": "^/so_many_uids\\?order%5Bid%5D=desc&id%5Blt%5D=1ec5c128-f3d4-62d0-b528-68fef707f0bd$"},
"@type": {"pattern": "^hydra:PartialCollectionView$"},
"hydra:previous": {"pattern": "^/so_many_uids\\?order%5Bid%5D=desc&id%5Bgt%5D=1ec5c128-f3d4-61ae-bb3c-68fef707f0bd$"},
"hydra:next": {"pattern": "^/so_many_uids\\?order%5Bid%5D=desc&id%5Blt%5D=1ec5c128-f3d3-6fc4-8b52-68fef707f0bd$"}
},
"additionalProperties": false
},
"hydra:member": {
"type": "array",
"items": {
"type": "object",
"properties": {
"content": {
"oneOf": [
{"pattern": "^Many #7$"},
{"pattern": "^Many #6$"},
{"pattern": "^Many #5$"}
]
}
}
},
"minItems": 3
}
}
}
"""

@createSchema
Scenario: Cursor-based pagination with range filtered items
Given there are 10 of these so many objects
Expand Down
7 changes: 4 additions & 3 deletions src/Doctrine/Common/Filter/RangeFilterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
use ApiPlatform\Exception\InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Ramsey\Uuid\Nonstandard\UuidV6;

/**
* Trait for filtering the collection by range.
Expand Down Expand Up @@ -124,18 +125,18 @@ private function normalizeBetweenValues(array $values): ?array
/**
* Normalize the value.
*
* @return int|float|null
* @return int|float|string | null
*/
private function normalizeValue(string $value, string $operator)
{
if (!is_numeric($value)) {
if (!is_numeric($value) && !UuidV6::isValid($value)) {
$this->getLogger()->notice('Invalid filter ignored', [
'exception' => new InvalidArgumentException(sprintf('Invalid value for "[%s]", expected number', $operator)),
]);

return null;
}

return $value + 0; // coerce $value to the right type.
return $value;
}
}
30 changes: 30 additions & 0 deletions tests/Core/Behat/DoctrineContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SecuredDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Site;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SoMany;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SoManyUids;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SymfonyUuidDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Taxon;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ThirdLevel;
Expand All @@ -173,6 +174,7 @@
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Ramsey\Uuid\Nonstandard\UuidV6;
use Ramsey\Uuid\Uuid;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
Expand Down Expand Up @@ -285,6 +287,34 @@ public function thereAreOfTheseSoManyObjects(int $nb)
$this->manager->flush();
}

/**
* @Given there are :nb of these so many uid objects
*/
public function thereAreOfTheseSoManyUidObjects(int $nb)
{
$ids = [
'1ec5c128-f3d2-643a-8b17-68fef707f0bd', // 1
'1ec5c128-f3d3-6cf4-a77e-68fef707f0bd',
'1ec5c128-f3d3-6e02-8834-68fef707f0bd',
'1ec5c128-f3d3-6ef2-b5f3-68fef707f0bd',
'1ec5c128-f3d3-6fc4-8b52-68fef707f0bd',
'1ec5c128-f3d4-6096-b820-68fef707f0bd',
'1ec5c128-f3d4-61ae-bb3c-68fef707f0bd', // 7
'1ec5c128-f3d4-62d0-b528-68fef707f0bd',
'1ec5c128-f3d4-63f2-b845-68fef707f0bd',
'1ec5c128-f3d4-6514-8d2b-68fef707f0bd', // 10
];

for ($i = 1; $i <= $nb; ++$i) {
$ids[] = UuidV6::uuid6()->toString();
$dummy = new SoManyUids($ids[$i -1] ?? null);
$dummy->content = 'Many #'.$i;

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

/**
* @When some dummy table inheritance data but not api resource child are created
*/
Expand Down
56 changes: 56 additions & 0 deletions tests/Fixtures/TestBundle/Entity/SoManyUids.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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\Entity;

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Nonstandard\UuidV6;

/**
* @ORM\Entity
* @ApiResource(attributes={
* "pagination_partial"=true,
* "pagination_via_cursor"={
* {"field"="id", "direction"="DESC"}
* }
* })
*
* @ApiFilter(RangeFilter::class, properties={"id"})
* @ApiFilter(OrderFilter::class, properties={"id"="DESC"})
*/
class SoManyUids
{
/**
* @ORM\Id
* @ORM\Column(type="uuid")
*/
public $id;

/**
* @ORM\Column(nullable=true)
*/
public $content;

public function __construct($id)
{
if ($id) {
$this->id = UuidV6::fromString($id);
} else {
$this->id = UuidV6::uuid6();
}
}
}