diff --git a/src/Support/Helpers/ExamplesExtractor.php b/src/Support/Helpers/ExamplesExtractor.php index bc0c908c..de840eb6 100644 --- a/src/Support/Helpers/ExamplesExtractor.php +++ b/src/Support/Helpers/ExamplesExtractor.php @@ -11,18 +11,19 @@ class ExamplesExtractor { public function __construct( - private ?PhpDocNode $docNode + private ?PhpDocNode $docNode, + private string $tagName = '@example', ) { } - public static function make(?PhpDocNode $docNode) + public static function make(?PhpDocNode $docNode, string $tagName = '@example') { - return new self($docNode); + return new self($docNode, $tagName); } public function extract(bool $preferString = false) { - if (! count($examples = $this->docNode->getTagsByName('@example'))) { + if (! count($examples = $this->docNode->getTagsByName($this->tagName))) { return []; } @@ -52,6 +53,10 @@ private function getTypedExampleValue($exampleValue, bool $preferString = false) $exampleValue = $exampleValue === 'true'; } elseif (is_numeric($exampleValue) && ! $preferString) { $exampleValue = floatval($exampleValue); + + if (floor($exampleValue) == $exampleValue) { + $exampleValue = intval($exampleValue); + } } return $exampleValue; diff --git a/src/Support/IndexBuilders/RequestParametersBuilder.php b/src/Support/IndexBuilders/RequestParametersBuilder.php index c3bc5c61..240252e4 100644 --- a/src/Support/IndexBuilders/RequestParametersBuilder.php +++ b/src/Support/IndexBuilders/RequestParametersBuilder.php @@ -7,6 +7,7 @@ use Dedoc\Scramble\Support\Generator\Parameter; use Dedoc\Scramble\Support\Generator\Schema; use Dedoc\Scramble\Support\Generator\TypeTransformer; +use Dedoc\Scramble\Support\Helpers\ExamplesExtractor; use Dedoc\Scramble\Support\Type\BooleanType; use Dedoc\Scramble\Support\Type\FloatType; use Dedoc\Scramble\Support\Type\IntegerType; @@ -62,7 +63,7 @@ public function afterAnalyzedNode(Scope $scope, Node $node) $parameter = Parameter::make($parameterName, 'query'/* @todo: this is just a temp solution */); - [$parameterType, $parameterDefaultFromMethodCall] = match ($name) { + [$parameterType, $parameterDefault] = match ($name) { 'integer' => $this->makeIntegerParameter($scope, $methodCallNode), 'float' => $this->makeFloatParameter($scope, $methodCallNode), 'boolean' => $this->makeBooleanParameter($scope, $methodCallNode), @@ -76,12 +77,16 @@ public function afterAnalyzedNode(Scope $scope, Node $node) return; } + if ($parameterDefaultFromDoc = $this->getParameterDefaultFromPhpDoc($node)) { + $parameterDefault = $parameterDefaultFromDoc; + } + $parameter ->description($this->makeDescriptionFromComments($node)) ->setSchema(Schema::fromType( app(TypeTransformer::class)->transform($parameterType) )) - ->default($parameterDefaultFromMethodCall ?? new MissingExample); + ->default($parameterDefault ?? new MissingExample); // @todo: query // @todo: get/input/post/? @@ -159,6 +164,17 @@ private function makeQueryParameter(Scope $scope, Node $node, Parameter $paramet private function makeDescriptionFromComments(Node\Stmt\Expression $node) { + /* + * @todo: consider adding only @param annotation support, + * so when description is taken only if comment is marked with @param + */ + if ($node->getDocComment()) { + /** @var PhpDocNode $phpDoc */ + $phpDoc = $node->getAttribute('parsedPhpDoc'); + + return trim($phpDoc->getAttribute('summary').' '.$phpDoc->getAttribute('description')); + } + if ($node->getComments()) { $docText = collect($node->getComments()) ->map(fn (Comment $c) => $c->getReformattedText()) @@ -167,16 +183,6 @@ private function makeDescriptionFromComments(Node\Stmt\Expression $node) return (string) Str::of($docText)->replace(['//', ' * ', '/**', '/*', '*/'], '')->trim(); } - /* - * @todo: consider adding only @param annotation support, - * so when description is taken only if comment is marked with @param - */ - if ($node->getDocComment()) { - return (string) Str::of($node->getDocComment()->getReformattedText()) - ->replace(['//', ' * ', '/**', '/*', '*/'], '') - ->trim(); - } - return ''; } @@ -187,4 +193,12 @@ private function shouldIgnoreParameter(Node\Stmt\Expression $node) return !! $phpDoc->getTagsByName('@ignoreParam'); } + + private function getParameterDefaultFromPhpDoc(Node\Stmt\Expression $node) + { + /** @var PhpDocNode $phpDoc */ + $phpDoc = $node->getAttribute('parsedPhpDoc'); + + return ExamplesExtractor::make($phpDoc, '@default')->extract()[0] ?? null; + } } diff --git a/tests/Support/OperationExtensions/RequestBodyExtensionTest.php b/tests/Support/OperationExtensions/RequestBodyExtensionTest.php index 78d576af..3f233c64 100644 --- a/tests/Support/OperationExtensions/RequestBodyExtensionTest.php +++ b/tests/Support/OperationExtensions/RequestBodyExtensionTest.php @@ -204,3 +204,20 @@ public function index(Illuminate\Http\Request $request) $request->integer('foo', 10); } } + +it('uses and overrides default param value when it is provided manually in doc', function () { + $openApiDocument = generateForRoute(function () { + return RouteFacade::post('api/test', [RequestBodyExtensionTest__uses_and_overrides_default_param_value_when_it_is_provided_manually_in_doc::class, 'index']); + }); + + expect($openApiDocument['paths']['/test']['post']['requestBody']['content']['application/json']['schema']['properties']['foo']['default']) + ->toBe(15); +}); +class RequestBodyExtensionTest__uses_and_overrides_default_param_value_when_it_is_provided_manually_in_doc +{ + public function index(Illuminate\Http\Request $request) + { + /** @default 15 */ + $request->integer('foo', 10); + } +}