Skip to content

Commit 0b724d9

Browse files
committed
feat(openapi): document parameter
1 parent 91e76e7 commit 0b724d9

File tree

2 files changed

+97
-10
lines changed

2 files changed

+97
-10
lines changed

src/OpenApi/Factory/OpenApiFactory.php

+70-8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use ApiPlatform\JsonSchema\TypeFactoryInterface;
2121
use ApiPlatform\Metadata\ApiResource;
2222
use ApiPlatform\Metadata\CollectionOperationInterface;
23+
use ApiPlatform\Metadata\HeaderParameterInterface;
2324
use ApiPlatform\Metadata\HttpOperation;
2425
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2526
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
@@ -274,29 +275,57 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection
274275
}
275276

276277
// Set up parameters
278+
$openapiParameters = $openapiOperation->getParameters();
277279
foreach ($operation->getUriVariables() ?? [] as $parameterName => $uriVariable) {
278280
if ($uriVariable->getExpandedValue() ?? false) {
279281
continue;
280282
}
281283

282-
$parameter = new Parameter($parameterName, 'path', "$resourceShortName identifier", true, false, false, ['type' => 'string']);
283-
if ($this->hasParameter($openapiOperation, $parameter)) {
284+
$parameter = new Parameter($parameterName, 'path', $uriVariable->getDescription() ?? "$resourceShortName identifier", $uriVariable->getRequired() ?? true, false, false, $uriVariable->getSchema() ?? ['type' => 'string']);
285+
286+
if ($linkParameter = $uriVariable->getOpenApi()) {
287+
$parameter = $this->mergeParameter($parameter, $linkParameter);
288+
}
289+
290+
if ([$i, $operationParameter] = $this->hasParameter($openapiOperation, $parameter)) {
291+
$openapiParameters[$i] = $this->mergeParameter($parameter, $operationParameter);
284292
continue;
285293
}
286294

287-
$openapiOperation = $openapiOperation->withParameter($parameter);
295+
$openapiParameters[] = $parameter;
288296
}
289297

298+
$openapiOperation = $openapiOperation->withParameters($openapiParameters);
299+
290300
if ($operation instanceof CollectionOperationInterface && 'POST' !== $method) {
291301
foreach (array_merge($this->getPaginationParameters($operation), $this->getFiltersParameters($operation)) as $parameter) {
292-
if ($this->hasParameter($openapiOperation, $parameter)) {
302+
if ($operationParameter = $this->hasParameter($openapiOperation, $parameter)) {
293303
continue;
294304
}
295305

296306
$openapiOperation = $openapiOperation->withParameter($parameter);
297307
}
298308
}
299309

310+
$openapiParameters = $openapiOperation->getParameters();
311+
foreach ($operation->getParameters() ?? [] as $key => $p) {
312+
$in = $p instanceof HeaderParameterInterface ? 'header' : 'query';
313+
$parameter = new Parameter($key, $in, $p->getDescription() ?? "$resourceShortName $key", $p->getRequired() ?? false, false, false, $p->getSchema() ?? ['type' => 'string']);
314+
315+
if ($linkParameter = $p->getOpenApi()) {
316+
$parameter = $this->mergeParameter($parameter, $linkParameter);
317+
}
318+
319+
if ([$i, $operationParameter] = $this->hasParameter($openapiOperation, $parameter)) {
320+
$openapiParameters[$i] = $this->mergeParameter($parameter, $operationParameter);
321+
continue;
322+
}
323+
324+
$openapiParameters[] = $parameter;
325+
}
326+
327+
$openapiOperation = $openapiOperation->withParameters($openapiParameters);
328+
300329
$existingResponses = $openapiOperation?->getResponses() ?: [];
301330
$overrideResponses = $operation->getExtraProperties()[self::OVERRIDE_OPENAPI_RESPONSES] ?? $this->openApiOptions->getOverrideResponses();
302331
if ($overrideResponses || !$existingResponses) {
@@ -712,14 +741,47 @@ private function appendSchemaDefinitions(\ArrayObject $schemas, \ArrayObject $de
712741
}
713742
}
714743

715-
private function hasParameter(Model\Operation $operation, Parameter $parameter): bool
744+
/**
745+
* @return array{0: int, 1: Parameter}|null
746+
*/
747+
private function hasParameter(Model\Operation $operation, Parameter $parameter): ?array
716748
{
717-
foreach ($operation->getParameters() as $existingParameter) {
749+
foreach ($operation->getParameters() as $key => $existingParameter) {
718750
if ($existingParameter->getName() === $parameter->getName() && $existingParameter->getIn() === $parameter->getIn()) {
719-
return true;
751+
return [$key, $existingParameter];
752+
}
753+
}
754+
755+
return null;
756+
}
757+
758+
private function mergeParameter(Parameter $actual, Parameter $defined): Parameter
759+
{
760+
foreach ([
761+
'name',
762+
'in',
763+
'description',
764+
'required',
765+
'deprecated',
766+
'allowEmptyValue',
767+
'style',
768+
'explode',
769+
'allowReserved',
770+
'example',
771+
] as $method) {
772+
$newValue = $defined->{"get$method"}();
773+
if (null !== $newValue && $actual->{"get$method"}() !== $newValue) {
774+
$actual = $actual->{"with$method"}($newValue);
775+
}
776+
}
777+
778+
foreach (['examples', 'content', 'schema'] as $method) {
779+
$newValue = $defined->{"get$method"}();
780+
if ($newValue && \count($newValue) > 0 && $actual->{"get$method"}() !== $newValue) {
781+
$actual = $actual->{"with$method"}($newValue);
720782
}
721783
}
722784

723-
return false;
785+
return $actual;
724786
}
725787
}

src/OpenApi/Tests/Factory/OpenApiFactoryTest.php

+27-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use ApiPlatform\Metadata\Delete;
2222
use ApiPlatform\Metadata\Get;
2323
use ApiPlatform\Metadata\GetCollection;
24+
use ApiPlatform\Metadata\HeaderParameter;
2425
use ApiPlatform\Metadata\HttpOperation;
2526
use ApiPlatform\Metadata\Link;
2627
use ApiPlatform\Metadata\NotExposed;
@@ -57,6 +58,7 @@
5758
use ApiPlatform\OpenApi\Tests\Fixtures\DummyFilter;
5859
use ApiPlatform\OpenApi\Tests\Fixtures\OutputDto;
5960
use ApiPlatform\State\Pagination\PaginationOptions;
61+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\WithParameter;
6062
use PHPUnit\Framework\TestCase;
6163
use Prophecy\Argument;
6264
use Prophecy\PhpUnit\ProphecyTrait;
@@ -252,11 +254,20 @@ public function testInvoke(): void
252254
])
253255
);
254256

