Skip to content

Commit

Permalink
feat(openapi): document parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Mar 27, 2024
1 parent 91e76e7 commit 0b724d9
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 10 deletions.
78 changes: 70 additions & 8 deletions src/OpenApi/Factory/OpenApiFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use ApiPlatform\JsonSchema\TypeFactoryInterface;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\Metadata\HeaderParameterInterface;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
Expand Down Expand Up @@ -274,29 +275,57 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection
}

// Set up parameters
$openapiParameters = $openapiOperation->getParameters();
foreach ($operation->getUriVariables() ?? [] as $parameterName => $uriVariable) {
if ($uriVariable->getExpandedValue() ?? false) {
continue;
}

$parameter = new Parameter($parameterName, 'path', "$resourceShortName identifier", true, false, false, ['type' => 'string']);
if ($this->hasParameter($openapiOperation, $parameter)) {
$parameter = new Parameter($parameterName, 'path', $uriVariable->getDescription() ?? "$resourceShortName identifier", $uriVariable->getRequired() ?? true, false, false, $uriVariable->getSchema() ?? ['type' => 'string']);

if ($linkParameter = $uriVariable->getOpenApi()) {
$parameter = $this->mergeParameter($parameter, $linkParameter);
}

if ([$i, $operationParameter] = $this->hasParameter($openapiOperation, $parameter)) {
$openapiParameters[$i] = $this->mergeParameter($parameter, $operationParameter);
continue;
}

$openapiOperation = $openapiOperation->withParameter($parameter);
$openapiParameters[] = $parameter;
}

$openapiOperation = $openapiOperation->withParameters($openapiParameters);

if ($operation instanceof CollectionOperationInterface && 'POST' !== $method) {
foreach (array_merge($this->getPaginationParameters($operation), $this->getFiltersParameters($operation)) as $parameter) {
if ($this->hasParameter($openapiOperation, $parameter)) {
if ($operationParameter = $this->hasParameter($openapiOperation, $parameter)) {
continue;
}

$openapiOperation = $openapiOperation->withParameter($parameter);
}
}

$openapiParameters = $openapiOperation->getParameters();
foreach ($operation->getParameters() ?? [] as $key => $p) {
$in = $p instanceof HeaderParameterInterface ? 'header' : 'query';
$parameter = new Parameter($key, $in, $p->getDescription() ?? "$resourceShortName $key", $p->getRequired() ?? false, false, false, $p->getSchema() ?? ['type' => 'string']);

if ($linkParameter = $p->getOpenApi()) {
$parameter = $this->mergeParameter($parameter, $linkParameter);
}

if ([$i, $operationParameter] = $this->hasParameter($openapiOperation, $parameter)) {
$openapiParameters[$i] = $this->mergeParameter($parameter, $operationParameter);
continue;
}

$openapiParameters[] = $parameter;
}

$openapiOperation = $openapiOperation->withParameters($openapiParameters);

$existingResponses = $openapiOperation?->getResponses() ?: [];
$overrideResponses = $operation->getExtraProperties()[self::OVERRIDE_OPENAPI_RESPONSES] ?? $this->openApiOptions->getOverrideResponses();
if ($overrideResponses || !$existingResponses) {
Expand Down Expand Up @@ -712,14 +741,47 @@ private function appendSchemaDefinitions(\ArrayObject $schemas, \ArrayObject $de
}
}

private function hasParameter(Model\Operation $operation, Parameter $parameter): bool
/**
* @return array{0: int, 1: Parameter}|null
*/
private function hasParameter(Model\Operation $operation, Parameter $parameter): ?array
{
foreach ($operation->getParameters() as $existingParameter) {
foreach ($operation->getParameters() as $key => $existingParameter) {
if ($existingParameter->getName() === $parameter->getName() && $existingParameter->getIn() === $parameter->getIn()) {
return true;
return [$key, $existingParameter];
}
}

return null;
}

private function mergeParameter(Parameter $actual, Parameter $defined): Parameter
{
foreach ([
'name',
'in',
'description',
'required',
'deprecated',
'allowEmptyValue',
'style',
'explode',
'allowReserved',
'example',
] as $method) {
$newValue = $defined->{"get$method"}();
if (null !== $newValue && $actual->{"get$method"}() !== $newValue) {
$actual = $actual->{"with$method"}($newValue);
}
}

foreach (['examples', 'content', 'schema'] as $method) {
$newValue = $defined->{"get$method"}();
if ($newValue && \count($newValue) > 0 && $actual->{"get$method"}() !== $newValue) {
$actual = $actual->{"with$method"}($newValue);
}
}

return false;
return $actual;
}
}
29 changes: 27 additions & 2 deletions src/OpenApi/Tests/Factory/OpenApiFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\HeaderParameter;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\NotExposed;
Expand Down Expand Up @@ -57,6 +58,7 @@
use ApiPlatform\OpenApi\Tests\Fixtures\DummyFilter;
use ApiPlatform\OpenApi\Tests\Fixtures\OutputDto;
use ApiPlatform\State\Pagination\PaginationOptions;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\WithParameter;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
Expand Down Expand Up @@ -252,11 +254,20 @@ public function testInvoke(): void
])
);

