-
-
Notifications
You must be signed in to change notification settings - Fork 895
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for generating property schema with Choice restriction
- Loading branch information
Showing
7 changed files
with
287 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
...ridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<?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\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction; | ||
|
||
use ApiPlatform\Core\Metadata\Property\PropertyMetadata; | ||
use Symfony\Component\PropertyInfo\Type; | ||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\Constraints\Choice; | ||
|
||
/** | ||
* @author Tomas Norkūnas <[email protected]> | ||
*/ | ||
final class PropertySchemaChoiceRestriction implements PropertySchemaRestrictionMetadataInterface | ||
{ | ||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array | ||
{ | ||
if (!\in_array($builtinType = $propertyMetadata->getType()->getBuiltinType(), [Type::BUILTIN_TYPE_STRING, Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true)) { | ||
return []; | ||
} | ||
|
||
if (\is_callable($choices = $constraint->callback)) { | ||
$choices = $choices(); | ||
} elseif (\is_array($constraint->choices)) { | ||
$choices = $constraint->choices; | ||
} else { | ||
$choices = []; | ||
} | ||
|
||
if (!$choices) { | ||
return []; | ||
} | ||
|
||
if ($constraint->multiple) { | ||
$restriction['type'] = 'array'; | ||
$restriction['items'] = ['type' => Type::BUILTIN_TYPE_STRING !== $builtinType ? 'string' : 'number', 'enum' => $choices]; | ||
|
||
if (null !== $constraint->min) { | ||
$restriction['minItems'] = $constraint->min; | ||
} | ||
|
||
if (null !== $constraint->max) { | ||
$restriction['maxItems'] = $constraint->max; | ||
} | ||
} else { | ||
$restriction['enum'] = $choices; | ||
} | ||
|
||
return $restriction; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool | ||
{ | ||
return $constraint instanceof Choice && null !== $propertyMetadata->getType(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
...e/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestrictionTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?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\Core\Tests\Bridge\Symfony\Validator\Metadata\Property\Restriction; | ||
|
||
use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaChoiceRestriction; | ||
use ApiPlatform\Core\Metadata\Property\PropertyMetadata; | ||
use ApiPlatform\Core\Tests\ProphecyTrait; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\PropertyInfo\Type; | ||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\Constraints\Choice; | ||
use Symfony\Component\Validator\Constraints\Positive; | ||
|
||
/** | ||
* @author Tomas Norkūnas <[email protected]> | ||
*/ | ||
final class PropertySchemaChoiceRestrictionTest extends TestCase | ||
{ | ||
use ProphecyTrait; | ||
|
||
private $propertySchemaChoiceRestriction; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->propertySchemaChoiceRestriction = new PropertySchemaChoiceRestriction(); | ||
} | ||
|
||
/** | ||
* @dataProvider supportsProvider | ||
*/ | ||
public function testSupports(Constraint $constraint, PropertyMetadata $propertyMetadata, bool $expectedResult): void | ||
{ | ||
self::assertSame($expectedResult, $this->propertySchemaChoiceRestriction->supports($constraint, $propertyMetadata)); | ||
} | ||
|
||
public function supportsProvider(): \Generator | ||
{ | ||
yield 'supported' => [new Choice(['choices' => ['a', 'b']]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)), true]; | ||
|
||
yield 'not supported' => [new Positive(), new PropertyMetadata(), false]; | ||
} | ||
|
||
/** | ||
* @dataProvider createProvider | ||
*/ | ||
public function testCreate(Constraint $constraint, PropertyMetadata $propertyMetadata, array $expectedResult): void | ||
{ | ||
self::assertSame($expectedResult, $this->propertySchemaChoiceRestriction->create($constraint, $propertyMetadata)); | ||
} | ||
|
||
public function createProvider(): \Generator | ||
{ | ||
yield 'single string choice' => [new Choice(['choices' => ['a', 'b']]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)), ['enum' => ['a', 'b']]]; | ||
yield 'multi string choice' => [new Choice(['choices' => ['a', 'b'], 'multiple' => true]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)), ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b']]]]; | ||
yield 'multi string choice min' => [new Choice(['choices' => ['a', 'b'], 'multiple' => true, 'min' => 2]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)), ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b']], 'minItems' => 2]]; | ||
yield 'multi string choice max' => [new Choice(['choices' => ['a', 'b', 'c', 'd'], 'multiple' => true, 'max' => 4]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)), ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']], 'maxItems' => 4]]; | ||
yield 'multi string choice min/max' => [new Choice(['choices' => ['a', 'b', 'c', 'd'], 'multiple' => true, 'min' => 2, 'max' => 4]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)), ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']], 'minItems' => 2, 'maxItems' => 4]]; | ||
|
||
yield 'single int choice' => [new Choice(['choices' => [1, 2]]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT)), ['enum' => [1, 2]]]; | ||
yield 'multi int choice' => [new Choice(['choices' => [1, 2], 'multiple' => true]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT)), ['type' => 'array', 'items' => ['type' => 'number', 'enum' => [1, 2]]]]; | ||
yield 'multi int choice min' => [new Choice(['choices' => [1, 2], 'multiple' => true, 'min' => 2]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT)), ['type' => 'array', 'items' => ['type' => 'number', 'enum' => [1, 2]], 'minItems' => 2]]; | ||
yield 'multi int choice max' => [new Choice(['choices' => [1, 2, 3, 4], 'multiple' => true, 'max' => 4]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT)), ['type' => 'array', 'items' => ['type' => 'number', 'enum' => [1, 2, 3, 4]], 'maxItems' => 4]]; | ||
yield 'multi int choice min/max' => [new Choice(['choices' => [1, 2, 3, 4], 'multiple' => true, 'min' => 2, 'max' => 4]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT)), ['type' => 'array', 'items' => ['type' => 'number', 'enum' => [1, 2, 3, 4]], 'minItems' => 2, 'maxItems' => 4]]; | ||
|
||
yield 'single float choice' => [new Choice(['choices' => [1.1, 2.2]]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_FLOAT)), ['enum' => [1.1, 2.2]]]; | ||
yield 'multi float choice' => [new Choice(['choices' => [1.1, 2.2], 'multiple' => true]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_FLOAT)), ['type' => 'array', 'items' => ['type' => 'number', 'enum' => [1.1, 2.2]]]]; | ||
yield 'multi float choice min' => [new Choice(['choices' => [1.1, 2.2], 'multiple' => true, 'min' => 2]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_FLOAT)), ['type' => 'array', 'items' => ['type' => 'number', 'enum' => [1.1, 2.2]], 'minItems' => 2]]; | ||
yield 'multi float choice max' => [new Choice(['choices' => [1.1, 2.2, 3.3, 4.4], 'multiple' => true, 'max' => 4]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_FLOAT)), ['type' => 'array', 'items' => ['type' => 'number', 'enum' => [1.1, 2.2, 3.3, 4.4]], 'maxItems' => 4]]; | ||
yield 'multi float choice min/max' => [new Choice(['choices' => [1.1, 2.2, 3.3, 4.4], 'multiple' => true, 'min' => 2, 'max' => 4]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_FLOAT)), ['type' => 'array', 'items' => ['type' => 'number', 'enum' => [1.1, 2.2, 3.3, 4.4]], 'minItems' => 2, 'maxItems' => 4]]; | ||
|
||
yield 'single choice callback' => [new Choice(['callback' => [ChoiceCallback::class, 'getChoices']]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)), ['enum' => ['a', 'b', 'c', 'd']]]; | ||
yield 'multi choice callback' => [new Choice(['callback' => [ChoiceCallback::class, 'getChoices'], 'multiple' => true]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)), ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']]]]; | ||
|
||
yield 'not supported type' => [new Choice(['choices' => [new \stdClass()]]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT)), []]; | ||
} | ||
} | ||
|
||
final class ChoiceCallback | ||
{ | ||
public static function getChoices(): array | ||
{ | ||
return ['a', 'b', 'c', 'd']; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<?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\Core\Tests\Fixtures; | ||
|
||
use Symfony\Component\Validator\Constraints as Assert; | ||
|
||
class DummyValidatedChoiceEntity | ||
{ | ||
/** | ||
* @var string | ||
* | ||
* @Assert\Choice(choices={"a", "b"}) | ||
*/ | ||
public $dummySingleChoice; | ||
|
||
/** | ||
* @var string | ||
* | ||
* @Assert\Choice(callback={DummyValidatedChoiceEntity::class, "getChoices"}) | ||
*/ | ||
public $dummySingleChoiceCallback; | ||
|
||
/** | ||
* @var string[] | ||
* | ||
* @Assert\Choice(choices={"a", "b"}, multiple=true) | ||
*/ | ||
public $dummyMultiChoice; | ||
|
||
/** | ||
* @var string[] | ||
* | ||
* @Assert\Choice(callback={DummyValidatedChoiceEntity::class, "getChoices"}, multiple=true) | ||
*/ | ||
public $dummyMultiChoiceCallback; | ||
|
||
/** | ||
* @var string[] | ||
* | ||
* @Assert\Choice(choices={"a", "b", "c", "d"}, multiple=true, min=2) | ||
*/ | ||
public $dummyMultiChoiceMin; | ||
|
||
/** | ||
* @var string[] | ||
* | ||
* @Assert\Choice(choices={"a", "b", "c", "d"}, multiple=true, max=4) | ||
*/ | ||
public $dummyMultiChoiceMax; | ||
|
||
/** | ||
* @var string[] | ||
* | ||
* @Assert\Choice(choices={"a", "b", "c", "d"}, multiple=true, min=2, max=4) | ||
*/ | ||
public $dummyMultiChoiceMinMax; | ||
|
||
public static function getChoices(): array | ||
{ | ||
return ['a', 'b', 'c', 'd']; | ||
} | ||
} |