-
-
Notifications
You must be signed in to change notification settings - Fork 899
feat: add ArrayShapeDescriber for precise array shape documentation #2696
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 5.x
Are you sure you want to change the base?
Changes from all commits
9c0489c
9f5734d
5f063c4
2243c13
cb056e9
15f5a30
f9d910f
18acb3b
a508f1c
392670a
1310437
1f7a3e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| <?php | ||
|
|
||
| /* | ||
| * This file is part of the NelmioApiDocBundle package. | ||
| * | ||
| * (c) Nelmio | ||
| * | ||
| * For the full copyright and license information, please view the LICENSE | ||
| * file that was distributed with this source code. | ||
| */ | ||
|
|
||
| namespace Nelmio\ApiDocBundle\TypeDescriber; | ||
|
|
||
| use Nelmio\ApiDocBundle\OpenApiPhp\Util; | ||
| use OpenApi\Annotations as OA; | ||
| use OpenApi\Annotations\Schema; | ||
| use Symfony\Component\TypeInfo\Type; | ||
| use Symfony\Component\TypeInfo\Type\ArrayShapeType; | ||
|
|
||
| /** | ||
| * @implements TypeDescriberInterface<ArrayShapeType> | ||
| * | ||
| * @internal | ||
| */ | ||
| final class ArrayShapeDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface | ||
| { | ||
| use TypeDescriberAwareTrait; | ||
|
|
||
| public function describe(Type $type, Schema $schema, array $context = []): void | ||
| { | ||
| $schema->type = 'object'; | ||
| $required = []; | ||
|
|
||
| foreach ($type->getShape() as $key => $shapeValue) { | ||
| $property = Util::getProperty($schema, (string) $key); | ||
| $this->describer->describe($shapeValue['type'], $property, $context); | ||
|
|
||
| if (!$shapeValue['optional']) { | ||
| $required[] = (string) $key; | ||
| } | ||
| } | ||
|
|
||
| if ([] !== $required) { | ||
| $schema->required = $required; | ||
| } | ||
|
|
||
| if (!$type->isSealed()) { | ||
| $additionalProperties = Util::getChild($schema, OA\AdditionalProperties::class); | ||
| $this->describer->describe($type->getExtraValueType(), $additionalProperties, $context); | ||
| } else { | ||
| $schema->additionalProperties = false; | ||
| } | ||
| } | ||
|
|
||
| public function supports(Type $type, array $context = []): bool | ||
| { | ||
| return $type instanceof ArrayShapeType; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -38,6 +38,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void | |||||||||||
| public function supports(Type $type, array $context = []): bool | ||||||||||||
| { | ||||||||||||
| return $type instanceof CollectionType | ||||||||||||
| && !$type instanceof Type\ArrayShapeType | ||||||||||||
|
||||||||||||
| && !$type instanceof Type\ArrayShapeType | |
| && ( | |
| !class_exists(Type\ArrayShapeType::class) | |
| || !is_a($type, Type\ArrayShapeType::class) | |
| ) |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -38,6 +38,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void | |||||||
| public function supports(Type $type, array $context = []): bool | ||||||||
| { | ||||||||
| return $type instanceof CollectionType | ||||||||
| && !$type instanceof Type\ArrayShapeType | ||||||||
|
||||||||
| && !$type instanceof Type\ArrayShapeType | |
| && !(class_exists(Type\ArrayShapeType::class) | |
| && is_a($type, Type\ArrayShapeType::class)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /* | ||
| * This file is part of the NelmioApiDocBundle package. | ||
| * | ||
| * (c) Nelmio | ||
| * | ||
| * For the full copyright and license information, please view the LICENSE | ||
| * file that was distributed with this source code. | ||
| */ | ||
|
|
||
| namespace Nelmio\ApiDocBundle\Tests\Functional\Controller; | ||
|
|
||
| use Nelmio\ApiDocBundle\Attribute\Model; | ||
| use Nelmio\ApiDocBundle\Tests\Functional\Entity\ArrayShapeTypes; | ||
| use OpenApi\Attributes as OA; | ||
| use Symfony\Component\Routing\Attribute\Route; | ||
|
|
||
| class ArrayShapeController | ||
| { | ||
| #[OA\Response( | ||
| response: '200', | ||
| description: 'Success', | ||
| content: new Model(type: ArrayShapeTypes::class), | ||
| )] | ||
| #[Route('/array-shapes', methods: ['GET'])] | ||
| public function arrayShapesAction(): void | ||
| { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /* | ||
| * This file is part of the NelmioApiDocBundle package. | ||
| * | ||
| * (c) Nelmio | ||
| * | ||
| * For the full copyright and license information, please view the LICENSE | ||
| * file that was distributed with this source code. | ||
| */ | ||
|
|
||
| namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; | ||
|
|
||
| class ArrayShapeTypes | ||
| { | ||
| /** @var array{name: string, age: int, email?: string} */ | ||
| public array $sealed; | ||
|
|
||
| /** @var array{id: int, label: string, ...} */ | ||
| public array $unsealed; | ||
|
|
||
| /** @var array{user: array{name: string, age: int}, active: bool} */ | ||
| public array $nested; | ||
|
|
||
| /** @var array{0: string, 1: int} */ | ||
| public array $numericKeys; | ||
|
|
||
| /** @var array{name: ?string, email: ?string} */ | ||
| public array $nullableValues; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| { | ||
| "openapi": "3.0.0", | ||
| "info": { | ||
| "title": "", | ||
| "version": "0.0.0" | ||
| }, | ||
| "paths": { | ||
| "/array-shapes": { | ||
| "get": { | ||
| "operationId": "get_nelmio_apidoc_tests_functional_arrayshape_arrayshapes", | ||
| "responses": { | ||
| "200": { | ||
| "description": "Success", | ||
| "content": { | ||
| "application/json": { | ||
| "schema": { | ||
| "$ref": "#/components/schemas/ArrayShapeTypes" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "components": { | ||
| "schemas": { | ||
| "ArrayShapeTypes": { | ||
| "required": [ | ||
| "sealed", | ||
| "unsealed", | ||
| "nested", | ||
| "numericKeys", | ||
| "nullableValues" | ||
| ], | ||
| "properties": { | ||
| "sealed": { | ||
| "required": [ | ||
| "age", | ||
| "name" | ||
| ], | ||
| "properties": { | ||
| "age": { | ||
| "type": "integer" | ||
| }, | ||
| "email": { | ||
| "type": "string" | ||
| }, | ||
| "name": { | ||
| "type": "string" | ||
| } | ||
| }, | ||
| "type": "object", | ||
| "additionalProperties": false | ||
| }, | ||
| "unsealed": { | ||
| "required": [ | ||
| "id", | ||
| "label" | ||
| ], | ||
| "properties": { | ||
| "id": { | ||
| "type": "integer" | ||
| }, | ||
| "label": { | ||
| "type": "string" | ||
| } | ||
| }, | ||
| "type": "object", | ||
| "additionalProperties": { | ||
| "nullable": true | ||
| } | ||
| }, | ||
| "nested": { | ||
| "required": [ | ||
| "active", | ||
| "user" | ||
| ], | ||
| "properties": { | ||
| "active": { | ||
| "type": "boolean" | ||
| }, | ||
| "user": { | ||
| "required": [ | ||
| "age", | ||
| "name" | ||
| ], | ||
| "properties": { | ||
| "age": { | ||
| "type": "integer" | ||
| }, | ||
| "name": { | ||
| "type": "string" | ||
| } | ||
| }, | ||
| "type": "object", | ||
| "additionalProperties": false | ||
| } | ||
| }, | ||
| "type": "object", | ||
| "additionalProperties": false | ||
| }, | ||
| "numericKeys": { | ||
| "required": [ | ||
| "0", | ||
| "1" | ||
| ], | ||
| "properties": { | ||
| "0": { | ||
| "type": "string" | ||
| }, | ||
| "1": { | ||
| "type": "integer" | ||
| } | ||
| }, | ||
| "type": "object", | ||
| "additionalProperties": false | ||
| }, | ||
| "nullableValues": { | ||
| "required": [ | ||
| "email", | ||
| "name" | ||
| ], | ||
| "properties": { | ||
| "email": { | ||
| "type": "string", | ||
| "nullable": true | ||
| }, | ||
| "name": { | ||
| "type": "string", | ||
| "nullable": true | ||
| } | ||
| }, | ||
| "type": "object", | ||
| "additionalProperties": false | ||
| } | ||
| }, | ||
| "type": "object" | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new baseline entries for
DictionaryDescriber/ListDescriberare currently required only because the code hard-referencesSymfony\\Component\\TypeInfo\\Type\\ArrayShapeType. Once the describers use a safe string-based/guarded check, these ignores should be removed to avoid permanently masking real analysis issues in those files.