$baseOperation = (new HttpOperation())->withTypes(['http://schema.example.com/Dummy'])->withInputFormats(self::OPERATION_FORMATS['input_formats'])->withOutputFormats(self::OPERATION_FORMATS['output_formats'])->withClass(Dummy::class)->withShortName('Parameter')->withDescription('This is a dummy');
$parameterResource = (new ApiResource())->withOperations(new Operations([
'uriVariableSchema' => (new Get(uriTemplate: '/uri_variable_uuid', uriVariables: ['id' => new Link(schema: ['type' => 'string', 'format' => 'uuid'], description: 'hello', required: true, openApi: new Parameter('id', 'path', allowEmptyValue: true))]))->withOperation($baseOperation),
'parameters' => (new Put(uriTemplate: '/parameters', parameters: [
'foo' => new HeaderParameter(description: 'hi', schema: ['type' => 'string', 'format' => 'uuid']),
]))->withOperation($baseOperation),
]));

$resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class);
$resourceNameCollectionFactoryProphecy->create()->shouldBeCalled()->willReturn(new ResourceNameCollection([Dummy::class]));
$resourceNameCollectionFactoryProphecy->create()->shouldBeCalled()->willReturn(new ResourceNameCollection([Dummy::class, WithParameter::class]));

$resourceCollectionMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
$resourceCollectionMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new ResourceMetadataCollection(Dummy::class, [$dummyResource, $dummyResourceWebhook]));
$resourceCollectionMetadataFactoryProphecy->create(WithParameter::class)->shouldBeCalled()->willReturn(new ResourceMetadataCollection(WithParameter::class, [$parameterResource]));

$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
$propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name', 'description', 'dummyDate', 'enum']));
Expand Down Expand Up @@ -504,7 +515,12 @@ public function testInvoke(): void
$components = $openApi->getComponents();
$this->assertInstanceOf(Components::class, $components);

$this->assertEquals($components->getSchemas(), new \ArrayObject(['Dummy' => $dummySchema->getDefinitions(), 'Dummy.OutputDto' => $dummySchema->getDefinitions()]));
$parameterSchema = $dummySchema->getDefinitions();
$this->assertEquals($components->getSchemas(), new \ArrayObject([
'Dummy' => $dummySchema->getDefinitions(),
'Dummy.OutputDto' => $dummySchema->getDefinitions(),
'Parameter' => $parameterSchema,
]));

$this->assertEquals($components->getSecuritySchemes(), new \ArrayObject([
'oauth' => new SecurityScheme('oauth2', 'OAuth 2.0 authorization code Grant', null, null, null, null, new OAuthFlows(null, null, null, new OAuthFlow('/oauth/v2/auth', '/oauth/v2/token', '/oauth/v2/refresh', new \ArrayObject(['scope param'])))),
Expand Down Expand Up @@ -970,5 +986,14 @@ public function testInvoke(): void
[],
null
), $emptyRequestBodyPath->getPost());

$parameter = $paths->getPath('/uri_variable_uuid')->getGet()->getParameters()[0];
$this->assertTrue($parameter->getAllowEmptyValue());
$this->assertEquals(['type' => 'string', 'format' => 'uuid'], $parameter->getSchema());

$parameter = $paths->getPath('/parameters')->getPut()->getParameters()[0];
$this->assertEquals(['type' => 'string', 'format' => 'uuid'], $parameter->getSchema());
$this->assertEquals('header', $parameter->getIn());
$this->assertEquals('hi', $parameter->getDescription());
}
}

0 comments on commit 0b724d9

Please sign in to comment.