Skip to content

Commit

Permalink
Merge branch 'main' into 226-docs-generation-fails-for-requests-that-…
Browse files Browse the repository at this point in the history
…utilise-custom-values
  • Loading branch information
romalytvynenko committed Mar 4, 2024
2 parents 4368d7b + f0331d5 commit c648396
Show file tree
Hide file tree
Showing 38 changed files with 484 additions and 37 deletions.
11 changes: 11 additions & 0 deletions config/scramble.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
*/
'api_domain' => null,

/*
* Define the theme of the documentation.
* Available options are `light` and `dark`.
*/
'theme' => 'dark',

'info' => [
/*
* API version.
Expand All @@ -40,6 +46,11 @@
* URL to an image that displays as a small square logo next to the title, above the table of contents.
*/
'logo' => '',

/*
* Use to fetch the credential policy for the Try It feature. Options are: omit, include (default), and same-origin
*/
'try_it_credentials_policy' => 'include',
],

/*
Expand Down
3 changes: 2 additions & 1 deletion resources/views/docs.blade.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="en" data-theme="{{ config('scramble.theme', 'light') }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
Expand All @@ -11,6 +11,7 @@
<body style="height: 100vh; overflow-y: hidden">
<elements-api
apiDescriptionUrl="{{ route('scramble.docs.index') }}"
tryItCredentialsPolicy="{{ config('scramble.ui.try_it_credentials_policy', 'include') }}"
router="hash"
@if(config('scramble.ui.hide_try_it')) hideTryIt="true" @endif
logo="{{ config('scramble.ui.logo') }}"
Expand Down
2 changes: 1 addition & 1 deletion src/Infer/Definition/ClassDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function getPropertyDefinition($name)
return $this->properties[$name] ?? null;
}

public function getMethodCallType(string $name, ObjectType $calledOn = null)
public function getMethodCallType(string $name, ?ObjectType $calledOn = null)
{
$methodDefinition = $this->methods[$name] ?? null;

Expand Down
2 changes: 1 addition & 1 deletion src/Infer/Definition/FunctionLikeDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class FunctionLikeDefinition
public bool $isFullyAnalyzed = false;

/**
* @param array<string, Type> $argumentsDefaults A map where the key is arg name and value is a default type.
* @param array<string, Type> $argumentsDefaults A map where the key is arg name and value is a default type.
*/
public function __construct(
public FunctionType $type,
Expand Down
2 changes: 1 addition & 1 deletion src/Infer/Scope/Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ public function setType(Node $node, Type $type)
return $type;
}

public function createChildScope(ScopeContext $context = null)
public function createChildScope(?ScopeContext $context = null)
{
return new Scope(
$this->index,
Expand Down
4 changes: 2 additions & 2 deletions src/Infer/Services/ReferenceTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ private function getFunctionCallResult(
FunctionLikeDefinition $callee,
array $arguments,
/* When this is a handling for non-static method call */
ObjectType|SelfType $calledOnType = null,
ObjectType|SelfType|null $calledOnType = null,
) {
$returnType = $callee->type->getReturnType();
$isSelf = false;
Expand Down Expand Up @@ -558,7 +558,7 @@ private function getFunctionCallResult(
* arguments defaults.
*
* @param ?FunctionLikeDefinition $callee
* @param array $realArguments The list of arguments a function has been called with.
* @param array $realArguments The list of arguments a function has been called with.
* @return array The actual list of arguments where not passed arguments replaced with default values.
*/
private function prepareArguments(?FunctionLikeDefinition $callee, array $realArguments)
Expand Down
2 changes: 2 additions & 0 deletions src/ScrambleServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Dedoc\Scramble\Support\InferExtensions\RuleExtension;
use Dedoc\Scramble\Support\InferExtensions\ValidatorTypeInfer;
use Dedoc\Scramble\Support\OperationBuilder;
use Dedoc\Scramble\Support\OperationExtensions\DeprecationExtension;
use Dedoc\Scramble\Support\OperationExtensions\ErrorResponsesExtension;
use Dedoc\Scramble\Support\OperationExtensions\RequestBodyExtension;
use Dedoc\Scramble\Support\OperationExtensions\RequestEssentialsExtension;
Expand Down Expand Up @@ -112,6 +113,7 @@ public function configurePackage(Package $package): void
RequestBodyExtension::class,
ErrorResponsesExtension::class,
ResponseExtension::class,
DeprecationExtension::class,
], $operationExtensions);
});

Expand Down
13 changes: 13 additions & 0 deletions src/Support/Generator/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class Operation

public string $summary = '';

public bool $deprecated = false;

/** @var array<Security|array> */
public array $security = [];

Expand Down Expand Up @@ -111,6 +113,13 @@ public function description(string $description)
return $this;
}

