diff --git a/assets/x-swagger-bake.yaml b/assets/x-swagger-bake.yaml index 7d6bca01..77d336c9 100644 --- a/assets/x-swagger-bake.yaml +++ b/assets/x-swagger-bake.yaml @@ -14,6 +14,21 @@ x-swagger-bake: required: false schema: type: integer + paginatorOrder: + name: order + in: query + required: false + schema: + type: array + items: + type: object + properties: + field: + type: string + example: name + dir: + type: string + example: asc paginatorSort: name: sort in: query diff --git a/src/Lib/Operation/OperationQueryParameter.php b/src/Lib/Operation/OperationQueryParameter.php index ed75a311..525dcfee 100644 --- a/src/Lib/Operation/OperationQueryParameter.php +++ b/src/Lib/Operation/OperationQueryParameter.php @@ -74,6 +74,7 @@ private function definePagination(): void $this->operation->pushRefParameter('#/x-swagger-bake/components/parameters/paginatorPage'); $this->operation->pushRefParameter('#/x-swagger-bake/components/parameters/paginatorLimit'); $this->pushSortParameter($paginator); + $this->operation->pushRefParameter('#/x-swagger-bake/components/parameters/paginatorOrder'); $this->operation->pushRefParameter('#/x-swagger-bake/components/parameters/paginatorDirection'); } diff --git a/src/Lib/Operation/OperationResponse.php b/src/Lib/Operation/OperationResponse.php index 26c925e1..69678428 100644 --- a/src/Lib/Operation/OperationResponse.php +++ b/src/Lib/Operation/OperationResponse.php @@ -3,9 +3,11 @@ namespace SwaggerBake\Lib\Operation; +use Cake\Utility\Inflector; use ReflectionClass; use ReflectionMethod; use SwaggerBake\Lib\Attribute\AttributeFactory; +use SwaggerBake\Lib\Attribute\OpenApiPaginator; use SwaggerBake\Lib\Attribute\OpenApiResponse; use SwaggerBake\Lib\Attribute\OpenApiSchema; use SwaggerBake\Lib\Attribute\OpenApiSchemaProperty; @@ -21,6 +23,7 @@ use SwaggerBake\Lib\OpenApi\Xml as OpenApiXml; use SwaggerBake\Lib\Route\RouteDecorator; use SwaggerBake\Lib\Swagger; +use UnexpectedValueException; /** * Builds OpenAPI Operation Responses for CRUD actions and controller actions annotated with SwagResponseSchema @@ -407,6 +410,11 @@ private function assignDefaultResponses(): void $schema->setXml((new OpenApiXml())->setName('response')); } + if (isset($this->refMethod) && !empty($this->refMethod->getAttributes(OpenApiPaginator::class))) { + $schema = Inflector::singularize($this->route->getController() ?? throw new UnexpectedValueException()); + $schema = $this->getMimeTypeSchema($mimeType, 'array', '#/components/schemas/' . $schema); + } + $response->pushContent(new Content($mimeType, $schema)); } diff --git a/tests/TestCase/Lib/Model/ModelScannerTest.php b/tests/TestCase/Lib/Model/ModelScannerTest.php index 4dd72d33..5f6f7f6b 100644 --- a/tests/TestCase/Lib/Model/ModelScannerTest.php +++ b/tests/TestCase/Lib/Model/ModelScannerTest.php @@ -23,17 +23,18 @@ class ModelScannerTest extends TestCase private array $config; + private Router $router; + public function setUp(): void { parent::setUp(); - $router = new Router(); Router::createRouteBuilder('/')->scope('/', function (RouteBuilder $builder) { $builder->setExtensions(['json']); $builder->resources('Employees'); }); - $this->router = $router; + $this->router = new Router(); $this->config = [ 'prefix' => '/', diff --git a/tests/TestCase/Lib/Operation/OperationQueryParameterTest.php b/tests/TestCase/Lib/Operation/OperationQueryParameterTest.php index 6c003c8e..cfcceaa1 100644 --- a/tests/TestCase/Lib/Operation/OperationQueryParameterTest.php +++ b/tests/TestCase/Lib/Operation/OperationQueryParameterTest.php @@ -109,7 +109,8 @@ public function test_all_attributes_in_one(): void $operation = $operationQueryParam->getOperationWithQueryParameters(); $parameters = $operation->getParameters(); - $this->assertCount(11, $parameters); + + $this->assertCount(12, $parameters); } public function test_openapi_paginator(): void diff --git a/tests/TestCase/Lib/Operation/OperationResponseTest.php b/tests/TestCase/Lib/Operation/OperationResponseTest.php index 57b6572a..c6c8c6e2 100644 --- a/tests/TestCase/Lib/Operation/OperationResponseTest.php +++ b/tests/TestCase/Lib/Operation/OperationResponseTest.php @@ -4,7 +4,9 @@ use Cake\Routing\RouteBuilder; use Cake\Routing\Router; +use Cake\TestSuite\PHPUnitConsecutiveTrait; use Cake\TestSuite\TestCase; +use SwaggerBake\Lib\Attribute\OpenApiPaginator; use SwaggerBake\Lib\Attribute\OpenApiResponse; use SwaggerBake\Lib\Configuration; use SwaggerBake\Lib\SwaggerFactory; @@ -19,6 +21,7 @@ class OperationResponseTest extends TestCase { use ReflectionAttributeTrait; + use PHPUnitConsecutiveTrait; /** * @var string[] @@ -44,7 +47,8 @@ public function setUp(): void 'delete', 'noResponsesDefined', 'textPlain', - 'options' + 'options', + 'list' ], 'map' => [ 'noResponsesDefined' => [ @@ -61,6 +65,11 @@ public function setUp(): void 'method' => ['options'], 'action' => 'options', 'path' => 'options' + ], + 'list' => [ + 'method' => 'get', + 'action' => 'customGet', + 'path' => 'custom-get' ] ] ]); @@ -68,7 +77,7 @@ public function setUp(): void $this->config = new Configuration([ 'prefix' => '/', - 'yml' => '/config/swagger-bare-bones.yml', + 'yml' => '/config/swagger-with-generic-collection.yml', 'json' => '/webroot/swagger.json', 'webPath' => '/swagger.json', 'hotReload' => false, @@ -172,13 +181,17 @@ public function test_add_operation_with_no_response_defined(): void $route = $this->routes['employees:add']; $mockReflectionMethod = $this->createPartialMock(\ReflectionMethod::class, ['getAttributes']); - $mockReflectionMethod->expects($this->once()) - ->method( - 'getAttributes' + $mockReflectionMethod->expects($this->exactly(2)) + ->method('getAttributes') + ->with( + ...$this->withConsecutive( + [OpenApiResponse::class], + [OpenApiPaginator::class] + ) ) - ->with(OpenApiResponse::class) - ->will( - $this->returnValue([]) + ->willReturnOnConsecutiveCalls( + [], + [] ); $operationResponse = new OperationResponse( @@ -232,13 +245,17 @@ public function test_no_response_defined(): void $route = $this->routes['employees:noresponsedefined']; $mockReflectionMethod = $this->createPartialMock(\ReflectionMethod::class, ['getAttributes']); - $mockReflectionMethod->expects($this->once()) - ->method( - 'getAttributes' + $mockReflectionMethod->expects($this->exactly(2)) + ->method('getAttributes') + ->with( + ...$this->withConsecutive( + [OpenApiResponse::class], + [OpenApiPaginator::class] + ) ) - ->with(OpenApiResponse::class) - ->will( - $this->returnValue([]) + ->willReturnOnConsecutiveCalls( + [], + [] ); $operationResponse = new OperationResponse( @@ -390,6 +407,39 @@ public function test_http_options(): void $this->assertNotEmpty($operationResponse->getOperationWithResponses()->getResponseByCode('200')); } + public function test_custom_get_method_with_collection_response(): void + { + $route = $this->routes['employees:customget']; + + $mockReflectionMethod = $this->mockReflectionMethod(OpenApiResponse::class, [ + 'statusCode' => '200', + 'schemaType' => 'array', + 'ref' => '#/components/schema/Employee', + ]); + + $operationResponse = new OperationResponse( + $this->mockSwagger('getSchemaByName', 'Employee'), + $this->config, + new Operation('employees:customget', 'get'), + $route, + null, + $mockReflectionMethod + ); + + $operation = $operationResponse->getOperationWithResponses(); + $response = $operation->getResponseByCode('200'); + $this->assertNotEmpty($response); + + $content = $response->getContentByMimeType('application/json'); + $this->assertNotEmpty($content); + $this->assertNotEmpty($content->getSchema()); + + /** @var Schema $schema */ + $schema = $content->getSchema(); + $this->assertEquals('array', $schema->getType()); + $this->assertEquals('#/components/schema/Employee', $schema->getItems()['$ref']); + } + /** * Builds a partial mock of Swagger. * diff --git a/tests/test_app/src/Controller/EmployeesController.php b/tests/test_app/src/Controller/EmployeesController.php index ab57ba26..a258f105 100644 --- a/tests/test_app/src/Controller/EmployeesController.php +++ b/tests/test_app/src/Controller/EmployeesController.php @@ -14,7 +14,6 @@ use SwaggerBake\Lib\Attribute\OpenApiResponse; use SwaggerBake\Lib\Attribute\OpenApiSecurity; use SwaggerBake\Lib\Extension\CakeSearch\Attribute\OpenApiSearch; -use SwaggerBake\Test\TestCase\Lib\Attribute\OpenApiDtoTest; use SwaggerBakeTest\App\Dto\CustomResponseSchema; use SwaggerBakeTest\App\Dto\CustomResponseSchemaPublic; use SwaggerBakeTest\App\Dto\EmployeeDataRequest; @@ -138,7 +137,6 @@ public function delete($id): void #[OpenApiQueryParam(name: 'queryParamName', type: "string", isRequired: false)] #[OpenApiHeader(name: 'X-HEAD-ATTRIBUTE', type: 'string', isRequired: true)] #[OpenApiPaginator] - #[OpenApiResponse(schemaType: 'object', description: "hello world")] public function customGet(): void {