257+
$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');
258+
$parameterResource = (new ApiResource())->withOperations(new Operations([
259+
'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),
260+
'parameters' => (new Put(uriTemplate: '/parameters', parameters: [
261+
'foo' => new HeaderParameter(description: 'hi', schema: ['type' => 'string', 'format' => 'uuid']),
262+
]))->withOperation($baseOperation),
263+
]));
264+
255265
$resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class);
256-
$resourceNameCollectionFactoryProphecy->create()->shouldBeCalled()->willReturn(new ResourceNameCollection([Dummy::class]));
266+
$resourceNameCollectionFactoryProphecy->create()->shouldBeCalled()->willReturn(new ResourceNameCollection([Dummy::class, WithParameter::class]));
257267

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

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

507-
$this->assertEquals($components->getSchemas(), new \ArrayObject(['Dummy' => $dummySchema->getDefinitions(), 'Dummy.OutputDto' => $dummySchema->getDefinitions()]));
518+
$parameterSchema = $dummySchema->getDefinitions();
519+
$this->assertEquals($components->getSchemas(), new \ArrayObject([
520+
'Dummy' => $dummySchema->getDefinitions(),
521+
'Dummy.OutputDto' => $dummySchema->getDefinitions(),
522+
'Parameter' => $parameterSchema,
523+
]));
508524

509525
$this->assertEquals($components->getSecuritySchemes(), new \ArrayObject([
510526
'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'])))),
@@ -970,5 +986,14 @@ public function testInvoke(): void
970986
[],
971987
null
972988
), $emptyRequestBodyPath->getPost());
989+
990+
$parameter = $paths->getPath('/uri_variable_uuid')->getGet()->getParameters()[0];
991+
$this->assertTrue($parameter->getAllowEmptyValue());
992+
$this->assertEquals(['type' => 'string', 'format' => 'uuid'], $parameter->getSchema());
993+
994+
$parameter = $paths->getPath('/parameters')->getPut()->getParameters()[0];
995+
$this->assertEquals(['type' => 'string', 'format' => 'uuid'], $parameter->getSchema());
996+
$this->assertEquals('header', $parameter->getIn());
997+
$this->assertEquals('hi', $parameter->getDescription());
973998
}
974999
}

0 commit comments

Comments
 (0)