Skip to content

Commit

Permalink
Fixed API resource schema when wrap key is already contained in resou…
Browse files Browse the repository at this point in the history
…rce (#446)

* dont wrap when wrap key already presents in the array

* Fix styling

---------

Co-authored-by: romalytvynenko <[email protected]>
  • Loading branch information
romalytvynenko and romalytvynenko authored Jul 9, 2024
1 parent 74ea258 commit 6119cb4
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 11 deletions.
60 changes: 49 additions & 11 deletions src/Support/TypeToSchemaExtensions/JsonResourceTypeToSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
namespace Dedoc\Scramble\Support\TypeToSchemaExtensions;

use Dedoc\Scramble\Extensions\TypeToSchemaExtension;
use Dedoc\Scramble\Support\Generator\Combined\AllOf;
use Dedoc\Scramble\Support\Generator\Reference;
use Dedoc\Scramble\Support\Generator\Response;
use Dedoc\Scramble\Support\Generator\Schema;
use Dedoc\Scramble\Support\Generator\Types\ObjectType as OpenApiObjectType;
use Dedoc\Scramble\Support\Generator\Types\UnknownType;
use Dedoc\Scramble\Support\InferExtensions\ResourceCollectionTypeInfer;
use Dedoc\Scramble\Support\Type\ArrayType;
Expand Down Expand Up @@ -87,17 +89,12 @@ public function toResponse(Type $type)
$wrapKey = $wrapKey ?: 'data';

if ($shouldWrap) {
$openApiType = (new \Dedoc\Scramble\Support\Generator\Types\ObjectType())
->addProperty($wrapKey, $openApiType)
->setRequired([$wrapKey]);

if ($withArray instanceof KeyedArrayType) {
$this->mergeOpenApiObjects($openApiType, $this->openApiTransformer->transform($withArray));
}

if ($additional instanceof KeyedArrayType) {
$this->mergeOpenApiObjects($openApiType, $this->openApiTransformer->transform($additional));
}
$openApiType = $this->mergeResourceTypeAndAdditionals(
$wrapKey,
$openApiType,
$this->normalizeKeyedArrayType($withArray),
$this->normalizeKeyedArrayType($additional),
);
}

return Response::make(200)
Expand All @@ -108,6 +105,47 @@ public function toResponse(Type $type)
);
}

private function mergeResourceTypeAndAdditionals(string $wrapKey, Reference|OpenApiObjectType $openApiType, ?KeyedArrayType $withArray, ?KeyedArrayType $additional)
{
$resolvedOpenApiType = $openApiType instanceof Reference ? $openApiType->resolve() : $openApiType;
$resolvedOpenApiType = $resolvedOpenApiType instanceof Schema ? $resolvedOpenApiType->type : $resolvedOpenApiType;

// If resolved type already contains wrapKey, we don't need to wrap it again. But we still need to merge additionals.
if ($resolvedOpenApiType instanceof OpenApiObjectType && $resolvedOpenApiType->hasProperty($wrapKey)) {
$items = array_values(array_filter([
$openApiType,
$this->transformNullableType($withArray),
$this->transformNullableType($additional),
]));

return count($items) > 1 ? (new AllOf)->setItems($items) : $items[0];
}

$openApiType = (new OpenApiObjectType())
->addProperty($wrapKey, $openApiType)
->setRequired([$wrapKey]);

if ($withArray) {
$this->mergeOpenApiObjects($openApiType, $this->openApiTransformer->transform($withArray));
}

if ($additional) {
$this->mergeOpenApiObjects($openApiType, $this->openApiTransformer->transform($additional));
}

return $openApiType;
}

private function normalizeKeyedArrayType($type): ?KeyedArrayType
{
return $type instanceof KeyedArrayType ? $type : null;
}

private function transformNullableType(?KeyedArrayType $type)
{
return $type ? $this->openApiTransformer->transform($type) : null;
}

public function reference(ObjectType $type)
{
return new Reference('schemas', $type->name, $this->components);
Expand Down
40 changes: 40 additions & 0 deletions tests/ResponseDocumentingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,43 @@ public function collection()
return Foo_TestFiveResource::collection()->response()->setStatusCode(201);
}
}

test('does not wrap resources when resource is wrapped', function () {
$openApiDocument = generateForRoute(fn () => \Illuminate\Support\Facades\Route::get('api/test', [Foo_TestSeven::class, 'index']));

expect($openApiDocument['paths']['/test']['get']['responses'][200]['content']['application/json']['schema'])
->toBe(['$ref' => '#/components/schemas/Foo_TestSevenResource']);
expect($openApiDocument['components']['schemas']['Foo_TestSevenResource'])
->toBe([
'type' => 'object',
'properties' => [
'data' => [
'type' => 'object',
'properties' => ['foo' => ['type' => 'string']],
'required' => ['foo'],
],
],
'required' => ['data'],
'title' => 'Foo_TestSevenResource',
]);
});
class Foo_TestSevenResource extends \Illuminate\Http\Resources\Json\JsonResource
{
public static $wrap = 'data';

public function toArray(\Illuminate\Http\Request $request)
{
return [
'data' => [
'foo' => $this->id,
],
];
}
}
class Foo_TestSeven
{
public function index()
{
return new Foo_TestSevenResource(unknown());
}
}

0 comments on commit 6119cb4

Please sign in to comment.