diff --git a/bin/php-openapi b/bin/php-openapi index ae2ffdc8..aa4b0697 100755 --- a/bin/php-openapi +++ b/bin/php-openapi @@ -215,7 +215,7 @@ switch ($command) { if ($outputFile === null) { if ($outputFormat === null) { - error("No output fromat specified, please specify --write-json or --write-yaml.", "usage"); + error("No output format specified, please specify --write-json or --write-yaml.", "usage"); } elseif ($outputFormat === 'json') { fwrite(STDOUT, \cebe\openapi\Writer::writeToJson($openApi)); } else { diff --git a/src/spec/PathItem.php b/src/spec/PathItem.php index 4b2debcd..6930e0af 100644 --- a/src/spec/PathItem.php +++ b/src/spec/PathItem.php @@ -180,7 +180,7 @@ public function resolveReferences(ReferenceContext $context = null) foreach ($this->$attribute as $k => $item) { if ($item instanceof Reference) { $referencedObject = $item->resolve(); - $this->$attribute = [$k => $referencedObject] + $this->$attribute; + $this->$attribute = $this->$attribute + [$k => $referencedObject]; if (!$referencedObject instanceof Reference && $referencedObject !== null) { $referencedObject->resolveReferences(); } diff --git a/tests/data/issue/155/compiled-symfony-6.yml b/tests/data/issue/155/compiled-symfony-6.yml new file mode 100644 index 00000000..e1774005 --- /dev/null +++ b/tests/data/issue/155/compiled-symfony-6.yml @@ -0,0 +1,128 @@ +openapi: 3.0.0 +info: + title: 'Test REST API' + description: 'Specifications for the Test REST API.' + contact: + name: bplainia + email: bplainia@lhespotlight.org + version: '2021-05-18' +servers: + - + url: 'http://localhost:8000' + description: Test +paths: + '/v1/organizations/{organizationId}/user': + post: + tags: + - pets + summary: 'Creates a user' + requestBody: + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + required: true + responses: + '201': + description: Created + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + '400': + description: 'Bad Request' + '403': + description: Forbidden + security: + - + BearerAuth: [] + parameters: + - + name: api-version + in: header + description: 'The API version' + required: false + schema: + type: string + format: date + example: '2021-05-18' + - + name: organizationId + in: path + description: 'The Organization ID' + required: true + schema: + type: string + format: uuid + '/v1/organizations/{organizationId}/user/{id}': + get: + summary: 'Gets a user' + responses: + '200': + description: 'A bar' + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + '400': + description: 'Bad Request' + '403': + description: Forbidden + '404': + description: 'Not Found' + security: + - + BearerAuth: [] + parameters: + - + name: api-version + in: header + description: 'The API version' + required: false + schema: + type: string + format: date + example: '2021-05-18' + - + name: organizationId + in: path + description: 'The Organization ID' + required: true + schema: + type: string + format: uuid + - + name: id + in: path + description: 'User''s ID' + required: true + schema: + type: string + format: uuid diff --git a/tests/data/issue/155/compiled-symfony-7.yml b/tests/data/issue/155/compiled-symfony-7.yml new file mode 100644 index 00000000..e7a45025 --- /dev/null +++ b/tests/data/issue/155/compiled-symfony-7.yml @@ -0,0 +1,128 @@ +openapi: 3.0.0 +info: + title: 'Test REST API' + description: 'Specifications for the Test REST API.' + contact: + name: bplainia + email: bplainia@lhespotlight.org + version: '2021-05-18' +servers: + - + url: 'http://localhost:8000' + description: Test +paths: + '/v1/organizations/{organizationId}/user': + post: + tags: + - pets + summary: 'Creates a user' + requestBody: + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + required: true + responses: + '201': + description: Created + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + '400': + description: 'Bad Request' + '403': + description: Forbidden + security: + - + BearerAuth: [] + parameters: + - + name: api-version + in: header + description: 'The API version' + required: false + schema: + type: string + format: date + example: '2021-05-18' + - + name: organizationId + in: path + description: 'The Organization ID' + required: true + schema: + type: string + format: uuid + '/v1/organizations/{organizationId}/user/{id}': + get: + summary: 'Gets a user' + responses: + '200': + description: 'A bar' + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + '400': + description: 'Bad Request' + '403': + description: Forbidden + '404': + description: 'Not Found' + security: + - + BearerAuth: [] + parameters: + - + name: api-version + in: header + description: 'The API version' + required: false + schema: + type: string + format: date + example: '2021-05-18' + - + name: organizationId + in: path + description: 'The Organization ID' + required: true + schema: + type: string + format: uuid + - + name: id + in: path + description: "User's ID" + required: true + schema: + type: string + format: uuid diff --git a/tests/spec/PathTest.php b/tests/spec/PathTest.php index 6c46e4b0..2d47e3e7 100644 --- a/tests/spec/PathTest.php +++ b/tests/spec/PathTest.php @@ -192,4 +192,71 @@ public function testPathItemReference() $this->assertEquals('A bar', $barPath->get->responses['200']->description); $this->assertEquals('non-existing resource', $barPath->get->responses['404']->description); } + + public function testPathParametersAreArrays() + { + $file = __DIR__ . '/data/path-params/openapi.yaml'; + /** @var $openapi \cebe\openapi\spec\OpenApi */ + $openapi = Reader::readFromYamlFile($file, \cebe\openapi\spec\OpenApi::class, true); + + $result = $openapi->validate(); + $this->assertEquals([], $openapi->getErrors(), print_r($openapi->getErrors(), true)); + $this->assertTrue($result); + + $this->assertInstanceOf(Paths::class, $openapi->paths); + $this->assertSame(gettype($openapi->paths->getPaths()), 'array'); + $this->assertInstanceOf(PathItem::class, $usersPath = $openapi->paths['/v1/organizations/{organizationId}/user']); + $this->assertInstanceOf(PathItem::class, $userIdPath = $openapi->paths['/v1/organizations/{organizationId}/user/{id}']); + + $result = $usersPath->validate(); + $this->assertTrue($result); + $this->assertSame(gettype($usersPath->parameters), 'array'); + $this->assertInstanceOf(\cebe\openapi\spec\Parameter::class, $usersPath->parameters[0]); + $this->assertInstanceOf(\cebe\openapi\spec\Parameter::class, $usersPath->parameters[1]); + $this->assertEquals('api-version', $usersPath->parameters[0]->name); + + $result = $userIdPath->validate(); + $this->assertTrue($result); + $this->assertSame(gettype($userIdPath->parameters), 'array'); + $this->assertInstanceOf(\cebe\openapi\spec\Parameter::class, $userIdPath->parameters[0]); + $this->assertInstanceOf(\cebe\openapi\spec\Parameter::class, $userIdPath->parameters[1]); + $this->assertEquals('id', $userIdPath->parameters[2]->name); + + $dirSep = DIRECTORY_SEPARATOR; + $output = dirname(__DIR__) . $dirSep . 'compiled.yml'; + shell_exec('php ' . dirname(__DIR__, 2) . "{$dirSep}bin{$dirSep}php-openapi inline " . $file . ' ' . $output); + + $baseExpected = dirname(__DIR__)."{$dirSep}data{$dirSep}issue{$dirSep}155{$dirSep}"; + $expected = $baseExpected.'compiled-symfony-7.yml'; + $version = static::symfonyYamlVersion(); + $majorVersion = explode('.', $version)[0]; + + if ($majorVersion == 6) { + $expected = $baseExpected."compiled-symfony-6.yml"; + if (version_compare(PHP_VERSION, '8.1', '>=') && version_compare($version, '6.0.0', '!=')) { + $expected = $baseExpected."compiled-symfony-7.yml"; + } + } elseif ($majorVersion == 5) { + $expected = $baseExpected."compiled-symfony-6.yml"; + } + if (stripos(PHP_OS, 'WIN') === 0) { # fixes https://github.com/cebe/php-openapi/actions/runs/14808968938/job/41581244210 + file_put_contents($expected, preg_replace('~\R~', "\n", file_get_contents($expected))); # not an ideal solution, can be refactored + } + + $this->assertFileEquals($output, $expected); + unlink($output); + } + + protected static function symfonyYamlVersion() + { + $package = 'symfony/yaml'; + $installed = json_decode(file_get_contents(__DIR__ . '/../../composer.lock'), true); + + foreach ($installed['packages'] as $pkg) { + if ($pkg['name'] === $package) { + return str_replace('v', '', $pkg['version']); + } + } + return '7.0.0'; + } } diff --git a/tests/spec/data/path-params/global.yaml b/tests/spec/data/path-params/global.yaml new file mode 100644 index 00000000..38348ee4 --- /dev/null +++ b/tests/spec/data/path-params/global.yaml @@ -0,0 +1,28 @@ +components: + parameters: + Version: + in: header + name: api-version + required: false + schema: + type: string + format: date + example: '2021-05-18' + description: The API version + OrganizationId: + in: path + name: organizationId + required: true + schema: + type: string + format: uuid + description: The Organization ID + responses: + BadRequest: + description: Bad Request + Forbidden: + description: Forbidden + NotFound: + description: Not Found + Success: + description: Success \ No newline at end of file diff --git a/tests/spec/data/path-params/openapi.yaml b/tests/spec/data/path-params/openapi.yaml new file mode 100644 index 00000000..9decce27 --- /dev/null +++ b/tests/spec/data/path-params/openapi.yaml @@ -0,0 +1,18 @@ +openapi: 3.0.0 +info: + version: "2021-05-18" + title: Test REST API + description: Specifications for the Test REST API. + contact: + name: bplainia + email: bplainia@lhespotlight.org + +servers: + - url: 'http://localhost:8000' + description: 'Test' + +paths: + /v1/organizations/{organizationId}/user: + $ref: 'user.yaml#/paths/Users' + /v1/organizations/{organizationId}/user/{id}: + $ref: 'user.yaml#/paths/UserId' \ No newline at end of file diff --git a/tests/spec/data/path-params/user.yaml b/tests/spec/data/path-params/user.yaml new file mode 100644 index 00000000..6f1402bd --- /dev/null +++ b/tests/spec/data/path-params/user.yaml @@ -0,0 +1,79 @@ + +paths: + Users: + parameters: + - $ref: 'global.yaml#/components/parameters/Version' + - $ref: 'global.yaml#/components/parameters/OrganizationId' + post: + summary: Creates a user + tags: + - pets + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/User' + responses: + '201': + description: Created + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/User' + '400': + $ref: 'global.yaml#/components/responses/BadRequest' + '403': + $ref: 'global.yaml#/components/responses/Forbidden' + UserId: + parameters: + - $ref: 'global.yaml#/components/parameters/Version' + - $ref: 'global.yaml#/components/parameters/OrganizationId' + - $ref: '#/components/parameters/UserId' + get: + summary: Gets a user + security: + - BearerAuth: [] + responses: + '200': + description: A bar + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/User' + '400': + $ref: 'global.yaml#/components/responses/BadRequest' + '403': + $ref: 'global.yaml#/components/responses/Forbidden' + '404': + $ref: 'global.yaml#/components/responses/NotFound' +components: + schemas: + User: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + parameters: + UserId: + in: path + name: id + required: true + schema: + type: string + format: uuid + description: User's ID