diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f20d4869f3..c952f11eb66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -264,7 +264,7 @@ jobs: continue-on-error: true phpunit-components: - name: PHPUnit ${{ matrix.component }} (PHP ${{ matrix.php.version }} ${{ matrix.php.coverage && 'coverage' || '' }}${{ matrix.php.deprecations && 'no deprecations' || '' }}) + name: PHPUnit ${{ matrix.component }} (PHP ${{ matrix.php.version }} ${{ matrix.php.coverage && 'coverage' || '' }} runs-on: ubuntu-latest timeout-minutes: 20 strategy: @@ -274,8 +274,6 @@ jobs: - version: '8.3' - version: '8.4' coverage: true - - version: '8.4' - deprecations: true component: - api-platform/doctrine-common - api-platform/doctrine-orm @@ -309,18 +307,13 @@ jobs: composer global require soyuka/pmu composer global config allow-plugins.soyuka/pmu true --no-interaction composer global link . --permanent - - name: Allow unstable project dependencies - if: matrix.php.deprecations == true - run: | - cd $(composer ${{matrix.component}} --cwd) - composer config minimum-stability dev - name: Run ${{ matrix.component }} install run: | composer ${{matrix.component}} update - name: Run ${{ matrix.component }} tests run: | mkdir -p /tmp/build/logs/phpunit - composer ${{matrix.component}} test --log-junit "/tmp/build/logs/phpunit/junit.xml" ${{ matrix.php.coverage && '--coverage-clover /tmp/build/logs/phpunit/clover.xml' || '' }} ${{ matrix.php.deprecations && '--fail-on-deprecation --display-deprecations' || '' }} + composer ${{matrix.component}} test --log-junit "/tmp/build/logs/phpunit/junit.xml" ${{ matrix.php.coverage && '--coverage-clover /tmp/build/logs/phpunit/clover.xml' || '' }} - name: Upload test artifacts if: always() uses: actions/upload-artifact@v4 @@ -347,6 +340,56 @@ jobs: php-coveralls --coverage_clover=/tmp/build/logs/phpunit/clover.xml continue-on-error: true + phpunit-components-fail-deprecation: + name: PHPUnit no deprecations ${{ matrix.component }} (PHP ${{ matrix.php.version }} + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + php: + - version: '8.4' + component: + - api-platform/doctrine-common + - api-platform/doctrine-orm + - api-platform/doctrine-odm + - api-platform/metadata + - api-platform/hydra + - api-platform/json-api + - api-platform/json-schema + - api-platform/elasticsearch + - api-platform/openapi + - api-platform/graphql + - api-platform/http-cache + - api-platform/ramsey-uuid + - api-platform/serializer + - api-platform/state + - api-platform/symfony + - api-platform/validator + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php.version }} + tools: pecl, composer + extensions: intl, bcmath, curl, openssl, mbstring, pdo_sqlite, mongodb + ini-values: memory_limit=-1 + - name: Linking + run: | + composer global require soyuka/pmu + composer global config allow-plugins.soyuka/pmu true --no-interaction + composer global link . --permanent + - name: Run ${{ matrix.component }} install + run: | + composer ${{matrix.component}} update + - name: Run ${{ matrix.component }} tests + run: | + mkdir -p /tmp/build/logs/phpunit + cd $(composer ${{matrix.component}} --cwd) + ./vendor/bin/phpunit --fail-on-deprecation --display-deprecations --log-junit "/tmp/build/logs/phpunit/junit.xml" + behat: name: Behat (PHP ${{ matrix.php }}) runs-on: ubuntu-latest diff --git a/composer.json b/composer.json index 6d0b0efb200..2e2a3be2281 100644 --- a/composer.json +++ b/composer.json @@ -114,7 +114,7 @@ "symfony/property-info": "^6.4 || ^7.1", "symfony/serializer": "^6.4 || ^7.0", "symfony/translation-contracts": "^3.3", - "symfony/type-info": "^7.3-dev", + "symfony/type-info": "v7.3.0-BETA1", "symfony/web-link": "^6.4 || ^7.0", "willdurand/negotiation": "^3.1" }, @@ -211,10 +211,6 @@ }, "type": "library", "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, {"type": "vcs", "url": "https://github.com/soyuka/phpunit"} ] } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index d1a6aa0bb23..4a77b83c2e2 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -62,10 +62,6 @@ parameters: paths: - tests/Fixtures/TestBundle/Document/ - src/Metadata/Tests/Fixtures/ApiResource/ - - - message: '#Parameter \#1 \$constraint of method#' - paths: - - tests/Symfony/Validator/Metadata/Property/Restriction/ - '#Access to an undefined property Prophecy\\Prophecy\\ObjectProphecy<(\\?[a-zA-Z0-9_]+)+>::\$[a-zA-Z0-9_]+#' # https://github.com/phpstan/phpstan-symfony/issues/76 - diff --git a/src/Doctrine/Common/composer.json b/src/Doctrine/Common/composer.json index def632fb4b6..1ebf18e6bcf 100644 --- a/src/Doctrine/Common/composer.json +++ b/src/Doctrine/Common/composer.json @@ -35,7 +35,7 @@ "doctrine/orm": "^2.17 || ^3.0", "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "11.5.x-dev", - "symfony/type-info": "^7.3-dev" + "symfony/type-info": "v7.3.0-BETA1" }, "conflict": { "doctrine/persistence": "<1.3" @@ -76,10 +76,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/Doctrine/Odm/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Doctrine/Odm/Tests/PropertyInfo/DoctrineExtractorTest.php index 78d7d70706d..017af790834 100644 --- a/src/Doctrine/Odm/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Doctrine/Odm/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -27,6 +27,7 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Types\Type as MongoDbType; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\TypeInfo\Type; @@ -83,7 +84,7 @@ public function testTestGetPropertiesWithEmbedded(): void ); } - #[\PHPUnit\Framework\Attributes\Group('legacy')] + #[IgnoreDeprecations] #[\PHPUnit\Framework\Attributes\DataProvider('legacyTypesProvider')] public function testExtractLegacy(string $property, ?array $type = null): void { @@ -96,7 +97,7 @@ public function testExtract(string $property, ?Type $type): void $this->assertEquals($type, $this->createExtractor()->getType(DoctrineDummy::class, $property)); } - #[\PHPUnit\Framework\Attributes\Group('legacy')] + #[IgnoreDeprecations] public function testExtractWithEmbedOneLegacy(): void { $expectedTypes = [ @@ -123,7 +124,7 @@ public function testExtractWithEmbedOne(): void ); } - #[\PHPUnit\Framework\Attributes\Group('legacy')] + #[IgnoreDeprecations] public function testExtractWithEmbedManyLegacy(): void { $expectedTypes = [ @@ -153,7 +154,7 @@ public function testExtractWithEmbedMany(): void ); } - #[\PHPUnit\Framework\Attributes\Group('legacy')] + #[IgnoreDeprecations] public function testExtractEnumLegacy(): void { $this->assertEquals([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, EnumString::class)], $this->createExtractor()->getTypes(DoctrineEnum::class, 'enumString')); @@ -168,6 +169,7 @@ public function testExtractEnum(): void $this->assertNull($this->createExtractor()->getType(DoctrineEnum::class, 'enumCustom')); } + #[IgnoreDeprecations] public static function legacyTypesProvider(): array { return [ @@ -258,7 +260,7 @@ public function testGetPropertiesCatchException(): void $this->assertNull($this->createExtractor()->getProperties('Not\Exist')); } - #[\PHPUnit\Framework\Attributes\Group('legacy')] + #[IgnoreDeprecations] public function testGetTypesCatchExceptionLegacy(): void { $this->assertNull($this->createExtractor()->getTypes('Not\Exist', 'baz')); @@ -278,7 +280,7 @@ public function testGeneratedValueNotWritable(): void $this->assertNull($extractor->isReadable(DoctrineGeneratedValue::class, 'foo')); } - #[\PHPUnit\Framework\Attributes\Group('legacy')] + #[IgnoreDeprecations] public function testGetTypesWithEmbedManyOmittingTargetDocumentLegacy(): void { $actualTypes = $this->createExtractor()->getTypes( diff --git a/src/Doctrine/Odm/composer.json b/src/Doctrine/Odm/composer.json index 4870207c873..2242c384e30 100644 --- a/src/Doctrine/Odm/composer.json +++ b/src/Doctrine/Odm/composer.json @@ -30,7 +30,7 @@ "api-platform/state": "^4.1", "doctrine/mongodb-odm": "^2.10", "symfony/property-info": "^6.4 || ^7.1", - "symfony/type-info": "^7.3-dev" + "symfony/type-info": "v7.3.0-BETA1" }, "require-dev": { "doctrine/doctrine-bundle": "^2.11", @@ -76,10 +76,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/Doctrine/Orm/composer.json b/src/Doctrine/Orm/composer.json index a2d49f2ddd2..d590c98465c 100644 --- a/src/Doctrine/Orm/composer.json +++ b/src/Doctrine/Orm/composer.json @@ -40,7 +40,7 @@ "symfony/framework-bundle": "^6.4 || ^7.0", "symfony/property-access": "^6.4 || ^7.0", "symfony/serializer": "^6.4 || ^7.0", - "symfony/type-info": "^7.3-dev", + "symfony/type-info": "v7.3.0-BETA1", "symfony/uid": "^6.4 || ^7.0", "symfony/validator": "^6.4 || ^7.0", "symfony/yaml": "^6.4 || ^7.0" @@ -76,10 +76,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/Documentation/composer.json b/src/Documentation/composer.json index 03a6a7ddb04..26b76eab900 100644 --- a/src/Documentation/composer.json +++ b/src/Documentation/composer.json @@ -40,10 +40,6 @@ "phpunit/phpunit": "11.5.x-dev" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/Elasticsearch/Tests/Extension/SortExtensionTest.php b/src/Elasticsearch/Tests/Extension/SortExtensionTest.php index db72e689550..f2b67414b82 100644 --- a/src/Elasticsearch/Tests/Extension/SortExtensionTest.php +++ b/src/Elasticsearch/Tests/Extension/SortExtensionTest.php @@ -20,6 +20,7 @@ use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\ResourceClassResolverInterface; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; @@ -53,6 +54,7 @@ public function testApplyToCollection(): void self::assertEquals(['sort' => [['name' => ['order' => 'asc']], ['bar' => ['order' => 'desc']]]], $sortExtension->applyToCollection([], Foo::class, (new GetCollection())->withOrder(['name', 'bar' => 'desc']))); } + #[IgnoreDeprecations] public function testApplyToCollectionWithNestedProperty(): void { $fooType = Type::list(Type::object(Foo::class)); diff --git a/src/Elasticsearch/Tests/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactoryTest.php b/src/Elasticsearch/Tests/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactoryTest.php index f9648876273..782f71663c3 100644 --- a/src/Elasticsearch/Tests/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactoryTest.php +++ b/src/Elasticsearch/Tests/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactoryTest.php @@ -16,9 +16,9 @@ use ApiPlatform\Elasticsearch\Metadata\Resource\Factory\ElasticsearchProviderResourceMetadataCollectionFactory; use ApiPlatform\Elasticsearch\Tests\Fixtures\Foo; use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; -use ApiPlatform\Metadata\Tests\Fixtures\Metadata\Get; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; diff --git a/src/Elasticsearch/composer.json b/src/Elasticsearch/composer.json index 42a5fc0b8e0..24be56fda53 100644 --- a/src/Elasticsearch/composer.json +++ b/src/Elasticsearch/composer.json @@ -33,7 +33,7 @@ "symfony/property-access": "^6.4 || ^7.0", "symfony/property-info": "^6.4 || ^7.1", "symfony/serializer": "^6.4 || ^7.0", - "symfony/type-info": "^7.3-dev", + "symfony/type-info": "v7.3.0-BETA1", "symfony/uid": "^6.4 || ^7.0" }, "require-dev": { @@ -76,10 +76,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/GraphQl/Tests/Type/TypeConverterTest.php b/src/GraphQl/Tests/Type/TypeConverterTest.php index 006ddccfbb4..8c4eb980391 100644 --- a/src/GraphQl/Tests/Type/TypeConverterTest.php +++ b/src/GraphQl/Tests/Type/TypeConverterTest.php @@ -30,14 +30,14 @@ use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type as GraphQLType; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\TypeInfo\Type; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\IgnoreDeprecations; /** * @author Alan Poulain diff --git a/src/GraphQl/Type/FieldsBuilder.php b/src/GraphQl/Type/FieldsBuilder.php index 584bfffcd2d..c19b47113bf 100644 --- a/src/GraphQl/Type/FieldsBuilder.php +++ b/src/GraphQl/Type/FieldsBuilder.php @@ -45,8 +45,8 @@ use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\CollectionType; -use Symfony\Component\TypeInfo\TypeIdentifier; use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Builds the GraphQL fields. diff --git a/src/GraphQl/composer.json b/src/GraphQl/composer.json index 788349b9a60..b6818e6e1dd 100644 --- a/src/GraphQl/composer.json +++ b/src/GraphQl/composer.json @@ -26,7 +26,7 @@ "api-platform/serializer": "^4.1", "symfony/property-info": "^6.4 || ^7.1", "symfony/serializer": "^6.4 || ^7.0", - "symfony/type-info": "^7.2", + "symfony/type-info": "v7.3.0-BETA1", "webonyx/graphql-php": "^15.0", "willdurand/negotiation": "^3.1" }, @@ -80,10 +80,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/Hal/Serializer/ItemNormalizer.php b/src/Hal/Serializer/ItemNormalizer.php index ae68b658144..cde2efac85f 100644 --- a/src/Hal/Serializer/ItemNormalizer.php +++ b/src/Hal/Serializer/ItemNormalizer.php @@ -21,6 +21,7 @@ use ApiPlatform\Metadata\ResourceClassResolverInterface; use ApiPlatform\Metadata\UrlGeneratorInterface; use ApiPlatform\Metadata\Util\ClassInfoTrait; +use ApiPlatform\Metadata\Util\TypeHelper; use ApiPlatform\Serializer\AbstractItemNormalizer; use ApiPlatform\Serializer\CacheKeyTrait; use ApiPlatform\Serializer\ContextTrait; @@ -39,7 +40,6 @@ use Symfony\Component\TypeInfo\Type\CollectionType; use Symfony\Component\TypeInfo\Type\CompositeTypeInterface; use Symfony\Component\TypeInfo\Type\ObjectType; -use Symfony\Component\TypeInfo\Type\WrappingTypeInterface; /** * Converts between objects and array including HAL metadata. @@ -212,17 +212,8 @@ private function getComponents(object $object, ?string $format, array $context): $isOne = $className && $this->resourceClassResolver->isResourceClass($className); } } elseif ($type instanceof Type) { - $typeIsCollection = function (Type $type) use (&$typeIsCollection, &$valueType): bool { - return match (true) { - $type instanceof CollectionType => null !== $valueType = $type->getCollectionValueType(), - $type instanceof WrappingTypeInterface => $type->wrappedTypeIsSatisfiedBy($typeIsCollection), - $type instanceof CompositeTypeInterface => $type->composedTypesAreSatisfiedBy($typeIsCollection), - default => false, - }; - }; - - if ($type->isSatisfiedBy($typeIsCollection)) { - $isMany = $valueType->isSatisfiedBy($typeIsResourceClass); + if ($type->isSatisfiedBy(fn ($t) => $t instanceof CollectionType)) { + $isMany = TypeHelper::getCollectionValueType($type)?->isSatisfiedBy($typeIsResourceClass); } else { $isOne = $type->isSatisfiedBy($typeIsResourceClass); } diff --git a/src/Hal/composer.json b/src/Hal/composer.json index 1d35ef3f250..f3f3182ca86 100644 --- a/src/Hal/composer.json +++ b/src/Hal/composer.json @@ -25,7 +25,7 @@ "api-platform/state": "^4.1", "api-platform/metadata": "^4.1", "api-platform/serializer": "^3.4 || ^4.0", - "symfony/type-info": "^7.2" + "symfony/type-info": "v7.3.0-BETA1" }, "autoload": { "psr-4": { @@ -66,10 +66,6 @@ "phpunit/phpunit": "11.5.x-dev" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/HttpCache/composer.json b/src/HttpCache/composer.json index 81627943b0f..c9073074d78 100644 --- a/src/HttpCache/composer.json +++ b/src/HttpCache/composer.json @@ -32,7 +32,7 @@ "symfony/dependency-injection": "^6.4 || ^7.0", "phpspec/prophecy-phpunit": "^2.2", "symfony/http-client": "^6.4 || ^7.0", - "symfony/type-info": "^7.3-dev", + "symfony/type-info": "v7.3.0-BETA1", "phpunit/phpunit": "11.5.x-dev" }, "autoload": { @@ -70,10 +70,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/Hydra/Serializer/DocumentationNormalizer.php b/src/Hydra/Serializer/DocumentationNormalizer.php index ff6fca7bb19..028f7e6ee9e 100644 --- a/src/Hydra/Serializer/DocumentationNormalizer.php +++ b/src/Hydra/Serializer/DocumentationNormalizer.php @@ -29,6 +29,7 @@ use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Metadata\ResourceClassResolverInterface; use ApiPlatform\Metadata\UrlGeneratorInterface; +use ApiPlatform\Metadata\Util\TypeHelper; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; @@ -36,9 +37,7 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\CollectionType; -use Symfony\Component\TypeInfo\Type\CompositeTypeInterface; use Symfony\Component\TypeInfo\Type\ObjectType; -use Symfony\Component\TypeInfo\Type\WrappingTypeInterface; use Symfony\Component\TypeInfo\TypeIdentifier; use const ApiPlatform\JsonLd\HYDRA_CONTEXT; @@ -371,19 +370,8 @@ private function getRange(ApiProperty $propertyMetadata): array|string|null return null; } - /** @var Type|null $collectionValueType */ - $collectionValueType = null; - $typeIsCollection = static function (Type $type) use (&$typeIsCollection, &$collectionValueType): bool { - return match (true) { - $type instanceof CollectionType => null !== $collectionValueType = $type->getCollectionValueType(), - $type instanceof WrappingTypeInterface => $type->wrappedTypeIsSatisfiedBy($typeIsCollection), - $type instanceof CompositeTypeInterface => $type->composedTypesAreSatisfiedBy($typeIsCollection), - default => false, - }; - }; - - if ($nativeType->isSatisfiedBy($typeIsCollection)) { - $nativeType = $collectionValueType; + if ($nativeType->isSatisfiedBy(fn ($t) => $t instanceof CollectionType)) { + $nativeType = TypeHelper::getCollectionValueType($nativeType); } // Check for specific types after potentially unwrapping the collection diff --git a/src/Hydra/Tests/Serializer/DocumentationNormalizerTest.php b/src/Hydra/Tests/Serializer/DocumentationNormalizerTest.php index 7b4270cddd6..d04c92cb3b7 100644 --- a/src/Hydra/Tests/Serializer/DocumentationNormalizerTest.php +++ b/src/Hydra/Tests/Serializer/DocumentationNormalizerTest.php @@ -32,6 +32,7 @@ use ApiPlatform\Metadata\Resource\ResourceNameCollection; use ApiPlatform\Metadata\ResourceClassResolverInterface; use ApiPlatform\Metadata\UrlGeneratorInterface; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -46,6 +47,7 @@ class DocumentationNormalizerTest extends TestCase { use ProphecyTrait; + #[IgnoreDeprecations] public function testNormalize(): void { $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); diff --git a/src/Hydra/composer.json b/src/Hydra/composer.json index fc80f7f69a7..2869f90bfd1 100644 --- a/src/Hydra/composer.json +++ b/src/Hydra/composer.json @@ -32,7 +32,7 @@ "api-platform/json-schema": "^4.1", "api-platform/serializer": "^4.1", "symfony/web-link": "^6.4 || ^7.1", - "symfony/type-info": "^7.3-dev" + "symfony/type-info": "v7.3.0-BETA1" }, "require-dev": { "api-platform/doctrine-odm": "^4.1", @@ -77,10 +77,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php b/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php index 18ad78895c0..b153b65a08b 100644 --- a/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php +++ b/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php @@ -17,10 +17,7 @@ use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\TypeInfo\Type; -use Symfony\Component\TypeInfo\Type\CompositeTypeInterface; use Symfony\Component\TypeInfo\Type\ObjectType; -use Symfony\Component\TypeInfo\Type\WrappingTypeInterface; use Symfony\Component\Validator\ConstraintViolationInterface; use Symfony\Component\Validator\ConstraintViolationListInterface; @@ -94,15 +91,7 @@ private function getSourcePointerFromViolation(ConstraintViolationInterface $vio return "data/relationships/$fieldName"; } } else { - $typeIsObject = static function (Type $type) use (&$typeIsObject): bool { - return match (true) { - $type instanceof WrappingTypeInterface => $type->wrappedTypeIsSatisfiedBy($typeIsObject), - $type instanceof CompositeTypeInterface => $type->composedTypesAreSatisfiedBy($typeIsObject), - default => $type instanceof ObjectType, - }; - }; - - if ($propertyMetadata->getNativeType()?->isSatisfiedBy($typeIsObject)) { + if ($propertyMetadata->getNativeType()?->isSatisfiedBy(fn ($t) => $t instanceof ObjectType)) { return "data/relationships/$fieldName"; } } diff --git a/src/JsonApi/Tests/Serializer/ConstraintViolationNormalizerTest.php b/src/JsonApi/Tests/Serializer/ConstraintViolationNormalizerTest.php index d8b62014488..e629f864b9d 100644 --- a/src/JsonApi/Tests/Serializer/ConstraintViolationNormalizerTest.php +++ b/src/JsonApi/Tests/Serializer/ConstraintViolationNormalizerTest.php @@ -18,6 +18,7 @@ use ApiPlatform\JsonApi\Tests\Fixtures\RelatedDummy; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; @@ -47,6 +48,7 @@ public function testSupportNormalization(): void $this->assertSame([ConstraintViolationListInterface::class => true], $normalizer->getSupportedTypes($normalizer::FORMAT)); } + #[IgnoreDeprecations] public function testNormalize(): void { $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); diff --git a/src/JsonApi/composer.json b/src/JsonApi/composer.json index 384a4a18ee8..acc471cf7ba 100644 --- a/src/JsonApi/composer.json +++ b/src/JsonApi/composer.json @@ -29,7 +29,7 @@ "api-platform/state": "^4.1", "symfony/error-handler": "^6.4 || ^7.0", "symfony/http-foundation": "^6.4 || ^7.0", - "symfony/type-info": "^7.2" + "symfony/type-info": "v7.3.0-BETA1" }, "require-dev": { "phpspec/prophecy": "^1.19", @@ -72,10 +72,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/JsonLd/composer.json b/src/JsonLd/composer.json index 53d7a77b97e..df0abb84aa5 100644 --- a/src/JsonLd/composer.json +++ b/src/JsonLd/composer.json @@ -66,14 +66,10 @@ "test": "./vendor/bin/phpunit" }, "require-dev": { - "symfony/type-info": "^7.3-dev", + "symfony/type-info": "v7.3.0-BETA1", "phpunit/phpunit": "11.5.x-dev" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/JsonSchema/Command/JsonSchemaGenerateCommand.php b/src/JsonSchema/Command/JsonSchemaGenerateCommand.php index eebcf5d5286..7b2f282146f 100644 --- a/src/JsonSchema/Command/JsonSchemaGenerateCommand.php +++ b/src/JsonSchema/Command/JsonSchemaGenerateCommand.php @@ -16,6 +16,7 @@ use ApiPlatform\JsonSchema\Schema; use ApiPlatform\JsonSchema\SchemaFactoryInterface; use ApiPlatform\Metadata\HttpOperation; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidOptionException; use Symfony\Component\Console\Input\InputArgument; @@ -29,6 +30,7 @@ * * @author Jacques Lefebvre */ +#[AsCommand(name: 'api:json-schema:generate')] final class JsonSchemaGenerateCommand extends Command { private array $formats; @@ -90,9 +92,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - - public static function getDefaultName(): string - { - return 'api:json-schema:generate'; - } } diff --git a/src/JsonSchema/SchemaFactory.php b/src/JsonSchema/SchemaFactory.php index a6c4d3aeb64..574ec26a6d2 100644 --- a/src/JsonSchema/SchemaFactory.php +++ b/src/JsonSchema/SchemaFactory.php @@ -22,6 +22,7 @@ use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\ResourceClassResolverInterface; +use ApiPlatform\Metadata\Util\TypeHelper; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; @@ -29,7 +30,6 @@ use Symfony\Component\TypeInfo\Type\CollectionType; use Symfony\Component\TypeInfo\Type\CompositeTypeInterface; use Symfony\Component\TypeInfo\Type\ObjectType; -use Symfony\Component\TypeInfo\Type\WrappingTypeInterface; use Symfony\Component\TypeInfo\TypeIdentifier; /** @@ -331,11 +331,7 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str $isCollection = $t instanceof CollectionType; if ($isCollection) { - $valueType = $t->getCollectionValueType(); - } - - while ($valueType instanceof WrappingTypeInterface) { - $valueType = $valueType->getWrappedType(); + $valueType = TypeHelper::getCollectionValueType($t); } if (!$valueType instanceof ObjectType) { diff --git a/src/JsonSchema/Tests/Metadata/Property/Factory/SchemaPropertyMetadataFactoryTest.php b/src/JsonSchema/Tests/Metadata/Property/Factory/SchemaPropertyMetadataFactoryTest.php index 3e3cc1ff47e..48b1528bbf6 100644 --- a/src/JsonSchema/Tests/Metadata/Property/Factory/SchemaPropertyMetadataFactoryTest.php +++ b/src/JsonSchema/Tests/Metadata/Property/Factory/SchemaPropertyMetadataFactoryTest.php @@ -20,15 +20,30 @@ use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\ResourceClassResolverInterface; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; class SchemaPropertyMetadataFactoryTest extends TestCase { + #[IgnoreDeprecations] + public function testEnumLegacy(): void + { + $this->expectUserDeprecationMessage('Since api_platform/metadata 4.2: The "builtinTypes" argument of "ApiPlatform\Metadata\ApiProperty" is deprecated, use "nativeType" instead.'); + $resourceClassResolver = $this->createMock(ResourceClassResolverInterface::class); + $apiProperty = new ApiProperty(builtinTypes: [new LegacyType(builtinType: 'object', nullable: true, class: IntEnumAsIdentifier::class)]); + $decorated = $this->createMock(PropertyMetadataFactoryInterface::class); + $decorated->expects($this->once())->method('create')->with(DummyWithEnum::class, 'intEnumAsIdentifier')->willReturn($apiProperty); + $schemaPropertyMetadataFactory = new SchemaPropertyMetadataFactory($resourceClassResolver, $decorated); + $apiProperty = $schemaPropertyMetadataFactory->create(DummyWithEnum::class, 'intEnumAsIdentifier'); + $this->assertEquals(['type' => ['integer', 'null'], 'enum' => [1, 2, null]], $apiProperty->getSchema()); + } + public function testEnum(): void { $resourceClassResolver = $this->createMock(ResourceClassResolverInterface::class); - $apiProperty = new ApiProperty(builtinTypes: [new Type(builtinType: 'object', nullable: true, class: IntEnumAsIdentifier::class)]); + $apiProperty = new ApiProperty(nativeType: Type::nullable(Type::enum(IntEnumAsIdentifier::class))); // @phpstan-ignore-line $decorated = $this->createMock(PropertyMetadataFactoryInterface::class); $decorated->expects($this->once())->method('create')->with(DummyWithEnum::class, 'intEnumAsIdentifier')->willReturn($apiProperty); $schemaPropertyMetadataFactory = new SchemaPropertyMetadataFactory($resourceClassResolver, $decorated); @@ -36,11 +51,27 @@ public function testEnum(): void $this->assertEquals(['type' => ['integer', 'null'], 'enum' => [1, 2, null]], $apiProperty->getSchema()); } + #[IgnoreDeprecations] + public function testWithCustomOpenApiContextLegacy(): void + { + $this->expectUserDeprecationMessage('Since api_platform/metadata 4.2: The "builtinTypes" argument of "ApiPlatform\Metadata\ApiProperty" is deprecated, use "nativeType" instead.'); + $resourceClassResolver = $this->createMock(ResourceClassResolverInterface::class); + $apiProperty = new ApiProperty( + builtinTypes: [new LegacyType(builtinType: 'object', nullable: true, class: IntEnumAsIdentifier::class)], + openapiContext: ['type' => 'object', 'properties' => ['alpha' => ['type' => 'integer']]], + ); + $decorated = $this->createMock(PropertyMetadataFactoryInterface::class); + $decorated->expects($this->once())->method('create')->with(DummyWithCustomOpenApiContext::class, 'acme')->willReturn($apiProperty); + $schemaPropertyMetadataFactory = new SchemaPropertyMetadataFactory($resourceClassResolver, $decorated); + $apiProperty = $schemaPropertyMetadataFactory->create(DummyWithCustomOpenApiContext::class, 'acme'); + $this->assertEquals([], $apiProperty->getSchema()); + } + public function testWithCustomOpenApiContext(): void { $resourceClassResolver = $this->createMock(ResourceClassResolverInterface::class); $apiProperty = new ApiProperty( - builtinTypes: [new Type(builtinType: 'object', nullable: true, class: IntEnumAsIdentifier::class)], + nativeType: Type::nullable(Type::enum(IntEnumAsIdentifier::class)), // @phpstan-ignore-line openapiContext: ['type' => 'object', 'properties' => ['alpha' => ['type' => 'integer']]], ); $decorated = $this->createMock(PropertyMetadataFactoryInterface::class); @@ -50,12 +81,43 @@ public function testWithCustomOpenApiContext(): void $this->assertEquals([], $apiProperty->getSchema()); } + #[IgnoreDeprecations] + public function testWithCustomOpenApiContextWithoutTypeDefinitionLegacy(): void + { + $this->expectUserDeprecationMessage('Since api_platform/metadata 4.2: The "builtinTypes" argument of "ApiPlatform\Metadata\ApiProperty" is deprecated, use "nativeType" instead.'); + $resourceClassResolver = $this->createMock(ResourceClassResolverInterface::class); + $apiProperty = new ApiProperty( + openapiContext: ['description' => 'My description'], + builtinTypes: [new LegacyType(builtinType: 'bool')], + ); + $decorated = $this->createMock(PropertyMetadataFactoryInterface::class); + $decorated->expects($this->once())->method('create')->with(DummyWithCustomOpenApiContext::class, 'foo')->willReturn($apiProperty); + $schemaPropertyMetadataFactory = new SchemaPropertyMetadataFactory($resourceClassResolver, $decorated); + $apiProperty = $schemaPropertyMetadataFactory->create(DummyWithCustomOpenApiContext::class, 'foo'); + $this->assertEquals([ + 'type' => 'boolean', + ], $apiProperty->getSchema()); + + $apiProperty = new ApiProperty( + openapiContext: ['iris' => 'https://schema.org/Date'], + builtinTypes: [new LegacyType(builtinType: 'object', class: \DateTimeImmutable::class)], + ); + $decorated = $this->createMock(PropertyMetadataFactoryInterface::class); + $decorated->expects($this->once())->method('create')->with(DummyWithCustomOpenApiContext::class, 'bar')->willReturn($apiProperty); + $schemaPropertyMetadataFactory = new SchemaPropertyMetadataFactory($resourceClassResolver, $decorated); + $apiProperty = $schemaPropertyMetadataFactory->create(DummyWithCustomOpenApiContext::class, 'bar'); + $this->assertEquals([ + 'type' => 'string', + 'format' => 'date-time', + ], $apiProperty->getSchema()); + } + public function testWithCustomOpenApiContextWithoutTypeDefinition(): void { $resourceClassResolver = $this->createMock(ResourceClassResolverInterface::class); $apiProperty = new ApiProperty( openapiContext: ['description' => 'My description'], - builtinTypes: [new Type(builtinType: 'bool')], + nativeType: Type::bool(), ); $decorated = $this->createMock(PropertyMetadataFactoryInterface::class); $decorated->expects($this->once())->method('create')->with(DummyWithCustomOpenApiContext::class, 'foo')->willReturn($apiProperty); @@ -67,7 +129,7 @@ public function testWithCustomOpenApiContextWithoutTypeDefinition(): void $apiProperty = new ApiProperty( openapiContext: ['iris' => 'https://schema.org/Date'], - builtinTypes: [new Type(builtinType: 'object', class: \DateTimeImmutable::class)], + nativeType: Type::object(\DateTimeImmutable::class), ); $decorated = $this->createMock(PropertyMetadataFactoryInterface::class); $decorated->expects($this->once())->method('create')->with(DummyWithCustomOpenApiContext::class, 'bar')->willReturn($apiProperty); diff --git a/src/JsonSchema/Tests/SchemaFactoryTest.php b/src/JsonSchema/Tests/SchemaFactoryTest.php index da38fc6474d..d0be41e60f2 100644 --- a/src/JsonSchema/Tests/SchemaFactoryTest.php +++ b/src/JsonSchema/Tests/SchemaFactoryTest.php @@ -32,16 +32,95 @@ use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Metadata\ResourceClassResolverInterface; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\TypeInfo\Type; class SchemaFactoryTest extends TestCase { use ProphecyTrait; + #[IgnoreDeprecations] + public function testBuildSchemaForNonResourceClassLegacy(): void + { + $this->expectUserDeprecationMessage('Since api-platform/metadata 4.2: The "ApiPlatform\Metadata\ApiProperty::withBuiltinTypes()" method is deprecated, use "ApiPlatform\Metadata\ApiProperty::withNativeType()" instead.'); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(NotAResource::class, Argument::cetera())->willReturn(new PropertyNameCollection(['foo', 'bar', 'genderType'])); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryProphecy->create(NotAResource::class, 'foo', Argument::cetera())->willReturn( + (new ApiProperty()) + ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]) + ->withReadable(true) + ->withSchema(['type' => 'string']) + ); + $propertyMetadataFactoryProphecy->create(NotAResource::class, 'bar', Argument::cetera())->willReturn( + (new ApiProperty()) + ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]) + ->withReadable(true) + ->withDefault('default_bar') + ->withExample('example_bar') + ->withSchema(['type' => 'integer', 'default' => 'default_bar', 'example' => 'example_bar']) + ); + $propertyMetadataFactoryProphecy->create(NotAResource::class, 'genderType', Argument::cetera())->willReturn( + (new ApiProperty()) + ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT)]) + ->withReadable(true) + ->withDefault('male') + ->withSchema(['type' => 'object', 'default' => 'male', 'example' => 'male']) + ); + + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->isResourceClass(NotAResource::class)->willReturn(false); + + $definitionNameFactory = new DefinitionNameFactory(['jsonapi' => true, 'jsonhal' => true, 'jsonld' => true]); + + $schemaFactory = new SchemaFactory( + resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(), + propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(), + propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(), + resourceClassResolver: $resourceClassResolverProphecy->reveal(), + definitionNameFactory: $definitionNameFactory, + ); + $resultSchema = $schemaFactory->buildSchema(NotAResource::class); + + $rootDefinitionKey = $resultSchema->getRootDefinitionKey(); + $definitions = $resultSchema->getDefinitions(); + + $this->assertSame((new \ReflectionClass(NotAResource::class))->getShortName(), $rootDefinitionKey); + $this->assertTrue(isset($definitions[$rootDefinitionKey])); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]); + $this->assertSame('object', $definitions[$rootDefinitionKey]['type']); + $this->assertArrayNotHasKey('additionalProperties', $definitions[$rootDefinitionKey]); + $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]); + $this->assertArrayHasKey('foo', $definitions[$rootDefinitionKey]['properties']); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['foo']); + $this->assertArrayNotHasKey('default', $definitions[$rootDefinitionKey]['properties']['foo']); + $this->assertArrayNotHasKey('example', $definitions[$rootDefinitionKey]['properties']['foo']); + $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['foo']['type']); + $this->assertArrayHasKey('bar', $definitions[$rootDefinitionKey]['properties']); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['bar']); + $this->assertArrayHasKey('default', $definitions[$rootDefinitionKey]['properties']['bar']); + $this->assertArrayHasKey('example', $definitions[$rootDefinitionKey]['properties']['bar']); + $this->assertSame('integer', $definitions[$rootDefinitionKey]['properties']['bar']['type']); + $this->assertSame('default_bar', $definitions[$rootDefinitionKey]['properties']['bar']['default']); + $this->assertSame('example_bar', $definitions[$rootDefinitionKey]['properties']['bar']['example']); + + $this->assertArrayHasKey('genderType', $definitions[$rootDefinitionKey]['properties']); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['genderType']); + $this->assertArrayHasKey('default', $definitions[$rootDefinitionKey]['properties']['genderType']); + $this->assertArrayHasKey('example', $definitions[$rootDefinitionKey]['properties']['genderType']); + $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['genderType']['type']); + $this->assertSame('male', $definitions[$rootDefinitionKey]['properties']['genderType']['default']); + $this->assertSame('male', $definitions[$rootDefinitionKey]['properties']['genderType']['example']); + } + public function testBuildSchemaForNonResourceClass(): void { $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); @@ -52,13 +131,13 @@ public function testBuildSchemaForNonResourceClass(): void $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $propertyMetadataFactoryProphecy->create(NotAResource::class, 'foo', Argument::cetera())->willReturn( (new ApiProperty()) - ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]) + ->withNativeType(Type::string()) ->withReadable(true) ->withSchema(['type' => 'string']) ); $propertyMetadataFactoryProphecy->create(NotAResource::class, 'bar', Argument::cetera())->willReturn( (new ApiProperty()) - ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]) + ->withNativeType(Type::int()) ->withReadable(true) ->withDefault('default_bar') ->withExample('example_bar') @@ -66,7 +145,7 @@ public function testBuildSchemaForNonResourceClass(): void ); $propertyMetadataFactoryProphecy->create(NotAResource::class, 'genderType', Argument::cetera())->willReturn( (new ApiProperty()) - ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT)]) + ->withNativeType(Type::object()) ->withReadable(true) ->withDefault('male') ->withSchema(['type' => 'object', 'default' => 'male', 'example' => 'male']) @@ -117,6 +196,78 @@ public function testBuildSchemaForNonResourceClass(): void $this->assertSame('male', $definitions[$rootDefinitionKey]['properties']['genderType']['example']); } + #[IgnoreDeprecations] + public function testBuildSchemaForNonResourceClassWithUnionIntersectTypesLegacy(): void + { + $this->expectUserDeprecationMessage('Since api-platform/metadata 4.2: The "ApiPlatform\Metadata\ApiProperty::withBuiltinTypes()" method is deprecated, use "ApiPlatform\Metadata\ApiProperty::withNativeType()" instead.'); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, Argument::cetera())->willReturn(new PropertyNameCollection(['ignoredProperty', 'unionType', 'intersectType'])); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'ignoredProperty', Argument::cetera())->willReturn( + (new ApiProperty()) + ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING, nullable: true)]) + ->withReadable(true) + ->withSchema(['type' => ['string', 'null']]) + ); + $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'unionType', Argument::cetera())->willReturn( + (new ApiProperty()) + ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING, nullable: true), new LegacyType(LegacyType::BUILTIN_TYPE_INT, nullable: true), new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT, nullable: true)]) + ->withReadable(true) + ->withSchema(['oneOf' => [ + ['type' => ['string', 'null']], + ['type' => ['integer', 'null']], + ]]) + ); + $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'intersectType', Argument::cetera())->willReturn( + (new ApiProperty()) + ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, class: Serializable::class), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, class: DummyResourceInterface::class)]) + ->withReadable(true) + ->withSchema(['type' => 'object']) + ); + + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->isResourceClass(NotAResourceWithUnionIntersectTypes::class)->willReturn(false); + + $definitionNameFactory = new DefinitionNameFactory(['jsonapi' => true, 'jsonhal' => true, 'jsonld' => true]); + + $schemaFactory = new SchemaFactory( + resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(), + propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(), + propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(), + resourceClassResolver: $resourceClassResolverProphecy->reveal(), + definitionNameFactory: $definitionNameFactory, + ); + $resultSchema = $schemaFactory->buildSchema(NotAResourceWithUnionIntersectTypes::class); + + $rootDefinitionKey = $resultSchema->getRootDefinitionKey(); + $definitions = $resultSchema->getDefinitions(); + + $this->assertSame((new \ReflectionClass(NotAResourceWithUnionIntersectTypes::class))->getShortName(), $rootDefinitionKey); + $this->assertTrue(isset($definitions[$rootDefinitionKey])); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]); + $this->assertSame('object', $definitions[$rootDefinitionKey]['type']); + $this->assertArrayNotHasKey('additionalProperties', $definitions[$rootDefinitionKey]); + $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]); + + $this->assertArrayHasKey('ignoredProperty', $definitions[$rootDefinitionKey]['properties']); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['ignoredProperty']); + $this->assertSame(['string', 'null'], $definitions[$rootDefinitionKey]['properties']['ignoredProperty']['type']); + + $this->assertArrayHasKey('unionType', $definitions[$rootDefinitionKey]['properties']); + $this->assertArrayHasKey('oneOf', $definitions[$rootDefinitionKey]['properties']['unionType']); + $this->assertCount(2, $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf']); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf'][0]); + $this->assertSame(['string', 'null'], $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf'][0]['type']); + $this->assertSame(['integer', 'null'], $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf'][1]['type']); + + $this->assertArrayHasKey('intersectType', $definitions[$rootDefinitionKey]['properties']); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['intersectType']); + $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['intersectType']['type']); + } + public function testBuildSchemaForNonResourceClassWithUnionIntersectTypes(): void { $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); @@ -127,13 +278,13 @@ public function testBuildSchemaForNonResourceClassWithUnionIntersectTypes(): voi $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'ignoredProperty', Argument::cetera())->willReturn( (new ApiProperty()) - ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING, nullable: true)]) + ->withNativeType(Type::nullable(Type::string())) // @phpstan-ignore-line ->withReadable(true) ->withSchema(['type' => ['string', 'null']]) ); $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'unionType', Argument::cetera())->willReturn( (new ApiProperty()) - ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING, nullable: true), new Type(Type::BUILTIN_TYPE_INT, nullable: true), new Type(Type::BUILTIN_TYPE_FLOAT, nullable: true)]) + ->withNativeType(Type::union(Type::string(), Type::int(), Type::float(), Type::null())) ->withReadable(true) ->withSchema(['oneOf' => [ ['type' => ['string', 'null']], @@ -142,7 +293,7 @@ public function testBuildSchemaForNonResourceClassWithUnionIntersectTypes(): voi ); $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'intersectType', Argument::cetera())->willReturn( (new ApiProperty()) - ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, class: Serializable::class), new Type(Type::BUILTIN_TYPE_OBJECT, class: DummyResourceInterface::class)]) + ->withNativeType(Type::intersection(Type::object(Serializable::class), Type::object(DummyResourceInterface::class))) ->withReadable(true) ->withSchema(['type' => 'object']) ); @@ -187,8 +338,10 @@ public function testBuildSchemaForNonResourceClassWithUnionIntersectTypes(): voi $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['intersectType']['type']); } - public function testBuildSchemaWithSerializerGroups(): void + #[IgnoreDeprecations] + public function testBuildSchemaWithSerializerGroupsLegacy(): void { + $this->expectUserDeprecationMessage('Since api-platform/metadata 4.2: The "ApiPlatform\Metadata\ApiProperty::withBuiltinTypes()" method is deprecated, use "ApiPlatform\Metadata\ApiProperty::withNativeType()" instead.'); $shortName = (new \ReflectionClass(OverriddenOperationDummy::class))->getShortName(); $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $operation = (new Put())->withName('put')->withNormalizationContext([ @@ -210,19 +363,19 @@ public function testBuildSchemaWithSerializerGroups(): void $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'alias', Argument::type('array'))->willReturn( (new ApiProperty()) - ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]) + ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]) ->withReadable(true) ->withSchema(['type' => 'string']) ); $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'description', Argument::type('array'))->willReturn( (new ApiProperty()) - ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]) + ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]) ->withReadable(true) ->withSchema(['type' => 'string']) ); $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'genderType', Argument::type('array'))->willReturn( (new ApiProperty()) - ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, GenderTypeEnum::class)]) + ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, GenderTypeEnum::class)]) ->withReadable(true) ->withDefault(GenderTypeEnum::MALE) ->withSchema(['type' => 'object']) @@ -265,6 +418,137 @@ public function testBuildSchemaWithSerializerGroups(): void $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['genderType']['type']); } + public function testBuildSchemaWithSerializerGroups(): void + { + $shortName = (new \ReflectionClass(OverriddenOperationDummy::class))->getShortName(); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + $operation = (new Put())->withName('put')->withNormalizationContext([ + 'groups' => 'overridden_operation_dummy_put', + AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false, + ])->withShortName($shortName)->withValidationContext(['groups' => ['validation_groups_dummy_put']]); + $resourceMetadataFactoryProphecy->create(OverriddenOperationDummy::class) + ->willReturn( + new ResourceMetadataCollection(OverriddenOperationDummy::class, [ + (new ApiResource())->withOperations(new Operations(['put' => $operation])), + ]) + ); + + $serializerGroup = 'custom_operation_dummy'; + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(OverriddenOperationDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['alias', 'description', 'genderType'])); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'alias', Argument::type('array'))->willReturn( + (new ApiProperty()) + ->withNativeType(Type::string()) + ->withReadable(true) + ->withSchema(['type' => 'string']) + ); + $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'description', Argument::type('array'))->willReturn( + (new ApiProperty()) + ->withNativeType(Type::string()) + ->withReadable(true) + ->withSchema(['type' => 'string']) + ); + $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'genderType', Argument::type('array'))->willReturn( + (new ApiProperty()) + ->withNativeType(Type::enum(GenderTypeEnum::class)) + ->withReadable(true) + ->withDefault(GenderTypeEnum::MALE) + ->withSchema(['type' => 'object']) + ); + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->isResourceClass(OverriddenOperationDummy::class)->willReturn(true); + $resourceClassResolverProphecy->isResourceClass(GenderTypeEnum::class)->willReturn(true); + + $definitionNameFactory = new DefinitionNameFactory(['jsonapi' => true, 'jsonhal' => true, 'jsonld' => true]); + + $schemaFactory = new SchemaFactory( + resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(), + propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(), + propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(), + resourceClassResolver: $resourceClassResolverProphecy->reveal(), + definitionNameFactory: $definitionNameFactory, + ); + $resultSchema = $schemaFactory->buildSchema(OverriddenOperationDummy::class, 'json', Schema::TYPE_OUTPUT, null, null, ['groups' => $serializerGroup, AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false]); + + $rootDefinitionKey = $resultSchema->getRootDefinitionKey(); + $definitions = $resultSchema->getDefinitions(); + + $this->assertSame((new \ReflectionClass(OverriddenOperationDummy::class))->getShortName().'-'.$serializerGroup, $rootDefinitionKey); + $this->assertTrue(isset($definitions[$rootDefinitionKey])); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]); + $this->assertSame('object', $definitions[$rootDefinitionKey]['type']); + $this->assertFalse($definitions[$rootDefinitionKey]['additionalProperties']); + $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]); + $this->assertArrayHasKey('alias', $definitions[$rootDefinitionKey]['properties']); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['alias']); + $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['alias']['type']); + $this->assertArrayHasKey('description', $definitions[$rootDefinitionKey]['properties']); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['description']); + $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['description']['type']); + $this->assertArrayHasKey('genderType', $definitions[$rootDefinitionKey]['properties']); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['genderType']); + $this->assertArrayNotHasKey('default', $definitions[$rootDefinitionKey]['properties']['genderType']); + $this->assertArrayNotHasKey('example', $definitions[$rootDefinitionKey]['properties']['genderType']); + $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['genderType']['type']); + } + + #[IgnoreDeprecations] + public function testBuildSchemaForAssociativeArrayLegacy(): void + { + $this->expectUserDeprecationMessage('Since api-platform/metadata 4.2: The "ApiPlatform\Metadata\ApiProperty::withBuiltinTypes()" method is deprecated, use "ApiPlatform\Metadata\ApiProperty::withNativeType()" instead.'); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(NotAResource::class, Argument::cetera())->willReturn(new PropertyNameCollection(['foo', 'bar'])); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryProphecy->create(NotAResource::class, 'foo', Argument::cetera())->willReturn( + (new ApiProperty()) + ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]) + ->withReadable(true) + ->withSchema(['type' => 'array', 'items' => ['string', 'int']]) + ); + $propertyMetadataFactoryProphecy->create(NotAResource::class, 'bar', Argument::cetera())->willReturn( + (new ApiProperty()) + ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_STRING), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))]) + ->withReadable(true) + ->withSchema(['type' => 'object', 'additionalProperties' => 'string']) + ); + + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->isResourceClass(NotAResource::class)->willReturn(false); + + $definitionNameFactory = new DefinitionNameFactory(['jsonapi' => true, 'jsonhal' => true, 'jsonld' => true]); + + $schemaFactory = new SchemaFactory( + resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(), + propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(), + propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(), + resourceClassResolver: $resourceClassResolverProphecy->reveal(), + definitionNameFactory: $definitionNameFactory, + ); + $resultSchema = $schemaFactory->buildSchema(NotAResource::class); + + $rootDefinitionKey = $resultSchema->getRootDefinitionKey(); + $definitions = $resultSchema->getDefinitions(); + + $this->assertSame((new \ReflectionClass(NotAResource::class))->getShortName(), $rootDefinitionKey); + $this->assertTrue(isset($definitions[$rootDefinitionKey])); + $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]); + $this->assertArrayHasKey('foo', $definitions[$rootDefinitionKey]['properties']); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['foo']); + $this->assertArrayNotHasKey('additionalProperties', $definitions[$rootDefinitionKey]['properties']['foo']); + $this->assertSame('array', $definitions[$rootDefinitionKey]['properties']['foo']['type']); + $this->assertArrayHasKey('bar', $definitions[$rootDefinitionKey]['properties']); + $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['bar']); + $this->assertArrayHasKey('additionalProperties', $definitions[$rootDefinitionKey]['properties']['bar']); + $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['bar']['type']); + $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['bar']['additionalProperties']); + } + public function testBuildSchemaForAssociativeArray(): void { $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); @@ -275,13 +559,13 @@ public function testBuildSchemaForAssociativeArray(): void $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $propertyMetadataFactoryProphecy->create(NotAResource::class, 'foo', Argument::cetera())->willReturn( (new ApiProperty()) - ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))]) + ->withNativeType(Type::list(Type::string())) ->withReadable(true) ->withSchema(['type' => 'array', 'items' => ['string', 'int']]) ); $propertyMetadataFactoryProphecy->create(NotAResource::class, 'bar', Argument::cetera())->willReturn( (new ApiProperty()) - ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_STRING))]) + ->withNativeType(Type::dict(Type::string())) ->withReadable(true) ->withSchema(['type' => 'object', 'additionalProperties' => 'string']) ); diff --git a/src/JsonSchema/composer.json b/src/JsonSchema/composer.json index e92321d03c4..b8e80483e5c 100644 --- a/src/JsonSchema/composer.json +++ b/src/JsonSchema/composer.json @@ -29,7 +29,7 @@ "symfony/console": "^6.4 || ^7.0", "symfony/property-info": "^6.4 || ^7.1", "symfony/serializer": "^6.4 || ^7.0", - "symfony/type-info": "^7.3-dev", + "symfony/type-info": "v7.3.0-BETA1", "symfony/uid": "^6.4 || ^7.0" }, "require-dev": { @@ -71,10 +71,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/Laravel/composer.json b/src/Laravel/composer.json index 5207eaebebb..982597033ab 100644 --- a/src/Laravel/composer.json +++ b/src/Laravel/composer.json @@ -47,7 +47,7 @@ "illuminate/routing": "^11.0 || ^12.0", "illuminate/support": "^11.0 || ^12.0", "illuminate/container": "^11.0 || ^12.0", - "symfony/type-info": "^7.3-dev", + "symfony/type-info": "^7.2 || v7.3.0-BETA1", "symfony/web-link": "^6.4 || ^7.1", "willdurand/negotiation": "^3.1", "phpstan/phpdoc-parser": "^1.29 || ^2.0", @@ -122,10 +122,6 @@ ] }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/Metadata/ApiProperty.php b/src/Metadata/ApiProperty.php index a2c90b872b5..d9bf25c80f2 100644 --- a/src/Metadata/ApiProperty.php +++ b/src/Metadata/ApiProperty.php @@ -234,7 +234,7 @@ public function __construct( $this->nativeType = $nativeType; if ($this->builtinTypes) { - trigger_deprecation('api_platform/metadata', '4.2', 'The "builtinTypes" argument of "%s()" is deprecated, use "nativeType" instead.'); + trigger_deprecation('api_platform/metadata', '4.2', \sprintf('The "builtinTypes" argument of "%s" is deprecated, use "nativeType" instead.', __CLASS__)); $this->nativeType ??= PropertyInfoToTypeInfoHelper::convertLegacyTypesToType($this->builtinTypes); } elseif ($this->nativeType) { $this->builtinTypes = PropertyInfoToTypeInfoHelper::convertTypeToLegacyTypes($this->nativeType) ?? []; diff --git a/src/Metadata/composer.json b/src/Metadata/composer.json index 2e92c639dd5..fbbb63553f7 100644 --- a/src/Metadata/composer.json +++ b/src/Metadata/composer.json @@ -33,7 +33,7 @@ "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/property-info": "^6.4 || ^7.1", "symfony/string": "^6.4 || ^7.0", - "symfony/type-info": "^7.3-dev" + "symfony/type-info": "v7.3.0-BETA1" }, "require-dev": { "api-platform/json-schema": "^4.1", @@ -88,10 +88,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/OpenApi/Command/OpenApiCommand.php b/src/OpenApi/Command/OpenApiCommand.php index 164a5afaff7..61368197ddb 100644 --- a/src/OpenApi/Command/OpenApiCommand.php +++ b/src/OpenApi/Command/OpenApiCommand.php @@ -14,6 +14,7 @@ namespace ApiPlatform\OpenApi\Command; use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -26,6 +27,7 @@ /** * Dumps Open API documentation. */ +#[AsCommand(name: 'api:openapi:export')] final class OpenApiCommand extends Command { public function __construct(private readonly OpenApiFactoryInterface $openApiFactory, private readonly NormalizerInterface $normalizer) @@ -82,9 +84,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int return \defined(Command::class.'::SUCCESS') ? Command::SUCCESS : 0; } - - public static function getDefaultName(): string - { - return 'api:openapi:export'; - } } diff --git a/src/OpenApi/composer.json b/src/OpenApi/composer.json index 9822a42d4a3..2c99cf73027 100644 --- a/src/OpenApi/composer.json +++ b/src/OpenApi/composer.json @@ -35,7 +35,7 @@ "symfony/filesystem": "^6.4 || ^7.0", "symfony/property-access": "^6.4 || ^7.0", "symfony/serializer": "^6.4 || ^7.0", - "symfony/type-info": "^7.2" + "symfony/type-info": "v7.3.0-BETA1" }, "require-dev": { "phpspec/prophecy-phpunit": "^2.2", @@ -80,10 +80,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/RamseyUuid/composer.json b/src/RamseyUuid/composer.json index 66a4fc910c4..ceec17c71a4 100644 --- a/src/RamseyUuid/composer.json +++ b/src/RamseyUuid/composer.json @@ -31,7 +31,7 @@ "ramsey/uuid": "^4.7", "ramsey/uuid-doctrine": "^2.0", "phpunit/phpunit": "11.5.x-dev", - "symfony/type-info": "^7.3-dev" + "symfony/type-info": "v7.3.0-BETA1" }, "autoload": { "psr-4": { @@ -65,10 +65,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/Serializer/composer.json b/src/Serializer/composer.json index f28906e46ff..3185415e1c6 100644 --- a/src/Serializer/composer.json +++ b/src/Serializer/composer.json @@ -42,7 +42,7 @@ "symfony/mercure-bundle": "*", "symfony/var-dumper": "^6.4 || ^7.0", "symfony/yaml": "^6.4 || ^7.0", - "symfony/type-info": "^7.3-dev" + "symfony/type-info": "v7.3.0-BETA1" }, "suggest": { "api-platform/doctrine-orm": "To support Doctrine ORM state options.", @@ -83,10 +83,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/State/composer.json b/src/State/composer.json index 19acee69e90..b24339f57ab 100644 --- a/src/State/composer.json +++ b/src/State/composer.json @@ -39,7 +39,7 @@ "willdurand/negotiation": "^3.1", "api-platform/validator": "^4.1", "api-platform/serializer": "^4.1", - "symfony/type-info": "^7.3-dev" + "symfony/type-info": "v7.3.0-BETA1" }, "autoload": { "psr-4": { @@ -83,10 +83,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/Symfony/Bundle/Command/DebugResourceCommand.php b/src/Symfony/Bundle/Command/DebugResourceCommand.php index cc5b86e4541..65eb6685d40 100644 --- a/src/Symfony/Bundle/Command/DebugResourceCommand.php +++ b/src/Symfony/Bundle/Command/DebugResourceCommand.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Symfony\Bundle\Command; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputArgument; @@ -22,6 +23,7 @@ use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\VarDumper\Cloner\ClonerInterface; +#[AsCommand(name: 'debug:api-resource')] final class DebugResourceCommand extends Command { public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ClonerInterface $cloner, private $dumper) @@ -111,9 +113,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int return \defined(Command::class.'::SUCCESS') ? Command::SUCCESS : 0; } - - public static function getDefaultName(): string - { - return 'debug:api-resource'; - } } diff --git a/tests/Fixtures/DummyAtLeastOneOfValidatedEntity.php b/src/Symfony/Tests/Fixtures/DummyAtLeastOneOfValidatedEntity.php similarity index 91% rename from tests/Fixtures/DummyAtLeastOneOfValidatedEntity.php rename to src/Symfony/Tests/Fixtures/DummyAtLeastOneOfValidatedEntity.php index 271787e5bea..55a73bc5e62 100644 --- a/tests/Fixtures/DummyAtLeastOneOfValidatedEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyAtLeastOneOfValidatedEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; diff --git a/tests/Fixtures/DummyCollectionValidatedEntity.php b/src/Symfony/Tests/Fixtures/DummyCollectionValidatedEntity.php similarity index 96% rename from tests/Fixtures/DummyCollectionValidatedEntity.php rename to src/Symfony/Tests/Fixtures/DummyCollectionValidatedEntity.php index 92e754c469c..621269c7334 100644 --- a/tests/Fixtures/DummyCollectionValidatedEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyCollectionValidatedEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; diff --git a/tests/Fixtures/DummyCompoundValidatedEntity.php b/src/Symfony/Tests/Fixtures/DummyCompoundValidatedEntity.php similarity index 73% rename from tests/Fixtures/DummyCompoundValidatedEntity.php rename to src/Symfony/Tests/Fixtures/DummyCompoundValidatedEntity.php index f8d9024d80b..1b37ae6d24b 100644 --- a/tests/Fixtures/DummyCompoundValidatedEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyCompoundValidatedEntity.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; -use ApiPlatform\Tests\Fixtures\TestBundle\Validator\Constraint\DummyCompoundRequirements; +use ApiPlatform\Symfony\Tests\Fixtures\TestBundle\Validator\Constraint\DummyCompoundRequirements; class DummyCompoundValidatedEntity { diff --git a/tests/Fixtures/DummyCountValidatedEntity.php b/src/Symfony/Tests/Fixtures/DummyCountValidatedEntity.php similarity index 93% rename from tests/Fixtures/DummyCountValidatedEntity.php rename to src/Symfony/Tests/Fixtures/DummyCountValidatedEntity.php index 47d0b84df2b..a6b5cec84c6 100644 --- a/tests/Fixtures/DummyCountValidatedEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyCountValidatedEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; diff --git a/tests/Fixtures/DummyIriWithValidationEntity.php b/src/Symfony/Tests/Fixtures/DummyIriWithValidationEntity.php similarity index 93% rename from tests/Fixtures/DummyIriWithValidationEntity.php rename to src/Symfony/Tests/Fixtures/DummyIriWithValidationEntity.php index eb2173b5426..5843c411c38 100644 --- a/tests/Fixtures/DummyIriWithValidationEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyIriWithValidationEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; @@ -22,7 +22,7 @@ */ class DummyIriWithValidationEntity { - #[Assert\Url] + #[Assert\Url(requireTld: true)] public $dummyUrl; #[Assert\Email] diff --git a/tests/Fixtures/DummyNumericValidatedEntity.php b/src/Symfony/Tests/Fixtures/DummyNumericValidatedEntity.php similarity index 96% rename from tests/Fixtures/DummyNumericValidatedEntity.php rename to src/Symfony/Tests/Fixtures/DummyNumericValidatedEntity.php index b98e7c157f0..a0be6697a83 100644 --- a/tests/Fixtures/DummyNumericValidatedEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyNumericValidatedEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; diff --git a/tests/Fixtures/DummyRangeValidatedEntity.php b/src/Symfony/Tests/Fixtures/DummyRangeValidatedEntity.php similarity index 95% rename from tests/Fixtures/DummyRangeValidatedEntity.php rename to src/Symfony/Tests/Fixtures/DummyRangeValidatedEntity.php index ea18b5e6758..83dff25e49f 100644 --- a/tests/Fixtures/DummyRangeValidatedEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyRangeValidatedEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; diff --git a/tests/Fixtures/DummySequentiallyValidatedEntity.php b/src/Symfony/Tests/Fixtures/DummySequentiallyValidatedEntity.php similarity index 92% rename from tests/Fixtures/DummySequentiallyValidatedEntity.php rename to src/Symfony/Tests/Fixtures/DummySequentiallyValidatedEntity.php index 1bae1861e77..a9bfc094647 100644 --- a/tests/Fixtures/DummySequentiallyValidatedEntity.php +++ b/src/Symfony/Tests/Fixtures/DummySequentiallyValidatedEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; diff --git a/tests/Fixtures/DummyUniqueValidatedEntity.php b/src/Symfony/Tests/Fixtures/DummyUniqueValidatedEntity.php similarity index 90% rename from tests/Fixtures/DummyUniqueValidatedEntity.php rename to src/Symfony/Tests/Fixtures/DummyUniqueValidatedEntity.php index a96ba6d01de..a70633c12cb 100644 --- a/tests/Fixtures/DummyUniqueValidatedEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyUniqueValidatedEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; diff --git a/tests/Fixtures/DummyValidatedChoiceEntity.php b/src/Symfony/Tests/Fixtures/DummyValidatedChoiceEntity.php similarity index 96% rename from tests/Fixtures/DummyValidatedChoiceEntity.php rename to src/Symfony/Tests/Fixtures/DummyValidatedChoiceEntity.php index 4229a760166..38d7250a4ce 100644 --- a/tests/Fixtures/DummyValidatedChoiceEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyValidatedChoiceEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; diff --git a/tests/Fixtures/DummyValidatedEntity.php b/src/Symfony/Tests/Fixtures/DummyValidatedEntity.php similarity index 94% rename from tests/Fixtures/DummyValidatedEntity.php rename to src/Symfony/Tests/Fixtures/DummyValidatedEntity.php index fd4fdc3276f..56dfb67ea53 100644 --- a/tests/Fixtures/DummyValidatedEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyValidatedEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; @@ -75,7 +75,7 @@ class DummyValidatedEntity /** * @var string A dummy url */ - #[Assert\Url] + #[Assert\Url(requireTld: true)] public $dummyUrl; /** diff --git a/tests/Fixtures/DummyValidatedHostnameEntity.php b/src/Symfony/Tests/Fixtures/DummyValidatedHostnameEntity.php similarity index 90% rename from tests/Fixtures/DummyValidatedHostnameEntity.php rename to src/Symfony/Tests/Fixtures/DummyValidatedHostnameEntity.php index febac7e5426..0605717093a 100644 --- a/tests/Fixtures/DummyValidatedHostnameEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyValidatedHostnameEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; diff --git a/tests/Fixtures/DummyValidatedUlidEntity.php b/src/Symfony/Tests/Fixtures/DummyValidatedUlidEntity.php similarity index 90% rename from tests/Fixtures/DummyValidatedUlidEntity.php rename to src/Symfony/Tests/Fixtures/DummyValidatedUlidEntity.php index 5f19f6d628e..34ac4da2ffd 100644 --- a/tests/Fixtures/DummyValidatedUlidEntity.php +++ b/src/Symfony/Tests/Fixtures/DummyValidatedUlidEntity.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures; +namespace ApiPlatform\Symfony\Tests\Fixtures; use Symfony\Component\Validator\Constraints as Assert; diff --git a/tests/Fixtures/TestBundle/Validator/Constraint/DummyCompoundRequirements.php b/src/Symfony/Tests/Fixtures/TestBundle/Validator/Constraint/DummyCompoundRequirements.php similarity index 89% rename from tests/Fixtures/TestBundle/Validator/Constraint/DummyCompoundRequirements.php rename to src/Symfony/Tests/Fixtures/TestBundle/Validator/Constraint/DummyCompoundRequirements.php index 25881bac3e8..b2c0dabe68e 100644 --- a/tests/Fixtures/TestBundle/Validator/Constraint/DummyCompoundRequirements.php +++ b/src/Symfony/Tests/Fixtures/TestBundle/Validator/Constraint/DummyCompoundRequirements.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Fixtures\TestBundle\Validator\Constraint; +namespace ApiPlatform\Symfony\Tests\Fixtures\TestBundle\Validator\Constraint; use Symfony\Component\Validator\Constraints\Compound; use Symfony\Component\Validator\Constraints\Length; diff --git a/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestrictionTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestrictionTest.php new file mode 100644 index 00000000000..55bac08181f --- /dev/null +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestrictionTest.php @@ -0,0 +1,185 @@ + + * + * 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\Symfony\Tests\Validator\Metadata\Property\Restriction; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaChoiceRestriction; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Choice; +use Symfony\Component\Validator\Constraints\Positive; + +/** + * @author Tomas Norkūnas + */ +final class PropertySchemaChoiceRestrictionTest extends TestCase +{ + use ProphecyTrait; + + private PropertySchemaChoiceRestriction $propertySchemaChoiceRestriction; + + protected function setUp(): void + { + $this->propertySchemaChoiceRestriction = new PropertySchemaChoiceRestriction(); + } + + #[IgnoreDeprecations] + public function testSupports(): void + { + foreach ($this->supportsProvider() as [$constraint, $propertyMetadata, $expectedResult]) { + self::assertSame($expectedResult, $this->propertySchemaChoiceRestriction->supports($constraint, $propertyMetadata)); + } + } + + public static function supportsProvider(): \Generator + { + yield 'supported string' => [new Choice(['choices' => ['a', 'b']]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), true]; + yield 'supported int' => [new Choice(['choices' => [1, 2]]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), true]; + yield 'supported float' => [new Choice(['choices' => [1.1, 2.2]]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), true]; + yield 'supported string/int/float with union types' => [new Choice(['choices' => [1, 2, 1.1, 2.2, 'a', 'b']]), (new ApiProperty())->withBuiltinTypes([ + new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + ]), true]; + + yield 'not supported constraint' => [new Positive(), new ApiProperty(), false]; + yield 'not supported type' => [new Choice(['choices' => [new \stdClass(), new \stdClass()]]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT)]), false]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('supportsNativeProvider')] + public function testSupportsNative(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaChoiceRestriction->supports($constraint, $propertyMetadata)); + } + + public static function supportsNativeProvider(): \Generator + { + yield 'supported string' => [new Choice(['choices' => ['a', 'b']]), (new ApiProperty())->withNativeType(Type::string()), true]; + yield 'supported int' => [new Choice(['choices' => [1, 2]]), (new ApiProperty())->withNativeType(Type::int()), true]; + yield 'supported float' => [new Choice(['choices' => [1.1, 2.2]]), (new ApiProperty())->withNativeType(Type::float()), true]; + yield 'supported string/int/float with union types' => [new Choice(['choices' => [1, 2, 1.1, 2.2, 'a', 'b']]), (new ApiProperty())->withNativeType( + Type::union(Type::float(), Type::int(), Type::string()), + ), true]; + + yield 'not supported constraint' => [new Positive(), new ApiProperty(), false]; + yield 'not supported type' => [new Choice(['choices' => [new \stdClass(), new \stdClass()]]), (new ApiProperty())->withNativeType(Type::object()), false]; + } + + #[IgnoreDeprecations] + public function testCreate(): void + { + foreach ($this->createProvider() as [$constraint, $propertyMetadata, $expectedResult]) { + self::assertSame($expectedResult, $this->propertySchemaChoiceRestriction->create($constraint, $propertyMetadata)); + } + } + + public static function createProvider(): \Generator + { + yield 'single string choice' => [new Choice(['choices' => ['a', 'b']]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), ['enum' => ['a', 'b']]]; + yield 'multi string choice' => [new Choice(['choices' => ['a', 'b'], 'multiple' => true]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::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 ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::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 ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::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 ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::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 ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), ['enum' => [1, 2]]]; + yield 'multi int choice' => [new Choice(['choices' => [1, 2], 'multiple' => true]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::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 ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::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 ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::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 ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::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 ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), ['enum' => [1.1, 2.2]]]; + yield 'multi float choice' => [new Choice(['choices' => [1.1, 2.2], 'multiple' => true]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::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 ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::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 ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::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 ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), ['type' => 'array', 'items' => ['type' => 'number', 'enum' => [1.1, 2.2, 3.3, 4.4]], 'minItems' => 2, 'maxItems' => 4]]; + + yield 'single string/int/float choice with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2]]), (new ApiProperty())->withBuiltinTypes([ + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), + ]), ['enum' => [1, 2, 'a', 'b', 1.1, 2.2]]]; + yield 'multi string/int/float choice with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2], 'multiple' => true]), (new ApiProperty())->withBuiltinTypes([ + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), + ]), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2]]]]; + yield 'multi string/int/float choice min with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2], 'multiple' => true, 'min' => 2]), (new ApiProperty())->withBuiltinTypes([ + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), + ]), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2]], 'minItems' => 2]]; + yield 'multi string/int/float choice max with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4], 'multiple' => true, 'max' => 4]), (new ApiProperty())->withBuiltinTypes([ + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), + ]), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4]], 'maxItems' => 4]]; + yield 'multi string/int/float choice min/max with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4], 'multiple' => true, 'min' => 2, 'max' => 4]), (new ApiProperty())->withBuiltinTypes([ + new LegacyType(LegacyType::BUILTIN_TYPE_STRING), + new LegacyType(LegacyType::BUILTIN_TYPE_INT), + new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), + ]), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4]], 'minItems' => 2, 'maxItems' => 4]]; + + yield 'single choice callback' => [new Choice(['callback' => ChoiceCallback::getChoices(...)]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), ['enum' => ['a', 'b', 'c', 'd']]]; + yield 'multi choice callback' => [new Choice(['callback' => ChoiceCallback::getChoices(...), 'multiple' => true]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']]]]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('createNativeProvider')] + public function testCreateNative(Choice $constraint, ApiProperty $propertyMetadata, array $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaChoiceRestriction->create($constraint, $propertyMetadata)); + } + + public static function createNativeProvider(): \Generator + { + yield 'single string choice' => [new Choice(['choices' => ['a', 'b']]), (new ApiProperty())->withNativeType(Type::string()), ['enum' => ['a', 'b']]]; + yield 'multi string choice' => [new Choice(['choices' => ['a', 'b'], 'multiple' => true]), (new ApiProperty())->withNativeType(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 ApiProperty())->withNativeType(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 ApiProperty())->withNativeType(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 ApiProperty())->withNativeType(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 ApiProperty())->withNativeType(Type::int()), ['enum' => [1, 2]]]; + yield 'multi int choice' => [new Choice(['choices' => [1, 2], 'multiple' => true]), (new ApiProperty())->withNativeType(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 ApiProperty())->withNativeType(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 ApiProperty())->withNativeType(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 ApiProperty())->withNativeType(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 ApiProperty())->withNativeType(Type::float()), ['enum' => [1.1, 2.2]]]; + yield 'multi float choice' => [new Choice(['choices' => [1.1, 2.2], 'multiple' => true]), (new ApiProperty())->withNativeType(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 ApiProperty())->withNativeType(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 ApiProperty())->withNativeType(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 ApiProperty())->withNativeType(Type::float()), ['type' => 'array', 'items' => ['type' => 'number', 'enum' => [1.1, 2.2, 3.3, 4.4]], 'minItems' => 2, 'maxItems' => 4]]; + + $unionType = Type::union(Type::string(), Type::int(), Type::float()); + yield 'single string/int/float choice with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2]]), (new ApiProperty())->withNativeType($unionType), ['enum' => [1, 2, 'a', 'b', 1.1, 2.2]]]; + yield 'multi string/int/float choice with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2], 'multiple' => true]), (new ApiProperty())->withNativeType($unionType), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2]]]]; + yield 'multi string/int/float choice min with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2], 'multiple' => true, 'min' => 2]), (new ApiProperty())->withNativeType($unionType), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2]], 'minItems' => 2]]; + yield 'multi string/int/float choice max with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4], 'multiple' => true, 'max' => 4]), (new ApiProperty())->withNativeType($unionType), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4]], 'maxItems' => 4]]; + yield 'multi string/int/float choice min/max with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4], 'multiple' => true, 'min' => 2, 'max' => 4]), (new ApiProperty())->withNativeType($unionType), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4]], 'minItems' => 2, 'maxItems' => 4]]; + + yield 'single choice callback' => [new Choice(['callback' => ChoiceCallback::getChoices(...)]), (new ApiProperty())->withNativeType(Type::string()), ['enum' => ['a', 'b', 'c', 'd']]]; + yield 'multi choice callback' => [new Choice(['callback' => ChoiceCallback::getChoices(...), 'multiple' => true]), (new ApiProperty())->withNativeType(Type::string()), ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']]]]; + } +} + +final class ChoiceCallback +{ + public static function getChoices(): array + { + return ['a', 'b', 'c', 'd']; + } +} diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCollectionRestrictionTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaCollectionRestrictionTest.php similarity index 97% rename from tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCollectionRestrictionTest.php rename to src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaCollectionRestrictionTest.php index 87ce6a0646c..57ad228b827 100644 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCollectionRestrictionTest.php +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaCollectionRestrictionTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Symfony\Validator\Metadata\Property\Restriction; +namespace ApiPlatform\Symfony\Tests\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaCollectionRestriction; @@ -68,7 +68,7 @@ public static function supportsProvider(): \Generator } #[\PHPUnit\Framework\Attributes\DataProvider('createProvider')] - public function testCreate(Constraint $constraint, ApiProperty $propertyMetadata, array $expectedResult): void + public function testCreate(Collection $constraint, ApiProperty $propertyMetadata, array $expectedResult): void { self::assertEquals($expectedResult, $this->propertySchemaCollectionRestriction->create($constraint, $propertyMetadata)); } diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCountRestrictionTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaCountRestrictionTest.php similarity index 92% rename from tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCountRestrictionTest.php rename to src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaCountRestrictionTest.php index b4a14957394..8dc85429910 100644 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCountRestrictionTest.php +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaCountRestrictionTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Symfony\Validator\Metadata\Property\Restriction; +namespace ApiPlatform\Symfony\Tests\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaCountRestriction; @@ -48,7 +48,7 @@ public static function supportsProvider(): \Generator } #[\PHPUnit\Framework\Attributes\DataProvider('createProvider')] - public function testCreate(Constraint $constraint, ApiProperty $propertyMetadata, array $expectedResult): void + public function testCreate(Count $constraint, ApiProperty $propertyMetadata, array $expectedResult): void { self::assertSame($expectedResult, $this->propertySchemaCountRestriction->create($constraint, $propertyMetadata)); } diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormatTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaFormatTest.php similarity index 93% rename from tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormatTest.php rename to src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaFormatTest.php index 41a078df582..177466e419d 100644 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormatTest.php +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaFormatTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Symfony\Validator\Metadata\Property\Restriction; +namespace ApiPlatform\Symfony\Tests\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaFormat; @@ -46,7 +46,7 @@ public function testSupports(Constraint $constraint, ApiProperty $propertyMetada public static function supportsProvider(): \Generator { yield 'email' => [new Email(), new ApiProperty(), true]; - yield 'url' => [new Url(), new ApiProperty(), true]; + yield 'url' => [new Url(requireTld: true), new ApiProperty(), true]; if (class_exists(Hostname::class)) { yield 'hostname' => [new Hostname(), new ApiProperty(), true]; } @@ -67,7 +67,7 @@ public function testCreate(Constraint $constraint, ApiProperty $propertyMetadata public static function createProvider(): \Generator { yield 'email' => [new Email(), new ApiProperty(), ['format' => 'email']]; - yield 'url' => [new Url(), new ApiProperty(), ['format' => 'uri']]; + yield 'url' => [new Url(requireTld: true), new ApiProperty(), ['format' => 'uri']]; if (class_exists(Hostname::class)) { yield 'hostname' => [new Hostname(), new ApiProperty(), ['format' => 'hostname']]; } diff --git a/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestrictionTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestrictionTest.php new file mode 100644 index 00000000000..ed3949672a5 --- /dev/null +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestrictionTest.php @@ -0,0 +1,89 @@ + + * + * 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\Symfony\Tests\Validator\Metadata\Property\Restriction; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaGreaterThanOrEqualRestriction; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; +use Symfony\Component\Validator\Constraints\Positive; +use Symfony\Component\Validator\Constraints\PositiveOrZero; + +/** + * @author Tomas Norkūnas + */ +final class PropertySchemaGreaterThanOrEqualRestrictionTest extends TestCase +{ + use ProphecyTrait; + + private PropertySchemaGreaterThanOrEqualRestriction $propertySchemaGreaterThanOrEqualRestriction; + + protected function setUp(): void + { + $this->propertySchemaGreaterThanOrEqualRestriction = new PropertySchemaGreaterThanOrEqualRestriction(); + } + + #[IgnoreDeprecations] + public function testSupports(): void + { + foreach ($this->supportsProvider() as [$constraint, $propertyMetadata, $expectedResult]) { + self::assertSame($expectedResult, $this->propertySchemaGreaterThanOrEqualRestriction->supports($constraint, $propertyMetadata)); + } + } + + #[DataProvider('supportsProviderWithNativeType')] + public function testSupportsWithNativeType(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaGreaterThanOrEqualRestriction->supports($constraint, $propertyMetadata)); + } + + public static function supportsProvider(): \Generator + { + yield 'supported int/float with union types' => [new GreaterThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), true]; + yield 'supported int' => [new GreaterThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), true]; + yield 'supported float' => [new GreaterThanOrEqual(['value' => 10.99]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), true]; + yield 'supported positive or zero' => [new PositiveOrZero(), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), true]; + yield 'not supported positive' => [new Positive(), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), false]; + yield 'not supported property path' => [new GreaterThanOrEqual(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), false]; + } + + public static function supportsProviderWithNativeType(): \Generator + { + yield 'native type: supported int/float with union types' => [new GreaterThanOrEqual(['value' => 10]), (new ApiProperty())->withNativeType(Type::union(Type::int(), Type::float())), true]; + yield 'native type: supported int' => [new GreaterThanOrEqual(['value' => 10]), (new ApiProperty())->withNativeType(Type::int()), true]; + yield 'native type: supported float' => [new GreaterThanOrEqual(['value' => 10.99]), (new ApiProperty())->withNativeType(Type::float()), true]; + yield 'native type: supported positive or zero' => [new PositiveOrZero(), (new ApiProperty())->withNativeType(Type::int()), true]; + yield 'native type: not supported positive' => [new Positive(), (new ApiProperty())->withNativeType(Type::int()), false]; + yield 'native type: not supported property path' => [new GreaterThanOrEqual(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withNativeType(Type::int()), false]; + } + + #[IgnoreDeprecations] + public function testCreate(): void + { + self::assertEquals(['minimum' => 10], $this->propertySchemaGreaterThanOrEqualRestriction->create(new GreaterThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]))); + } + + public function testCreateWithNativeType(): void + { + self::assertEquals(['minimum' => 10], $this->propertySchemaGreaterThanOrEqualRestriction->create(new GreaterThanOrEqual(['value' => 10]), (new ApiProperty())->withNativeType(Type::int()))); + self::assertEquals(['minimum' => 0], $this->propertySchemaGreaterThanOrEqualRestriction->create(new PositiveOrZero(), (new ApiProperty())->withNativeType(Type::int()))); + self::assertEquals(['minimum' => 10.99], $this->propertySchemaGreaterThanOrEqualRestriction->create(new GreaterThanOrEqual(['value' => 10.99]), (new ApiProperty())->withNativeType(Type::float()))); + } +} diff --git a/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestrictionTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestrictionTest.php new file mode 100644 index 00000000000..6a83464feab --- /dev/null +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestrictionTest.php @@ -0,0 +1,99 @@ + + * + * 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\Symfony\Tests\Validator\Metadata\Property\Restriction; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaGreaterThanRestriction; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\GreaterThan; +use Symfony\Component\Validator\Constraints\Positive; +use Symfony\Component\Validator\Constraints\PositiveOrZero; + +/** + * @author Tomas Norkūnas + */ +final class PropertySchemaGreaterThanRestrictionTest extends TestCase +{ + use ProphecyTrait; + + private PropertySchemaGreaterThanRestriction $propertySchemaGreaterThanRestriction; + + protected function setUp(): void + { + $this->propertySchemaGreaterThanRestriction = new PropertySchemaGreaterThanRestriction(); + } + + #[IgnoreDeprecations] + public function testSupports(): void + { + foreach ($this->supportsProvider() as [$constraint, $propertyMetadata, $expectedResult]) { + self::assertSame($expectedResult, $this->propertySchemaGreaterThanRestriction->supports($constraint, $propertyMetadata)); + } + } + + #[DataProvider('supportsProviderWithNativeType')] + public function testSupportsWithNativeType(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaGreaterThanRestriction->supports($constraint, $propertyMetadata)); + } + + public static function supportsProvider(): \Generator + { + yield 'supported int/float with union types' => [new GreaterThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), true]; + yield 'supported int' => [new GreaterThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), true]; + yield 'supported float' => [new GreaterThan(['value' => 10.99]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), true]; + yield 'supported positive' => [new Positive(), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), true]; + yield 'not supported positive or zero' => [new PositiveOrZero(), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), false]; + yield 'not supported property path' => [new GreaterThan(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), false]; + } + + public static function supportsProviderWithNativeType(): \Generator + { + yield 'native type: supported int/float with union types' => [new GreaterThan(['value' => 10]), (new ApiProperty())->withNativeType(Type::union(Type::int(), Type::float())), true]; + yield 'native type: supported int' => [new GreaterThan(['value' => 10]), (new ApiProperty())->withNativeType(Type::int()), true]; + yield 'native type: supported float' => [new GreaterThan(['value' => 10.99]), (new ApiProperty())->withNativeType(Type::float()), true]; + yield 'native type: supported positive' => [new Positive(), (new ApiProperty())->withNativeType(Type::int()), true]; + yield 'native type: not supported positive or zero' => [new PositiveOrZero(), (new ApiProperty())->withNativeType(Type::int()), false]; + yield 'native type: not supported property path' => [new GreaterThan(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withNativeType(Type::int()), false]; + } + + #[IgnoreDeprecations] + public function testCreate(): void + { + self::assertEquals([ + 'exclusiveMinimum' => 10, + ], $this->propertySchemaGreaterThanRestriction->create(new GreaterThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]))); + } + + public function testCreateWithNativeType(): void + { + self::assertEquals([ + 'exclusiveMinimum' => 10, + ], $this->propertySchemaGreaterThanRestriction->create(new GreaterThan(['value' => 10]), (new ApiProperty())->withNativeType(Type::int()))); + + self::assertEquals([ + 'exclusiveMinimum' => 0, + ], $this->propertySchemaGreaterThanRestriction->create(new Positive(), (new ApiProperty())->withNativeType(Type::int()))); + + self::assertEquals([ + 'exclusiveMinimum' => 10.99, + ], $this->propertySchemaGreaterThanRestriction->create(new GreaterThan(['value' => 10.99]), (new ApiProperty())->withNativeType(Type::float()))); + } +} diff --git a/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestrictionTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestrictionTest.php new file mode 100644 index 00000000000..09b5ef5bd2b --- /dev/null +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestrictionTest.php @@ -0,0 +1,89 @@ + + * + * 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\Symfony\Tests\Validator\Metadata\Property\Restriction; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaLessThanOrEqualRestriction; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\LessThanOrEqual; +use Symfony\Component\Validator\Constraints\Negative; +use Symfony\Component\Validator\Constraints\NegativeOrZero; + +/** + * @author Tomas Norkūnas + */ +final class PropertySchemaLessThanOrEqualRestrictionTest extends TestCase +{ + use ProphecyTrait; + + private PropertySchemaLessThanOrEqualRestriction $propertySchemaLessThanOrEqualRestriction; + + protected function setUp(): void + { + $this->propertySchemaLessThanOrEqualRestriction = new PropertySchemaLessThanOrEqualRestriction(); + } + + #[IgnoreDeprecations] + public function testSupports(): void + { + foreach ($this->supportsProvider() as [$constraint, $propertyMetadata, $expectedResult]) { + self::assertSame($expectedResult, $this->propertySchemaLessThanOrEqualRestriction->supports($constraint, $propertyMetadata)); + } + } + + #[DataProvider('supportsProviderWithNativeType')] + public function testSupportsWithNativeType(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaLessThanOrEqualRestriction->supports($constraint, $propertyMetadata)); + } + + public static function supportsProvider(): \Generator + { + yield 'supported int/float with union types' => [new LessThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), true]; + yield 'supported int' => [new LessThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), true]; + yield 'supported float' => [new LessThanOrEqual(['value' => 10.99]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), true]; + yield 'supported negative or zero' => [new NegativeOrZero(), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), true]; + yield 'not supported negative' => [new Negative(), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), false]; + yield 'not supported property path' => [new LessThanOrEqual(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), false]; + } + + public static function supportsProviderWithNativeType(): \Generator + { + yield 'native type: supported int/float with union types' => [new LessThanOrEqual(['value' => 10]), (new ApiProperty())->withNativeType(Type::union(Type::int(), Type::float())), true]; + yield 'native type: supported int' => [new LessThanOrEqual(['value' => 10]), (new ApiProperty())->withNativeType(Type::int()), true]; + yield 'native type: supported float' => [new LessThanOrEqual(['value' => 10.99]), (new ApiProperty())->withNativeType(Type::float()), true]; + yield 'native type: supported negative or zero' => [new NegativeOrZero(), (new ApiProperty())->withNativeType(Type::int()), true]; + yield 'native type: not supported negative' => [new Negative(), (new ApiProperty())->withNativeType(Type::int()), false]; + yield 'native type: not supported property path' => [new LessThanOrEqual(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withNativeType(Type::int()), false]; + } + + #[IgnoreDeprecations] + public function testCreate(): void + { + self::assertEquals(['maximum' => 10], $this->propertySchemaLessThanOrEqualRestriction->create(new LessThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]))); + } + + public function testCreateWithNativeType(): void + { + self::assertEquals(['maximum' => 10], $this->propertySchemaLessThanOrEqualRestriction->create(new LessThanOrEqual(['value' => 10]), (new ApiProperty())->withNativeType(Type::int()))); + self::assertEquals(['maximum' => 0], $this->propertySchemaLessThanOrEqualRestriction->create(new NegativeOrZero(), (new ApiProperty())->withNativeType(Type::int()))); + self::assertEquals(['maximum' => 10.99], $this->propertySchemaLessThanOrEqualRestriction->create(new LessThanOrEqual(['value' => 10.99]), (new ApiProperty())->withNativeType(Type::float()))); + } +} diff --git a/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestrictionTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestrictionTest.php new file mode 100644 index 00000000000..b636079f869 --- /dev/null +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestrictionTest.php @@ -0,0 +1,99 @@ + + * + * 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\Symfony\Tests\Validator\Metadata\Property\Restriction; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaLessThanRestriction; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\LessThan; +use Symfony\Component\Validator\Constraints\Negative; +use Symfony\Component\Validator\Constraints\NegativeOrZero; + +/** + * @author Tomas Norkūnas + */ +final class PropertySchemaLessThanRestrictionTest extends TestCase +{ + use ProphecyTrait; + + private PropertySchemaLessThanRestriction $propertySchemaLessThanRestriction; + + protected function setUp(): void + { + $this->propertySchemaLessThanRestriction = new PropertySchemaLessThanRestriction(); + } + + #[IgnoreDeprecations] + public function testSupports(): void + { + foreach ($this->supportsProvider() as [$constraint, $propertyMetadata, $expectedResult]) { + self::assertSame($expectedResult, $this->propertySchemaLessThanRestriction->supports($constraint, $propertyMetadata)); + } + } + + #[DataProvider('supportsProviderWithNativeType')] + public function testSupportsWithNativeType(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaLessThanRestriction->supports($constraint, $propertyMetadata)); + } + + public static function supportsProvider(): \Generator + { + yield 'supported int/float with union types' => [new LessThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), true]; + yield 'supported int' => [new LessThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), true]; + yield 'supported float' => [new LessThan(['value' => 10.99]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), true]; + yield 'supported negative' => [new Negative(), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), true]; + yield 'not supported negative or zero' => [new NegativeOrZero(), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), false]; + yield 'not supported property path' => [new LessThan(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), false]; + } + + public static function supportsProviderWithNativeType(): \Generator + { + yield 'native type: supported int/float with union types' => [new LessThan(['value' => 10]), (new ApiProperty())->withNativeType(Type::union(Type::int(), Type::float())), true]; + yield 'native type: supported int' => [new LessThan(['value' => 10]), (new ApiProperty())->withNativeType(Type::int()), true]; + yield 'native type: supported float' => [new LessThan(['value' => 10.99]), (new ApiProperty())->withNativeType(Type::float()), true]; + yield 'native type: supported negative' => [new Negative(), (new ApiProperty())->withNativeType(Type::int()), true]; + yield 'native type: not supported negative or zero' => [new NegativeOrZero(), (new ApiProperty())->withNativeType(Type::int()), false]; + yield 'native type: not supported property path' => [new LessThan(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withNativeType(Type::int()), false]; + } + + #[IgnoreDeprecations] + public function testCreate(): void + { + self::assertEquals([ + 'exclusiveMaximum' => 10, + ], $this->propertySchemaLessThanRestriction->create(new LessThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]))); + } + + public function testCreateWithNativeType(): void + { + self::assertEquals([ + 'exclusiveMaximum' => 10, + ], $this->propertySchemaLessThanRestriction->create(new LessThan(['value' => 10]), (new ApiProperty())->withNativeType(Type::int()))); + + self::assertEquals([ + 'exclusiveMaximum' => 0, + ], $this->propertySchemaLessThanRestriction->create(new Negative(), (new ApiProperty())->withNativeType(Type::int()))); + + self::assertEquals([ + 'exclusiveMaximum' => 10.99, + ], $this->propertySchemaLessThanRestriction->create(new LessThan(['value' => 10.99]), (new ApiProperty())->withNativeType(Type::float()))); + } +} diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaOneOfRestrictionTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaOneOfRestrictionTest.php similarity index 50% rename from tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaOneOfRestrictionTest.php rename to src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaOneOfRestrictionTest.php index 45b65d1165d..281d3d174b1 100644 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaOneOfRestrictionTest.php +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaOneOfRestrictionTest.php @@ -11,15 +11,18 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Symfony\Validator\Metadata\Property\Restriction; +namespace ApiPlatform\Symfony\Tests\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaLengthRestriction; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaOneOfRestriction; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRegexRestriction; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\AtLeastOneOf; use Symfony\Component\Validator\Constraints\Length; @@ -42,8 +45,16 @@ protected function setUp(): void ]); } - #[\PHPUnit\Framework\Attributes\DataProvider('supportsProvider')] - public function testSupports(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void + #[IgnoreDeprecations] + public function testSupports(): void + { + foreach ($this->supportsProvider() as [$constraint, $propertyMetadata, $expectedResult]) { + self::assertSame($expectedResult, $this->propertySchemaOneOfRestriction->supports($constraint, $propertyMetadata)); + } + } + + #[DataProvider('supportsProviderWithNativeType')] + public function testSupportsWithNativeType(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void { self::assertSame($expectedResult, $this->propertySchemaOneOfRestriction->supports($constraint, $propertyMetadata)); } @@ -58,8 +69,26 @@ public static function supportsProvider(): \Generator yield 'not supported' => [new Positive(), new ApiProperty(), false]; } - #[\PHPUnit\Framework\Attributes\DataProvider('createProvider')] - public function testCreate(Constraint $constraint, ApiProperty $propertyMetadata, array $expectedResult): void + public static function supportsProviderWithNativeType(): \Generator + { + if (!class_exists(AtLeastOneOf::class)) { + return; + } + + yield 'native type: supported' => [new AtLeastOneOf(['constraints' => []]), (new ApiProperty())->withNativeType(Type::mixed()), true]; + yield 'native type: not supported' => [new Positive(), (new ApiProperty())->withNativeType(Type::mixed()), false]; + } + + #[IgnoreDeprecations] + public function testCreate(): void + { + foreach ($this->createProvider() as [$constraint, $propertyMetadata, $expectedResult]) { + self::assertSame($expectedResult, $this->propertySchemaOneOfRestriction->create($constraint, $propertyMetadata)); + } + } + + #[DataProvider('createProviderWithNativeType')] + public function testCreateWithNativeType(AtLeastOneOf $constraint, ApiProperty $propertyMetadata, array $expectedResult): void { self::assertSame($expectedResult, $this->propertySchemaOneOfRestriction->create($constraint, $propertyMetadata)); } @@ -67,10 +96,17 @@ public function testCreate(Constraint $constraint, ApiProperty $propertyMetadata public static function createProvider(): \Generator { yield 'empty' => [new AtLeastOneOf(['constraints' => []]), new ApiProperty(), []]; - yield 'not supported constraints' => [new AtLeastOneOf(['constraints' => [new Positive(), new Length(['min' => 3])]]), new ApiProperty(), []]; + yield 'one supported constraint' => [new AtLeastOneOf(['constraints' => [new Positive(), new Length(['min' => 3])]]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), [ + 'oneOf' => [['minLength' => 3]], + ]]; + } - yield 'one supported constraint' => [new AtLeastOneOf(['constraints' => [new Positive(), new Length(['min' => 3])]]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), [ + public static function createProviderWithNativeType(): \Generator + { + yield 'native type: empty' => [new AtLeastOneOf(['constraints' => []]), new ApiProperty(), []]; + yield 'native type: not supported constraints' => [new AtLeastOneOf(['constraints' => [new Positive(), new Length(['min' => 3])]]), new ApiProperty(), []]; + yield 'native type: one supported constraint' => [new AtLeastOneOf(['constraints' => [new Positive(), new Length(['min' => 3])]]), (new ApiProperty())->withNativeType(Type::string()), [ 'oneOf' => [['minLength' => 3]], ]]; } diff --git a/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestrictionTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestrictionTest.php new file mode 100644 index 00000000000..3e535560cd6 --- /dev/null +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestrictionTest.php @@ -0,0 +1,111 @@ + + * + * 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\Symfony\Tests\Validator\Metadata\Property\Restriction; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRangeRestriction; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\Range; + +/** + * @author Tomas Norkūnas + */ +final class PropertySchemaRangeRestrictionTest extends TestCase +{ + use ProphecyTrait; + + private PropertySchemaRangeRestriction $propertySchemaRangeRestriction; + + protected function setUp(): void + { + $this->propertySchemaRangeRestriction = new PropertySchemaRangeRestriction(); + } + + #[IgnoreDeprecations] + public function testSupports(): void + { + foreach ($this->supportsProvider() as [$constraint, $propertyMetadata, $expectedResult]) { + self::assertSame($expectedResult, $this->propertySchemaRangeRestriction->supports($constraint, $propertyMetadata)); + } + } + + #[DataProvider('supportsProviderWithNativeType')] + public function testSupportsWithNativeType(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaRangeRestriction->supports($constraint, $propertyMetadata)); + } + + public static function supportsProvider(): \Generator + { + yield 'supported int/float with union types' => [new Range(['min' => 1, 'max' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), true]; + yield 'supported int' => [new Range(['min' => 1, 'max' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), true]; + yield 'supported float' => [new Range(['min' => 1, 'max' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), true]; + + yield 'not supported constraint' => [new Length(['min' => 1]), new ApiProperty(), false]; + yield 'not supported type' => [new Range(['min' => 1]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), false]; + } + + public static function supportsProviderWithNativeType(): \Generator + { + yield 'native type: supported int/float with union types' => [new Range(['min' => 1, 'max' => 10]), (new ApiProperty())->withNativeType(Type::union(Type::int(), Type::float())), true]; + yield 'native type: supported int' => [new Range(['min' => 1, 'max' => 10]), (new ApiProperty())->withNativeType(Type::int()), true]; + yield 'native type: supported float' => [new Range(['min' => 1, 'max' => 10]), (new ApiProperty())->withNativeType(Type::float()), true]; + + yield 'native type: not supported constraint' => [new Length(['min' => 1]), (new ApiProperty())->withNativeType(Type::string()), false]; + yield 'native type: not supported type' => [new Range(['min' => 1]), (new ApiProperty())->withNativeType(Type::string()), false]; + } + + #[IgnoreDeprecations] + public function testCreate(): void + { + foreach ($this->createProvider() as [$constraint, $propertyMetadata, $expectedResult]) { + self::assertSame($expectedResult, $this->propertySchemaRangeRestriction->create($constraint, $propertyMetadata)); + } + } + + #[DataProvider('createProviderWithNativeType')] + public function testCreateWithNativeType(Range $constraint, ApiProperty $propertyMetadata, array $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaRangeRestriction->create($constraint, $propertyMetadata)); + } + + public static function createProvider(): \Generator + { + yield 'int min' => [new Range(['min' => 1]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), ['minimum' => 1]]; + yield 'int max' => [new Range(['max' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), ['maximum' => 10]]; + yield 'int min max' => [new Range(['min' => 1, 'max' => 10]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), ['minimum' => 1, 'maximum' => 10]]; + + yield 'float min' => [new Range(['min' => 1.5]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), ['minimum' => 1.5]]; + yield 'float max' => [new Range(['max' => 10.5]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), ['maximum' => 10.5]]; + yield 'float min max' => [new Range(['min' => 1.5, 'max' => 10.5]), (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), ['minimum' => 1.5, 'maximum' => 10.5]]; + } + + public static function createProviderWithNativeType(): \Generator + { + yield 'native type: int min' => [new Range(['min' => 1]), (new ApiProperty())->withNativeType(Type::int()), ['minimum' => 1]]; + yield 'native type: int max' => [new Range(['max' => 10]), (new ApiProperty())->withNativeType(Type::int()), ['maximum' => 10]]; + yield 'native type: int min max' => [new Range(['min' => 1, 'max' => 10]), (new ApiProperty())->withNativeType(Type::int()), ['minimum' => 1, 'maximum' => 10]]; + + yield 'native type: float min' => [new Range(['min' => 1.5]), (new ApiProperty())->withNativeType(Type::float()), ['minimum' => 1.5]]; + yield 'native type: float max' => [new Range(['max' => 10.5]), (new ApiProperty())->withNativeType(Type::float()), ['maximum' => 10.5]]; + yield 'native type: float min max' => [new Range(['min' => 1.5, 'max' => 10.5]), (new ApiProperty())->withNativeType(Type::float()), ['minimum' => 1.5, 'maximum' => 10.5]]; + } +} diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestrictionTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestrictionTest.php similarity index 93% rename from tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestrictionTest.php rename to src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestrictionTest.php index 4066ee77938..dcf7b92d12a 100644 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestrictionTest.php +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestrictionTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Symfony\Validator\Metadata\Property\Restriction; +namespace ApiPlatform\Symfony\Tests\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRegexRestriction; @@ -49,7 +49,7 @@ public static function supportsProvider(): \Generator } #[\PHPUnit\Framework\Attributes\DataProvider('createProvider')] - public function testCreate(Constraint $constraint, ApiProperty $propertyMetadata, array $expectedResult): void + public function testCreate(Regex $constraint, ApiProperty $propertyMetadata, array $expectedResult): void { self::assertSame($expectedResult, $this->propertySchemaRegexRestriction->create($constraint, $propertyMetadata)); } diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaUniqueRestrictionTest.php b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaUniqueRestrictionTest.php similarity index 96% rename from tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaUniqueRestrictionTest.php rename to src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaUniqueRestrictionTest.php index 76fc276a01e..8bd0f11e18b 100644 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaUniqueRestrictionTest.php +++ b/src/Symfony/Tests/Validator/Metadata/Property/Restriction/PropertySchemaUniqueRestrictionTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Symfony\Validator\Metadata\Property\Restriction; +namespace ApiPlatform\Symfony\Tests\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaUniqueRestriction; diff --git a/tests/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php b/src/Symfony/Tests/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php similarity index 68% rename from tests/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php rename to src/Symfony/Tests/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php index c27cb48f7fb..8f1cb99bced 100644 --- a/tests/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php +++ b/src/Symfony/Tests/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php @@ -11,10 +11,23 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Symfony\Validator\Metadata\Property; +namespace ApiPlatform\Symfony\Tests\Validator\Metadata\Property; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Symfony\Tests\Fixtures\DummyAtLeastOneOfValidatedEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummyCollectionValidatedEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummyCompoundValidatedEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummyCountValidatedEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummyIriWithValidationEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummyNumericValidatedEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummyRangeValidatedEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummySequentiallyValidatedEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummyUniqueValidatedEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummyValidatedChoiceEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummyValidatedEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummyValidatedHostnameEntity; +use ApiPlatform\Symfony\Tests\Fixtures\DummyValidatedUlidEntity; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaChoiceRestriction; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaCollectionRestriction; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaCountRestriction; @@ -29,27 +42,16 @@ use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRegexRestriction; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaUniqueRestriction; use ApiPlatform\Symfony\Validator\Metadata\Property\ValidatorPropertyMetadataFactory; -use ApiPlatform\Tests\Fixtures\DummyAtLeastOneOfValidatedEntity; -use ApiPlatform\Tests\Fixtures\DummyCollectionValidatedEntity; -use ApiPlatform\Tests\Fixtures\DummyCompoundValidatedEntity; -use ApiPlatform\Tests\Fixtures\DummyCountValidatedEntity; -use ApiPlatform\Tests\Fixtures\DummyIriWithValidationEntity; -use ApiPlatform\Tests\Fixtures\DummyNumericValidatedEntity; -use ApiPlatform\Tests\Fixtures\DummyRangeValidatedEntity; -use ApiPlatform\Tests\Fixtures\DummySequentiallyValidatedEntity; -use ApiPlatform\Tests\Fixtures\DummyUniqueValidatedEntity; -use ApiPlatform\Tests\Fixtures\DummyValidatedChoiceEntity; -use ApiPlatform\Tests\Fixtures\DummyValidatedEntity; -use ApiPlatform\Tests\Fixtures\DummyValidatedHostnameEntity; -use ApiPlatform\Tests\Fixtures\DummyValidatedUlidEntity; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; use Symfony\Component\Validator\Constraints\Hostname; use Symfony\Component\Validator\Constraints\Ulid; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; -use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; use Symfony\Component\Validator\Mapping\Loader\AttributeLoader; /** @@ -64,7 +66,7 @@ class ValidatorPropertyMetadataFactoryTest extends TestCase protected function setUp(): void { $this->validatorClassMetadata = new ClassMetadata(DummyValidatedEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($this->validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($this->validatorClassMetadata); } public function testCreateWithPropertyWithRequiredConstraints(): void @@ -226,7 +228,7 @@ public function testCreateWithRequiredByDecorated(): void public function testCreateWithPropertyWithValidationConstraints(): void { $validatorClassMetadata = new ClassMetadata(DummyIriWithValidationEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $types = [ 'dummyUrl' => 'https://schema.org/url', @@ -268,7 +270,7 @@ public function testCreateWithPropertyWithValidationConstraints(): void public function testCreateWithPropertyLengthRestriction(): void { $validatorClassMetadata = new ClassMetadata(DummyValidatedEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class) @@ -278,7 +280,7 @@ public function testCreateWithPropertyLengthRestriction(): void $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); $property = 'dummy'; $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, $property, [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]) + (new ApiProperty())->withNativeType(Type::string()) )->shouldBeCalled(); $lengthRestrictions = new PropertySchemaLengthRestriction(); @@ -297,7 +299,7 @@ public function testCreateWithPropertyLengthRestriction(): void public function testCreateWithPropertyRegexRestriction(): void { $validatorClassMetadata = new ClassMetadata(DummyValidatedEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class) @@ -321,11 +323,11 @@ public function testCreateWithPropertyRegexRestriction(): void $this->assertEquals('^(dummy)$', $schema['pattern']); } - #[\PHPUnit\Framework\Attributes\DataProvider('providePropertySchemaFormatCases')] + #[DataProvider('providePropertySchemaFormatCases')] public function testCreateWithPropertyFormatRestriction(string $property, string $class, array $expectedSchema): void { $validatorClassMetadata = new ClassMetadata($class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor($class) @@ -364,7 +366,7 @@ public static function providePropertySchemaFormatCases(): \Generator public function testCreateWithSequentiallyConstraint(): void { $validatorClassMetadata = new ClassMetadata(DummySequentiallyValidatedEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor(DummySequentiallyValidatedEntity::class) @@ -373,7 +375,7 @@ public function testCreateWithSequentiallyConstraint(): void $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); $decoratedPropertyMetadataFactory->create(DummySequentiallyValidatedEntity::class, 'dummy', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]) + (new ApiProperty())->withNativeType(Type::string()) )->shouldBeCalled(); $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( $validatorMetadataFactory->reveal(), @@ -391,7 +393,7 @@ public function testCreateWithSequentiallyConstraint(): void public function testCreateWithCompoundConstraint(): void { $validatorClassMetadata = new ClassMetadata(DummyCompoundValidatedEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor(DummyCompoundValidatedEntity::class) @@ -400,7 +402,7 @@ public function testCreateWithCompoundConstraint(): void $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); $decoratedPropertyMetadataFactory->create(DummyCompoundValidatedEntity::class, 'dummy', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]) + (new ApiProperty())->withNativeType(Type::string()) )->shouldBeCalled(); $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( $validatorMetadataFactory->reveal(), @@ -418,7 +420,7 @@ public function testCreateWithCompoundConstraint(): void public function testCreateWithAtLeastOneOfConstraint(): void { $validatorClassMetadata = new ClassMetadata(DummyAtLeastOneOfValidatedEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor(DummyAtLeastOneOfValidatedEntity::class) @@ -427,7 +429,7 @@ public function testCreateWithAtLeastOneOfConstraint(): void $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); $decoratedPropertyMetadataFactory->create(DummyAtLeastOneOfValidatedEntity::class, 'dummy', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]) + (new ApiProperty())->withNativeType(Type::string()) )->shouldBeCalled(); $restrictionsMetadata = [new PropertySchemaLengthRestriction(), new PropertySchemaRegexRestriction()]; $restrictionsMetadata = [new PropertySchemaOneOfRestriction($restrictionsMetadata), new PropertySchemaLengthRestriction(), new PropertySchemaRegexRestriction()]; @@ -449,7 +451,7 @@ public function testCreateWithAtLeastOneOfConstraint(): void public function testCreateWithPropertyUniqueRestriction(): void { $validatorClassMetadata = new ClassMetadata(DummyUniqueValidatedEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor(DummyUniqueValidatedEntity::class) @@ -472,11 +474,48 @@ public function testCreateWithPropertyUniqueRestriction(): void $this->assertEquals(['uniqueItems' => true], $schema); } - #[\PHPUnit\Framework\Attributes\DataProvider('provideRangeConstraintCases')] - public function testCreateWithRangeConstraint(Type $type, string $property, array $expectedSchema): void + #[IgnoreDeprecations] + public function testLegacyCreateWithRangeConstraint(): void + { + foreach ($this->provideRangeConstraintCases() as ['type' => $type, 'property' => $property, 'expectedSchema' => $expectedSchema]) { + $validatorClassMetadata = new ClassMetadata(DummyRangeValidatedEntity::class); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); + + $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); + $validatorMetadataFactory->getMetadataFor(DummyRangeValidatedEntity::class) + ->willReturn($validatorClassMetadata) + ->shouldBeCalled(); + + $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); + $decoratedPropertyMetadataFactory->create(DummyRangeValidatedEntity::class, $property, [])->willReturn( + (new ApiProperty())->withBuiltinTypes([$type]) + )->shouldBeCalled(); + $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( + $validatorMetadataFactory->reveal(), + $decoratedPropertyMetadataFactory->reveal(), + [new PropertySchemaRangeRestriction()] + ); + $schema = $validationPropertyMetadataFactory->create(DummyRangeValidatedEntity::class, $property)->getSchema(); + + $this->assertEquals($expectedSchema, $schema); + } + } + + public static function provideRangeConstraintCases(): \Generator + { + yield 'min int' => ['type' => new LegacyType(LegacyType::BUILTIN_TYPE_INT), 'property' => 'dummyIntMin', 'expectedSchema' => ['minimum' => 1]]; + yield 'max int' => ['type' => new LegacyType(LegacyType::BUILTIN_TYPE_INT), 'property' => 'dummyIntMax', 'expectedSchema' => ['maximum' => 10]]; + yield 'min/max int' => ['type' => new LegacyType(LegacyType::BUILTIN_TYPE_INT), 'property' => 'dummyIntMinMax', 'expectedSchema' => ['minimum' => 1, 'maximum' => 10]]; + yield 'min float' => ['type' => new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), 'property' => 'dummyFloatMin', 'expectedSchema' => ['minimum' => 1.5]]; + yield 'max float' => ['type' => new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), 'property' => 'dummyFloatMax', 'expectedSchema' => ['maximum' => 10.5]]; + yield 'min/max float' => ['type' => new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), 'property' => 'dummyFloatMinMax', 'expectedSchema' => ['minimum' => 1.5, 'maximum' => 10.5]]; + } + + #[DataProvider('provideRangeConstraintCasesWithNativeType')] + public function testCreateWithRangeConstraintWithNativeType(Type $type, string $property, array $expectedSchema): void // Use new Type { $validatorClassMetadata = new ClassMetadata(DummyRangeValidatedEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor(DummyRangeValidatedEntity::class) @@ -485,7 +524,7 @@ public function testCreateWithRangeConstraint(Type $type, string $property, arra $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); $decoratedPropertyMetadataFactory->create(DummyRangeValidatedEntity::class, $property, [])->willReturn( - (new ApiProperty())->withBuiltinTypes([$type]) + (new ApiProperty())->withNativeType($type) // Use withNativeType and new Type )->shouldBeCalled(); $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( $validatorMetadataFactory->reveal(), @@ -497,21 +536,61 @@ public function testCreateWithRangeConstraint(Type $type, string $property, arra $this->assertEquals($expectedSchema, $schema); } - public static function provideRangeConstraintCases(): \Generator + public static function provideRangeConstraintCasesWithNativeType(): \Generator + { + yield 'native type: min int' => ['type' => Type::int(), 'property' => 'dummyIntMin', 'expectedSchema' => ['minimum' => 1]]; + yield 'native type: max int' => ['type' => Type::int(), 'property' => 'dummyIntMax', 'expectedSchema' => ['maximum' => 10]]; + yield 'native type: min/max int' => ['type' => Type::int(), 'property' => 'dummyIntMinMax', 'expectedSchema' => ['minimum' => 1, 'maximum' => 10]]; + yield 'native type: min float' => ['type' => Type::float(), 'property' => 'dummyFloatMin', 'expectedSchema' => ['minimum' => 1.5]]; + yield 'native type: max float' => ['type' => Type::float(), 'property' => 'dummyFloatMax', 'expectedSchema' => ['maximum' => 10.5]]; + yield 'native type: min/max float' => ['type' => Type::float(), 'property' => 'dummyFloatMinMax', 'expectedSchema' => ['minimum' => 1.5, 'maximum' => 10.5]]; + } + + #[IgnoreDeprecations] + public function testCreateWithPropertyChoiceRestriction(): void + { + foreach ($this->provideChoiceConstraintCases() as ['propertyMetadata' => $propertyMetadata, 'property' => $property, 'expectedSchema' => $expectedSchema]) { + $validatorClassMetadata = new ClassMetadata(DummyValidatedChoiceEntity::class); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); + + $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); + $validatorMetadataFactory->getMetadataFor(DummyValidatedChoiceEntity::class) + ->willReturn($validatorClassMetadata) + ->shouldBeCalled(); + + $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); + $decoratedPropertyMetadataFactory->create(DummyValidatedChoiceEntity::class, $property, [])->willReturn( + $propertyMetadata + )->shouldBeCalled(); + + $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( + $validatorMetadataFactory->reveal(), + $decoratedPropertyMetadataFactory->reveal(), + [new PropertySchemaChoiceRestriction()] + ); + + $schema = $validationPropertyMetadataFactory->create(DummyValidatedChoiceEntity::class, $property)->getSchema(); + + $this->assertEquals($expectedSchema, $schema); + } + } + + public static function provideChoiceConstraintCases(): \Generator { - yield 'min int' => ['type' => new Type(Type::BUILTIN_TYPE_INT), 'property' => 'dummyIntMin', 'expectedSchema' => ['minimum' => 1]]; - yield 'max int' => ['type' => new Type(Type::BUILTIN_TYPE_INT), 'property' => 'dummyIntMax', 'expectedSchema' => ['maximum' => 10]]; - yield 'min/max int' => ['type' => new Type(Type::BUILTIN_TYPE_INT), 'property' => 'dummyIntMinMax', 'expectedSchema' => ['minimum' => 1, 'maximum' => 10]]; - yield 'min float' => ['type' => new Type(Type::BUILTIN_TYPE_FLOAT), 'property' => 'dummyFloatMin', 'expectedSchema' => ['minimum' => 1.5]]; - yield 'max float' => ['type' => new Type(Type::BUILTIN_TYPE_FLOAT), 'property' => 'dummyFloatMax', 'expectedSchema' => ['maximum' => 10.5]]; - yield 'min/max float' => ['type' => new Type(Type::BUILTIN_TYPE_FLOAT), 'property' => 'dummyFloatMinMax', 'expectedSchema' => ['minimum' => 1.5, 'maximum' => 10.5]]; + yield 'single choice' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), 'property' => 'dummySingleChoice', 'expectedSchema' => ['enum' => ['a', 'b']]]; + yield 'single choice callback' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), 'property' => 'dummySingleChoiceCallback', 'expectedSchema' => ['enum' => ['a', 'b', 'c', 'd']]]; + yield 'multi choice' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), 'property' => 'dummyMultiChoice', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b']]]]; + yield 'multi choice callback' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), 'property' => 'dummyMultiChoiceCallback', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']]]]; + yield 'multi choice min' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), 'property' => 'dummyMultiChoiceMin', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']], 'minItems' => 2]]; + yield 'multi choice max' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), 'property' => 'dummyMultiChoiceMax', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']], 'maxItems' => 4]]; + yield 'multi choice min/max' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)]), 'property' => 'dummyMultiChoiceMinMax', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']], 'minItems' => 2, 'maxItems' => 4]]; } - #[\PHPUnit\Framework\Attributes\DataProvider('provideChoiceConstraintCases')] - public function testCreateWithPropertyChoiceRestriction(ApiProperty $propertyMetadata, string $property, array $expectedSchema): void + #[DataProvider('provideChoiceConstraintCasesWithNativeType')] + public function testCreateWithPropertyChoiceRestrictionWithNativeType(ApiProperty $propertyMetadata, string $property, array $expectedSchema): void { $validatorClassMetadata = new ClassMetadata(DummyValidatedChoiceEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor(DummyValidatedChoiceEntity::class) @@ -520,7 +599,7 @@ public function testCreateWithPropertyChoiceRestriction(ApiProperty $propertyMet $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); $decoratedPropertyMetadataFactory->create(DummyValidatedChoiceEntity::class, $property, [])->willReturn( - $propertyMetadata + $propertyMetadata // Provider now sends ApiProperty configured with withNativeType )->shouldBeCalled(); $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( @@ -534,22 +613,22 @@ public function testCreateWithPropertyChoiceRestriction(ApiProperty $propertyMet $this->assertEquals($expectedSchema, $schema); } - public static function provideChoiceConstraintCases(): \Generator + public static function provideChoiceConstraintCasesWithNativeType(): \Generator { - yield 'single choice' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), 'property' => 'dummySingleChoice', 'expectedSchema' => ['enum' => ['a', 'b']]]; - yield 'single choice callback' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), 'property' => 'dummySingleChoiceCallback', 'expectedSchema' => ['enum' => ['a', 'b', 'c', 'd']]]; - yield 'multi choice' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), 'property' => 'dummyMultiChoice', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b']]]]; - yield 'multi choice callback' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), 'property' => 'dummyMultiChoiceCallback', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']]]]; - yield 'multi choice min' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), 'property' => 'dummyMultiChoiceMin', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']], 'minItems' => 2]]; - yield 'multi choice max' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), 'property' => 'dummyMultiChoiceMax', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']], 'maxItems' => 4]]; - yield 'multi choice min/max' => ['propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), 'property' => 'dummyMultiChoiceMinMax', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']], 'minItems' => 2, 'maxItems' => 4]]; + yield 'native type: single choice' => ['propertyMetadata' => (new ApiProperty())->withNativeType(Type::string()), 'property' => 'dummySingleChoice', 'expectedSchema' => ['enum' => ['a', 'b']]]; + yield 'native type: single choice callback' => ['propertyMetadata' => (new ApiProperty())->withNativeType(Type::string()), 'property' => 'dummySingleChoiceCallback', 'expectedSchema' => ['enum' => ['a', 'b', 'c', 'd']]]; + yield 'native type: multi choice' => ['propertyMetadata' => (new ApiProperty())->withNativeType(Type::string()), 'property' => 'dummyMultiChoice', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b']]]]; + yield 'native type: multi choice callback' => ['propertyMetadata' => (new ApiProperty())->withNativeType(Type::string()), 'property' => 'dummyMultiChoiceCallback', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']]]]; + yield 'native type: multi choice min' => ['propertyMetadata' => (new ApiProperty())->withNativeType(Type::string()), 'property' => 'dummyMultiChoiceMin', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']], 'minItems' => 2]]; + yield 'native type: multi choice max' => ['propertyMetadata' => (new ApiProperty())->withNativeType(Type::string()), 'property' => 'dummyMultiChoiceMax', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']], 'maxItems' => 4]]; + yield 'native type: multi choice min/max' => ['propertyMetadata' => (new ApiProperty())->withNativeType(Type::string()), 'property' => 'dummyMultiChoiceMinMax', 'expectedSchema' => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']], 'minItems' => 2, 'maxItems' => 4]]; } - #[\PHPUnit\Framework\Attributes\DataProvider('provideCountConstraintCases')] + #[DataProvider('provideCountConstraintCases')] public function testCreateWithPropertyCountRestriction(string $property, array $expectedSchema): void { $validatorClassMetadata = new ClassMetadata(DummyCountValidatedEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor(DummyCountValidatedEntity::class) @@ -582,7 +661,7 @@ public static function provideCountConstraintCases(): \Generator public function testCreateWithPropertyCollectionRestriction(): void { $validatorClassMetadata = new ClassMetadata(DummyCollectionValidatedEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor(DummyCollectionValidatedEntity::class) @@ -591,7 +670,7 @@ public function testCreateWithPropertyCollectionRestriction(): void $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); $decoratedPropertyMetadataFactory->create(DummyCollectionValidatedEntity::class, 'dummyData', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_ARRAY)]) + (new ApiProperty())->withNativeType(Type::list()) )->shouldBeCalled(); $greaterThanRestriction = new PropertySchemaGreaterThanRestriction(); @@ -648,11 +727,96 @@ public function testCreateWithPropertyCollectionRestriction(): void ], $schema); } - #[\PHPUnit\Framework\Attributes\DataProvider('provideNumericConstraintCases')] - public function testCreateWithPropertyNumericRestriction(ApiProperty $propertyMetadata, string $property, array $expectedSchema): void + #[IgnoreDeprecations] + public function testCreateWithPropertyNumericRestriction(): void + { + foreach ($this->provideNumericConstraintCases() as ['propertyMetadata' => $propertyMetadata, 'property' => $property, 'expectedSchema' => $expectedSchema]) { + $validatorClassMetadata = new ClassMetadata(DummyNumericValidatedEntity::class); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); + + $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); + $validatorMetadataFactory->getMetadataFor(DummyNumericValidatedEntity::class) + ->willReturn($validatorClassMetadata) + ->shouldBeCalled(); + + $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); + $decoratedPropertyMetadataFactory->create(DummyNumericValidatedEntity::class, $property, [])->willReturn( + $propertyMetadata + )->shouldBeCalled(); + + $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( + $validatorMetadataFactory->reveal(), + $decoratedPropertyMetadataFactory->reveal(), + [ + new PropertySchemaGreaterThanOrEqualRestriction(), + new PropertySchemaGreaterThanRestriction(), + new PropertySchemaLessThanOrEqualRestriction(), + new PropertySchemaLessThanRestriction(), + ] + ); + + $schema = $validationPropertyMetadataFactory->create(DummyNumericValidatedEntity::class, $property)->getSchema(); + + $this->assertEquals($expectedSchema, $schema); + } + } + + public static function provideNumericConstraintCases(): \Generator + { + yield [ + 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), + 'property' => 'greaterThanMe', + 'expectedSchema' => ['exclusiveMinimum' => 10], + ]; + + yield [ + 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), + 'property' => 'greaterThanOrEqualToMe', + 'expectedSchema' => ['minimum' => 10.99], + ]; + + yield [ + 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), + 'property' => 'lessThanMe', + 'expectedSchema' => ['exclusiveMaximum' => 99], + ]; + + yield [ + 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)]), + 'property' => 'lessThanOrEqualToMe', + 'expectedSchema' => ['maximum' => 99.33], + ]; + + yield [ + 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), + 'property' => 'positive', + 'expectedSchema' => ['exclusiveMinimum' => 0], + ]; + + yield [ + 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), + 'property' => 'positiveOrZero', + 'expectedSchema' => ['minimum' => 0], + ]; + + yield [ + 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), + 'property' => 'negative', + 'expectedSchema' => ['exclusiveMaximum' => 0], + ]; + + yield [ + 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)]), + 'property' => 'negativeOrZero', + 'expectedSchema' => ['maximum' => 0], + ]; + } + + #[DataProvider('provideNumericConstraintCasesWithNativeType')] + public function testCreateWithPropertyNumericRestrictionWithNativeType(ApiProperty $propertyMetadata, string $property, array $expectedSchema): void { $validatorClassMetadata = new ClassMetadata(DummyNumericValidatedEntity::class); - (class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader())->loadClassMetadata($validatorClassMetadata); + (new AttributeLoader())->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); $validatorMetadataFactory->getMetadataFor(DummyNumericValidatedEntity::class) @@ -680,52 +844,52 @@ public function testCreateWithPropertyNumericRestriction(ApiProperty $propertyMe $this->assertEquals($expectedSchema, $schema); } - public static function provideNumericConstraintCases(): \Generator + public static function provideNumericConstraintCasesWithNativeType(): \Generator { yield [ - 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), + 'propertyMetadata' => (new ApiProperty())->withNativeType(Type::int()), 'property' => 'greaterThanMe', 'expectedSchema' => ['exclusiveMinimum' => 10], ]; yield [ - 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)]), + 'propertyMetadata' => (new ApiProperty())->withNativeType(Type::float()), 'property' => 'greaterThanOrEqualToMe', 'expectedSchema' => ['minimum' => 10.99], ]; yield [ - 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), + 'propertyMetadata' => (new ApiProperty())->withNativeType(Type::int()), 'property' => 'lessThanMe', 'expectedSchema' => ['exclusiveMaximum' => 99], ]; yield [ - 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)]), + 'propertyMetadata' => (new ApiProperty())->withNativeType(Type::float()), 'property' => 'lessThanOrEqualToMe', 'expectedSchema' => ['maximum' => 99.33], ]; yield [ - 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), + 'propertyMetadata' => (new ApiProperty())->withNativeType(Type::int()), 'property' => 'positive', 'expectedSchema' => ['exclusiveMinimum' => 0], ]; yield [ - 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), + 'propertyMetadata' => (new ApiProperty())->withNativeType(Type::int()), 'property' => 'positiveOrZero', 'expectedSchema' => ['minimum' => 0], ]; yield [ - 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), + 'propertyMetadata' => (new ApiProperty())->withNativeType(Type::int()), 'property' => 'negative', 'expectedSchema' => ['exclusiveMaximum' => 0], ]; yield [ - 'propertyMetadata' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), + 'propertyMetadata' => (new ApiProperty())->withNativeType(Type::int()), 'property' => 'negativeOrZero', 'expectedSchema' => ['maximum' => 0], ]; diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php index 943701449d5..cd630978e10 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php @@ -14,7 +14,12 @@ namespace ApiPlatform\Symfony\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; -use Symfony\Component\PropertyInfo\Type; +use ApiPlatform\Metadata\Util\TypeHelper; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\TypeIdentifier; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Choice; @@ -73,24 +78,47 @@ public function create(Constraint $constraint, ApiProperty $propertyMetadata): a return $restriction; } - /** - * {@inheritdoc} - */ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): bool { - $types = array_map(static fn (Type $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + if (!$constraint instanceof Choice) { + return false; + } + + if (method_exists(PropertyInfoExtractor::class, 'getType')) { + $nativeType = $propertyMetadata->getExtraProperties()['nested_schema'] ?? false + ? Type::string() + : $propertyMetadata->getNativeType(); + + $isValidScalarType = fn (Type $t): bool => $t->isSatisfiedBy( + fn (Type $subType): bool => $subType->isIdentifiedBy(TypeIdentifier::STRING, TypeIdentifier::INT, TypeIdentifier::FLOAT) + ); + + if ($isValidScalarType($nativeType)) { + return true; + } + + if ($nativeType->isSatisfiedBy(fn ($t) => $t instanceof CollectionType)) { + if (null !== ($collectionValueType = TypeHelper::getCollectionValueType($nativeType)) && $isValidScalarType($collectionValueType)) { + return true; + } + } + + return false; + } + + $types = array_map(static fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { - $types = [Type::BUILTIN_TYPE_STRING]; + $types = [LegacyType::BUILTIN_TYPE_STRING]; } if ( - null !== ($builtinType = $propertyMetadata->getBuiltinTypes()[0] ?? null) + null !== ($builtinType = ($propertyMetadata->getBuiltinTypes()[0] ?? null)) && $builtinType->isCollection() - && \count($builtinType->getCollectionValueTypes()) + && \count($builtinType->getCollectionValueTypes()) > 0 ) { - $types = array_unique(array_merge($types, array_map(static fn (Type $type) => $type->getBuiltinType(), $builtinType->getCollectionValueTypes()))); + $types = array_unique(array_merge($types, array_map(static fn (LegacyType $type) => $type->getBuiltinType(), $builtinType->getCollectionValueTypes()))); } - return $constraint instanceof Choice && \count($types) && array_intersect($types, [Type::BUILTIN_TYPE_STRING, Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT]); + return \count($types) > 0 && \count(array_intersect($types, [LegacyType::BUILTIN_TYPE_STRING, LegacyType::BUILTIN_TYPE_INT, LegacyType::BUILTIN_TYPE_FLOAT])) > 0; } } diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestriction.php index 318adf1ef09..185157d3954 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestriction.php @@ -14,7 +14,10 @@ namespace ApiPlatform\Symfony\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; @@ -35,16 +38,25 @@ public function create(Constraint $constraint, ApiProperty $propertyMetadata): a ]; } - /** - * {@inheritdoc} - */ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): bool { - $types = array_map(fn (Type $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + if (!$constraint instanceof GreaterThanOrEqual || !is_numeric($constraint->value)) { + return false; + } + + if (method_exists(PropertyInfoExtractor::class, 'getType')) { + $type = $propertyMetadata->getExtraProperties()['nested_schema'] ?? false + ? Type::int() + : $propertyMetadata->getNativeType(); + + return $type->isIdentifiedBy(TypeIdentifier::INT, TypeIdentifier::FLOAT); + } + + $types = array_map(fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { - $types = [Type::BUILTIN_TYPE_INT]; + $types = [LegacyType::BUILTIN_TYPE_INT]; } - return $constraint instanceof GreaterThanOrEqual && is_numeric($constraint->value) && \count($types) && array_intersect($types, [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT]); + return \count($types) > 0 && \count(array_intersect($types, [LegacyType::BUILTIN_TYPE_INT, LegacyType::BUILTIN_TYPE_FLOAT])) > 0; } } diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestriction.php index 63186611e3e..97918da97e3 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestriction.php @@ -14,7 +14,10 @@ namespace ApiPlatform\Symfony\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\GreaterThan; @@ -40,11 +43,23 @@ public function create(Constraint $constraint, ApiProperty $propertyMetadata): a */ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): bool { - $types = array_map(fn (Type $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + if (!$constraint instanceof GreaterThan || !is_numeric($constraint->value)) { + return false; + } + + if (method_exists(PropertyInfoExtractor::class, 'getType')) { + $type = $propertyMetadata->getExtraProperties()['nested_schema'] ?? false + ? Type::int() + : $propertyMetadata->getNativeType(); + + return $type->isIdentifiedBy(TypeIdentifier::INT, TypeIdentifier::FLOAT); + } + + $types = array_map(fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { - $types = [Type::BUILTIN_TYPE_INT]; + $types = [LegacyType::BUILTIN_TYPE_INT]; } - return $constraint instanceof GreaterThan && is_numeric($constraint->value) && \count($types) && array_intersect($types, [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT]); + return \count($types) && array_intersect($types, [LegacyType::BUILTIN_TYPE_INT, LegacyType::BUILTIN_TYPE_FLOAT]); } } diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php index d79cd00ddfd..b6af9794b00 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php @@ -14,7 +14,10 @@ namespace ApiPlatform\Symfony\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Length; @@ -50,11 +53,17 @@ public function create(Constraint $constraint, ApiProperty $propertyMetadata): a */ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): bool { - $types = array_map(fn (Type $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + if (method_exists(PropertyInfoExtractor::class, 'getType')) { + $type = $propertyMetadata->getExtraProperties()['nested_schema'] ?? false ? Type::string() : $propertyMetadata->getNativeType(); + + return $constraint instanceof Length && $type?->isIdentifiedBy(TypeIdentifier::STRING); + } + + $types = array_map(fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { - $types = [Type::BUILTIN_TYPE_STRING]; + $types = [LegacyType::BUILTIN_TYPE_STRING]; } - return $constraint instanceof Length && \count($types) && \in_array(Type::BUILTIN_TYPE_STRING, $types, true); + return $constraint instanceof Length && \count($types) && \in_array(LegacyType::BUILTIN_TYPE_STRING, $types, true); } } diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestriction.php index 3c8836e0324..ff37eb2174f 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestriction.php @@ -14,7 +14,10 @@ namespace ApiPlatform\Symfony\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\LessThanOrEqual; @@ -40,11 +43,23 @@ public function create(Constraint $constraint, ApiProperty $propertyMetadata): a */ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): bool { - $types = array_map(fn (Type $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + if (!$constraint instanceof LessThanOrEqual || !is_numeric($constraint->value)) { + return false; + } + + if (method_exists(PropertyInfoExtractor::class, 'getType')) { + $type = $propertyMetadata->getExtraProperties()['nested_schema'] ?? false + ? Type::int() + : $propertyMetadata->getNativeType(); + + return $type->isIdentifiedBy(TypeIdentifier::INT, TypeIdentifier::FLOAT); + } + + $types = array_map(fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { - $types = [Type::BUILTIN_TYPE_INT]; + $types = [LegacyType::BUILTIN_TYPE_INT]; } - return $constraint instanceof LessThanOrEqual && is_numeric($constraint->value) && \count($types) && array_intersect($types, [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT]); + return \count($types) > 0 && \count(array_intersect($types, [LegacyType::BUILTIN_TYPE_INT, LegacyType::BUILTIN_TYPE_FLOAT])) > 0; } } diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestriction.php index 6fcf40761cb..f19648a8620 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestriction.php @@ -14,7 +14,10 @@ namespace ApiPlatform\Symfony\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\LessThan; @@ -35,16 +38,25 @@ public function create(Constraint $constraint, ApiProperty $propertyMetadata): a ]; } - /** - * {@inheritdoc} - */ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): bool { - $types = array_map(fn (Type $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + if (!$constraint instanceof LessThan || !is_numeric($constraint->value)) { + return false; + } + + if (method_exists(PropertyInfoExtractor::class, 'getType')) { + $type = $propertyMetadata->getExtraProperties()['nested_schema'] ?? false + ? Type::int() + : $propertyMetadata->getNativeType(); + + return $type->isIdentifiedBy(TypeIdentifier::INT, TypeIdentifier::FLOAT); + } + + $types = array_map(fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { - $types = [Type::BUILTIN_TYPE_INT]; + $types = [LegacyType::BUILTIN_TYPE_INT]; } - return $constraint instanceof LessThan && is_numeric($constraint->value) && \count($types) && array_intersect($types, [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT]); + return \count($types) > 0 && \count(array_intersect($types, [LegacyType::BUILTIN_TYPE_INT, LegacyType::BUILTIN_TYPE_FLOAT])) > 0; } } diff --git a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php index 832a3f495fb..20d0ddf1457 100644 --- a/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php +++ b/src/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php @@ -14,7 +14,10 @@ namespace ApiPlatform\Symfony\Validator\Metadata\Property\Restriction; use ApiPlatform\Metadata\ApiProperty; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Range; @@ -48,11 +51,23 @@ public function create(Constraint $constraint, ApiProperty $propertyMetadata): a */ public function supports(Constraint $constraint, ApiProperty $propertyMetadata): bool { - $types = array_map(fn (Type $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); + if (!$constraint instanceof Range) { + return false; + } + + if (method_exists(PropertyInfoExtractor::class, 'getType')) { + $type = $propertyMetadata->getExtraProperties()['nested_schema'] ?? false + ? Type::int() + : $propertyMetadata->getNativeType(); + + return $type->isIdentifiedBy(TypeIdentifier::INT, TypeIdentifier::FLOAT); + } + + $types = array_map(fn (LegacyType $type) => $type->getBuiltinType(), $propertyMetadata->getBuiltinTypes() ?? []); if ($propertyMetadata->getExtraProperties()['nested_schema'] ?? false) { - $types = [Type::BUILTIN_TYPE_INT]; + $types = [LegacyType::BUILTIN_TYPE_INT]; } - return $constraint instanceof Range && \count($types) && array_intersect($types, [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT]); + return \count($types) > 0 && \count(array_intersect($types, [LegacyType::BUILTIN_TYPE_INT, LegacyType::BUILTIN_TYPE_FLOAT])) > 0; } } diff --git a/src/Symfony/composer.json b/src/Symfony/composer.json index e89c3a18b8f..61ebc209d40 100644 --- a/src/Symfony/composer.json +++ b/src/Symfony/composer.json @@ -46,20 +46,21 @@ "willdurand/negotiation": "^3.1" }, "require-dev": { - "phpspec/prophecy-phpunit": "^2.2", - "symfony/routing": "^6.4 || ^7.0", - "phpunit/phpunit": "11.5.x-dev", - "symfony/validator": "^6.4 || ^7.0", - "symfony/mercure-bundle": "*", - "webonyx/graphql-php": "^15.0", "api-platform/doctrine-common": "^4.1", + "api-platform/doctrine-odm": "^4.1", + "api-platform/doctrine-orm": "^4.1", "api-platform/elasticsearch": "^4.1", "api-platform/graphql": "^4.1", - "api-platform/doctrine-orm": "^4.1", - "api-platform/doctrine-odm": "^4.1", "api-platform/parameter-validator": "^3.1", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", "symfony/expression-language": "^6.4 || ^7.0", - "symfony/type-info": "^7.3-dev" + "symfony/intl": "^7.2", + "symfony/mercure-bundle": "*", + "symfony/routing": "^6.4 || ^7.0", + "symfony/type-info": "v7.3.0-BETA1", + "symfony/validator": "^6.4 || ^7.0", + "webonyx/graphql-php": "^15.0" }, "suggest": { "api-platform/doctrine-orm": "To support Doctrine ORM.", @@ -117,10 +118,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/src/Validator/composer.json b/src/Validator/composer.json index 837240d5171..2ac6a53f9f3 100644 --- a/src/Validator/composer.json +++ b/src/Validator/composer.json @@ -32,7 +32,7 @@ "phpunit/phpunit": "11.5.x-dev", "symfony/validator": "^6.4 || ^7.0", "symfony/http-kernel": "^6.4 || ^7.0", - "symfony/type-info": "^7.3-dev" + "symfony/type-info": "v7.3.0-BETA1" }, "autoload": { "psr-4": { @@ -66,10 +66,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" diff --git a/tests/JsonLd/ContextBuilderTest.php b/tests/JsonLd/ContextBuilderTest.php index 7a64f7d3beb..f0f3f87a030 100644 --- a/tests/JsonLd/ContextBuilderTest.php +++ b/tests/JsonLd/ContextBuilderTest.php @@ -33,7 +33,7 @@ use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; /** * @author Markus Mächler @@ -69,7 +69,7 @@ public function testResourceContext(): void ->withOperations(new Operations(['get' => (new Get())->withShortName('DummyEntity')])), ])); $this->propertyNameCollectionFactoryProphecy->create($this->entityClass)->willReturn(new PropertyNameCollection(['dummyPropertyA'])); - $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); + $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); $this->urlGeneratorProphecy->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL)->willReturn(''); $contextBuilder = new ContextBuilder($this->resourceNameCollectionFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->urlGeneratorProphecy->reveal()); @@ -91,7 +91,7 @@ public function testIriOnlyResourceContext(): void ->withOperations(new Operations(['get' => (new Get())->withShortName('DummyEntity')->withNormalizationContext(['iri_only' => true])])), ])); $this->propertyNameCollectionFactoryProphecy->create($this->entityClass)->willReturn(new PropertyNameCollection(['dummyPropertyA'])); - $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); + $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); $this->urlGeneratorProphecy->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL)->willReturn(''); $contextBuilder = new ContextBuilder($this->resourceNameCollectionFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->urlGeneratorProphecy->reveal()); @@ -115,7 +115,7 @@ public function testResourceContextWithJsonldContext(): void ->withOperations(new Operations(['get' => (new Get())->withShortName('DummyEntity')])), ])); $this->propertyNameCollectionFactoryProphecy->create($this->entityClass)->willReturn(new PropertyNameCollection(['dummyPropertyA'])); - $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withJsonldContext(['@type' => '@id', '@id' => 'customId', 'foo' => 'bar'])); + $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withJsonldContext(['@type' => '@id', '@id' => 'customId', 'foo' => 'bar'])); $this->urlGeneratorProphecy->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL)->willReturn(''); $contextBuilder = new ContextBuilder($this->resourceNameCollectionFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->urlGeneratorProphecy->reveal()); @@ -142,7 +142,7 @@ public function testGetEntryPointContext(): void ])); $this->propertyNameCollectionFactoryProphecy->create($this->entityClass)->willReturn(new PropertyNameCollection(['dummyPropertyA'])); $this->resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection(['dummyPropertyA'])); - $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withJsonldContext(['@type' => '@id', '@id' => 'customId', 'foo' => 'bar'])); + $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withJsonldContext(['@type' => '@id', '@id' => 'customId', 'foo' => 'bar'])); $this->urlGeneratorProphecy->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL)->willReturn(''); $contextBuilder = new ContextBuilder($this->resourceNameCollectionFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->urlGeneratorProphecy->reveal()); @@ -167,7 +167,7 @@ public function testResourceContextWithReverse(): void ->withOperations(new Operations(['get' => (new Get())->withShortName('DummyEntity')])), ])); $this->propertyNameCollectionFactoryProphecy->create($this->entityClass)->willReturn(new PropertyNameCollection(['dummyPropertyA'])); - $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withJsonldContext(['@reverse' => 'parent'])); + $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withJsonldContext(['@reverse' => 'parent'])); $this->urlGeneratorProphecy->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL)->willReturn(''); $contextBuilder = new ContextBuilder($this->resourceNameCollectionFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->urlGeneratorProphecy->reveal()); @@ -188,7 +188,7 @@ public function testAnonymousResourceContext(): void { $dummy = new Dummy(); $this->propertyNameCollectionFactoryProphecy->create(Dummy::class)->willReturn(new PropertyNameCollection(['dummyPropertyA'])); - $this->propertyMetadataFactoryProphecy->create(Dummy::class, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withGenId(true)); + $this->propertyMetadataFactoryProphecy->create(Dummy::class, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)->withGenId(true)); $this->urlGeneratorProphecy->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL)->willReturn(''); $this->iriConverterProphecy->getIriFromResource($dummy)->willreturn('/.well-known/genid/1'); @@ -208,7 +208,7 @@ public function testAnonymousResourceContextWithIri(): void { $output = new OutputDto(); $this->propertyNameCollectionFactoryProphecy->create(OutputDto::class)->willReturn(new PropertyNameCollection(['dummyPropertyA'])); - $this->propertyMetadataFactoryProphecy->create(OutputDto::class, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); + $this->propertyMetadataFactoryProphecy->create(OutputDto::class, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); $this->urlGeneratorProphecy->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL)->willReturn(''); $contextBuilder = new ContextBuilder($this->resourceNameCollectionFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->urlGeneratorProphecy->reveal()); @@ -230,7 +230,7 @@ public function testAnonymousResourceContextWithApiResource(): void { $output = new OutputDto(); $this->propertyNameCollectionFactoryProphecy->create(OutputDto::class)->willReturn(new PropertyNameCollection(['dummyPropertyA'])); - $this->propertyMetadataFactoryProphecy->create(OutputDto::class, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); + $this->propertyMetadataFactoryProphecy->create(OutputDto::class, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); $this->urlGeneratorProphecy->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL)->willReturn(''); $this->resourceMetadataCollectionFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection('Dummy', [ @@ -258,7 +258,7 @@ public function testAnonymousResourceContextWithApiResourceHavingContext(): void { $output = new OutputDto(); $this->propertyNameCollectionFactoryProphecy->create(OutputDto::class)->willReturn(new PropertyNameCollection(['dummyPropertyA'])); - $this->propertyMetadataFactoryProphecy->create(OutputDto::class, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); + $this->propertyMetadataFactoryProphecy->create(OutputDto::class, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); $this->urlGeneratorProphecy->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL)->willReturn(''); $this->resourceMetadataCollectionFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection('Dummy', [ @@ -285,7 +285,7 @@ public function testResourceContextWithoutHydraPrefix(): void ->withOperations(new Operations(['get' => (new Get())->withShortName('DummyEntity')])), ])); $this->propertyNameCollectionFactoryProphecy->create($this->entityClass)->willReturn(new PropertyNameCollection(['dummyPropertyA'])); - $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); + $this->propertyMetadataFactoryProphecy->create($this->entityClass, 'dummyPropertyA', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('Dummy property A')->withReadable(true)->withWritable(true)->withReadableLink(true)->withWritableLink(true)); $this->urlGeneratorProphecy->generate('api_doc', ['_format' => 'jsonld'], UrlGeneratorInterface::ABS_URL)->willReturn(''); $contextBuilder = new ContextBuilder($this->resourceNameCollectionFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->urlGeneratorProphecy->reveal(), null, null, [ContextBuilder::HYDRA_CONTEXT_HAS_PREFIX => false]); diff --git a/tests/Symfony/Bundle/Test/ApiTestCaseTest.php b/tests/Symfony/Bundle/Test/ApiTestCaseTest.php index dbf5f074f2c..8e48db6a294 100644 --- a/tests/Symfony/Bundle/Test/ApiTestCaseTest.php +++ b/tests/Symfony/Bundle/Test/ApiTestCaseTest.php @@ -74,7 +74,8 @@ public function testAssertJsonContains(): void public function testAssertJsonContainsWithJsonObjectString(): void { self::createClient()->request('GET', '/'); - $this->assertJsonContains(<<assertJsonContains( + <<expectExceptionMessage('$subset must be array or string (JSON array or JSON object)'); self::createClient()->request('GET', '/'); - $this->assertJsonContains(<<assertJsonContains( + <<request('GET', '/contexts/Address'); - $this->assertJsonEquals(<<assertJsonEquals( + <<method('getContainer')->willReturn(self::getContainer()); $mock->expects($this->never())->method('boot'); + $oldKernel = self::$kernel; self::$kernel = $mock; self::createClient(); + + // restore old kernel for proper shutdown + self::$kernel = $oldKernel; } } diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestrictionTest.php b/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestrictionTest.php deleted file mode 100644 index 476f5394969..00000000000 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestrictionTest.php +++ /dev/null @@ -1,123 +0,0 @@ - - * - * 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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaChoiceRestriction; -use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -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 - */ -final class PropertySchemaChoiceRestrictionTest extends TestCase -{ - use ProphecyTrait; - - private PropertySchemaChoiceRestriction $propertySchemaChoiceRestriction; - - protected function setUp(): void - { - $this->propertySchemaChoiceRestriction = new PropertySchemaChoiceRestriction(); - } - - #[\PHPUnit\Framework\Attributes\DataProvider('supportsProvider')] - public function testSupports(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void - { - self::assertSame($expectedResult, $this->propertySchemaChoiceRestriction->supports($constraint, $propertyMetadata)); - } - - public static function supportsProvider(): \Generator - { - yield 'supported string' => [new Choice(['choices' => ['a', 'b']]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), true]; - yield 'supported int' => [new Choice(['choices' => [1, 2]]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), true]; - yield 'supported float' => [new Choice(['choices' => [1.1, 2.2]]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)]), true]; - yield 'supported string/int/float with union types' => [new Choice(['choices' => [1, 2, 1.1, 2.2, 'a', 'b']]), (new ApiProperty())->withBuiltinTypes([ - new Type(Type::BUILTIN_TYPE_FLOAT), - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_STRING), - ]), true]; - - yield 'not supported constraint' => [new Positive(), new ApiProperty(), false]; - yield 'not supported type' => [new Choice(['choices' => [new \stdClass(), new \stdClass()]]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT)]), false]; - } - - #[\PHPUnit\Framework\Attributes\DataProvider('createProvider')] - public function testCreate(Constraint $constraint, ApiProperty $propertyMetadata, array $expectedResult): void - { - self::assertSame($expectedResult, $this->propertySchemaChoiceRestriction->create($constraint, $propertyMetadata)); - } - - public static function createProvider(): \Generator - { - yield 'single string choice' => [new Choice(['choices' => ['a', 'b']]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), ['enum' => ['a', 'b']]]; - yield 'multi string choice' => [new Choice(['choices' => ['a', 'b'], 'multiple' => true]), (new ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), ['enum' => [1, 2]]]; - yield 'multi int choice' => [new Choice(['choices' => [1, 2], 'multiple' => true]), (new ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([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 ApiProperty())->withBuiltinTypes([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 string/int/float choice with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2]]), (new ApiProperty())->withBuiltinTypes([ - new Type(Type::BUILTIN_TYPE_STRING), - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_FLOAT), - ]), ['enum' => [1, 2, 'a', 'b', 1.1, 2.2]]]; - yield 'multi string/int/float choice with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2], 'multiple' => true]), (new ApiProperty())->withBuiltinTypes([ - new Type(Type::BUILTIN_TYPE_STRING), - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_FLOAT), - ]), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2]]]]; - yield 'multi string/int/float choice min with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2], 'multiple' => true, 'min' => 2]), (new ApiProperty())->withBuiltinTypes([ - new Type(Type::BUILTIN_TYPE_STRING), - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_FLOAT), - ]), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2]], 'minItems' => 2]]; - yield 'multi string/int/float choice max with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4], 'multiple' => true, 'max' => 4]), (new ApiProperty())->withBuiltinTypes([ - new Type(Type::BUILTIN_TYPE_STRING), - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_FLOAT), - ]), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4]], 'maxItems' => 4]]; - yield 'multi string/int/float choice min/max with union types' => [new Choice(['choices' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4], 'multiple' => true, 'min' => 2, 'max' => 4]), (new ApiProperty())->withBuiltinTypes([ - new Type(Type::BUILTIN_TYPE_STRING), - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_FLOAT), - ]), ['type' => 'array', 'items' => ['type' => ['number', 'string'], 'enum' => [1, 2, 'a', 'b', 1.1, 2.2, 3.3, 4.4]], 'minItems' => 2, 'maxItems' => 4]]; - - yield 'single choice callback' => [new Choice(['callback' => ChoiceCallback::getChoices(...)]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), ['enum' => ['a', 'b', 'c', 'd']]]; - yield 'multi choice callback' => [new Choice(['callback' => ChoiceCallback::getChoices(...), 'multiple' => true]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['a', 'b', 'c', 'd']]]]; - } -} - -final class ChoiceCallback -{ - public static function getChoices(): array - { - return ['a', 'b', 'c', 'd']; - } -} diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestrictionTest.php b/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestrictionTest.php deleted file mode 100644 index 5d42f79540e..00000000000 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestrictionTest.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * 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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaGreaterThanOrEqualRestriction; -use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; -use Symfony\Component\Validator\Constraints\Positive; -use Symfony\Component\Validator\Constraints\PositiveOrZero; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaGreaterThanOrEqualRestrictionTest extends TestCase -{ - use ProphecyTrait; - - private PropertySchemaGreaterThanOrEqualRestriction $propertySchemaGreaterThanOrEqualRestriction; - - protected function setUp(): void - { - $this->propertySchemaGreaterThanOrEqualRestriction = new PropertySchemaGreaterThanOrEqualRestriction(); - } - - #[\PHPUnit\Framework\Attributes\DataProvider('supportsProvider')] - public function testSupports(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void - { - self::assertSame($expectedResult, $this->propertySchemaGreaterThanOrEqualRestriction->supports($constraint, $propertyMetadata)); - } - - public static function supportsProvider(): \Generator - { - yield 'supported int/float with union types' => [new GreaterThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]), true]; - yield 'supported int' => [new GreaterThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), true]; - yield 'supported float' => [new GreaterThanOrEqual(['value' => 10.99]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)]), true]; - yield 'supported positive or zero' => [new PositiveOrZero(), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), true]; - yield 'not supported positive' => [new Positive(), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), false]; - yield 'not supported property path' => [new GreaterThanOrEqual(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), false]; - } - - public function testCreate(): void - { - self::assertEquals(['minimum' => 10], $this->propertySchemaGreaterThanOrEqualRestriction->create(new GreaterThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]))); - } -} diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestrictionTest.php b/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestrictionTest.php deleted file mode 100644 index f348b951bb4..00000000000 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestrictionTest.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * 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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaGreaterThanRestriction; -use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\GreaterThan; -use Symfony\Component\Validator\Constraints\Positive; -use Symfony\Component\Validator\Constraints\PositiveOrZero; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaGreaterThanRestrictionTest extends TestCase -{ - use ProphecyTrait; - - private PropertySchemaGreaterThanRestriction $propertySchemaGreaterThanRestriction; - - protected function setUp(): void - { - $this->propertySchemaGreaterThanRestriction = new PropertySchemaGreaterThanRestriction(); - } - - #[\PHPUnit\Framework\Attributes\DataProvider('supportsProvider')] - public function testSupports(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void - { - self::assertSame($expectedResult, $this->propertySchemaGreaterThanRestriction->supports($constraint, $propertyMetadata)); - } - - public static function supportsProvider(): \Generator - { - yield 'supported int/float with union types' => [new GreaterThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]), true]; - yield 'supported int' => [new GreaterThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), true]; - yield 'supported float' => [new GreaterThan(['value' => 10.99]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)]), true]; - yield 'supported positive' => [new Positive(), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), true]; - yield 'not supported positive or zero' => [new PositiveOrZero(), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), false]; - yield 'not supported property path' => [new GreaterThan(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), false]; - } - - public function testCreate(): void - { - self::assertEquals([ - 'exclusiveMinimum' => 10, - ], $this->propertySchemaGreaterThanRestriction->create(new GreaterThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]))); - } -} diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestrictionTest.php b/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestrictionTest.php deleted file mode 100644 index fcab413d95b..00000000000 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestrictionTest.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * 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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaLessThanOrEqualRestriction; -use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\LessThanOrEqual; -use Symfony\Component\Validator\Constraints\Negative; -use Symfony\Component\Validator\Constraints\NegativeOrZero; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaLessThanOrEqualRestrictionTest extends TestCase -{ - use ProphecyTrait; - - private PropertySchemaLessThanOrEqualRestriction $propertySchemaLessThanOrEqualRestriction; - - protected function setUp(): void - { - $this->propertySchemaLessThanOrEqualRestriction = new PropertySchemaLessThanOrEqualRestriction(); - } - - #[\PHPUnit\Framework\Attributes\DataProvider('supportsProvider')] - public function testSupports(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void - { - self::assertSame($expectedResult, $this->propertySchemaLessThanOrEqualRestriction->supports($constraint, $propertyMetadata)); - } - - public static function supportsProvider(): \Generator - { - yield 'supported int/float with union types' => [new LessThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]), true]; - yield 'supported int' => [new LessThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), true]; - yield 'supported float' => [new LessThanOrEqual(['value' => 10.99]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)]), true]; - yield 'supported negative or zero' => [new NegativeOrZero(), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), true]; - yield 'not supported negative' => [new Negative(), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), false]; - yield 'not supported property path' => [new LessThanOrEqual(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), false]; - } - - public function testCreate(): void - { - self::assertEquals(['maximum' => 10], $this->propertySchemaLessThanOrEqualRestriction->create(new LessThanOrEqual(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]))); - } -} diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestrictionTest.php b/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestrictionTest.php deleted file mode 100644 index ad2b942e9ee..00000000000 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestrictionTest.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * 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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaLessThanRestriction; -use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\LessThan; -use Symfony\Component\Validator\Constraints\Negative; -use Symfony\Component\Validator\Constraints\NegativeOrZero; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaLessThanRestrictionTest extends TestCase -{ - use ProphecyTrait; - - private PropertySchemaLessThanRestriction $propertySchemaLessThanRestriction; - - protected function setUp(): void - { - $this->propertySchemaLessThanRestriction = new PropertySchemaLessThanRestriction(); - } - - #[\PHPUnit\Framework\Attributes\DataProvider('supportsProvider')] - public function testSupports(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void - { - self::assertSame($expectedResult, $this->propertySchemaLessThanRestriction->supports($constraint, $propertyMetadata)); - } - - public static function supportsProvider(): \Generator - { - yield 'supported int/float with union types' => [new LessThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]), true]; - yield 'supported int' => [new LessThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), true]; - yield 'supported float' => [new LessThan(['value' => 10.99]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)]), true]; - yield 'supported negative' => [new Negative(), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), true]; - yield 'not supported negative or zero' => [new NegativeOrZero(), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), false]; - yield 'not supported property path' => [new LessThan(['propertyPath' => 'greaterThanMe']), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), false]; - } - - public function testCreate(): void - { - self::assertEquals([ - 'exclusiveMaximum' => 10, - ], $this->propertySchemaLessThanRestriction->create(new LessThan(['value' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]))); - } -} diff --git a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestrictionTest.php b/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestrictionTest.php deleted file mode 100644 index 8d56c342c1e..00000000000 --- a/tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestrictionTest.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * 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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRangeRestriction; -use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\Length; -use Symfony\Component\Validator\Constraints\Range; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaRangeRestrictionTest extends TestCase -{ - use ProphecyTrait; - - private PropertySchemaRangeRestriction $propertySchemaRangeRestriction; - - protected function setUp(): void - { - $this->propertySchemaRangeRestriction = new PropertySchemaRangeRestriction(); - } - - #[\PHPUnit\Framework\Attributes\DataProvider('supportsProvider')] - public function testSupports(Constraint $constraint, ApiProperty $propertyMetadata, bool $expectedResult): void - { - self::assertSame($expectedResult, $this->propertySchemaRangeRestriction->supports($constraint, $propertyMetadata)); - } - - public static function supportsProvider(): \Generator - { - yield 'supported int/float with union types' => [new Range(['min' => 1, 'max' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)]), true]; - yield 'supported int' => [new Range(['min' => 1, 'max' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), true]; - yield 'supported float' => [new Range(['min' => 1, 'max' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)]), true]; - - yield 'not supported constraint' => [new Length(['min' => 1]), new ApiProperty(), false]; - yield 'not supported type' => [new Range(['min' => 1]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)]), false]; - } - - #[\PHPUnit\Framework\Attributes\DataProvider('createProvider')] - public function testCreate(Constraint $constraint, ApiProperty $propertyMetadata, array $expectedResult): void - { - self::assertSame($expectedResult, $this->propertySchemaRangeRestriction->create($constraint, $propertyMetadata)); - } - - public static function createProvider(): \Generator - { - yield 'int min' => [new Range(['min' => 1]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), ['minimum' => 1]]; - yield 'int max' => [new Range(['max' => 10]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)]), ['maximum' => 10]]; - - yield 'float min' => [new Range(['min' => 1.5]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)]), ['minimum' => 1.5]]; - yield 'float max' => [new Range(['max' => 10.5]), (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)]), ['maximum' => 10.5]]; - } -}