diff --git a/src/bundle/Resources/config/routing.yml b/src/bundle/Resources/config/routing.yml index ba95b55..2663e92 100644 --- a/src/bundle/Resources/config/routing.yml +++ b/src/bundle/Resources/config/routing.yml @@ -383,6 +383,18 @@ ezpublish_rest_setObjectStatesForContent: contentId: \d+ +# Languages + +ibexa.rest.languages.list: + path: /languages + methods: [GET] + controller: Ibexa\Rest\Server\Controller\Language::listLanguages + +ibexa.rest.languages.view: + path: /languages/{languageCode} + methods: [GET] + controller: Ibexa\Rest\Server\Controller\Language::loadLanguage + # Locations diff --git a/src/bundle/Resources/config/services.yml b/src/bundle/Resources/config/services.yml index 4eede10..fc814a1 100644 --- a/src/bundle/Resources/config/services.yml +++ b/src/bundle/Resources/config/services.yml @@ -177,6 +177,10 @@ services: - "@ezpublish.api.service.location" tags: [controller.service_arguments] + Ibexa\Rest\Server\Controller\Language: + autowire: true + tags: [ controller.service_arguments ] + ezpublish_rest.controller.location: class: "%ezpublish_rest.controller.location.class%" parent: ezpublish_rest.controller.base diff --git a/src/bundle/Resources/config/value_object_visitors.yml b/src/bundle/Resources/config/value_object_visitors.yml index 1273d15..4c76b2e 100644 --- a/src/bundle/Resources/config/value_object_visitors.yml +++ b/src/bundle/Resources/config/value_object_visitors.yml @@ -206,6 +206,11 @@ services: - { name: ezpublish_rest.output.value_object_visitor, type: Symfony\Component\HttpKernel\Exception\HttpException } # Language + Ibexa\Rest\Server\Output\ValueObjectVisitor\LanguageList: + parent: Ibexa\Contracts\Rest\Output\ValueObjectVisitor + tags: + - { name: ibexa.rest.output.value_object.visitor, type: Ibexa\Rest\Server\Values\LanguageList } + ezpublish_rest.output.value_object_visitor.Language: parent: ezpublish_rest.output.value_object_visitor.base class: EzSystems\EzPlatformRest\Server\Output\ValueObjectVisitor\Language diff --git a/src/lib/Server/Controller/Language.php b/src/lib/Server/Controller/Language.php new file mode 100644 index 0000000..9c005c8 --- /dev/null +++ b/src/lib/Server/Controller/Language.php @@ -0,0 +1,41 @@ +languageService = $languageService; + } + + public function listLanguages(): LanguageList + { + $languages = $this->languageService->loadLanguages(); + + if ($languages instanceof Traversable) { + $languages = iterator_to_array($languages); + } + + return new LanguageList($languages); + } + + public function loadLanguage(string $languageCode): ApiLanguage + { + return $this->languageService->loadLanguage($languageCode); + } +} diff --git a/src/lib/Server/Output/ValueObjectVisitor/Language.php b/src/lib/Server/Output/ValueObjectVisitor/Language.php index 8e05eee..69272aa 100644 --- a/src/lib/Server/Output/ValueObjectVisitor/Language.php +++ b/src/lib/Server/Output/ValueObjectVisitor/Language.php @@ -28,8 +28,15 @@ public function visit(Visitor $visitor, Generator $generator, $data): void private function visitLanguageAttributes(Visitor $visitor, Generator $generator, LanguageValue $language): void { - $generator->valueElement('languageId', $language->id); - $generator->valueElement('languageCode', $language->languageCode); - $generator->valueElement('name', $language->name); + $generator->attribute( + 'href', + $this->router->generate( + 'ibexa.rest.languages.view', + ['languageCode' => $language->getLanguageCode()], + ), + ); + $generator->valueElement('languageId', $language->getId()); + $generator->valueElement('languageCode', $language->getLanguageCode()); + $generator->valueElement('name', $language->getName()); } } diff --git a/src/lib/Server/Output/ValueObjectVisitor/LanguageList.php b/src/lib/Server/Output/ValueObjectVisitor/LanguageList.php new file mode 100644 index 0000000..c9c82d4 --- /dev/null +++ b/src/lib/Server/Output/ValueObjectVisitor/LanguageList.php @@ -0,0 +1,35 @@ +startObjectElement('LanguageList'); + $visitor->setHeader('Content-Type', $generator->getMediaType('LanguageList')); + + $generator->attribute('href', $this->router->generate('ibexa.rest.languages.list')); + + $generator->startList('Language'); + foreach ($data->languages as $language) { + $visitor->visitValueObject($language); + } + $generator->endList('Language'); + + $generator->endObjectElement('LanguageList'); + } +} diff --git a/src/lib/Server/Values/LanguageList.php b/src/lib/Server/Values/LanguageList.php new file mode 100644 index 0000000..b8ff3ac --- /dev/null +++ b/src/lib/Server/Values/LanguageList.php @@ -0,0 +1,25 @@ + $languages + */ + public function __construct(array $languages) + { + $this->languages = $languages; + } +} diff --git a/tests/bundle/Functional/JsonSchema/Language.json b/tests/bundle/Functional/JsonSchema/Language.json new file mode 100644 index 0000000..427ed75 --- /dev/null +++ b/tests/bundle/Functional/JsonSchema/Language.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "Language": { + "type": "object", + "properties": { + "_media-type": { + "type": "string" + }, + "_href": { + "type": "string" + }, + "languageId": { + "type": "integer" + }, + "languageCode": { + "type": "string", + "minLength": 1, + "pattern": "^[[:alnum:]_]+" + }, + "name": { + "type": "string" + } + }, + "required": [ + "_media-type", + "_href", + "languageId", + "languageCode", + "name" + ] + } + }, + "required": [ + "Language" + ] +} diff --git a/tests/bundle/Functional/JsonSchema/LanguageList.json b/tests/bundle/Functional/JsonSchema/LanguageList.json new file mode 100644 index 0000000..c26ce25 --- /dev/null +++ b/tests/bundle/Functional/JsonSchema/LanguageList.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "LanguageList": { + "type": "object", + "properties": { + "_media-type": { + "type": "string" + }, + "_href": { + "type": "string" + }, + "Language": { + "type":"array", + "items": { + "properties": { + "_media-type": { + "type": "string" + }, + "_href": { + "type": "string" + }, + "languageId": { + "type": "integer" + }, + "languageCode": { + "type": "string", + "minLength": 1, + "pattern": "^[[:alnum:]_]+" + }, + "name": { + "type": "string" + } + }, + "required": [ + "_media-type", + "_href", + "languageId", + "languageCode", + "name" + ] + } + } + }, + "required": [ + "_media-type", + "_href", + "Language" + ] + } + }, + "required": [ + "LanguageList" + ] +} diff --git a/tests/bundle/Functional/LanguageTest.php b/tests/bundle/Functional/LanguageTest.php new file mode 100644 index 0000000..f56f1e3 --- /dev/null +++ b/tests/bundle/Functional/LanguageTest.php @@ -0,0 +1,64 @@ +createHttpRequest('GET', '/api/ibexa/v2/languages', '', 'LanguageList+json'); + $response = $this->sendHttpRequest($request); + + self::assertHttpResponseCodeEquals($response, 200); + $content = $response->getBody()->getContents(); + self::assertJson($content); + + self::assertJsonResponseIsValid($content, 'LanguageList'); + self::assertResponseMatchesJsonSnapshot($content, self::SNAPSHOT_DIR . '/LanguageList.json'); + } + + public function testLanguageListXml(): void + { + $request = $this->createHttpRequest('GET', '/api/ibexa/v2/languages'); + $response = $this->sendHttpRequest($request); + + self::assertHttpResponseCodeEquals($response, 200); + $content = $response->getBody()->getContents(); + self::assertResponseMatchesXmlSnapshot($content, self::SNAPSHOT_DIR . '/LanguageList.xml'); + } + + public function testLanguageViewJson(): void + { + $request = $this->createHttpRequest('GET', '/api/ibexa/v2/languages/eng-GB', '', 'LanguageList+json'); + $response = $this->sendHttpRequest($request); + + self::assertHttpResponseCodeEquals($response, 200); + $content = $response->getBody()->getContents(); + self::assertJson($content); + + self::assertJsonResponseIsValid($content, 'Language'); + self::assertResponseMatchesJsonSnapshot($content, self::SNAPSHOT_DIR . '/Language.json'); + } + + public function testLanguageViewXml(): void + { + $request = $this->createHttpRequest('GET', '/api/ibexa/v2/languages/eng-GB'); + $response = $this->sendHttpRequest($request); + + self::assertHttpResponseCodeEquals($response, 200); + $content = $response->getBody()->getContents(); + self::assertResponseMatchesXmlSnapshot($content, self::SNAPSHOT_DIR . '/Language.xml'); + } +} diff --git a/tests/bundle/Functional/ResourceAssertionsTrait.php b/tests/bundle/Functional/ResourceAssertionsTrait.php new file mode 100644 index 0000000..8e55183 --- /dev/null +++ b/tests/bundle/Functional/ResourceAssertionsTrait.php @@ -0,0 +1,123 @@ + 'file://' . self::getSchemaFileLocation($resource), + ]; + + $validator->validate($decodedData, $schemaReference); + + self::assertTrue($validator->isValid(), self::convertErrorsToString($validator, $data)); + } + + private static function convertErrorsToString(Validator $validator, string $data): string + { + $errorMessage = ''; + foreach ($validator->getErrors() as $error) { + $errorMessage .= sprintf( + "property: [%s], constraint: %s, error: %s\n", + $error['property'], + $error['constraint'], + $error['message'] + ); + } + + $errorMessage .= "\n\nFor data:\n\n" . $data; + + return $errorMessage; + } + + private static function getSchemaFileLocation(string $resource): string + { + return __DIR__ . '/JsonSchema/' . $resource . '.json'; + } + + private static function checkSnapshotFileExistence(string $file, string $content): void + { + if (file_exists($file)) { + return; + } + + if ($_ENV['IBEXA_REST_GENERATE_SNAPSHOTS'] ?? false) { + file_put_contents($file, rtrim($content, "\n") . "\n"); + + return; + } + + self::fail(sprintf( + 'File %s does not exist. If it\'s a new REST route, add environment variable "%s" to phpunit.xml ' + . '(or environment) set to truthy value to enable snapshot generation.', + $file, + 'IBEXA_REST_GENERATE_SNAPSHOTS', + )); + } + + private static function getDefaultSnapshotFileLocation(?string $type): string + { + $classInfo = new \ReflectionClass(static::class); + $class = substr(static::class, strrpos(static::class, '\\') + 1); + $classFilename = $classInfo->getFileName(); + self::assertNotFalse($classFilename); + + return dirname($classFilename) . '/_snapshot/' . $class . '.' . ($type ?? 'log'); + } +} diff --git a/tests/bundle/Functional/_snapshot/Language.json b/tests/bundle/Functional/_snapshot/Language.json new file mode 100644 index 0000000..e82ba70 --- /dev/null +++ b/tests/bundle/Functional/_snapshot/Language.json @@ -0,0 +1,9 @@ +{ + "Language": { + "_media-type": "application\/vnd.ibexa.api.Language+json", + "_href": "\/api\/ibexa\/v2\/languages\/eng-GB", + "languageId": 2, + "languageCode": "eng-GB", + "name": "English (United Kingdom)" + } +} diff --git a/tests/bundle/Functional/_snapshot/Language.xml b/tests/bundle/Functional/_snapshot/Language.xml new file mode 100644 index 0000000..43519f1 --- /dev/null +++ b/tests/bundle/Functional/_snapshot/Language.xml @@ -0,0 +1,6 @@ + + + 2 + eng-GB + English (United Kingdom) + diff --git a/tests/bundle/Functional/_snapshot/LanguageList.json b/tests/bundle/Functional/_snapshot/LanguageList.json new file mode 100644 index 0000000..b9eb538 --- /dev/null +++ b/tests/bundle/Functional/_snapshot/LanguageList.json @@ -0,0 +1,21 @@ +{ + "LanguageList": { + "_media-type": "application\/vnd.ibexa.api.LanguageList+json", + "_href": "\/api\/ibexa\/v2\/languages", + "Language": [ + { + "_media-type": "application\/vnd.ibexa.api.Language+json", + "_href": "\/api\/ibexa\/v2\/languages\/eng-GB", + "languageId": 2, + "languageCode": "eng-GB", + "name": "English (United Kingdom)" + }, { + "_href": "/api/ibexa/v2/languages/pol-PL", + "_media-type": "application/vnd.ibexa.api.Language+json", + "languageCode": "pol-PL", + "languageId": 4, + "name": "Polish (polski)" + } + ] + } +} diff --git a/tests/bundle/Functional/_snapshot/LanguageList.xml b/tests/bundle/Functional/_snapshot/LanguageList.xml new file mode 100644 index 0000000..f61cfa7 --- /dev/null +++ b/tests/bundle/Functional/_snapshot/LanguageList.xml @@ -0,0 +1,13 @@ + + + + 2 + eng-GB + English (United Kingdom) + + + 4 + pol-PL + Polish (polski) + +