-
-
Notifications
You must be signed in to change notification settings - Fork 848
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(jsonapi): add missing "included" schema parts (#6277)
* fix(jsonapi): add missing "included" schema parts * fix(test): test correct format * chore(jsonschema): refactor definition name logic * remove useless comment * remove empty line * add on invalid --------- Co-authored-by: Antoine Bluchet <soyuka@users.noreply.github.com>
- Loading branch information
1 parent
93f8b5f
commit 678eb4f
Showing
15 changed files
with
444 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the API Platform project. | ||
* | ||
* (c) Kévin Dunglas <dunglas@gmail.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ApiPlatform\JsonSchema; | ||
|
||
use ApiPlatform\Metadata\Operation; | ||
use ApiPlatform\Metadata\Util\ResourceClassInfoTrait; | ||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; | ||
|
||
final class DefinitionNameFactory implements DefinitionNameFactoryInterface | ||
{ | ||
use ResourceClassInfoTrait; | ||
|
||
public function __construct(private ?array $distinctFormats) | ||
{ | ||
} | ||
|
||
public function create(string $className, string $format = 'json', ?string $inputOrOutputClass = null, ?Operation $operation = null, array $serializerContext = []): string | ||
{ | ||
if ($operation) { | ||
$prefix = $operation->getShortName(); | ||
} | ||
|
||
if (!isset($prefix)) { | ||
$prefix = (new \ReflectionClass($className))->getShortName(); | ||
} | ||
|
||
if (null !== $inputOrOutputClass && $className !== $inputOrOutputClass) { | ||
$parts = explode('\\', $inputOrOutputClass); | ||
$shortName = end($parts); | ||
$prefix .= '.'.$shortName; | ||
} | ||
|
||
if ('json' !== $format && ($this->distinctFormats[$format] ?? false)) { | ||
// JSON is the default, and so isn't included in the definition name | ||
$prefix .= '.'.$format; | ||
} | ||
|
||
$definitionName = $serializerContext[SchemaFactory::OPENAPI_DEFINITION_NAME] ?? null; | ||
if ($definitionName) { | ||
$name = sprintf('%s-%s', $prefix, $definitionName); | ||
} else { | ||
$groups = (array) ($serializerContext[AbstractNormalizer::GROUPS] ?? []); | ||
$name = $groups ? sprintf('%s-%s', $prefix, implode('_', $groups)) : $prefix; | ||
} | ||
|
||
return $this->encodeDefinitionName($name); | ||
} | ||
|
||
private function encodeDefinitionName(string $name): string | ||
{ | ||
return preg_replace('/[^a-zA-Z0-9.\-_]/', '.', $name); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?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\JsonSchema; | ||
|
||
use ApiPlatform\Metadata\Operation; | ||
|
||
/** | ||
* Factory for creating definition names for resources in a JSON Schema document. | ||
* | ||
* @author Gwendolen Lynch <gwendolen.lynch@gmail.com> | ||
*/ | ||
interface DefinitionNameFactoryInterface | ||
{ | ||
/** | ||
* Creates a resource definition name. | ||
* | ||
* @param class-string $className | ||
* | ||
* @return string the definition name | ||
*/ | ||
public function create(string $className, string $format = 'json', ?string $inputOrOutputClass = null, ?Operation $operation = null, array $serializerContext = []): string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
<?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\JsonSchema; | ||
|
||
use ApiPlatform\Metadata\CollectionOperationInterface; | ||
use ApiPlatform\Metadata\Exception\OperationNotFoundException; | ||
use ApiPlatform\Metadata\HttpOperation; | ||
use ApiPlatform\Metadata\Operation; | ||
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; | ||
use ApiPlatform\Metadata\Util\ResourceClassInfoTrait; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
trait ResourceMetadataTrait | ||
{ | ||
use ResourceClassInfoTrait; | ||
|
||
private function findOutputClass(string $className, string $type, Operation $operation, ?array $serializerContext): ?string | ||
{ | ||
$inputOrOutput = ['class' => $className]; | ||
$inputOrOutput = Schema::TYPE_OUTPUT === $type ? ($operation->getOutput() ?? $inputOrOutput) : ($operation->getInput() ?? $inputOrOutput); | ||
$forceSubschema = $serializerContext[SchemaFactory::FORCE_SUBSCHEMA] ?? false; | ||
|
||
return $forceSubschema ? ($inputOrOutput['class'] ?? $inputOrOutput->class ?? $operation->getClass()) : ($inputOrOutput['class'] ?? $inputOrOutput->class ?? null); | ||
} | ||
|
||
private function findOperation(string $className, string $type, ?Operation $operation, ?array $serializerContext): Operation | ||
{ | ||
if (null === $operation) { | ||
if (null === $this->resourceMetadataFactory) { | ||
return new HttpOperation(); | ||
} | ||
$resourceMetadataCollection = $this->resourceMetadataFactory->create($className); | ||
|
||
try { | ||
$operation = $resourceMetadataCollection->getOperation(); | ||
} catch (OperationNotFoundException $e) { | ||
$operation = new HttpOperation(); | ||
} | ||
$forceSubschema = $serializerContext[SchemaFactory::FORCE_SUBSCHEMA] ?? false; | ||
if ($operation->getShortName() === $this->getShortClassName($className) && $forceSubschema) { | ||
$operation = new HttpOperation(); | ||
} | ||
|
||
return $this->findOperationForType($resourceMetadataCollection, $type, $operation); | ||
} | ||
|
||
// The best here is to use an Operation when calling `buildSchema`, we try to do a smart guess otherwise | ||
if ($this->resourceMetadataFactory && !$operation->getClass()) { | ||
$resourceMetadataCollection = $this->resourceMetadataFactory->create($className); | ||
|
||
if ($operation->getName()) { | ||
return $resourceMetadataCollection->getOperation($operation->getName()); | ||
} | ||
|
||
return $this->findOperationForType($resourceMetadataCollection, $type, $operation); | ||
} | ||
|
||
return $operation; | ||
} | ||
|
||
private function findOperationForType(ResourceMetadataCollection $resourceMetadataCollection, string $type, Operation $operation): Operation | ||
{ | ||
// Find the operation and use the first one that matches criterias | ||
foreach ($resourceMetadataCollection as $resourceMetadata) { | ||
foreach ($resourceMetadata->getOperations() ?? [] as $op) { | ||
if ($operation instanceof CollectionOperationInterface && $op instanceof CollectionOperationInterface) { | ||
$operation = $op; | ||
break 2; | ||
} | ||
|
||
if (Schema::TYPE_INPUT === $type && \in_array($op->getMethod(), ['POST', 'PATCH', 'PUT'], true)) { | ||
$operation = $op; | ||
break 2; | ||
} | ||
} | ||
} | ||
|
||
return $operation; | ||
} | ||
|
||
private function getSerializerContext(Operation $operation, string $type = Schema::TYPE_OUTPUT): array | ||
{ | ||
return Schema::TYPE_OUTPUT === $type ? ($operation->getNormalizationContext() ?? []) : ($operation->getDenormalizationContext() ?? []); | ||
} | ||
|
||
private function getShortClassName(string $fullyQualifiedName): string | ||
{ | ||
$parts = explode('\\', $fullyQualifiedName); | ||
return end($parts); | ||
} | ||
} |
Oops, something went wrong.