public function deprecated(bool $deprecated)
{
$this->deprecated = $deprecated;

return $this;
}

public function setTags(array $tags)
{
$this->tags = array_map(fn ($t) => (string) $t, $tags);
Expand Down Expand Up @@ -141,6 +150,10 @@ public function toArray()
$result['summary'] = $this->summary;
}

if ($this->deprecated) {
$result['deprecated'] = $this->deprecated;
}

if (count($this->tags)) {
$result['tags'] = $this->tags;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Support/Generator/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function toArray()

$content = [];
foreach ($this->content as $mediaType => $schema) {
$content[$mediaType] = $schema ? ['schema' => $schema->toArray()] : [];
$content[$mediaType] = $schema ? ['schema' => $schema->toArray()] : (object) [];
}

$result['content'] = $content;
Expand Down
15 changes: 9 additions & 6 deletions src/Support/Generator/SecuritySchemes/OAuthFlow.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,14 @@ public function addScope(string $name, string $description = '')

public function toArray()
{
return array_filter([
'authorizationUrl' => $this->authorizationUrl,
'tokenUrl' => $this->tokenUrl,
'refreshUrl' => $this->refreshUrl,
'scopes' => $this->scopes,
]);
return [
...array_filter([
'authorizationUrl' => $this->authorizationUrl,
'tokenUrl' => $this->tokenUrl,
'refreshUrl' => $this->refreshUrl,
]),
// Never filter 'scopes' as it is allowed to be empty. If empty it must be an object
'scopes' => empty($this->scopes) ? new \stdClass() : $this->scopes,
];
}
}
4 changes: 2 additions & 2 deletions src/Support/Generator/ServerVariable.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ServerVariable
public function __construct(
string $default,
array $enum = [],
string $description = null
?string $description = null
) {
$this->default = $default;
$this->enum = $enum;
Expand All @@ -23,7 +23,7 @@ public function __construct(
public static function make(
string $default,
array $enum = [],
string $description = null
?string $description = null
) {
return new self($default, $enum, $description);
}
Expand Down
14 changes: 13 additions & 1 deletion src/Support/Generator/TypeTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Dedoc\Scramble\Support\Generator\Types\StringType;
use Dedoc\Scramble\Support\Generator\Types\UnknownType;
use Dedoc\Scramble\Support\Type\ArrayItemType_;
use Dedoc\Scramble\Support\Type\Literal\LiteralIntegerType;
use Dedoc\Scramble\Support\Type\Literal\LiteralStringType;
use Dedoc\Scramble\Support\Type\Type;
use Dedoc\Scramble\Support\Type\Union;
Expand Down Expand Up @@ -125,7 +126,10 @@ public function transform(Type $type)
$openApiType = new NullType();
}
} else {
[$stringLiterals, $otherTypes] = collect($type->types)
[$literals, $otherTypes] = collect($type->types)
->partition(fn ($t) => $t instanceof LiteralStringType || $t instanceof LiteralIntegerType);

[$stringLiterals, $integerLiterals] = collect($literals)
->partition(fn ($t) => $t instanceof LiteralStringType);

$items = array_map($this->transform(...), $otherTypes->values()->toArray());
Expand All @@ -136,10 +140,18 @@ public function transform(Type $type)
);
}

if ($integerLiterals->count()) {
$items[] = (new IntegerType())->enum(
$integerLiterals->map->value->unique()->toArray()
);
}

$openApiType = count($items) === 1 ? $items[0] : (new AnyOf)->setItems($items);
}
} elseif ($type instanceof LiteralStringType) {
$openApiType = (new StringType())->example($type->value);
} elseif ($type instanceof LiteralIntegerType) {
$openApiType = (new IntegerType())->example($type->value);
} elseif ($type instanceof \Dedoc\Scramble\Support\Type\StringType) {
$openApiType = new StringType();
} elseif ($type instanceof \Dedoc\Scramble\Support\Type\FloatType) {
Expand Down
31 changes: 28 additions & 3 deletions src/Support/Generator/Types/ArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,42 @@ class ArrayType extends Type
/** @var Type|Schema */
public $items;

private $minItems = null;

private $maxItems = null;

public function __construct()
{
parent::__construct('array');
$this->items = new StringType;
}

public function setMin($min)
{
$this->minItems = $min;

return $this;
}

public function setMax($max)
{
$this->maxItems = $max;

return $this;
}

public function toArray()
{
return array_merge(parent::toArray(), [
'items' => $this->items->toArray(),
]);
return array_merge(
parent::toArray(),
[
'items' => $this->items->toArray(),
],
array_filter([
'minItems' => $this->minItems,
'maxItems' => $this->maxItems,
], fn ($v) => $v !== null)
);
}

public function setItems($items)
Expand Down
2 changes: 1 addition & 1 deletion src/Support/Generator/UniqueNamesOptionsCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function push(UniqueNameOptions $name)
return $this;
}

