diff --git a/README.md b/README.md index c551c328..6491ebf5 100644 --- a/README.md +++ b/README.md @@ -45,15 +45,19 @@ You can configure the bundle by adding the following parameters to your `/config ```yaml open_api_server: - root_name_space: App\Generated # Namespace for DTOs and Api Interfaces + #root_name_space: App\Generated # Namespace for DTOs and Api Interfaces ## The bundle will try to derive the paths for the generated files from the namespace. If you do not want them to be ## stored in \App namespace or if you \App namespace is not in %kernel.project_dir%/src/, then you ## can specify this path manually: - root_path: %kernel.project_dir%/src/Generated - language_level: 7.4.0 # minimum PHP version the generated code should be compatible with - generated_dir_permissions: 0755 # permissions for the generated directories - full_doc_blocks: false # whether to generate DocBlocks for typed variables and params - send_nulls: false # return null values in responses if property is nullable and not required + #root_path: %kernel.project_dir%/src/Generated + #language_level: 8.0.0 # minimum PHP version the generated code should be compatible with + #generated_dir_permissions: 0755 # permissions for the generated directories + #full_doc_blocks: false # whether to generate DocBlocks for typed variables and params + #send_nulls: false # return null values in responses if property is nullable and not required + #skip_http_codes: [] # List of response codes ignored while parsing specification. + ## Can be any open api response code ( like 500, "5XX", "default"), or + ## "5**", which will include both numeric (500) and XX ("5XX") codes. + ## Might be useful if you want to generate error responses in event listener. specs: petstore: path: '../spec/petstore.yaml' # path to OpenApi specification @@ -87,17 +91,16 @@ Currently, there are also the following limitations: ## Generating the API Server code -There are three console commands that work with the generated API server code: +There are two console commands that work with the generated API server code: - Generate the server code: `php bin/console open-api:generate` -- Refresh the server code: `php bin/console open-api:refresh` - Delete the server code: `php bin/console open-api:delete` -Most of the time you should use the `refresh` command. +Most of the time you should use the `generate` command. It will clear the bundle cache, delete the old generated server code if it exists and generate the new code. -Be careful with the refresh and delete commands, they will delete the entire contents of the directory you have specified -in `root_name_space` in the `/config/packages/open_api_server.yaml` file. That directory should contain no files except +Be careful with the generate and delete commands, they will delete the entire contents of the directory you have specified +in `root_path` in the `/config/packages/open_api_server.yaml` file. That directory should contain no files except the code generated by this bundle, as it will be deleted every time you generate the API server code. For each operation described in the specification, a API call handler interface will be generated that you should implement diff --git a/composer.json b/composer.json index 69e5ca67..4a7caf07 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,6 @@ "symfony/dependency-injection": "^6.0", "symfony/event-dispatcher": "^6.0", "symfony/http-kernel": "^6.0", - "symfony/process": "^6.0", "symfony/psr-http-message-bridge": "^2.1", "symfony/routing": "^6.0", "symfony/yaml": "^6.0", @@ -86,8 +85,8 @@ } }, "scripts": { - "cs": "phpcs --config-set php_version 7040 && phpcs", - "csfix": "phpcs --config-set php_version 7040 && phpcbf", + "cs": "phpcs --config-set php_version 8000 && phpcs", + "csfix": "phpcs --config-set php_version 8000 && phpcbf", "psalm": "psalm", "stan": "phpstan analyse --memory-limit=-1 --xdebug", "tests": "phpunit --fail-on-warning", diff --git a/phpstan.neon b/phpstan.neon index 8986f679..fd730ca9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,7 +2,7 @@ parameters: level: 8 paths: - src - - test + # - test checkMissingIterableValueType: false treatPhpDocTypesAsCertain: false excludePaths: @@ -17,18 +17,12 @@ parameters: message: '#Variable static method call on class-string.#' paths: - %currentWorkingDirectory%/src/Serializer/ArrayDtoSerializer.php - - - message: '#Call to function is_array\(\) with array will always evaluate to true.#' - paths: - - %currentWorkingDirectory%/src/Specification/SpecificationParser.php + - message: '#Variable static method call on OnMoon\\OpenApiServerBundle\\Types\\TypeSerializer.#' paths: - %currentWorkingDirectory%/src/Types/ScalarTypesResolver.php - - - message: '#Instanceof between cebe\\openapi\\spec\\MediaType and cebe\\openapi\\spec\\MediaType will always evaluate to true\.#' - paths: - - %currentWorkingDirectory%/src/Specification/SpecificationParser.php + - message: '#OnMoon\\OpenApiServerBundle\\Router\\RouteLoader::__construct\(\) does not call parent constructor from Symfony\\Component\\Config\\Loader\\Loader\.#' paths: diff --git a/src/CodeGenerator/ApiServerCodeGenerator.php b/src/CodeGenerator/ApiServerCodeGenerator.php index c3c3e191..74c13859 100644 --- a/src/CodeGenerator/ApiServerCodeGenerator.php +++ b/src/CodeGenerator/ApiServerCodeGenerator.php @@ -9,31 +9,31 @@ use OnMoon\OpenApiServerBundle\Event\CodeGenerator\FilesReadyEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use const DIRECTORY_SEPARATOR; + class ApiServerCodeGenerator { private GraphGenerator $graphGenerator; private NameGenerator $nameGenerator; - private InterfaceGenerator $interfaceGenerator; private FileGenerator $filesGenerator; private AttributeGenerator $attributeGenerator; private FileWriter $writer; private EventDispatcherInterface $eventDispatcher; - public function __construct(GraphGenerator $graphGenerator, NameGenerator $nameGenerator, InterfaceGenerator $interfaceGenerator, FileGenerator $filesGenerator, AttributeGenerator $attributeGenerator, FileWriter $writer, EventDispatcherInterface $eventDispatcher) + public function __construct(GraphGenerator $graphGenerator, NameGenerator $nameGenerator, FileGenerator $filesGenerator, AttributeGenerator $attributeGenerator, FileWriter $writer, EventDispatcherInterface $eventDispatcher) { $this->graphGenerator = $graphGenerator; $this->nameGenerator = $nameGenerator; - $this->interfaceGenerator = $interfaceGenerator; $this->filesGenerator = $filesGenerator; $this->attributeGenerator = $attributeGenerator; $this->writer = $writer; $this->eventDispatcher = $eventDispatcher; } - public function generate(): void + /** @return string[] */ + public function generate(): array { $graph = $this->graphGenerator->generateClassGraph(); - $this->interfaceGenerator->setAllInterfaces($graph); $this->attributeGenerator->setAllAttributes($graph); $this->nameGenerator->setAllNamesAndPaths($graph); @@ -42,9 +42,13 @@ public function generate(): void $files = $this->filesGenerator->generateAllFiles($graph); $this->eventDispatcher->dispatch(new FilesReadyEvent($files)); + $writtenFiles = []; foreach ($files as $item) { $this->writer->write($item->getClass()->getFilePath(), $item->getClass()->getFileName(), $item->getFileContents()); + $writtenFiles[] = $item->getClass()->getFilePath() . DIRECTORY_SEPARATOR . $item->getClass()->getFileName(); } + + return $writtenFiles; } } diff --git a/src/CodeGenerator/AttributeGenerator.php b/src/CodeGenerator/AttributeGenerator.php index c765c78d..68216d31 100644 --- a/src/CodeGenerator/AttributeGenerator.php +++ b/src/CodeGenerator/AttributeGenerator.php @@ -5,63 +5,77 @@ namespace OnMoon\OpenApiServerBundle\CodeGenerator; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoReference; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GraphDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\PropertyDefinition; +use OnMoon\OpenApiServerBundle\Specification\Definitions\Property; class AttributeGenerator { public function setAllAttributes(GraphDefinition $graph): void { foreach ($graph->getSpecifications() as $specificationDefinition) { + foreach ($specificationDefinition->getComponents() as $component) { + $this->componentsPass($component->getDto()); + } + foreach ($specificationDefinition->getOperations() as $operation) { - $request = $operation->getRequest(); - if ($request !== null) { - $this->requestPass($request); - } + $this->requestPass($operation->getRequest()); foreach ($operation->getResponses() as $response) { - $this->responsePass($response); + $this->responsePass($response->getResponseBody()); } } } } - public function requestPass(DtoDefinition $root): void + public function componentsPass(?DtoReference $root): void { - foreach ($root->getProperties() as $property) { - $specProperty = $property->getSpecProperty(); - $willExist = $specProperty->isRequired() || $specProperty->getDefaultValue() !== null; + $this->treeWalk($root, static function (Property $specProperty, PropertyDefinition $property): void { + $needValue = $specProperty->isRequired() && $specProperty->getDefaultValue() === null; + $property + ->setHasGetter(true) + ->setHasSetter(! $needValue) + ->setNullable(! $needValue || $specProperty->isNullable()) + ->setInConstructor($needValue); + }); + } + + public function requestPass(?DtoReference $root): void + { + $this->treeWalk($root, static function (Property $specProperty, PropertyDefinition $property): void { + $willExist = $specProperty->isRequired() || $specProperty->getDefaultValue() !== null; $property ->setHasGetter(true) ->setHasSetter(false) ->setNullable(! $willExist || $specProperty->isNullable()) ->setInConstructor(false); - - $object = $property->getObjectTypeDefinition(); - if ($object === null) { - continue; - } - - $this->requestPass($object); - } + }); } - public function responsePass(DtoDefinition $root): void + public function responsePass(?DtoReference $root): void { - foreach ($root->getProperties() as $property) { - $specProperty = $property->getSpecProperty(); - $needValue = $specProperty->isRequired() && $specProperty->getDefaultValue() === null; + $this->treeWalk($root, static function (Property $specProperty, PropertyDefinition $property): void { + $needValue = $specProperty->isRequired() && $specProperty->getDefaultValue() === null; $property ->setHasGetter(true) ->setHasSetter(! $needValue) ->setNullable(! $needValue || $specProperty->isNullable()) ->setInConstructor($needValue); + }); + } - $object = $property->getObjectTypeDefinition(); - if ($object === null) { - continue; - } + /** @param callable(Property, PropertyDefinition): void $action */ + private function treeWalk(?DtoReference $root, callable $action): void + { + if (! $root instanceof DtoDefinition) { + return; + } - $this->responsePass($object); + foreach ($root->getProperties() as $property) { + $specProperty = $property->getSpecProperty(); + $action($specProperty, $property); + $this->treeWalk($property->getObjectTypeDefinition(), $action); } } } diff --git a/src/CodeGenerator/Definitions/ClassDefinition.php b/src/CodeGenerator/Definitions/ClassDefinition.php index f252aeb0..ee076112 100644 --- a/src/CodeGenerator/Definitions/ClassDefinition.php +++ b/src/CodeGenerator/Definitions/ClassDefinition.php @@ -7,7 +7,7 @@ use function Safe\substr; use function strrpos; -class ClassDefinition +class ClassDefinition implements ClassReference { private string $className; private string $namespace; diff --git a/src/CodeGenerator/Definitions/ClassReference.php b/src/CodeGenerator/Definitions/ClassReference.php new file mode 100644 index 00000000..591d24f2 --- /dev/null +++ b/src/CodeGenerator/Definitions/ClassReference.php @@ -0,0 +1,14 @@ +name; + } + + public function getDto(): DtoDefinition + { + return $this->dto; + } + + public function setDto(DtoDefinition $dto): self + { + $this->dto = $dto; + + return $this; + } +} diff --git a/src/CodeGenerator/Definitions/ComponentReference.php b/src/CodeGenerator/Definitions/ComponentReference.php new file mode 100644 index 00000000..d4882019 --- /dev/null +++ b/src/CodeGenerator/Definitions/ComponentReference.php @@ -0,0 +1,32 @@ +referencedComponent->getDto()->getClassName(); + } + + public function getNamespace(): string + { + return $this->referencedComponent->getDto()->getNamespace(); + } + + public function getFQCN(): string + { + return $this->referencedComponent->getDto()->getFQCN(); + } + + public function isEmpty(): bool + { + return $this->referencedComponent->getDto()->isEmpty(); + } +} diff --git a/src/CodeGenerator/Definitions/DtoDefinition.php b/src/CodeGenerator/Definitions/DtoDefinition.php index a74c7b4a..a65c02f1 100644 --- a/src/CodeGenerator/Definitions/DtoDefinition.php +++ b/src/CodeGenerator/Definitions/DtoDefinition.php @@ -4,19 +4,22 @@ namespace OnMoon\OpenApiServerBundle\CodeGenerator\Definitions; +use OnMoon\OpenApiServerBundle\Interfaces\Dto; + use function count; -class DtoDefinition extends GeneratedClassDefinition +class DtoDefinition extends GeneratedClassDefinition implements DtoReference { /** @var PropertyDefinition[] $properties; */ private array $properties; - private ?ClassDefinition $implements = null; + private ?ClassReference $implements; /** * @param PropertyDefinition[] $properties */ public function __construct(array $properties) { + $this->implements = ClassDefinition::fromFQCN(Dto::class); $this->properties = $properties; } @@ -33,15 +36,12 @@ final public function getProperties(): array return $this->properties; } - final public function getImplements(): ?ClassDefinition + final public function getImplements(): ?ClassReference { return $this->implements; } - /** - * @return DtoDefinition - */ - final public function setImplements(?ClassDefinition $implements): self + final public function setImplements(?ClassReference $implements): self { $this->implements = $implements; diff --git a/src/CodeGenerator/Definitions/RequestBodyDtoDefinition.php b/src/CodeGenerator/Definitions/DtoReference.php similarity index 55% rename from src/CodeGenerator/Definitions/RequestBodyDtoDefinition.php rename to src/CodeGenerator/Definitions/DtoReference.php index 3dcbe095..a9523014 100644 --- a/src/CodeGenerator/Definitions/RequestBodyDtoDefinition.php +++ b/src/CodeGenerator/Definitions/DtoReference.php @@ -4,6 +4,7 @@ namespace OnMoon\OpenApiServerBundle\CodeGenerator\Definitions; -final class RequestBodyDtoDefinition extends DtoDefinition +interface DtoReference extends ClassReference { + public function isEmpty(): bool; } diff --git a/src/CodeGenerator/Definitions/GeneratedInterfaceDefinition.php b/src/CodeGenerator/Definitions/GeneratedInterfaceDefinition.php index a7ab6d20..28867bce 100644 --- a/src/CodeGenerator/Definitions/GeneratedInterfaceDefinition.php +++ b/src/CodeGenerator/Definitions/GeneratedInterfaceDefinition.php @@ -6,9 +6,9 @@ class GeneratedInterfaceDefinition extends GeneratedClassDefinition { - private ?ClassDefinition $extends = null; + private ?ClassReference $extends = null; - final public function getExtends(): ?ClassDefinition + final public function getExtends(): ?ClassReference { return $this->extends; } @@ -16,7 +16,7 @@ final public function getExtends(): ?ClassDefinition /** * @return GeneratedInterfaceDefinition */ - final public function setExtends(?ClassDefinition $extends): self + final public function setExtends(?ClassReference $extends): self { $this->extends = $extends; diff --git a/src/CodeGenerator/Definitions/OperationDefinition.php b/src/CodeGenerator/Definitions/OperationDefinition.php index ca17ea53..72a3711e 100644 --- a/src/CodeGenerator/Definitions/OperationDefinition.php +++ b/src/CodeGenerator/Definitions/OperationDefinition.php @@ -6,30 +6,20 @@ final class OperationDefinition { - private string $url; - private string $method; - private string $operationId; - private string $requestHandlerName; - private ?string $summary = null; - private ?RequestDtoDefinition $request = null; - private ?ClassDefinition $markersInterface = null; - private RequestHandlerInterfaceDefinition $requestHandlerInterface; - - /** @var ResponseDtoDefinition[] */ - private array $responses; - /** - * @param ResponseDtoDefinition[] $responses + * @param ResponseDefinition[] $responses */ - public function __construct(string $url, string $method, string $operationId, string $requestHandlerName, ?string $summary, ?RequestDtoDefinition $request, array $responses) - { - $this->url = $url; - $this->method = $method; - $this->operationId = $operationId; - $this->requestHandlerName = $requestHandlerName; - $this->summary = $summary; - $this->request = $request; - $this->responses = $responses; + public function __construct( + private string $url, + private string $method, + private string $operationId, + private string $requestHandlerName, + private ?string $summary, + private ?string $singleHttpCode, + private ?DtoReference $request, + private array $responses, + private RequestHandlerInterfaceDefinition $requestHandlerInterface + ) { } public function getUrl(): string @@ -57,40 +47,26 @@ public function getSummary(): ?string return $this->summary; } - public function getRequest(): ?RequestDtoDefinition + public function getRequest(): ?DtoReference { return $this->request; } /** - * @return ResponseDtoDefinition[] + * @return ResponseDefinition[] */ public function getResponses(): array { return $this->responses; } - public function getMarkersInterface(): ?ClassDefinition - { - return $this->markersInterface; - } - - public function setMarkersInterface(?ClassDefinition $markersInterface): self - { - $this->markersInterface = $markersInterface; - - return $this; - } - public function getRequestHandlerInterface(): RequestHandlerInterfaceDefinition { return $this->requestHandlerInterface; } - public function setRequestHandlerInterface(RequestHandlerInterfaceDefinition $requestHandlerInterface): self + public function getSingleHttpCode(): ?string { - $this->requestHandlerInterface = $requestHandlerInterface; - - return $this; + return $this->singleHttpCode; } } diff --git a/src/CodeGenerator/Definitions/PropertyDefinition.php b/src/CodeGenerator/Definitions/PropertyDefinition.php index 41dacb31..4b19c96b 100644 --- a/src/CodeGenerator/Definitions/PropertyDefinition.php +++ b/src/CodeGenerator/Definitions/PropertyDefinition.php @@ -10,13 +10,13 @@ final class PropertyDefinition { private Property $specProperty; private string $classPropertyName; - private bool $nullable = true; - private ?DtoDefinition $objectTypeDefinition = null; - private ?string $getterName = null; - private ?string $setterName = null; - private bool $hasGetter = false; - private bool $hasSetter = false; - private bool $inConstructor = false; + private bool $nullable = true; + private ?DtoReference $objectTypeDefinition = null; + private ?string $getterName = null; + private ?string $setterName = null; + private bool $hasGetter = false; + private bool $hasSetter = false; + private bool $inConstructor = false; public function __construct(Property $specProperty) { @@ -60,12 +60,12 @@ public function getScalarTypeId(): ?int return $this->specProperty->getScalarTypeId(); } - public function getObjectTypeDefinition(): ?DtoDefinition + public function getObjectTypeDefinition(): ?DtoReference { return $this->objectTypeDefinition; } - public function setObjectTypeDefinition(?DtoDefinition $objectTypeDefinition): self + public function setObjectTypeDefinition(?DtoReference $objectTypeDefinition): self { $this->objectTypeDefinition = $objectTypeDefinition; diff --git a/src/CodeGenerator/Definitions/RequestDtoDefinition.php b/src/CodeGenerator/Definitions/RequestDtoDefinition.php deleted file mode 100644 index fdfff683..00000000 --- a/src/CodeGenerator/Definitions/RequestDtoDefinition.php +++ /dev/null @@ -1,36 +0,0 @@ - $pathParameters, - 'queryParameters' => $queryParameters, - 'body' => $bodyDtoDefinition, - ]; - - $properties = []; - - foreach ($fields as $name => $definition) { - if ($definition === null) { - continue; - } - - $specProperty = (new Property($name))->setRequired(true); - $properties[] = (new PropertyDefinition($specProperty)) - ->setObjectTypeDefinition($definition); - } - - parent::__construct($properties); - } -} diff --git a/src/CodeGenerator/Definitions/RequestHandlerInterfaceDefinition.php b/src/CodeGenerator/Definitions/RequestHandlerInterfaceDefinition.php index f43846fd..55e02147 100644 --- a/src/CodeGenerator/Definitions/RequestHandlerInterfaceDefinition.php +++ b/src/CodeGenerator/Definitions/RequestHandlerInterfaceDefinition.php @@ -4,33 +4,46 @@ namespace OnMoon\OpenApiServerBundle\CodeGenerator\Definitions; +use OnMoon\OpenApiServerBundle\Interfaces\RequestHandler; + final class RequestHandlerInterfaceDefinition extends GeneratedInterfaceDefinition { - private ?ClassDefinition $requestType = null; - private ?ClassDefinition $responseType = null; + private ?DtoReference $requestType; + /** @var DtoReference[] */ + private array $responseTypes; private string $methodName; private ?string $methodDescription = null; - public function getRequestType(): ?ClassDefinition + /** @param DtoReference[] $responseTypes */ + public function __construct(?DtoReference $requestType, array $responseTypes) + { + $this->requestType = $requestType; + $this->responseTypes = $responseTypes; + $this->setExtends(ClassDefinition::fromFQCN(RequestHandler::class)); + } + + public function getRequestType(): ?DtoReference { return $this->requestType; } - public function setRequestType(?ClassDefinition $requestType): self + public function setRequestType(?DtoReference $requestType): self { $this->requestType = $requestType; return $this; } - public function getResponseType(): ?ClassDefinition + /** @return DtoReference[] */ + public function getResponseTypes(): array { - return $this->responseType; + return $this->responseTypes; } - public function setResponseType(?ClassDefinition $responseType): self + /** @param DtoReference[] $responseTypes */ + public function setResponseTypes(array $responseTypes): self { - $this->responseType = $responseType; + $this->responseTypes = $responseTypes; return $this; } diff --git a/src/CodeGenerator/Definitions/RequestParametersDtoDefinition.php b/src/CodeGenerator/Definitions/RequestParametersDtoDefinition.php deleted file mode 100644 index 17f6a1e7..00000000 --- a/src/CodeGenerator/Definitions/RequestParametersDtoDefinition.php +++ /dev/null @@ -1,9 +0,0 @@ -statusCode = $statusCode; + $this->responseBody = $responseBody; + } + + public function getStatusCode(): string + { + return $this->statusCode; + } + + public function getResponseBody(): DtoReference + { + return $this->responseBody; + } +} diff --git a/src/CodeGenerator/Definitions/ResponseDtoDefinition.php b/src/CodeGenerator/Definitions/ResponseDtoDefinition.php deleted file mode 100644 index 8c7a21bb..00000000 --- a/src/CodeGenerator/Definitions/ResponseDtoDefinition.php +++ /dev/null @@ -1,24 +0,0 @@ -statusCode = $statusCode; - parent::__construct($properties); - } - - public function getStatusCode(): string - { - return $this->statusCode; - } -} diff --git a/src/CodeGenerator/Definitions/ServiceSubscriberDefinition.php b/src/CodeGenerator/Definitions/ServiceSubscriberDefinition.php index fafef8f8..fbf9e455 100644 --- a/src/CodeGenerator/Definitions/ServiceSubscriberDefinition.php +++ b/src/CodeGenerator/Definitions/ServiceSubscriberDefinition.php @@ -4,13 +4,20 @@ namespace OnMoon\OpenApiServerBundle\CodeGenerator\Definitions; +use OnMoon\OpenApiServerBundle\Interfaces\ApiLoader; + final class ServiceSubscriberDefinition extends GeneratedClassDefinition { - /** @var ClassDefinition[] */ - private array $implements = []; + /** @var ClassReference[] */ + private array $implements; + + public function __construct() + { + $this->implements = [ClassDefinition::fromFQCN(ApiLoader::class)]; + } /** - * @return ClassDefinition[] + * @return ClassReference[] */ public function getImplements(): array { @@ -18,7 +25,7 @@ public function getImplements(): array } /** - * @param ClassDefinition[] $implements + * @param ClassReference[] $implements */ public function setImplements(array $implements): self { diff --git a/src/CodeGenerator/Definitions/SpecificationDefinition.php b/src/CodeGenerator/Definitions/SpecificationDefinition.php index 4d716d02..e7e2d406 100644 --- a/src/CodeGenerator/Definitions/SpecificationDefinition.php +++ b/src/CodeGenerator/Definitions/SpecificationDefinition.php @@ -8,17 +8,12 @@ final class SpecificationDefinition { - private SpecificationConfig $specification; - /** @var OperationDefinition[] */ - private array $operations; - /** * @param OperationDefinition[] $operations + * @param ComponentDefinition[] $components */ - public function __construct(SpecificationConfig $specification, array $operations) + public function __construct(private SpecificationConfig $specification, private array $operations, private array $components) { - $this->specification = $specification; - $this->operations = $operations; } /** @@ -33,4 +28,12 @@ public function getSpecification(): SpecificationConfig { return $this->specification; } + + /** + * @return ComponentDefinition[] + */ + public function getComponents(): array + { + return $this->components; + } } diff --git a/src/CodeGenerator/FileGenerator.php b/src/CodeGenerator/FileGenerator.php index e8e5a515..aaad4be1 100644 --- a/src/CodeGenerator/FileGenerator.php +++ b/src/CodeGenerator/FileGenerator.php @@ -5,14 +5,14 @@ namespace OnMoon\OpenApiServerBundle\CodeGenerator; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoReference; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GeneratedFileDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GeneratedInterfaceDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GraphDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\PhpParserGenerators\DtoCodeGenerator; use OnMoon\OpenApiServerBundle\CodeGenerator\PhpParserGenerators\InterfaceCodeGenerator; use OnMoon\OpenApiServerBundle\CodeGenerator\PhpParserGenerators\ServiceSubscriberCodeGenerator; -use function array_merge; +use function array_push; class FileGenerator { @@ -38,19 +38,15 @@ public function generateAllFiles(GraphDefinition $graph): array /** @var GeneratedFileDefinition[] $result */ $result = []; foreach ($graph->getSpecifications() as $specificationDefinition) { + foreach ($specificationDefinition->getComponents() as $component) { + array_push($result, ...$this->generateDtoTree($component->getDto())); + } + foreach ($specificationDefinition->getOperations() as $operation) { - $request = $operation->getRequest(); - if ($request !== null) { - $result = array_merge($result, $this->generateDtoTree($request)); - } + array_push($result, ...$this->generateDtoTree($operation->getRequest())); foreach ($operation->getResponses() as $response) { - $result = array_merge($result, $this->generateDtoTree($response)); - } - - $markersInterface = $operation->getMarkersInterface(); - if ($markersInterface instanceof GeneratedInterfaceDefinition) { - $result[] = $this->interfaceGenerator->generate($markersInterface); + array_push($result, ...$this->generateDtoTree($response->getResponseBody())); } $result[] = $this->interfaceGenerator->generate($operation->getRequestHandlerInterface()); @@ -65,17 +61,16 @@ public function generateAllFiles(GraphDefinition $graph): array /** * @return GeneratedFileDefinition[] */ - public function generateDtoTree(DtoDefinition $root): array + public function generateDtoTree(?DtoReference $root): array { + if (! $root instanceof DtoDefinition) { + return []; + } + $result = []; $result[] = $this->dtoGenerator->generate($root); foreach ($root->getProperties() as $property) { - $object = $property->getObjectTypeDefinition(); - if ($object === null) { - continue; - } - - $result = array_merge($result, $this->generateDtoTree($object)); + array_push($result, ...$this->generateDtoTree($property->getObjectTypeDefinition())); } return $result; diff --git a/src/CodeGenerator/GraphGenerator.php b/src/CodeGenerator/GraphGenerator.php index bfca379f..2eee373a 100644 --- a/src/CodeGenerator/GraphGenerator.php +++ b/src/CodeGenerator/GraphGenerator.php @@ -4,22 +4,26 @@ namespace OnMoon\OpenApiServerBundle\CodeGenerator; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ComponentDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ComponentReference; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoReference; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GraphDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\OperationDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\PropertyDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestBodyDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestParametersDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestHandlerInterfaceDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ServiceSubscriberDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\SpecificationDefinition; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Exception\CannotParseOpenApi; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectReference; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; +use OnMoon\OpenApiServerBundle\Specification\Definitions\Operation; use OnMoon\OpenApiServerBundle\Specification\Definitions\Property; use OnMoon\OpenApiServerBundle\Specification\SpecificationLoader; -use function array_key_exists; use function array_map; +use function count; class GraphGenerator { @@ -36,21 +40,34 @@ public function generateClassGraph(): GraphDefinition foreach ($this->loader->list() as $specificationName => $specificationConfig) { $parsedSpecification = $this->loader->load($specificationName); + $componentDefinitions = []; + $componentSchemas = $parsedSpecification->getComponentSchemas(); + foreach ($componentSchemas as $name => $_objectSchema) { + $componentDefinitions[] = new ComponentDefinition($name); + } + + foreach ($componentDefinitions as $component) { + $component->setDto($this->objectSchemaToDefinition($componentSchemas[$component->getName()], $componentDefinitions)); + } + $operationDefinitions = []; foreach ($parsedSpecification->getOperations() as $operationId => $operation) { - $requestBody = null; - $bodyType = $operation->getRequestBody(); - if ($bodyType !== null) { - $requestBody = new RequestBodyDtoDefinition( - $this->propertiesToDefinitions($bodyType->getProperties()) - ); + $requestDefinition = $this->getRequestDefinition($operation, $componentDefinitions); + + $singleHttpCode = null; + $responses = $this->getResponseDefinitions($operation->getResponses(), $componentDefinitions); + if (count($responses) === 1 && $responses[0]->getResponseBody()->isEmpty()) { + $singleHttpCode = $responses[0]->getStatusCode(); + $responses = []; } - $requestDefinitions = new RequestDtoDefinition( - $requestBody, - $this->parametersToDto('query', $operation->getRequestParameters()), - $this->parametersToDto('path', $operation->getRequestParameters()) + $service = new RequestHandlerInterfaceDefinition( + $requestDefinition, + array_map( + static fn (ResponseDefinition $response) => $response->getResponseBody(), + $responses + ) ); $operationDefinitions[] = new OperationDefinition( @@ -59,12 +76,14 @@ public function generateClassGraph(): GraphDefinition $operationId, $operation->getRequestHandlerName(), $operation->getSummary(), - $requestDefinitions->isEmpty() ? null : $requestDefinitions, - $this->getResponseDtoDefinitions($operation->getResponses()) + $singleHttpCode, + $requestDefinition, + $responses, + $service ); } - $specificationDefinitions[] = new SpecificationDefinition($specificationConfig, $operationDefinitions); + $specificationDefinitions[] = new SpecificationDefinition($specificationConfig, $operationDefinitions, $componentDefinitions); } $serviceSubscriber = new ServiceSubscriberDefinition(); @@ -72,64 +91,88 @@ public function generateClassGraph(): GraphDefinition return new GraphDefinition($specificationDefinitions, $serviceSubscriber); } + /** @param ComponentDefinition[] $components */ + private function getRequestDefinition(Operation $operation, array $components): ?DtoDefinition + { + $fields = [ + 'pathParameters' => $operation->getRequestParameters()['path'] ?? null, + 'queryParameters' => $operation->getRequestParameters()['query'] ?? null, + 'body' => $operation->getRequestBody(), + ]; + + $properties = []; + + foreach ($fields as $name => $definition) { + if ($definition === null) { + continue; + } + + $specProperty = (new Property($name))->setRequired(true); + $properties[] = (new PropertyDefinition($specProperty)) + ->setObjectTypeDefinition($this->objectTypeToDefinition($definition, $components)); + } + + if (count($properties) === 0) { + return null; + } + + return new DtoDefinition($properties); + } + /** - * @param ObjectType[] $responses + * @param array $responses + * @param ComponentDefinition[] $components * - * @return ResponseDtoDefinition[] + * @return ResponseDefinition[] */ - private function getResponseDtoDefinitions(array $responses): array + private function getResponseDefinitions(array $responses, array $components): array { $responseDtoDefinitions = []; foreach ($responses as $statusCode => $response) { - $responseDtoDefinitions[] = new ResponseDtoDefinition( + $responseDtoDefinitions[] = new ResponseDefinition( (string) $statusCode, - $this->propertiesToDefinitions($response->getProperties()) + $this->objectTypeToDefinition($response, $components) ); } return $responseDtoDefinitions; } - /** - * @param ObjectType[] $parameters - */ - private function parametersToDto(string $in, array $parameters): ?RequestParametersDtoDefinition + /** @param ComponentDefinition[] $components */ + private function objectTypeToDefinition(ObjectSchema|ObjectReference $type, array $components): DtoReference { - if (! array_key_exists($in, $parameters)) { - return null; - } - - return new RequestParametersDtoDefinition( - $this->propertiesToDefinitions($parameters[$in]->getProperties()) - ); - } + if ($type instanceof ObjectReference) { + foreach ($components as $component) { + if ($component->getName() === $type->getSchemaName()) { + return new ComponentReference($component); + } + } - private function objectTypeToDefinition(?ObjectType $type): ?DtoDefinition - { - if ($type === null) { - return null; + throw CannotParseOpenApi::becauseUnknownReferenceFound($type->getSchemaName()); } - return new DtoDefinition($this->propertiesToDefinitions($type->getProperties())); + return $this->objectSchemaToDefinition($type, $components); } - /** - * @param Property[] $properties - * - * @return PropertyDefinition[] - */ - private function propertiesToDefinitions(array $properties): array + /** @param ComponentDefinition[] $components */ + private function objectSchemaToDefinition(ObjectSchema $type, array $components): DtoDefinition { - return array_map( - fn (Property $p): PropertyDefinition => $this->propertyToDefinition($p), - $properties - ); + return new DtoDefinition(array_map( + fn (Property $p): PropertyDefinition => $this->propertyToDefinition($p, $components), + $type->getProperties() + )); } - private function propertyToDefinition(Property $property): PropertyDefinition + /** @param ComponentDefinition[] $components */ + private function propertyToDefinition(Property $property, array $components): PropertyDefinition { - return (new PropertyDefinition($property)) - ->setObjectTypeDefinition($this->objectTypeToDefinition($property->getObjectTypeDefinition())); + $definition = new PropertyDefinition($property); + $objectType = $property->getObjectTypeDefinition(); + if ($objectType !== null) { + $definition->setObjectTypeDefinition($this->objectTypeToDefinition($objectType, $components)); + } + + return $definition; } } diff --git a/src/CodeGenerator/InterfaceGenerator.php b/src/CodeGenerator/InterfaceGenerator.php deleted file mode 100644 index ddd0f96f..00000000 --- a/src/CodeGenerator/InterfaceGenerator.php +++ /dev/null @@ -1,88 +0,0 @@ -defaultDto = ClassDefinition::fromFQCN(Dto::class); - $this->defaultResponseDto = ClassDefinition::fromFQCN(ResponseDto::class); - $this->defaultHandler = ClassDefinition::fromFQCN(RequestHandler::class); - } - - public function setAllInterfaces(GraphDefinition $graph): void - { - $graph->getServiceSubscriber()->setImplements([ - ClassDefinition::fromFQCN(ApiLoader::class), - ]); - - foreach ($graph->getSpecifications() as $specificationDefinition) { - foreach ($specificationDefinition->getOperations() as $operation) { - /** @var ClassDefinition|null $responseClass */ - $responseClass = null; - $responses = $operation->getResponses(); - if (count($responses) > 1) { - $makersInterface = new GeneratedInterfaceDefinition(); - $makersInterface->setExtends($this->defaultResponseDto); - $operation->setMarkersInterface($makersInterface); - $responseClass = $makersInterface; - } else { - $makersInterface = $this->defaultResponseDto; - if (count($responses) === 1) { - $responseClass = $responses[0]; - } - } - - foreach ($responses as $response) { - $response->setImplements($makersInterface); - $this->setChildrenRecursive($response, $this->defaultDto); - } - - $request = $operation->getRequest(); - if ($request !== null) { - $request->setImplements($this->defaultDto); - $this->setChildrenRecursive($request, $this->defaultDto); - } - - $service = new RequestHandlerInterfaceDefinition(); - $service - ->setResponseType($responseClass) - ->setRequestType($operation->getRequest()) - ->setExtends($this->defaultHandler); - $operation->setRequestHandlerInterface($service); - } - } - } - - private function setChildrenRecursive(DtoDefinition $root, ClassDefinition $implements): void - { - foreach ($root->getProperties() as $property) { - $objectDefinition = $property->getObjectTypeDefinition(); - if ($objectDefinition === null) { - continue; - } - - $objectDefinition->setImplements($implements); - $this->setChildrenRecursive($objectDefinition, $implements); - } - } -} diff --git a/src/CodeGenerator/NameGenerator.php b/src/CodeGenerator/NameGenerator.php index 2f3681f2..2ecade1e 100644 --- a/src/CodeGenerator/NameGenerator.php +++ b/src/CodeGenerator/NameGenerator.php @@ -6,10 +6,8 @@ use Lukasoppermann\Httpstatus\Httpstatus; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GeneratedInterfaceDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GraphDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Naming\NamingStrategy; use Throwable; @@ -17,11 +15,12 @@ class NameGenerator { - private const DTO_NAMESPACE = 'Dto'; - private const REQUEST_SUFFIX = 'Request'; - private const RESPONSE_SUFFIX = 'Response'; - public const DTO_SUFFIX = 'Dto'; - public const APIS_NAMESPACE = 'Apis'; + private const DTO_NAMESPACE = 'Dto'; + private const REQUEST_SUFFIX = 'Request'; + private const RESPONSE_SUFFIX = 'Response'; + public const DTO_SUFFIX = 'Dto'; + public const APIS_NAMESPACE = 'Apis'; + public const COMPONENTS_NAMESPACE = 'Components'; private NamingStrategy $naming; private Httpstatus $httpstatus; @@ -45,10 +44,20 @@ public function setAllNamesAndPaths(GraphDefinition $graph): void ->setNamespace($this->naming->buildNamespace($this->rootNamespace, 'ServiceSubscriber')); foreach ($graph->getSpecifications() as $specificationDefinition) { - $specification = $specificationDefinition->getSpecification(); - $apiName = $this->naming->stringToNamespace($specification->getNameSpace()); - $apiNamespace = $this->naming->buildNamespace($this->rootNamespace, self::APIS_NAMESPACE, $apiName); - $apiPath = $this->naming->buildPath($this->rootPath, self::APIS_NAMESPACE, $apiName); + $specification = $specificationDefinition->getSpecification(); + $apiName = $this->naming->stringToNamespace($specification->getNameSpace()); + $apiNamespace = $this->naming->buildNamespace($this->rootNamespace, self::APIS_NAMESPACE, $apiName); + $apiPath = $this->naming->buildPath($this->rootPath, self::APIS_NAMESPACE, $apiName); + $componentsNamespace = $this->naming->buildNamespace($this->rootNamespace, self::COMPONENTS_NAMESPACE, $apiName); + $componentsPath = $this->naming->buildPath($this->rootPath, self::COMPONENTS_NAMESPACE, $apiName); + + foreach ($specificationDefinition->getComponents() as $component) { + $componentName = $this->naming->stringToNamespace($component->getName()); + $componentNamespace = $this->naming->buildNamespace($componentsNamespace, $componentName); + $componentPath = $this->naming->buildPath($componentsPath, $componentName); + + $this->setTreeNames($component->getDto(), $componentNamespace, $componentName, $componentPath); + } foreach ($specificationDefinition->getOperations() as $operation) { $operationName = $this->naming->stringToNamespace($operation->getOperationId()); @@ -65,10 +74,8 @@ public function setAllNamesAndPaths(GraphDefinition $graph): void ->setClassName($operationName); $request = $operation->getRequest(); - if ($request !== null) { - $this->setTreePropertyClassNames($request); + if ($request instanceof DtoDefinition) { $this->setRequestNames($request, $operationNamespace, $operationName, $operationPath); - $this->setTreeGettersSetters($request); } $responseNamespace = $this->naming->buildNamespace( @@ -84,27 +91,13 @@ public function setAllNamesAndPaths(GraphDefinition $graph): void ); foreach ($operation->getResponses() as $response) { - $this->setTreePropertyClassNames($response); $this->setResponseNames($response, $responseNamespace, $operationName, $responsePath); - $this->setTreeGettersSetters($response); - } - - $markersInterface = $operation->getMarkersInterface(); - if (! ($markersInterface instanceof GeneratedInterfaceDefinition)) { - continue; } - - $interfaceName = $this->naming->stringToNamespace($operationName . self::RESPONSE_SUFFIX); - $markersInterface - ->setFileName($this->getFileName($interfaceName)) - ->setFilePath($responsePath) - ->setClassName($interfaceName) - ->setNamespace($responseNamespace); } } } - public function setRequestNames(RequestDtoDefinition $request, string $operationNamespace, string $operationName, string $operationPath): void + public function setRequestNames(DtoDefinition $request, string $operationNamespace, string $operationName, string $operationPath): void { $requestDtoNamespace = $this->naming->buildNamespace( $operationNamespace, @@ -120,11 +113,16 @@ public function setRequestNames(RequestDtoDefinition $request, string $operation self::REQUEST_SUFFIX ); - $this->setTreePathsAndClassNames($request, $requestDtoNamespace, $requestDtoClassName, $requestDtoPath); + $this->setTreeNames($request, $requestDtoNamespace, $requestDtoClassName, $requestDtoPath); } - public function setResponseNames(ResponseDtoDefinition $response, string $responseNamespace, string $operationName, string $responsePath): void + public function setResponseNames(ResponseDefinition $response, string $responseNamespace, string $operationName, string $responsePath): void { + $responseBody = $response->getResponseBody(); + if (! $responseBody instanceof DtoDefinition) { + return; + } + try { $statusNamespace = $this->httpstatus->getReasonPhrase((int) $response->getStatusCode()); } catch (Throwable $e) { @@ -139,19 +137,21 @@ public function setResponseNames(ResponseDtoDefinition $response, string $respon ); $responseDtoPath = $this->naming->buildPath($responsePath, $statusNamespace); - $this->setTreePathsAndClassNames($response, $responseDtoNamespace, $responseDtoClassName, $responseDtoPath); + $this->setTreeNames($responseBody, $responseDtoNamespace, $responseDtoClassName, $responseDtoPath); } - public function setTreePathsAndClassNames(DtoDefinition $root, string $namespace, string $className, string $path): void + public function setTreeNames(DtoDefinition $root, string $namespace, string $className, string $path): void { $root->setClassName($className); $root->setFileName($this->getFileName($className)); $root->setFilePath($path); $root->setNamespace($namespace); + $this->setPropertyClassNames($root); + $this->setGettersSetters($root); foreach ($root->getProperties() as $property) { $objectDefinition = $property->getObjectTypeDefinition(); - if ($objectDefinition === null) { + if (! $objectDefinition instanceof DtoDefinition) { continue; } @@ -159,7 +159,7 @@ public function setTreePathsAndClassNames(DtoDefinition $root, string $namespace $subClassName = $this->naming->stringToNamespace($part . self::DTO_SUFFIX); $subNamespace = $this->naming->buildNamespace($namespace, $part); $subPath = $this->naming->buildPath($path, $part); - $this->setTreePathsAndClassNames($objectDefinition, $subNamespace, $subClassName, $subPath); + $this->setTreeNames($objectDefinition, $subNamespace, $subClassName, $subPath); } } @@ -168,23 +168,16 @@ public function getFileName(string $className): string return $className . '.php'; } - public function setTreeGettersSetters(DtoDefinition $root): void + public function setGettersSetters(DtoDefinition $root): void { foreach ($root->getProperties() as $property) { $baseName = ucfirst($this->naming->stringToMethodName($property->getClassPropertyName())); $property->setGetterName('get' . $baseName); $property->setSetterName('set' . $baseName); - - $objectDefinition = $property->getObjectTypeDefinition(); - if ($objectDefinition === null) { - continue; - } - - $this->setTreeGettersSetters($objectDefinition); } } - public function setTreePropertyClassNames(DtoDefinition $root): void + public function setPropertyClassNames(DtoDefinition $root): void { foreach ($root->getProperties() as $property) { $propertyName = $property->getSpecPropertyName(); @@ -194,13 +187,6 @@ public function setTreePropertyClassNames(DtoDefinition $root): void } $property->setClassPropertyName($propertyName); - - $objectDefinition = $property->getObjectTypeDefinition(); - if ($objectDefinition === null) { - continue; - } - - $this->setTreePropertyClassNames($objectDefinition); } } } diff --git a/src/CodeGenerator/Naming/DefaultNamingStrategy.php b/src/CodeGenerator/Naming/DefaultNamingStrategy.php index 3eb14f92..76b5b4a4 100644 --- a/src/CodeGenerator/Naming/DefaultNamingStrategy.php +++ b/src/CodeGenerator/Naming/DefaultNamingStrategy.php @@ -39,7 +39,7 @@ public function __construct( public function isAllowedPhpPropertyName(string $name): bool { - return preg_match('/^\d/', $name) === 0 && preg_match('/^[A-Za-z0-9_]+$/i', $name) === 1; + return preg_match('/^\d/', $name) === 0 && preg_match('/^[A-Za-z0-9_]+$/', $name) === 1; } public function getInterfaceFQCN(string $apiNameSpace, string $operationId): string diff --git a/src/CodeGenerator/PhpParserGenerators/DtoCodeGenerator.php b/src/CodeGenerator/PhpParserGenerators/DtoCodeGenerator.php index 0e2a0f11..ccaaeb8e 100644 --- a/src/CodeGenerator/PhpParserGenerators/DtoCodeGenerator.php +++ b/src/CodeGenerator/PhpParserGenerators/DtoCodeGenerator.php @@ -8,7 +8,6 @@ use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GeneratedFileDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\PropertyDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDtoDefinition; use PhpParser\Builder; use PhpParser\Builder\Method; use PhpParser\Builder\Param; @@ -58,11 +57,6 @@ public function generate(DtoDefinition $definition): GeneratedFileDefinition $classBuilder->addStmts($this->generateConstructor($fileBuilder, $definition)); $classBuilder->addStmts($this->generateGetters($fileBuilder, $definition)); $classBuilder->addStmts($this->generateSetters($fileBuilder, $definition)); - - if ($definition instanceof ResponseDtoDefinition) { - $classBuilder->addStmt($this->generateResponseCodeStaticMethod($definition)); - } - $classBuilder->addStmt($this->generateToArray($fileBuilder, $definition)); $classBuilder->addStmt($this->generateFromArray($fileBuilder, $definition)); @@ -266,29 +260,6 @@ private function generateSetter(FileBuilder $builder, PropertyDefinition $defini return $method; } - private function generateResponseCodeStaticMethod(ResponseDtoDefinition $definition): Method - { - $responseCode = $definition->getStatusCode(); - $method = $this - ->factory - ->method('_getResponseCode') - ->makePublic() - ->makeStatic() - ->setReturnType('string') - ->addStmt( - new Return_( - new String_($responseCode) - ) - ); - if ($this->fullDocs) { - $method->setDocComment( - $this->getDocComment(['@return string']) - ); - } - - return $method; - } - private function generateToArray(FileBuilder $builder, DtoDefinition $definition): Method { return $this diff --git a/src/CodeGenerator/PhpParserGenerators/FileBuilder.php b/src/CodeGenerator/PhpParserGenerators/FileBuilder.php index 12fb4499..6dedcadc 100644 --- a/src/CodeGenerator/PhpParserGenerators/FileBuilder.php +++ b/src/CodeGenerator/PhpParserGenerators/FileBuilder.php @@ -5,13 +5,14 @@ namespace OnMoon\OpenApiServerBundle\CodeGenerator\PhpParserGenerators; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ClassDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ClassReference; use PhpParser\Builder; use PhpParser\Builder\Namespace_; use PhpParser\Builder\Use_; use PhpParser\Node; use PhpParser\Node\Stmt\Use_ as UseStmt; -use function array_search; +use function in_array; use function Safe\preg_match; use function Safe\preg_replace; use function Safe\substr; @@ -30,7 +31,7 @@ public function __construct(ClassDefinition $definition) $this->namespace = new Namespace_($definition->getNamespace()); } - public function getReference(ClassDefinition $class): string + public function getReference(ClassReference $class): string { $fullName = $class->getFQCN(); @@ -40,7 +41,7 @@ public function getReference(ClassDefinition $class): string $reference = $class->getClassName(); $rename = false; - while (array_search($reference, $this->references, true) !== false) { + while (in_array($reference, $this->references, true)) { $reference = $this->rename($reference); $rename = true; } diff --git a/src/CodeGenerator/PhpParserGenerators/InterfaceCodeGenerator.php b/src/CodeGenerator/PhpParserGenerators/InterfaceCodeGenerator.php index 57abb304..7312472c 100644 --- a/src/CodeGenerator/PhpParserGenerators/InterfaceCodeGenerator.php +++ b/src/CodeGenerator/PhpParserGenerators/InterfaceCodeGenerator.php @@ -4,16 +4,18 @@ namespace OnMoon\OpenApiServerBundle\CodeGenerator\PhpParserGenerators; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ClassReference; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GeneratedFileDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GeneratedInterfaceDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestHandlerInterfaceDefinition; +use function array_map; use function count; +use function implode; use function Safe\sprintf; class InterfaceCodeGenerator extends CodeGenerator { - public function generate(GeneratedInterfaceDefinition $definition): GeneratedFileDefinition + public function generate(RequestHandlerInterfaceDefinition $definition): GeneratedFileDefinition { $fileBuilder = new FileBuilder($definition); @@ -27,55 +29,54 @@ public function generate(GeneratedInterfaceDefinition $definition): GeneratedFil $interfaceBuilder->extend($fileBuilder->getReference($extends)); } - if ($definition instanceof RequestHandlerInterfaceDefinition) { - $methodBuilder = $this->factory->method($definition->getMethodName())->makePublic(); - $request = $definition->getRequestType(); - $docBlocks = []; + $methodBuilder = $this->factory->method($definition->getMethodName())->makePublic(); + $request = $definition->getRequestType(); + $docBlocks = []; - if ($request !== null) { - $requestClass = $fileBuilder->getReference($request); - $methodBuilder->addParam( - $this->factory->param('request')->setType($requestClass) + if ($request !== null) { + $requestClass = $fileBuilder->getReference($request); + $methodBuilder->addParam( + $this->factory->param('request')->setType($requestClass) + ); + if ($this->fullDocs) { + $docBlocks[] = sprintf( + '@param %s $%s', + $requestClass, + 'request' ); - if ($this->fullDocs) { - $docBlocks[] = sprintf( - '@param %s $%s', - $requestClass, - 'request' - ); - } - } - - $response = $definition->getResponseType(); - if ($response !== null) { - $responseClass = $fileBuilder->getReference($response); - $methodBuilder->setReturnType($responseClass); - if ($this->fullDocs) { - $docBlocks[] = sprintf( - '@return %s', - $responseClass - ); - } - } else { - $methodBuilder->setReturnType('void'); } + } - $description = $definition->getMethodDescription(); - if ($description !== null) { - if (count($docBlocks) > 0) { - $docBlocks = [$description, '', ...$docBlocks]; - } else { - $docBlocks[] = $description; - } + $responses = $definition->getResponseTypes(); + if (count($responses) > 0) { + $responseClasses = array_map(static fn (ClassReference $response) => $fileBuilder->getReference($response), $responses); + $unionClass = implode('|', $responseClasses); + $methodBuilder->setReturnType($unionClass); + if ($this->fullDocs) { + $docBlocks[] = sprintf( + '@return %s', + $unionClass + ); } + } else { + $methodBuilder->setReturnType('void'); + } + $description = $definition->getMethodDescription(); + if ($description !== null) { if (count($docBlocks) > 0) { - $methodBuilder->setDocComment($this->getDocComment($docBlocks)); + $docBlocks = [$description, '', ...$docBlocks]; + } else { + $docBlocks[] = $description; } + } - $interfaceBuilder->addStmt($methodBuilder); + if (count($docBlocks) > 0) { + $methodBuilder->setDocComment($this->getDocComment($docBlocks)); } + $interfaceBuilder->addStmt($methodBuilder); + $fileBuilder = $fileBuilder->addStmt($interfaceBuilder); return new GeneratedFileDefinition( diff --git a/src/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGenerator.php b/src/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGenerator.php index ba39ae45..cefe0a0c 100644 --- a/src/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGenerator.php +++ b/src/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGenerator.php @@ -10,6 +10,7 @@ use OnMoon\OpenApiServerBundle\Interfaces\RequestHandler; use PhpParser\Node\Arg; use PhpParser\Node\Expr\Array_; +use PhpParser\Node\Expr\ArrayDimFetch; use PhpParser\Node\Expr\ArrayItem; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\BinaryOp\Concat; @@ -45,10 +46,11 @@ public function generate(GraphDefinition $graphDefinition): GeneratedFileDefinit $classBuilder->implement($fileBuilder->getReference($implement)); } - $services = []; + $services = []; + $responseCodeMapper = []; foreach ($graphDefinition->getSpecifications() as $specification) { foreach ($specification->getOperations() as $operation) { - $services[] = new ArrayItem( + $services[] = new ArrayItem( new Concat( new String_('?'), new ClassConstFetch( @@ -58,9 +60,39 @@ public function generate(GraphDefinition $graphDefinition): GeneratedFileDefinit ), new String_($operation->getRequestHandlerName()) ); + $responseTypes = []; + if ($operation->getSingleHttpCode() !== null) { + $responseTypes[] = new ArrayItem( + new Array_([new ArrayItem(new String_($operation->getSingleHttpCode()))], ['kind' => Array_::KIND_SHORT]), + new String_('void') + ); + } else { + foreach ($operation->getResponses() as $response) { + $responseTypes[] = new ArrayItem( + new Array_([new ArrayItem(new String_($response->getStatusCode()))], ['kind' => Array_::KIND_SHORT]), + new ClassConstFetch( + new Name($fileBuilder->getReference($response->getResponseBody())), + 'class' + ) + ); + } + } + + $responseCodeMapper[] = new ArrayItem( + new Array_($responseTypes, ['kind' => Array_::KIND_SHORT]), + new ClassConstFetch( + new Name($fileBuilder->getReference($operation->getRequestHandlerInterface())), + 'class' + ) + ); } } + $httpCodeMapper = $this + ->factory + ->classConst('HTTP_CODES', new Array_($responseCodeMapper, ['kind' => Array_::KIND_SHORT])) + ->makePrivate(); + $property = $this ->factory ->property('locator') @@ -96,7 +128,8 @@ public function generate(GraphDefinition $graphDefinition): GeneratedFileDefinit ->addStmt( new Return_( new Array_( - $services + $services, + ['kind' => Array_::KIND_SHORT] ) ) ); @@ -142,7 +175,27 @@ public function generate(GraphDefinition $graphDefinition): GeneratedFileDefinit $getRequestHandler->setDocComment($this->getDocComment($docs)); } - $classBuilder->addStmts([$property, $constructor, $getSubscribedServices, $getRequestHandler]); + $getAllowedCodes = $this->factory->method('getAllowedCodes') + ->setReturnType('array') + ->makePublic() + ->setDocComment($this->getDocComment(['@return string[]'])) + ->addParams([ + $this->factory->param('apiClass')->setType('string'), + $this->factory->param('dtoClass')->setType('string'), + ]) + ->addStmt( + new Return_( + new ArrayDimFetch( + new ArrayDimFetch( + new ClassConstFetch(new Name('self'), 'HTTP_CODES'), + new Variable('apiClass') + ), + new Variable('dtoClass') + ) + ) + ); + + $classBuilder->addStmts([$property, $constructor, $httpCodeMapper, $getSubscribedServices, $getRequestHandler, $getAllowedCodes]); $fileBuilder->addStmt($classBuilder); diff --git a/src/Command/DeleteGeneratedCodeCommand.php b/src/Command/DeleteGeneratedCodeCommand.php index c413e33a..c0785cde 100644 --- a/src/Command/DeleteGeneratedCodeCommand.php +++ b/src/Command/DeleteGeneratedCodeCommand.php @@ -85,7 +85,7 @@ private function recursiveDelete(string $directoryPath): void /** @var SplFileInfo[] $iterator */ $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( - $this->rootPath, + $directoryPath, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST diff --git a/src/Command/GenerateApiCodeCommand.php b/src/Command/GenerateApiCodeCommand.php index 4ba3c0c8..8ebce175 100644 --- a/src/Command/GenerateApiCodeCommand.php +++ b/src/Command/GenerateApiCodeCommand.php @@ -4,15 +4,25 @@ namespace OnMoon\OpenApiServerBundle\Command; +use FilesystemIterator; use OnMoon\OpenApiServerBundle\CodeGenerator\ApiServerCodeGenerator; use OnMoon\OpenApiServerBundle\Specification\SpecificationLoader; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use SplFileInfo; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Contracts\Cache\TagAwareCacheInterface; +use function in_array; +use function is_dir; +use function iterator_count; +use function Safe\rmdir; use function Safe\sprintf; +use function Safe\unlink; #[AsCommand(name: 'open-api:generate')] final class GenerateApiCodeCommand extends Command @@ -38,7 +48,14 @@ public function __construct( protected function configure(): void { - $this->setDescription('Generates API server code'); + $this + ->setDescription('Generates API server code') + ->addOption( + 'keep', + 'k', + InputOption::VALUE_NONE, + 'Keep files that are no longer part of specification' + ); } /** @@ -49,11 +66,64 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { + $keep = (bool) $input->getOption('keep'); + $this->cache->invalidateTags([SpecificationLoader::CACHE_TAG]); - $this->apiServerCodeGenerator->generate(); + $files = $this->apiServerCodeGenerator->generate(); + + if (! $keep) { + $this->removeExtraFiles($this->rootPath, $files); + $this->removeEmptyDirectories($this->rootPath); + } $output->writeln(sprintf('API server code generated in: %s', $this->rootPath)); return 0; } + + /** @param string[] $generatedFiles */ + private function removeExtraFiles(string $root, array $generatedFiles): void + { + if (! is_dir($root)) { + return; + } + + /** @var SplFileInfo[] $iterator */ + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $root, + FilesystemIterator::SKIP_DOTS + ), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($iterator as $directoryOrFile) { + if ($directoryOrFile->isDir() || in_array($directoryOrFile->getPathname(), $generatedFiles, true)) { + continue; + } + + unlink($directoryOrFile->getPathname()); + } + } + + private function removeEmptyDirectories(string $root): void + { + if (! is_dir($root)) { + return; + } + + $directories = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST + ); + + /** @var SplFileInfo $dir */ + foreach ($directories as $dir) { + if (! $dir->isDir() || iterator_count($directories->callGetChildren()) !== 0) { + continue; + } + + rmdir($dir->getPathname()); + } + } } diff --git a/src/Command/ProcessFactory.php b/src/Command/ProcessFactory.php deleted file mode 100644 index 31173624..00000000 --- a/src/Command/ProcessFactory.php +++ /dev/null @@ -1,15 +0,0 @@ -rootPath = $rootPath; - $this->processFactory = $processFactory; - - parent::__construct($name); - } - - /** - * phpcs:disable SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint - * @var string|null - */ - protected static $defaultName = 'open-api:refresh'; - - protected function configure(): void - { - $this->setDescription('Refreshes API server code'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - if (! $this->isConfirmed($input, $output)) { - return 0; - } - - $deleteCommandProcess = $this->processFactory->getProcess(['php', 'bin/console', DeleteGeneratedCodeCommand::COMMAND, '-y']); - $generateCommandProcess = $this->processFactory->getProcess(['php', 'bin/console', GenerateApiCodeCommand::COMMAND]); - - return $deleteCommandProcess->run($this->processOutputHandler($output)) === 0 && - $generateCommandProcess->run($this->processOutputHandler($output)) === 0 ? - 0 : - 1; - } - - private function isConfirmed(InputInterface $input, OutputInterface $output): bool - { - /** @var QuestionHelper $questionHelper */ - $questionHelper = $this->getHelper('question'); - $question = new ConfirmationQuestion( - sprintf( - 'Delete all contents of the directory %s? (y/n): ', - $this->rootPath - ), - false - ); - - return (bool) $questionHelper->ask($input, $output, $question); - } - - private function processOutputHandler(OutputInterface $output): callable - { - return static function (string $type, string $data) use ($output): void { - if ($type === Process::ERR && $output instanceof ConsoleOutputInterface) { - $output->getErrorOutput()->writeln($data); - - return; - } - - $output->writeln($data); - }; - } -} diff --git a/src/Command/SymfonyProcessFactory.php b/src/Command/SymfonyProcessFactory.php deleted file mode 100644 index 9e0ad0e4..00000000 --- a/src/Command/SymfonyProcessFactory.php +++ /dev/null @@ -1,18 +0,0 @@ -executeRequestHandler($requestHandler, $methodName, $requestDto); $this->eventDispatcher->dispatch(new ResponseDtoEvent($responseDto, $operationId, $specification)); - $response = $this->createResponse($requestHandler, $operation, $responseDto); + $response = $this->createResponse($requestHandler, $operation, $requestHandlerInterface, $responseDto); $this->eventDispatcher->dispatch(new ResponseEvent($response, $operationId, $specification)); return $response; @@ -129,8 +133,8 @@ private function executeRequestHandler( RequestHandler $requestHandler, string $methodName, ?Dto $requestDto - ): ?ResponseDto { - /** @var ResponseDto|null $responseDto */ + ): ?Dto { + /** @var Dto|null $responseDto */ $responseDto = $requestDto !== null ? $requestHandler->{$methodName}($requestDto) : $requestHandler->{$methodName}(); @@ -143,17 +147,14 @@ private function executeRequestHandler( */ private function getRequestHandler(Request $request, Operation $operation): array { - if ($this->apiLoader === null) { - throw ApiCallFailed::becauseApiLoaderNotFound(); - } - $handlerName = $operation->getRequestHandlerName(); - $requestHandlers = $this->apiLoader::getSubscribedServices(); + $requestHandlers = $this->getApiLoader()::getSubscribedServices(); + /** @var String $requestHandlerSubscribedString */ $requestHandlerSubscribedString = $requestHandlers[$handlerName]; /** @psalm-var class-string $requestHandlerInterface */ $requestHandlerInterface = ltrim($requestHandlerSubscribedString, '?'); - $requestHandler = $this->apiLoader->get($handlerName); + $requestHandler = $this->getApiLoader()->get($handlerName); if ($requestHandler === null) { throw ApiCallFailed::becauseNotImplemented($requestHandlerInterface); @@ -170,31 +171,69 @@ private function getRequestHandler(Request $request, Operation $operation): arra return [$requestHandlerInterface, $requestHandler]; } - private function createResponse(RequestHandler $requestHandler, Operation $operation, ?ResponseDto $responseDto = null): Response - { + private function createResponse( + RequestHandler $requestHandler, + Operation $operation, + string $handlerInterface, + ?Dto $responseDto = null + ): Response { $response = new JsonResponse(); $response->setEncodingOptions(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); - $statusCode = null; + $allowedCodes = $this->getApiLoader()->getAllowedCodes( + $handlerInterface, + $responseDto !== null ? get_class($responseDto) : 'void' + ); - if ($responseDto instanceof ResponseDto) { - $responseData = $this->serializer->createResponseFromDto($responseDto, $operation); - $response->setData($responseData); - $dtoStatusCode = (int) $responseDto::_getResponseCode(); - $statusCode = $dtoStatusCode !== 0 ? $dtoStatusCode : $statusCode; + $guessedCode = null; + if (count($allowedCodes) === 1 && is_numeric($allowedCodes[0])) { + $guessedCode = (int) $allowedCodes[0]; } if ($requestHandler instanceof GetResponseCode) { - $statusCode = $requestHandler->getResponseCode($statusCode) ?? $statusCode; + $statusCode = $requestHandler->getResponseCode($guessedCode) ?? $guessedCode; + } else { + $statusCode = $guessedCode; + } + + if ($statusCode === null) { + throw ApiCallFailed::becauseNoResponseCodeSet(); } - $statusCode ??= Response::HTTP_OK; + $matchedCode = $this->findMatchingResponseCode($statusCode, $allowedCodes); $response->setStatusCode($statusCode); + if ($responseDto !== null) { + $responseData = $this->serializer->createResponseFromDto($responseDto, $operation->getResponse($matchedCode)->getSchema()); + $response->setData($responseData); + } + return $response; } + /** @param string[] $allowedCodes */ + private function findMatchingResponseCode(int $statusCode, array $allowedCodes): string + { + $code = (string) $statusCode; + if (in_array($code, $allowedCodes, true)) { + return $code; + } + + $code = (string) intdiv($statusCode, 100) . 'XX'; + foreach ($allowedCodes as $allowedCode) { + if (strcasecmp($code, $allowedCode) === 0) { + return $allowedCode; + } + } + + if (in_array('default', $allowedCodes, true)) { + return 'default'; + } + + throw ApiCallFailed::becauseWrongResponseCodeSet($allowedCodes); + } + private function getRoute(Request $request): Route { /** @var string $routeName */ @@ -244,4 +283,13 @@ private function getMethodAndInputDtoFQCN(string $requestHandlerInterface): arra return [$methodName, $inputTypeName]; } + + private function getApiLoader(): ApiLoader + { + if ($this->apiLoader === null) { + throw ApiCallFailed::becauseApiLoaderNotFound(); + } + + return $this->apiLoader; + } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index a398f10f..07a8b06c 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -17,10 +17,11 @@ public function getConfigTreeBuilder(): TreeBuilder ->children() ->scalarNode('root_path')->end() ->scalarNode('root_name_space')->defaultValue('App\Generated')->cannotBeEmpty()->end() - ->scalarNode('language_level')->defaultValue('7.4.0')->cannotBeEmpty()->end() + ->scalarNode('language_level')->defaultValue('8.0.0')->cannotBeEmpty()->end() ->scalarNode('generated_dir_permissions')->defaultValue('0755')->cannotBeEmpty()->end() ->booleanNode('full_doc_blocks')->defaultValue(false)->end() ->booleanNode('send_nulls')->defaultValue(false)->end() + ->arrayNode('skip_http_codes')->scalarPrototype()->end()->defaultValue([])->end() ->arrayNode('specs') ->arrayPrototype() ->children() diff --git a/src/DependencyInjection/OpenApiServerExtension.php b/src/DependencyInjection/OpenApiServerExtension.php index 0aa5a570..fea53b23 100644 --- a/src/DependencyInjection/OpenApiServerExtension.php +++ b/src/DependencyInjection/OpenApiServerExtension.php @@ -35,6 +35,7 @@ public function load(array $configs, ContainerBuilder $container): void * generated_dir_permissions: string, * full_doc_blocks: bool, * send_nulls: bool, + * skip_http_codes: array, * specs: array{ * path: string, * type?: string, @@ -65,6 +66,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('openapi.generated.code.language.level', $config['language_level']); $container->setParameter('openapi.generated.code.dir.permissions', $config['generated_dir_permissions']); $container->setParameter('openapi.generated.code.full.doc.blocks', $config['full_doc_blocks']); + $container->setParameter('openapi.generated.code.skip.http.codes', $config['skip_http_codes']); $container->setParameter('openapi.send.nulls', $config['send_nulls']); $definition = $container->getDefinition(SpecificationLoader::class); diff --git a/src/Event/Server/ResponseDtoEvent.php b/src/Event/Server/ResponseDtoEvent.php index dd8593b6..a14e0eb2 100644 --- a/src/Event/Server/ResponseDtoEvent.php +++ b/src/Event/Server/ResponseDtoEvent.php @@ -4,7 +4,7 @@ namespace OnMoon\OpenApiServerBundle\Event\Server; -use OnMoon\OpenApiServerBundle\Interfaces\ResponseDto; +use OnMoon\OpenApiServerBundle\Interfaces\Dto; use OnMoon\OpenApiServerBundle\Specification\Definitions\Specification; use Symfony\Contracts\EventDispatcher\Event; @@ -26,18 +26,18 @@ */ final class ResponseDtoEvent extends Event { - private ?ResponseDto $responseDto; + private ?Dto $responseDto; private string $operationId; private Specification $specification; - public function __construct(?ResponseDto $responseDto, string $operationId, Specification $specification) + public function __construct(?Dto $responseDto, string $operationId, Specification $specification) { $this->responseDto = $responseDto; $this->operationId = $operationId; $this->specification = $specification; } - public function getResponseDto(): ?ResponseDto + public function getResponseDto(): ?Dto { return $this->responseDto; } diff --git a/src/Exception/ApiCallFailed.php b/src/Exception/ApiCallFailed.php index c9ce9cd9..4898bec3 100644 --- a/src/Exception/ApiCallFailed.php +++ b/src/Exception/ApiCallFailed.php @@ -4,6 +4,7 @@ namespace OnMoon\OpenApiServerBundle\Exception; +use function implode; use function Safe\sprintf; final class ApiCallFailed extends OpenApiError @@ -22,4 +23,15 @@ public static function becauseNotImplemented(string $interface): self ) ); } + + public static function becauseNoResponseCodeSet(): self + { + return new self('Response type is ambiguous, please set response code manually'); + } + + /** @param string[] $allowedCodes */ + public static function becauseWrongResponseCodeSet(array $allowedCodes): self + { + return new self('Response code does not match specification, allowed are ' . implode(', ', $allowedCodes)); + } } diff --git a/src/Exception/CannotParseOpenApi.php b/src/Exception/CannotParseOpenApi.php index b2aa30d8..809f3b59 100644 --- a/src/Exception/CannotParseOpenApi.php +++ b/src/Exception/CannotParseOpenApi.php @@ -26,6 +26,11 @@ public static function becausePropertyIsNotScheme(): self return new self('Property is not scheme'); } + public static function becauseUnknownReferenceFound(string $name): self + { + return new self(sprintf('Component "%s does not exist"', $name)); + } + /** @param array{method:string,url:string,path:string} $context */ public static function becauseDuplicateOperationId(string $id, array $context): self { diff --git a/src/Interfaces/ApiLoader.php b/src/Interfaces/ApiLoader.php index 47068802..3ebe0214 100644 --- a/src/Interfaces/ApiLoader.php +++ b/src/Interfaces/ApiLoader.php @@ -9,4 +9,7 @@ interface ApiLoader extends ServiceSubscriberInterface { public function get(string $interfaceName): ?RequestHandler; + + /** @return string[] */ + public function getAllowedCodes(string $apiClass, string $dtoClass): array; } diff --git a/src/Interfaces/ResponseDto.php b/src/Interfaces/ResponseDto.php deleted file mode 100644 index 8da8f5c4..00000000 --- a/src/Interfaces/ResponseDto.php +++ /dev/null @@ -1,11 +0,0 @@ - %openapi.generated.code.root.path% - - - %openapi.generated.code.root.path% - %openapi.send.nulls% @@ -63,10 +59,10 @@ - - + + %openapi.generated.code.skip.http.codes% + - diff --git a/src/Serializer/ArrayDtoSerializer.php b/src/Serializer/ArrayDtoSerializer.php index 648e3730..9343b94a 100644 --- a/src/Serializer/ArrayDtoSerializer.php +++ b/src/Serializer/ArrayDtoSerializer.php @@ -6,8 +6,7 @@ use Exception; use OnMoon\OpenApiServerBundle\Interfaces\Dto; -use OnMoon\OpenApiServerBundle\Interfaces\ResponseDto; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\Operation; use OnMoon\OpenApiServerBundle\Types\ScalarTypesResolver; use Symfony\Component\HttpFoundation\Request; @@ -50,6 +49,7 @@ public function createRequestDto( $bodyType = $operation->getRequestBody(); if ($bodyType !== null) { + /** @var resource|string $source */ $source = $request->getContent(); if (is_resource($source)) { throw new Exception('Expecting string as contents, resource received'); @@ -57,7 +57,7 @@ public function createRequestDto( /** @var mixed[] $rawBody */ $rawBody = json_decode($source, true); - $input['body'] = $this->convert(true, $rawBody, $bodyType); + $input['body'] = $this->convert(true, $rawBody, $bodyType->getSchema()); } /** @@ -69,12 +69,9 @@ public function createRequestDto( } /** @inheritDoc */ - public function createResponseFromDto(ResponseDto $responseDto, Operation $operation): array + public function createResponseFromDto(Dto $responseDto, ObjectSchema $definition): array { - $statusCode = $responseDto::_getResponseCode(); - $source = $responseDto->toArray(); - - return $this->convert(false, $source, $operation->getResponse($statusCode)); + return $this->convert(false, $responseDto->toArray(), $definition); } /** @@ -87,7 +84,7 @@ public function createResponseFromDto(ResponseDto $responseDto, Operation $opera * @psalm-suppress MixedArgument * @psalm-suppress MixedAssignment */ - private function convert(bool $deserialize, array $source, ObjectType $params): array + private function convert(bool $deserialize, array $source, ObjectSchema $params): array { $result = []; foreach ($params->getProperties() as $property) { @@ -114,7 +111,7 @@ private function convert(bool $deserialize, array $source, ObjectType $params): if ($objectType !== null) { /** @psalm-suppress MissingClosureParamType */ - $converter = fn ($v) => $this->convert($deserialize, $v, $objectType); + $converter = fn ($v) => $this->convert($deserialize, $v, $objectType->getSchema()); } else { /** @psalm-suppress MissingClosureParamType */ $converter = fn ($v) => $this->resolver->convert($deserialize, $typeId ?? 0, $v); diff --git a/src/Serializer/DtoSerializer.php b/src/Serializer/DtoSerializer.php index de7ff53d..d5ef15ec 100644 --- a/src/Serializer/DtoSerializer.php +++ b/src/Serializer/DtoSerializer.php @@ -5,7 +5,7 @@ namespace OnMoon\OpenApiServerBundle\Serializer; use OnMoon\OpenApiServerBundle\Interfaces\Dto; -use OnMoon\OpenApiServerBundle\Interfaces\ResponseDto; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\Operation; use Symfony\Component\HttpFoundation\Request; @@ -21,5 +21,5 @@ public function createRequestDto( ): Dto; /** @return mixed[] */ - public function createResponseFromDto(ResponseDto $responseDto, Operation $operation): array; + public function createResponseFromDto(Dto $responseDto, ObjectSchema $definition): array; } diff --git a/src/Specification/Definitions/ComponentArray.php b/src/Specification/Definitions/ComponentArray.php new file mode 100644 index 00000000..242c88c9 --- /dev/null +++ b/src/Specification/Definitions/ComponentArray.php @@ -0,0 +1,12 @@ + */ +class ComponentArray extends ArrayObject +{ +} diff --git a/src/Specification/Definitions/GetSchema.php b/src/Specification/Definitions/GetSchema.php new file mode 100644 index 00000000..06fb89c1 --- /dev/null +++ b/src/Specification/Definitions/GetSchema.php @@ -0,0 +1,10 @@ +schemaName; + } + + public function getReferencedObject(): ObjectSchema + { + return $this->referencedObject; + } + + public function getSchema(): ObjectSchema + { + return $this->getReferencedObject(); + } +} diff --git a/src/Specification/Definitions/ObjectType.php b/src/Specification/Definitions/ObjectSchema.php similarity index 78% rename from src/Specification/Definitions/ObjectType.php rename to src/Specification/Definitions/ObjectSchema.php index 81ca8c83..da4dbe25 100644 --- a/src/Specification/Definitions/ObjectType.php +++ b/src/Specification/Definitions/ObjectSchema.php @@ -4,7 +4,7 @@ namespace OnMoon\OpenApiServerBundle\Specification\Definitions; -final class ObjectType +final class ObjectSchema implements GetSchema { /** @var Property[] $properties; */ private array $properties; @@ -24,4 +24,9 @@ public function getProperties(): array { return $this->properties; } + + public function getSchema(): ObjectSchema + { + return $this; + } } diff --git a/src/Specification/Definitions/Operation.php b/src/Specification/Definitions/Operation.php index 821e9c28..ab33b554 100644 --- a/src/Specification/Definitions/Operation.php +++ b/src/Specification/Definitions/Operation.php @@ -6,45 +6,19 @@ final class Operation { - private string $url; - private string $method; - private string $requestHandlerName; - private ?string $summary; - private ?ObjectType $requestBody; - - /** - * @var ObjectType[] - * @psalm-var array - */ - private array $requestParameters; - /** - * @var ObjectType[] - * @psalm-var array - */ - private array $responses; - /** - * @param ObjectType[] $requestParameters - * @param ObjectType[] $responses - * @psalm-param array $requestParameters - * @psalm-param array $responses + * @param array $requestParameters + * @param array $responses */ public function __construct( - string $url, - string $method, - string $requestHandlerName, - ?string $summary = null, - ?ObjectType $requestBody = null, - array $requestParameters = [], - array $responses = [] + private string $url, + private string $method, + private string $requestHandlerName, + private ?string $summary = null, + private ObjectSchema|ObjectReference|null $requestBody = null, + private array $requestParameters = [], + private array $responses = [] ) { - $this->url = $url; - $this->method = $method; - $this->requestHandlerName = $requestHandlerName; - $this->summary = $summary; - $this->requestBody = $requestBody; - $this->requestParameters = $requestParameters; - $this->responses = $responses; } public function getUrl(): string @@ -67,14 +41,13 @@ public function getSummary(): ?string return $this->summary; } - public function getRequestBody(): ?ObjectType + public function getRequestBody(): ObjectSchema|ObjectReference|null { return $this->requestBody; } /** - * @return ObjectType[] - * @psalm-return array + * @return array */ public function getRequestParameters(): array { @@ -82,15 +55,14 @@ public function getRequestParameters(): array } /** - * @return ObjectType[] - * @psalm-return array + * @return array */ public function getResponses(): array { return $this->responses; } - public function getResponse(string $code): ObjectType + public function getResponse(string $code): ObjectSchema|ObjectReference { return $this->responses[$code]; } diff --git a/src/Specification/Definitions/Property.php b/src/Specification/Definitions/Property.php index 314bf15c..a3469838 100644 --- a/src/Specification/Definitions/Property.php +++ b/src/Specification/Definitions/Property.php @@ -10,13 +10,13 @@ final class Property private bool $array = false; /** @var string|int|float|bool|null */ - private $defaultValue = null; - private bool $required = false; - private bool $nullable = false; - private ?int $scalarTypeId = null; - private ?ObjectType $objectTypeDefinition = null; - private ?string $description = null; - private ?string $pattern = null; + private $defaultValue = null; + private bool $required = false; + private bool $nullable = false; + private ?int $scalarTypeId = null; + private ObjectSchema|ObjectReference|null $objectTypeDefinition = null; + private ?string $description = null; + private ?string $pattern = null; public function __construct(string $name) { @@ -91,7 +91,7 @@ public function setScalarTypeId(?int $scalarTypeId): self return $this; } - public function getObjectTypeDefinition(): ?ObjectType + public function getObjectTypeDefinition(): ObjectSchema|ObjectReference|null { return $this->objectTypeDefinition; } @@ -99,7 +99,7 @@ public function getObjectTypeDefinition(): ?ObjectType /** * @return Property */ - public function setObjectTypeDefinition(?ObjectType $objectTypeDefinition): self + public function setObjectTypeDefinition(ObjectSchema|ObjectReference|null $objectTypeDefinition): self { $this->objectTypeDefinition = $objectTypeDefinition; diff --git a/src/Specification/Definitions/Specification.php b/src/Specification/Definitions/Specification.php index 39c0cb80..e13dcff7 100644 --- a/src/Specification/Definitions/Specification.php +++ b/src/Specification/Definitions/Specification.php @@ -9,20 +9,14 @@ final class Specification { /** - * @var Operation[] - * @psalm-var array + * @param array $operations + * @param array $componentSchemas */ - private array $operations; - private OpenApi $openApi; - - /** - * @param Operation[] $operations - * @psalm-param array $operations - */ - public function __construct(array $operations, OpenApi $openApi) - { - $this->operations = $operations; - $this->openApi = $openApi; + public function __construct( + private array $operations, + private array $componentSchemas, + private OpenApi $openApi + ) { } /** @@ -39,6 +33,14 @@ public function getOperation(string $id): Operation return $this->operations[$id]; } + /** + * @return array + */ + public function getComponentSchemas(): array + { + return $this->componentSchemas; + } + public function getOpenApi(): OpenApi { return $this->openApi; diff --git a/src/Specification/SpecificationLoader.php b/src/Specification/SpecificationLoader.php index 1462415f..dc034866 100644 --- a/src/Specification/SpecificationLoader.php +++ b/src/Specification/SpecificationLoader.php @@ -78,8 +78,7 @@ public function get(string $name): SpecificationConfig public function load(string $name): Specification { - /** @var Specification $parsedSpecification */ - $parsedSpecification = $this->cache->get( + return $this->cache->get( self::CACHE_KEY_PREFIX . $name, function (ItemInterface $cacheItem) use ($name): Specification { $cacheItem->tag(self::CACHE_TAG); @@ -87,8 +86,6 @@ function (ItemInterface $cacheItem) use ($name): Specification { return $this->parseSpecification($name, $this->get($name)); } ); - - return $parsedSpecification; } private function parseSpecification(string $specificationName, SpecificationConfig $specificationConfig): Specification @@ -115,7 +112,7 @@ private function parseSpecification(string $specificationName, SpecificationConf $specification = null; if ($type === 'yaml') { - $specification = Reader::readFromYamlFile($specPath); + $specification = Reader::readFromYamlFile($specPath, OpenApi::class, true); } if ($type === 'json') { diff --git a/src/Specification/SpecificationParser.php b/src/Specification/SpecificationParser.php index 47f96e09..7612e350 100644 --- a/src/Specification/SpecificationParser.php +++ b/src/Specification/SpecificationParser.php @@ -16,7 +16,9 @@ use cebe\openapi\spec\Schema; use cebe\openapi\spec\Type; use OnMoon\OpenApiServerBundle\Exception\CannotParseOpenApi; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType as ObjectDefinition; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ComponentArray; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectReference; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\Operation as OperationDefinition; use OnMoon\OpenApiServerBundle\Specification\Definitions\Property as PropertyDefinition; use OnMoon\OpenApiServerBundle\Specification\Definitions\Specification; @@ -32,18 +34,29 @@ use function in_array; use function is_array; use function is_int; +use function Safe\preg_match; +use function str_ends_with; +use function strcasecmp; +use function substr; class SpecificationParser { private ScalarTypesResolver $typeResolver; + /** @var string[] */ + private array $skipHttpCodes; - public function __construct(ScalarTypesResolver $typeResolver) + /** @param array $skipHttpCodes */ + public function __construct(ScalarTypesResolver $typeResolver, array $skipHttpCodes) { $this->typeResolver = $typeResolver; + + $this->skipHttpCodes = array_map(static fn ($code) => (string) $code, $skipHttpCodes); } public function parseOpenApi(string $specificationName, SpecificationConfig $specificationConfig, OpenApi $parsedSpecification): Specification { + $componentSchemas = new ComponentArray(); + $operationDefinitions = []; /** * @var string $url @@ -70,19 +83,17 @@ public function parseOpenApi(string $specificationName, SpecificationConfig $spe throw CannotParseOpenApi::becauseDuplicateOperationId($operationId, $exceptionContext); } - $responses = $this->getResponseDtoDefinitions($operation->responses, $specificationConfig, $exceptionContext); + $responses = $this->getResponseDtoDefinitions($operation->responses, $specificationConfig, $componentSchemas, $exceptionContext); $requestSchema = $this->findByMediaType($operation->requestBody, $specificationConfig->getMediaType()); $requestBody = null; if ($requestSchema !== null) { - $requestBody = new ObjectDefinition( - $this->getPropertyGraph( - $requestSchema, - true, - true, - $exceptionContext + ['location' => 'request body'] - ) + $requestBody = $this->getObjectSchema( + $requestSchema, + true, + $componentSchemas, + $exceptionContext + ['location' => 'request body'] ); } @@ -90,7 +101,7 @@ public function parseOpenApi(string $specificationName, SpecificationConfig $spe $requestParameters = []; foreach (['path', 'query'] as $in) { - $params = $this->parseParameters($in, $parameters, $exceptionContext + ['location' => 'request ' . $in . ' parameters']); + $params = $this->parseParameters($in, $parameters, $componentSchemas, $exceptionContext + ['location' => 'request ' . $in . ' parameters']); if ($params === null) { continue; @@ -113,18 +124,19 @@ public function parseOpenApi(string $specificationName, SpecificationConfig $spe } } - return new Specification($operationDefinitions, $parsedSpecification); + return new Specification($operationDefinitions, $componentSchemas->getArrayCopy(), $parsedSpecification); } /** * @param Response[]|Responses|null $responses * @param array{location?:string,method:string,url:string,path:string} $exceptionContext * - * @return ObjectDefinition[] + * @return array */ private function getResponseDtoDefinitions( - $responses, + array|Responses|null $responses, SpecificationConfig $specificationConfig, + ComponentArray $componentSchemas, array $exceptionContext ): array { $responseDefinitions = []; @@ -137,33 +149,50 @@ private function getResponseDtoDefinitions( $responses = $responses->getResponses(); } - /** - * @var string $responseCode - */ foreach ($responses as $responseCode => $response) { + if ($this->isHttpCodeSkipped((string) $responseCode)) { + continue; + } + $responseSchema = $this->findByMediaType($response, $specificationConfig->getMediaType()); if ($responseSchema === null) { - continue; + $responseDefinitions[$responseCode] = new ObjectSchema([]); + } else { + $responseDefinitions[$responseCode] = $this->getObjectSchema( + $responseSchema, + false, + $componentSchemas, + $exceptionContext + ['location' => 'response (code "' . (string) $responseCode . '")'] + ); } - - $propertyDefinitions = $this->getPropertyGraph( - $responseSchema, - false, - true, - $exceptionContext + ['location' => 'response (code "' . $responseCode . '")'] - ); - $responseDefinition = new ObjectDefinition($propertyDefinitions); - $responseDefinitions[$responseCode] = $responseDefinition; } return $responseDefinitions; } - /** - * @param RequestBody|Response|Reference|null $body - */ - private function findByMediaType($body, string $mediaType): ?Schema + private function isHttpCodeSkipped(string $code): bool + { + foreach ($this->skipHttpCodes as $skippedCode) { + if (strcasecmp($skippedCode, $code) === 0) { + return true; + } + + if ( + str_ends_with($skippedCode, '**') && + strcasecmp( + substr($skippedCode, 0, -2), + substr($code, 0, -2) + ) === 0 + ) { + return true; + } + } + + return false; + } + + private function findByMediaType(Response|RequestBody|Reference|null $body, string $mediaType): ?Schema { if ($body === null || $body instanceof Reference) { return null; @@ -232,7 +261,7 @@ private function filterSupportedParameters(string $in, array $parameters): array * @param Parameter[] $parameters * @param array{location:string,method:string,url:string,path:string} $exceptionContext */ - private function parseParameters(string $in, array $parameters, array $exceptionContext): ?ObjectDefinition + private function parseParameters(string $in, array $parameters, ComponentArray $componentSchemas, array $exceptionContext): ?ObjectSchema { $properties = array_map( fn (Parameter $p) => $this @@ -241,6 +270,7 @@ private function parseParameters(string $in, array $parameters, array $exception $p->schema, // @codeCoverageIgnoreStart true, + $componentSchemas, // @codeCoverageIgnoreEnd $exceptionContext, false @@ -254,23 +284,37 @@ private function parseParameters(string $in, array $parameters, array $exception return null; } - return new ObjectDefinition($properties); + return new ObjectSchema($properties); + } + + private function getComponentSchemaName(string $path): ?string + { + if (preg_match('#^/components/schemas/([^/]+)$#', $path, $match) === 1) { + /** @psalm-suppress PossiblyNullArrayAccess */ + return $match[1]; + } + + return null; } /** * @param array{location:string,method:string,url:string,path:string} $exceptionContext - * - * @return PropertyDefinition[] */ - private function getPropertyGraph(Schema $schema, bool $isRequest, bool $isRoot, array $exceptionContext): array + private function getObjectSchema(Schema $schema, ?bool $isRequest, ComponentArray $componentSchemas, array $exceptionContext): ObjectSchema|ObjectReference { - if ($isRoot && $schema->type !== Type::OBJECT) { + if ($schema->type !== Type::OBJECT) { throw CannotParseOpenApi::becauseRootIsNotObject( $exceptionContext, ($schema->type === Type::ARRAY) ); } + $componentName = $this->getComponentSchemaName($schema->getDocumentPosition()?->getPointer() ?? ''); + if ($componentName !== null && $componentSchemas->offsetExists($componentName)) { + /** @phpstan-ignore-next-line */ + return new ObjectReference($componentName, $componentSchemas[$componentName]); + } + $propertyDefinitions = []; /** * @var string $propertyName @@ -280,7 +324,8 @@ private function getPropertyGraph(Schema $schema, bool $isRequest, bool $isRoot, throw CannotParseOpenApi::becausePropertyIsNotScheme(); } - if (($property->readOnly && $isRequest) || ($property->writeOnly && ! $isRequest)) { + //ToDo: Rework this in components + if (($property->readOnly && $isRequest === true) || ($property->writeOnly && $isRequest === false)) { continue; } @@ -288,18 +333,31 @@ private function getPropertyGraph(Schema $schema, bool $isRequest, bool $isRoot, * @psalm-suppress RedundantConditionGivenDocblockType */ $required = is_array($schema->required) && in_array($propertyName, $schema->required, true); - $propertyDefinitions[] = $this->getProperty($propertyName, $property, $isRequest, $exceptionContext)->setRequired($required); + $propertyDefinitions[] = $this->getProperty($propertyName, $property, $isRequest, $componentSchemas, $exceptionContext)->setRequired($required); + } + + $objectSchema = new ObjectSchema($propertyDefinitions); + + if ($componentName !== null) { + $componentSchemas[$componentName] = $objectSchema; + + return new ObjectReference($componentName, $objectSchema); } - return $propertyDefinitions; + return $objectSchema; } /** - * @param Schema|Reference|null $property * @param array{location:string,method:string,url:string,path:string} $exceptionContext */ - private function getProperty(string $propertyName, $property, bool $isRequest, array $exceptionContext, bool $allowNonScalar = true): PropertyDefinition - { + private function getProperty( + string $propertyName, + Schema|Reference|null $property, + ?bool $isRequest, + ComponentArray $componentSchemas, + array $exceptionContext, + bool $allowNonScalar = true + ): PropertyDefinition { if (! ($property instanceof Schema)) { throw CannotParseOpenApi::becausePropertyIsNotScheme(); } @@ -328,15 +386,11 @@ private function getProperty(string $propertyName, $property, bool $isRequest, a $scalarTypeId = $this->typeResolver->findScalarType($itemProperty->type, $itemProperty->format); $propertyDefinition->setScalarTypeId($scalarTypeId); } elseif ($itemProperty->type === Type::OBJECT) { - $objectType = new ObjectDefinition( - $this->getPropertyGraph( - $itemProperty, - $isRequest, - // @codeCoverageIgnoreStart - false, - // @codeCoverageIgnoreEnd - $exceptionContext - ) + $objectType = $this->getObjectSchema( + $itemProperty, + $isRequest, + $componentSchemas, + $exceptionContext ); $propertyDefinition->setObjectTypeDefinition($objectType); $isScalar = false; diff --git a/src/Types/ArgumentResolver.php b/src/Types/ArgumentResolver.php index c76d4ab7..947309ef 100644 --- a/src/Types/ArgumentResolver.php +++ b/src/Types/ArgumentResolver.php @@ -5,7 +5,7 @@ namespace OnMoon\OpenApiServerBundle\Types; use Exception; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use function Safe\preg_match; @@ -22,7 +22,7 @@ public function __construct(ScalarTypesResolver $typesResolver) * @return string[] * @psalm-return array */ - public function resolveArgumentPatterns(ObjectType $pathParameters): array + public function resolveArgumentPatterns(ObjectSchema $pathParameters): array { $patterns = []; diff --git a/test/functional/Command/GenerateApiCodeCommandTest.php b/test/functional/Command/GenerateApiCodeCommandTest.php index b49259eb..f7102ef9 100644 --- a/test/functional/Command/GenerateApiCodeCommandTest.php +++ b/test/functional/Command/GenerateApiCodeCommandTest.php @@ -13,6 +13,8 @@ use function Safe\sprintf; use function ucfirst; +use const DIRECTORY_SEPARATOR; + /** * @covers \OnMoon\OpenApiServerBundle\Command\GenerateApiCodeCommand */ @@ -36,10 +38,10 @@ public function testGeneration(): void Assert::assertSame(0, $this->commandTester->getStatusCode()); Assert::assertDirectoryExists(TestKernel::$bundleRootPath); Assert::assertDirectoryIsReadable(TestKernel::$bundleRootPath); - Assert::assertFileExists(TestKernel::$bundleRootPath . '/ServiceSubscriber/ApiServiceLoaderServiceSubscriber.php'); - Assert::assertFileExists(TestKernel::$bundleRootPath . '/Apis/' . $this->openapiNamespace . '/' . ucfirst($this->openapiOperationId) . '/' . ucfirst($this->openapiOperationId) . '.php'); - Assert::assertFileExists(TestKernel::$bundleRootPath . '/Apis/' . $this->openapiNamespace . '/' . ucfirst($this->openapiOperationId) . '/Dto/Request/' . ucfirst($this->openapiOperationId) . 'RequestDto.php'); - Assert::assertFileExists(TestKernel::$bundleRootPath . '/Apis/' . $this->openapiNamespace . '/' . ucfirst($this->openapiOperationId) . '/Dto/Request/PathParameters/PathParametersDto.php'); - Assert::assertFileExists(TestKernel::$bundleRootPath . '/Apis/' . $this->openapiNamespace . '/' . ucfirst($this->openapiOperationId) . '/Dto/Response/OK/' . ucfirst($this->openapiOperationId) . 'OKDto.php'); + Assert::assertFileExists(TestKernel::$bundleRootPath . DIRECTORY_SEPARATOR . 'ServiceSubscriber' . DIRECTORY_SEPARATOR . 'ApiServiceLoaderServiceSubscriber.php'); + Assert::assertFileExists(TestKernel::$bundleRootPath . DIRECTORY_SEPARATOR . 'Apis' . DIRECTORY_SEPARATOR . $this->openapiNamespace . DIRECTORY_SEPARATOR . ucfirst($this->openapiOperationId) . DIRECTORY_SEPARATOR . ucfirst($this->openapiOperationId) . '.php'); + Assert::assertFileExists(TestKernel::$bundleRootPath . DIRECTORY_SEPARATOR . 'Apis' . DIRECTORY_SEPARATOR . $this->openapiNamespace . DIRECTORY_SEPARATOR . ucfirst($this->openapiOperationId) . DIRECTORY_SEPARATOR . 'Dto' . DIRECTORY_SEPARATOR . 'Request' . DIRECTORY_SEPARATOR . ucfirst($this->openapiOperationId) . 'RequestDto.php'); + Assert::assertFileExists(TestKernel::$bundleRootPath . DIRECTORY_SEPARATOR . 'Apis' . DIRECTORY_SEPARATOR . $this->openapiNamespace . DIRECTORY_SEPARATOR . ucfirst($this->openapiOperationId) . DIRECTORY_SEPARATOR . 'Dto' . DIRECTORY_SEPARATOR . 'Request' . DIRECTORY_SEPARATOR . 'PathParameters' . DIRECTORY_SEPARATOR . 'PathParametersDto.php'); + Assert::assertFileExists(TestKernel::$bundleRootPath . DIRECTORY_SEPARATOR . 'Components' . DIRECTORY_SEPARATOR . $this->openapiNamespace . DIRECTORY_SEPARATOR . 'GoodResponseSchema' . DIRECTORY_SEPARATOR . 'GoodResponseSchema.php'); } } diff --git a/test/functional/Command/RefreshApiCodeCommandTest.php b/test/functional/Command/RefreshApiCodeCommandTest.php deleted file mode 100644 index 34dbb331..00000000 --- a/test/functional/Command/RefreshApiCodeCommandTest.php +++ /dev/null @@ -1,79 +0,0 @@ -processFactory = $this->createMock(ProcessFactory::class); - } - - public function tearDown(): void - { - unset($this->processFactory); - parent::tearDown(); - } - - public function testRefreshingAcceptedByUser(): void - { - $this->processFactory - ->expects(self::atLeast(2)) - ->method('getProcess'); - - $command = new RefreshApiCodeCommand(TestKernel::$bundleRootPath, $this->processFactory, self::COMMAND); - $this->application->add($command); - $this->commandTester = new CommandTester($command); - - $this->commandTester->setInputs(['y']); - - $this->commandTester->execute([ - 'command' => self::COMMAND, - ]); - - $output = $this->commandTester->getDisplay(); - Assert::assertEquals(sprintf('Delete all contents of the directory %s? (y/n):', TestKernel::$bundleRootPath), rtrim($output)); - Assert::assertSame(0, $this->commandTester->getStatusCode()); - } - - public function testRefreshingDeclinedByUser(): void - { - $this->processFactory - ->expects(self::never()) - ->method('getProcess'); - - $command = new RefreshApiCodeCommand(TestKernel::$bundleRootPath, $this->processFactory, self::COMMAND); - $this->application->add($command); - $this->commandTester = new CommandTester($command); - - $this->commandTester->setInputs(['n']); - - $this->commandTester->execute([ - 'command' => self::COMMAND, - ]); - - $output = $this->commandTester->getDisplay(); - Assert::assertEquals(sprintf('Delete all contents of the directory %s? (y/n):', TestKernel::$bundleRootPath), rtrim($output)); - Assert::assertSame(0, $this->commandTester->getStatusCode()); - } -} diff --git a/test/functional/Command/SymfonyProcessFactoryTest.php b/test/functional/Command/SymfonyProcessFactoryTest.php deleted file mode 100644 index c6baf60d..00000000 --- a/test/functional/Command/SymfonyProcessFactoryTest.php +++ /dev/null @@ -1,22 +0,0 @@ -getProcess($args); - Assert::assertSame($expectedProcess->getCommandLine(), $process->getCommandLine()); - } -} diff --git a/test/functional/Controller/ApiControllerTest.php b/test/functional/Controller/ApiControllerTest.php index 37933e03..5f4938a7 100644 --- a/test/functional/Controller/ApiControllerTest.php +++ b/test/functional/Controller/ApiControllerTest.php @@ -23,6 +23,8 @@ use function Safe\file_put_contents; use function Safe\json_decode; +use const DIRECTORY_SEPARATOR; + /** * @covers \OnMoon\OpenApiServerBundle\Controller\ApiController */ @@ -91,7 +93,7 @@ protected function configureContainer(ContainerConfigurator $c): void protected function configureRoutes(RoutingConfigurator $routes): void { - $routes->import(__DIR__ . '/openapi_routes.yaml'); + $routes->import(__DIR__ . DIRECTORY_SEPARATOR . 'openapi_routes.yaml'); } }; } @@ -106,19 +108,19 @@ private function createGetGoodImpl(): string namespace OnMoon\OpenApiServerBundle\Test\Functional\Generated; use OnMoon\OpenApiServerBundle\Test\Functional\Generated\Apis\PetStore\GetGood\Dto\Request\GetGoodRequestDto; -use OnMoon\OpenApiServerBundle\Test\Functional\Generated\Apis\PetStore\GetGood\Dto\Response\OK\GetGoodOKDto; +use OnMoon\OpenApiServerBundle\Test\Functional\Generated\Components\PetStore\GoodResponseSchema\GoodResponseSchema; use OnMoon\OpenApiServerBundle\Test\Functional\Generated\Apis\PetStore\GetGood\GetGood; class GetGoodImpl implements GetGood { - public function getGood(GetGoodRequestDto \$request): GetGoodOKDto + public function getGood(GetGoodRequestDto \$request): GoodResponseSchema { - return new GetGoodOKDto('test'); + return new GoodResponseSchema('test'); } } EOD; - file_put_contents(TestKernel::$bundleRootPath . '/GetGoodImpl.php', $content); + file_put_contents(TestKernel::$bundleRootPath . DIRECTORY_SEPARATOR . 'GetGoodImpl.php', $content); return TestKernel::$bundleRootNamespace . '\GetGoodImpl'; } diff --git a/test/functional/DependencyInjection/ConfigurationTest.php b/test/functional/DependencyInjection/ConfigurationTest.php index 9145269c..c21a0dc8 100644 --- a/test/functional/DependencyInjection/ConfigurationTest.php +++ b/test/functional/DependencyInjection/ConfigurationTest.php @@ -126,11 +126,12 @@ public function testParametersDefaultValues(): void { $this->assertProcessedConfigurationEquals([], [ 'root_name_space' => 'App\Generated', - 'language_level' => '7.4.0', + 'language_level' => '8.0.0', 'generated_dir_permissions' => '0755', 'full_doc_blocks' => false, 'send_nulls' => false, 'specs' => [], + 'skip_http_codes' => [], ]); } } diff --git a/test/functional/Specification/SpecificationLoaderTest.php b/test/functional/Specification/SpecificationLoaderTest.php index 9d5e4f25..2d5180cb 100644 --- a/test/functional/Specification/SpecificationLoaderTest.php +++ b/test/functional/Specification/SpecificationLoaderTest.php @@ -139,7 +139,7 @@ public function tearDown(): void public function testRegisterSpecAndGettingRegisteredSpecifications(): void { $specificationLoader = new SpecificationLoader( - new SpecificationParser(new ScalarTypesResolver()), + new SpecificationParser(new ScalarTypesResolver(), []), $this->fileLocator, $this->cache ); @@ -284,7 +284,7 @@ public function testLoadSavesSpecificationInCache(string $specificationFileName) $this->specificationParser ->expects(self::once()) ->method('parseOpenApi') - ->willReturn(new Specification([], new OpenApi([]))); + ->willReturn(new Specification([], [], new OpenApi([]))); $specificationLoader = new SpecificationLoader( $this->specificationParser, @@ -317,7 +317,7 @@ public function testLoadFetchesSpecificationFromCache(): void $specificationLoader->registerSpec(self::SPECIFICATION_NAME, $specificationArray); $cacheKey = 'openapi-server-bundle-specification-' . self::SPECIFICATION_NAME; - $cachedSpecification = new Specification([], new OpenApi([])); + $cachedSpecification = new Specification([], [], new OpenApi([])); $this->cache->set($cacheKey, $cachedSpecification); $loadedSpecification = $specificationLoader->load(self::SPECIFICATION_NAME); diff --git a/test/functional/TestKernel.php b/test/functional/TestKernel.php index d7fc4a79..fb8bc3a9 100644 --- a/test/functional/TestKernel.php +++ b/test/functional/TestKernel.php @@ -11,18 +11,20 @@ use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\Kernel as BaseKernel; +use const DIRECTORY_SEPARATOR; + abstract class TestKernel extends BaseKernel { use MicroKernelTrait; - public static string $bundleRootPath = __DIR__ . '/Generated'; + public static string $bundleRootPath = __DIR__ . DIRECTORY_SEPARATOR . 'Generated'; public static string $bundleRootNamespace = __NAMESPACE__ . '\Generated'; protected function build(ContainerBuilder $container): void { $specificationName = 'petstore'; $specification = [ - 'path' => __DIR__ . '/openapi_specification.yaml', + 'path' => __DIR__ . DIRECTORY_SEPARATOR . 'openapi_specification.yaml', 'type' => 'yaml', 'name_space' => 'PetStore', 'media_type' => 'application/json', @@ -45,12 +47,12 @@ protected function build(ContainerBuilder $container): void public function getCacheDir(): string { - return __DIR__ . '/var/cache'; + return __DIR__ . DIRECTORY_SEPARATOR . 'var' . DIRECTORY_SEPARATOR . 'cache'; } public function getLogDir(): string { - return __DIR__ . '/var/log'; + return __DIR__ . DIRECTORY_SEPARATOR . 'var' . DIRECTORY_SEPARATOR . 'log'; } /** @@ -74,7 +76,7 @@ public function registerBundles(): iterable public function shutdown(): void { $filesystem = new Filesystem(); - $filesystem->remove([__DIR__ . '/var']); + $filesystem->remove([__DIR__ . DIRECTORY_SEPARATOR . 'var']); parent::shutdown(); } diff --git a/test/generation/TestApiServerCodeGeneratorFactory.php b/test/generation/TestApiServerCodeGeneratorFactory.php index b4519ec9..5bc0f0ad 100644 --- a/test/generation/TestApiServerCodeGeneratorFactory.php +++ b/test/generation/TestApiServerCodeGeneratorFactory.php @@ -12,7 +12,6 @@ use OnMoon\OpenApiServerBundle\CodeGenerator\FileGenerator; use OnMoon\OpenApiServerBundle\CodeGenerator\Filesystem\FileWriter; use OnMoon\OpenApiServerBundle\CodeGenerator\GraphGenerator; -use OnMoon\OpenApiServerBundle\CodeGenerator\InterfaceGenerator; use OnMoon\OpenApiServerBundle\CodeGenerator\NameGenerator; use OnMoon\OpenApiServerBundle\CodeGenerator\Naming\DefaultNamingStrategy; use OnMoon\OpenApiServerBundle\CodeGenerator\PhpParserGenerators\DtoCodeGenerator; @@ -46,7 +45,8 @@ public static function getCodeGenerator( $specificationLoader = new SpecificationLoader( new SpecificationParser( - new ScalarTypesResolver() + new ScalarTypesResolver(), + [] ), new FileLocator(), new class () implements TagAwareCacheInterface { @@ -129,7 +129,6 @@ public function delete(string $key): bool $rootNamespace, $rootPath ), - new InterfaceGenerator(), new FileGenerator( new DtoCodeGenerator( $builderFactory, diff --git a/test/generation/Types/TypesGenerationTest.php b/test/generation/Types/TypesGenerationTest.php index 450b4bc0..929a4694 100644 --- a/test/generation/Types/TypesGenerationTest.php +++ b/test/generation/Types/TypesGenerationTest.php @@ -4,8 +4,8 @@ namespace OnMoon\OpenApiServerBundle\Test\Generation\Types; +use OnMoon\OpenApiServerBundle\Interfaces\Dto; use OnMoon\OpenApiServerBundle\Interfaces\RequestHandler; -use OnMoon\OpenApiServerBundle\Interfaces\ResponseDto; use OnMoon\OpenApiServerBundle\Test\Generation\GeneratedClassAsserter; use OnMoon\OpenApiServerBundle\Test\Generation\GenerationTestCase; use Psr\Container\ContainerInterface; @@ -23,9 +23,9 @@ public function testGetTestOKDto(): void $okDtoAsserter->assertInNamespace('Test\Apis\TestApi\GetTest\Dto\Response\OK'); $okDtoAsserter->assertHasName('GetTestOKDto'); - $okDtoAsserter->assertImplements(ResponseDto::class); + $okDtoAsserter->assertImplements(Dto::class); $okDtoAsserter->assertHasProperty('string_property', 'string', false); - $okDtoAsserter->assertHasUseStatement('OnMoon\OpenApiServerBundle\Interfaces\ResponseDto'); + $okDtoAsserter->assertHasUseStatement('OnMoon\OpenApiServerBundle\Interfaces\Dto'); $okDtoAsserter->assertHasMethod('getStringProperty'); $okDtoAsserter->assertMethodDocblockContains('toArray', '/** @inheritDoc */'); $okDtoAsserter->assertMethodReturns('getStringProperty', 'string', false); diff --git a/test/unit/CodeGenerator/ApiServerCodeGeneratorTest.php b/test/unit/CodeGenerator/ApiServerCodeGeneratorTest.php index 333d3834..d3662e0e 100644 --- a/test/unit/CodeGenerator/ApiServerCodeGeneratorTest.php +++ b/test/unit/CodeGenerator/ApiServerCodeGeneratorTest.php @@ -14,13 +14,15 @@ use OnMoon\OpenApiServerBundle\CodeGenerator\FileGenerator; use OnMoon\OpenApiServerBundle\CodeGenerator\Filesystem\FileWriter; use OnMoon\OpenApiServerBundle\CodeGenerator\GraphGenerator; -use OnMoon\OpenApiServerBundle\CodeGenerator\InterfaceGenerator; use OnMoon\OpenApiServerBundle\CodeGenerator\NameGenerator; use OnMoon\OpenApiServerBundle\Event\CodeGenerator\ClassGraphReadyEvent; use OnMoon\OpenApiServerBundle\Event\CodeGenerator\FilesReadyEvent; use OnMoon\OpenApiServerBundle\Specification\Definitions\SpecificationConfig; use PHPUnit\Framework\TestCase; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Webmozart\Assert\Assert; + +use const DIRECTORY_SEPARATOR; /** * @covers \OnMoon\OpenApiServerBundle\CodeGenerator\ApiServerCodeGenerator @@ -33,6 +35,12 @@ public function testGenerate(): void [ new SpecificationDefinition( new SpecificationConfig('/', null, '/', 'application/json'), + [], + [] + ), + new SpecificationDefinition( + new SpecificationConfig('/someAnotherPath', null, '/SomeNameSpace', 'application/json'), + [], [] ), ], @@ -45,12 +53,6 @@ public function testGenerate(): void ->method('generateClassGraph') ->willReturn($graphDefinition); - $interfaceGenerator = $this->createMock(InterfaceGenerator::class); - $interfaceGenerator - ->expects(self::once()) - ->method('setAllInterfaces') - ->with($graphDefinition); - $attributeGenerator = $this->createMock(AttributeGenerator::class); $attributeGenerator ->expects(self::once()) @@ -69,13 +71,19 @@ public function testGenerate(): void $generatedFileDefinition = new GeneratedFileDefinition($generatedClassDefinition, 'test'); + $generatedClassDefinitionTwo = new GeneratedClassDefinition(); + $generatedClassDefinitionTwo->setFilePath('test_file_path_two'); + $generatedClassDefinitionTwo->setFileName('test_file_name_two'); + + $generatedFileDefinitionTwo = new GeneratedFileDefinition($generatedClassDefinitionTwo, 'test_two'); + $eventDispatcher = $this->createMock(EventDispatcherInterface::class); $eventDispatcher ->expects(self::exactly(2)) ->method('dispatch') ->withConsecutive( [new ClassGraphReadyEvent($graphDefinition)], - [new FilesReadyEvent([$generatedFileDefinition])] + [new FilesReadyEvent([$generatedFileDefinition, $generatedFileDefinitionTwo])] ); $fileGenerator = $this->createMock(FileGenerator::class); @@ -83,27 +91,30 @@ public function testGenerate(): void ->expects(self::once()) ->method('generateAllFiles') ->with($graphDefinition) - ->willReturn([$generatedFileDefinition]); + ->willReturn([$generatedFileDefinition, $generatedFileDefinitionTwo]); $fileWriter = $this->createMock(FileWriter::class); $fileWriter - ->expects(self::once()) + ->expects(self::exactly(2)) ->method('write') - ->with( - $generatedClassDefinition->getFilePath(), - $generatedClassDefinition->getFileName(), - $generatedFileDefinition->getFileContents() + ->withConsecutive( + [$generatedClassDefinition->getFilePath(), $generatedClassDefinition->getFileName(), $generatedFileDefinition->getFileContents()], + [$generatedClassDefinitionTwo->getFilePath(), $generatedClassDefinitionTwo->getFileName(), $generatedFileDefinitionTwo->getFileContents()] ); $apiServerCodeGenerator = new ApiServerCodeGenerator( $graphGenerator, $nameGenerator, - $interfaceGenerator, $fileGenerator, $attributeGenerator, $fileWriter, $eventDispatcher ); - $apiServerCodeGenerator->generate(); + + $writtenFiles = $apiServerCodeGenerator->generate(); + + Assert::count($writtenFiles, 2); + Assert::same($writtenFiles[0], 'test_file_path' . DIRECTORY_SEPARATOR . 'test_file_name'); + Assert::same($writtenFiles[1], 'test_file_path_two' . DIRECTORY_SEPARATOR . 'test_file_name_two'); } } diff --git a/test/unit/CodeGenerator/AttributeGeneratorTest.php b/test/unit/CodeGenerator/AttributeGeneratorTest.php index ce5f2dc6..06eb8050 100644 --- a/test/unit/CodeGenerator/AttributeGeneratorTest.php +++ b/test/unit/CodeGenerator/AttributeGeneratorTest.php @@ -5,13 +5,13 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\CodeGenerator; use OnMoon\OpenApiServerBundle\CodeGenerator\AttributeGenerator; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ComponentDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GraphDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\OperationDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\PropertyDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestBodyDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestHandlerInterfaceDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ServiceSubscriberDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\SpecificationDefinition; use OnMoon\OpenApiServerBundle\Specification\Definitions\Property; @@ -19,6 +19,8 @@ use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; +use function array_map; + /** * @covers \OnMoon\OpenApiServerBundle\CodeGenerator\AttributeGenerator */ @@ -26,25 +28,33 @@ class AttributeGeneratorTest extends TestCase { private Property $property; private Property $propertyTwo; + private Property $propertyThree; private PropertyDefinition $propertyDefinition; private PropertyDefinition $propertyDefinitionTwo; + private PropertyDefinition $propertyDefinitionThree; - private RequestDtoDefinition $requestDtoDefinition; - private ResponseDtoDefinition $responseDtoDefinition; + private DtoDefinition $requestDtoDefinition; + private ResponseDefinition $responseDtoDefinition; private OperationDefinition $operationDefinition; private GraphDefinition $graphDefinition; private AttributeGenerator $attributesGenerator; + private RequestHandlerInterfaceDefinition $requestHandlerInterface; + + private ComponentDefinition $componentDefinition; + public function setUp(): void { - $this->property = new Property('testOne'); - $this->propertyTwo = new Property('testTwo'); + $this->property = new Property('testOne'); + $this->propertyTwo = new Property('testTwo'); + $this->propertyThree = new Property('testThree'); - $propertyObjectTypeDefinition = new DtoDefinition([]); - $propertyObjectTypeDefinitionTwo = new DtoDefinition([]); + $propertyObjectTypeDefinition = new DtoDefinition([]); + $propertyObjectTypeDefinitionTwo = new DtoDefinition([]); + $propertyObjectTypeDefinitionThree = new DtoDefinition([]); $this->propertyDefinition = new PropertyDefinition($this->property); $this->propertyDefinition->setObjectTypeDefinition($propertyObjectTypeDefinition); @@ -52,24 +62,41 @@ public function setUp(): void $this->propertyDefinitionTwo = new PropertyDefinition($this->propertyTwo); $this->propertyDefinitionTwo->setObjectTypeDefinition($propertyObjectTypeDefinitionTwo); - $requestBodyDtoDefinition = new RequestBodyDtoDefinition([$this->propertyDefinition]); - $this->requestDtoDefinition = new RequestDtoDefinition($requestBodyDtoDefinition, null, null); + $this->propertyDefinitionThree = new PropertyDefinition($this->propertyThree); + $this->propertyDefinitionThree->setObjectTypeDefinition($propertyObjectTypeDefinitionThree); + + $this->requestDtoDefinition = new DtoDefinition([$this->propertyDefinition]); + $this->responseDtoDefinition = new ResponseDefinition('200', new DtoDefinition([$this->propertyDefinitionTwo])); - $this->responseDtoDefinition = new ResponseDtoDefinition('200', [$this->propertyDefinitionTwo]); - $this->operationDefinition = new OperationDefinition( + $this->requestHandlerInterface = new RequestHandlerInterfaceDefinition( + $this->requestDtoDefinition, + array_map( + static fn (ResponseDefinition $response) => $response->getResponseBody(), + [$this->responseDtoDefinition] + ) + ); + + $this->operationDefinition = new OperationDefinition( '/', 'get', 'test', '', null, + null, $this->requestDtoDefinition, - [$this->responseDtoDefinition] + [$this->responseDtoDefinition], + $this->requestHandlerInterface ); - $this->graphDefinition = new GraphDefinition( + + $this->componentDefinition = new ComponentDefinition('TestComponent'); + $this->componentDefinition->setDto(new DtoDefinition([$this->propertyDefinitionThree])); + + $this->graphDefinition = new GraphDefinition( [ new SpecificationDefinition( new SpecificationConfig('/', null, '/', 'application/json'), - [$this->operationDefinition] + [$this->operationDefinition], + [$this->componentDefinition] ), ], new ServiceSubscriberDefinition() @@ -94,10 +121,21 @@ public function testSetAllAttributesDefaultCall(): void Assert::assertFalse($this->propertyDefinition->isNullable()); Assert::assertFalse($this->propertyDefinition->isInConstructor()); + Assert::assertFalse($this->propertyTwo->isRequired()); + Assert::assertFalse($this->propertyTwo->isNullable()); + Assert::assertTrue($this->propertyDefinitionTwo->hasGetter()); Assert::assertTrue($this->propertyDefinitionTwo->hasSetter()); Assert::assertTrue($this->propertyDefinitionTwo->isNullable()); Assert::assertFalse($this->propertyDefinitionTwo->isInConstructor()); + + Assert::assertFalse($this->propertyThree->isRequired()); + Assert::assertFalse($this->propertyThree->isNullable()); + + Assert::assertTrue($this->propertyDefinitionThree->hasGetter()); + Assert::assertTrue($this->propertyDefinitionThree->hasSetter()); + Assert::assertTrue($this->propertyDefinitionThree->isNullable()); + Assert::assertFalse($this->propertyDefinitionThree->isInConstructor()); } public function testSetAllAttributesCaseTwo(): void @@ -126,13 +164,17 @@ public function testSetAllAttributesCaseOne(): void '', null, null, - [$this->responseDtoDefinition] + null, + [$this->responseDtoDefinition], + $this->requestHandlerInterface ); - $this->graphDefinition = new GraphDefinition( + + $this->graphDefinition = new GraphDefinition( [ new SpecificationDefinition( new SpecificationConfig('/', null, '/', 'application/json'), - [$this->operationDefinition] + [$this->operationDefinition], + [$this->componentDefinition] ), ], new ServiceSubscriberDefinition() @@ -147,112 +189,152 @@ public function testSetAllAttributesCaseOne(): void public function testRequestPassDefault(): void { - $specProperty = new Property('first'); - $specProperty->setNullable(true); - $specProperty->setRequired(true); - $specProperty->setDefaultValue('test'); + $property = new Property('first'); + $property->setNullable(true); + $property->setRequired(true); + $property->setDefaultValue('test'); - $specPropertyTwo = new Property('two'); - $specPropertyTwo->setNullable(true); - $specPropertyTwo->setRequired(true); - $specPropertyTwo->setDefaultValue('testTwo'); + $propertyTwo = new Property('two'); + $propertyTwo->setNullable(true); + $propertyTwo->setRequired(true); + $propertyTwo->setDefaultValue('testTwo'); - $property = new PropertyDefinition($specProperty); - $secondaryProperty = new PropertyDefinition($specPropertyTwo); - $root = new DtoDefinition([$property, $secondaryProperty]); + $propertyDefinition = new PropertyDefinition($property); + $propertyDefinitionTwo = new PropertyDefinition($propertyTwo); + $root = new DtoDefinition([$propertyDefinition, $propertyDefinitionTwo]); $attributesGenerator = new AttributeGenerator(); $attributesGenerator->requestPass($root); - Assert::assertTrue($property->hasGetter()); - Assert::assertFalse($property->hasSetter()); - Assert::assertFalse($property->isInConstructor()); - Assert::assertTrue($property->isNullable()); + Assert::assertTrue($propertyDefinition->hasGetter()); + Assert::assertFalse($propertyDefinition->hasSetter()); + Assert::assertFalse($propertyDefinition->isInConstructor()); + Assert::assertTrue($propertyDefinition->isNullable()); - Assert::assertTrue($secondaryProperty->hasGetter()); - Assert::assertFalse($secondaryProperty->hasSetter()); - Assert::assertFalse($secondaryProperty->isInConstructor()); - Assert::assertTrue($secondaryProperty->isNullable()); + Assert::assertTrue($propertyDefinitionTwo->hasGetter()); + Assert::assertFalse($propertyDefinitionTwo->hasSetter()); + Assert::assertFalse($propertyDefinitionTwo->isInConstructor()); + Assert::assertTrue($propertyDefinitionTwo->isNullable()); } public function testRequestPassWithNestedObject(): void { - $specProperty = new Property('first'); - $specProperty->setNullable(true); - $specProperty->setRequired(true); - $specProperty->setDefaultValue('test'); + $property = new Property('first'); + $property->setNullable(true); + $property->setRequired(true); + $property->setDefaultValue('test'); - $property = new PropertyDefinition($specProperty); - $secondaryProperty = new PropertyDefinition($specProperty); + $propertyDefinition = new PropertyDefinition($property); + $propertyDefinitionTwo = new PropertyDefinition($property); - $root = new DtoDefinition([$property]); - $secondary = new DtoDefinition([$secondaryProperty]); - $property->setObjectTypeDefinition($secondary); + $root = new DtoDefinition([$propertyDefinition]); + $secondary = new DtoDefinition([$propertyDefinitionTwo]); + $propertyDefinition->setObjectTypeDefinition($secondary); $attributesGenerator = new AttributeGenerator(); $attributesGenerator->requestPass($root); - Assert::assertTrue($property->hasGetter()); - Assert::assertFalse($property->hasSetter()); - Assert::assertFalse($property->isInConstructor()); - Assert::assertTrue($property->isNullable()); + Assert::assertTrue($propertyDefinition->hasGetter()); + Assert::assertFalse($propertyDefinition->hasSetter()); + Assert::assertFalse($propertyDefinition->isInConstructor()); + Assert::assertTrue($propertyDefinition->isNullable()); } public function testResponsePassDefault(): void { - $specProperty = new Property('first'); - $specProperty->setNullable(true); - $specProperty->setRequired(true); - $specProperty->setDefaultValue('test'); + $property = new Property('first'); + $property->setNullable(true); + $property->setRequired(true); + $property->setDefaultValue('test'); - $specPropertyTwo = new Property('two'); - $specPropertyTwo->setNullable(true); - $specPropertyTwo->setRequired(true); - $specPropertyTwo->setDefaultValue('testTwo'); + $propertyTwo = new Property('two'); + $propertyTwo->setNullable(true); + $propertyTwo->setRequired(true); + $propertyTwo->setDefaultValue('testTwo'); - $property = new PropertyDefinition($specProperty); - $secondaryProperty = new PropertyDefinition($specPropertyTwo); - $root = new DtoDefinition([$property, $secondaryProperty]); + $propertyDefinition = new PropertyDefinition($property); + $propertyDefinitionTwo = new PropertyDefinition($propertyTwo); + $root = new DtoDefinition([$propertyDefinition, $propertyDefinitionTwo]); $attributesGenerator = new AttributeGenerator(); $attributesGenerator->responsePass($root); - Assert::assertTrue($property->hasGetter()); - Assert::assertTrue($property->hasSetter()); - Assert::assertTrue($property->isNullable()); - Assert::assertFalse($property->isInConstructor()); + Assert::assertTrue($propertyDefinition->hasGetter()); + Assert::assertTrue($propertyDefinition->hasSetter()); + Assert::assertTrue($propertyDefinition->isNullable()); + Assert::assertFalse($propertyDefinition->isInConstructor()); - Assert::assertTrue($secondaryProperty->hasGetter()); - Assert::assertTrue($secondaryProperty->hasSetter()); - Assert::assertFalse($secondaryProperty->isInConstructor()); - Assert::assertTrue($secondaryProperty->isNullable()); + Assert::assertTrue($propertyDefinitionTwo->hasGetter()); + Assert::assertTrue($propertyDefinitionTwo->hasSetter()); + Assert::assertFalse($propertyDefinitionTwo->isInConstructor()); + Assert::assertTrue($propertyDefinitionTwo->isNullable()); } public function testResponsePassWithNestedObject(): void { - $specProperty = new Property('first'); - $specProperty->setNullable(true); - $specProperty->setRequired(true); - $specProperty->setDefaultValue('test'); + $property = new Property('first'); + $property->setNullable(true); + $property->setRequired(true); + $property->setDefaultValue('test'); - $specPropertyTwo = new Property('two'); - $specPropertyTwo->setNullable(true); - $specPropertyTwo->setRequired(true); - $specPropertyTwo->setDefaultValue('testTwo'); + $propertyTwo = new Property('two'); + $propertyTwo->setNullable(true); + $propertyTwo->setRequired(true); + $propertyTwo->setDefaultValue('testTwo'); - $property = new PropertyDefinition($specProperty); - $secondaryProperty = new PropertyDefinition($specPropertyTwo); + $propertyDefinition = new PropertyDefinition($property); + $propertyDefinitionTwo = new PropertyDefinition($propertyTwo); - $root = new DtoDefinition([$property]); - $secondary = new DtoDefinition([$secondaryProperty]); - $property->setObjectTypeDefinition($secondary); + $root = new DtoDefinition([$propertyDefinition]); + $secondary = new DtoDefinition([$propertyDefinitionTwo]); + $propertyDefinition->setObjectTypeDefinition($secondary); $attributesGenerator = new AttributeGenerator(); $attributesGenerator->responsePass($root); - Assert::assertTrue($secondaryProperty->hasGetter()); - Assert::assertTrue($secondaryProperty->hasSetter()); - Assert::assertTrue($secondaryProperty->isNullable()); - Assert::assertFalse($secondaryProperty->isInConstructor()); + Assert::assertTrue($propertyDefinitionTwo->hasGetter()); + Assert::assertTrue($propertyDefinitionTwo->hasSetter()); + Assert::assertTrue($propertyDefinitionTwo->isNullable()); + Assert::assertFalse($propertyDefinitionTwo->isInConstructor()); + } + + public function testComponentPassDefault(): void + { + $this->propertyThree + ->setRequired(false) + ->setDefaultValue('testValue') + ->setNullable(true); + + $attributesGenerator = new AttributeGenerator(); + $attributesGenerator->componentsPass($this->componentDefinition->getDto()); + + Assert::assertFalse($this->propertyThree->isRequired()); + Assert::assertSame('testValue', $this->propertyThree->getDefaultValue()); + Assert::assertTrue($this->propertyThree->isNullable()); + + Assert::assertTrue($this->propertyDefinitionThree->hasGetter()); + Assert::assertTrue($this->propertyDefinitionThree->hasSetter()); + Assert::assertTrue($this->propertyDefinitionThree->isNullable()); + Assert::assertFalse($this->propertyDefinitionThree->isInConstructor()); + } + + public function testComponentPassCaseTwo(): void + { + $this->propertyThree + ->setRequired(true) + ->setDefaultValue(null) + ->setNullable(false); + + $attributesGenerator = new AttributeGenerator(); + $attributesGenerator->componentsPass($this->componentDefinition->getDto()); + + Assert::assertTrue($this->propertyThree->isRequired()); + Assert::assertSame(null, $this->propertyThree->getDefaultValue()); + Assert::assertFalse($this->propertyThree->isNullable()); + + Assert::assertTrue($this->propertyDefinitionThree->hasGetter()); + Assert::assertFalse($this->propertyDefinitionThree->hasSetter()); + Assert::assertFalse($this->propertyDefinitionThree->isNullable()); + Assert::assertTrue($this->propertyDefinitionThree->isInConstructor()); } } diff --git a/test/unit/CodeGenerator/Definitions/GraphDefinitionTest.php b/test/unit/CodeGenerator/Definitions/GraphDefinitionTest.php index 0456dd9c..e413634b 100644 --- a/test/unit/CodeGenerator/Definitions/GraphDefinitionTest.php +++ b/test/unit/CodeGenerator/Definitions/GraphDefinitionTest.php @@ -49,6 +49,7 @@ public function testGraphDefinition(array $conditions): void 'Some\Namespace', 'some/media-type' ), + [], [] ); $serviceSubscriberDefinition = new ServiceSubscriberDefinition(); diff --git a/test/unit/CodeGenerator/Definitions/OperationDefinitionTest.php b/test/unit/CodeGenerator/Definitions/OperationDefinitionTest.php index ba5632f1..f7b98d6e 100644 --- a/test/unit/CodeGenerator/Definitions/OperationDefinitionTest.php +++ b/test/unit/CodeGenerator/Definitions/OperationDefinitionTest.php @@ -4,11 +4,10 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\CodeGenerator\Definitions; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ClassDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\OperationDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestDtoDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestHandlerInterfaceDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDefinition; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; @@ -61,20 +60,27 @@ public function operationDefinitionProvider(): array public function testOperationDefinition(array $payload, array $conditions): void { $payload['request'] = (bool) $conditions['hasRequestDtoDefinition'] - ? new RequestDtoDefinition() + ? new DtoDefinition([]) : null; $payload['responses'] = (bool) $conditions['hasResponseDtoDefinition'] - ? [new ResponseDtoDefinition('200', [])] + ? [new ResponseDefinition('200', new DtoDefinition([]))] : []; + $requestHandlerInterface = new RequestHandlerInterfaceDefinition( + $payload['request'], + (bool) $conditions['hasResponseDtoDefinition'] ? [$payload['responses'][0]->getResponseBody()] : [] + ); + $generatedInterfaceDefinition = new OperationDefinition( $payload['url'], $payload['method'], $payload['operationId'], $payload['requestHandlerName'], $payload['summary'], + null, $payload['request'], - $payload['responses'] + $payload['responses'], + $requestHandlerInterface ); Assert::assertSame($payload['url'], $generatedInterfaceDefinition->getUrl()); @@ -84,46 +90,6 @@ public function testOperationDefinition(array $payload, array $conditions): void Assert::assertSame($payload['summary'], $generatedInterfaceDefinition->getSummary()); Assert::assertSame($payload['request'], $generatedInterfaceDefinition->getRequest()); Assert::assertSame($payload['responses'], $generatedInterfaceDefinition->getResponses()); - } - - public function testOperationDefinitionChanged(): void - { - $classDefinition = new ClassDefinition(); - $requestHandlerInterface = new RequestHandlerInterfaceDefinition(); - $changedClassDefinition = new ClassDefinition(); - $changedRequestHandlerInterface = new RequestHandlerInterfaceDefinition(); - - $payload = [ - 'url' => '/some/custom/relative/url', - 'method' => 'GET', - 'operationId' => '', - 'requestHandlerName' => 'SomeCustomRequestHandlerName', - 'summary' => null, - 'request' => null, - 'responses' => [], - ]; - - $generatedInterfaceDefinition = new OperationDefinition( - $payload['url'], - $payload['method'], - $payload['operationId'], - $payload['requestHandlerName'], - $payload['summary'], - $payload['request'], - $payload['responses'] - ); - - $generatedInterfaceDefinition->setMarkersInterface($classDefinition); - $generatedInterfaceDefinition->setRequestHandlerInterface($requestHandlerInterface); - - Assert::assertSame($classDefinition, $generatedInterfaceDefinition->getMarkersInterface()); Assert::assertSame($requestHandlerInterface, $generatedInterfaceDefinition->getRequestHandlerInterface()); - - $generatedInterfaceDefinition - ->setMarkersInterface($changedClassDefinition) - ->setRequestHandlerInterface($changedRequestHandlerInterface); - - Assert::assertSame($changedClassDefinition, $generatedInterfaceDefinition->getMarkersInterface()); - Assert::assertSame($changedRequestHandlerInterface, $generatedInterfaceDefinition->getRequestHandlerInterface()); } } diff --git a/test/unit/CodeGenerator/Definitions/RequestDtoDefinitionTest.php b/test/unit/CodeGenerator/Definitions/RequestDtoDefinitionTest.php index 19a07205..52cefe98 100644 --- a/test/unit/CodeGenerator/Definitions/RequestDtoDefinitionTest.php +++ b/test/unit/CodeGenerator/Definitions/RequestDtoDefinitionTest.php @@ -4,15 +4,14 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\CodeGenerator\Definitions; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestBodyDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestParametersDtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\PropertyDefinition; +use OnMoon\OpenApiServerBundle\Specification\Definitions\Property; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; -/** - * @covers \OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestDtoDefinition - */ +use function array_key_exists; + final class RequestDtoDefinitionTest extends TestCase { /** @@ -96,20 +95,42 @@ public function requestDtoDefinitionProvider(): array */ public function testRequestDtoDefinition(array $conditions, array $expected): void { - $bodyDtoDefinition = new RequestBodyDtoDefinition([]); - $queryParameters = new RequestParametersDtoDefinition([]); - $pathParameters = new RequestParametersDtoDefinition([]); + $propertyOne = new Property('body'); + $propertyOne->setRequired(true); + + $propertyTwo = new Property('queryParameters'); + $propertyTwo->setRequired(true); + + $propertyThree = new Property('pathParameters'); + $propertyThree->setRequired(true); + + $propertyObjectTypeDefinitionOne = new DtoDefinition([]); + $propertyObjectTypeDefinitionTwo = new DtoDefinition([]); + $propertyObjectTypeDefinitionThree = new DtoDefinition([]); + + $propertyDefinitionOne = new PropertyDefinition($propertyOne); + $propertyDefinitionOne->setObjectTypeDefinition($propertyObjectTypeDefinitionOne); + + $propertyDefinitionTwo = new PropertyDefinition($propertyTwo); + $propertyDefinitionTwo->setObjectTypeDefinition($propertyObjectTypeDefinitionTwo); - $payload = []; - $payload['bodyDtoDefinition'] = (bool) $conditions['hasBodyDtoDefinition'] ? $bodyDtoDefinition : null; - $payload['queryParameters'] = (bool) $conditions['hasQueryParameters'] ? $queryParameters : null; - $payload['pathParameters'] = (bool) $conditions['hasPathParameters'] ? $pathParameters : null; + $propertyDefinitionThree = new PropertyDefinition($propertyThree); + $propertyDefinitionThree->setObjectTypeDefinition($propertyObjectTypeDefinitionThree); + + $payload = []; + if ((bool) $conditions['hasBodyDtoDefinition']) { + $payload['bodyDtoDefinition'] = $propertyDefinitionOne; + } + + if ((bool) $conditions['hasQueryParameters']) { + $payload['queryParameters'] = $propertyDefinitionTwo; + } + + if ((bool) $conditions['hasPathParameters']) { + $payload['pathParameters'] = $propertyDefinitionThree; + } - $requestDtoDefinition = new RequestDtoDefinition( - $payload['bodyDtoDefinition'], - $payload['queryParameters'], - $payload['pathParameters'] - ); + $requestDtoDefinition = new DtoDefinition($payload); Assert::assertSame($expected['isEmpty'], $requestDtoDefinition->isEmpty()); @@ -118,9 +139,9 @@ public function testRequestDtoDefinition(array $conditions, array $expected): vo } $propertiesMap = [ - 'body' => $payload['bodyDtoDefinition'], - 'queryParameters' => $payload['queryParameters'], - 'pathParameters' => $payload['pathParameters'], + 'body' => array_key_exists('bodyDtoDefinition', $payload) ? $payload['bodyDtoDefinition']->getObjectTypeDefinition() : null, + 'queryParameters' => array_key_exists('queryParameters', $payload) ? $payload['queryParameters']->getObjectTypeDefinition() : null, + 'pathParameters' => array_key_exists('pathParameters', $payload) ? $payload['pathParameters']->getObjectTypeDefinition() : null, ]; foreach ($requestDtoDefinition->getProperties() as $property) { diff --git a/test/unit/CodeGenerator/Definitions/RequestHandlerInterfaceDefinitionTest.php b/test/unit/CodeGenerator/Definitions/RequestHandlerInterfaceDefinitionTest.php index 1e917ad6..dcdd3b80 100644 --- a/test/unit/CodeGenerator/Definitions/RequestHandlerInterfaceDefinitionTest.php +++ b/test/unit/CodeGenerator/Definitions/RequestHandlerInterfaceDefinitionTest.php @@ -4,8 +4,9 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\CodeGenerator\Definitions; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ClassDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestHandlerInterfaceDefinition; +use OnMoon\OpenApiServerBundle\Interfaces\RequestHandler; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; @@ -51,19 +52,18 @@ public function requestHandlerInterfaceDefinitionProvider(): array */ public function testRequestHandlerInterfaceDefinition(array $payload, array $conditions): void { - $payload['requestType'] = (bool) $conditions['hasRequestType'] ? new ClassDefinition() : null; - $payload['responseType'] = (bool) $conditions['hasResponseType'] ? new ClassDefinition() : null; + $payload['requestType'] = (bool) $conditions['hasRequestType'] ? new DtoDefinition([]) : null; + $payload['responseTypes'] = (bool) $conditions['hasResponseType'] ? [new DtoDefinition([])] : []; - $requestHandlerInterfaceDefinition = new RequestHandlerInterfaceDefinition(); + $requestHandlerInterfaceDefinition = new RequestHandlerInterfaceDefinition($payload['requestType'], $payload['responseTypes']); $requestHandlerInterfaceDefinition - ->setRequestType($payload['requestType']) - ->setResponseType($payload['responseType']) ->setMethodName($payload['methodName']) ->setMethodDescription($payload['methodDescription']); Assert::assertSame($payload['requestType'], $requestHandlerInterfaceDefinition->getRequestType()); - Assert::assertSame($payload['responseType'], $requestHandlerInterfaceDefinition->getResponseType()); + Assert::assertSame($payload['responseTypes'], $requestHandlerInterfaceDefinition->getResponseTypes()); Assert::assertSame($payload['methodName'], $requestHandlerInterfaceDefinition->getMethodName()); Assert::assertSame($payload['methodDescription'], $requestHandlerInterfaceDefinition->getMethodDescription()); + Assert::assertSame(RequestHandler::class, $requestHandlerInterfaceDefinition->getExtends()->getFQCN()); } } diff --git a/test/unit/CodeGenerator/Definitions/ResponseDtoDefinitionTest.php b/test/unit/CodeGenerator/Definitions/ResponseDtoDefinitionTest.php index 719ebcb3..d8b12730 100644 --- a/test/unit/CodeGenerator/Definitions/ResponseDtoDefinitionTest.php +++ b/test/unit/CodeGenerator/Definitions/ResponseDtoDefinitionTest.php @@ -4,14 +4,15 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\CodeGenerator\Definitions; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\PropertyDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDefinition; use OnMoon\OpenApiServerBundle\Specification\Definitions\Property; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; /** - * @covers \OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDtoDefinition + * @covers \OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDefinition */ final class ResponseDtoDefinitionTest extends TestCase { @@ -43,14 +44,14 @@ public function testResponseDtoDefinition(array $conditions): void $payload = []; $payload['statusCode'] = '200'; - $payload['properties'] = (bool) $conditions['hasProperties'] ? [$propertyDefinition] : []; + $payload['properties'] = (bool) $conditions['hasProperties'] ? new DtoDefinition([$propertyDefinition]) : new DtoDefinition([]); - $responseDtoDefinition = new ResponseDtoDefinition( + $responseDtoDefinition = new ResponseDefinition( $payload['statusCode'], $payload['properties'] ); Assert::assertSame($payload['statusCode'], $responseDtoDefinition->getStatusCode()); - Assert::assertSame($payload['properties'], $responseDtoDefinition->getProperties()); + Assert::assertSame($payload['properties'], $responseDtoDefinition->getResponseBody()); } } diff --git a/test/unit/CodeGenerator/Definitions/ServiceSubscriberDefinitionTest.php b/test/unit/CodeGenerator/Definitions/ServiceSubscriberDefinitionTest.php index 8d3c71e9..cf32ff02 100644 --- a/test/unit/CodeGenerator/Definitions/ServiceSubscriberDefinitionTest.php +++ b/test/unit/CodeGenerator/Definitions/ServiceSubscriberDefinitionTest.php @@ -6,6 +6,7 @@ use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ClassDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ServiceSubscriberDefinition; +use OnMoon\OpenApiServerBundle\Interfaces\ApiLoader; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; @@ -42,6 +43,9 @@ public function testServiceSubscriberDefinition(array $conditions): void $payload['implements'] = (bool) $conditions['hasImplements'] ? [$classDefinition] : []; $serviceSubscriberDefinition = new ServiceSubscriberDefinition(); + + Assert::assertSame(ApiLoader::class, $serviceSubscriberDefinition->getImplements()[0]->getFQCN()); + $serviceSubscriberDefinition->setImplements($payload['implements']); Assert::assertSame($payload['implements'], $serviceSubscriberDefinition->getImplements()); diff --git a/test/unit/CodeGenerator/Definitions/SpecificationDefinitionTest.php b/test/unit/CodeGenerator/Definitions/SpecificationDefinitionTest.php index 8a740d72..5c3f9fc8 100644 --- a/test/unit/CodeGenerator/Definitions/SpecificationDefinitionTest.php +++ b/test/unit/CodeGenerator/Definitions/SpecificationDefinitionTest.php @@ -4,7 +4,9 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\CodeGenerator\Definitions; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ComponentDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\OperationDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestHandlerInterfaceDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\SpecificationDefinition; use OnMoon\OpenApiServerBundle\Specification\Definitions\SpecificationConfig; use PHPUnit\Framework\Assert; @@ -57,14 +59,19 @@ public function testSpecificationDefinition(array $conditions): void 'Some\Namespace', 'some/media-type' ); + + $requestHandlerInterfaceDefinition = new RequestHandlerInterfaceDefinition(null, []); + $operationDefinition = new OperationDefinition( $payload['url'], $payload['method'], $payload['operationId'], $payload['requestHandlerName'], $payload['summary'], + null, $payload['request'], - $payload['responses'] + $payload['responses'], + $requestHandlerInterfaceDefinition ); $payload = []; @@ -73,7 +80,8 @@ public function testSpecificationDefinition(array $conditions): void $specificationDefinition = new SpecificationDefinition( $payload['specification'], - $payload['operations'] + $payload['operations'], + [new ComponentDefinition('TestComponent')] ); Assert::assertSame($payload['operations'], $specificationDefinition->getOperations()); diff --git a/test/unit/CodeGenerator/GraphGeneratorTest.php b/test/unit/CodeGenerator/GraphGeneratorTest.php index 030ed8bb..3e2c0402 100644 --- a/test/unit/CodeGenerator/GraphGeneratorTest.php +++ b/test/unit/CodeGenerator/GraphGeneratorTest.php @@ -6,18 +6,17 @@ use cebe\openapi\ReferenceContext; use cebe\openapi\spec\OpenApi; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ComponentDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GraphDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\OperationDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\PropertyDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestBodyDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestParametersDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestHandlerInterfaceDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ServiceSubscriberDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\SpecificationDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\GraphGenerator; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\Property; use OnMoon\OpenApiServerBundle\Specification\Definitions\SpecificationConfig; use OnMoon\OpenApiServerBundle\Specification\SpecificationLoader; @@ -27,15 +26,60 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use function array_map; +use function count; + /** @covers \OnMoon\OpenApiServerBundle\CodeGenerator\GraphGenerator */ class GraphGeneratorTest extends TestCase { + private const SPECIFICATION_PATH = '/'; + private const SPECIFICATION_NAME = 'test'; + /** @var SpecificationLoader|MockObject */ private $specificationLoader; + private SpecificationConfig $specificationConfig; + private Property $pathParameterProperty; + private Property $pathProperty; + private Property $bodyParameterProperty; + private Property $bodyProperty; + private DtoDefinition $pathParameterPropertyDefinition; + private DtoDefinition $bodyParameterPropertyDefinition; + private DtoDefinition $requestDefinition; + private PropertyDefinition $pathPropertyDefinition; + private PropertyDefinition $bodyPropertyDefinition; public function setUp(): void { $this->specificationLoader = $this->createMock(SpecificationLoader::class); + + $this->specificationConfig = new SpecificationConfig( + self::SPECIFICATION_PATH, + null, + '/', + 'application/json' + ); + + $this->pathParameterProperty = new Property('goodId'); + $this->pathParameterProperty + ->setRequired(true) + ->setScalarTypeId(0); + $this->pathParameterPropertyDefinition = new DtoDefinition([new PropertyDefinition($this->pathParameterProperty)]); + + $this->pathProperty = new Property('pathParameters'); + $this->pathProperty->setRequired(true); + $this->pathPropertyDefinition = new PropertyDefinition($this->pathProperty); + $this->pathPropertyDefinition->setObjectTypeDefinition($this->pathParameterPropertyDefinition); + + $this->bodyParameterProperty = new Property('id'); + $this->bodyParameterProperty->setScalarTypeId(0); + $this->bodyParameterPropertyDefinition = new DtoDefinition([new PropertyDefinition($this->bodyParameterProperty)]); + + $this->bodyProperty = new Property('body'); + $this->bodyProperty->setRequired(true); + $this->bodyPropertyDefinition = new PropertyDefinition($this->bodyProperty); + $this->bodyPropertyDefinition->setObjectTypeDefinition($this->bodyParameterPropertyDefinition); + + $this->requestDefinition = new DtoDefinition([$this->pathPropertyDefinition, $this->bodyPropertyDefinition]); } public function tearDown(): void @@ -46,7 +90,6 @@ public function tearDown(): void public function testGenerateClassGraph(): void { - $specificationPath = '/'; $openApiSpecificationArray = [ 'paths' => [ @@ -118,40 +161,294 @@ public function testGenerateClassGraph(): void ], ]; - $specificationName = 'test'; - $specificationConfig = new SpecificationConfig( - $specificationPath, + $openApi = new OpenApi($openApiSpecificationArray); + $openApi->setReferenceContext(new ReferenceContext($openApi, self::SPECIFICATION_PATH)); + $openApi->resolveReferences(); + + $specification = (new SpecificationParser(new ScalarTypesResolver(), []))->parseOpenApi(self::SPECIFICATION_NAME, $this->specificationConfig, $openApi); + + $this->specificationLoader->expects(self::once()) + ->method('list') + ->willReturn([self::SPECIFICATION_NAME => $this->specificationConfig]); + + $this->specificationLoader + ->expects(self::once()) + ->method('load') + ->with(self::SPECIFICATION_NAME) + ->willReturn($specification); + + $componentDefinitions = []; + $componentSchemas = $specification->getComponentSchemas(); + foreach ($componentSchemas as $name => $_objectSchema) { + $componentDefinitions[] = new ComponentDefinition($name); + } + + $responseTitleProperty = new Property('title'); + $responseTitleProperty + ->setRequired(true) + ->setScalarTypeId(0); + + $responseObjectProperty = new Property('object_property'); + $responseObjectProperty + ->setRequired(true) + ->setObjectTypeDefinition( + new ObjectSchema([]) + ); + $responseObjectPropertyTypeDefinition = new PropertyDefinition($responseObjectProperty); + $responseObjectPropertyTypeDefinition->setObjectTypeDefinition( + new DtoDefinition([]) + ); + $responseDefinition = new ResponseDefinition('200', new DtoDefinition([new PropertyDefinition($responseTitleProperty), $responseObjectPropertyTypeDefinition])); + $redirectResponseDefinition = new ResponseDefinition('304', new DtoDefinition([])); + $responses = [$responseDefinition, $redirectResponseDefinition]; + + $singleHttpCode = null; + if (count($responses) === 1 && $responses[0]->getResponseBody()->isEmpty()) { + $singleHttpCode = $responses[0]->getStatusCode(); + $responses = []; + } + + $service = new RequestHandlerInterfaceDefinition( + $this->requestDefinition, + array_map( + static fn (ResponseDefinition $response) => $response->getResponseBody(), + $responses + ) + ); + + $operationDefinition = new OperationDefinition( + '/goods/{goodId}', + 'get', + 'getGood', + 'test.getGood', null, - '/', - 'application/json' + $singleHttpCode, + $this->requestDefinition, + $responses, + $service ); + $specificationDefinition = new SpecificationDefinition($this->specificationConfig, [$operationDefinition], $componentDefinitions); + $expectedGraphDefinition = new GraphDefinition([$specificationDefinition], new ServiceSubscriberDefinition()); + + $graphGenerator = new GraphGenerator($this->specificationLoader); + $graphDefinition = $graphGenerator->generateClassGraph(); + + $generatedSpecification = $graphDefinition->getSpecifications()[0]; + $generatedSpecificationOperations = $generatedSpecification->getOperations(); + $generatedSpecificationOperationResponses = $generatedSpecificationOperations[0]->getResponses(); + $generatedSpecificationOperationResponseStatusCode = $generatedSpecificationOperationResponses[0]->getStatusCode(); + + Assert::assertEquals($expectedGraphDefinition, $graphDefinition); + Assert::assertCount(2, $generatedSpecificationOperationResponses); + Assert::assertFalse($generatedSpecificationOperationResponses[0]->getResponseBody()->isEmpty()); + Assert::assertSame('200', $generatedSpecificationOperationResponseStatusCode); + } + + public function testGenerateClassGraphTwo(): void + { + $openApiSpecificationArray = + [ + 'paths' => [ + '/goods/{goodId}' => [ + 'get' => [ + 'operationId' => 'getGood', + 'parameters' => [ + ['$ref' => '#/components/parameters/GoodIdParam'], + ], + 'requestBody' => [ + 'required' => true, + 'content' => [ + 'application/json' => [ + 'schema' => ['$ref' => '#/components/schemas/GoodRequestSchema'], + ], + ], + ], + 'responses' => [ + '200' => [ + 'description' => 'OK', + 'content' => [ + 'application/json' => [ + 'schema' => ['$ref' => '#/components/schemas/GoodResponseSchema'], + ], + ], + ], + ], + ], + ], + ], + 'components' => [ + 'schemas' => [ + 'GoodResponseSchema' => [ + 'type' => 'object', + 'properties' => [], + ], + 'GoodRequestSchema' => [ + 'required' => true, + 'type' => 'object', + 'properties' => [ + 'id' => ['type' => 'string'], + ], + ], + ], + 'parameters' => [ + 'GoodIdParam' => [ + 'name' => 'goodId', + 'in' => 'path', + 'required' => true, + 'schema' => ['type' => 'string'], + ], + ], + ], + ]; + $openApi = new OpenApi($openApiSpecificationArray); - $openApi->setReferenceContext(new ReferenceContext($openApi, $specificationPath)); + $openApi->setReferenceContext(new ReferenceContext($openApi, self::SPECIFICATION_PATH)); $openApi->resolveReferences(); - $specification = (new SpecificationParser(new ScalarTypesResolver()))->parseOpenApi($specificationName, $specificationConfig, $openApi); + $specification = (new SpecificationParser(new ScalarTypesResolver(), []))->parseOpenApi(self::SPECIFICATION_NAME, $this->specificationConfig, $openApi); $this->specificationLoader->expects(self::once()) ->method('list') - ->willReturn([$specificationName => $specificationConfig]); + ->willReturn([self::SPECIFICATION_NAME => $this->specificationConfig]); $this->specificationLoader ->expects(self::once()) ->method('load') - ->with($specificationName) + ->with(self::SPECIFICATION_NAME) ->willReturn($specification); - $requestProperty = new Property('goodId'); - $requestProperty + $componentDefinitions = []; + $componentSchemas = $specification->getComponentSchemas(); + foreach ($componentSchemas as $name => $_objectSchema) { + $componentDefinitions[] = new ComponentDefinition($name); + } + + $responseTitleProperty = new Property('title'); + $responseTitleProperty ->setRequired(true) ->setScalarTypeId(0); - $requestParametersDtoDefinition = new RequestParametersDtoDefinition([new PropertyDefinition($requestProperty)]); - $requestBodyProperty = new Property('id'); - $requestBodyProperty - ->setScalarTypeId(0); - $requestBodyDtoDefinition = new RequestBodyDtoDefinition([new PropertyDefinition($requestBodyProperty)]); - $requestDtoDefinition = new RequestDtoDefinition($requestBodyDtoDefinition, null, $requestParametersDtoDefinition); + + $responseDefinition = new ResponseDefinition('200', new DtoDefinition([])); + $responses = [$responseDefinition]; + + $singleHttpCode = null; + if (count($responses) === 1 && $responses[0]->getResponseBody()->isEmpty()) { + $singleHttpCode = $responses[0]->getStatusCode(); + $responses = []; + } + + $service = new RequestHandlerInterfaceDefinition( + $this->requestDefinition, + array_map( + static fn (ResponseDefinition $response) => $response->getResponseBody(), + $responses + ) + ); + + $operationDefinition = new OperationDefinition( + '/goods/{goodId}', + 'get', + 'getGood', + 'test.getGood', + null, + $singleHttpCode, + $this->requestDefinition, + $responses, + $service + ); + + $specificationDefinition = new SpecificationDefinition($this->specificationConfig, [$operationDefinition], $componentDefinitions); + $expectedGraphDefinition = new GraphDefinition([$specificationDefinition], new ServiceSubscriberDefinition()); + + $graphGenerator = new GraphGenerator($this->specificationLoader); + $graphDefinition = $graphGenerator->generateClassGraph(); + + Assert::assertEquals($expectedGraphDefinition, $graphDefinition); + } + + public function testGenerateClassGraphThree(): void + { + $openApiSpecificationArray = + [ + 'paths' => [ + '/goods/{goodId}' => [ + 'get' => [ + 'operationId' => 'getGood', + 'parameters' => [ + ['$ref' => '#/components/parameters/GoodIdParam'], + ], + 'requestBody' => [ + 'required' => true, + 'content' => [ + 'application/json' => [ + 'schema' => ['$ref' => '#/components/schemas/GoodRequestSchema'], + ], + ], + ], + 'responses' => [ + '200' => [ + 'description' => 'OK', + 'content' => [ + 'application/json' => [ + 'schema' => ['$ref' => '#/components/schemas/GoodResponseSchema'], + ], + ], + ], + ], + ], + ], + ], + 'components' => [ + 'schemas' => [ + 'GoodResponseSchema' => [ + 'type' => 'object', + 'properties' => [ + 'title' => ['type' => 'string'], + 'object_property' => ['type' => 'object'], + ], + 'required' => ['title','object_property'], + ], + 'GoodRequestSchema' => [ + 'required' => true, + 'type' => 'object', + 'properties' => [ + 'id' => ['type' => 'string'], + ], + ], + ], + 'parameters' => [ + 'GoodIdParam' => [ + 'name' => 'goodId', + 'in' => 'path', + 'required' => true, + 'schema' => ['type' => 'string'], + ], + ], + ], + ]; + + $openApi = new OpenApi($openApiSpecificationArray); + $openApi->setReferenceContext(new ReferenceContext($openApi, self::SPECIFICATION_PATH)); + $openApi->resolveReferences(); + + $specification = (new SpecificationParser(new ScalarTypesResolver(), []))->parseOpenApi(self::SPECIFICATION_NAME, $this->specificationConfig, $openApi); + + $this->specificationLoader->expects(self::once()) + ->method('list') + ->willReturn([self::SPECIFICATION_NAME => $this->specificationConfig]); + + $this->specificationLoader + ->expects(self::once()) + ->method('load') + ->with(self::SPECIFICATION_NAME) + ->willReturn($specification); + + $componentDefinitions = []; + $componentSchemas = $specification->getComponentSchemas(); + foreach ($componentSchemas as $name => $_objectSchema) { + $componentDefinitions[] = new ComponentDefinition($name); + } $responseTitleProperty = new Property('title'); $responseTitleProperty @@ -162,22 +459,55 @@ public function testGenerateClassGraph(): void $responseObjectProperty ->setRequired(true) ->setObjectTypeDefinition( - new ObjectType([]) + new ObjectSchema([]) ); $responseObjectPropertyTypeDefinition = new PropertyDefinition($responseObjectProperty); $responseObjectPropertyTypeDefinition->setObjectTypeDefinition( new DtoDefinition([]) ); - $responseDtoDefinition = new ResponseDtoDefinition('200', [new PropertyDefinition($responseTitleProperty), $responseObjectPropertyTypeDefinition]); - $redirectResponseDtoDefinition = new ResponseDtoDefinition('304', []); + $responseDefinition = new ResponseDefinition('200', new DtoDefinition([new PropertyDefinition($responseTitleProperty), $responseObjectPropertyTypeDefinition])); + $responses = [$responseDefinition]; + + $singleHttpCode = null; + if (count($responses) === 1 && $responses[0]->getResponseBody()->isEmpty()) { + $singleHttpCode = $responses[0]->getStatusCode(); + $responses = []; + } + + $service = new RequestHandlerInterfaceDefinition( + $this->requestDefinition, + array_map( + static fn (ResponseDefinition $response) => $response->getResponseBody(), + $responses + ) + ); - $operationDefinition = new OperationDefinition('/goods/{goodId}', 'get', 'getGood', 'test.getGood', null, $requestDtoDefinition, [$responseDtoDefinition, $redirectResponseDtoDefinition]); - $specificationDefinition = new SpecificationDefinition($specificationConfig, [$operationDefinition]); + $operationDefinition = new OperationDefinition( + '/goods/{goodId}', + 'get', + 'getGood', + 'test.getGood', + null, + $singleHttpCode, + $this->requestDefinition, + $responses, + $service + ); + + $specificationDefinition = new SpecificationDefinition($this->specificationConfig, [$operationDefinition], $componentDefinitions); $expectedGraphDefinition = new GraphDefinition([$specificationDefinition], new ServiceSubscriberDefinition()); $graphGenerator = new GraphGenerator($this->specificationLoader); $graphDefinition = $graphGenerator->generateClassGraph(); + $generatedSpecification = $graphDefinition->getSpecifications()[0]; + $generatedSpecificationOperations = $generatedSpecification->getOperations(); + $generatedSpecificationOperationResponses = $generatedSpecificationOperations[0]->getResponses(); + $generatedSpecificationOperationResponseStatusCode = $generatedSpecificationOperationResponses[0]->getStatusCode(); + Assert::assertEquals($expectedGraphDefinition, $graphDefinition); + Assert::assertCount(1, $generatedSpecificationOperationResponses); + Assert::assertFalse($generatedSpecificationOperationResponses[0]->getResponseBody()->isEmpty()); + Assert::assertSame('200', $generatedSpecificationOperationResponseStatusCode); } } diff --git a/test/unit/CodeGenerator/InterfaceGeneratorTest.php b/test/unit/CodeGenerator/InterfaceGeneratorTest.php deleted file mode 100644 index dcd5d0e3..00000000 --- a/test/unit/CodeGenerator/InterfaceGeneratorTest.php +++ /dev/null @@ -1,249 +0,0 @@ -setObjectTypeDefinition($propertyObjectTypeDefinition); - $responseDtoDefinition = new ResponseDtoDefinition('200', [$propertyDefinition]); - $operationDefinition = new OperationDefinition( - '/', - 'get', - 'test', - '', - null, - $request, - [$responseDtoDefinition] - ); - $graphDefinition = new GraphDefinition( - [ - new SpecificationDefinition( - new SpecificationConfig('/', null, '/', 'application/json'), - [$operationDefinition] - ), - ], - new ServiceSubscriberDefinition() - ); - - $interfaceGenerator = new InterfaceGenerator(); - $interfaceGenerator->setAllInterfaces($graphDefinition); - - Assert::assertSame( - ApiLoader::class, - $graphDefinition->getServiceSubscriber()->getImplements()[0]->getFQCN() - ); - } - - public function testSetAllInterfacesForOneResponseSetsResponse(): void - { - $request = null; - $propertyObjectTypeDefinition = new DtoDefinition([]); - $propertyDefinition = new PropertyDefinition(new Property('')); - $propertyDefinition->setObjectTypeDefinition($propertyObjectTypeDefinition); - $responseDtoDefinition = new ResponseDtoDefinition('200', [$propertyDefinition]); - $operationDefinition = new OperationDefinition( - '/', - 'get', - 'test', - '', - null, - $request, - [$responseDtoDefinition] - ); - $graphDefinition = new GraphDefinition( - [ - new SpecificationDefinition( - new SpecificationConfig('/', null, '/', 'application/json'), - [$operationDefinition] - ), - ], - new ServiceSubscriberDefinition() - ); - - $interfaceGenerator = new InterfaceGenerator(); - $interfaceGenerator->setAllInterfaces($graphDefinition); - - Assert::assertNull($operationDefinition->getMarkersInterface()); - - $expectedResponseImplements = ClassDefinition::fromFQCN(ResponseDto::class); - $responseImplements = $responseDtoDefinition->getImplements(); - Assert::assertEquals($expectedResponseImplements, $responseImplements); - - $expectedRequestHandler = new RequestHandlerInterfaceDefinition(); - $expectedRequestHandler - ->setResponseType($responseDtoDefinition) - ->setRequestType($request) - ->setExtends(ClassDefinition::fromFQCN(RequestHandler::class)); - $requestHandler = $operationDefinition->getRequestHandlerInterface(); - Assert::assertEquals($expectedRequestHandler, $requestHandler); - - $expectedPropertyObjectTypeDefinitionImplements = ClassDefinition::fromFQCN(Dto::class); - $propertyObjectTypeDefinitionImplements = $propertyObjectTypeDefinition->getImplements(); - Assert::assertEquals($expectedPropertyObjectTypeDefinitionImplements, $propertyObjectTypeDefinitionImplements); - } - - public function testSetAllInterfacesForSeveralResponsesSetsResponse(): void - { - $request = null; - $responseDtoDefinitionFirst = new ResponseDtoDefinition('200', []); - $responseDtoDefinitionSecond = new ResponseDtoDefinition('304', []); - $operationDefinition = new OperationDefinition( - '/', - 'get', - 'test', - '', - null, - $request, - [$responseDtoDefinitionFirst, $responseDtoDefinitionSecond] - ); - $graphDefinition = new GraphDefinition( - [ - new SpecificationDefinition( - new SpecificationConfig('/', null, '/', 'application/json'), - [$operationDefinition] - ), - ], - new ServiceSubscriberDefinition() - ); - - $interfaceGenerator = new InterfaceGenerator(); - $interfaceGenerator->setAllInterfaces($graphDefinition); - - $expectedMakersInterface = new GeneratedInterfaceDefinition(); - $expectedMakersInterface->setExtends(ClassDefinition::fromFQCN(ResponseDto::class)); - $makerInterface = $operationDefinition->getMarkersInterface(); - Assert::assertEquals($expectedMakersInterface, $makerInterface); - - $expectedResponseDtoDefinitionImplements = $expectedMakersInterface; - Assert::assertEquals($expectedResponseDtoDefinitionImplements, $responseDtoDefinitionFirst->getImplements()); - Assert::assertEquals($expectedResponseDtoDefinitionImplements, $responseDtoDefinitionSecond->getImplements()); - - $expectedRequestHandler = new RequestHandlerInterfaceDefinition(); - $expectedRequestHandler - ->setResponseType($expectedMakersInterface) - ->setRequestType($request) - ->setExtends(ClassDefinition::fromFQCN(RequestHandler::class)); - $requestHandler = $operationDefinition->getRequestHandlerInterface(); - Assert::assertEquals($expectedRequestHandler, $requestHandler); - } - - public function testSetAllInterfacesWithRequestSetsRequest(): void - { - $requestBodyDtoDefinition = new RequestBodyDtoDefinition([]); - $request = new RequestDtoDefinition($requestBodyDtoDefinition, null, null); - $responseDtoDefinition = new ResponseDtoDefinition('200', []); - $operationDefinition = new OperationDefinition( - '/', - 'get', - 'test', - '', - null, - $request, - [$responseDtoDefinition] - ); - $graphDefinition = new GraphDefinition( - [ - new SpecificationDefinition( - new SpecificationConfig('/', null, '/', 'application/json'), - [$operationDefinition] - ), - ], - new ServiceSubscriberDefinition() - ); - - $interfaceGenerator = new InterfaceGenerator(); - $interfaceGenerator->setAllInterfaces($graphDefinition); - - $expectedRequestImplements = ClassDefinition::fromFQCN(Dto::class); - $requestImplements = $request->getImplements(); - Assert::assertEquals($expectedRequestImplements, $requestImplements); - - $expectedRequestBodyDtoDefinitionImplements = ClassDefinition::fromFQCN(Dto::class); - $requestBodyDtoDefinitionImplements = $requestBodyDtoDefinition->getImplements(); - Assert::assertEquals($expectedRequestBodyDtoDefinitionImplements, $requestBodyDtoDefinitionImplements); - } - - public function testSetAllInterfacesSetsNestedObjects(): void - { - $request = null; - $responseDtoDefinition = new ResponseDtoDefinition( - '200', - [ - (new PropertyDefinition(new Property('property'))) - ->setObjectTypeDefinition( - new DtoDefinition([ - (new PropertyDefinition(new Property('property'))) - ->setObjectTypeDefinition(new DtoDefinition([])), - ]) - ), - ] - ); - $operationDefinition = new OperationDefinition( - '/', - 'get', - 'test', - '', - null, - $request, - [$responseDtoDefinition] - ); - $graphDefinition = new GraphDefinition( - [ - new SpecificationDefinition( - new SpecificationConfig('/', null, '/', 'application/json'), - [$operationDefinition] - ), - ], - new ServiceSubscriberDefinition() - ); - - $interfaceGenerator = new InterfaceGenerator(); - $interfaceGenerator->setAllInterfaces($graphDefinition); - - $expectedNestedResponseObjectImplements = ClassDefinition::fromFQCN(Dto::class); - /** @var DtoDefinition $responsePropertyObjectTypeDefinition */ - $responsePropertyObjectTypeDefinition = $responseDtoDefinition - ->getProperties()[0] - ->getObjectTypeDefinition(); - /** @var DtoDefinition $responsePropertyNestedObjectTypeDefinition */ - $responsePropertyNestedObjectTypeDefinition = $responsePropertyObjectTypeDefinition - ->getProperties()[0] - ->getObjectTypeDefinition(); - Assert::assertEquals( - $expectedNestedResponseObjectImplements, - $responsePropertyNestedObjectTypeDefinition->getImplements() - ); - } -} diff --git a/test/unit/CodeGenerator/NameGeneratorTest.php b/test/unit/CodeGenerator/NameGeneratorTest.php index b124e091..aaa68e25 100644 --- a/test/unit/CodeGenerator/NameGeneratorTest.php +++ b/test/unit/CodeGenerator/NameGeneratorTest.php @@ -6,14 +6,11 @@ use Lukasoppermann\Httpstatus\Httpstatus; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GeneratedInterfaceDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GraphDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\OperationDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\PropertyDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestBodyDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestDtoDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestHandlerInterfaceDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ServiceSubscriberDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\SpecificationDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\NameGenerator; @@ -122,7 +119,6 @@ public function setAllNamesAndPathsProvider(): array 'className' => 'SomeCustomOperationId', ], ], - 'hasMakersInterface' => false, ], [ 'url' => 'http://example.local', @@ -174,14 +170,6 @@ public function setAllNamesAndPathsProvider(): array 'className' => 'SomeCustomOperationIdOKDto', 'namespace' => 'Some\Custom\Namespace\Apis\SomeCustomNamespace\SomeCustomOperationId\Dto\Response\OK', ], - - 'responseMarkersInterfaceNames' => [ - 'extends' => null, - 'fileName' => 'SomeCustomOperationIdResponse.php', - 'filePath' => str_replace(['/', '\\'], DIRECTORY_SEPARATOR, '/Some/Custom/Path/Apis/SomeCustomNamespace/SomeCustomOperationId/Dto/Response'), - 'className' => 'SomeCustomOperationIdResponse', - 'namespace' => 'Some\Custom\Namespace\Apis\SomeCustomNamespace\SomeCustomOperationId\Dto\Response', - ], ], ], 'expected' => [ @@ -194,7 +182,6 @@ public function setAllNamesAndPathsProvider(): array 'className' => 'SomeCustomOperationId', ], ], - 'hasMakersInterface' => true, ], ], ], @@ -340,22 +327,22 @@ public function testSetAllNamesAndPaths(array $payload): void foreach ($operationDefinition->getResponses() as $operationResponseIndex => $operationDefinitionResponse) { Assert::assertSame( $operationDefinitionPayload['responses'][0]['responseNames']['fileName'], - $operationDefinitionResponse->getFileName() + $operationDefinitionResponse->getResponseBody()->getFileName() ); Assert::assertSame( $operationDefinitionPayload['responses'][0]['responseNames']['filePath'], - $operationDefinitionResponse->getFilePath() + $operationDefinitionResponse->getResponseBody()->getFilePath() ); Assert::assertSame( $operationDefinitionPayload['responses'][0]['responseNames']['className'], - $operationDefinitionResponse->getClassName() + $operationDefinitionResponse->getResponseBody()->getClassName() ); Assert::assertSame( $operationDefinitionPayload['responses'][0]['responseNames']['namespace'], - $operationDefinitionResponse->getNamespace() + $operationDefinitionResponse->getResponseBody()->getNamespace() ); - $responseProperties = $operationDefinitionResponse->getProperties(); + $responseProperties = $operationDefinitionResponse->getResponseBody()->getProperties(); if (count($responseProperties) <= 0) { continue; } @@ -374,29 +361,9 @@ public function testSetAllNamesAndPaths(array $payload): void ); } - if (! ($operationDefinition->getMarkersInterface() instanceof GeneratedInterfaceDefinition)) { - continue; - } - - /** @var GeneratedInterfaceDefinition $markersInterface */ - $markersInterface = $operationDefinition->getMarkersInterface(); - - Assert::assertSame( - $operationDefinitionPayload['responses'][0]['responseMarkersInterfaceNames']['fileName'], - $markersInterface->getFileName() - ); - Assert::assertSame( - $operationDefinitionPayload['responses'][0]['responseMarkersInterfaceNames']['filePath'], - $markersInterface->getFilePath() - ); - Assert::assertSame( - $operationDefinitionPayload['responses'][0]['responseMarkersInterfaceNames']['className'], - $markersInterface->getClassName() - ); - Assert::assertSame( - $operationDefinitionPayload['responses'][0]['responseMarkersInterfaceNames']['namespace'], - $markersInterface->getNamespace() - ); +// if (count($operationDefinitionPayload['responses']) === 0) { +// continue; +// } } } } @@ -426,15 +393,17 @@ private function getSpecificationsByPayload(array $payload): array ); } - $request = new RequestDtoDefinition( - new RequestBodyDtoDefinition($requestProperties) - ); + $request = new DtoDefinition([ + (new PropertyDefinition( + (new Property('body'))->setRequired(true) + ))->setObjectTypeDefinition(new DtoDefinition($requestProperties)), + ]); } else { $request = null; } - /** @var ResponseDtoDefinition[]|array $responses */ - $responses = array_map(static function (array $payload): ResponseDtoDefinition { + /** @var ResponseDefinition[]|array $responses */ + $responses = array_map(static function (array $payload): ResponseDefinition { $responseProperties = []; if (count($payload['properties']) > 0) { $responseProperties[] = new PropertyDefinition( @@ -442,36 +411,31 @@ private function getSpecificationsByPayload(array $payload): array ); } - return new ResponseDtoDefinition( + return new ResponseDefinition( $payload['statusCode'], - $responseProperties + new DtoDefinition($responseProperties) ); }, $payload['responses']); - $operationDefinition = new OperationDefinition( + $requestHandlerInterfaceDefinition = new RequestHandlerInterfaceDefinition(null, []); + + return new OperationDefinition( $payload['url'], $payload['method'], $payload['operationId'], $payload['requestHandlerName'], $payload['summary'], + null, $request, - $responses + $responses, + $requestHandlerInterfaceDefinition ); - - $operationDefinition->setMarkersInterface( - (bool) $payload['hasMakersInterface'] ? new GeneratedInterfaceDefinition() : null - ); - - $requestHandlerInterfaceDefinition = new RequestHandlerInterfaceDefinition(); - - $operationDefinition->setRequestHandlerInterface($requestHandlerInterfaceDefinition); - - return $operationDefinition; }, $payload['operations']); return new SpecificationDefinition( $specificationConfig, - $operations + $operations, + [] ); }, $payload['graph']['specifications']); } @@ -492,7 +456,7 @@ public function testSetRequestNames(): void $httpStatus = new Httpstatus($payload['httpStatus']['statusArray']); - $root = new RequestDtoDefinition(); + $root = new DtoDefinition([]); $nameGenerator = new NameGenerator( $namingStrategy, @@ -562,7 +526,7 @@ public function testSetResponseNames(array $additionalPayload, array $expected): $httpStatus = new Httpstatus($payload['httpStatus']['statusArray']); - $root = new ResponseDtoDefinition($additionalPayload['statusCode'], []); + $root = new ResponseDefinition($additionalPayload['statusCode'], new DtoDefinition([])); $nameGenerator = new NameGenerator( $namingStrategy, @@ -577,10 +541,10 @@ public function testSetResponseNames(array $additionalPayload, array $expected): $path ); - Assert::assertSame($expected['fileName'], $root->getFileName()); - Assert::assertSame($expected['filePath'], $root->getFilePath()); - Assert::assertSame($expected['className'], $root->getClassName()); - Assert::assertSame($expected['namespace'], $root->getNamespace()); + Assert::assertSame($expected['fileName'], $root->getResponseBody()->getFileName()); + Assert::assertSame($expected['filePath'], $root->getResponseBody()->getFilePath()); + Assert::assertSame($expected['className'], $root->getResponseBody()->getClassName()); + Assert::assertSame($expected['namespace'], $root->getResponseBody()->getNamespace()); } public function testSetTreePathsAndClassNames(): void @@ -621,7 +585,7 @@ public function testSetTreePathsAndClassNames(): void $payload['rootPath'] ); - $nameGenerator->setTreePathsAndClassNames( + $nameGenerator->setTreeNames( $root, $namespace, $className, @@ -638,10 +602,14 @@ public function testSetTreePathsAndClassNames(): void return; } - Assert::assertSame('SomeCustomClassPropertyDto.php', $rootSubDefinition->getFileName()); - Assert::assertSame(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, '/CustomPath/SomeCustomClassProperty'), $rootSubDefinition->getFilePath()); - Assert::assertSame('SomeCustomClassPropertyDto', $rootSubDefinition->getClassName()); - Assert::assertSame('CustomNamespace\SomeCustomClassProperty', $rootSubDefinition->getNamespace()); +// Assert::assertSame('SomeCustomClassPropertyDto.php', $rootSubDefinition->getFileName()); +// Assert::assertSame(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, '/CustomPath/SomeCustomClassProperty'), $rootSubDefinition->getFilePath()); +// Assert::assertSame('SomeCustomClassPropertyDto', $rootSubDefinition->getClassName()); +// Assert::assertSame('CustomNamespace\SomeCustomClassProperty', $rootSubDefinition->getNamespace()); + Assert::assertSame('SomeCustomPropertyDto.php', $rootSubDefinition->getFileName()); + Assert::assertSame(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, '/CustomPath/SomeCustomProperty'), $rootSubDefinition->getFilePath()); + Assert::assertSame('SomeCustomPropertyDto', $rootSubDefinition->getClassName()); + Assert::assertSame('CustomNamespace\SomeCustomProperty', $rootSubDefinition->getNamespace()); } public function testGetFileName(): void @@ -702,7 +670,7 @@ public function testSetTreeGettersSetters(): void $payload['rootPath'] ); - $nameGenerator->setTreeGettersSetters($root); + $nameGenerator->setGettersSetters($root); Assert::assertSame( 'getSomeCustomMinorClassProperty', @@ -721,19 +689,19 @@ public function testSetTreeGettersSetters(): void $root->getProperties()[1]->getSetterName() ); - $subDefinition = $root->getProperties()[1]->getObjectTypeDefinition(); - if ($subDefinition === null) { - return; - } - - Assert::assertSame( - 'getSomeCustomClassSubProperty', - $subDefinition->getProperties()[0]->getGetterName() - ); - Assert::assertSame( - 'setSomeCustomClassSubProperty', - $subDefinition->getProperties()[0]->getSetterName() - ); +// $subDefinition = $root->getProperties()[1]->getObjectTypeDefinition(); +// if ($subDefinition === null) { +// return; +// } +// +// Assert::assertSame( +// 'getSomeCustomClassSubProperty', +// $subDefinition->getProperties()[0]->getGetterName() +// ); +// Assert::assertSame( +// 'setSomeCustomClassSubProperty', +// $subDefinition->getProperties()[0]->getSetterName() +// ); } public function testSetTreePropertyClassNames(): void @@ -773,7 +741,7 @@ public function testSetTreePropertyClassNames(): void $payload['rootPath'] ); - $nameGenerator->setTreePropertyClassNames($root); + $nameGenerator->setPropertyClassNames($root); Assert::assertSame( 'someCustomMinorProperty', @@ -784,13 +752,13 @@ public function testSetTreePropertyClassNames(): void $root->getProperties()[1]->getClassPropertyName() ); - $subDefinition = $root->getProperties()[1]->getObjectTypeDefinition(); - if ($subDefinition !== null) { - Assert::assertSame( - 'someCustomSubProperty', - $subDefinition->getProperties()[0]->getClassPropertyName() - ); - } +// $subDefinition = $root->getProperties()[1]->getObjectTypeDefinition(); +// if ($subDefinition !== null) { +// Assert::assertSame( +// 'someCustomSubProperty', +// $subDefinition->getProperties()[0]->getClassPropertyName() +// ); +// } Assert::assertSame( '_111', diff --git a/test/unit/CodeGenerator/Naming/DefaultNamingStrategyTest.php b/test/unit/CodeGenerator/Naming/DefaultNamingStrategyTest.php index 1956b0cc..d8e5a4bd 100644 --- a/test/unit/CodeGenerator/Naming/DefaultNamingStrategyTest.php +++ b/test/unit/CodeGenerator/Naming/DefaultNamingStrategyTest.php @@ -21,9 +21,9 @@ final class DefaultNamingStrategyTest extends TestCase public function setUp(): void { - $someReservedWords = ['key' => 'SomeReservedWord']; - $reservedWords = new ReservedWords(['keySome' => $someReservedWords]); - $this->defaultNamingStrategy = new DefaultNamingStrategy($reservedWords, 'NameSpace', '0'); + $someReservedWords = ['namespace' => '8.0', 'method' => '7.4']; + $reservedWords = new ReservedWords(['somereservedword' => $someReservedWords]); + $this->defaultNamingStrategy = new DefaultNamingStrategy($reservedWords, 'NameSpace', '8.0'); } /** @@ -36,6 +36,9 @@ public function isAllowedPhpPropertyNameDataProvider(): array ['SomeCamelCase', true], ['Spaces are not allowed', false], ['&^%$!@#', false], + ['1SomeClass', false], + ['SomeClass1', true], + ['SOMEUPPERCASECLASS', true], ]; } @@ -57,7 +60,7 @@ public function stringToMethodNameDataProvider(): array return [ ['test', 'test'], ['some Random Phrase', 'someRandomPhrase'], - ['SomeReservedWord', 'someReservedWord'], + ['SomeReservedWord', '_someReservedWord'], ['1test', '_1test'], ['9999', '_9999'], ]; @@ -70,7 +73,7 @@ public function testStringToMethodNameData(string $string, string $expectedOutpu { $actualOutput = $this->defaultNamingStrategy->stringToMethodName($string); - TestCase::assertEquals($actualOutput, $expectedOutput); + TestCase::assertEquals($expectedOutput, $actualOutput); } public function testGetInterfaceFQCN(): void @@ -81,10 +84,24 @@ public function testGetInterfaceFQCN(): void TestCase::assertEquals($expectedOutput, $actualOutput); } - public function testStringToNamespace(): void + /** + * @return array> + */ + public function stringToNamespaceDataProvider(): array + { + return [ + ['1some random string', '_1someRandomString'], + ['SomeReservedWord', '_SomeReservedWord'], + ['Some random 1 string', 'SomeRandom1String'], + ]; + } + + /** + * @dataProvider stringToNamespaceDataProvider + */ + public function testStringToNamespace(string $string, string $expectedOutput): void { - $expectedOutput = '_1someRandomString'; - $actualOutput = $this->defaultNamingStrategy->stringToNamespace('1some random string'); + $actualOutput = $this->defaultNamingStrategy->stringToNamespace($string); TestCase::assertEquals($expectedOutput, $actualOutput); } diff --git a/test/unit/CodeGenerator/PhpParserGenerators/InterfaceCodeGeneratorTest.php b/test/unit/CodeGenerator/PhpParserGenerators/InterfaceCodeGeneratorTest.php index d4ef90c1..7166409f 100644 --- a/test/unit/CodeGenerator/PhpParserGenerators/InterfaceCodeGeneratorTest.php +++ b/test/unit/CodeGenerator/PhpParserGenerators/InterfaceCodeGeneratorTest.php @@ -5,11 +5,10 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\CodeGenerator\PhpParserGenerators; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ClassDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GeneratedFileDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GeneratedInterfaceDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestHandlerInterfaceDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\PhpParserGenerators\InterfaceCodeGenerator; -use OnMoon\OpenApiServerBundle\Interfaces\ResponseDto; use OnMoon\OpenApiServerBundle\Types\ScalarTypesResolver; use PhpParser\BuilderFactory; use PHPUnit\Framework\Assert; @@ -42,71 +41,9 @@ protected function tearDown(): void parent::tearDown(); } - public function testGenerateWithNotExtendedGeneratedInterfaceDefinition(): void - { - $generatedInterfaceDefinition = new GeneratedInterfaceDefinition(); - $generatedInterfaceDefinition->setNamespace('Test\Test2'); - $generatedInterfaceDefinition->setClassName('TestClass'); - - $generatedCode = - <<interfaceCodeGenerator->generate($generatedInterfaceDefinition); - - Assert::assertEquals($expectedGeneratedFileDefinition, $generatedFileDefinition); - } - - public function testGenerateWithExtendedGeneratedInterfaceDefinition(): void - { - $generatedInterfaceDefinition = new GeneratedInterfaceDefinition(); - $generatedInterfaceDefinition->setExtends(ClassDefinition::fromFQCN(ResponseDto::class)); - $generatedInterfaceDefinition->setNamespace('Test\Test2'); - $generatedInterfaceDefinition->setClassName('TestClass'); - - $generatedCode = - <<interfaceCodeGenerator->generate($generatedInterfaceDefinition); - - Assert::assertEquals($expectedGeneratedFileDefinition, $generatedFileDefinition); - } - public function testGenerateWithRequestHandlerInterfaceDefinition(): void { - $generatedInterfaceDefinition = new RequestHandlerInterfaceDefinition(); + $generatedInterfaceDefinition = new RequestHandlerInterfaceDefinition(null, []); $generatedInterfaceDefinition->setClassName('TestClass'); $generatedInterfaceDefinition->setMethodName('test'); @@ -119,11 +56,12 @@ public function testGenerateWithRequestHandlerInterfaceDefinition(): void declare (strict_types=1); namespace Test\Test2; +use OnMoon\OpenApiServerBundle\Interfaces\RequestHandler; /** * This interface was automatically generated * You should not change it manually as it will be overwritten */ -interface TestClass +interface TestClass extends RequestHandler { public function test() : void; } @@ -139,12 +77,15 @@ public function test() : void; public function testGenerateWithRequestHandlerInterfaceDefinitionAndRequestedType(): void { - $generatedInterfaceDefinition = new RequestHandlerInterfaceDefinition(); + $dtoDefinition = new DtoDefinition([]); + $dtoDefinition + ->setNamespace('') + ->setClassName('TestClass'); + $generatedInterfaceDefinition = new RequestHandlerInterfaceDefinition($dtoDefinition, []); $generatedInterfaceDefinition->setClassName('TestClass'); - $generatedInterfaceDefinition->setMethodName('test'); - $generatedInterfaceDefinition->setNamespace('Test\Test2'); - $generatedInterfaceDefinition->setRequestType(ClassDefinition::fromFQCN('TestClass')); + $generatedInterfaceDefinition->setMethodName('test'); + $generatedInterfaceDefinition->setNamespace('Test\Test2'); $generatedCode = <<setClassName('TestClass'); $generatedInterfaceDefinition->setMethodName('test'); $generatedInterfaceDefinition->setNamespace('Test\Test2'); - $generatedInterfaceDefinition->setResponseType(ClassDefinition::fromFQCN('TestClass')); + $generatedInterfaceDefinition->setResponseTypes([ClassDefinition::fromFQCN('TestClass')]); $generatedCode = <<setClassName('TestClass'); $generatedInterfaceDefinition->setMethodName('test'); @@ -225,11 +168,12 @@ public function testGenerateWithRequestHandlerInterfaceDefinitionAndDescription( declare (strict_types=1); namespace Test\Test2; +use OnMoon\OpenApiServerBundle\Interfaces\RequestHandler; /** * This interface was automatically generated * You should not change it manually as it will be overwritten */ -interface TestClass +interface TestClass extends RequestHandler { /** method description */ public function test() : void; diff --git a/test/unit/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGeneratorTest.php b/test/unit/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGeneratorTest.php index 6f56bb0f..6da908c0 100644 --- a/test/unit/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGeneratorTest.php +++ b/test/unit/CodeGenerator/PhpParserGenerators/ServiceSubscriberCodeGeneratorTest.php @@ -5,14 +5,13 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\CodeGenerator\PhpParserGenerators; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ClassDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ComponentDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GraphDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\OperationDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\PropertyDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestBodyDtoDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestDtoDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\RequestHandlerInterfaceDefinition; -use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDtoDefinition; +use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ResponseDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\ServiceSubscriberDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\SpecificationDefinition; use OnMoon\OpenApiServerBundle\CodeGenerator\PhpParserGenerators\ServiceSubscriberCodeGenerator; @@ -36,26 +35,25 @@ public function testGenerateWithValidParams(): void $this->serviceSubscriberCodeGenerator = new ServiceSubscriberCodeGenerator($builderFactory, $scalarTypeResolver, '1', false); - $requestHandlerInterfaceDefinition = new RequestHandlerInterfaceDefinition(); + $requestHandlerInterfaceDefinition = new RequestHandlerInterfaceDefinition(null, []); $requestHandlerInterfaceDefinition->setNamespace('NamespaceOne\NamespaceTwo'); $requestHandlerInterfaceDefinition->setClassName('ClassName'); - $request = new RequestDtoDefinition( - new RequestBodyDtoDefinition( - [ - (new PropertyDefinition(new Property('locator'))) - ->setObjectTypeDefinition( - new DtoDefinition([ - (new PropertyDefinition(new Property('locator'))) - ->setObjectTypeDefinition(new DtoDefinition([])), - ]) - ), - ] - ) + $request = new DtoDefinition( + [ + (new PropertyDefinition(new Property('locator'))) + ->setObjectTypeDefinition( + new DtoDefinition([ + (new PropertyDefinition(new Property('locator'))) + ->setObjectTypeDefinition(new DtoDefinition([])), + ]) + ), + ] ); - $responseDtoDefinition = new ResponseDtoDefinition( + + $responseDtoDefinition = new ResponseDefinition( '200', - [ + (new DtoDefinition([ (new PropertyDefinition(new Property('locator'))) ->setObjectTypeDefinition( new DtoDefinition([ @@ -63,19 +61,20 @@ public function testGenerateWithValidParams(): void ->setObjectTypeDefinition(new DtoDefinition([])), ]) ), - ] + ]))->setNamespace('')->setClassName('ResponceClassName') ); - $operationDefinition = new OperationDefinition( + + $operationDefinition = new OperationDefinition( '/', 'get', 'test', 'test', null, + null, $request, - [$responseDtoDefinition] + [$responseDtoDefinition], + $requestHandlerInterfaceDefinition ); - $operationDefinition->setRequestHandlerInterface($requestHandlerInterfaceDefinition); - $operationDefinition->setMarkersInterface(ClassDefinition::fromFQCN('NamespaceOne\NamespaceTwo\ClassName')); $serviceSubscriberDefinition = new ServiceSubscriberDefinition(); $serviceSubscriberDefinition->setNamespace('NamespaceOne\NamespaceTwo'); @@ -89,7 +88,8 @@ public function testGenerateWithValidParams(): void [ new SpecificationDefinition( new SpecificationConfig('/', null, '/', 'application/json'), - [$operationDefinition] + [$operationDefinition], + [new ComponentDefinition('TestComponent')] ), ], $serviceSubscriberDefinition @@ -108,12 +108,14 @@ public function testGenerateWithValidParams(): void use Psr\Container\ContainerInterface; use OnMoon\OpenApiServerBundle\Interfaces\RequestHandler; +use \ResponceClassName; /** * This class was automatically generated * You should not change it manually as it will be overwritten */ class ClassName implements ClassName, ClassName { + private const HTTP_CODES = [ClassName::class => [ResponceClassName::class => ['200']]]; private ContainerInterface $locator; public function __construct(ContainerInterface $locator) { @@ -124,7 +126,7 @@ public function __construct(ContainerInterface $locator) */ public static function getSubscribedServices() : array { - return array('test' => '?' . ClassName::class); + return ['test' => '?' . ClassName::class]; } public function get(string $interface) : ?RequestHandler { @@ -133,6 +135,11 @@ public function get(string $interface) : ?RequestHandler } return $this->locator->get($interface); } + /** @return string[] */ + public function getAllowedCodes(string $apiClass, string $dtoClass) : array + { + return self::HTTP_CODES[$apiClass][$dtoClass]; + } } EOD; diff --git a/test/unit/Event/CodeGenerator/ClassGraphReadyEventTest.php b/test/unit/Event/CodeGenerator/ClassGraphReadyEventTest.php index c9bc7df9..80c0c831 100644 --- a/test/unit/Event/CodeGenerator/ClassGraphReadyEventTest.php +++ b/test/unit/Event/CodeGenerator/ClassGraphReadyEventTest.php @@ -19,15 +19,17 @@ final class ClassGraphReadyEventTest extends TestCase { public function testGraphMethodReturnGraph(): void { - $graph = new GraphDefinition( + $graph = new GraphDefinition( [ new SpecificationDefinition( new SpecificationConfig('/', null, '/', 'application/json'), + [], [] ), ], new ServiceSubscriberDefinition() ); + $classGraphReadyEvent = new ClassGraphReadyEvent($graph); $return = $classGraphReadyEvent->graph(); diff --git a/test/unit/Event/Server/RequestDtoEventTest.php b/test/unit/Event/Server/RequestDtoEventTest.php index a542a78e..67ddc276 100644 --- a/test/unit/Event/Server/RequestDtoEventTest.php +++ b/test/unit/Event/Server/RequestDtoEventTest.php @@ -39,7 +39,7 @@ public static function fromArray(array $data): Dto } }; $operationId = '12345'; - $specification = new Specification([], new OpenApi([])); + $specification = new Specification([], [], new OpenApi([])); $requestHandler = new class () implements RequestHandler{ }; @@ -57,7 +57,7 @@ public function testRequestDtoEventGettersWhenRequestDtoNull(): void $request = new Request(); $requestDto = null; $operationId = '12345'; - $specification = new Specification([], new OpenApi([])); + $specification = new Specification([], [], new OpenApi([])); $requestHandler = new class () implements RequestHandler{ }; diff --git a/test/unit/Event/Server/RequestEventTest.php b/test/unit/Event/Server/RequestEventTest.php index 69c2ab94..2290c02f 100644 --- a/test/unit/Event/Server/RequestEventTest.php +++ b/test/unit/Event/Server/RequestEventTest.php @@ -20,7 +20,7 @@ public function testRequestEventGettersReturnCorrectValues(): void { $requestMock = new Request(); $operationId = '12345'; - $specification = new Specification([], new OpenApi([])); + $specification = new Specification([], [], new OpenApi([])); $requestDtoEvent = new RequestEvent($requestMock, $operationId, $specification); diff --git a/test/unit/Event/Server/ResponseDtoEventTest.php b/test/unit/Event/Server/ResponseDtoEventTest.php index 6682d1ae..52252612 100644 --- a/test/unit/Event/Server/ResponseDtoEventTest.php +++ b/test/unit/Event/Server/ResponseDtoEventTest.php @@ -6,7 +6,7 @@ use cebe\openapi\spec\OpenApi; use OnMoon\OpenApiServerBundle\Event\Server\ResponseDtoEvent; -use OnMoon\OpenApiServerBundle\Interfaces\ResponseDto; +use OnMoon\OpenApiServerBundle\Interfaces\Dto; use OnMoon\OpenApiServerBundle\Specification\Definitions\Specification; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; @@ -18,9 +18,9 @@ final class ResponseDtoEventTest extends TestCase { public function testResponseDtoGettersReturnCorrectValues(): void { - $responseDtoMock = $this->createMock(ResponseDto::class); + $responseDtoMock = $this->createMock(Dto::class); $operationId = '12345'; - $specification = new Specification([], new OpenApi([])); + $specification = new Specification([], [], new OpenApi([])); $responseDtoEvent = new ResponseDtoEvent($responseDtoMock, $operationId, $specification); diff --git a/test/unit/Event/Server/ResponseEventTest.php b/test/unit/Event/Server/ResponseEventTest.php index b9494557..d1d487ff 100644 --- a/test/unit/Event/Server/ResponseEventTest.php +++ b/test/unit/Event/Server/ResponseEventTest.php @@ -20,7 +20,7 @@ public function testResponseEventGettersReturnCorrectValues(): void { $response = new Response(); $operationId = '12345'; - $specification = new Specification([], new OpenApi([])); + $specification = new Specification([], [], new OpenApi([])); $responseEvent = new ResponseEvent($response, $operationId, $specification); diff --git a/test/unit/Router/RouteLoaderTest.php b/test/unit/Router/RouteLoaderTest.php index 508a178d..45f08603 100644 --- a/test/unit/Router/RouteLoaderTest.php +++ b/test/unit/Router/RouteLoaderTest.php @@ -6,7 +6,7 @@ use cebe\openapi\spec\OpenApi; use OnMoon\OpenApiServerBundle\Router\RouteLoader; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\Operation; use OnMoon\OpenApiServerBundle\Specification\Definitions\Property; use OnMoon\OpenApiServerBundle\Specification\Definitions\Specification; @@ -95,7 +95,7 @@ public function testLoad($resource, ?string $type = null): void $operationMethod = 'GET'; $operationRequestHandlerName = 'RequestHandlerExample'; $operationRequestParameters = [ - 'path' => new ObjectType([new Property('path')]), + 'path' => new ObjectSchema([new Property('path')]), ]; $requirements = []; @@ -113,7 +113,7 @@ public function testLoad($resource, ?string $type = null): void $expectedOperationsCount = count($operations); - $specification = new Specification($operations, new OpenApi([])); + $specification = new Specification($operations, [], new OpenApi([])); $this->specificationLoaderMock ->expects(self::once()) diff --git a/test/unit/Serializer/ArrayDtoSerializerTest.php b/test/unit/Serializer/ArrayDtoSerializerTest.php index 9d31b3f1..ba566117 100644 --- a/test/unit/Serializer/ArrayDtoSerializerTest.php +++ b/test/unit/Serializer/ArrayDtoSerializerTest.php @@ -5,9 +5,8 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\Serializer; use OnMoon\OpenApiServerBundle\Interfaces\Dto; -use OnMoon\OpenApiServerBundle\Interfaces\ResponseDto; use OnMoon\OpenApiServerBundle\Serializer\ArrayDtoSerializer; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\Operation; use OnMoon\OpenApiServerBundle\Specification\Definitions\Property; use OnMoon\OpenApiServerBundle\Types\ScalarTypesResolver; @@ -199,11 +198,11 @@ public function testCreateRequestDtoWithPath(array $payload, array $expected, ar $requestContent ); - $requestQuery = new ObjectType([ + $requestQuery = new ObjectSchema([ (new Property('firstParam')) ->setDefaultValue('SomeDefaultQueryParam'), ]); - $requestPath = new ObjectType([ + $requestPath = new ObjectSchema([ (new Property('firstParam')) ->setDefaultValue('SomeDefaultPathParam'), ]); @@ -211,14 +210,14 @@ public function testCreateRequestDtoWithPath(array $payload, array $expected, ar $requestBodyThirdParam = new Property('thirdParam'); if (isset($payload['bodyParams']['thirdParam'])) { $requestBodyThirdParam->setObjectTypeDefinition( - new ObjectType([ + new ObjectSchema([ (new Property('firstParam')) ->setDefaultValue('SomeDefaultFirstBodySubParam'), ]) ); } - $requestBody = new ObjectType([ + $requestBody = new ObjectSchema([ (new Property('firstParam')) ->setDefaultValue('SomeDefaultFirstBodyParam'), (new Property('secondParam')) @@ -527,10 +526,9 @@ public function testCreateResponseFromDtoPropertyIsNull( bool $sendNotRequiredNullableNulls, array $expectedResult ): void { - /** @var ResponseDto $okResponseDtoFQCN */ + /** @var Dto $okResponseDtoFQCN */ $okResponseDtoFQCN = get_class($this->makeOKResponseDtoFCQN()); - /** @var ResponseDto $okResponseDto */ $okResponseDto = $okResponseDtoFQCN::fromArray([ self::OK_RESPONSE_DTO_FIRST_PROP => null, self::OK_RESPONSE_DTO_SECOND_PROP => null, @@ -541,22 +539,12 @@ public function testCreateResponseFromDtoPropertyIsNull( $result = $arrayDtoSerializer->createResponseFromDto( $okResponseDto, - new Operation( - '/example/path', - 'POST', - 'PostRequestHandler', - null, - null, - [], - [ - $okResponseDtoFQCN::_getResponseCode() => new ObjectType([ - (new Property(self::OK_RESPONSE_DTO_FIRST_PROP)) - ->setDefaultValue($propertyConditions['defaultValue']) - ->setRequired($propertyConditions['isRequired']) - ->setNullable($propertyConditions['isNullable']), - ]), - ] - ) + new ObjectSchema([ + (new Property(self::OK_RESPONSE_DTO_FIRST_PROP)) + ->setDefaultValue($propertyConditions['defaultValue']) + ->setRequired($propertyConditions['isRequired']) + ->setNullable($propertyConditions['isNullable']), + ]) ); Assert::assertSame($expectedResult, $result); @@ -615,10 +603,9 @@ public function createResponseFromDtoMultiplePropertiesProvider(): array */ public function testCreateResponseFromDtoMultipleProperties(array $properties, array $expected): void { - /** @var ResponseDto $okResponseDtoFQCN */ + /** @var Dto $okResponseDtoFQCN */ $okResponseDtoFQCN = get_class($this->makeOKResponseDtoFCQN()); - /** @var ResponseDto $okResponseDto */ $okResponseDto = $okResponseDtoFQCN::fromArray($properties); $arrayDtoSerializer = new ArrayDtoSerializer(new ScalarTypesResolver(), false); @@ -626,7 +613,7 @@ public function testCreateResponseFromDtoMultipleProperties(array $properties, a $responseThirdParam = new Property(self::OK_RESPONSE_DTO_THIRD_PROP); if (isset($properties[self::OK_RESPONSE_DTO_THIRD_PROP])) { $responseThirdParam->setObjectTypeDefinition( - new ObjectType([ + new ObjectSchema([ (new Property('firstParam')) ->setDefaultValue('SomeDefaultFirstSubParam'), ]) @@ -635,23 +622,13 @@ public function testCreateResponseFromDtoMultipleProperties(array $properties, a $result = $arrayDtoSerializer->createResponseFromDto( $okResponseDto, - new Operation( - '/example/path', - 'POST', - 'PostRequestHandler', - null, - null, - [], - [ - $okResponseDtoFQCN::_getResponseCode() => new ObjectType([ - (new Property(self::OK_RESPONSE_DTO_FIRST_PROP)) - ->setDefaultValue('SomeFirstDefaultValue'), - (new Property(self::OK_RESPONSE_DTO_SECOND_PROP)) - ->setDefaultValue('SomeSecondDefaultValue'), - $responseThirdParam, - ]), - ] - ) + new ObjectSchema([ + (new Property(self::OK_RESPONSE_DTO_FIRST_PROP)) + ->setDefaultValue('SomeFirstDefaultValue'), + (new Property(self::OK_RESPONSE_DTO_SECOND_PROP)) + ->setDefaultValue('SomeSecondDefaultValue'), + $responseThirdParam, + ]) ); Assert::assertSame( @@ -673,9 +650,9 @@ public function testCreateResponseFromDtoMultipleProperties(array $properties, a ); } - private function makeOKResponseDtoFCQN(): ResponseDto + private function makeOKResponseDtoFCQN(): Dto { - return new class () implements ResponseDto { + return new class () implements Dto { private ?string $firstProp = null; private ?string $secondProp = null; private ?Dto $thirdProp = null; diff --git a/test/unit/Specification/Definitions/ObjectTypeTest.php b/test/unit/Specification/Definitions/ObjectSchemaTest.php similarity index 84% rename from test/unit/Specification/Definitions/ObjectTypeTest.php rename to test/unit/Specification/Definitions/ObjectSchemaTest.php index f1fead07..14b1428c 100644 --- a/test/unit/Specification/Definitions/ObjectTypeTest.php +++ b/test/unit/Specification/Definitions/ObjectSchemaTest.php @@ -4,15 +4,15 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\Specification\Definitions; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\Property; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; /** - * @covers \OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType + * @covers \OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema */ -final class ObjectTypeTest extends TestCase +final class ObjectSchemaTest extends TestCase { public function testGetProperties(): void { @@ -22,7 +22,7 @@ public function testGetProperties(): void 'array' => true, 'description' => 'Some Custom Description', 'nullable' => true, - 'objectTypeDefinition' => new ObjectType([]), + 'objectTypeDefinition' => new ObjectSchema([]), 'pattern' => '/[0-9]+/', 'required' => true, 'scalarTypeId' => 777, @@ -39,7 +39,7 @@ public function testGetProperties(): void ->setRequired($propertyData['required']) ->setScalarTypeId($propertyData['scalarTypeId']); - $objectType = new ObjectType([$property]); + $objectType = new ObjectSchema([$property]); Assert::assertSame([$property], $objectType->getProperties()); } diff --git a/test/unit/Specification/Definitions/OperationTest.php b/test/unit/Specification/Definitions/OperationTest.php index 9d80b74c..ba95b527 100644 --- a/test/unit/Specification/Definitions/OperationTest.php +++ b/test/unit/Specification/Definitions/OperationTest.php @@ -4,7 +4,7 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\Specification\Definitions; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\Operation; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; @@ -39,13 +39,13 @@ public function operationsProvider(): array 'method' => 'POST', 'requestHandlerName' => 'some-custom-request-handler-name', 'summary' => 'Some Custom Summary', - 'requestBody' => new ObjectType([]), + 'requestBody' => new ObjectSchema([]), 'requestParameters' => [ - 'query' => new ObjectType([]), - 'path' => new ObjectType([]), + 'query' => new ObjectSchema([]), + 'path' => new ObjectSchema([]), ], 'responses' => [ - '200' => new ObjectType([]), + '200' => new ObjectSchema([]), ], ], ], diff --git a/test/unit/Specification/Definitions/PropertyTest.php b/test/unit/Specification/Definitions/PropertyTest.php index b63a03e1..b91a07c7 100644 --- a/test/unit/Specification/Definitions/PropertyTest.php +++ b/test/unit/Specification/Definitions/PropertyTest.php @@ -4,7 +4,7 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\Specification\Definitions; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\Property; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; @@ -63,7 +63,7 @@ public function propertiesProvider(): array 'array' => true, 'description' => 'Some Custom Description', 'nullable' => true, - 'objectTypeDefinition' => new ObjectType([]), + 'objectTypeDefinition' => new ObjectSchema([]), 'pattern' => '/[0-9]+/', 'required' => true, 'scalarTypeId' => 777, diff --git a/test/unit/Specification/Definitions/SpecificationTest.php b/test/unit/Specification/Definitions/SpecificationTest.php index 8093492d..48962166 100644 --- a/test/unit/Specification/Definitions/SpecificationTest.php +++ b/test/unit/Specification/Definitions/SpecificationTest.php @@ -5,7 +5,7 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\Specification\Definitions; use cebe\openapi\spec\OpenApi; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\Operation; use OnMoon\OpenApiServerBundle\Specification\Definitions\Specification; use PHPUnit\Framework\Assert; @@ -28,10 +28,10 @@ public function specificationsProvider(): array 'method' => 'POST', 'requestHandlerName' => 'some-custom-request-handler-name', 'summary' => 'Some Custom Summary', - 'requestBody' => new ObjectType([]), + 'requestBody' => new ObjectSchema([]), 'requestParameters' => [ - 'query' => new ObjectType([]), - 'path' => new ObjectType([]), + 'query' => new ObjectSchema([]), + 'path' => new ObjectSchema([]), ], 'responses' => [], ]; @@ -71,6 +71,7 @@ public function testSpecifications(array $specificationData): void $specification = new Specification( $specificationData['operations'], + [], $openApiMock ); diff --git a/test/unit/Specification/SpecificationParserTest.php b/test/unit/Specification/SpecificationParserTest.php index dac56279..eda7f8b0 100644 --- a/test/unit/Specification/SpecificationParserTest.php +++ b/test/unit/Specification/SpecificationParserTest.php @@ -16,7 +16,7 @@ use cebe\openapi\spec\Schema; use cebe\openapi\spec\Type; use OnMoon\OpenApiServerBundle\Exception\CannotParseOpenApi; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\SpecificationConfig; use OnMoon\OpenApiServerBundle\Specification\SpecificationParser; use OnMoon\OpenApiServerBundle\Types\ScalarTypesResolver; @@ -201,7 +201,7 @@ public function testParseOpenApiSuccess(): void ]), ]); - $specificationParser = new SpecificationParser(new ScalarTypesResolver()); + $specificationParser = new SpecificationParser(new ScalarTypesResolver(), []); $specification = $specificationParser->parseOpenApi( $specificationName, @@ -232,7 +232,7 @@ public function testParseOpenApiSuccess(): void Assert::assertSame('someProperty3', $requestBodyProperties[3]->getName()); - /** @var ObjectType $objectTypeDefinition */ + /** @var ObjectSchema $objectTypeDefinition */ $objectTypeDefinition = $requestBodyProperties[3]->getObjectTypeDefinition(); Assert::assertTrue($objectTypeDefinition->getProperties()[2]->isArray()); @@ -323,7 +323,7 @@ public function testParseOpenApiSuccessRequestBadMediaType(): void ]), ]); - $specificationParser = new SpecificationParser(new ScalarTypesResolver()); + $specificationParser = new SpecificationParser(new ScalarTypesResolver(), []); $specification = $specificationParser->parseOpenApi( $specificationName, @@ -372,7 +372,7 @@ public function testParseOpenApiSuccessRequestBadAndGoodMediaType(): void ]), ]); - $specificationParser = new SpecificationParser(new ScalarTypesResolver()); + $specificationParser = new SpecificationParser(new ScalarTypesResolver(), []); $specification = $specificationParser->parseOpenApi( $specificationName, @@ -381,7 +381,7 @@ public function testParseOpenApiSuccessRequestBadAndGoodMediaType(): void ); Assert::assertInstanceOf( - ObjectType::class, + ObjectSchema::class, $specification ->getOperation('SomeCustomOperationRequestBadMediaType') ->getRequestBody() @@ -473,7 +473,7 @@ public function parseOpenApiSuccessDefaultValueProvider(): array */ public function testParseOpenApiSuccessDefaultValue(array $payload): void { - $specification = (new SpecificationParser(new ScalarTypesResolver()))->parseOpenApi( + $specification = (new SpecificationParser(new ScalarTypesResolver(), []))->parseOpenApi( 'SomeCustomSpecification', new SpecificationConfig( '/some/custom/specification/path', @@ -517,7 +517,7 @@ static function (array $data): Schema { foreach ($specification->getOperation('SomeCustomThirdPostOperation')->getResponse('200')->getProperties() as $propertyName => $property) { if ((bool) $payload['responseProperties'][$property->getName()]['expected']['hasObjectTypeDefinitionInstance']) { - Assert::assertInstanceOf(ObjectType::class, $property->getObjectTypeDefinition()); + Assert::assertInstanceOf(ObjectSchema::class, $property->getObjectTypeDefinition()); } Assert::assertSame( @@ -639,7 +639,7 @@ public function testParseOpenApiThrowCannotParseOpenApi(array $paths, array $exp 'paths' => new Paths($paths), ]); - $specificationParser = new SpecificationParser(new ScalarTypesResolver()); + $specificationParser = new SpecificationParser(new ScalarTypesResolver(), []); if ($expected['exceptionMessage'] === null) { $this->expectException(CannotParseOpenApi::class); @@ -671,7 +671,7 @@ public function testParseOpenApiThrowExceptionNoOperationIdSpecified(): void ]), ]); - $specificationParser = new SpecificationParser(new ScalarTypesResolver()); + $specificationParser = new SpecificationParser(new ScalarTypesResolver(), []); $this->expectException(CannotParseOpenApi::class); @@ -714,7 +714,7 @@ public function testParseOpenApiThrowExceptionPropertyIsNotScheme(): void ]), ]); - $specificationParser = new SpecificationParser(new ScalarTypesResolver()); + $specificationParser = new SpecificationParser(new ScalarTypesResolver(), []); $this->expectException(CannotParseOpenApi::class); $this->expectExceptionMessage('Property is not scheme'); @@ -752,7 +752,7 @@ public function testParseOpenApiWithReferenceInParametersThrowExceptionPropertyI ]), ]); - $specificationParser = new SpecificationParser(new ScalarTypesResolver()); + $specificationParser = new SpecificationParser(new ScalarTypesResolver(), []); $this->expectException(CannotParseOpenApi::class); $this->expectExceptionMessage('Property is not scheme'); @@ -792,7 +792,7 @@ public function testParseOpenApiThrowExceptionArrayIsNotDescribed(): void ]), ]); - $specificationParser = new SpecificationParser(new ScalarTypesResolver()); + $specificationParser = new SpecificationParser(new ScalarTypesResolver(), []); $this->expectException(CannotParseOpenApi::class); $this->expectExceptionMessage( @@ -839,7 +839,7 @@ public function testParseOpenApiThrowExceptionPropertyNotScalar(): void ]), ]); - $specificationParser = new SpecificationParser(new ScalarTypesResolver()); + $specificationParser = new SpecificationParser(new ScalarTypesResolver(), []); $this->expectException(CannotParseOpenApi::class); $this->expectExceptionMessage( @@ -881,7 +881,7 @@ public function testParseOpenApiThrowExceptionTypeNotSupported(): void ]), ]); - $specificationParser = new SpecificationParser(new ScalarTypesResolver()); + $specificationParser = new SpecificationParser(new ScalarTypesResolver(), []); $this->expectException(CannotParseOpenApi::class); $this->expectExceptionMessage( @@ -917,7 +917,7 @@ public function testParseOpenApiThrowExceptionDuplicateOperationId(): void ]), ]); - $specificationParser = new SpecificationParser(new ScalarTypesResolver()); + $specificationParser = new SpecificationParser(new ScalarTypesResolver(), []); $this->expectException(CannotParseOpenApi::class); diff --git a/test/unit/Types/ArgumentResolverTest.php b/test/unit/Types/ArgumentResolverTest.php index 2c9fa435..4e02a38e 100644 --- a/test/unit/Types/ArgumentResolverTest.php +++ b/test/unit/Types/ArgumentResolverTest.php @@ -4,7 +4,7 @@ namespace OnMoon\OpenApiServerBundle\Test\Unit\Types; -use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectType; +use OnMoon\OpenApiServerBundle\Specification\Definitions\ObjectSchema; use OnMoon\OpenApiServerBundle\Specification\Definitions\Property; use OnMoon\OpenApiServerBundle\Types\ArgumentResolver; use OnMoon\OpenApiServerBundle\Types\ScalarTypesResolver; @@ -30,7 +30,7 @@ public function testResolveArgumentPatternsThrowsException(): void { $this->pathParameter->setScalarTypeId(null); $argumentResolver = new ArgumentResolver($this->typeResolver); - $pathParameters = new ObjectType([$this->pathParameter]); + $pathParameters = new ObjectSchema([$this->pathParameter]); $this->expectException(Throwable::class); $this->expectExceptionMessage('Object types are not supported in parameters'); $argumentResolver->resolveArgumentPatterns($pathParameters); @@ -42,7 +42,7 @@ public function testResolveArgumentPatternsResolvesEmptyPatterns(): void ->setScalarTypeId(0) ->setPattern('test_pattern'); $expectedPatterns = []; - $pathParameters = new ObjectType([$this->pathParameter]); + $pathParameters = new ObjectSchema([$this->pathParameter]); $argumentResolver = new ArgumentResolver($this->typeResolver); $patterns = $argumentResolver->resolveArgumentPatterns($pathParameters); Assert::assertSame($expectedPatterns, $patterns); @@ -54,7 +54,7 @@ public function testResolveArgumentPatternsResolvesPregMatchedPatterns(): void ->setScalarTypeId(1) ->setPattern('^123$'); $expectedPatterns = [$this->pathParameter->getName() => '123']; - $pathParameters = new ObjectType([$this->pathParameter]); + $pathParameters = new ObjectSchema([$this->pathParameter]); $argumentResolver = new ArgumentResolver($this->typeResolver); $patterns = $argumentResolver->resolveArgumentPatterns($pathParameters); Assert::assertSame($expectedPatterns, $patterns); @@ -69,7 +69,7 @@ public function testResolveArgumentPatternsResolvesPattern(): void $expectedPatterns = [ $this->pathParameter->getName() => $this->typeResolver->getPattern($scalarTypeId), ]; - $pathParameters = new ObjectType([$this->pathParameter]); + $pathParameters = new ObjectSchema([$this->pathParameter]); $argumentResolver = new ArgumentResolver($this->typeResolver); $patterns = $argumentResolver->resolveArgumentPatterns($pathParameters); Assert::assertSame($expectedPatterns, $patterns); @@ -90,7 +90,7 @@ public function testResolveArgumentPatternsReturnsSeveralPatterns(): void $this->pathParameter->getName() => $this->typeResolver->getPattern($scalarTypeId), $pathParameter2->getName() => $this->typeResolver->getPattern($scalarTypeId), ]; - $pathParameters = new ObjectType([$this->pathParameter, $pathParameter2]); + $pathParameters = new ObjectSchema([$this->pathParameter, $pathParameter2]); $argumentResolver = new ArgumentResolver($this->typeResolver); $patterns = $argumentResolver->resolveArgumentPatterns($pathParameters); Assert::assertSame($expectedPatterns, $patterns); diff --git a/test/unit/Validator/LeaguePSR7RequestSchemaValidatorTest.php b/test/unit/Validator/LeaguePSR7RequestSchemaValidatorTest.php index c5d7da12..49123d61 100644 --- a/test/unit/Validator/LeaguePSR7RequestSchemaValidatorTest.php +++ b/test/unit/Validator/LeaguePSR7RequestSchemaValidatorTest.php @@ -28,7 +28,7 @@ public function testValidate(): void $request = new Request(); $operation = new Operation('test_url', 'test_method', 'RequestHandler'); - $specification = new Specification([$operationId => $operation], $openApi); + $specification = new Specification([$operationId => $operation], [], $openApi); $validatorBuilderMock = $this->createMock(ValidatorBuilder::class); $validatorBuilderMock