Skip to content

Commit

Permalink
Added documenting of optional params (by adding a comment about it is…
Browse files Browse the repository at this point in the history
… being optional) (#448)

* handling optional params in opinionated way

* added optional param test

* handling the case when param name is in different case

* added extension property marking optional properties as optional

* update test

* Fix styling

---------

Co-authored-by: romalytvynenko <[email protected]>
  • Loading branch information
romalytvynenko and romalytvynenko authored Jul 10, 2024
1 parent 71199f3 commit 6821dd2
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 5 deletions.
4 changes: 3 additions & 1 deletion src/Support/Generator/Parameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
class Parameter
{
use WithAttributes;
use WithExtensions;

public string $name;

Expand Down Expand Up @@ -75,7 +76,8 @@ public function toArray(): array
$this->example instanceof MissingExample ? [] : ['example' => $this->example],
! is_null($this->explode) ? [
'explode' => $this->explode,
] : []
] : [],
$this->extensionPropertiesToArray(),
);
}

Expand Down
47 changes: 47 additions & 0 deletions src/Support/Generator/WithExtensions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Dedoc\Scramble\Support\Generator;

trait WithExtensions
{
/** @var array<string, mixed> */
private $extensions = [];

public function setExtensionProperty(string $key, mixed $value): void
{
$this->extensions[$key] = $value;
}

public function hasExtensionProperty(string $key): bool
{
return array_key_exists($key, $this->extensions);
}

public function getExtensionProperty(string $key): mixed
{
if ($this->hasExtensionProperty($key)) {
return $this->extensions[$key];
}

return null;
}

public function extensionProperties()
{
return $this->extensions;
}

public function mergeExtensionProperties($extensionsProperties)
{
$this->extensions = array_merge($this->extensions, $extensionsProperties);

return $this;
}

public function extensionPropertiesToArray(): array
{
return collect($this->extensions)
->mapWithKeys(fn ($v, $k) => ["x-$k" => $v])
->all();
}
}
27 changes: 23 additions & 4 deletions src/Support/OperationExtensions/RequestEssentialsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,14 @@ public function handle(Operation $operation, RouteInfo $routeInfo)
]);
};

$uriWithoutOptionalParams = Str::replace('?}', '}', $routeInfo->route->uri);

$operation
->setMethod(strtolower($routeInfo->route->methods()[0]))
->setPath(Str::replace(
collect($pathAliases)->keys()->map(fn ($k) => '{'.$k.'}')->all(),
collect($pathAliases)->values()->map(fn ($v) => '{'.$v.'}')->all(),
$routeInfo->route->uri,
$uriWithoutOptionalParams,
))
->setTags($tagResolver($routeInfo, $operation))
->servers($this->getAlternativeServers($routeInfo->route))
Expand Down Expand Up @@ -183,7 +185,7 @@ private function getRoutePathParameters(RouteInfo $routeInfo)
$paramName = $aliases[$paramName];

$description = $phpDocTypehintParam[$paramName]?->description ?? '';
[$schemaType, $description] = $this->getParameterType(
[$schemaType, $description, $isOptional] = $this->getParameterType(
$paramName,
$description,
$routeInfo,
Expand All @@ -192,9 +194,15 @@ private function getRoutePathParameters(RouteInfo $routeInfo)
$reflectionParamsByKeys[$paramName] ?? null,
);

return Parameter::make($paramName, 'path')
$param = Parameter::make($paramName, 'path')
->description($description)
->setSchema(Schema::fromType($schemaType));

if ($isOptional) {
$param->setExtensionProperty('optional', true);
}

return $param;
}, array_values(array_diff($route->parameterNames(), $this->getParametersFromString($route->getDomain()))));

return [$params, $aliases];
Expand Down Expand Up @@ -235,7 +243,18 @@ private function getParameterType(string $paramName, string $description, RouteI
$schemaType = (new StringType)->mergeAttributes($schemaType->attributes());
}

return [$schemaType, $description ?? ''];
if ($reflectionParam?->isDefaultValueAvailable()) {
$schemaType->default($reflectionParam->getDefaultValue());
}

$description ??= '';

$isOptional = false;
if ($isOptional = Str::contains($route->uri(), ['{'.$paramName.'?}', '{'.Str::snake($paramName).'?}'], ignoreCase: true)) {
$description = implode('. ', array_filter(['**Optional**', $description]));
}

return [$schemaType, $description, $isOptional];
}

private function getModelIdTypeAndDescription(
Expand Down
27 changes: 27 additions & 0 deletions tests/Generator/Request/ParametersDocumentationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,30 @@ public function __invoke(Request $request)
]);
}
}

it('supports optional parameters', function () {
$openApiDocument = generateForRoute(fn () => RouteFacade::get('api/test/{payment_preference?}', SupportOptionalParam_ParametersDocumentationTestController::class));

expect($openApiDocument['paths']['/test/{paymentPreference}']['get']['parameters'])
->toHaveCount(1)
->and($openApiDocument['paths']['/test/{paymentPreference}']['get']['parameters'][0])
->toBe([
'name' => 'paymentPreference',
'in' => 'path',
'required' => true,
'description' => '**Optional**. The name of the payment preference to use',
'schema' => [
'type' => ['string', 'null'],
'default' => 'paypal',
],
'x-optional' => true,
]);
});

class SupportOptionalParam_ParametersDocumentationTestController
{
/**
* @param string|null $paymentPreference The name of the payment preference to use
*/
public function __invoke(?string $paymentPreference = 'paypal') {}
}

0 comments on commit 6821dd2

Please sign in to comment.