public function getUniqueName(UniqueNameOptions $name, callable $onNotUniqueFallback = null): string
public function getUniqueName(UniqueNameOptions $name, ?callable $onNotUniqueFallback = null): string
{
if ($name->eloquent && count($this->eloquentNames[$name->eloquent]) === 1) {
return $name->eloquent;
Expand Down
22 changes: 20 additions & 2 deletions src/Support/InferExtensions/JsonResourceTypeInfer.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Dedoc\Scramble\Support\Type\BooleanType;
use Dedoc\Scramble\Support\Type\FunctionType;
use Dedoc\Scramble\Support\Type\Generic;
use Dedoc\Scramble\Support\Type\IntegerType;
use Dedoc\Scramble\Support\Type\Literal\LiteralBooleanType;
use Dedoc\Scramble\Support\Type\ObjectType;
use Dedoc\Scramble\Support\Type\Type;
Expand Down Expand Up @@ -83,7 +84,7 @@ public function getType(Expr $node, Scope $scope): ?Type
* $this->when()
*/
if ($this->isMethodCallToThis($node, ['when'])) {
return new Union([
return Union::wrap([
$this->value(TypeHelper::getArgType($scope, $node->args, ['value', 1])),
$this->value(TypeHelper::getArgType($scope, $node->args, ['default', 2], new ObjectType(MissingValue::class))),
]);
Expand All @@ -101,7 +102,24 @@ public function getType(Expr $node, Scope $scope): ?Type
]);
}

return new Union([
return Union::wrap([
$this->value(TypeHelper::getArgType($scope, $node->args, ['value', 1])),
$this->value(TypeHelper::getArgType($scope, $node->args, ['default', 2], new ObjectType(MissingValue::class))),
]);
}

/*
* $this->whenCounted()
*/
if ($this->isMethodCallToThis($node, ['whenCounted'])) {
if (count($node->args) === 1) {
return new Union([
new IntegerType(),
new ObjectType(MissingValue::class),
]);
}

return Union::wrap([
$this->value(TypeHelper::getArgType($scope, $node->args, ['value', 1])),
$this->value(TypeHelper::getArgType($scope, $node->args, ['default', 2], new ObjectType(MissingValue::class))),
]);
Expand Down
71 changes: 71 additions & 0 deletions src/Support/OperationExtensions/DeprecationExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace Dedoc\Scramble\Support\OperationExtensions;

use Dedoc\Scramble\Extensions\OperationExtension;
use Dedoc\Scramble\Infer\Reflector\ClassReflector;
use Dedoc\Scramble\Support\Generator\Operation;
use Dedoc\Scramble\Support\PhpDoc;
use Dedoc\Scramble\Support\RouteInfo;
use Illuminate\Support\Str;
use PHPStan\PhpDocParser\Ast\PhpDoc\DeprecatedTagValueNode;

/**
* Extension to add deprecation notice to the operation description
* Skips if method is not deprecated
* If a whole class is deprecated, all methods are deprecated, only add the description if exists
*/
class DeprecationExtension extends OperationExtension
{
public function handle(Operation $operation, RouteInfo $routeInfo)
{
// Skip if method is not deprecated
if (! $routeInfo->reflectionMethod() || $routeInfo->phpDoc()->getTagsByName('@not-deprecated')) {
return;
}

$fqdn = $routeInfo->reflectionMethod()->getDeclaringClass()->getName();
$deprecatedClass = $this->getClassDeprecatedValues($fqdn);
$deprecatedTags = $routeInfo->phpDoc()->getDeprecatedTagValues();

// Skip if no deprecations found
if (! $deprecatedClass && ! $deprecatedTags) {
return;
}

$description = Str::of($this->generateDescription($deprecatedClass));

if ($description->isNotEmpty()) {
$description = $description->append("\n\n");
}

$description = $description->append($this->generateDescription($deprecatedTags));

$operation
->description((string) $description)
->deprecated(true);
}

/**
* @return array<DeprecatedTagValueNode>
*/
protected function getClassDeprecatedValues(string $fqdn)
{
$reflector = ClassReflector::make($fqdn);
$classPhpDocString = $reflector->getReflection()->getDocComment();

if ($classPhpDocString === false) {
return [];
}

return PhpDoc::parse($classPhpDocString)->getDeprecatedTagValues();
}

/**
* @return string
*/
private function generateDescription(array $deprecation)
{
return implode("\n", array_map(fn ($tag) => $tag->description, $deprecation));
}
}
Loading

0 comments on commit c648396

Please sign in to comment.