Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ jobs:
composer global config allow-plugins.soyuka/pmu true --no-interaction
composer global link .
composer require --dev doctrine/mongodb-odm-bundle
composer require --dev --no-interaction "symfony/json-schema:8.2.x-dev"
- name: Cache PHPStan results
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
Expand Down Expand Up @@ -990,6 +991,57 @@ jobs:
- name: Run PHPUnit tests
run: vendor/bin/phpunit

phpunit-json-schema-generator:
name: PHPUnit (PHP ${{ matrix.php }}) (Symfony JsonSchema generator)
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
matrix:
php:
- '8.5'
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ matrix.php }}
tools: pecl, composer:2.9.8
extensions: intl, bcmath, curl, openssl, mbstring
coverage: none
ini-values: memory_limit=-1
- name: Get composer cache directory
id: composercache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-json-schema-generator-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-json-schema-generator-
- name: Update project dependencies
run: |
composer global require "soyuka/pmu:$PMU_VERSION"
composer global config allow-plugins.soyuka/pmu true --no-interaction
composer global link .
composer require --dev --no-interaction "symfony/json-schema:8.2.x-dev"
- name: Remove cache
run: rm -Rf tests/Fixtures/app/var/cache/*
- name: Run golden schema tests (generator disabled)
run: vendor/bin/phpunit tests/Functional/JsonSchema/GoldenSchemaTest.php
- name: Clear test app cache
run: rm -Rf tests/Fixtures/app/var/cache/test
- name: Run JSON Schema tests (generator enabled)
env:
JSON_SCHEMA_GENERATOR: '1'
run: |
vendor/bin/phpunit tests/Functional/JsonSchema/JsonSchemaTest.php
vendor/bin/phpunit tests/Functional/JsonSchema/JsonLdJsonSchemaTest.php
vendor/bin/phpunit tests/Functional/OpenApiTest.php
- name: Run container extension tests
run: vendor/bin/phpunit src/Symfony/Tests/Bundle/DependencyInjection/ApiPlatformExtensionTest.php --filter JsonSchema

phpunit-symfony-lowest:
name: PHPUnit (PHP ${{ matrix.php }}) (Symfony lowest)
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@
"symfony/uid": "To support Symfony UUID/ULID identifiers.",
"symfony/messenger": "To support messenger integration.",
"symfony/web-profiler-bundle": "To use the data collector.",
"symfony/json-schema": "To generate JSON Schemas with the Symfony JsonSchema component (opt-in, api_platform.json_schema.generator).",
"symfony/json-streamer": "To use the JSON Streamer component.",
"webonyx/graphql-php": "To support GraphQL."
},
Expand Down
51 changes: 51 additions & 0 deletions src/Hydra/JsonSchema/ItemDefinitionProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Hydra\JsonSchema;

use ApiPlatform\JsonSchema\Generator\ApiPlatformDefinitionPolicy;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\Util\ResourceClassInfoTrait;
use Symfony\Component\JsonSchema\Configuration;
use Symfony\Component\JsonSchema\DefinitionParent;
use Symfony\Component\JsonSchema\DefinitionProcessor\DefinitionProcessorInterface;
use Symfony\Component\JsonSchema\SchemaDocument;

final class ItemDefinitionProcessor implements DefinitionProcessorInterface
{
use ResourceClassInfoTrait;

public function __construct(
ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory,
private readonly ApiPlatformDefinitionPolicy $definitionPolicy,
) {
$this->resourceMetadataFactory = $resourceMetadataFactory;
}

public function process(array $definition, string $class, Configuration $config, SchemaDocument $document, ?DefinitionParent $parent): array
{
if (null === $parent || 'jsonld' !== $config->format || !$this->isResourceClass($class)) {
return $definition;
}

$baseName = $this->definitionPolicy->resolveGenId($parent) ? SchemaFactory::ITEM_BASE_SCHEMA_NAME : SchemaFactory::ITEM_WITHOUT_ID_BASE_SCHEMA_NAME;
$item = ['allOf' => [['$ref' => $config->dialect->refPath.$baseName], $definition]];

if (isset($definition['description'])) {
$item['description'] = $definition['description'];
unset($item['allOf'][1]['description']);
}

return $item;
}
}
22 changes: 18 additions & 4 deletions src/Hydra/JsonSchema/SchemaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareI
use ResourceMetadataTrait;
use SchemaUriPrefixTrait;

private const ITEM_BASE_SCHEMA_NAME = 'HydraItemBaseSchema';
private const ITEM_WITHOUT_ID_BASE_SCHEMA_NAME = 'HydraItemBaseSchemaWithoutId';
public const ITEM_BASE_SCHEMA_NAME = 'HydraItemBaseSchema';
public const ITEM_WITHOUT_ID_BASE_SCHEMA_NAME = 'HydraItemBaseSchemaWithoutId';
private const COLLECTION_BASE_SCHEMA_NAME_NO_PAGINATION = 'HydraCollectionBaseSchemaNoPagination';
private const COLLECTION_BASE_SCHEMA_NAME = 'HydraCollectionBaseSchema';

Expand Down Expand Up @@ -130,6 +130,7 @@ public function buildSchema(string $className, string $format = 'jsonld', string
$definitions = $schema->getDefinitions();
$prefix = $this->getSchemaUriPrefix($schema->getVersion());
$collectionKey = $schema->getItemsDefinitionKey();
$this->addReferencedItemBaseSchemas($definitions, $prefix);

if (!$collectionKey) {
$definitionName = $schema->getRootDefinitionKey() ?? $this->definitionNameFactory->create($className, $format, $inputOrOutputClass, $operation, $serializerContext);
Expand Down Expand Up @@ -232,9 +233,8 @@ public function buildSchema(string $className, string $format = 'jsonld', string
];
}

$definitionName = $this->definitionNameFactory->create($className, $format, $inputOrOutputClass, $operation, $serializerContext + ['schema_type' => $type]);
$schema['type'] = 'object';
$schema['description'] = "$definitionName collection.";
$schema['description'] = "$collectionKey collection.";
$schema['allOf'] = [
['$ref' => $prefix.(false === $operation->getPaginationEnabled() ? self::COLLECTION_BASE_SCHEMA_NAME_NO_PAGINATION : self::COLLECTION_BASE_SCHEMA_NAME)],
[
Expand Down Expand Up @@ -267,6 +267,20 @@ public function setSchemaFactory(SchemaFactoryInterface $schemaFactory): void
}
}

private function addReferencedItemBaseSchemas(\ArrayObject $definitions, string $prefix): void
{
$references = [];
foreach ($definitions as $definition) {
$references[$definition['allOf'][0]['$ref'] ?? ''] = true;
}

foreach ([self::ITEM_BASE_SCHEMA_NAME => self::ITEM_BASE_SCHEMA_WITH_ID, self::ITEM_WITHOUT_ID_BASE_SCHEMA_NAME => self::ITEM_BASE_SCHEMA_WITHOUT_ID] as $name => $baseSchema) {
if (isset($references[$prefix.$name]) && !isset($definitions[$name])) {
$definitions[$name] = $baseSchema;
}
}
}

private function decorateItemDefinition(string $definitionName, \ArrayObject $definitions, string $prefix, string $type, ?array $serializerContext): void
{
if (!isset($definitions[$definitionName])) {
Expand Down
2 changes: 1 addition & 1 deletion src/JsonApi/JsonSchema/SchemaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public function buildSchema(string $className, string $format = 'jsonapi', strin
// That is done by query parameter. @see https://jsonapi.org/format/#fetching-includes
$jsonApiSerializerContext = $serializerContext;
if (true === ($serializerContext[self::DISABLE_JSON_SCHEMA_SERIALIZER_GROUPS] ?? true) && $inputOrOutputClass === $className) {
unset($jsonApiSerializerContext['groups']);
$jsonApiSerializerContext['groups'] = null;
}

$schema = $this->schemaFactory->buildSchema($className, 'json', $type, $operation, $schema, $jsonApiSerializerContext, $forceCollection);
Expand Down
Loading
Loading