diff --git a/README.md b/README.md index e26ff18a15b..b21dc4f5eed 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,4 @@ -# API Platform Core +videni/rest +========== -API Platform Core is an easy to use and powerful system to create [hypermedia-driven REST APIs](https://en.wikipedia.org/wiki/HATEOAS). -It is a component of the [API Platform framework](https://api-platform.com) and it can be integrated -with [the Symfony framework](https://symfony.com) using the bundle distributed with the library. - -It natively supports popular open formats including [JSON for Linked Data (JSON-LD)](http://json-ld.org), [Hydra Core Vocabulary](http://www.hydra-cg.com), [Swagger (OpenAPI)](http://swagger.io), [HAL](http://stateless.co/hal_specification.html) and [HTTP Problem](https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-03). - -Build a working and fully-featured CRUD API in minutes. Leverage the awesome features of the tool to develop complex and -high performance API-first projects. Extend or override everything you want. - -[![Build Status](https://travis-ci.org/api-platform/core.svg?branch=master)](https://travis-ci.org/api-platform/core) -[![Build status](https://ci.appveyor.com/api/projects/status/grwuyprts3wdqx5l?svg=true)](https://ci.appveyor.com/project/dunglas/dunglasapibundle) -[![Coverage Status](https://coveralls.io/repos/github/api-platform/core/badge.svg)](https://coveralls.io/github/api-platform/core) -[![SensioLabsInsight](https://insight.sensiolabs.com/projects/92d78899-946c-4282-89a3-ac92344f9a93/mini.png)](https://insight.sensiolabs.com/projects/92d78899-946c-4282-89a3-ac92344f9a93) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/api-platform/core/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/api-platform/core/?branch=master) - -## Documentation - -The documentation of API Platform Core Library can be browsed [on the official website](https://api-platform.com/docs/core). +A simplified api-platform. currently only support HAL, because it is simple and flexible. JsonLd won't support unless the `IRI hell` is eliminated. diff --git a/composer.json b/composer.json index e98b421f549..760a5c4f1e3 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "api-platform/core", + "name": "videni/rest", "type": "library", "description": "The ultimate solution to create web APIs.", "keywords": ["REST", "API", "JSON", "JSON-LD", "Hydra", "Swagger", "HAL"], diff --git a/features/authorization/deny.feature b/features/authorization/deny.feature deleted file mode 100644 index 19658cdb4a2..00000000000 --- a/features/authorization/deny.feature +++ /dev/null @@ -1,72 +0,0 @@ -Feature: Authorization checking - In order to use the API - As a client software user - I need to be authorized to access a given resource. - - @createSchema - Scenario: An anonymous user retrieves a secured resource - When I add "Accept" header equal to "application/ld+json" - And I send a "GET" request to "/secured_dummies" - Then the response status code should be 401 - - Scenario: An authenticated user retrieve a secured resource - When I add "Accept" header equal to "application/ld+json" - And I add "Authorization" header equal to "Basic ZHVuZ2xhczprZXZpbg==" - And I send a "GET" request to "/secured_dummies" - Then the response status code should be 200 - And the response should be in JSON - - Scenario: A standard user cannot create a secured resource - When I add "Accept" header equal to "application/ld+json" - And I add "Content-Type" header equal to "application/ld+json" - And I add "Authorization" header equal to "Basic ZHVuZ2xhczprZXZpbg==" - And I send a "POST" request to "/secured_dummies" with body: - """ - { - "title": "Title", - "description": "Description", - "owner": "foo" - } - """ - Then the response status code should be 403 - - Scenario: An admin can create a secured resource - When I add "Accept" header equal to "application/ld+json" - And I add "Content-Type" header equal to "application/ld+json" - And I add "Authorization" header equal to "Basic YWRtaW46a2l0dGVu" - And I send a "POST" request to "/secured_dummies" with body: - """ - { - "title": "Title", - "description": "Description", - "owner": "someone" - } - """ - Then the response status code should be 201 - - Scenario: An admin can create another secured resource - When I add "Accept" header equal to "application/ld+json" - And I add "Content-Type" header equal to "application/ld+json" - And I add "Authorization" header equal to "Basic YWRtaW46a2l0dGVu" - And I send a "POST" request to "/secured_dummies" with body: - """ - { - "title": "Special Title", - "description": "Description", - "owner": "dunglas" - } - """ - Then the response status code should be 201 - - Scenario: An user cannot retrieve an item he doesn't own - When I add "Accept" header equal to "application/ld+json" - And I add "Authorization" header equal to "Basic ZHVuZ2xhczprZXZpbg==" - And I send a "GET" request to "/secured_dummies/1" - Then the response status code should be 403 - And the response should be in JSON - - Scenario: An user can retrieve an item he owns - When I add "Accept" header equal to "application/ld+json" - And I add "Authorization" header equal to "Basic ZHVuZ2xhczprZXZpbg==" - And I send a "GET" request to "/secured_dummies/2" - Then the response status code should be 200 diff --git a/features/bootstrap/CoverageContext.php b/features/bootstrap/CoverageContext.php deleted file mode 100644 index 90e8ea0098f..00000000000 --- a/features/bootstrap/CoverageContext.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -use Behat\Behat\Context\Context; -use Behat\Behat\Hook\Scope\BeforeScenarioScope; -use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeCoverage\Filter; -use SebastianBergmann\CodeCoverage\Report\PHP; - -/** - * Behat coverage. - * - * @author eliecharra - * @author Kévin Dunglas - * @copyright Adapted from https://gist.github.com/eliecharra/9c8b3ba57998b50e14a6 - */ -final class CoverageContext implements Context -{ - /** - * @var CodeCoverage - */ - private static $coverage; - - /** - * @BeforeSuite - */ - public static function setup() - { - $filter = new Filter(); - $filter->addDirectoryToWhitelist(__DIR__.'/../../src'); - self::$coverage = new CodeCoverage(null, $filter); - } - - /** - * @AfterSuite - */ - public static function tearDown() - { - $feature = getenv('FEATURE') ?: 'behat'; - (new PHP())->process(self::$coverage, __DIR__."/../../build/cov/coverage-$feature.cov"); - } - - /** - * @BeforeScenario - */ - public function startCoverage(BeforeScenarioScope $scope) - { - self::$coverage->start("{$scope->getFeature()->getTitle()}::{$scope->getScenario()->getTitle()}"); - } - - /** - * @AfterScenario - */ - public function stopCoverage() - { - self::$coverage->stop(); - } -} diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php deleted file mode 100644 index 2629e7f8f94..00000000000 --- a/features/bootstrap/FeatureContext.php +++ /dev/null @@ -1,1027 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Answer; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeItem; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeLabel; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositePrimitiveItem; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Container; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyAggregateOffer; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCarColor; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyDate; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyFriend; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyGroup; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyImmutableDate; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyOffer; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyProduct; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyProperty; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\EmbeddableDummy; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\EmbeddedDummy; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Foo; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FooDummy; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FourthLevel; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Greeting; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Node; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Person; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\PersonToPet; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Pet; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RamseyUuidDummy; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedToDummyFriend; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelationEmbedder; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\SecuredDummy; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThirdLevel; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\User; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\UuidIdentifierDummy; -use Behat\Behat\Context\Context; -use Behat\Behat\Context\SnippetAcceptingContext; -use Behat\Behat\Hook\Scope\AfterStepScope; -use Behatch\HttpCall\Request; -use Doctrine\Common\Persistence\ManagerRegistry; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Tools\SchemaTool; - -/** - * Defines application features from the specific context. - */ -final class FeatureContext implements Context, SnippetAcceptingContext -{ - /** - * @var EntityManagerInterface - */ - private $manager; - private $doctrine; - private $schemaTool; - private $classes; - private $request; - - /** - * Initializes context. - * - * Every scenario gets its own context instance. - * You can also pass arbitrary arguments to the - * context constructor through behat.yml. - */ - public function __construct(ManagerRegistry $doctrine, Request $request) - { - $this->doctrine = $doctrine; - $this->manager = $doctrine->getManager(); - $this->schemaTool = new SchemaTool($this->manager); - $this->classes = $this->manager->getMetadataFactory()->getAllMetadata(); - $this->request = $request; - } - - /** - * Sets the default Accept HTTP header to null (workaround to artificially remove it). - * - * @AfterStep - */ - public function removeAcceptHeaderAfterRequest(AfterStepScope $event) - { - if (preg_match('/^I send a "[A-Z]+" request to ".+"/', $event->getStep()->getText())) { - $this->request->setHttpHeader('Accept', null); - } - } - - /** - * Sets the default Accept HTTP header to null (workaround to artificially remove it). - * - * @BeforeScenario - */ - public function removeAcceptHeaderBeforeScenario() - { - $this->request->setHttpHeader('Accept', null); - } - - /** - * @BeforeScenario @createSchema - */ - public function createDatabase() - { - $this->schemaTool->dropSchema($this->classes); - $this->doctrine->getManager()->clear(); - $this->schemaTool->createSchema($this->classes); - } - - /** - * @Given there are :nb dummy objects - */ - public function thereAreDummyObjects(int $nb) - { - $descriptions = ['Smart dummy.', 'Not so smart dummy.']; - - for ($i = 1; $i <= $nb; ++$i) { - $dummy = new Dummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setAlias('Alias #'.($nb - $i)); - $dummy->setDummy('SomeDummyTest'.$i); - $dummy->setDescription($descriptions[($i - 1) % 2]); - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb foo objects with fake names - */ - public function thereAreFooObjectsWithFakeNames(int $nb) - { - $names = ['Hawsepipe', 'Sthenelus', 'Ephesian', 'Separativeness', 'Balbo']; - $bars = ['Lorem', 'Ipsum', 'Dolor', 'Sit', 'Amet']; - - for ($i = 0; $i < $nb; ++$i) { - $foo = new Foo(); - $foo->setName($names[$i]); - $foo->setBar($bars[$i]); - - $this->manager->persist($foo); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb fooDummy objects with fake names - */ - public function thereAreFooDummyObjectsWithFakeNames($nb) - { - $names = ['Hawsepipe', 'Ephesian', 'Sthenelus', 'Separativeness', 'Balbo']; - $dummies = ['Lorem', 'Ipsum', 'Dolor', 'Sit', 'Amet']; - - for ($i = 0; $i < $nb; ++$i) { - $dummy = new Dummy(); - $dummy->setName($dummies[$i]); - - $foo = new FooDummy(); - $foo->setName($names[$i]); - $foo->setDummy($dummy); - - $this->manager->persist($foo); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy group objects - */ - public function thereAreDummyGroupObjects(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $dummyGroup = new DummyGroup(); - - foreach (['foo', 'bar', 'baz', 'qux'] as $property) { - $dummyGroup->$property = ucfirst($property).' #'.$i; - } - - $this->manager->persist($dummyGroup); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy property objects - */ - public function thereAreDummyPropertyObjects(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $dummyProperty = new DummyProperty(); - $dummyGroup = new DummyGroup(); - - foreach (['foo', 'bar', 'baz'] as $property) { - $dummyProperty->$property = $dummyGroup->$property = ucfirst($property).' #'.$i; - } - - $dummyProperty->group = $dummyGroup; - - $this->manager->persist($dummyGroup); - $this->manager->persist($dummyProperty); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy property objects with a shared group - */ - public function thereAreDummyPropertyObjectsWithASharedGroup(int $nb) - { - $dummyGroup = new DummyGroup(); - foreach (['foo', 'bar', 'baz'] as $property) { - $dummyGroup->$property = ucfirst($property).' #shared'; - } - $this->manager->persist($dummyGroup); - - for ($i = 1; $i <= $nb; ++$i) { - $dummyProperty = new DummyProperty(); - - foreach (['foo', 'bar', 'baz'] as $property) { - $dummyProperty->$property = ucfirst($property).' #'.$i; - } - - $dummyProperty->group = $dummyGroup; - $this->manager->persist($dummyProperty); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy property objects with different number of related groups - */ - public function thereAreDummyPropertyObjectsWithADifferentNumberRelatedGroups(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $dummyGroup = new DummyGroup(); - $dummyProperty = new DummyProperty(); - - foreach (['foo', 'bar', 'baz'] as $property) { - $dummyProperty->$property = $dummyGroup->$property = ucfirst($property).' #'.$i; - } - - $this->manager->persist($dummyGroup); - $dummyGroups[$i] = $dummyGroup; - - for ($j = 1; $j <= $i; ++$j) { - $dummyProperty->groups[] = $dummyGroups[$j]; - } - - $this->manager->persist($dummyProperty); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy property objects with :nb2 groups - */ - public function thereAreDummyPropertyObjectsWithGroups(int $nb, int $nb2) - { - for ($i = 1; $i <= $nb; ++$i) { - $dummyProperty = new DummyProperty(); - $dummyGroup = new DummyGroup(); - - foreach (['foo', 'bar', 'baz'] as $property) { - $dummyProperty->$property = $dummyGroup->$property = ucfirst($property).' #'.$i; - } - - $dummyProperty->group = $dummyGroup; - - $this->manager->persist($dummyGroup); - for ($j = 1; $j <= $nb2; ++$j) { - $dummyGroup = new DummyGroup(); - - foreach (['foo', 'bar', 'baz'] as $property) { - $dummyGroup->$property = ucfirst($property).' #'.$i.$j; - } - - $dummyProperty->groups[] = $dummyGroup; - $this->manager->persist($dummyGroup); - } - - $this->manager->persist($dummyProperty); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb embedded dummy objects - */ - public function thereAreEmbeddedDummyObjects(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $dummy = new EmbeddedDummy(); - $dummy->setName('Dummy #'.$i); - - $embeddableDummy = new EmbeddableDummy(); - $embeddableDummy->setDummyName('Dummy #'.$i); - $dummy->setEmbeddedDummy($embeddableDummy); - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy objects with relatedDummy - */ - public function thereAreDummyObjectsWithRelatedDummy(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $relatedDummy = new RelatedDummy(); - $relatedDummy->setName('RelatedDummy #'.$i); - - $dummy = new Dummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setAlias('Alias #'.($nb - $i)); - $dummy->setRelatedDummy($relatedDummy); - - $this->manager->persist($relatedDummy); - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy objects with JSON and array data - */ - public function thereAreDummyObjectsWithJsonData(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $dummy = new Dummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setAlias('Alias #'.($nb - $i)); - $dummy->setJsonData(['foo' => ['bar', 'baz'], 'bar' => 5]); - $dummy->setArrayData(['foo', 'bar', 'baz']); - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy objects with relatedDummy and its thirdLevel - * @Given there is :nb dummy object with relatedDummy and its thirdLevel - */ - public function thereAreDummyObjectsWithRelatedDummyAndItsThirdLevel(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $thirdLevel = new ThirdLevel(); - - $relatedDummy = new RelatedDummy(); - $relatedDummy->setName('RelatedDummy #'.$i); - $relatedDummy->setThirdLevel($thirdLevel); - - $dummy = new Dummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setAlias('Alias #'.($nb - $i)); - $dummy->setRelatedDummy($relatedDummy); - - $this->manager->persist($thirdLevel); - $this->manager->persist($relatedDummy); - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy objects with embeddedDummy - */ - public function thereAreDummyObjectsWithEmbeddedDummy(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $embeddableDummy = new EmbeddableDummy(); - $embeddableDummy->setDummyName('EmbeddedDummy #'.$i); - - $dummy = new EmbeddedDummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setEmbeddedDummy($embeddableDummy); - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy objects having each :nbrelated relatedDummies - */ - public function thereAreDummyObjectsWithRelatedDummies(int $nb, int $nbrelated) - { - for ($i = 1; $i <= $nb; ++$i) { - $dummy = new Dummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setAlias('Alias #'.($nb - $i)); - - for ($j = 1; $j <= $nbrelated; ++$j) { - $relatedDummy = new RelatedDummy(); - $relatedDummy->setName('RelatedDummy'.$j.$i); - - $this->manager->persist($relatedDummy); - - $dummy->addRelatedDummy($relatedDummy); - } - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy objects with dummyDate - * @Given there is :nb dummy object with dummyDate - */ - public function thereAreDummyObjectsWithDummyDate(int $nb) - { - $descriptions = ['Smart dummy.', 'Not so smart dummy.']; - - for ($i = 1; $i <= $nb; ++$i) { - $date = new \DateTime(sprintf('2015-04-%d', $i), new \DateTimeZone('UTC')); - - $dummy = new Dummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setAlias('Alias #'.($nb - $i)); - $dummy->setDescription($descriptions[($i - 1) % 2]); - - // Last Dummy has a null date - if ($nb !== $i) { - $dummy->setDummyDate($date); - } - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy objects with dummyDate and dummyBoolean :bool - */ - public function thereAreDummyObjectsWithDummyDateAndDummyBoolean(int $nb, string $bool) - { - $descriptions = ['Smart dummy.', 'Not so smart dummy.']; - - if (\in_array($bool, ['true', '1', 1], true)) { - $bool = true; - } elseif (\in_array($bool, ['false', '0', 0], true)) { - $bool = false; - } else { - $expected = ['true', 'false', '1', '0']; - throw new \InvalidArgumentException(sprintf('Invalid boolean value for "%s" property, expected one of ( "%s" )', $bool, implode('" | "', $expected))); - } - - for ($i = 1; $i <= $nb; ++$i) { - $date = new \DateTime(sprintf('2015-04-%d', $i), new \DateTimeZone('UTC')); - - $dummy = new Dummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setAlias('Alias #'.($nb - $i)); - $dummy->setDescription($descriptions[($i - 1) % 2]); - $dummy->setDummyBoolean($bool); - - // Last Dummy has a null date - if ($nb !== $i) { - $dummy->setDummyDate($date); - } - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy objects with dummyDate and relatedDummy - */ - public function thereAreDummyObjectsWithDummyDateAndRelatedDummy(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $date = new \DateTime(sprintf('2015-04-%d', $i), new \DateTimeZone('UTC')); - - $relatedDummy = new RelatedDummy(); - $relatedDummy->setName('RelatedDummy #'.$i); - $relatedDummy->setDummyDate($date); - - $dummy = new Dummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setAlias('Alias #'.($nb - $i)); - $dummy->setRelatedDummy($relatedDummy); - // Last Dummy has a null date - if ($nb !== $i) { - $dummy->setDummyDate($date); - } - - $this->manager->persist($relatedDummy); - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb embedded dummy objects with dummyDate and embeddedDummy - */ - public function thereAreDummyObjectsWithDummyDateAndEmbeddedDummy(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $date = new \DateTime(sprintf('2015-04-%d', $i), new \DateTimeZone('UTC')); - - $embeddableDummy = new EmbeddableDummy(); - $embeddableDummy->setDummyName('Embeddable #'.$i); - $embeddableDummy->setDummyDate($date); - - $dummy = new EmbeddedDummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setEmbeddedDummy($embeddableDummy); - // Last Dummy has a null date - if ($nb !== $i) { - $dummy->setDummyDate($date); - } - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy objects with dummyPrice - */ - public function thereAreDummyObjectsWithDummyPrice(int $nb) - { - $descriptions = ['Smart dummy.', 'Not so smart dummy.']; - $prices = ['9.99', '12.99', '15.99', '19.99']; - - for ($i = 1; $i <= $nb; ++$i) { - $dummy = new Dummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setAlias('Alias #'.($nb - $i)); - $dummy->setDescription($descriptions[($i - 1) % 2]); - $dummy->setDummyPrice($prices[($i - 1) % 4]); - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummy objects with dummyBoolean :bool - * @Given there is :nb dummy object with dummyBoolean :bool - */ - public function thereAreDummyObjectsWithDummyBoolean(int $nb, string $bool) - { - if (\in_array($bool, ['true', '1', 1], true)) { - $bool = true; - } elseif (\in_array($bool, ['false', '0', 0], true)) { - $bool = false; - } else { - $expected = ['true', 'false', '1', '0']; - throw new \InvalidArgumentException(sprintf('Invalid boolean value for "%s" property, expected one of ( "%s" )', $bool, implode('" | "', $expected))); - } - $descriptions = ['Smart dummy.', 'Not so smart dummy.']; - - for ($i = 1; $i <= $nb; ++$i) { - $dummy = new Dummy(); - $dummy->setName('Dummy #'.$i); - $dummy->setAlias('Alias #'.($nb - $i)); - $dummy->setDescription($descriptions[($i - 1) % 2]); - $dummy->setDummyBoolean($bool); - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb embedded dummy objects with embeddedDummy.dummyBoolean :bool - */ - public function thereAreDummyObjectsWithEmbeddedDummyBoolean(int $nb, string $bool) - { - if (\in_array($bool, ['true', '1', 1], true)) { - $bool = true; - } elseif (\in_array($bool, ['false', '0', 0], true)) { - $bool = false; - } else { - $expected = ['true', 'false', '1', '0']; - throw new \InvalidArgumentException(sprintf('Invalid boolean value for "%s" property, expected one of ( "%s" )', $bool, implode('" | "', $expected))); - } - - for ($i = 1; $i <= $nb; ++$i) { - $dummy = new EmbeddedDummy(); - $dummy->setName('Embedded Dummy #'.$i); - $embeddableDummy = new EmbeddableDummy(); - $embeddableDummy->setDummyName('Embedded Dummy #'.$i); - $embeddableDummy->setDummyBoolean($bool); - $dummy->setEmbeddedDummy($embeddableDummy); - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb embedded dummy objects with relatedDummy.embeddedDummy.dummyBoolean :bool - */ - public function thereAreDummyObjectsWithRelationEmbeddedDummyBoolean(int $nb, string $bool) - { - if (\in_array($bool, ['true', '1', 1], true)) { - $bool = true; - } elseif (\in_array($bool, ['false', '0', 0], true)) { - $bool = false; - } else { - $expected = ['true', 'false', '1', '0']; - throw new \InvalidArgumentException(sprintf('Invalid boolean value for "%s" property, expected one of ( "%s" )', $bool, implode('" | "', $expected))); - } - - for ($i = 1; $i <= $nb; ++$i) { - $dummy = new EmbeddedDummy(); - $dummy->setName('Embedded Dummy #'.$i); - $embeddableDummy = new EmbeddableDummy(); - $embeddableDummy->setDummyName('Embedded Dummy #'.$i); - $embeddableDummy->setDummyBoolean($bool); - - $relationDummy = new RelatedDummy(); - $relationDummy->setEmbeddedDummy($embeddableDummy); - - $dummy->setRelatedDummy($relationDummy); - - $this->manager->persist($relationDummy); - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb SecuredDummy objects - */ - public function thereAreSecuredDummyObjects(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $securedDummy = new SecuredDummy(); - $securedDummy->setTitle("#$i"); - $securedDummy->setDescription("Hello #$i"); - $securedDummy->setOwner('notexist'); - - $this->manager->persist($securedDummy); - } - - $this->manager->flush(); - } - - /** - * @Given there is a RelationEmbedder object - */ - public function thereIsARelationEmbedderObject() - { - $relationEmbedder = new RelationEmbedder(); - - $this->manager->persist($relationEmbedder); - $this->manager->flush(); - } - - /** - * @Given there is a Dummy Object mapped by UUID - */ - public function thereIsADummyObjectMappedByUUID() - { - $dummy = new UuidIdentifierDummy(); - $dummy->setName('My Dummy'); - $dummy->setUuid('41B29566-144B-11E6-A148-3E1D05DEFE78'); - - $this->manager->persist($dummy); - $this->manager->flush(); - } - - /** - * @Given there are Composite identifier objects - */ - public function thereIsACompositeIdentifierObject() - { - $item = new CompositeItem(); - $item->setField1('foobar'); - $this->manager->persist($item); - $this->manager->flush(); - - for ($i = 0; $i < 4; ++$i) { - $label = new CompositeLabel(); - $label->setValue('foo-'.$i); - - $rel = new CompositeRelation(); - $rel->setCompositeLabel($label); - $rel->setCompositeItem($item); - $rel->setValue('somefoobardummy'); - - $this->manager->persist($label); - // since doctrine 2.6 we need existing identifiers on relations - $this->manager->flush(); - $this->manager->persist($rel); - } - - $this->manager->flush(); - $this->manager->clear(); - } - - /** - * @Given there are composite primitive identifiers objects - */ - public function thereAreCompositePrimitiveIdentifiersObjects() - { - $foo = new CompositePrimitiveItem('Foo', 2016); - $foo->setDescription('This is foo.'); - $this->manager->persist($foo); - - $bar = new CompositePrimitiveItem('Bar', 2017); - $bar->setDescription('This is bar.'); - $this->manager->persist($bar); - - $this->manager->flush(); - $this->manager->clear(); - } - - /** - * @Given there is a FileConfigDummy object - */ - public function thereIsAFileConfigDummyObject() - { - $fileConfigDummy = new FileConfigDummy(); - $fileConfigDummy->setName('ConfigDummy'); - $fileConfigDummy->setFoo('Foo'); - - $this->manager->persist($fileConfigDummy); - $this->manager->flush(); - } - - /** - * @Given there is a DummyCar entity with related colors - */ - public function thereIsAFooEntityWithRelatedBars() - { - $foo = new DummyCar(); - $foo->setName('mustli'); - $foo->setCanSell(true); - $foo->setAvailableAt(new \DateTime()); - $this->manager->persist($foo); - - $bar1 = new DummyCarColor(); - $bar1->setProp('red'); - $bar1->setCar($foo); - $this->manager->persist($bar1); - - $bar2 = new DummyCarColor(); - $bar2->setProp('blue'); - $bar2->setCar($foo); - $this->manager->persist($bar2); - - $foo->setColors([$bar1, $bar2]); - $this->manager->persist($foo); - - $this->manager->flush(); - } - - /** - * @Given there is a RelatedDummy with :nb friends - */ - public function thereIsARelatedDummyWithFriends(int $nb) - { - $relatedDummy = new RelatedDummy(); - $relatedDummy->setName('RelatedDummy with friends'); - $this->manager->persist($relatedDummy); - $this->manager->flush(); - - for ($i = 1; $i <= $nb; ++$i) { - $friend = new DummyFriend(); - $friend->setName('Friend-'.$i); - - $this->manager->persist($friend); - // since doctrine 2.6 we need existing identifiers on relations - // See https://github.com/doctrine/doctrine2/pull/6701 - $this->manager->flush(); - - $relation = new RelatedToDummyFriend(); - $relation->setName('Relation-'.$i); - $relation->setDummyFriend($friend); - $relation->setRelatedDummy($relatedDummy); - - $relatedDummy->addRelatedToDummyFriend($relation); - - $this->manager->persist($relation); - } - - $relatedDummy2 = new RelatedDummy(); - $relatedDummy2->setName('RelatedDummy without friends'); - $this->manager->persist($relatedDummy2); - $this->manager->flush(); - $this->manager->clear(); - } - - /** - * @Given there is an answer :answer to the question :question - */ - public function thereIsAnAnswerToTheQuestion(string $a, string $q) - { - $answer = new Answer(); - $answer->setContent($a); - - $question = new Question(); - $question->setContent($q); - $question->setAnswer($answer); - $answer->addRelatedQuestion($question); - - $this->manager->persist($answer); - $this->manager->persist($question); - - $this->manager->flush(); - $this->manager->clear(); - } - - /** - * @Given there are :nb nodes in a container :uuid - */ - public function thereAreNodesInAContainer(int $nb, string $uuid) - { - $container = new Container(); - $container->setId($uuid); - $this->manager->persist($container); - - for ($i = 0; $i < $nb; ++$i) { - $node = new Node(); - $node->setContainer($container); - $node->setSerial($i); - $this->manager->persist($node); - } - - $this->manager->flush(); - } - - /** - * @Then the password :password for user :user should be hashed - */ - public function thePasswordForUserShouldBeHashed(string $password, string $user) - { - $user = $this->doctrine->getRepository(User::class)->find($user); - - if (!password_verify($password, $user->getPassword())) { - throw new \Exception('User password mismatch'); - } - } - - /** - * @Given I have a product with offers - */ - public function createProductWithOffers() - { - $offer = new DummyOffer(); - $offer->setValue(2); - $aggregate = new DummyAggregateOffer(); - $aggregate->setValue(1); - $aggregate->addOffer($offer); - - $product = new DummyProduct(); - $product->setName('Dummy product'); - $product->addOffer($aggregate); - - $relatedProduct = new DummyProduct(); - $relatedProduct->setName('Dummy related product'); - $relatedProduct->setParent($product); - - $product->addRelatedProduct($relatedProduct); - - $this->manager->persist($relatedProduct); - $this->manager->persist($product); - $this->manager->flush(); - } - - /** - * @Given there are people having pets - */ - public function createPeopleWithPets() - { - $personToPet = new PersonToPet(); - - $person = new Person(); - $person->name = 'foo'; - - $pet = new Pet(); - $pet->name = 'bar'; - - $personToPet->person = $person; - $personToPet->pet = $pet; - - $this->manager->persist($person); - $this->manager->persist($pet); - // since doctrine 2.6 we need existing identifiers on relations - $this->manager->flush(); - $this->manager->persist($personToPet); - - $person->pets->add($personToPet); - $this->manager->persist($person); - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummydate objects with dummyDate - * @Given there is :nb dummydate object with dummyDate - */ - public function thereAreDummyDateObjectsWithDummyDate(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $date = new \DateTime(sprintf('2015-04-%d', $i), new \DateTimeZone('UTC')); - - $dummy = new DummyDate(); - $dummy->dummyDate = $date; - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there are :nb dummyimmutabledate objects with dummyDate - */ - public function thereAreDummyImmutableDateObjectsWithDummyDate(int $nb) - { - for ($i = 1; $i <= $nb; ++$i) { - $date = new \DateTimeImmutable(sprintf('2015-04-%d', $i), new \DateTimeZone('UTC')); - $dummy = new DummyImmutableDate(); - $dummy->dummyDate = $date; - - $this->manager->persist($dummy); - } - - $this->manager->flush(); - } - - /** - * @Given there is a ramsey identified resource with uuid :uuid - */ - public function thereIsARamseyIdentifiedResource(string $uuid) - { - $dummy = new RamseyUuidDummy(); - $dummy->setId($uuid); - - $this->manager->persist($dummy); - $this->manager->flush(); - } - - /** - * @Given there is a dummy object with a fourth level relation - */ - public function thereIsADummyObjectWithAFourthLevelRelation() - { - $fourthLevel = new FourthLevel(); - $fourthLevel->setLevel(4); - $this->manager->persist($fourthLevel); - - $thirdLevel = new ThirdLevel(); - $thirdLevel->setLevel(3); - $thirdLevel->setFourthLevel($fourthLevel); - $this->manager->persist($thirdLevel); - - $namedRelatedDummy = new RelatedDummy(); - $namedRelatedDummy->setName('Hello'); - $namedRelatedDummy->setThirdLevel($thirdLevel); - $this->manager->persist($namedRelatedDummy); - - $relatedDummy = new RelatedDummy(); - $relatedDummy = new RelatedDummy(); - $relatedDummy->setThirdLevel($thirdLevel); - $this->manager->persist($relatedDummy); - - $dummy = new Dummy(); - $dummy->setName('Dummy with relations'); - $dummy->setRelatedDummy($namedRelatedDummy); - $dummy->addRelatedDummy($namedRelatedDummy); - $dummy->addRelatedDummy($relatedDummy); - $this->manager->persist($dummy); - - $this->manager->flush(); - } - - /** - * @Given there is a person named :name greeting with a :message message - */ - public function thereIsAPersonWithAGreeting(string $name, string $message) - { - $person = new Person(); - $person->name = $name; - - $greeting = new Greeting(); - $greeting->message = $message; - $greeting->sender = $person; - - $this->manager->persist($person); - $this->manager->persist($greeting); - - $this->manager->flush(); - $this->manager->clear(); - } -} diff --git a/features/bootstrap/GraphqlContext.php b/features/bootstrap/GraphqlContext.php deleted file mode 100644 index 56462b69b24..00000000000 --- a/features/bootstrap/GraphqlContext.php +++ /dev/null @@ -1,128 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -use Behat\Behat\Context\Context; -use Behat\Behat\Context\Environment\InitializedContextEnvironment; -use Behat\Behat\Hook\Scope\BeforeScenarioScope; -use Behat\Gherkin\Node\PyStringNode; -use Behatch\Context\RestContext; -use Behatch\HttpCall\Request; -use GraphQL\Type\Introspection; -use PHPUnit\Framework\ExpectationFailedException; - -/** - * Context for GraphQL. - * - * @author Alan Poulain - */ -final class GraphqlContext implements Context -{ - /** - * @var RestContext - */ - private $restContext; - - /** - * @var array - */ - private $graphqlRequest; - - /** - * @var int - */ - private $graphqlLine; - - private $request; - - public function __construct(Request $request) - { - $this->request = $request; - } - - /** - * Gives access to the Behatch context. - * - * @BeforeScenario - */ - public function gatherContexts(BeforeScenarioScope $scope) - { - /** @var InitializedContextEnvironment $environment */ - $environment = $scope->getEnvironment(); - $this->restContext = $environment->getContext(RestContext::class); - } - - /** - * @When I have the following GraphQL request: - */ - public function IHaveTheFollowingGraphqlRequest(PyStringNode $request) - { - $this->graphqlRequest = ['query' => $request->getRaw()]; - $this->graphqlLine = $request->getLine(); - } - - /** - * @When I send the following GraphQL request: - */ - public function ISendTheFollowingGraphqlRequest(PyStringNode $request) - { - $this->IHaveTheFollowingGraphqlRequest($request); - $this->sendGraphqlRequest(); - } - - /** - * @When I send the GraphQL request with variables: - */ - public function ISendTheGraphqlRequestWithVariables(PyStringNode $variables) - { - $this->graphqlRequest['variables'] = $variables->getRaw(); - $this->sendGraphqlRequest(); - } - - /** - * @When I send the GraphQL request with operation :operation - */ - public function ISendTheGraphqlRequestWithOperation(string $operation) - { - $this->graphqlRequest['operation'] = $operation; - $this->sendGraphqlRequest(); - } - - /** - * @When I send the query to introspect the schema - */ - public function ISendTheQueryToIntrospectTheSchema() - { - $this->graphqlRequest = ['query' => Introspection::getIntrospectionQuery()]; - $this->sendGraphqlRequest(); - } - - /** - * @Then the GraphQL field :fieldName is deprecated for the reason :reason - */ - public function theGraphQLFieldIsDeprecatedForTheReason(string $fieldName, string $reason) - { - foreach (json_decode($this->request->getContent(), true)['data']['__type']['fields'] as $field) { - if ($fieldName === $field['name'] && $field['isDeprecated'] && $reason === $field['deprecationReason']) { - return; - } - } - - throw new ExpectationFailedException(sprintf('The field "%s" is not deprecated.', $fieldName)); - } - - private function sendGraphqlRequest() - { - $this->request->setHttpHeader('Accept', null); - $this->restContext->iSendARequestTo('GET', '/graphql?'.http_build_query($this->graphqlRequest)); - } -} diff --git a/features/bootstrap/HttpCacheContext.php b/features/bootstrap/HttpCacheContext.php deleted file mode 100644 index b4d4452b552..00000000000 --- a/features/bootstrap/HttpCacheContext.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -use Behat\Symfony2Extension\Context\KernelAwareContext; -use PHPUnit\Framework\ExpectationFailedException; -use Symfony\Component\HttpKernel\KernelInterface; - -/** - * @author Kévin Dunglas - */ -final class HttpCacheContext implements KernelAwareContext -{ - /** - * @var KernelInterface - */ - private $kernel; - - public function setKernel(KernelInterface $kernel) - { - $this->kernel = $kernel; - } - - /** - * @Then :iris IRIs should be purged - */ - public function irisShouldBePurged(string $iris) - { - $purger = $this->kernel->getContainer()->get('test.api_platform.http_cache.purger'); - - $purgedIris = implode(',', $purger->getIris()); - $purger->clear(); - - if ($iris !== $purgedIris) { - throw new ExpectationFailedException(sprintf('IRIs "%s" does not match expected "%s".', $purgedIris, $iris)); - } - } -} diff --git a/features/bootstrap/HydraContext.php b/features/bootstrap/HydraContext.php deleted file mode 100644 index b54c9a38204..00000000000 --- a/features/bootstrap/HydraContext.php +++ /dev/null @@ -1,301 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -use Behat\Behat\Context\Context; -use Behat\Behat\Context\Environment\InitializedContextEnvironment; -use Behat\Behat\Hook\Scope\BeforeScenarioScope; -use Behatch\Context\RestContext; -use PHPUnit\Framework\Assert; -use PHPUnit\Framework\ExpectationFailedException; -use Symfony\Component\PropertyAccess\PropertyAccess; - -final class HydraContext implements Context -{ - /** - * @var RestContext - */ - private $restContext; - private $propertyAccessor; - - public function __construct() - { - $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); - } - - /** - * Gives access to the Behatch context. - * - * @BeforeScenario - */ - public function gatherContexts(BeforeScenarioScope $scope) - { - /** @var InitializedContextEnvironment $environment */ - $environment = $scope->getEnvironment(); - $this->restContext = $environment->getContext(RestContext::class); - } - - /** - * @Then the Hydra class :class exists - */ - public function assertTheHydraClassExist(string $className) - { - try { - $this->getClassInfo($className); - } catch (\InvalidArgumentException $e) { - throw new ExpectationFailedException(sprintf('The class "%s" doesn\'t exist.', $className), null, $e); - } - } - - /** - * @Then the Hydra class :class doesn't exist - */ - public function assertTheHydraClassNotExist(string $className) - { - try { - $this->getClassInfo($className); - } catch (\InvalidArgumentException $exception) { - return; - } - - throw new ExpectationFailedException(sprintf('The class "%s" exists.', $className)); - } - - /** - * @Then the boolean value of the node :node of the Hydra class :class is true - */ - public function assertBooleanNodeValueIs(string $nodeName, string $className) - { - Assert::assertTrue($this->propertyAccessor->getValue($this->getClassInfo($className), $nodeName)); - } - - /** - * @Then the value of the node :node of the Hydra class :class is :value - */ - public function assertNodeValueIs(string $nodeName, string $className, string $value) - { - Assert::assertEquals( - $this->propertyAccessor->getValue($this->getClassInfo($className), $nodeName), - $value - ); - } - - /** - * @Then the boolean value of the node :node of the property :prop of the Hydra class :class is true - */ - public function assertPropertyNodeValueIsTrue(string $nodeName, string $propertyName, string $className) - { - Assert::assertTrue($this->propertyAccessor->getValue($this->getPropertyInfo($propertyName, $className), $nodeName)); - } - - /** - * @Then the value of the node :node of the property :prop of the Hydra class :class is :value - */ - public function assertPropertyNodeValueIs(string $nodeName, string $propertyName, string $className, string $value) - { - Assert::assertEquals( - $this->propertyAccessor->getValue($this->getPropertyInfo($propertyName, $className), $nodeName), - $value - ); - } - - /** - * @Then the boolean value of the node :node of the operation :operation of the Hydra class :class is true - */ - public function assertOperationNodeBooleanValueIs(string $nodeName, string $operationMethod, string $className) - { - Assert::assertTrue($this->propertyAccessor->getValue($this->getOperation($operationMethod, $className), $nodeName)); - } - - /** - * @Then the value of the node :node of the operation :operation of the Hydra class :class is :value - */ - public function assertOperationNodeValueIs(string $nodeName, string $operationMethod, string $className, string $value) - { - Assert::assertEquals( - $this->propertyAccessor->getValue($this->getOperation($operationMethod, $className), $nodeName), - $value - ); - } - - /** - * @Then the value of the node :node of the operation :operation of the Hydra class :class contains :value - */ - public function assertOperationNodeValueContains(string $nodeName, string $operationMethod, string $className, string $value) - { - $property = $this->getOperation($operationMethod, $className); - - Assert::assertContains($value, $this->propertyAccessor->getValue($property, $nodeName)); - } - - /** - * @Then :nb operations are available for Hydra class :class - */ - public function assertNbOperationsExist(int $nb, string $className) - { - Assert::assertEquals($nb, \count($this->getOperations($className))); - } - - /** - * @Then :nb properties are available for Hydra class :class - */ - public function assertNbPropertiesExist(int $nb, string $className) - { - Assert::assertEquals($nb, \count($this->getProperties($className))); - } - - /** - * @Then :prop property doesn't exist for the Hydra class :class - */ - public function assertPropertyNotExist(string $propertyName, string $className) - { - try { - $this->getPropertyInfo($propertyName, $className); - } catch (\InvalidArgumentException $exception) { - return; - } - - throw new ExpectationFailedException(sprintf('Property "%s" of class "%s" exists.', $propertyName, $className)); - } - - /** - * @Then :prop property is readable for Hydra class :class - */ - public function assertPropertyIsReadable(string $propertyName, string $className) - { - if (!$this->getPropertyInfo($propertyName, $className)->{'hydra:readable'}) { - throw new ExpectationFailedException(sprintf('Property "%s" of class "%s" is not readable', $propertyName, $className)); - } - } - - /** - * @Then :prop property is not readable for Hydra class :class - */ - public function assertPropertyIsNotReadable(string $propertyName, string $className) - { - if ($this->getPropertyInfo($propertyName, $className)->{'hydra:readable'}) { - throw new ExpectationFailedException(sprintf('Property "%s" of class "%s" is readable', $propertyName, $className)); - } - } - - /** - * @Then :prop property is writable for Hydra class :class - */ - public function assertPropertyIsWritable(string $propertyName, string $className) - { - if (!$this->getPropertyInfo($propertyName, $className)->{'hydra:writable'}) { - throw new ExpectationFailedException(sprintf('Property "%s" of class "%s" is not writable', $propertyName, $className)); - } - } - - /** - * @Then :prop property is required for Hydra class :class - */ - public function assertPropertyIsRequired(string $propertyName, string $className) - { - if (!$this->getPropertyInfo($propertyName, $className)->{'hydra:required'}) { - throw new ExpectationFailedException(sprintf('Property "%s" of class "%s" is not required', $propertyName, $className)); - } - } - - /** - * @Then :prop property is not required for Hydra class :class - */ - public function assertPropertyIsNotRequired(string $propertyName, string $className) - { - if ($this->getPropertyInfo($propertyName, $className)->{'hydra:required'}) { - throw new ExpectationFailedException(sprintf('Property "%s" of class "%s" is required', $propertyName, $className)); - } - } - - /** - * Gets information about a property. - * - * @throws \InvalidArgumentException - */ - private function getPropertyInfo(string $propertyName, string $className): \stdClass - { - foreach ($this->getProperties($className) as $property) { - if ($property->{'hydra:title'} === $propertyName) { - return $property; - } - } - - throw new \InvalidArgumentException(sprintf('Property "%s" of class "%s" does\'nt exist', $propertyName, $className)); - } - - /** - * Gets an operation by its method name. - * - * @throws \InvalidArgumentException - */ - private function getOperation(string $method, string $className): \stdClass - { - foreach ($this->getOperations($className) as $operation) { - if ($operation->{'hydra:method'} === $method) { - return $operation; - } - } - - throw new \InvalidArgumentException(sprintf('Operation "%s" of class "%s" doesn\'t exist.', $method, $className)); - } - - /** - * Gets all operations of a given class. - */ - private function getOperations(string $className): array - { - return $this->getClassInfo($className)->{'hydra:supportedOperation'} ?? []; - } - - /** - * Gets all properties of a given class. - */ - private function getProperties(string $className): array - { - return $this->getClassInfo($className)->{'hydra:supportedProperty'} ?? []; - } - - /** - * Gets information about a class. - * - * @throws \InvalidArgumentException - */ - private function getClassInfo(string $className): \stdClass - { - $json = $this->getLastJsonResponse(); - - if (isset($json->{'hydra:supportedClass'})) { - foreach ($json->{'hydra:supportedClass'} as $classData) { - if ($classData->{'hydra:title'} === $className) { - return $classData; - } - } - } - - throw new \InvalidArgumentException(sprintf('Class %s cannot be found in the vocabulary', $className)); - } - - /** - * Gets the last JSON response. - * - * @throws \RuntimeException - */ - private function getLastJsonResponse(): \stdClass - { - if (null === $decoded = json_decode($this->restContext->getMink()->getSession()->getDriver()->getContent())) { - throw new \RuntimeException('JSON response seems to be invalid'); - } - - return $decoded; - } -} diff --git a/features/bootstrap/JsonApiContext.php b/features/bootstrap/JsonApiContext.php deleted file mode 100644 index 829bee79841..00000000000 --- a/features/bootstrap/JsonApiContext.php +++ /dev/null @@ -1,163 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CircularReference; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyFriend; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use Behat\Behat\Context\Context; -use Behat\Behat\Context\Environment\InitializedContextEnvironment; -use Behat\Behat\Hook\Scope\BeforeScenarioScope; -use Behatch\Context\RestContext; -use Behatch\Json\Json; -use Behatch\Json\JsonInspector; -use Doctrine\Common\Persistence\ManagerRegistry; -use JsonSchema\Validator; -use PHPUnit\Framework\ExpectationFailedException; - -final class JsonApiContext implements Context -{ - /** - * @var RestContext - */ - private $restContext; - private $validator; - private $inspector; - private $jsonApiSchemaFile; - private $manager; - - public function __construct(ManagerRegistry $doctrine, string $jsonApiSchemaFile) - { - if (!is_file($jsonApiSchemaFile)) { - throw new \InvalidArgumentException('The JSON API schema doesn\'t exist.'); - } - - $this->validator = new Validator(); - $this->inspector = new JsonInspector('javascript'); - $this->jsonApiSchemaFile = $jsonApiSchemaFile; - $this->manager = $doctrine->getManager(); - } - - /** - * Gives access to the Behatch context. - * - * @BeforeScenario - */ - public function gatherContexts(BeforeScenarioScope $scope) - { - /** @var InitializedContextEnvironment $environment */ - $environment = $scope->getEnvironment(); - $this->restContext = $environment->getContext(RestContext::class); - } - - /** - * @Then the JSON should be valid according to the JSON API schema - */ - public function theJsonShouldBeValidAccordingToTheJsonApiSchema() - { - $json = $this->getJson()->getContent(); - $this->validator->validate($json, (object) ['$ref' => 'file://'.__DIR__.'/../../'.$this->jsonApiSchemaFile]); - - if (!$this->validator->isValid()) { - throw new ExpectationFailedException(sprintf('The JSON is not valid according to the JSON API schema.')); - } - } - - /** - * @Then the JSON node :node should be an empty array - */ - public function theJsonNodeShouldBeAnEmptyArray($node) - { - $actual = $this->getValueOfNode($node); - if (null !== $actual && [] !== $actual) { - throw new ExpectationFailedException(sprintf('The node value is `%s`', json_encode($actual))); - } - } - - /** - * @Then the JSON node :node should be a number - */ - public function theJsonNodeShouldBeANumber($node) - { - if (!is_numeric($actual = $this->getValueOfNode($node))) { - throw new ExpectationFailedException(sprintf('The node value is `%s`', json_encode($actual))); - } - } - - /** - * @Then the JSON node :node should not be an empty string - */ - public function theJsonNodeShouldNotBeAnEmptyString($node) - { - if ('' === $actual = $this->getValueOfNode($node)) { - throw new ExpectationFailedException(sprintf('The node value is `%s`', json_encode($actual))); - } - } - - /** - * @Given there is a RelatedDummy - */ - public function thereIsARelatedDummy() - { - $relatedDummy = new RelatedDummy(); - $relatedDummy->setName('RelatedDummy with no friends'); - - $this->manager->persist($relatedDummy); - $this->manager->flush(); - } - - /** - * @Given there is a DummyFriend - */ - public function thereIsADummyFriend() - { - $friend = new DummyFriend(); - $friend->setName('DummyFriend'); - - $this->manager->persist($friend); - $this->manager->flush(); - } - - /** - * @Given there is a CircularReference - */ - public function thereIsACircularReference() - { - $circularReference = new CircularReference(); - $circularReference->parent = $circularReference; - - $circularReferenceBis = new CircularReference(); - $circularReferenceBis->parent = $circularReference; - - $circularReference->children->add($circularReference); - $circularReference->children->add($circularReferenceBis); - - $this->manager->persist($circularReference); - $this->manager->persist($circularReferenceBis); - $this->manager->flush(); - } - - private function getValueOfNode($node) - { - return $this->inspector->evaluate($this->getJson(), $node); - } - - private function getJson() - { - return new Json($this->getContent()); - } - - private function getContent() - { - return $this->restContext->getMink()->getSession()->getDriver()->getContent(); - } -} diff --git a/features/bootstrap/JsonContext.php b/features/bootstrap/JsonContext.php deleted file mode 100644 index c432860437f..00000000000 --- a/features/bootstrap/JsonContext.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -use Behat\Gherkin\Node\PyStringNode; -use Behatch\Context\JsonContext as BaseJsonContext; -use Behatch\HttpCall\HttpCallResultPool; -use Behatch\Json\Json; -use PHPUnit\Framework\Assert; - -final class JsonContext extends BaseJsonContext -{ - public function __construct(HttpCallResultPool $httpCallResultPool) - { - parent::__construct($httpCallResultPool); - } - - private function sortArrays($obj) - { - $isObject = \is_object($obj); - - foreach ($obj as $key => $value) { - if (null === $value || is_scalar($value)) { - continue; - } - - if (\is_array($value)) { - sort($value); - } - - $value = $this->sortArrays($value); - - $isObject ? $obj->{$key} = $value : $obj[$key] = $value; - } - - return $obj; - } - - /** - * @Then /^the JSON should be deep equal to:$/ - */ - public function theJsonShouldBeDeepEqualTo(PyStringNode $content) - { - $actual = $this->getJson(); - try { - $expected = new Json($content); - } catch (\Exception $e) { - throw new \Exception('The expected JSON is not a valid'); - } - - $actual = new Json(json_encode($this->sortArrays($actual->getContent()))); - $expected = new Json(json_encode($this->sortArrays($expected->getContent()))); - - $this->assertSame( - (string) $expected, - (string) $actual, - "The json is equal to:\n".$actual->encode() - ); - } - - /** - * @Then /^the JSON should be a superset of:$/ - */ - public function theJsonIsASupersetOf(PyStringNode $content) - { - $actual = json_decode($this->httpCallResultPool->getResult()->getValue(), true); - Assert::assertArraySubset(json_decode($content->getRaw(), true), $actual); - } -} diff --git a/features/bootstrap/JsonHalContext.php b/features/bootstrap/JsonHalContext.php deleted file mode 100644 index c8caa5c0599..00000000000 --- a/features/bootstrap/JsonHalContext.php +++ /dev/null @@ -1,75 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -use Behat\Behat\Context\Context; -use Behat\Behat\Context\Environment\InitializedContextEnvironment; -use Behat\Behat\Hook\Scope\BeforeScenarioScope; -use Behatch\Context\RestContext; -use Behatch\Json\Json; -use JsonSchema\Validator; -use PHPUnit\Framework\ExpectationFailedException; - -final class JsonHalContext implements Context -{ - /** - * @var RestContext - */ - private $restContext; - private $validator; - private $schemaFile; - - public function __construct(string $schemaFile) - { - if (!is_file($schemaFile)) { - throw new \InvalidArgumentException('The JSON HAL schema doesn\'t exist.'); - } - - $this->validator = new Validator(); - $this->schemaFile = $schemaFile; - } - - /** - * Gives access to the Behatch context. - * - * @BeforeScenario - */ - public function gatherContexts(BeforeScenarioScope $scope) - { - /** @var InitializedContextEnvironment $environment */ - $environment = $scope->getEnvironment(); - $this->restContext = $environment->getContext(RestContext::class); - } - - /** - * @Then the JSON should be valid according to the JSON HAL schema - */ - public function theJsonShouldBeValidAccordingToTheJsonHALSchema() - { - $json = $this->getJson()->getContent(); - $this->validator->validate($json, (object) ['$ref' => 'file://'.__DIR__.'/../../'.$this->schemaFile]); - - if (!$this->validator->isValid()) { - throw new ExpectationFailedException(sprintf('The JSON is not valid according to the HAL+JSON schema.')); - } - } - - private function getJson() - { - return new Json($this->getContent()); - } - - private function getContent() - { - return $this->restContext->getMink()->getSession()->getDriver()->getContent(); - } -} diff --git a/features/bootstrap/SwaggerContext.php b/features/bootstrap/SwaggerContext.php deleted file mode 100644 index 643026de458..00000000000 --- a/features/bootstrap/SwaggerContext.php +++ /dev/null @@ -1,151 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -use Behat\Behat\Context\Context; -use Behat\Behat\Context\Environment\InitializedContextEnvironment; -use Behat\Behat\Hook\Scope\BeforeScenarioScope; -use Behatch\Context\RestContext; -use PHPUnit\Framework\Assert; -use PHPUnit\Framework\ExpectationFailedException; - -final class SwaggerContext implements Context -{ - /** - * @var RestContext - */ - private $restContext; - - /** - * Gives access to the Behatch context. - * - * @BeforeScenario - */ - public function gatherContexts(BeforeScenarioScope $scope) - { - /** @var InitializedContextEnvironment $environment */ - $environment = $scope->getEnvironment(); - $this->restContext = $environment->getContext(RestContext::class); - } - - /** - * @Then the Swagger class :class exists - */ - public function assertTheSwaggerClassExist(string $className) - { - try { - $this->getClassInfo($className); - } catch (\InvalidArgumentException $e) { - throw new ExpectationFailedException(sprintf('The class "%s" doesn\'t exist.', $className), null, $e); - } - } - - /** - * @Then the Swagger class :class doesn't exist - */ - public function assertTheSwaggerClassNotExist(string $className) - { - try { - $this->getClassInfo($className); - } catch (\InvalidArgumentException $e) { - return; - } - - throw new ExpectationFailedException(sprintf('The class "%s" exists.', $className)); - } - - /** - * @Then the Swagger path :arg1 exists - */ - public function assertThePathExist(string $path) - { - $json = $this->getLastJsonResponse(); - - Assert::assertTrue(isset($json->paths) && isset($json->paths->{$path})); - } - - /** - * @Then :prop property exists for the Swagger class :class - */ - public function assertPropertyExist(string $propertyName, string $className) - { - try { - $this->getPropertyInfo($propertyName, $className); - } catch (\InvalidArgumentException $e) { - throw new ExpectationFailedException(sprintf('Property "%s" of class "%s" exists.', $propertyName, $className), null, $e); - } - } - - /** - * @Then :prop property is required for Swagger class :class - */ - public function assertPropertyIsRequired(string $propertyName, string $className) - { - if (!\in_array($propertyName, $this->getClassInfo($className)->required, true)) { - throw new ExpectationFailedException(sprintf('Property "%s" of class "%s" should be required', $propertyName, $className)); - } - } - - /** - * Gets information about a property. - * - * @throws \InvalidArgumentException - */ - private function getPropertyInfo(string $propertyName, string $className): \stdClass - { - foreach ($this->getProperties($className) as $classPropertyName => $property) { - if ($classPropertyName === $propertyName) { - return $property; - } - } - - throw new \InvalidArgumentException(sprintf('The property "%s" for the class "%s" doesn\'t exist.', $propertyName, $className)); - } - - /** - * Gets all operations of a given class. - */ - private function getProperties(string $className): \stdClass - { - return $this->getClassInfo($className)->{'properties'} ?? new \stdClass(); - } - - /** - * Gets information about a class. - * - * @throws \InvalidArgumentException - */ - private function getClassInfo(string $className): \stdClass - { - foreach ($this->getLastJsonResponse()->{'definitions'} as $classTitle => $classData) { - if ($classTitle === $className) { - return $classData; - } - } - - throw new \InvalidArgumentException(sprintf('Class %s cannot be found in the vocabulary', $className)); - } - - /** - * Gets the last JSON response. - * - * @throws \RuntimeException - */ - private function getLastJsonResponse(): \stdClass - { - if (null === ($decoded = json_decode($this->restContext->getMink()->getSession()->getDriver()->getContent()))) { - throw new \RuntimeException('JSON response seems to be invalid'); - } - - return $decoded; - } -} diff --git a/features/doctrine/boolean_filter.feature b/features/doctrine/boolean_filter.feature deleted file mode 100644 index 5ee947172b0..00000000000 --- a/features/doctrine/boolean_filter.feature +++ /dev/null @@ -1,450 +0,0 @@ -Feature: Boolean filter on collections - In order to retrieve ordered large collections of resources - As a client software developer - I need to retrieve collections with boolean value - - @createSchema - Scenario: Get collection by dummyBoolean true - Given there are 15 dummy objects with dummyBoolean true - And there are 10 dummy objects with dummyBoolean false - When I send a "GET" request to "/dummies?dummyBoolean=true" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyBoolean=true"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - And the JSON node "hydra:totalItems" should be equal to 15 - - Scenario: Get collection by dummyBoolean true - When I send a "GET" request to "/dummies?dummyBoolean=1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyBoolean=1"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - And the JSON node "hydra:totalItems" should be equal to 15 - - Scenario: Get collection by dummyBoolean false - When I send a "GET" request to "/dummies?dummyBoolean=false" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/16$"}, - {"pattern": "^/dummies/17$"}, - {"pattern": "^/dummies/18$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyBoolean=false"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - And the JSON node "hydra:totalItems" should be equal to 10 - - Scenario: Get collection by dummyBoolean false - When I send a "GET" request to "/dummies?dummyBoolean=0" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/16$"}, - {"pattern": "^/dummies/17$"}, - {"pattern": "^/dummies/18$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyBoolean=0"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - And the JSON node "hydra:totalItems" should be equal to 10 - - Scenario: Get collection by embeddedDummy.dummyBoolean true - Given there are 15 embedded dummy objects with embeddedDummy.dummyBoolean true - And there are 10 embedded dummy objects with embeddedDummy.dummyBoolean false - When I send a "GET" request to "/embedded_dummies?embeddedDummy.dummyBoolean=true" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/EmbeddedDummy$"}, - "@id": {"pattern": "^/embedded_dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/embedded_dummies/1$"}, - {"pattern": "^/embedded_dummies/2$"}, - {"pattern": "^/embedded_dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/embedded_dummies\\?embeddedDummy\\.dummyBoolean=true"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - And the JSON node "hydra:totalItems" should be equal to 15 - - Scenario: Get collection by embeddedDummy.dummyBoolean true - When I send a "GET" request to "/embedded_dummies?embeddedDummy.dummyBoolean=1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/EmbeddedDummy$"}, - "@id": {"pattern": "^/embedded_dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/embedded_dummies/1$"}, - {"pattern": "^/embedded_dummies/2$"}, - {"pattern": "^/embedded_dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/embedded_dummies\\?embeddedDummy\\.dummyBoolean=1"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - And the JSON node "hydra:totalItems" should be equal to 15 - - Scenario: Get collection by embeddedDummy.dummyBoolean false - When I send a "GET" request to "/embedded_dummies?embeddedDummy.dummyBoolean=false" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/EmbeddedDummy$"}, - "@id": {"pattern": "^/embedded_dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/embedded_dummies/16$"}, - {"pattern": "^/embedded_dummies/17$"}, - {"pattern": "^/embedded_dummies/18$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/embedded_dummies\\?embeddedDummy\\.dummyBoolean=false"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - And the JSON node "hydra:totalItems" should be equal to 10 - - Scenario: Get collection by embeddedDummy.dummyBoolean false - When I send a "GET" request to "/embedded_dummies?embeddedDummy.dummyBoolean=0" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/EmbeddedDummy$"}, - "@id": {"pattern": "^/embedded_dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/embedded_dummies/16$"}, - {"pattern": "^/embedded_dummies/17$"}, - {"pattern": "^/embedded_dummies/18$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/embedded_dummies\\?embeddedDummy\\.dummyBoolean=0"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - And the JSON node "hydra:totalItems" should be equal to 10 - - Scenario: Get collection by association with embed relatedDummy.embeddedDummy.dummyBoolean true - Given there are 15 embedded dummy objects with relatedDummy.embeddedDummy.dummyBoolean true - And there are 10 embedded dummy objects with relatedDummy.embeddedDummy.dummyBoolean false - When I send a "GET" request to "/embedded_dummies?relatedDummy.embeddedDummy.dummyBoolean=true" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/EmbeddedDummy$"}, - "@id": {"pattern": "^/embedded_dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/embedded_dummies/26$"}, - {"pattern": "^/embedded_dummies/27$"}, - {"pattern": "^/embedded_dummies/28$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/embedded_dummies\\?relatedDummy.embeddedDummy\\.dummyBoolean=true"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - And the JSON node "hydra:totalItems" should be equal to 15 - - Scenario: Get collection filtered by non valid properties - When I send a "GET" request to "/dummies?unknown=0" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?unknown=0"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - And the JSON node "hydra:totalItems" should be equal to 25 - - When I send a "GET" request to "/dummies?unknown=1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?unknown=1"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - And the JSON node "hydra:totalItems" should be equal to 25 diff --git a/features/doctrine/date_filter.feature b/features/doctrine/date_filter.feature deleted file mode 100644 index 24b3d384719..00000000000 --- a/features/doctrine/date_filter.feature +++ /dev/null @@ -1,980 +0,0 @@ -Feature: Date filter on collections - In order to retrieve large collections of resources filtered by date - As a client software developer - I need to retrieve collections filtered by date - - @createSchema - Scenario: Get collection filtered by date - Given there are 30 dummy objects with dummyDate - When I send a "GET" request to "/dummies?dummyDate[after]=2015-04-28" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/28$"}, - {"pattern": "^/dummies/29$"} - ] - } - } - }, - "maxItems": 2 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyDate%5Bafter%5D=2015-04-28$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - When I send a "GET" request to "/dummies?dummyDate[before]=2015-04-05" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - }, - "maxItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyDate%5Bbefore%5D=2015-04-05&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - When I send a "GET" request to "/dummies?dummyDate[after]=2015-04-28T00:00:00%2B00:00" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/28$"}, - {"pattern": "^/dummies/29$"} - ] - } - } - }, - "maxItems": 2 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyDate%5Bafter%5D=2015-04-28T00%3A00%3A00%2B00%3A00$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - When I send a "GET" request to "/dummies?dummyDate[before]=2015-04-05Z" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - }, - "maxItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyDate%5Bbefore%5D=2015-04-05Z&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Search for entities within a range - # The order should not influence the search - When I send a "GET" request to "/dummies?dummyDate[before]=2015-04-05&dummyDate[after]=2015-04-05" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/5$"} - ] - } - } - }, - "maxItems": 1 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyDate%5Bbefore%5D=2015-04-05&dummyDate%5Bafter%5D=2015-04-05$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - When I send a "GET" request to "/dummies?dummyDate[after]=2015-04-05&dummyDate[before]=2015-04-05" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/5$"} - ] - } - } - }, - "maxItems": 1 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyDate%5Bafter%5D=2015-04-05&dummyDate%5Bbefore%5D=2015-04-05$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Search for entities within an impossible range - When I send a "GET" request to "/dummies?dummyDate[after]=2015-04-06&dummyDate[before]=2015-04-04" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "maxItems": 0 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyDate%5Bafter%5D=2015-04-06&dummyDate%5Bbefore%5D=2015-04-04$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection filtered by association date - Given there are 30 dummy objects with dummyDate and relatedDummy - When I send a "GET" request to "/dummies?relatedDummy.dummyDate[after]=2015-04-28" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/58$"}, - {"pattern": "^/dummies/59$"}, - {"pattern": "^/dummies/60$"} - ] - } - } - }, - "maxItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?relatedDummy\\.dummyDate%5Bafter%5D=2015-04-28$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - When I send a "GET" request to "/dummies?relatedDummy.dummyDate[after]=2015-04-28&relatedDummy_dummyDate[after]=2015-04-28" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/58$"}, - {"pattern": "^/dummies/59$"}, - {"pattern": "^/dummies/60$"} - ] - } - } - }, - "maxItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?relatedDummy\\.dummyDate%5Bafter%5D=2015-04-28&relatedDummy_dummyDate%5Bafter%5D=2015-04-28$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - When I send a "GET" request to "/dummies?relatedDummy.dummyDate[after]=2015-04-28T00:00:00%2B00:00" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/58$"}, - {"pattern": "^/dummies/59$"}, - {"pattern": "^/dummies/60$"} - ] - } - } - }, - "maxItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?relatedDummy\\.dummyDate%5Bafter%5D=2015-04-28T00%3A00%3A00%2B00%3A00$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - @createSchema - Scenario: Get collection filtered by association date - Given there are 2 dummy objects with dummyDate and relatedDummy - When I send a "GET" request to "/dummies?relatedDummy.dummyDate[after]=2015-04-28" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Dummy", - "@id": "/dummies", - "@type": "hydra:Collection", - "hydra:member": [], - "hydra:totalItems": 0, - "hydra:view": { - "@id": "/dummies?relatedDummy.dummyDate%5Bafter%5D=2015-04-28", - "@type": "hydra:PartialCollectionView" - }, - "hydra:search": { - "@type": "hydra:IriTemplate", - "hydra:template": "/dummies{?dummyBoolean,relatedDummy.embeddedDummy.dummyBoolean,dummyDate[before],dummyDate[strictly_before],dummyDate[after],dummyDate[strictly_after],relatedDummy.dummyDate[before],relatedDummy.dummyDate[strictly_before],relatedDummy.dummyDate[after],relatedDummy.dummyDate[strictly_after],description[exists],relatedDummy.name[exists],dummyBoolean[exists],relatedDummy[exists],dummyFloat,dummyPrice,order[id],order[name],order[description],order[relatedDummy.name],order[relatedDummy.symfony],order[dummyDate],dummyFloat[between],dummyFloat[gt],dummyFloat[gte],dummyFloat[lt],dummyFloat[lte],dummyPrice[between],dummyPrice[gt],dummyPrice[gte],dummyPrice[lt],dummyPrice[lte],id,id[],name,alias,description,relatedDummy.name,relatedDummy.name[],relatedDummies,relatedDummies[],dummy,relatedDummies.name,properties[]}", - "hydra:variableRepresentation": "BasicRepresentation", - "hydra:mapping": [ - { - "@type": "IriTemplateMapping", - "variable": "dummyBoolean", - "property": "dummyBoolean", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.embeddedDummy.dummyBoolean", - "property": "relatedDummy.embeddedDummy.dummyBoolean", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[before]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[strictly_before]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[after]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[strictly_after]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[before]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[strictly_before]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[after]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[strictly_after]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "description[exists]", - "property": "description", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.name[exists]", - "property": "relatedDummy.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyBoolean[exists]", - "property": "dummyBoolean", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy[exists]", - "property": "relatedDummy", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[id]", - "property": "id", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[name]", - "property": "name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[description]", - "property": "description", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[relatedDummy.name]", - "property": "relatedDummy.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[relatedDummy.symfony]", - "property": "relatedDummy.symfony", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[dummyDate]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[between]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[gt]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[gte]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[lt]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[lte]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[between]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[gt]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[gte]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[lt]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[lte]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "id", - "property": "id", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "id[]", - "property": "id", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "name", - "property": "name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "alias", - "property": "alias", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "description", - "property": "description", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.name", - "property": "relatedDummy.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.name[]", - "property": "relatedDummy.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummies", - "property": "relatedDummies", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummies[]", - "property": "relatedDummies", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummy", - "property": "dummy", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummies.name", - "property": "relatedDummies.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "properties[]", - "property": null, - "required": false - } - ] - } - } - """ - - @createSchema - Scenario: Get collection filtered by date that is not a datetime - Given there are 30 dummydate objects with dummyDate - When I send a "GET" request to "/dummy_dates?dummyDate[after]=2015-04-28" - Then the response status code should be 200 - And the JSON node "hydra:totalItems" should be equal to 3 - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - @createSchema - Scenario: Get collection filtered by date that is an immutable date variant - Given there are 30 dummyimmutabledate objects with dummyDate - When I send a "GET" request to "/dummy_immutable_dates?dummyDate[after]=2015-04-28" - Then the response status code should be 200 - And the JSON node "hydra:totalItems" should be equal to 3 - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - @createSchema - Scenario: Get collection filtered by embedded date - Given there are 2 embedded dummy objects with dummyDate and embeddedDummy - When I send a "GET" request to "/embedded_dummies?embeddedDummy.dummyDate[after]=2015-04-28" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/EmbeddedDummy$"}, - "@id": {"pattern": "^/embedded_dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/embedded_dummies/28$"}, - {"pattern": "^/embedded_dummies/29$"} - ] - } - } - }, - "hydra:search": { - "@type": "hydra:IriTemplate", - "hydra:template": "/dummies{?dummyBoolean,dummyDate[before],dummyDate[after],relatedDummy.dummyDate[before],relatedDummy.dummyDate[strictly_before],relatedDummy.dummyDate[after],relatedDummy.dummyDate[strictly_after],description[exists],relatedDummy.name[exists],dummyBoolean[exists],relatedDummy[exists],dummyFloat,dummyPrice,order[id],order[name],order[relatedDummy.symfony],dummyFloat[between],dummyFloat[gt],dummyFloat[gte],dummyFloat[lt],dummyFloat[lte],dummyPrice[between],dummyPrice[gt],dummyPrice[gte],dummyPrice[lt],dummyPrice[lte],id,id[],name,alias,description,relatedDummy.name,relatedDummy.name[],relatedDummies,relatedDummies[],dummy,relatedDummies.name}", - "hydra:variableRepresentation": "BasicRepresentation", - "hydra:mapping": [ - { - "@type": "IriTemplateMapping", - "variable": "dummyBoolean", - "property": "dummyBoolean", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[before]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[strictly_before]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[after]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[strictly_after]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[before]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[strictly_before]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[after]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[strictly_after]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "description[exists]", - "property": "description", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.name[exists]", - "property": "relatedDummy.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy[exists]", - "property": "relatedDummy", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyBoolean[exists]", - "property": "dummyBoolean", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[id]", - "property": "id", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[name]", - "property": "name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[relatedDummy.symfony]", - "property": "relatedDummy.symfony", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[between]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[gt]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[gte]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[lt]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[lte]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[between]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[gt]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[gte]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[lt]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[lte]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "id", - "property": "id", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "id[]", - "property": "id", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "name", - "property": "name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "alias", - "property": "alias", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "description", - "property": "description", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.name", - "property": "relatedDummy.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.name[]", - "property": "relatedDummy.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummies", - "property": "relatedDummies", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummies[]", - "property": "relatedDummies", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummy", - "property": "dummy", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummies.name", - "property": "relatedDummies.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "properties[]", - "property": null, - "required": false - } - ] - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/embedded_dummies\\?embeddedDummy\\.dummyDate%5Bafter%5D=2015-04-28$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - } - """ diff --git a/features/doctrine/exists_filter.feature b/features/doctrine/exists_filter.feature deleted file mode 100644 index 6cbe3bb63f6..00000000000 --- a/features/doctrine/exists_filter.feature +++ /dev/null @@ -1,64 +0,0 @@ -Feature: Exists filter on collections - In order to retrieve large collections of resources - As a client software developer - I need to retrieve collections that exists - - @createSchema - Scenario: Get collection where exists does not exist - Given there are 15 dummy objects with dummyBoolean true - When I send a "GET" request to "/dummies?dummyBoolean[exists]=0" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 0}, - "hydra:member": { - "type": "array", - "maxItems": 0 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyBoolean%5Bexists%5D=0$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection where exists does exist - When I send a "GET" request to "/dummies?dummyBoolean[exists]=1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "minimum": 3}, - "hydra:member": { - "type": "array", - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyBoolean%5Bexists%5D=1&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ diff --git a/features/doctrine/multiple_filter.feature b/features/doctrine/multiple_filter.feature deleted file mode 100644 index a6c25bf7425..00000000000 --- a/features/doctrine/multiple_filter.feature +++ /dev/null @@ -1,47 +0,0 @@ -Feature: Multiple filters on collections - In order to retrieve large collections of filtered resources - As a client software developer - I need to retrieve collections filtered by multiple parameters - - @createSchema - Scenario: Get collection filtered by multiple parameters - Given there are 30 dummy objects with dummyDate and dummyBoolean true - And there are 20 dummy objects with dummyDate and dummyBoolean false - When I send a "GET" request to "/dummies?dummyDate[after]=2015-04-28&dummyBoolean=1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/28$"}, - {"pattern": "^/dummies/29$"} - ] - } - } - }, - "maxItems": 2 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyDate%5Bafter%5D=2015-04-28&dummyBoolean=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - diff --git a/features/doctrine/numeric_filter.feature b/features/doctrine/numeric_filter.feature deleted file mode 100644 index 7d4a8279096..00000000000 --- a/features/doctrine/numeric_filter.feature +++ /dev/null @@ -1,91 +0,0 @@ -Feature: Numeric filter on collections - In order to retrieve ordered large collections of resources - As a client software developer - I need to retrieve collections with numerical value - - @createSchema - Scenario: Get collection by dummyPrice=9.99 - Given there are 10 dummy objects with dummyPrice - When I send a "GET" request to "/dummies?dummyPrice=9.99" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/5$"}, - {"pattern": "^/dummies/9$"} - ] - } - } - }, - "maxItems": 3, - "uniqueItems": true - }, - "hydra:totalItems": {"pattern": "^3$"}, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyPrice=9.99"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection by non-numeric dummyPrice=marty - Given there are 10 dummy objects with dummyPrice - When I send a "GET" request to "/dummies?dummyPrice=marty" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - }, - "maxItems": 3, - "uniqueItems": true - }, - "hydra:totalItems": {"pattern": "^20$"}, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyPrice=marty"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ diff --git a/features/doctrine/order_filter.feature b/features/doctrine/order_filter.feature deleted file mode 100644 index f235c65f30e..00000000000 --- a/features/doctrine/order_filter.feature +++ /dev/null @@ -1,884 +0,0 @@ -Feature: Order filter on collections - In order to retrieve ordered large collections of resources - As a client software developer - I need to retrieve collections ordered properties - - @createSchema - Scenario: Get collection ordered in ascending order on an integer property and on which order filter has been enabled in whitelist mode - Given there are 30 dummy objects - When I send a "GET" request to "/dummies?order[id]=asc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/1$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/2$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/3$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5Bid%5D=asc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection ordered in descending order on an integer property and on which order filter has been enabled in whitelist mode - When I send a "GET" request to "/dummies?order[id]=desc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/30$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/29$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/28$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5Bid%5D=desc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection ordered in ascending order on a string property and on which order filter has been enabled in whitelist mode - When I send a "GET" request to "/dummies?order[name]=asc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/1$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/10$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/11$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5Bname%5D=asc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection ordered in descending order on a string property and on which order filter has been enabled in whitelist mode - When I send a "GET" request to "/dummies?order[name]=desc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/9$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/8$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/7$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5Bname%5D=desc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection ordered by default configured order on a string property and on which order filter has been enabled in whitelist mode with default descending order - When I send a "GET" request to "/dummies?order[name]" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/9$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/8$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/7$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5Bname%5D="}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection ordered collection on several property keep the order - # Adding 30 more data with the same name - Given there are 30 dummy objects - When I send a "GET" request to "/dummies?order[name]=desc&order[id]=desc" - Then the response status code should be 200 - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/39$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/9$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/38$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5Bname%5D=desc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection ordered in ascending order on an association and on which order filter has been enabled in whitelist mode - Given there are 30 dummy objects with relatedDummy - When I send a "GET" request to "/dummies?order[relatedDummy]=asc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/1$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/2$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/3$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5BrelatedDummy%5D=asc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection ordered in ascending order on an embedded and on which order filter has been enabled in whitelist mode - Given there are 30 dummy objects with embeddedDummy - When I send a "GET" request to "/embedded_dummies?order[embeddedDummy]=asc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/EmbeddedDummy$"}, - "@id": {"pattern": "^/embedded_dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/embedded_dummies/1$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/embedded_dummies/2$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/embedded_dummies/3$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/embedded_dummies\\?order%5BembeddedDummy%5D=asc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection ordered by default configured order on a embedded string property and on which order filter has been enabled in whitelist mode with default descending order - When I send a "GET" request to "/embedded_dummies?order[embeddedDummy.dummyName]" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/EmbeddedDummy$"}, - "@id": {"pattern": "^/embedded_dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/embedded_dummies/9" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/embedded_dummies/8" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/embedded_dummies/7" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/embedded_dummies\\?order%5BembeddedDummy\\.dummyName%5D="}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection even if the order parameter is not well-formed - When I send a "GET" request to "/dummies?sort=id&order=asc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/1$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/2$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/3$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection ordered by a non valid properties and on which order filter has been enabled in whitelist mode - When I send a "GET" request to "/dummies?order[alias]=asc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/1$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/2$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/3$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5Balias%5D=asc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - When I send a "GET" request to "/dummies?order[alias]=desc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/1$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/2$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/3$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5Balias%5D=desc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - When I send a "GET" request to "/dummies?order[unknown]=asc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/1$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/2$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/3$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5Bunknown%5D=asc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - When I send a "GET" request to "/dummies?order[unknown]=desc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/1$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/2$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/3$" - } - } - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5Bunknown%5D=desc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - @createSchema - Scenario: Get collection ordered in descending order on a related property - Given there are 2 dummy objects with relatedDummy - When I send a "GET" request to "/dummies?order[relatedDummy.name]=desc" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/2$" - } - } - }, - { - "type": "object", - "properties": { - "@id": { - "type": "string", - "pattern": "^/dummies/1$" - } - } - } - ], - "additionalItems": false, - "maxItems": 2, - "minItems": 2 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?order%5BrelatedDummy.name%5D=desc"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ diff --git a/features/doctrine/range_filter.feature b/features/doctrine/range_filter.feature deleted file mode 100644 index dab2d60feb1..00000000000 --- a/features/doctrine/range_filter.feature +++ /dev/null @@ -1,374 +0,0 @@ -Feature: Range filter on collections - In order to filter results from large collections of resources - As a client software developer - I need to filter collections by range - - @createSchema - Scenario: Get collection filtered by range (between) - Given there are 30 dummy objects with dummyPrice - When I send a "GET" request to "/dummies?dummyPrice[between]=12.99..15.99" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"}, - {"pattern": "^/dummies/6$"}, - {"pattern": "^/dummies/7$"}, - {"pattern": "^/dummies/10$"}, - {"pattern": "^/dummies/11$"}, - {"pattern": "^/dummies/14$"}, - {"pattern": "^/dummies/15$"}, - {"pattern": "^/dummies/18$"}, - {"pattern": "^/dummies/19$"}, - {"pattern": "^/dummies/22$"}, - {"pattern": "^/dummies/23$"}, - {"pattern": "^/dummies/26$"}, - {"pattern": "^/dummies/27$"}, - {"pattern": "^/dummies/30$"} - ] - } - } - }, - "maxItems": 15, - "uniqueItems": true - }, - "hydra:totalItems": {"pattern": "^15$"}, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyPrice%5Bbetween%5D=12.99..15.99"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Filter by range (between) with invalid format - When I send a "GET" request to "/dummies?dummyPrice[between]=9.99..12.99..15.99" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "pattern": "^/dummies/([1-9]|[12][0-9]|30)$" - } - } - }, - "maxItems": 30, - "uniqueItems": true - }, - "hydra:totalItems": {"pattern": "^30$"}, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyPrice%5Bbetween%5D=9.99..12.99..15.99"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Filter for entities by range (less than) - When I send a "GET" request to "/dummies?dummyPrice[lt]=12.99" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/5$"}, - {"pattern": "^/dummies/9$"}, - {"pattern": "^/dummies/13$"}, - {"pattern": "^/dummies/17$"}, - {"pattern": "^/dummies/21$"}, - {"pattern": "^/dummies/25$"}, - {"pattern": "^/dummies/29$"} - ] - } - } - }, - "maxItems": 8, - "uniqueItems": true - }, - "hydra:totalItems": {"pattern": "^8$"}, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyPrice%5Blt%5D=12.99"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Filter for entities by range (less than or equal) - When I send a "GET" request to "/dummies?dummyPrice[lte]=12.99" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/5$"}, - {"pattern": "^/dummies/6$"}, - {"pattern": "^/dummies/9$"}, - {"pattern": "^/dummies/10$"}, - {"pattern": "^/dummies/13$"}, - {"pattern": "^/dummies/14$"}, - {"pattern": "^/dummies/17$"}, - {"pattern": "^/dummies/18$"}, - {"pattern": "^/dummies/21$"}, - {"pattern": "^/dummies/22$"}, - {"pattern": "^/dummies/25$"}, - {"pattern": "^/dummies/26$"}, - {"pattern": "^/dummies/29$"}, - {"pattern": "^/dummies/30$"} - ] - } - } - }, - "maxItems": 16, - "uniqueItems": true - }, - "hydra:totalItems": {"pattern": "^16$"}, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyPrice%5Blte%5D=12.99"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Filter for entities by range (greater than) - When I send a "GET" request to "/dummies?dummyPrice[gt]=15.99" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/4$"}, - {"pattern": "^/dummies/8$"}, - {"pattern": "^/dummies/12$"}, - {"pattern": "^/dummies/15$"}, - {"pattern": "^/dummies/20$"}, - {"pattern": "^/dummies/24$"}, - {"pattern": "^/dummies/28$"} - ] - } - } - }, - "maxItems": 7, - "uniqueItems": true - }, - "hydra:totalItems": {"pattern": "^7$"}, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyPrice%5Bgt%5D=15.99"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Filter for entities by range (greater than or equal) - When I send a "GET" request to "/dummies?dummyPrice[gte]=15.99" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/3$"}, - {"pattern": "^/dummies/4$"}, - {"pattern": "^/dummies/7$"}, - {"pattern": "^/dummies/8$"}, - {"pattern": "^/dummies/11$"}, - {"pattern": "^/dummies/12$"}, - {"pattern": "^/dummies/14$"}, - {"pattern": "^/dummies/15$"}, - {"pattern": "^/dummies/19$"}, - {"pattern": "^/dummies/20$"}, - {"pattern": "^/dummies/23$"}, - {"pattern": "^/dummies/24$"}, - {"pattern": "^/dummies/27$"}, - {"pattern": "^/dummies/28$"} - ] - } - } - }, - "maxItems": 14, - "uniqueItems": true - }, - "hydra:totalItems": {"pattern": "^14$"}, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyPrice%5Bgte%5D=15.99"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Filter for entities by range (greater than and less than) - When I send a "GET" request to "/dummies?dummyPrice[gt]=12.99&dummyPrice[lt]=19.99" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/3$"}, - {"pattern": "^/dummies/7$"}, - {"pattern": "^/dummies/11$"}, - {"pattern": "^/dummies/15$"}, - {"pattern": "^/dummies/19$"}, - {"pattern": "^/dummies/23$"}, - {"pattern": "^/dummies/27$"} - ] - } - } - }, - "maxItems": 7, - "uniqueItems": true - }, - "hydra:totalItems": {"pattern": "^7$"}, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyPrice%5Bgt%5D=12.99&dummyPrice%5Blt%5D=19.99"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Filter for entities within an impossible range - When I send a "GET" request to "/dummies?dummyPrice[gt]=19.99" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "maxItems": 0 - }, - "hydra:totalItems": {"pattern": "^0$"}, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?dummyPrice%5Bgt%5D=19.99$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ diff --git a/features/doctrine/search_filter.feature b/features/doctrine/search_filter.feature deleted file mode 100644 index 6f16a04e17a..00000000000 --- a/features/doctrine/search_filter.feature +++ /dev/null @@ -1,543 +0,0 @@ -Feature: Search filter on collections - In order to get specific result from a large collections of resources - As a client software developer - I need to search for collections properties - - @createSchema - Scenario: Test ManyToMany with filter on join table - Given there is a RelatedDummy with 4 friends - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/related_dummies?relatedToDummyFriend.dummyFriend=/dummy_friends/4" - Then the response status code should be 200 - And the JSON node "_embedded.item" should have 1 element - And the JSON node "_embedded.item[0].id" should be equal to the number 1 - And the JSON node "_embedded.item[0]._links.relatedToDummyFriend" should have 4 elements - And the JSON node "_embedded.item[0]._embedded.relatedToDummyFriend" should have 4 elements - - @createSchema - Scenario: Test #944 - Given there is a DummyCar entity with related colors - When I send a "GET" request to "/dummy_cars?colors.prop=red" - Then the response status code should be 200 - And the JSON should be deep equal to: - """ - { - "@context": "/contexts/DummyCar", - "@id": "/dummy_cars", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/dummy_cars/1", - "@type": "DummyCar", - "colors": [ - { - "@id": "/dummy_car_colors/1", - "@type": "DummyCarColor", - "prop": "red" - }, - { - "@id": "/dummy_car_colors/2", - "@type": "DummyCarColor", - "prop": "blue" - } - ] - } - ], - "hydra:totalItems": 1, - "hydra:view": { - "@id": "/dummy_cars?colors.prop=red", - "@type": "hydra:PartialCollectionView" - }, - "hydra:search": { - "@type": "hydra:IriTemplate", - "hydra:template": "\/dummy_cars{?availableAt[before],availableAt[strictly_before],availableAt[after],availableAt[strictly_after],canSell,foobar[],foobargroups[],foobargroups_override[],colors.prop,name}", - "hydra:variableRepresentation": "BasicRepresentation", - "hydra:mapping": [ - { - "@type": "IriTemplateMapping", - "variable": "availableAt[after]", - "property": "availableAt", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "availableAt[before]", - "property": "availableAt", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "availableAt[strictly_after]", - "property": "availableAt", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "availableAt[strictly_before]", - "property": "availableAt", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "canSell", - "property": "canSell", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "colors.prop", - "property": "colors.prop", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "foobar[]", - "property": null, - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "foobargroups[]", - "property": null, - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "foobargroups_override[]", - "property": null, - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "name", - "property": "name", - "required": false - } - ] - } - } - """ - - Scenario: Search collection by name (partial) - Given there are 30 dummy objects - When I send a "GET" request to "/dummies?name=my" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?name=my"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Search collection by name (partial) - Given there are 30 embedded dummy objects - When I send a "GET" request to "/embedded_dummies?embeddedDummy.dummyName=my" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/EmbeddedDummy$"}, - "@id": {"pattern": "^/embedded_dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/embedded_dummies/1$"}, - {"pattern": "^/embedded_dummies/2$"}, - {"pattern": "^/embedded_dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/embedded_dummies\\?embeddedDummy\\.dummyName=my"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Search collection by name (partial case insensitive) - When I send a "GET" request to "/dummies?dummy=somedummytest1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "dummy": { - "pattern": "^SomeDummyTest\\d{1,2}$" - } - } - } - } - } - } - """ - - Scenario: Search collection by alias (start) - When I send a "GET" request to "/dummies?alias=Ali" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?alias=Ali"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - @sqlite - Scenario: Search collection by description (word_start) - When I send a "GET" request to "/dummies?description=smart" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?description=smart"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - # note on Postgres compared to sqlite the LIKE clause is case sensitive - @postgres - Scenario: Search collection by description (word_start) - When I send a "GET" request to "/dummies?description=smart" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/4$"}, - {"pattern": "^/dummies/6$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?description=smart"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Search for entities within an impossible range - When I send a "GET" request to "/dummies?name=MuYm" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "maxItems": 0 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?name=MuYm$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - @createSchema - Scenario: Search related collection by name - Given there are 3 dummy objects having each 3 relatedDummies - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/dummies?relatedDummies.name=RelatedDummy1" - Then the response status code should be 200 - And the response should be in JSON - And the JSON node "_embedded.item" should have 3 elements - And the JSON node "_embedded.item[0]._links.relatedDummies" should have 3 elements - And the JSON node "_embedded.item[1]._links.relatedDummies" should have 3 elements - And the JSON node "_embedded.item[2]._links.relatedDummies" should have 3 elements - - @createSchema - Scenario: Get collection by id equals 9.99 which is not possible - Given there are 30 dummy objects - When I send a "GET" request to "/dummies?id=9.99" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?id=9.99"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get collection by id 10 - When I send a "GET" request to "/dummies?id=10" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/10$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?id=10"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - - Scenario: Get collection ordered by a non valid properties - When I send a "GET" request to "/dummies?unknown=0" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?unknown=0"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - When I send a "GET" request to "/dummies?unknown=1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - } - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?unknown=1"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ diff --git a/features/filter/filter_validation.feature b/features/filter/filter_validation.feature deleted file mode 100644 index ab85b45dd5d..00000000000 --- a/features/filter/filter_validation.feature +++ /dev/null @@ -1,39 +0,0 @@ -Feature: Validate filters based upon filter description - - @createSchema - Scenario: Required filter should not throw an error if set - When I am on "/filter_validators?required=foo" - Then the response status code should be 200 - - When I am on "/filter_validators?required=" - Then the response status code should be 200 - - Scenario: Required filter should throw an error if not set - When I am on "/filter_validators" - Then the response status code should be 400 - And the JSON node "detail" should be equal to 'Query parameter "required" is required' - - Scenario: Required filter should not throw an error if set - When I am on "/array_filter_validators?arrayRequired[]=foo&indexedArrayRequired[foo]=foo" - Then the response status code should be 200 - - Scenario: Required filter should throw an error if not set - When I am on "/array_filter_validators" - Then the response status code should be 400 - And the JSON node "detail" should match '/^Query parameter "arrayRequired\[\]" is required\nQuery parameter "indexedArrayRequired\[foo\]" is required$/' - - When I am on "/array_filter_validators?arrayRequired=foo&indexedArrayRequired[foo]=foo" - Then the response status code should be 400 - And the JSON node "detail" should be equal to 'Query parameter "arrayRequired[]" is required' - - When I am on "/array_filter_validators?arrayRequired[foo]=foo" - Then the response status code should be 400 - And the JSON node "detail" should match '/^Query parameter "arrayRequired\[\]" is required\nQuery parameter "indexedArrayRequired\[foo\]" is required$/' - - When I am on "/array_filter_validators?arrayRequired[]=foo" - Then the response status code should be 400 - And the JSON node "detail" should be equal to 'Query parameter "indexedArrayRequired[foo]" is required' - - When I am on "/array_filter_validators?arrayRequired[]=foo&indexedArrayRequired[bar]=bar" - Then the response status code should be 400 - And the JSON node "detail" should be equal to 'Query parameter "indexedArrayRequired[foo]" is required' diff --git a/features/filter/property_filter.feature b/features/filter/property_filter.feature deleted file mode 100644 index 722ced764ae..00000000000 --- a/features/filter/property_filter.feature +++ /dev/null @@ -1,19 +0,0 @@ -Feature: Set properties to include - In order to select specific properties from a resource - As a client software developer - I need to select attributes to retrieve - - @createSchema - Scenario: Test properties filter - Given there are 1 dummy objects with relatedDummy and its thirdLevel - When I send a "GET" request to "/dummies/1?properties[]=name&properties[]=alias&properties[]=relatedDummy" - And the JSON node "name" should be equal to "Dummy #1" - And the JSON node "alias" should be equal to "Alias #0" - And the JSON node "relatedDummies" should not exist - - Scenario: Test relation embedding - When I send a "GET" request to "/dummies/1?properties[]=name&properties[]=alias&properties[relatedDummy][]=name" - And the JSON node "name" should be equal to "Dummy #1" - And the JSON node "alias" should be equal to "Alias #0" - And the JSON node "relatedDummy.name" should be equal to "RelatedDummy #1" - And the JSON node "relatedDummies" should not exist diff --git a/features/graphql/authorization.feature b/features/graphql/authorization.feature deleted file mode 100644 index 64ddc8ccc18..00000000000 --- a/features/graphql/authorization.feature +++ /dev/null @@ -1,56 +0,0 @@ -Feature: Authorization checking - In order to use the GraphQL API - As a client software user - I need to be authorized to access a given resource. - - @createSchema - Scenario: An anonymous user tries to retrieve a secured item - Given there are 1 SecuredDummy objects - When I send the following GraphQL request: - """ - { - securedDummy(id: "/secured_dummies/1") { - title - description - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "errors[0].message" should be equal to "Access Denied." - - Scenario: An anonymous user tries to retrieve a secured collection - Given there are 1 SecuredDummy objects - When I send the following GraphQL request: - """ - { - securedDummies { - edges { - node { - title - description - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "errors[0].message" should be equal to "Access Denied." - - Scenario: An anonymous user tries to create a resource he is not allowed to - When I send the following GraphQL request: - """ - mutation { - createSecuredDummy(input: {owner: "me", title: "Hi", description: "Desc", clientMutationId: "auth"}) { - title - owner - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "errors[0].message" should be equal to "Only admins can create a secured dummy." diff --git a/features/graphql/collection.feature b/features/graphql/collection.feature deleted file mode 100644 index 32405160155..00000000000 --- a/features/graphql/collection.feature +++ /dev/null @@ -1,350 +0,0 @@ -Feature: GraphQL collection support - @createSchema - Scenario: Retrieve a collection through a GraphQL query - Given there are 4 dummy objects with relatedDummy and its thirdLevel - When I send the following GraphQL request: - """ - { - dummies { - ...dummyFields - } - } - fragment dummyFields on DummyConnection { - edges { - node { - id - name - relatedDummy { - name - thirdLevel { - id - level - } - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.edges[2].node.name" should be equal to "Dummy #3" - And the JSON node "data.dummies.edges[2].node.relatedDummy.name" should be equal to "RelatedDummy #3" - And the JSON node "data.dummies.edges[2].node.relatedDummy.thirdLevel.level" should be equal to 3 - - @createSchema - Scenario: Retrieve an nonexistent collection through a GraphQL query - When I send the following GraphQL request: - """ - { - dummies { - edges { - node { - name - } - } - pageInfo { - endCursor - hasNextPage - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.edges" should have 0 element - And the JSON node "data.dummies.pageInfo.endCursor" should be null - And the JSON node "data.dummies.pageInfo.hasNextPage" should be false - - @createSchema - Scenario: Retrieve a collection with a nested collection through a GraphQL query - Given there are 4 dummy objects having each 3 relatedDummies - When I send the following GraphQL request: - """ - { - dummies { - edges { - node { - name - relatedDummies { - edges { - node { - name - } - } - } - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.edges[2].node.name" should be equal to "Dummy #3" - And the JSON node "data.dummies.edges[2].node.relatedDummies.edges[1].node.name" should be equal to "RelatedDummy23" - - @createSchema - Scenario: Retrieve a collection and an item through a GraphQL query - Given there are 3 dummy objects with dummyDate - And there are 2 dummy group objects - When I send the following GraphQL request: - """ - { - dummies { - edges { - node { - name - dummyDate - } - } - } - dummyGroup(id: "/dummy_groups/2") { - foo - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.edges[1].node.name" should be equal to "Dummy #2" - And the JSON node "data.dummies.edges[1].node.dummyDate" should be equal to "2015-04-02T00:00:00+00:00" - And the JSON node "data.dummyGroup.foo" should be equal to "Foo #2" - - @createSchema - Scenario: Retrieve a specific number of items in a collection through a GraphQL query - Given there are 4 dummy objects - When I send the following GraphQL request: - """ - { - dummies(first: 2) { - edges { - node { - name - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.edges" should have 2 elements - - @createSchema - Scenario: Retrieve a specific number of items in a nested collection through a GraphQL query - Given there are 2 dummy objects having each 5 relatedDummies - When I send the following GraphQL request: - """ - { - dummies(first: 1) { - edges { - node { - name - relatedDummies(first: 2) { - edges { - node { - name - } - } - } - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.edges" should have 1 element - And the JSON node "data.dummies.edges[0].node.relatedDummies.edges" should have 2 elements - - @createSchema - Scenario: Paginate through collections through a GraphQL query - Given there are 4 dummy objects having each 4 relatedDummies - When I send the following GraphQL request: - """ - { - dummies(first: 2) { - edges { - node { - name - relatedDummies(first: 2) { - edges { - node { - name - } - cursor - } - totalCount - pageInfo { - endCursor - hasNextPage - } - } - } - cursor - } - totalCount - pageInfo { - endCursor - hasNextPage - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.pageInfo.endCursor" should be equal to "Mw==" - And the JSON node "data.dummies.pageInfo.hasNextPage" should be true - And the JSON node "data.dummies.totalCount" should be equal to 4 - And the JSON node "data.dummies.edges[1].node.name" should be equal to "Dummy #2" - And the JSON node "data.dummies.edges[1].cursor" should be equal to "MQ==" - And the JSON node "data.dummies.edges[1].node.relatedDummies.pageInfo.endCursor" should be equal to "Mw==" - And the JSON node "data.dummies.edges[1].node.relatedDummies.pageInfo.hasNextPage" should be true - And the JSON node "data.dummies.edges[1].node.relatedDummies.totalCount" should be equal to 4 - And the JSON node "data.dummies.edges[1].node.relatedDummies.edges[0].node.name" should be equal to "RelatedDummy12" - And the JSON node "data.dummies.edges[1].node.relatedDummies.edges[0].cursor" should be equal to "MA==" - When I send the following GraphQL request: - """ - { - dummies(first: 2, after: "MQ==") { - edges { - node { - name - relatedDummies(first: 2, after: "MA==") { - edges { - node { - name - } - cursor - } - pageInfo { - endCursor - hasNextPage - } - } - } - cursor - } - pageInfo { - endCursor - hasNextPage - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.edges[0].node.name" should be equal to "Dummy #3" - And the JSON node "data.dummies.edges[0].cursor" should be equal to "Mg==" - And the JSON node "data.dummies.edges[1].node.relatedDummies.edges[0].node.name" should be equal to "RelatedDummy24" - And the JSON node "data.dummies.edges[1].node.relatedDummies.edges[0].cursor" should be equal to "MQ==" - When I send the following GraphQL request: - """ - { - dummies(first: 2, after: "Mg==") { - edges { - node { - name - relatedDummies(first: 3, after: "MQ==") { - edges { - node { - name - } - cursor - } - pageInfo { - endCursor - hasNextPage - } - } - } - cursor - } - pageInfo { - endCursor - hasNextPage - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.edges" should have 1 element - And the JSON node "data.dummies.pageInfo.hasNextPage" should be false - And the JSON node "data.dummies.edges[0].node.name" should be equal to "Dummy #4" - And the JSON node "data.dummies.edges[0].cursor" should be equal to "Mw==" - And the JSON node "data.dummies.edges[0].node.relatedDummies.pageInfo.hasNextPage" should be false - And the JSON node "data.dummies.edges[0].node.relatedDummies.edges" should have 2 elements - And the JSON node "data.dummies.edges[0].node.relatedDummies.edges[1].node.name" should be equal to "RelatedDummy44" - And the JSON node "data.dummies.edges[0].node.relatedDummies.edges[1].cursor" should be equal to "Mw==" - When I send the following GraphQL request: - """ - { - dummies(first: 2, after: "Mw==") { - edges { - node { - name - relatedDummies(first: 1, after: "MQ==") { - edges { - node { - name - } - cursor - } - pageInfo { - endCursor - hasNextPage - } - } - } - cursor - } - pageInfo { - endCursor - hasNextPage - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.edges" should have 0 element - - @createSchema - Scenario: Retrieve an item with composite primitive identifiers through a GraphQL query - Given there are composite primitive identifiers objects - When I send the following GraphQL request: - """ - { - compositePrimitiveItem(id: "/composite_primitive_items/name=Bar;year=2017") { - description - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.compositePrimitiveItem.description" should be equal to "This is bar." - - @createSchema - Scenario: Retrieve an item with composite identifiers through a GraphQL query - Given there are Composite identifier objects - When I send the following GraphQL request: - """ - { - compositeRelation(id: "/composite_relations/compositeItem=1;compositeLabel=1") { - value - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.compositeRelation.value" should be equal to "somefoobardummy" diff --git a/features/graphql/filters.feature b/features/graphql/filters.feature deleted file mode 100644 index 17f4c6bd129..00000000000 --- a/features/graphql/filters.feature +++ /dev/null @@ -1,181 +0,0 @@ -Feature: Collections filtering - In order to retrieve subsets of collections - As an API consumer - I need to be able to set filters - - @createSchema - Scenario: Retrieve a collection filtered using the boolean filter - Given there is 1 dummy object with dummyBoolean true - And there is 1 dummy object with dummyBoolean false - When I send the following GraphQL request: - """ - { - dummies(dummyBoolean: false) { - edges { - node { - id - dummyBoolean - } - } - } - } - """ - Then the JSON node "data.dummies.edges" should have 1 element - And the JSON node "data.dummies.edges[0].node.dummyBoolean" should be false - - @createSchema - Scenario: Retrieve a collection filtered using the exists filter - Given there are 3 dummy objects - And there are 2 dummy objects with relatedDummy - When I send the following GraphQL request: - """ - { - dummies(relatedDummy: {exists: true}) { - edges { - node { - id - relatedDummy { - name - } - } - } - } - } - """ - Then the response status code should be 200 - And the JSON node "data.dummies.edges" should have 2 elements - And the JSON node "data.dummies.edges[0].node.relatedDummy" should have 1 element - - @createSchema - Scenario: Retrieve a collection filtered using the date filter - Given there are 3 dummy objects with dummyDate - When I send the following GraphQL request: - """ - { - dummies(dummyDate: {after: "2015-04-02"}) { - edges { - node { - id - dummyDate - } - } - } - } - """ - Then the JSON node "data.dummies.edges" should have 1 element - And the JSON node "data.dummies.edges[0].node.dummyDate" should be equal to "2015-04-02T00:00:00+00:00" - - @createSchema - Scenario: Retrieve a collection filtered using the search filter - Given there are 10 dummy objects - When I send the following GraphQL request: - """ - { - dummies(name: "#2") { - edges { - node { - id - name - } - } - } - } - """ - Then the JSON node "data.dummies.edges" should have 1 element - And the JSON node "data.dummies.edges[0].node.id" should be equal to "/dummies/2" - - @createSchema - Scenario: Retrieve a collection filtered using the search filter - Given there are 3 dummy objects having each 3 relatedDummies - When I send the following GraphQL request: - """ - { - dummies { - edges { - node { - id - relatedDummies(name: "RelatedDummy13") { - edges { - node { - id - name - } - } - } - } - } - } - } - """ - And the JSON node "data.dummies.edges[0].node.relatedDummies.edges" should have 0 elements - And the JSON node "data.dummies.edges[1].node.relatedDummies.edges" should have 0 elements - And the JSON node "data.dummies.edges[2].node.relatedDummies.edges" should have 1 element - And the JSON node "data.dummies.edges[2].node.relatedDummies.edges[0].node.name" should be equal to "RelatedDummy13" - - @createSchema - Scenario: Retrieve a collection filtered using the related search filter - Given there are 1 dummy objects having each 2 relatedDummies - And there are 1 dummy objects having each 3 relatedDummies - When I send the following GraphQL request: - """ - { - dummies(relatedDummies_name: "RelatedDummy31") { - edges { - node { - id - } - } - } - } - """ - And the response status code should be 200 - And the JSON node "data.dummies.edges" should have 1 element - - @createSchema - Scenario: Retrieve a collection ordered using nested properties - Given there are 2 dummy objects with relatedDummy - When I send the following GraphQL request: - """ - { - dummies(order: {relatedDummy_name: "DESC"}) { - edges { - node { - name - relatedDummy { - id - name - } - } - } - } - } - """ - Then the response status code should be 200 - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.edges[0].node.name" should be equal to "Dummy #2" - And the JSON node "data.dummies.edges[1].node.name" should be equal to "Dummy #1" - - @createSchema - Scenario: Retrieve a collection filtered using the related search filter with two values and exact strategy - Given there are 3 dummy objects with relatedDummy - When I send the following GraphQL request: - """ - { - dummies(relatedDummy_name_list: ["RelatedDummy #1", "RelatedDummy #2"]) { - edges { - node { - id - name - relatedDummy { - name - } - } - } - } - } - """ - Then the response status code should be 200 - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummies.edges" should have 2 element - And the JSON node "data.dummies.edges[0].node.relatedDummy.name" should be equal to "RelatedDummy #1" - And the JSON node "data.dummies.edges[1].node.relatedDummy.name" should be equal to "RelatedDummy #2" diff --git a/features/graphql/introspection.feature b/features/graphql/introspection.feature deleted file mode 100644 index 56e9f15748a..00000000000 --- a/features/graphql/introspection.feature +++ /dev/null @@ -1,334 +0,0 @@ -Feature: GraphQL introspection support - - @createSchema - Scenario: Execute an empty GraphQL query - When I send a "GET" request to "/graphql" - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "errors[0].message" should be equal to "GraphQL query is not valid" - - Scenario: Introspect the GraphQL schema - When I send the query to introspect the schema - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.__schema.types" should exist - And the JSON node "data.__schema.queryType.name" should be equal to "Query" - And the JSON node "data.__schema.mutationType.name" should be equal to "Mutation" - - Scenario: Introspect types - When I send the following GraphQL request: - """ - { - type1: __type(name: "DummyProduct") { - description, - fields { - name - type { - name - kind - ofType { - name - kind - } - } - } - } - type2: __type(name: "DummyAggregateOfferConnection") { - description, - fields { - name - type { - name - kind - ofType { - name - kind - } - } - } - } - type3: __type(name: "DummyAggregateOfferEdge") { - description, - fields { - name - type { - name - kind - ofType { - name - kind - } - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.type1.description" should be equal to "Dummy Product." - And the JSON node "data.type1.fields[1].type.name" should be equal to "DummyAggregateOfferConnection" - And the JSON node "data.type2.fields[0].name" should be equal to "edges" - And the JSON node "data.type2.fields[0].type.ofType.name" should be equal to "DummyAggregateOfferEdge" - And the JSON node "data.type3.fields[0].name" should be equal to "node" - And the JSON node "data.type3.fields[1].name" should be equal to "cursor" - And the JSON node "data.type3.fields[0].type.name" should be equal to "DummyAggregateOffer" - - Scenario: Introspect deprecated queries - When I send the following GraphQL request: - """ - { - __type (name: "Query") { - name - fields(includeDeprecated: true) { - name - isDeprecated - deprecationReason - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the GraphQL field "deprecatedResource" is deprecated for the reason "This resource is deprecated" - And the GraphQL field "deprecatedResources" is deprecated for the reason "This resource is deprecated" - - Scenario: Introspect deprecated mutations - When I send the following GraphQL request: - """ - { - __type (name: "Mutation") { - name - fields(includeDeprecated: true) { - name - isDeprecated - deprecationReason - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the GraphQL field "deleteDeprecatedResource" is deprecated for the reason "This resource is deprecated" - And the GraphQL field "updateDeprecatedResource" is deprecated for the reason "This resource is deprecated" - And the GraphQL field "createDeprecatedResource" is deprecated for the reason "This resource is deprecated" - - Scenario: Introspect a deprecated field - When I send the following GraphQL request: - """ - { - __type(name: "DeprecatedResource") { - fields(includeDeprecated: true) { - name - isDeprecated - deprecationReason - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the GraphQL field "deprecatedField" is deprecated for the reason "This field is deprecated" - - Scenario: Retrieve the Relay's node interface - When I send the following GraphQL request: - """ - { - __type(name: "Node") { - name - kind - fields { - name - type { - kind - ofType { - name - kind - } - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON should be deep equal to: - """ - { - "data": { - "__type": { - "name": "Node", - "kind": "INTERFACE", - "fields": [ - { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "name": "ID", - "kind": "SCALAR" - } - } - } - ] - } - } - } - """ - - Scenario: Retrieve the Relay's node field - When I send the following GraphQL request: - """ - { - __schema { - queryType { - fields { - name - type { - name - kind - } - args { - name - type { - kind - ofType { - name - kind - } - } - } - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.__schema.queryType.fields[0].name" should be equal to "node" - And the JSON node "data.__schema.queryType.fields[0].type.name" should be equal to "Node" - And the JSON node "data.__schema.queryType.fields[0].type.kind" should be equal to "INTERFACE" - And the JSON node "data.__schema.queryType.fields[0].args[0].name" should be equal to "id" - And the JSON node "data.__schema.queryType.fields[0].args[0].type.kind" should be equal to "NON_NULL" - And the JSON node "data.__schema.queryType.fields[0].args[0].type.ofType.name" should be equal to "ID" - And the JSON node "data.__schema.queryType.fields[0].args[0].type.ofType.kind" should be equal to "SCALAR" - - Scenario: Introspect an Iterable type field - When I send the following GraphQL request: - """ - { - __type(name: "Dummy") { - description, - fields { - name - type { - name - kind - ofType { - name - kind - } - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.__type.fields[9].name" should be equal to "jsonData" - And the JSON node "data.__type.fields[9].type.name" should be equal to "Iterable" - - Scenario: Retrieve entity - using serialization groups - fields - When I send the following GraphQL request: - """ - { - typeQuery: __type(name: "DummyGroup") { - description, - fields { - name - type { - name - kind - ofType { - name - kind - } - } - } - } - typeCreateInput: __type(name: "createDummyGroupInput") { - description, - inputFields { - name - type { - name - kind - ofType { - name - kind - } - } - } - } - typeCreatePayload: __type(name: "createDummyGroupPayload") { - description, - fields { - name - type { - name - kind - ofType { - name - kind - } - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.typeQuery.fields" should have 2 elements - And the JSON node "data.typeQuery.fields[0].name" should be equal to "id" - And the JSON node "data.typeQuery.fields[1].name" should be equal to "foo" - And the JSON node "data.typeCreateInput.inputFields" should have 3 elements - And the JSON node "data.typeCreateInput.inputFields[0].name" should be equal to "bar" - And the JSON node "data.typeCreateInput.inputFields[1].name" should be equal to "baz" - And the JSON node "data.typeCreateInput.inputFields[2].name" should be equal to "clientMutationId" - And the JSON node "data.typeCreatePayload.fields" should have 4 elements - And the JSON node "data.typeCreatePayload.fields[0].name" should be equal to "id" - And the JSON node "data.typeCreatePayload.fields[1].name" should be equal to "bar" - And the JSON node "data.typeCreatePayload.fields[2].name" should be equal to "baz" - And the JSON node "data.typeCreatePayload.fields[3].name" should be equal to "clientMutationId" - - @dropSchema - Scenario: Retrieve an item through a GraphQL query - Given there are 4 dummy objects with relatedDummy - When I send the following GraphQL request: - """ - { - dummyItem: dummy(id: "/dummies/3") { - name - relatedDummy { - id - name - __typename - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummyItem.name" should be equal to "Dummy #3" - And the JSON node "data.dummyItem.relatedDummy.name" should be equal to "RelatedDummy #3" - And the JSON node "data.dummyItem.relatedDummy.__typename" should be equal to "RelatedDummy" diff --git a/features/graphql/mutation.feature b/features/graphql/mutation.feature deleted file mode 100644 index c1859a18811..00000000000 --- a/features/graphql/mutation.feature +++ /dev/null @@ -1,266 +0,0 @@ -Feature: GraphQL mutation support - @createSchema - Scenario: Introspect types - When I send the following GraphQL request: - """ - { - __type(name: "Mutation") { - fields { - name - description - type { - name - kind - } - args { - name - type { - name - kind - } - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.__type.fields[0].name" should contain "delete" - And the JSON node "data.__type.fields[0].description" should match '/^Deletes a [A-z0-9]+\.$/' - And the JSON node "data.__type.fields[0].type.name" should match "/^delete[A-z0-9]+Payload$/" - And the JSON node "data.__type.fields[0].type.kind" should be equal to "OBJECT" - And the JSON node "data.__type.fields[0].args[0].name" should be equal to "input" - And the JSON node "data.__type.fields[0].args[0].type.name" should match "/^delete[A-z0-9]+Input$/" - And the JSON node "data.__type.fields[0].args[0].type.kind" should be equal to "INPUT_OBJECT" - - Scenario: Create an item - When I send the following GraphQL request: - """ - mutation { - createFoo(input: {name: "A new one", bar: "new", clientMutationId: "myId"}) { - id - name - bar - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.createFoo.id" should be equal to "/foos/1" - And the JSON node "data.createFoo.name" should be equal to "A new one" - And the JSON node "data.createFoo.bar" should be equal to "new" - And the JSON node "data.createFoo.clientMutationId" should be equal to "myId" - - Scenario: Create an item with a subresource - Given there are 1 dummy objects with relatedDummy - When I send the following GraphQL request: - """ - mutation { - createDummy(input: {_id: 1, name: "A dummy", foo: [], relatedDummy: "/related_dummies/1", clientMutationId: "myId"}) { - id - name - foo - relatedDummy { - name - } - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.createDummy.id" should be equal to "/dummies/2" - And the JSON node "data.createDummy.name" should be equal to "A dummy" - And the JSON node "data.createDummy.foo" should have 0 elements - And the JSON node "data.createDummy.relatedDummy.name" should be equal to "RelatedDummy #1" - And the JSON node "data.createDummy.clientMutationId" should be equal to "myId" - - Scenario: Create an item with an iterable field - When I send the following GraphQL request: - """ - mutation { - createDummy(input: {_id: 2, name: "A dummy", foo: [], jsonData: {bar:{baz:3,qux:[7.6,false,null]}}, arrayData: ["bar", "baz"], clientMutationId: "myId"}) { - id - name - foo - jsonData - arrayData - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.createDummy.id" should be equal to "/dummies/3" - And the JSON node "data.createDummy.name" should be equal to "A dummy" - And the JSON node "data.createDummy.foo" should have 0 elements - And the JSON node "data.createDummy.jsonData.bar.baz" should be equal to the number 3 - And the JSON node "data.createDummy.jsonData.bar.qux[0]" should be equal to the number 7.6 - And the JSON node "data.createDummy.jsonData.bar.qux[1]" should be false - And the JSON node "data.createDummy.jsonData.bar.qux[2]" should be null - And the JSON node "data.createDummy.arrayData[1]" should be equal to baz - And the JSON node "data.createDummy.clientMutationId" should be equal to "myId" - - Scenario: Delete an item through a mutation - When I send the following GraphQL request: - """ - mutation { - deleteFoo(input: {id: "/foos/1", clientMutationId: "anotherId"}) { - id - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.deleteFoo.id" should be equal to "/foos/1" - And the JSON node "data.deleteFoo.clientMutationId" should be equal to "anotherId" - - @dropSchema - Scenario: Delete an item with composite identifiers through a mutation - Given there are Composite identifier objects - When I send the following GraphQL request: - """ - mutation { - deleteCompositeRelation(input: {id: "/composite_relations/compositeItem=1;compositeLabel=1", clientMutationId: "myId"}) { - id - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.deleteCompositeRelation.id" should be equal to "/composite_relations/compositeItem=1;compositeLabel=1" - And the JSON node "data.deleteCompositeRelation.clientMutationId" should be equal to "myId" - - @createSchema - Scenario: Modify an item through a mutation - Given there are 1 dummy objects having each 2 relatedDummies - When I send the following GraphQL request: - """ - mutation { - updateDummy(input: {id: "/dummies/1", description: "Modified description.", dummyDate: "2018-06-05", clientMutationId: "myId"}) { - id - name - description - dummyDate - relatedDummies { - edges { - node { - name - } - } - } - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.updateDummy.id" should be equal to "/dummies/1" - And the JSON node "data.updateDummy.name" should be equal to "Dummy #1" - And the JSON node "data.updateDummy.description" should be equal to "Modified description." - And the JSON node "data.updateDummy.dummyDate" should be equal to "2018-06-05T00:00:00+00:00" - And the JSON node "data.updateDummy.relatedDummies.edges[0].node.name" should be equal to "RelatedDummy11" - And the JSON node "data.updateDummy.clientMutationId" should be equal to "myId" - - Scenario: Modify an item with composite identifiers through a mutation - Given there are Composite identifier objects - When I send the following GraphQL request: - """ - mutation { - updateCompositeRelation(input: {id: "/composite_relations/compositeItem=1;compositeLabel=2", value: "Modified value.", clientMutationId: "myId"}) { - id - value - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.updateCompositeRelation.id" should be equal to "/composite_relations/compositeItem=1;compositeLabel=2" - And the JSON node "data.updateCompositeRelation.value" should be equal to "Modified value." - And the JSON node "data.updateCompositeRelation.clientMutationId" should be equal to "myId" - - Scenario: Create an item with a custom UUID - When I send the following GraphQL request: - """ - mutation { - createWritableId(input: {_id: "c6b722fe-0331-48c4-a214-f81f9f1ca082", name: "Foo", clientMutationId: "m"}) { - id - _id - name - clientMutationId - } - } - """ - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.createWritableId.id" should be equal to "/writable_ids/c6b722fe-0331-48c4-a214-f81f9f1ca082" - And the JSON node "data.createWritableId._id" should be equal to "c6b722fe-0331-48c4-a214-f81f9f1ca082" - And the JSON node "data.createWritableId.name" should be equal to "Foo" - And the JSON node "data.createWritableId.clientMutationId" should be equal to "m" - - Scenario: Update an item with a custom UUID - When I send the following GraphQL request: - """ - mutation { - updateWritableId(input: {id: "/writable_ids/c6b722fe-0331-48c4-a214-f81f9f1ca082", _id: "f8a708b2-310f-416c-9aef-b1b5719dfa47", name: "Foo", clientMutationId: "m"}) { - id - _id - name - clientMutationId - } - } - """ - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.updateWritableId.id" should be equal to "/writable_ids/f8a708b2-310f-416c-9aef-b1b5719dfa47" - And the JSON node "data.updateWritableId._id" should be equal to "f8a708b2-310f-416c-9aef-b1b5719dfa47" - And the JSON node "data.updateWritableId.name" should be equal to "Foo" - And the JSON node "data.updateWritableId.clientMutationId" should be equal to "m" - - Scenario: Use serialization groups - Given there are 1 dummy group objects - When I send the following GraphQL request: - """ - mutation { - createDummyGroup(input: {bar: "Bar", baz: "Baz", clientMutationId: "myId"}) { - id - bar - baz - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.createDummyGroup.id" should be equal to "/dummy_groups/2" - And the JSON node "data.createDummyGroup.bar" should be equal to "Bar" - And the JSON node "data.createDummyGroup.baz" should be null - And the JSON node "data.createDummyGroup.clientMutationId" should be equal to "myId" - - @dropSchema - Scenario: Trigger a validation error - When I send the following GraphQL request: - """ - mutation { - createDummy(input: {_id: 12, name: "", foo: [], clientMutationId: "myId"}) { - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "errors[0].message" should be equal to "name: This value should not be blank." diff --git a/features/graphql/query.feature b/features/graphql/query.feature deleted file mode 100644 index 0fc7472d208..00000000000 --- a/features/graphql/query.feature +++ /dev/null @@ -1,154 +0,0 @@ -Feature: GraphQL query support - @createSchema - Scenario: Execute a basic GraphQL query - Given there are 2 dummy objects with relatedDummy - When I send the following GraphQL request: - """ - { - dummy(id: "/dummies/1") { - id - name - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummy.id" should be equal to "/dummies/1" - And the JSON node "data.dummy.name" should be equal to "Dummy #1" - - Scenario: Retrieve a Relay Node - When I send the following GraphQL request: - """ - { - node(id: "/dummies/1") { - id - ... on Dummy { - name - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.node.id" should be equal to "/dummies/1" - And the JSON node "data.node.name" should be equal to "Dummy #1" - - Scenario: Retrieve an item with an iterable field - Given there are 2 dummy objects with JSON and array data - When I send the following GraphQL request: - """ - { - dummy(id: "/dummies/3") { - id - name - jsonData - arrayData - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummy.id" should be equal to "/dummies/3" - And the JSON node "data.dummy.name" should be equal to "Dummy #1" - And the JSON node "data.dummy.jsonData.foo" should have 2 elements - And the JSON node "data.dummy.jsonData.bar" should be equal to 5 - And the JSON node "data.dummy.arrayData[2]" should be equal to baz - - Scenario: Retrieve an item through a GraphQL query with variables - When I have the following GraphQL request: - """ - query DummyWithId($itemId: ID = "/dummies/1") { - dummyItem: dummy(id: $itemId) { - id - name - relatedDummy { - id - name - } - } - } - """ - And I send the GraphQL request with variables: - """ - { - "itemId": "/dummies/2" - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummyItem.id" should be equal to "/dummies/2" - And the JSON node "data.dummyItem.name" should be equal to "Dummy #2" - And the JSON node "data.dummyItem.relatedDummy.id" should be equal to "/related_dummies/2" - And the JSON node "data.dummyItem.relatedDummy.name" should be equal to "RelatedDummy #2" - - Scenario: Run a specific operation through a GraphQL query - When I have the following GraphQL request: - """ - query DummyWithId1 { - dummyItem: dummy(id: "/dummies/1") { - name - } - } - query DummyWithId2 { - dummyItem: dummy(id: "/dummies/2") { - id - name - } - } - """ - And I send the GraphQL request with operation "DummyWithId2" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummyItem.id" should be equal to "/dummies/2" - And the JSON node "data.dummyItem.name" should be equal to "Dummy #2" - And I send the GraphQL request with operation "DummyWithId1" - And the JSON node "data.dummyItem.name" should be equal to "Dummy #1" - - Scenario: Use serialization groups - Given there are 1 dummy group objects - When I send the following GraphQL request: - """ - { - dummyGroup(id: "/dummy_groups/1") { - foo - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummyGroup.foo" should be equal to "Foo #1" - - Scenario: Fetch only the internal id - When I send the following GraphQL request: - """ - { - dummy(id: "/dummies/1") { - _id - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummy._id" should be equal to "1" - - @dropSchema - Scenario: Retrieve an nonexistent item through a GraphQL query - When I send the following GraphQL request: - """ - { - dummy(id: "/dummies/5") { - name - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.dummy" should be null diff --git a/features/hal/collection.feature b/features/hal/collection.feature deleted file mode 100644 index 845a4439e3d..00000000000 --- a/features/hal/collection.feature +++ /dev/null @@ -1,830 +0,0 @@ -Feature: HAL Collections support - In order to retrieve large collections of resources - As a client software developer - I need to retrieve paged collections respecting the HAL specification - - @createSchema - Scenario: Retrieve an empty collection - When I add "Accept" header equal to "application/hal+json" - When I send a "GET" request to "/dummies" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/dummies" - } - }, - "totalItems": 0, - "itemsPerPage": 3 - } - """ - - Scenario: Retrieve the first page of a collection - Given there are 10 dummy objects - And I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/dummies" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/dummies?page=1" - }, - "first": { - "href": "/dummies?page=1" - }, - "last": { - "href": "/dummies?page=4" - }, - "next": { - "href": "/dummies?page=2" - }, - "item": [ - { - "href": "/dummies/1" - }, - { - "href": "/dummies/2" - }, - { - "href": "/dummies/3" - } - ] - }, - "totalItems": 10, - "itemsPerPage": 3, - "_embedded": { - "item": [ - { - "_links": { - "self": { - "href": "/dummies/1" - } - }, - "description": "Smart dummy.", - "dummy": "SomeDummyTest1", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 1, - "name": "Dummy #1", - "alias": "Alias #9", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/2" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest2", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 2, - "name": "Dummy #2", - "alias": "Alias #8", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/3" - } - }, - "description": "Smart dummy.", - "dummy": "SomeDummyTest3", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 3, - "name": "Dummy #3", - "alias": "Alias #7", - "foo": null - } - ] - } - } - """ - - Scenario: Retrieve a page of a collection - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/dummies?page=3" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/dummies?page=3" - }, - "first": { - "href": "/dummies?page=1" - }, - "last": { - "href": "/dummies?page=4" - }, - "prev": { - "href": "/dummies?page=2" - }, - "next": { - "href": "/dummies?page=4" - }, - "item": [ - { - "href": "/dummies/7" - }, - { - "href": "/dummies/8" - }, - { - "href": "/dummies/9" - } - ] - }, - "totalItems": 10, - "itemsPerPage": 3, - "_embedded": { - "item": [ - { - "_links": { - "self": { - "href": "/dummies/7" - } - }, - "description": "Smart dummy.", - "dummy": "SomeDummyTest7", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 7, - "name": "Dummy #7", - "alias": "Alias #3", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/8" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest8", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 8, - "name": "Dummy #8", - "alias": "Alias #2", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/9" - } - }, - "description": "Smart dummy.", - "dummy": "SomeDummyTest9", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 9, - "name": "Dummy #9", - "alias": "Alias #1", - "foo": null - } - ] - } - } - """ - - Scenario: Retrieve the last page of a collection - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/dummies?page=4" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/dummies?page=4" - }, - "first": { - "href": "/dummies?page=1" - }, - "last": { - "href": "/dummies?page=4" - }, - "prev": { - "href": "/dummies?page=3" - }, - "item": [ - { - "href": "/dummies/10" - } - ] - }, - "totalItems": 10, - "itemsPerPage": 3, - "_embedded": { - "item": [ - { - "_links": { - "self": { - "href": "/dummies/10" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest10", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 10, - "name": "Dummy #10", - "alias": "Alias #0", - "foo": null - } - ] - } - } - """ - - Scenario: Enable the partial pagination client side - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/dummies?page=2&partial=1" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/dummies?partial=1&page=2" - }, - "prev": { - "href": "/dummies?partial=1&page=1" - }, - "next": { - "href": "/dummies?partial=1&page=3" - }, - "item": [ - { - "href": "/dummies/4" - }, - { - "href": "/dummies/5" - }, - { - "href": "/dummies/6" - } - ] - }, - "itemsPerPage": 3, - "_embedded": { - "item": [ - { - "_links": { - "self": { - "href": "/dummies/4" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest4", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 4, - "name": "Dummy #4", - "alias": "Alias #6", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/5" - } - }, - "description": "Smart dummy.", - "dummy": "SomeDummyTest5", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 5, - "name": "Dummy #5", - "alias": "Alias #5", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/6" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest6", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 6, - "name": "Dummy #6", - "alias": "Alias #4", - "foo": null - } - ] - } - } - """ - - Scenario: Disable the pagination client side - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/dummies?pagination=0" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/dummies?pagination=0" - }, - "item": [ - { - "href": "/dummies/1" - }, - { - "href": "/dummies/2" - }, - { - "href": "/dummies/3" - }, - { - "href": "/dummies/4" - }, - { - "href": "/dummies/5" - }, - { - "href": "/dummies/6" - }, - { - "href": "/dummies/7" - }, - { - "href": "/dummies/8" - }, - { - "href": "/dummies/9" - }, - { - "href": "/dummies/10" - } - ] - }, - "totalItems": 10, - "_embedded": { - "item": [ - { - "_links": { - "self": { - "href": "/dummies/1" - } - }, - "description": "Smart dummy.", - "dummy": "SomeDummyTest1", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 1, - "name": "Dummy #1", - "alias": "Alias #9", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/2" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest2", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 2, - "name": "Dummy #2", - "alias": "Alias #8", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/3" - } - }, - "description": "Smart dummy.", - "dummy": "SomeDummyTest3", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 3, - "name": "Dummy #3", - "alias": "Alias #7", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/4" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest4", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 4, - "name": "Dummy #4", - "alias": "Alias #6", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/5" - } - }, - "description": "Smart dummy.", - "dummy": "SomeDummyTest5", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 5, - "name": "Dummy #5", - "alias": "Alias #5", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/6" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest6", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 6, - "name": "Dummy #6", - "alias": "Alias #4", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/7" - } - }, - "description": "Smart dummy.", - "dummy": "SomeDummyTest7", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 7, - "name": "Dummy #7", - "alias": "Alias #3", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/8" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest8", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 8, - "name": "Dummy #8", - "alias": "Alias #2", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/9" - } - }, - "description": "Smart dummy.", - "dummy": "SomeDummyTest9", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 9, - "name": "Dummy #9", - "alias": "Alias #1", - "foo": null - }, - { - "_links": { - "self": { - "href": "/dummies/10" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest10", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 10, - "name": "Dummy #10", - "alias": "Alias #0", - "foo": null - } - ] - } - } - """ - - Scenario: Change the number of element by page client side - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/dummies?page=2&itemsPerPage=1" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/dummies?itemsPerPage=1&page=2" - }, - "first": { - "href": "/dummies?itemsPerPage=1&page=1" - }, - "last": { - "href": "/dummies?itemsPerPage=1&page=10" - }, - "prev": { - "href": "/dummies?itemsPerPage=1&page=1" - }, - "next": { - "href": "/dummies?itemsPerPage=1&page=3" - }, - "item": [ - { - "href": "/dummies/2" - } - ] - }, - "totalItems": 10, - "itemsPerPage": 1, - "_embedded": { - "item": [ - { - "_links": { - "self": { - "href": "/dummies/2" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest2", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 2, - "name": "Dummy #2", - "alias": "Alias #8", - "foo": null - } - ] - } - } - """ - - Scenario: Filter with a raw URL - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/dummies?id=%2fdummies%2f8" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/dummies?id=%2Fdummies%2F8" - }, - "item": [ - { - "href": "/dummies/8" - } - ] - }, - "totalItems": 1, - "itemsPerPage": 3, - "_embedded": { - "item": [ - { - "_links": { - "self": { - "href": "/dummies/8" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest8", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 8, - "name": "Dummy #8", - "alias": "Alias #2", - "foo": null - } - ] - } -} - """ - - Scenario: Filter with non-exact match - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/dummies?name=Dummy%20%238" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/dummies?name=Dummy%20%238" - }, - "item": [ - { - "href": "/dummies/8" - } - ] - }, - "totalItems": 1, - "itemsPerPage": 3, - "_embedded": { - "item": [ - { - "_links": { - "self": { - "href": "/dummies/8" - } - }, - "description": "Not so smart dummy.", - "dummy": "SomeDummyTest8", - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 8, - "name": "Dummy #8", - "alias": "Alias #2", - "foo": null - } - ] - } - } - """ - - @dropSchema - Scenario: Allow passing 0 to `itemsPerPage` - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/dummies?itemsPerPage=0" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links":{ - "self":{ - "href":"/dummies?itemsPerPage=0" - } - }, - "totalItems":10, - "itemsPerPage":0 - } - """ diff --git a/features/hal/hal.feature b/features/hal/hal.feature deleted file mode 100644 index 933c844caed..00000000000 --- a/features/hal/hal.feature +++ /dev/null @@ -1,183 +0,0 @@ -Feature: HAL support - In order to use the HAL hypermedia format - As a client software developer - I need to be able to retrieve valid HAL responses. - - @createSchema - Scenario: Retrieve the API entrypoint - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON node "_links.self.href" should be equal to "/" - And the JSON node "_links.dummy.href" should be equal to "/dummies" - - Scenario: Create a third level - When I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/third_levels" with body: - """ - {"level": 3} - """ - Then the response status code should be 201 - - Scenario: Create a related dummy - When I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/related_dummies" with body: - """ - {"thirdLevel": "/third_levels/1"} - """ - Then the response status code should be 201 - - Scenario: Create a dummy with relations - When I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Dummy with relations", - "dummyDate": "2015-03-01T10:00:00+00:00", - "relatedDummy": "http://example.com/related_dummies/1", - "relatedDummies": [ - "/related_dummies/1" - ] - } - """ - Then the response status code should be 201 - - Scenario: Get a resource with relations - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/dummies/1" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/dummies/1" - }, - "relatedDummy": { - "href": "/related_dummies/1" - }, - "relatedDummies": [ - { - "href": "/related_dummies/1" - } - ] - }, - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": "2015-03-01T10:00:00+00:00", - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 1, - "name": "Dummy with relations", - "alias": null, - "foo": null - } - """ - - Scenario: Update a resource - When I add "Accept" header equal to "application/hal+json" - And I add "Content-Type" header equal to "application/json" - And I send a "PUT" request to "/dummies/1" with body: - """ - {"name": "A nice dummy"} - """ - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/dummies/1" - }, - "relatedDummy": { - "href": "/related_dummies/1" - }, - "relatedDummies": [ - { - "href": "/related_dummies/1" - } - ] - }, - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": "2015-03-01T10:00:00+00:00", - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "id": 1, - "name": "A nice dummy", - "alias": null, - "foo": null - } - """ - - Scenario: Embed a relation in a parent object - When I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "related": "/related_dummies/1" - } - """ - Then the response status code should be 201 - - Scenario: Get the object with the embedded relation - When I add "Accept" header equal to "application/hal+json" - And I send a "GET" request to "/relation_embedders/1" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON HAL schema - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "/relation_embedders/1" - }, - "related": { - "href": "/related_dummies/1" - } - }, - "_embedded": { - "related": { - "_links": { - "self": { - "href": "/related_dummies/1" - }, - "thirdLevel": { - "href": "/third_levels/1" - } - }, - "_embedded": { - "thirdLevel": { - "_links": { - "self": { - "href": "/third_levels/1" - } - }, - "level": 3 - } - }, - "symfony": "symfony" - } - }, - "krondstadt": "Krondstadt" - } - """ diff --git a/features/hal/max_depth.feature b/features/hal/max_depth.feature deleted file mode 100644 index 65f4e8ed021..00000000000 --- a/features/hal/max_depth.feature +++ /dev/null @@ -1,93 +0,0 @@ -Feature: Max depth handling - In order to handle MaxChildDepth resources - As a developer - I need to be able to limit their depth with @maxDepth - - @createSchema - Scenario: Create a resource with 1 level of descendants - When I add "Accept" header equal to "application/hal+json" - And I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/max_depth_dummies" with body: - """ - { - "name": "level 1", - "child": { - "name": "level 2" - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "\/max_depth_dummies\/1" - }, - "child": { - "href": "\/max_depth_dummies\/2" - } - }, - "_embedded": { - "child": { - "_links": { - "self": { - "href": "\/max_depth_dummies\/2" - } - }, - "id": 2, - "name": "level 2" - } - }, - "id": 1, - "name": "level 1" - } - """ - - @dropSchema - Scenario: Add a 2nd level of descendants - When I add "Accept" header equal to "application/hal+json" - And I add "Content-Type" header equal to "application/json" - And I send a "PUT" request to "max_depth_dummies/1" with body: - """ - { - "id": "/max_depth_dummies/1", - "child": { - "id": "/max_depth_dummies/2", - "child": { - "name": "level 3" - } - } - } - """ - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "_links": { - "self": { - "href": "\/max_depth_dummies\/1" - }, - "child": { - "href": "\/max_depth_dummies\/2" - } - }, - "_embedded": { - "child": { - "_links": { - "self": { - "href": "\/max_depth_dummies\/2" - } - }, - "id": 2, - "name": "level 2" - } - }, - "id": 1, - "name": "level 1" - } - """ diff --git a/features/hal/problem.feature b/features/hal/problem.feature deleted file mode 100644 index fe92ff4ba3f..00000000000 --- a/features/hal/problem.feature +++ /dev/null @@ -1,49 +0,0 @@ -Feature: Error handling valid according to RFC 7807 (application/problem+json) - In order to be able to handle error client side - As a client software developer - I need to retrieve an RFC 7807 compliant serialization of errors - - Scenario: Get an error - When I add "Content-Type" header equal to "application/json" - And I add "Accept" header equal to "application/json" - And I send a "POST" request to "/dummies" with body: - """ - {} - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "type": "https://tools.ietf.org/html/rfc2616#section-10", - "title": "An error occurred", - "detail": "name: This value should not be blank.", - "violations": [ - { - "propertyPath": "name", - "message": "This value should not be blank." - } - ] - } - """ - - Scenario: Get an error during deserialization of simple relation - When I add "Content-Type" header equal to "application/json" - And I add "Accept" header equal to "application/json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Foo", - "relatedDummy": { - "name": "bar" - } - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8" - And the JSON node "type" should be equal to "https://tools.ietf.org/html/rfc2616#section-10" - And the JSON node "title" should be equal to "An error occurred" - And the JSON node "detail" should be equal to 'Nested documents for attribute "relatedDummy" are not allowed. Use IRIs instead.' - And the JSON node "trace" should exist diff --git a/features/http_cache/headers.feature b/features/http_cache/headers.feature deleted file mode 100644 index 1a95d130f4d..00000000000 --- a/features/http_cache/headers.feature +++ /dev/null @@ -1,12 +0,0 @@ -Feature: Default values of HTTP cache headers - In order to make API responses cacheable - As an API software developer - I need to be able to set default cache headers values - - @createSchema - Scenario: Cache headers default value - When I send a "GET" request to "/relation_embedders" - Then the response status code should be 200 - And the header "Etag" should be equal to '"21248afbca1f242fd3009ac7cdf13293"' - And the header "Cache-Control" should be equal to "max-age=60, public, s-maxage=3600" - And the header "Vary" should be equal to "Accept, Cookie" diff --git a/features/http_cache/tags.feature b/features/http_cache/tags.feature deleted file mode 100644 index 7b4d765cdb8..00000000000 --- a/features/http_cache/tags.feature +++ /dev/null @@ -1,105 +0,0 @@ -@sqlite -Feature: Cache invalidation through HTTP Cache tags - In order to have a fast API - As an API software developer - I need to store API responses in a cache - - @createSchema - Scenario: Create some embedded resources - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "anotherRelated": { - "name": "Related", - "thirdLevel": {} - } - } - """ - Then the response status code should be 201 - And the header "Cache-Tags" should not exist - And "/relation_embedders,/related_dummies,/third_levels" IRIs should be purged - - Scenario: Tags must be set for items - When I send a "GET" request to "/relation_embedders/1" - Then the response status code should be 200 - And the header "Cache-Tags" should be equal to "/relation_embedders/1,/related_dummies/1,/third_levels/1" - - Scenario: Create some more resources - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "anotherRelated": { - "name": "Another Related", - "thirdLevel": {} - } - } - """ - Then the response status code should be 201 - And the header "Cache-Tags" should not exist - - Scenario: Tags must be set for collections - When I send a "GET" request to "/relation_embedders" - Then the response status code should be 200 - And the header "Cache-Tags" should be equal to "/relation_embedders/1,/related_dummies/1,/third_levels/1,/relation_embedders/2,/related_dummies/2,/third_levels/2,/relation_embedders" - - Scenario: Purge item on update - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/relation_embedders/1" with body: - """ - { - "paris": "France" - } - """ - Then the response status code should be 200 - And the header "Cache-Tags" should not exist - And "/relation_embedders,/relation_embedders/1,/related_dummies/1" IRIs should be purged - - Scenario: Purge item and the related collection on update - When I add "Content-Type" header equal to "application/ld+json" - And I send a "DELETE" request to "/relation_embedders/1" - Then the response status code should be 204 - And the header "Cache-Tags" should not exist - And "/relation_embedders,/relation_embedders/1,/related_dummies/1" IRIs should be purged - - Scenario: Create two Relation2 - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation2s" with body: - """ - { - } - """ - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation2s" with body: - """ - { - } - """ - Then the response status code should be 201 - - Scenario: Embedded collection must be listed in cache tags - When I send a "GET" request to "/relation2s/1" - Then the header "Cache-Tags" should be equal to "/relation2s/1" - - Scenario: Create a Relation1 - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation1s" with body: - """ - { - "relation2": "/relation2s/1" - } - """ - Then the response status code should be 201 - And "/relation1s,/relation2s/1" IRIs should be purged - - Scenario: Update a Relation1 - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/relation1s/1" with body: - """ - { - "relation2": "/relation2s/2" - } - """ - Then the response status code should be 200 - And "/relation1s,/relation1s/1,/relation2s/2,/relation2s/1" IRIs should be purged diff --git a/features/hydra/collection.feature b/features/hydra/collection.feature deleted file mode 100644 index 3ddd6787e71..00000000000 --- a/features/hydra/collection.feature +++ /dev/null @@ -1,437 +0,0 @@ -Feature: Collections support - In order to retrieve large collections of resources - As a client software developer - I need to retrieve paged collections respecting the Hydra specification - - @createSchema - Scenario: Retrieve an empty collection - When I send a "GET" request to "/dummies" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 0}, - "hydra:member": { - "type": "array", - "maxItems": 0 - }, - "hydra:search": {} - }, - "additionalProperties": false - } - """ - - Scenario: Retrieve the first page of a collection - Given there are 30 dummy objects - And I send a "GET" request to "/dummies" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 30}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/1$"}, - {"pattern": "^/dummies/2$"}, - {"pattern": "^/dummies/3$"} - ] - } - } - }, - "maxItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"}, - "hydra:first": {"pattern": "^/dummies\\?page=1$"}, - "hydra:last": {"pattern": "^/dummies\\?page=10$"}, - "hydra:next": {"pattern": "^/dummies\\?page=2$"} - } - } - } - } - """ - - Scenario: Retrieve a page of a collection - When I send a "GET" request to "/dummies?page=7" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 30}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/19$"}, - {"pattern": "^/dummies/20$"}, - {"pattern": "^/dummies/21$"} - ] - } - } - }, - "maxItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?page=7$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"}, - "hydra:first": {"pattern": "^/dummies\\?page=1$"}, - "hydra:last": {"pattern": "^/dummies\\?page=10$"}, - "hydra:next": {"pattern": "^/dummies\\?page=8$"}, - "hydra:previous": {"pattern": "^/dummies\\?page=6$"} - } - } - } - } - """ - - Scenario: Retrieve the last page of a collection - When I send a "GET" request to "/dummies?page=10" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 30}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/28$"}, - {"pattern": "^/dummies/29$"}, - {"pattern": "^/dummies/30$"} - ] - } - } - }, - "maxItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?page=10$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"}, - "hydra:first": {"pattern": "^/dummies\\?page=1$"}, - "hydra:last": {"pattern": "^/dummies\\?page=10$"}, - "hydra:previous": {"pattern": "^/dummies\\?page=9$"} - } - }, - "hydra:search": {} - }, - "additionalProperties": false - } - """ - - Scenario: Enable the partial pagination client side - When I send a "GET" request to "/dummies?page=7&partial=1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 30}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": { - "oneOf": [ - {"pattern": "^/dummies/19$"}, - {"pattern": "^/dummies/20$"}, - {"pattern": "^/dummies/21$"} - ] - } - } - }, - "maxItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?partial=1&page=7$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"}, - "hydra:next": {"pattern": "^/dummies\\?partial=1&page=8$"}, - "hydra:previous": {"pattern": "^/dummies\\?partial=1&page=6$"} - }, - "additionalProperties": false - } - } - } - """ - - Scenario: Disable the pagination client side - When I send a "GET" request to "/dummies?pagination=0" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "minimum": 30}, - "hydra:member": { - "type": "array", - "minItems": 30 - }, - "hydra:search": {} - }, - "additionalProperties": false - } - """ - - Scenario: Change the number of element by page client side - When I send a "GET" request to "/dummies?page=2&itemsPerPage=10" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 30}, - "hydra:member": { - "type": "array", - "minItems": 10, - "maxItems": 10 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?itemsPerPage=10&page=2$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"}, - "hydra:first": {"pattern": "^/dummies\\?itemsPerPage=10&page=1$"}, - "hydra:last": {"pattern": "^/dummies\\?itemsPerPage=10&page=3$"}, - "hydra:previous": {"pattern": "^/dummies\\?itemsPerPage=10&page=1$"}, - "hydra:next": {"pattern": "^/dummies\\?itemsPerPage=10&page=3$"} - } - }, - "hydra:search": {} - }, - "additionalProperties": false - } - """ - - Scenario: Test presence of next - When I send a "GET" request to "/dummies?page=3" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "@id":"/dummies?page=3", - "@type":"hydra:PartialCollectionView", - "hydra:first":"/dummies?page=1", - "hydra:last":"/dummies?page=10", - "hydra:previous":"/dummies?page=2", - "hydra:next":"/dummies?page=4" - } - """ - Scenario: Filter with exact match - When I send a "GET" request to "/dummies?id=8" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 1}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies/8$"} - } - }, - "maxItems": 1 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?id=8$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - }, - "hydra:search": {} - }, - "additionalProperties": false - } - """ - - Scenario: Filter with a raw URL - When I send a "GET" request to "/dummies?id=%2fdummies%2f8" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 1}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies/8$"} - } - }, - "maxItems": 1 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?id=%2Fdummies%2F8$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - }, - "hydra:search": {} - }, - "additionalProperties": false - } - """ - - Scenario: Filter with non-exact match - When I send a "GET" request to "/dummies?name=Dummy%20%238" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 1}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies/8$"} - } - }, - "maxItems": 1 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?name=Dummy%20%238$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - }, - "hydra:search": {} - }, - "additionalProperties": false - } - """ - - @createSchema - Scenario: Allow passing 0 to `itemsPerPage` - When I send a "GET" request to "/dummies?itemsPerPage=0" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 30}, - "hydra:member": { - "type": "array", - "minItems": 0, - "maxItems": 0 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?itemsPerPage=0$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"}, - "hydra:first": {"pattern": "^/dummies\\?itemsPerPage=0&page=1$"}, - "hydra:last": {"pattern": "^/dummies\\?itemsPerPage=0&page=1$"}, - "hydra:previous": {"pattern": "^/dummies\\?itemsPerPage=0&page=1$"}, - "hydra:next": {"pattern": "^/dummies\\?itemsPerPage=0&page=1$"} - } - }, - "hydra:search": {} - }, - "additionalProperties": false - } - """ - - When I send a "GET" request to "/dummies?itemsPerPage=0&page=2" - Then the response status code should be 400 - And the JSON node "hydra:description" should be equal to "Page should not be greater than 1 if itemsPerPage is equal to 0" diff --git a/features/hydra/docs.feature b/features/hydra/docs.feature deleted file mode 100644 index 62b85555644..00000000000 --- a/features/hydra/docs.feature +++ /dev/null @@ -1,90 +0,0 @@ -Feature: Documentation support - In order to build an auto-discoverable API - As a client software developer - I need to know Hydra specifications of objects I send and receive - - Scenario: Checks that the Link pointing to the Hydra documentation is set - Given I send a "GET" request to "/" - Then the header "Link" should be equal to '; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"' - - Scenario: Retrieve the API vocabulary - Given I send a "GET" request to "/docs.jsonld" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - # Context - And the JSON node "@context.@vocab" should be equal to "http://example.com/docs.jsonld#" - And the JSON node "@context.hydra" should be equal to "http://www.w3.org/ns/hydra/core#" - And the JSON node "@context.rdf" should be equal to "http://www.w3.org/1999/02/22-rdf-syntax-ns#" - And the JSON node "@context.rdfs" should be equal to "http://www.w3.org/2000/01/rdf-schema#" - And the JSON node "@context.xmls" should be equal to "http://www.w3.org/2001/XMLSchema#" - And the JSON node "@context.owl" should be equal to "http://www.w3.org/2002/07/owl#" - And the JSON node "@context.domain.@id" should be equal to "rdfs:domain" - And the JSON node "@context.domain.@type" should be equal to "@id" - And the JSON node "@context.range.@id" should be equal to "rdfs:range" - And the JSON node "@context.range.@type" should be equal to "@id" - And the JSON node "@context.subClassOf.@id" should be equal to "rdfs:subClassOf" - And the JSON node "@context.subClassOf.@type" should be equal to "@id" - And the JSON node "@context.expects.@id" should be equal to "hydra:expects" - And the JSON node "@context.expects.@type" should be equal to "@id" - And the JSON node "@context.returns.@id" should be equal to "hydra:returns" - And the JSON node "@context.returns.@type" should be equal to "@id" - # Root properties - And the JSON node "@id" should be equal to "/docs.jsonld" - And the JSON node "hydra:title" should be equal to "My Dummy API" - And the JSON node "hydra:description" should be equal to "This is a test API." - And the JSON node "hydra:entrypoint" should be equal to "/" - # Supported classes - And the Hydra class "The API entrypoint" exists - And the Hydra class "A constraint violation" exists - And the Hydra class "A constraint violation list" exists - And the Hydra class "CircularReference" exists - And the Hydra class "CustomIdentifierDummy" exists - And the Hydra class "CustomNormalizedDummy" exists - And the Hydra class "CustomWritableIdentifierDummy" exists - And the Hydra class "Dummy" exists - And the Hydra class "RelatedDummy" exists - And the Hydra class "RelationEmbedder" exists - And the Hydra class "ThirdLevel" exists - And the Hydra class "ParentDummy" doesn't exist - And the Hydra class "UnknownDummy" doesn't exist - # Doc - And the value of the node "@id" of the Hydra class "Dummy" is "#Dummy" - And the value of the node "@type" of the Hydra class "Dummy" is "hydra:Class" - And the value of the node "rdfs:label" of the Hydra class "Dummy" is "Dummy" - And the value of the node "hydra:title" of the Hydra class "Dummy" is "Dummy" - And the value of the node "hydra:description" of the Hydra class "Dummy" is "Dummy." - # Properties - And "id" property is readable for Hydra class "Dummy" - And "id" property is writable for Hydra class "Dummy" - And "name" property is readable for Hydra class "Dummy" - And "name" property is writable for Hydra class "Dummy" - And "name" property is required for Hydra class "Dummy" - And "plainPassword" property is not readable for Hydra class "User" - And "plainPassword" property is writable for Hydra class "User" - And "plainPassword" property is not required for Hydra class "User" - And the value of the node "@type" of the property "name" of the Hydra class "Dummy" is "hydra:SupportedProperty" - And the value of the node "hydra:property.@id" of the property "name" of the Hydra class "Dummy" is "http://schema.org/name" - And the value of the node "hydra:property.@type" of the property "name" of the Hydra class "Dummy" is "rdf:Property" - And the value of the node "hydra:property.rdfs:label" of the property "name" of the Hydra class "Dummy" is "name" - And the value of the node "hydra:property.domain" of the property "name" of the Hydra class "Dummy" is "#Dummy" - And the value of the node "hydra:property.range" of the property "name" of the Hydra class "Dummy" is "xmls:string" - And the value of the node "hydra:property.range" of the property "relatedDummy" of the Hydra class "Dummy" is "https://schema.org/Product" - And the value of the node "hydra:property.range" of the property "relatedDummies" of the Hydra class "Dummy" is "https://schema.org/Product" - And the value of the node "hydra:title" of the property "name" of the Hydra class "Dummy" is "name" - And the value of the node "hydra:description" of the property "name" of the Hydra class "Dummy" is "The dummy name" - # Operations - And the value of the node "@type" of the operation "GET" of the Hydra class "Dummy" contains "hydra:Operation" - And the value of the node "@type" of the operation "GET" of the Hydra class "Dummy" contains "schema:FindAction" - And the value of the node "hydra:method" of the operation "GET" of the Hydra class "Dummy" is "GET" - And the value of the node "hydra:title" of the operation "GET" of the Hydra class "Dummy" is "Retrieves Dummy resource." - And the value of the node "rdfs:label" of the operation "GET" of the Hydra class "Dummy" is "Retrieves Dummy resource." - And the value of the node "returns" of the operation "GET" of the Hydra class "Dummy" is "#Dummy" - And the value of the node "hydra:title" of the operation "PUT" of the Hydra class "Dummy" is "Replaces the Dummy resource." - And the value of the node "hydra:title" of the operation "DELETE" of the Hydra class "Dummy" is "Deletes the Dummy resource." - And the value of the node "returns" of the operation "DELETE" of the Hydra class "Dummy" is "owl:Nothing" - # Deprecations - And the boolean value of the node "owl:deprecated" of the Hydra class "DeprecatedResource" is true - And the boolean value of the node "owl:deprecated" of the property "deprecatedField" of the Hydra class "DeprecatedResource" is true - And the boolean value of the node "owl:deprecated" of the property "The collection of DeprecatedResource resources" of the Hydra class "The API entrypoint" is true - And the boolean value of the node "owl:deprecated" of the operation "GET" of the Hydra class "DeprecatedResource" is true diff --git a/features/hydra/entrypoint.feature b/features/hydra/entrypoint.feature deleted file mode 100644 index 7fd1b631137..00000000000 --- a/features/hydra/entrypoint.feature +++ /dev/null @@ -1,28 +0,0 @@ -Feature: Entrypoint support - In order to build an auto-discoverable API - As a client software developer - I need to access to an entrypoint listing top-level resources - - Scenario: Retrieve the Entrypoint - When I send a "GET" request to "/" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "@context" should be equal to "/contexts/Entrypoint" - And the JSON node "@id" should be equal to "/" - And the JSON node "@type" should be equal to "Entrypoint" - And the JSON node "abstractDummy" should be equal to "/abstract_dummies" - And the JSON node "circularReference" should be equal to "/circular_references" - And the JSON node "compositeItem" should be equal to "/composite_items" - And the JSON node "compositeLabel" should be equal to "/composite_labels" - And the JSON node "compositeRelation" should be equal to "/composite_relations" - And the JSON node "concreteDummy" should be equal to "/concrete_dummies" - And the JSON node "customIdentifierDummy" should be equal to "/custom_identifier_dummies" - And the JSON node "customNormalizedDummy" should be equal to "/custom_normalized_dummies" - And the JSON node "customWritableIdentifierDummy" should be equal to "/custom_writable_identifier_dummies" - And the JSON node "dummy" should be equal to "/dummies" - And the JSON node "relatedDummy" should be equal to "/related_dummies" - And the JSON node "relationEmbedder" should be equal to "/relation_embedders" - And the JSON node "thirdLevel" should be equal to "/third_levels" - And the JSON node "user" should be equal to "/users" - And the JSON node "fileconfigdummy" should be equal to "/fileconfigdummies" diff --git a/features/hydra/error.feature b/features/hydra/error.feature deleted file mode 100644 index 9ec12d469fa..00000000000 --- a/features/hydra/error.feature +++ /dev/null @@ -1,140 +0,0 @@ -Feature: Error handling - In order to be able to handle error client side - As a client software developer - I need to retrieve an Hydra serialization of errors - - Scenario: Get an error - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - {} - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/ConstraintViolationList", - "@type": "ConstraintViolationList", - "hydra:title": "An error occurred", - "hydra:description": "name: This value should not be blank.", - "violations": [ - { - "propertyPath": "name", - "message": "This value should not be blank." - } - ] - } - """ - - Scenario: Get an error during deserialization of simple relation - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Foo", - "relatedDummy": { - "name": "bar" - } - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "@context" should be equal to "/contexts/Error" - And the JSON node "@type" should be equal to "hydra:Error" - And the JSON node "hydra:title" should be equal to "An error occurred" - And the JSON node "hydra:description" should be equal to 'Nested documents for attribute "relatedDummy" are not allowed. Use IRIs instead.' - And the JSON node "trace" should exist - - Scenario: Get an error during deserialization of collection - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Foo", - "relatedDummies": [{ - "name": "bar" - }] - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "@context" should be equal to "/contexts/Error" - And the JSON node "@type" should be equal to "hydra:Error" - And the JSON node "hydra:title" should be equal to "An error occurred" - And the JSON node "hydra:description" should be equal to 'Nested documents for attribute "relatedDummies" are not allowed. Use IRIs instead.' - And the JSON node "trace" should exist - - Scenario: Get an error because of an invalid JSON - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Foo", - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "@context" should be equal to "/contexts/Error" - And the JSON node "@type" should be equal to "hydra:Error" - And the JSON node "hydra:title" should be equal to "An error occurred" - And the JSON node "hydra:description" should exist - And the JSON node "trace" should exist - - Scenario: Get an error during update of an existing resource with a non-allowed update operation - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "@id": "/dummies/1", - "name": "Foo" - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "@context" should be equal to "/contexts/Error" - And the JSON node "@type" should be equal to "hydra:Error" - And the JSON node "hydra:title" should be equal to "An error occurred" - And the JSON node "hydra:description" should be equal to "Update is not allowed for this operation." - And the JSON node "trace" should exist - - @createSchema - Scenario: Populate database with related dummies. Check that id will be "/related_dummies/1" - Given I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/related_dummies" with body: - """ - { - "@type": "https://schema.org/Product", - "symfony": "laravel" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the JSON node "@id" should be equal to "/related_dummies/1" - And the JSON node "symfony" should be equal to "laravel" - - Scenario: Do not get an error during update of an existing relation with a non-allowed update operation - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "anotherRelated": { - "@id": "/related_dummies/1", - "@type": "https://schema.org/Product", - "symfony": "phalcon" - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "@context" should be equal to "/contexts/RelationEmbedder" - And the JSON node "@type" should be equal to "RelationEmbedder" - And the JSON node "@id" should be equal to "/relation_embedders/1" - And the JSON node "anotherRelated.@id" should be equal to "/related_dummies/1" - And the JSON node "anotherRelated.symfony" should be equal to "phalcon" diff --git a/features/integration/fos_user.feature b/features/integration/fos_user.feature deleted file mode 100644 index 82dc25441d8..00000000000 --- a/features/integration/fos_user.feature +++ /dev/null @@ -1,36 +0,0 @@ -Feature: FOSUser integration - In order to use FOSUserBundle - As an API software developer - I need to be able manage users - - @createSchema - Scenario: Create a user - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/users" with body: - """ - { - "fullname": "Dummy User", - "username": "dummy.user", - "email": "dummy.user@example.com", - "plainPassword": "azerty" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/User", - "@id": "/users/1", - "@type": "User", - "email": "dummy.user@example.com", - "fullname": "Dummy User", - "username": "dummy.user" - } - """ - And the password "azerty" for user 1 should be hashed - - Scenario: Delete a user - When I send a "DELETE" request to "/users/1" - Then the response status code should be 204 diff --git a/features/integration/nelmio_api_doc.feature b/features/integration/nelmio_api_doc.feature deleted file mode 100644 index 385474eaf4f..00000000000 --- a/features/integration/nelmio_api_doc.feature +++ /dev/null @@ -1,16 +0,0 @@ -Feature: NelmioApiDoc integration - In order to use NelmioApiDocBundle - As an API software developer - I need to see the generated documentation - - Scenario: Test if the NelmioApiDoc integration works - When I send a "GET" request to "/nelmioapidoc" - Then the response status code should be 200 - And I should see text matching "AbstractDummy" - And I should see text matching "Dummy" - And I should see text matching "User" - And I should see text matching "Retrieves the collection of Dummy resources." - And I should see text matching "Creates a Dummy resource." - And I should see text matching "Deletes the Dummy resource." - And I should see text matching "Updates the Dummy resource." - And I should see text matching "Replaces the Dummy resource." diff --git a/features/jsonapi/collection_attributes.feature b/features/jsonapi/collection_attributes.feature deleted file mode 100644 index 5a506c56a31..00000000000 --- a/features/jsonapi/collection_attributes.feature +++ /dev/null @@ -1,45 +0,0 @@ -Feature: JSON API collections support - In order to use the JSON API hypermedia format - As a client software developer - I need to be able to retrieve valid JSON API responses for collection attributes on entities. - - Background: - Given I add "Accept" header equal to "application/vnd.api+json" - And I add "Content-Type" header equal to "application/vnd.api+json" - - @createSchema - Scenario: Correctly serialize a collection - Given there is a CircularReference - When I send a "GET" request to "/circular_references/1" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be equal to: - """ - { - "data": { - "id": "/circular_references/1", - "type": "CircularReference", - "relationships": { - "parent": { - "data": { - "type": "CircularReference", - "id": "/circular_references/1" - } - }, - "children": { - "data": [ - { - "type": "CircularReference", - "id": "/circular_references/1" - }, - { - "type": "CircularReference", - "id": "/circular_references/2" - } - ] - } - } - } - } - """ diff --git a/features/jsonapi/errors.feature b/features/jsonapi/errors.feature deleted file mode 100644 index b08343e74dd..00000000000 --- a/features/jsonapi/errors.feature +++ /dev/null @@ -1,73 +0,0 @@ -Feature: JSON API error handling - In order to be able to handle error client side - As a client software developer - I need to retrieve an JSON API serialization of errors - - Background: - Given I add "Accept" header equal to "application/vnd.api+json" - And I add "Content-Type" header equal to "application/vnd.api+json" - - @createSchema - Scenario: Get a validation error on an attribute - When I send a "POST" request to "/dummies" with body: - """ - { - "data": { - "type": "dummy", - "attributes": {} - } - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be equal to: - """ - { - "errors": [ - { - "detail": "This value should not be blank.", - "source": { - "pointer": "data\/attributes\/name" - } - } - ] - } - """ - - Scenario: Get a validation error on an relationship - Given there is a RelatedDummy - And there is a DummyFriend - When I send a "POST" request to "/related_to_dummy_friends" with body: - """ - { - "data": { - "type": "RelatedToDummyFriend", - "attributes": { - "name": "Related to dummy friend" - } - } - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be equal to: - """ - { - "errors": [ - { - "detail": "This value should not be null.", - "source": { - "pointer": "data\/relationships\/dummyFriend" - } - }, - { - "detail": "This value should not be null.", - "source": { - "pointer": "data\/relationships\/relatedDummy" - } - } - ] - } - """ diff --git a/features/jsonapi/filtering.feature b/features/jsonapi/filtering.feature deleted file mode 100644 index fd00f6a35fe..00000000000 --- a/features/jsonapi/filtering.feature +++ /dev/null @@ -1,43 +0,0 @@ -Feature: JSON API filter handling - In order to be able to handle filtering - As a client software developer - I need to be able to specify filtering parameters according to JSON API recommendation - - Background: - Given I add "Accept" header equal to "application/vnd.api+json" - And I add "Content-Type" header equal to "application/vnd.api+json" - - @createSchema - Scenario: Apply filters based on the 'filter' query parameter with 'my' as value - Given there are 30 dummy objects with dummyDate - When I send a "GET" request to "/dummies?filter[name]=my" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data" should have 3 elements - - Scenario: Apply filters based on the 'filter' query parameter with 'foo' as value - When I send a "GET" request to "/dummies?filter[name]=foo" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data" should have 0 elements - - Scenario: Apply property filter based on the 'fields' - Given there are 2 dummy property objects - When I send a "GET" request to "/dummy_properties?fields[DummyProperty]=id,foo,bar" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data" should have 2 elements - And the JSON node "data[0].attributes._id" should be equal to "1" - And the JSON node "data[0].attributes.foo" should be equal to "Foo #1" - And the JSON node "data[0].attributes.bar" should be equal to "Bar #1" - And the JSON node "data[0].attributes.group" should not exist - - Scenario: Apply filters based on the 'filter' query parameter with second level arguments - When I send a "GET" request to "/dummies?filter[dummyDate][after]=2015-04-28" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data" should have 2 elements diff --git a/features/jsonapi/jsonapi.feature b/features/jsonapi/jsonapi.feature deleted file mode 100644 index 6195854a906..00000000000 --- a/features/jsonapi/jsonapi.feature +++ /dev/null @@ -1,254 +0,0 @@ -Feature: JSON API basic support - In order to use the JSON API hypermedia format - As a client software developer - I need to be able to retrieve valid JSON API responses. - - Background: - Given I add "Accept" header equal to "application/vnd.api+json" - And I add "Content-Type" header equal to "application/vnd.api+json" - - @createSchema - Scenario: Retrieve the API entrypoint - When I send a "GET" request to "/" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/vnd.api+json; charset=utf-8" - And the JSON node "links.self" should be equal to "http://example.com/" - And the JSON node "links.dummy" should be equal to "http://example.com/dummies" - - Scenario: Test empty list against JSON API schema - When I send a "GET" request to "/dummies" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data" should be an empty array - - Scenario: Create a ThirdLevel - When I send a "POST" request to "/third_levels" with body: - """ - { - "data": { - "type": "third-level", - "attributes": { - "level": 3 - } - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data.id" should not be an empty string - - Scenario: Retrieve the collection - When I send a "GET" request to "/third_levels" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - - Scenario: Retrieve the third level - When I send a "GET" request to "/third_levels/1" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - - Scenario: Create a related dummy - When I send a "POST" request to "/related_dummies" with body: - """ - { - "data": { - "type": "related-dummy", - "attributes": { - "name": "John Doe", - "age": 23 - }, - "relationships": { - "thirdLevel": { - "data": { - "type": "third-level", - "id": "/third_levels/1" - } - } - } - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data.id" should not be an empty string - And the JSON node "data.attributes.name" should be equal to "John Doe" - And the JSON node "data.attributes.age" should be equal to the number 23 - - Scenario: Create a dummy with relations - Given there is a RelatedDummy - When I send a "POST" request to "/dummies" with body: - """ - { - "data": { - "type": "dummy", - "attributes": { - "name": "Dummy with relations", - "dummyDate": "2015-03-01T10:00:00+00:00" - }, - "relationships": { - "relatedDummy": { - "data": { - "type": "related-dummy", - "id": "/related_dummies/2" - } - }, - "relatedDummies": { - "data": [ - { - "type": "related-dummy", - "id": "/related_dummies/1" - }, - { - "type": "related-dummy", - "id": "/related_dummies/2" - } - ] - } - } - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data.relationships.relatedDummies.data" should have 2 elements - And the JSON node "data.relationships.relatedDummy.data.id" should be equal to "/related_dummies/2" - - Scenario: Update a resource with a many-to-many relationship via PATCH - When I send a "PATCH" request to "/dummies/1" with body: - """ - { - "data": { - "type": "dummy", - "relationships": { - "relatedDummy": { - "data": { - "type": "related-dummy", - "id": "/related_dummies/1" - } - }, - "relatedDummies": { - "data": [ - { - "type": "related-dummy", - "id": "/related_dummies/2" - } - ] - } - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data.relationships.relatedDummies.data" should have 1 elements - And the JSON node "data.relationships.relatedDummy.data.id" should be equal to "/related_dummies/1" - - Scenario: Create a related dummy with an empty relationship - When I send a "POST" request to "/related_dummies" with body: - """ - { - "data": { - "type": "related-dummy", - "attributes": { - "name": "John Doe" - }, - "relationships": { - "thirdLevel": { - "data": null - } - } - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - - Scenario: Retrieve a collection with relationships - When I send a "GET" request to "/related_dummies" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data[0].relationships.thirdLevel.data.id" should be equal to "/third_levels/1" - - Scenario: Retrieve the related dummy - When I send a "GET" request to "/related_dummies/1" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be equal to: - """ - { - "data": { - "id": "/related_dummies/1", - "type": "RelatedDummy", - "attributes": { - "_id": 1, - "name": "John Doe", - "symfony": "symfony", - "dummyDate": null, - "dummyBoolean": null, - "embeddedDummy": [], - "age": 23 - }, - "relationships": { - "thirdLevel": { - "data": { - "type": "ThirdLevel", - "id": "/third_levels/1" - } - } - } - } - } - """ - - Scenario: Update a resource via PATCH - When I send a "PATCH" request to "/related_dummies/1" with body: - """ - { - "data": { - "type": "related-dummy", - "attributes": { - "name": "Jane Doe" - } - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data.id" should not be an empty string - And the JSON node "data.attributes.name" should be equal to "Jane Doe" - And the JSON node "data.attributes.age" should be equal to the number 23 - - Scenario: Embed a relation in a parent object - When I send a "POST" request to "/relation_embedders" with body: - """ - { - "data": { - "relationships": { - "related": { - "data": { - "type": "related-dummy", - "id": "/related_dummies/1" - } - } - } - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON node "data.id" should not be an empty string - And the JSON node "data.attributes.krondstadt" should be equal to "Krondstadt" - And the JSON node "data.relationships.related.data.id" should be equal to "/related_dummies/1" diff --git a/features/jsonapi/ordering.feature b/features/jsonapi/ordering.feature deleted file mode 100644 index b3d4f687a3c..00000000000 --- a/features/jsonapi/ordering.feature +++ /dev/null @@ -1,144 +0,0 @@ -Feature: JSON API order handling - In order to be able to handle ordering - As a client software developer - I need to be able to specify ordering parameters according to JSON API recommendation - - Background: - Given I add "Content-Type" header equal to "application/vnd.api+json" - And I add "Accept" header equal to "application/vnd.api+json" - - @createSchema - Scenario: Get collection ordered in ascending order on an integer property and on which order filter has been enabled in whitelist mode - Given there are 30 dummy objects - When I send a "GET" request to "/dummies?sort=id" - Then the response status code should be 200 - And the JSON should be valid according to the JSON API schema - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "_id": { - "type": "string", - "pattern": "^1$" - } - } - }, - { - "type": "object", - "properties": { - "_id": { - "type": "string", - "pattern": "^2$" - } - } - }, - { - "type": "object", - "properties": { - "_id": { - "type": "string", - "pattern": "^3$" - } - } - } - ] - } - } - } - """ - - Scenario: Get collection ordered in descending order on an integer property and on which order filter has been enabled in whitelist mode - When I send a "GET" request to "/dummies?sort=-id" - Then the response status code should be 200 - And the JSON should be valid according to the JSON API schema - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "_id": { - "type": "string", - "pattern": "^30$" - } - } - }, - { - "type": "object", - "properties": { - "_id": { - "type": "string", - "pattern": "^29$" - } - } - }, - { - "type": "object", - "properties": { - "_id": { - "type": "string", - "pattern": "^28$" - } - } - } - ] - } - } - } - """ - - Scenario: Get collection ordered on two properties previously whitelisted - When I send a "GET" request to "/dummies?sort=description,-id" - Then the JSON should be valid according to the JSON API schema - Then the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "_id": { - "type": "string", - "pattern": "^30$" - } - } - }, - { - "type": "object", - "properties": { - "_id": { - "type": "string", - "pattern": "^28$" - } - } - }, - { - "type": "object", - "properties": { - "_id": { - "type": "string", - "pattern": "^26$" - } - } - } - ] - } - } - } - """ diff --git a/features/jsonapi/pagination.feature b/features/jsonapi/pagination.feature deleted file mode 100644 index d07aacf3b48..00000000000 --- a/features/jsonapi/pagination.feature +++ /dev/null @@ -1,38 +0,0 @@ -Feature: JSON API pagination handling - In order to be able to handle pagination - As a client software developer - I need to retrieve an JSON API pagination information as metadata and links - - Background: - Given I add "Accept" header equal to "application/vnd.api+json" - And I add "Content-Type" header equal to "application/vnd.api+json" - - @createSchema - Scenario: Get the first page of a paginated collection according to basic config - Given there are 10 dummy objects - When I send a "GET" request to "/dummies" - Then the response status code should be 200 - And the JSON should be valid according to the JSON API schema - And the JSON node "data" should have 3 elements - And the JSON node "meta.totalItems" should be equal to the number 10 - And the JSON node "meta.itemsPerPage" should be equal to the number 3 - And the JSON node "meta.currentPage" should be equal to the number 1 - - Scenario: Get the fourth page of a paginated collection according to basic config - When I send a "GET" request to "/dummies?page[page]=4" - Then the JSON should be valid according to the JSON API schema - And the JSON node "data" should have 1 elements - And the JSON node "meta.currentPage" should be equal to the number 4 - - Scenario: Get a paginated collection according to custom items per page in request - When I send a "GET" request to "/dummies?page[itemsPerPage]=15" - Then the response status code should be 200 - And the JSON should be valid according to the JSON API schema - And the JSON node "data" should have 10 elements - And the JSON node "meta.totalItems" should be equal to the number 10 - And the JSON node "meta.itemsPerPage" should be equal to the number 15 - And the JSON node "meta.currentPage" should be equal to the number 1 - - Scenario: Get an error when provided page number is not valid - When I send a "GET" request to "/dummies?page[page]=0" - Then the response status code should be 400 diff --git a/features/jsonapi/related-resouces-inclusion.feature b/features/jsonapi/related-resouces-inclusion.feature deleted file mode 100644 index 2f00db1fac5..00000000000 --- a/features/jsonapi/related-resouces-inclusion.feature +++ /dev/null @@ -1,664 +0,0 @@ -Feature: JSON API Inclusion of Related Resources - In order to be able to handle inclusion of related resources - As a client software developer - I need to be able to specify include parameters according to JSON API recommendation - - Background: - Given I add "Accept" header equal to "application/vnd.api+json" - And I add "Content-Type" header equal to "application/vnd.api+json" - - @createSchema - Scenario: Request inclusion of a related resource (many to one) - Given there are 3 dummy property objects - When I send a "GET" request to "/dummy_properties/1?include=group" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be deep equal to: - """ - { - "data": { - "id": "/dummy_properties/1", - "type": "DummyProperty", - "attributes": { - "_id": 1, - "foo": "Foo #1", - "bar": "Bar #1", - "baz": "Baz #1" - }, - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/1" - } - } - } - }, - "included": [ - { - "id": "\/dummy_groups\/1", - "type": "DummyGroup", - "attributes": { - "_id": 1, - "foo": "Foo #1", - "bar": "Bar #1", - "baz": "Baz #1" - } - } - ] - } - """ - - Scenario: Request inclusion of a non existing related resource - When I send a "GET" request to "/dummy_properties/1?include=foo" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be deep equal to: - """ - { - "data": { - "id": "/dummy_properties/1", - "type": "DummyProperty", - "attributes": { - "_id": 1, - "foo": "Foo #1", - "bar": "Bar #1", - "baz": "Baz #1" - }, - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/1" - } - } - } - } - } - """ - - Scenario: Request inclusion of a related resource keeping main object properties unfiltered - When I send a "GET" request to "/dummy_properties/1?include=group&fields[group]=id,foo&fields[DummyProperty]=bar,baz" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be deep equal to: - """ - { - "data": { - "id": "/dummy_properties/1", - "type": "DummyProperty", - "attributes": { - "bar": "Bar #1", - "baz": "Baz #1" - }, - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/1" - } - } - } - }, - "included": [ - { - "id": "\/dummy_groups\/1", - "type": "DummyGroup", - "attributes": { - "_id": 1, - "foo": "Foo #1" - } - } - ] - } - """ - - Scenario: Request inclusion of related resources and specific fields - When I send a "GET" request to "/dummy_properties/1?include=group&fields[group]=id,foo" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be deep equal to: - """ - { - "data": { - "id": "/dummy_properties/1", - "type": "DummyProperty", - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/1" - } - } - } - }, - "included": [ - { - "id": "\/dummy_groups\/1", - "type": "DummyGroup", - "attributes": { - "_id": 1, - "foo": "Foo #1" - } - } - ] - } - """ - - @createSchema - Scenario: Request inclusion of related resources (many to many) - Given there are 1 dummy property objects with 3 groups - When I send a "GET" request to "/dummy_properties/1?include=groups" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be deep equal to: - """ - { - "data": { - "id": "/dummy_properties/1", - "type": "DummyProperty", - "attributes": { - "_id": 1, - "foo": "Foo #1", - "bar": "Bar #1", - "baz": "Baz #1" - }, - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/1" - } - }, - "groups": { - "data": [ - { - "type": "DummyGroup", - "id": "/dummy_groups/2" - }, - { - "type": "DummyGroup", - "id": "/dummy_groups/3" - }, - { - "type": "DummyGroup", - "id": "/dummy_groups/4" - } - ] - } - } - }, - "included": [ - { - "id": "/dummy_groups/2", - "type": "DummyGroup", - "attributes": { - "_id": 2, - "foo": "Foo #11", - "bar": "Bar #11", - "baz": "Baz #11" - } - }, - { - "id": "/dummy_groups/3", - "type": "DummyGroup", - "attributes": { - "_id": 3, - "foo": "Foo #12", - "bar": "Bar #12", - "baz": "Baz #12" - } - }, - { - "id": "/dummy_groups/4", - "type": "DummyGroup", - "attributes": { - "_id": 4, - "foo": "Foo #13", - "bar": "Bar #13", - "baz": "Baz #13" - } - } - ] - } - """ - - @createSchema - Scenario: Request inclusion of related resources (many to many and many to one) - Given there are 1 dummy property objects with 3 groups - When I send a "GET" request to "/dummy_properties/1?include=groups,group" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be deep equal to: - """ - { - "data": { - "id": "/dummy_properties/1", - "type": "DummyProperty", - "attributes": { - "_id": 1, - "foo": "Foo #1", - "bar": "Bar #1", - "baz": "Baz #1" - }, - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/1" - } - }, - "groups": { - "data": [ - { - "type": "DummyGroup", - "id": "/dummy_groups/2" - }, - { - "type": "DummyGroup", - "id": "/dummy_groups/3" - }, - { - "type": "DummyGroup", - "id": "/dummy_groups/4" - } - ] - } - } - }, - "included": [ - { - "id": "/dummy_groups/1", - "type": "DummyGroup", - "attributes": { - "_id": 1, - "foo": "Foo #1", - "bar": "Bar #1", - "baz": "Baz #1" - } - }, - { - "id": "/dummy_groups/2", - "type": "DummyGroup", - "attributes": { - "_id": 2, - "foo": "Foo #11", - "bar": "Bar #11", - "baz": "Baz #11" - } - }, - { - "id": "/dummy_groups/3", - "type": "DummyGroup", - "attributes": { - "_id": 3, - "foo": "Foo #12", - "bar": "Bar #12", - "baz": "Baz #12" - } - }, - { - "id": "/dummy_groups/4", - "type": "DummyGroup", - "attributes": { - "_id": 4, - "foo": "Foo #13", - "bar": "Bar #13", - "baz": "Baz #13" - } - } - ] - } - """ - - @createSchema - Scenario: Request inclusion of resource with relation - Given there are 1 dummy objects with relatedDummy and its thirdLevel - When I send a "GET" request to "/dummies/1?include=relatedDummy" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be deep equal to: - """ - { - "data": { - "id": "/dummies/1", - "type": "Dummy", - "attributes": { - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "jsonData": [], - "arrayData": [], - "name_converted": null, - "_id": 1, - "name": "Dummy #1", - "alias": "Alias #0", - "foo": null - }, - "relationships": { - "relatedDummy": { - "data": { - "type": "RelatedDummy", - "id": "/related_dummies/1" - } - } - } - }, - "included": [ - { - "id": "/related_dummies/1", - "type": "RelatedDummy", - "attributes": { - "name": "RelatedDummy #1", - "dummyDate": null, - "dummyBoolean": null, - "embeddedDummy": { - "dummyName": null, - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "symfony": null - }, - "_id": 1, - "symfony": "symfony", - "age": null - }, - "relationships": { - "thirdLevel": { - "data": { - "id": "/third_levels/1", - "type": "ThirdLevel" - } - } - } - } - ] - } - """ - - @createSchema - Scenario: Request inclusion of a related resources on collection - Given there are 3 dummy property objects - When I send a "GET" request to "/dummy_properties?include=group" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be deep equal to: - """ - { - "links": { - "self": "\/dummy_properties?include=group" - }, - "meta": { - "totalItems": 3, - "itemsPerPage": 3, - "currentPage": 1 - }, - "data": [ - { - "id": "/dummy_properties/1", - "type": "DummyProperty", - "attributes": { - "_id": 1, - "foo": "Foo #1", - "bar": "Bar #1", - "baz": "Baz #1" - }, - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/1" - } - } - } - }, - { - "id": "/dummy_properties/2", - "type": "DummyProperty", - "attributes": { - "_id": 2, - "foo": "Foo #2", - "bar": "Bar #2", - "baz": "Baz #2" - }, - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/2" - } - } - } - }, - { - "id": "/dummy_properties/3", - "type": "DummyProperty", - "attributes": { - "_id": 3, - "foo": "Foo #3", - "bar": "Bar #3", - "baz": "Baz #3" - }, - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/3" - } - } - } - } - ], - "included": [ - { - "id": "\/dummy_groups\/1", - "type": "DummyGroup", - "attributes": { - "_id": 1, - "foo": "Foo #1", - "bar": "Bar #1", - "baz": "Baz #1" - } - }, - { - "id": "\/dummy_groups\/2", - "type": "DummyGroup", - "attributes": { - "_id": 2, - "foo": "Foo #2", - "bar": "Bar #2", - "baz": "Baz #2" - } - }, - { - "id": "\/dummy_groups\/3", - "type": "DummyGroup", - "attributes": { - "_id": 3, - "foo": "Foo #3", - "bar": "Bar #3", - "baz": "Baz #3" - } - } - ] - } - """ - - @createSchema - Scenario: Request inclusion of a related resources on collection should not duplicated included object - Given there are 3 dummy property objects with a shared group - When I send a "GET" request to "/dummy_properties?include=group" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be deep equal to: - """ - { - "links": { - "self": "\/dummy_properties?include=group" - }, - "meta": { - "totalItems": 3, - "itemsPerPage": 3, - "currentPage": 1 - }, - "data": [ - { - "id": "/dummy_properties/1", - "type": "DummyProperty", - "attributes": { - "_id": 1, - "foo": "Foo #1", - "bar": "Bar #1", - "baz": "Baz #1" - }, - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/1" - } - } - } - }, - { - "id": "/dummy_properties/2", - "type": "DummyProperty", - "attributes": { - "_id": 2, - "foo": "Foo #2", - "bar": "Bar #2", - "baz": "Baz #2" - }, - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/1" - } - } - } - }, - { - "id": "/dummy_properties/3", - "type": "DummyProperty", - "attributes": { - "_id": 3, - "foo": "Foo #3", - "bar": "Bar #3", - "baz": "Baz #3" - }, - "relationships": { - "group": { - "data": { - "type": "DummyGroup", - "id": "/dummy_groups/1" - } - } - } - } - ], - "included": [ - { - "id": "\/dummy_groups\/1", - "type": "DummyGroup", - "attributes": { - "_id": 1, - "foo": "Foo #shared", - "bar": "Bar #shared", - "baz": "Baz #shared" - } - } - ] - } - """ - - @createSchema - Scenario: Request inclusion of a related resources on collection should not duplicated included object - Given there are 2 dummy property objects with different number of related groups - When I send a "GET" request to "/dummy_properties?include=groups" - Then the response status code should be 200 - And the response should be in JSON - And the JSON should be valid according to the JSON API schema - And the JSON should be deep equal to: - """ - { - "links": { - "self": "/dummy_properties?include=groups" - }, - "meta": { - "totalItems": 2, - "itemsPerPage": 3, - "currentPage": 1 - }, - "data": [{ - "id": "/dummy_properties/1", - "type": "DummyProperty", - "attributes": { - "_id": 1, - "foo": "Foo #1", - "bar": "Bar #1", - "baz": "Baz #1" - }, - "relationships": { - "groups": { - "data": [{ - "type": "DummyGroup", - "id": "/dummy_groups/1" - }] - } - } - }, { - "id": "/dummy_properties/2", - "type": "DummyProperty", - "attributes": { - "_id": 2, - "foo": "Foo #2", - "bar": "Bar #2", - "baz": "Baz #2" - }, - "relationships": { - "groups": { - "data": [{ - "type": "DummyGroup", - "id": "/dummy_groups/1" - }, { - "type": "DummyGroup", - "id": "/dummy_groups/2" - }] - } - } - }], - "included": [{ - "id": "/dummy_groups/1", - "type": "DummyGroup", - "attributes": { - "_id": 1, - "foo": "Foo #1", - "bar": "Bar #1", - "baz": "Baz #1" - } - }, { - "id": "/dummy_groups/2", - "type": "DummyGroup", - "attributes": { - "_id": 2, - "foo": "Foo #2", - "bar": "Bar #2", - "baz": "Baz #2" - } - }] - } - """ diff --git a/features/jsonld/context.feature b/features/jsonld/context.feature deleted file mode 100644 index cfc7e75c375..00000000000 --- a/features/jsonld/context.feature +++ /dev/null @@ -1,92 +0,0 @@ -Feature: JSON-LD contexts generation - In order to have an hypermedia, Linked Data enabled API - As a client software developer - I need to access to a JSON-LD context describing data types - - Scenario: Retrieve Entrypoint context - When I send a "GET" request to "/contexts/Entrypoint" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "@context.@vocab" should be equal to "http://example.com/docs.jsonld#" - And the JSON node "@context.hydra" should be equal to "http://www.w3.org/ns/hydra/core#" - And the JSON node "@context.dummy.@id" should be equal to "Entrypoint/dummy" - And the JSON node "@context.dummy.@type" should be equal to "@id" - - Scenario: Retrieve Dummy context - When I send a "GET" request to "/contexts/Dummy" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be a superset of: - """ - { - "@context": { - "@vocab": "http://example.com/docs.jsonld#", - "hydra": "http://www.w3.org/ns/hydra/core#", - "description": "https://schema.org/description", - "dummy": "Dummy/dummy", - "dummyBoolean": "Dummy/dummyBoolean", - "dummyDate": { - "@id": "Dummy/dummyDate", - "@type": "@id" - }, - "dummyFloat": "Dummy/dummyFloat", - "dummyPrice": "Dummy/dummyPrice", - "relatedDummy": { - "@id": "Dummy/relatedDummy", - "@type": "@id" - }, - "relatedDummies": { - "@id": "Dummy/relatedDummies", - "@type": "@id" - }, - "jsonData": "Dummy/jsonData", - "arrayData": "Dummy/arrayData", - "nameConverted": "Dummy/nameConverted", - "id": "Dummy/id", - "name": "http://schema.org/name", - "alias": "https://schema.org/alternateName", - "foo": "Dummy/foo" - } - } - """ - - Scenario: Retrieve context of an object with an embed relation - When I send a "GET" request to "/contexts/RelationEmbedder" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": { - "@vocab": "http://example.com/docs.jsonld#", - "hydra": "http://www.w3.org/ns/hydra/core#", - "paris": "RelationEmbedder/paris", - "krondstadt": "RelationEmbedder/krondstadt", - "anotherRelated": "RelationEmbedder/anotherRelated", - "related": "RelationEmbedder/related" - } - } - """ - - Scenario: Retrieve Dummy with extended jsonld context - When I send a "GET" request to "/contexts/JsonldContextDummy" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": { - "@vocab": "http://example.com/docs.jsonld#", - "hydra": "http://www.w3.org/ns/hydra/core#", - "person": { - "@id": "http://example.com/id", - "@type": "@id", - "foo": "bar" - } - } - } - """ diff --git a/features/jsonld/max_depth.feature b/features/jsonld/max_depth.feature deleted file mode 100644 index 99469b5f554..00000000000 --- a/features/jsonld/max_depth.feature +++ /dev/null @@ -1,72 +0,0 @@ -Feature: Max depth handling - In order to handle MaxDepthDummy resources - As a developer - I need to be able to limit their depth with @maxDepth - - @createSchema - Scenario: Create a resource with 1 level of descendants - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/max_depth_dummies" with body: - """ - { - "name": "level 1", - "child": { - "name": "level 2" - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/MaxDepthDummy", - "@id": "/max_depth_dummies/1", - "@type": "MaxDepthDummy", - "id": 1, - "name": "level 1", - "child": { - "@id": "/max_depth_dummies/2", - "@type": "MaxDepthDummy", - "id": 2, - "name": "level 2" - } - } - """ - - @dropSchema - Scenario: Add a 2nd level of descendants - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "max_depth_dummies/1" with body: - """ - { - "@id": "/max_depth_dummies/1", - "child": { - "@id": "/max_depth_dummies/2", - "child": { - "name": "level 3" - } - } - } - """ - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/MaxDepthDummy", - "@id": "/max_depth_dummies/1", - "@type": "MaxDepthDummy", - "id": 1, - "name": "level 1", - "child": { - "@id": "/max_depth_dummies/2", - "@type": "MaxDepthDummy", - "id": 2, - "name": "level 2" - } - } - """ - diff --git a/features/main/circular_reference.feature b/features/main/circular_reference.feature deleted file mode 100644 index f53d44d9164..00000000000 --- a/features/main/circular_reference.feature +++ /dev/null @@ -1,89 +0,0 @@ -Feature: Circular references handling - In order to handle circular references - As a developer - I should be able to catch circular references. - - @createSchema - Scenario: Create a circular reference - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/circular_references" with body: - """ - {} - """ - And I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/circular_references/1" with body: - """ - { - "parent": "/circular_references/1" - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CircularReference", - "@id": "/circular_references/1", - "@type": "CircularReference", - "parent": "/circular_references/1", - "children": [ - "/circular_references/1" - ] - } - """ - - Scenario: Fetch circular reference - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/circular_references" with body: - """ - {} - """ - And I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/circular_references/2" with body: - """ - { - "parent": "/circular_references/1" - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CircularReference", - "@id": "/circular_references/2", - "@type": "CircularReference", - "parent": { - "@id": "/circular_references/1", - "@type": "CircularReference", - "parent": "/circular_references/1", - "children": [ - "/circular_references/1", - "/circular_references/2" - ] - }, - "children": [] - } - """ - And I send a "GET" request to "/circular_references/1" - Then the response status code should be 200 - And the JSON should be equal to: - """ - { - "@context": "/contexts/CircularReference", - "@id": "/circular_references/1", - "@type": "CircularReference", - "parent": "/circular_references/1", - "children": [ - "/circular_references/1", - { - "@id": "/circular_references/2", - "@type": "CircularReference", - "parent": "/circular_references/1", - "children": [] - } - ] - } - """ diff --git a/features/main/composite.feature b/features/main/composite.feature deleted file mode 100644 index 2e25ab50280..00000000000 --- a/features/main/composite.feature +++ /dev/null @@ -1,131 +0,0 @@ -Feature: Retrieve data with Composite identifiers - In order to retrieve relations with composite identifiers - As a client software developer - I need to retrieve all collections - - @createSchema - Scenario: Get a collection with composite identifiers - Given there are Composite identifier objects - When I send a "GET" request to "/composite_items" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be deep equal to: - """ - { - "@context": "/contexts/CompositeItem", - "@id": "/composite_items", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/composite_items/1", - "@type": "CompositeItem", - "id": 1, - "field1": "foobar", - "compositeValues": [ - "/composite_relations/compositeItem=1;compositeLabel=1", - "/composite_relations/compositeItem=1;compositeLabel=2", - "/composite_relations/compositeItem=1;compositeLabel=3", - "/composite_relations/compositeItem=1;compositeLabel=4" - ] - } - ], - "hydra:totalItems": 1 - } - """ - - @createSchema - Scenario: Get collection with composite identifiers - Given there are Composite identifier objects - When I send a "GET" request to "/composite_relations" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CompositeRelation", - "@id": "/composite_relations", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/composite_relations/compositeItem=1;compositeLabel=1", - "@type": "CompositeRelation", - "value": "somefoobardummy", - "compositeItem": "/composite_items/1", - "compositeLabel": "/composite_labels/1" - }, - { - "@id": "/composite_relations/compositeItem=1;compositeLabel=2", - "@type": "CompositeRelation", - "value": "somefoobardummy", - "compositeItem": "/composite_items/1", - "compositeLabel": "/composite_labels/2" - }, - { - "@id": "/composite_relations/compositeItem=1;compositeLabel=3", - "@type": "CompositeRelation", - "value": "somefoobardummy", - "compositeItem": "/composite_items/1", - "compositeLabel": "/composite_labels/3" - } - ], - "hydra:totalItems": 4, - "hydra:view": { - "@id": "/composite_relations?page=1", - "@type": "hydra:PartialCollectionView", - "hydra:first": "/composite_relations?page=1", - "hydra:last": "/composite_relations?page=2", - "hydra:next": "/composite_relations?page=2" - } - } - """ - - @createSchema - Scenario: Get the first composite relation - Given there are Composite identifier objects - When I send a "GET" request to "/composite_relations/compositeItem=1;compositeLabel=1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CompositeRelation", - "@id": "/composite_relations/compositeItem=1;compositeLabel=1", - "@type": "CompositeRelation", - "value": "somefoobardummy", - "compositeItem": "/composite_items/1", - "compositeLabel": "/composite_labels/1" - } - """ - - Scenario: Get the first composite relation with a reverse identifiers order - Given there are Composite identifier objects - When I send a "GET" request to "/composite_relations/compositeLabel=1;compositeItem=1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CompositeRelation", - "@id": "/composite_relations/compositeItem=1;compositeLabel=1", - "@type": "CompositeRelation", - "value": "somefoobardummy", - "compositeItem": "/composite_items/1", - "compositeLabel": "/composite_labels/1" - } - """ - - Scenario: Get the first composite relation with a missing identifier - Given there are Composite identifier objects - When I send a "GET" request to "/composite_relations/compositeLabel=1;" - Then the response status code should be 404 - - Scenario: Get first composite item - Given there are Composite identifier objects - When I send a "GET" request to "/composite_items/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" diff --git a/features/main/configurable.feature b/features/main/configurable.feature deleted file mode 100644 index c0e73d1ba2b..00000000000 --- a/features/main/configurable.feature +++ /dev/null @@ -1,62 +0,0 @@ -Feature: Configurable resource CRUD - As a client software developer - I need to be able to configure api resources through YAML - - @createSchema - Scenario: Retrieve the ConfigDummy resource - Given there is a FileConfigDummy object - When I send a "GET" request to "/fileconfigdummies" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/fileconfigdummy", - "@id": "/fileconfigdummies", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/fileconfigdummies/1", - "@type": "fileconfigdummy", - "id": 1, - "name": "ConfigDummy", - "foo": "Foo" - } - ], - "hydra:totalItems": 1 - } - """ - - Scenario: Get a single file configured resource - When I send a "GET" request to "/single_file_configs" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/single_file_config", - "@id": "/single_file_configs", - "@type": "hydra:Collection", - "hydra:member": [], - "hydra:totalItems": 0 - } - """ - - Scenario: Retrieve the ConfigDummy resource - When I send a "GET" request to "/fileconfigdummies/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/fileconfigdummy", - "@id": "/fileconfigdummies/1", - "@type": "fileconfigdummy", - "id": 1, - "name": "ConfigDummy", - "foo": "Foo" - } - """ diff --git a/features/main/content_negotiation.feature b/features/main/content_negotiation.feature deleted file mode 100644 index acfbbbd3791..00000000000 --- a/features/main/content_negotiation.feature +++ /dev/null @@ -1,152 +0,0 @@ -Feature: Content Negotiation support - In order to make the API supporting several input and output formats - As an API developer - I need to be able to specify the format I want to use - - @createSchema - Scenario: Post an XML body - When I add "Accept" header equal to "application/xml" - And I add "Content-Type" header equal to "application/xml" - And I send a "POST" request to "/dummies" with body: - """ - - XML! - - """ - Then the response status code should be 201 - And the header "Content-Type" should be equal to "application/xml; charset=utf-8" - And the response should be equal to - """ - - 1XML! - """ - - Scenario: Retrieve a collection in XML - When I add "Accept" header equal to "text/xml" - And I send a "GET" request to "/dummies" - Then the response status code should be 200 - And the header "Content-Type" should be equal to "application/xml; charset=utf-8" - And the response should be equal to - """ - - 1XML! - """ - - Scenario: Retrieve a collection in XML using the .xml URL - When I send a "GET" request to "/dummies.xml" - Then the response status code should be 200 - And the header "Content-Type" should be equal to "application/xml; charset=utf-8" - And the response should be equal to - """ - - 1XML! - """ - - Scenario: Retrieve a collection in JSON - When I add "Accept" header equal to "application/json" - And I send a "GET" request to "/dummies" - Then the response status code should be 200 - And the header "Content-Type" should be equal to "application/json; charset=utf-8" - And the response should be in JSON - And the JSON should be equal to: - """ - [ - { - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "relatedDummy": null, - "relatedDummies": [], - "jsonData": [], - "arrayData": [], - "name_converted": null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id": 1, - "name": "XML!", - "alias": null, - "foo": null - } - ] - """ - - Scenario: Post a JSON document and retrieve an XML body - When I add "Accept" header equal to "application/xml" - And I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/dummies" with body: - """ - {"name": "Sent in JSON"} - """ - Then the response status code should be 201 - And the header "Content-Type" should be equal to "application/xml; charset=utf-8" - And the response should be equal to - """ - - 2Sent in JSON - """ - - Scenario: Requesting the same format in the Accept header and in the URL should work - When I add "Accept" header equal to "text/xml" - And I send a "GET" request to "/dummies/1.xml" - Then the response status code should be 200 - And the header "Content-Type" should be equal to "application/xml; charset=utf-8" - - Scenario: Requesting any format in the Accept header should default to the first configured format - When I add "Accept" header equal to "*/*" - And I send a "GET" request to "/dummies/1" - Then the response status code should be 200 - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - Scenario: Requesting any format in the Accept header should default to the format passed in the URL - When I add "Accept" header equal to "text/plain; charset=utf-8, */*" - And I send a "GET" request to "/dummies/1.xml" - Then the response status code should be 200 - And the header "Content-Type" should be equal to "application/xml; charset=utf-8" - - Scenario: Requesting an unknown format should throw an error - When I add "Accept" header equal to "text/plain" - And I send a "GET" request to "/dummies/1" - Then the response status code should be 406 - And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8" - - Scenario: If the request format is HTML, the error should be in HTML - When I add "Accept" header equal to "text/html" - And I send a "GET" request to "/dummies/666" - Then the response status code should be 404 - And the header "Content-Type" should be equal to "text/html; charset=utf-8" - - Scenario: Retrieve a collection in JSON should not be possible if the format has been removed at resource level - When I add "Accept" header equal to "application/json" - And I send a "GET" request to "/dummy_custom_formats" - Then the response status code should be 406 - And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8" - - Scenario: Post an CSV body allowed on a single resource - When I add "Accept" header equal to "application/xml" - And I add "Content-Type" header equal to "text/csv" - And I send a "POST" request to "/dummy_custom_formats" with body: - """ - name - Kevin - """ - Then the response status code should be 201 - And the header "Content-Type" should be equal to "application/xml; charset=utf-8" - And the response should be equal to - """ - - 1Kevin - """ - - Scenario: Retrieve a collection in CSV should be possible if the format is at resource level - When I add "Accept" header equal to "text/csv" - And I send a "GET" request to "/dummy_custom_formats" - Then the response status code should be 200 - And the header "Content-Type" should be equal to "text/csv; charset=utf-8" - And the response should be equal to - """ - id,name - 1,Kevin - """ diff --git a/features/main/crud.feature b/features/main/crud.feature deleted file mode 100644 index 83013a36792..00000000000 --- a/features/main/crud.feature +++ /dev/null @@ -1,508 +0,0 @@ -Feature: Create-Retrieve-Update-Delete - In order to use an hypermedia API - As a client software developer - I need to be able to retrieve, create, update and delete JSON-LD encoded resources. - - @createSchema - Scenario: Create a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "My Dummy", - "dummyDate": "2015-03-01T10:00:00+00:00", - "jsonData": { - "key": [ - "value1", - "value2" - ] - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/dummies/1" - And the header "Location" should be equal to "/dummies/1" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Dummy", - "@id": "/dummies/1", - "@type": "Dummy", - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": "2015-03-01T10:00:00+00:00", - "dummyFloat": null, - "dummyPrice": null, - "relatedDummy": null, - "relatedDummies": [], - "jsonData": { - "key": [ - "value1", - "value2" - ] - }, - "arrayData": [], - "name_converted": null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id": 1, - "name": "My Dummy", - "alias": null, - "foo": null - } - """ - - Scenario: Get a resource - When I send a "GET" request to "/dummies/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Dummy", - "@id": "/dummies/1", - "@type": "Dummy", - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": "2015-03-01T10:00:00+00:00", - "dummyFloat": null, - "dummyPrice": null, - "relatedDummy": null, - "relatedDummies": [], - "jsonData": { - "key": [ - "value1", - "value2" - ] - }, - "arrayData": [], - "name_converted": null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id": 1, - "name": "My Dummy", - "alias": null, - "foo": null - } - """ - - Scenario: Get a not found exception - When I send a "GET" request to "/dummies/42" - Then the response status code should be 404 - - Scenario: Get a collection - When I send a "GET" request to "/dummies" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Dummy", - "@id": "/dummies", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/dummies/1", - "@type": "Dummy", - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": "2015-03-01T10:00:00+00:00", - "dummyFloat": null, - "dummyPrice": null, - "relatedDummy": null, - "relatedDummies": [], - "jsonData": { - "key": [ - "value1", - "value2" - ] - }, - "arrayData": [], - "name_converted": null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id": 1, - "name": "My Dummy", - "alias": null, - "foo": null - } - ], - "hydra:totalItems": 1, - "hydra:search": { - "@type": "hydra:IriTemplate", - "hydra:template": "/dummies{?dummyBoolean,relatedDummy.embeddedDummy.dummyBoolean,dummyDate[before],dummyDate[strictly_before],dummyDate[after],dummyDate[strictly_after],relatedDummy.dummyDate[before],relatedDummy.dummyDate[strictly_before],relatedDummy.dummyDate[after],relatedDummy.dummyDate[strictly_after],description[exists],relatedDummy.name[exists],dummyBoolean[exists],relatedDummy[exists],dummyFloat,dummyPrice,order[id],order[name],order[description],order[relatedDummy.name],order[relatedDummy.symfony],order[dummyDate],dummyFloat[between],dummyFloat[gt],dummyFloat[gte],dummyFloat[lt],dummyFloat[lte],dummyPrice[between],dummyPrice[gt],dummyPrice[gte],dummyPrice[lt],dummyPrice[lte],id,id[],name,alias,description,relatedDummy.name,relatedDummy.name[],relatedDummies,relatedDummies[],dummy,relatedDummies.name,properties[]}", - "hydra:variableRepresentation": "BasicRepresentation", - "hydra:mapping": [ - { - "@type": "IriTemplateMapping", - "variable": "dummyBoolean", - "property": "dummyBoolean", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.embeddedDummy.dummyBoolean", - "property": "relatedDummy.embeddedDummy.dummyBoolean", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[before]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[strictly_before]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[after]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyDate[strictly_after]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[before]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[strictly_before]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[after]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.dummyDate[strictly_after]", - "property": "relatedDummy.dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "description[exists]", - "property": "description", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.name[exists]", - "property": "relatedDummy.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyBoolean[exists]", - "property": "dummyBoolean", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy[exists]", - "property": "relatedDummy", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[id]", - "property": "id", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[name]", - "property": "name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[description]", - "property": "description", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[relatedDummy.name]", - "property": "relatedDummy.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[relatedDummy.symfony]", - "property": "relatedDummy.symfony", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "order[dummyDate]", - "property": "dummyDate", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[between]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[gt]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[gte]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[lt]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyFloat[lte]", - "property": "dummyFloat", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[between]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[gt]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[gte]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[lt]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummyPrice[lte]", - "property": "dummyPrice", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "id", - "property": "id", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "id[]", - "property": "id", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "name", - "property": "name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "alias", - "property": "alias", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "description", - "property": "description", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.name", - "property": "relatedDummy.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummy.name[]", - "property": "relatedDummy.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummies", - "property": "relatedDummies", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummies[]", - "property": "relatedDummies", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "dummy", - "property": "dummy", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedDummies.name", - "property": "relatedDummies.name", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "properties[]", - "property": null, - "required": false - } - ] - } - } - """ - - Scenario: Update a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/dummies/1" with body: - """ - { - "@id": "/dummies/1", - "name": "A nice dummy", - "jsonData": [{ - "key": "value1" - }, - { - "key": "value2" - } - ] - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/dummies/1" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Dummy", - "@id": "/dummies/1", - "@type": "Dummy", - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": "2015-03-01T10:00:00+00:00", - "dummyFloat": null, - "dummyPrice": null, - "relatedDummy": null, - "relatedDummies": [], - "jsonData": [ - { - "key": "value1" - }, - { - "key": "value2" - } - ], - "arrayData": [], - "name_converted": null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id": 1, - "name": "A nice dummy", - "alias": null, - "foo": null - } - """ - - Scenario: Update a resource with empty body - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/dummies/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/dummies/1" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Dummy", - "@id": "/dummies/1", - "@type": "Dummy", - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": "2015-03-01T10:00:00+00:00", - "dummyFloat": null, - "dummyPrice": null, - "relatedDummy": null, - "relatedDummies": [], - "jsonData": [ - { - "key": "value1" - }, - { - "key": "value2" - } - ], - "arrayData": [], - "name_converted": null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id": 1, - "name": "A nice dummy", - "alias": null, - "foo": null - } - """ - - Scenario: Delete a resource - When I send a "DELETE" request to "/dummies/1" - Then the response status code should be 204 - And the response should be empty diff --git a/features/main/crud_abstract.feature b/features/main/crud_abstract.feature deleted file mode 100644 index c7c15a31821..00000000000 --- a/features/main/crud_abstract.feature +++ /dev/null @@ -1,111 +0,0 @@ -Feature: Create-Retrieve-Update-Delete on abstract resource - In order to use an hypermedia API - As a client software developer - I need to be able to retrieve, create, update and delete JSON-LD encoded resources even if they are abstract. - - @createSchema - Scenario: Create a concrete resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/concrete_dummies" with body: - """ - { - "instance": "Concrete", - "name": "My Dummy" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/concrete_dummies/1" - And the header "Location" should be equal to "/concrete_dummies/1" - And the JSON should be equal to: - """ - { - "@context": "/contexts/ConcreteDummy", - "@id": "/concrete_dummies/1", - "@type": "ConcreteDummy", - "instance": "Concrete", - "id": 1, - "name": "My Dummy" - } - """ - - Scenario: Get a resource - When I send a "GET" request to "/abstract_dummies/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/ConcreteDummy", - "@id": "/concrete_dummies/1", - "@type": "ConcreteDummy", - "instance": "Concrete", - "id": 1, - "name": "My Dummy" - } - """ - - Scenario: Get a collection - When I send a "GET" request to "/abstract_dummies" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@type": { - "type": "string", - "pattern": "^ConcreteDummy$" - }, - "instance": { - "type": "string", - "required": "true" - } - } - }, - "minItems": 1 - } - }, - "required": ["hydra:member"] - } - """ - - Scenario: Update a concrete resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/concrete_dummies/1" with body: - """ - { - "@id": "/concrete_dummies/1", - "instance": "Become real", - "name": "A nice dummy" - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/concrete_dummies/1" - And the JSON should be equal to: - """ - { - "@context": "/contexts/ConcreteDummy", - "@id": "/concrete_dummies/1", - "@type": "ConcreteDummy", - "instance": "Become real", - "id": 1, - "name": "A nice dummy" - } - """ - - Scenario: Delete a resource - When I send a "DELETE" request to "/abstract_dummies/1" - Then the response status code should be 204 - And the response should be empty diff --git a/features/main/custom_identifier.feature b/features/main/custom_identifier.feature deleted file mode 100644 index ba37934d76b..00000000000 --- a/features/main/custom_identifier.feature +++ /dev/null @@ -1,103 +0,0 @@ -Feature: Using custom identifier on resource - In order to use an hypermedia API - As a client software developer - I need to be able to user other identifier than id in resources - - @createSchema - Scenario: Create a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/custom_identifier_dummies" with body: - """ - { - "name": "My Dummy" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomIdentifierDummy", - "@id": "/custom_identifier_dummies/1", - "@type": "CustomIdentifierDummy", - "customId": 1, - "name": "My Dummy" - } - """ - - Scenario: Get a resource - When I send a "GET" request to "/custom_identifier_dummies/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomIdentifierDummy", - "@id": "/custom_identifier_dummies/1", - "@type": "CustomIdentifierDummy", - "customId": 1, - "name": "My Dummy" - } - """ - - Scenario: Get a collection - When I send a "GET" request to "/custom_identifier_dummies" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomIdentifierDummy", - "@id": "/custom_identifier_dummies", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/custom_identifier_dummies/1", - "@type": "CustomIdentifierDummy", - "customId": 1, - "name": "My Dummy" - } - ], - "hydra:totalItems": 1 - } - """ - - Scenario: Update a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/custom_identifier_dummies/1" with body: - """ - { - "name": "My Dummy modified" - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomIdentifierDummy", - "@id": "/custom_identifier_dummies/1", - "@type": "CustomIdentifierDummy", - "customId": 1, - "name": "My Dummy modified" - } - """ - - Scenario: API doc is correctly generated - When I send a "GET" request to "/docs.jsonld" - Then the response status code should be 200 - And the response should be in JSON - And the Hydra class "CustomIdentifierDummy" exists - And 4 operations are available for Hydra class "CustomIdentifierDummy" - And 1 properties are available for Hydra class "CustomIdentifierDummy" - And "name" property is readable for Hydra class "CustomIdentifierDummy" - And "name" property is writable for Hydra class "CustomIdentifierDummy" - - Scenario: Delete a resource - When I send a "DELETE" request to "/custom_identifier_dummies/1" - Then the response status code should be 204 - And the response should be empty diff --git a/features/main/custom_normalized.feature b/features/main/custom_normalized.feature deleted file mode 100644 index f9d20c12176..00000000000 --- a/features/main/custom_normalized.feature +++ /dev/null @@ -1,170 +0,0 @@ -Feature: Using custom normalized entity - In order to use an hypermedia API - As a client software developer - I need to be able to filter correctly attribute of my entities - - @createSchema - Scenario: Create a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/custom_normalized_dummies" with body: - """ - { - "name": "My Dummy", - "alias": "My alias" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/custom_normalized_dummies/1" - And the header "Location" should be equal to "/custom_normalized_dummies/1" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomNormalizedDummy", - "@id": "/custom_normalized_dummies/1", - "@type": "CustomNormalizedDummy", - "id": 1, - "name": "My Dummy", - "alias": "My alias" - } - """ - - Scenario: Create a resource with a custom normalized dummy - When I add "Content-Type" header equal to "application/json" - When I add "Accept" header equal to "application/json" - And I send a "POST" request to "/related_normalized_dummies" with body: - """ - { - "name": "My Dummy" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json; charset=utf-8" - And the header "Content-Location" should be equal to "/related_normalized_dummies/1" - And the header "Location" should be equal to "/related_normalized_dummies/1" - And the JSON should be equal to: - """ - { - "id": 1, - "name": "My Dummy", - "customNormalizedDummy": [] - } - """ - - Scenario: Create a resource with a custom normalized dummy and an id - When I add "Content-Type" header equal to "application/json" - When I add "Accept" header equal to "application/json" - And I send a "PUT" request to "/related_normalized_dummies/1" with body: - """ - { - "name": "My Dummy", - "customNormalizedDummy":[{ - "@context": "/contexts/CustomNormalizedDummy", - "@id": "/custom_normalized_dummies/1", - "@type": "CustomNormalizedDummy", - "id": 1, - "name": "My Dummy" - }] - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json; charset=utf-8" - And the header "Content-Location" should be equal to "/related_normalized_dummies/1" - And the JSON should be equal to: - """ - { - "id": 1, - "name": "My Dummy", - "customNormalizedDummy":[{ - "id": 1, - "name": "My Dummy", - "alias": "My alias" - }] - } - """ - - - Scenario: Get a custom normalized dummy resource - When I send a "GET" request to "/custom_normalized_dummies/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomNormalizedDummy", - "@id": "/custom_normalized_dummies/1", - "@type": "CustomNormalizedDummy", - "id": 1, - "name": "My Dummy", - "alias": "My alias" - } - """ - - Scenario: Get a collection - When I send a "GET" request to "/custom_normalized_dummies" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomNormalizedDummy", - "@id": "/custom_normalized_dummies", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/custom_normalized_dummies/1", - "@type": "CustomNormalizedDummy", - "id": 1, - "name": "My Dummy", - "alias": "My alias" - } - ], - "hydra:totalItems": 1 - } - """ - - Scenario: Update a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/custom_normalized_dummies/1" with body: - """ - { - "name": "My Dummy modified" - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/custom_normalized_dummies/1" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomNormalizedDummy", - "@id": "/custom_normalized_dummies/1", - "@type": "CustomNormalizedDummy", - "id": 1, - "name": "My Dummy modified", - "alias": "My alias" - } - """ - - Scenario: API doc is correctly generated - When I send a "GET" request to "/docs.jsonld" - Then the response status code should be 200 - And the response should be in JSON - And the Hydra class "CustomNormalizedDummy" exists - And 4 operations are available for Hydra class "CustomNormalizedDummy" - And 2 properties are available for Hydra class "CustomNormalizedDummy" - And "name" property is readable for Hydra class "CustomNormalizedDummy" - And "name" property is writable for Hydra class "CustomNormalizedDummy" - And "alias" property is readable for Hydra class "CustomNormalizedDummy" - And "alias" property is writable for Hydra class "CustomNormalizedDummy" - - Scenario: Delete a resource - When I send a "DELETE" request to "/custom_normalized_dummies/1" - Then the response status code should be 204 - And the response should be empty diff --git a/features/main/custom_operation.feature b/features/main/custom_operation.feature deleted file mode 100644 index 3dfd9ee3a00..00000000000 --- a/features/main/custom_operation.feature +++ /dev/null @@ -1,70 +0,0 @@ -Feature: Custom operation - As a client software developer - I need to be able to create custom operations - - @createSchema - Scenario: Custom normalization operation - When I send a "POST" request to "/custom/denormalization" - And I add "Content-Type" header equal to "application/ld+json" - Then the JSON should be equal to: - """ - { - "@context": "/contexts/CustomActionDummy", - "@id": "/custom_action_dummies/1", - "@type": "CustomActionDummy", - "id": 1, - "foo": "custom!" - } - """ - - Scenario: Custom normalization operation - When I send a "GET" request to "/custom/1/normalization" - Then the JSON should be equal to: - """ - { - "id": 1, - "foo": "foo" - } - """ - - Scenario: Custom normalization operation with shorthand configuration - When I send a "POST" request to "/short_custom/denormalization" - And I add "Content-Type" header equal to "application/ld+json" - Then the JSON should be equal to: - """ - { - "@context": "/contexts/CustomActionDummy", - "@id": "/custom_action_dummies/2", - "@type": "CustomActionDummy", - "id": 2, - "foo": "short declaration" - } - """ - - Scenario: Custom normalization operation with shorthand configuration - When I send a "GET" request to "/short_custom/2/normalization" - Then the JSON should be equal to: - """ - { - "id": 2, - "foo": "short" - } - """ - - Scenario: Custom collection name without specific route - When I send a "GET" request to "/custom_action_collection_dummies" - Then the response status code should be 200 - Then the JSON node "hydra:member" should have 2 elements - - Scenario: Custom operation name without specific route - When I send a "GET" request to "/custom_action_collection_dummies/1" - Then the JSON should be equal to: - """ - { - "@context": "/contexts/CustomActionDummy", - "@id": "/custom_action_dummies/1", - "@type": "CustomActionDummy", - "id": 1, - "foo": "custom!" - } - """ diff --git a/features/main/custom_writable_identifier.feature b/features/main/custom_writable_identifier.feature deleted file mode 100644 index 9e141a563db..00000000000 --- a/features/main/custom_writable_identifier.feature +++ /dev/null @@ -1,110 +0,0 @@ -Feature: Using custom writable identifier on resource - In order to use an hypermedia API - As a client software developer - I need to be able to user other identifier than id in resource and set it via API call on POST / PUT. - - @createSchema - Scenario: Create a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/custom_writable_identifier_dummies" with body: - """ - { - "name": "My Dummy", - "slug": "my_slug" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/custom_writable_identifier_dummies/my_slug" - And the header "Location" should be equal to "/custom_writable_identifier_dummies/my_slug" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomWritableIdentifierDummy", - "@id": "/custom_writable_identifier_dummies/my_slug", - "@type": "CustomWritableIdentifierDummy", - "slug": "my_slug", - "name": "My Dummy" - } - """ - - Scenario: Get a resource - When I send a "GET" request to "/custom_writable_identifier_dummies/my_slug" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomWritableIdentifierDummy", - "@id": "/custom_writable_identifier_dummies/my_slug", - "@type": "CustomWritableIdentifierDummy", - "slug": "my_slug", - "name": "My Dummy" - } - """ - - Scenario: Get a collection - When I send a "GET" request to "/custom_writable_identifier_dummies" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomWritableIdentifierDummy", - "@id": "/custom_writable_identifier_dummies", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/custom_writable_identifier_dummies/my_slug", - "@type": "CustomWritableIdentifierDummy", - "slug": "my_slug", - "name": "My Dummy" - } - ], - "hydra:totalItems": 1 - } - """ - - Scenario: Update a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/custom_writable_identifier_dummies/my_slug" with body: - """ - { - "name": "My Dummy modified", - "slug": "slug_modified" - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/custom_writable_identifier_dummies/slug_modified" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomWritableIdentifierDummy", - "@id": "/custom_writable_identifier_dummies/slug_modified", - "@type": "CustomWritableIdentifierDummy", - "slug": "slug_modified", - "name": "My Dummy modified" - } - """ - - Scenario: API docs are correctly generated - When I send a "GET" request to "/docs.jsonld" - Then the response status code should be 200 - And the response should be in JSON - And the Hydra class "CustomWritableIdentifierDummy" exists - And 4 operations are available for Hydra class "CustomWritableIdentifierDummy" - And 2 properties are available for Hydra class "CustomWritableIdentifierDummy" - And "name" property is readable for Hydra class "CustomWritableIdentifierDummy" - And "name" property is writable for Hydra class "CustomWritableIdentifierDummy" - And "slug" property is readable for Hydra class "CustomWritableIdentifierDummy" - And "slug" property is writable for Hydra class "CustomWritableIdentifierDummy" - - Scenario: Delete a resource - When I send a "DELETE" request to "/custom_writable_identifier_dummies/slug_modified" - Then the response status code should be 204 - And the response should be empty diff --git a/features/main/default_order.feature b/features/main/default_order.feature deleted file mode 100644 index 6b39736eee2..00000000000 --- a/features/main/default_order.feature +++ /dev/null @@ -1,119 +0,0 @@ -Feature: Default order - In order to get a list in a specific order, - As a client software developer, - I need to be able to specify default order. - - @createSchema - Scenario: Override custom order - Given there are 5 foo objects with fake names - When I send a "GET" request to "/foos?itemsPerPage=10" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Foo", - "@id": "/foos", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/foos/5", - "@type": "Foo", - "id": 5, - "name": "Balbo", - "bar": "Amet" - }, - { - "@id": "/foos/3", - "@type": "Foo", - "id": 3, - "name": "Ephesian", - "bar": "Dolor" - }, - { - "@id": "/foos/2", - "@type": "Foo", - "id": 2, - "name": "Sthenelus", - "bar": "Ipsum" - }, - { - "@id": "/foos/1", - "@type": "Foo", - "id": 1, - "name": "Hawsepipe", - "bar": "Lorem" - }, - { - "@id": "/foos/4", - "@type": "Foo", - "id": 4, - "name": "Separativeness", - "bar": "Sit" - } - ], - "hydra:totalItems": 5, - "hydra:view": { - "@id": "/foos?itemsPerPage=10", - "@type": "hydra:PartialCollectionView" - } - } - """ - - Scenario: Override custom order by association - Given there are 5 fooDummy objects with fake names - When I send a "GET" request to "/foo_dummies?itemsPerPage=10" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/FooDummy", - "@id": "/foo_dummies", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/foo_dummies/5", - "@type": "FooDummy", - "id": 5, - "name": "Balbo", - "dummy": "/dummies/5" - }, - { - "@id": "/foo_dummies/3", - "@type": "FooDummy", - "id": 3, - "name": "Sthenelus", - "dummy": "/dummies/3" - }, - { - "@id": "/foo_dummies/2", - "@type": "FooDummy", - "id": 2, - "name": "Ephesian", - "dummy": "/dummies/2" - }, - { - "@id": "/foo_dummies/1", - "@type": "FooDummy", - "id": 1, - "name": "Hawsepipe", - "dummy": "/dummies/1" - }, - { - "@id": "/foo_dummies/4", - "@type": "FooDummy", - "id": 4, - "name": "Separativeness", - "dummy": "/dummies/4" - } - ], - "hydra:totalItems": 5, - "hydra:view": { - "@id": "/foo_dummies?itemsPerPage=10", - "@type": "hydra:PartialCollectionView" - } - } - """ diff --git a/features/main/exposed_state.feature b/features/main/exposed_state.feature deleted file mode 100644 index 9ff27875483..00000000000 --- a/features/main/exposed_state.feature +++ /dev/null @@ -1,47 +0,0 @@ -@postgres -Feature: Expose persisted object state - In order to use an hypermedia API - As a client software developer - I need to be able to retrieve the exact state of resources after persistence. - - @createSchema - Scenario: Create a resource with truncable value should return the correct object state - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/truncated_dummies" with body: - """ - { - "value": "20.3325" - } - """ - Then the response status code should be 201 - And the JSON should be equal to: - """ - { - "@context": "/contexts/TruncatedDummy", - "@id": "/truncated_dummies/1", - "@type": "TruncatedDummy", - "value": "20.3", - "id": 1 - } - """ - - @dropSchema - Scenario: Update a resource with truncable value value should return the correct object state - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/truncated_dummies/1" with body: - """ - { - "value": "42.42" - } - """ - Then the response status code should be 200 - And the JSON should be equal to: - """ - { - "@context": "/contexts/TruncatedDummy", - "@id": "/truncated_dummies/1", - "@type": "TruncatedDummy", - "value": "42.4", - "id": 1 - } - """ diff --git a/features/main/non_resource.feature b/features/main/non_resource.feature deleted file mode 100644 index 0cbe67ed308..00000000000 --- a/features/main/non_resource.feature +++ /dev/null @@ -1,30 +0,0 @@ -Feature: Non-resources handling - In order to handle use non-resource types - As a developer - I should be able serialize types not mapped to an API resource. - - Scenario: Get a resource containing a raw object - When I send a "GET" request to "/contain_non_resources/1" - Then the JSON should be equal to: - """ - { - "@context": "/contexts/ContainNonResource", - "@id": "/contain_non_resources/1", - "@type": "ContainNonResource", - "id": 1, - "nested": { - "@id": "/contain_non_resources/1-nested", - "@type": "ContainNonResource", - "id": "1-nested", - "nested": null, - "notAResource": { - "foo": "f2", - "bar": "b2" - } - }, - "notAResource": { - "foo": "f1", - "bar": "b1" - } - } - """ diff --git a/features/main/operation.feature b/features/main/operation.feature deleted file mode 100644 index 5a8aed2549c..00000000000 --- a/features/main/operation.feature +++ /dev/null @@ -1,59 +0,0 @@ -Feature: Operation support - In order to make the API fitting custom need - As an API developer - I need to be able to add custom operations and remove built-in ones - - @createSchema - @dropSchema - Scenario: Can not write readonly property - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/readable_only_properties" with body: - """ - { - "name": "My Dummy" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/ReadableOnlyProperty", - "@id": "/readable_only_properties/1", - "@type": "ReadableOnlyProperty", - "id": 1, - "name": "Read only" - } - """ - - Scenario: Access custom operations - When I send a "GET" request to "/relation_embedders/42/custom" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - "This is a custom action for 42." - """ - - @createSchema - @dropSchema - Scenario: Select a resource and it's embedded data - Given there are 1 embedded dummy objects - When I send a "GET" request to "/embedded_dummies_groups/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/EmbeddedDummy", - "@id": "/embedded_dummies/1", - "@type": "EmbeddedDummy", - "name": "Dummy #1", - "embeddedDummy": { - "dummyName": "Dummy #1" - } - } - """ diff --git a/features/main/overridden_operation.feature b/features/main/overridden_operation.feature deleted file mode 100644 index a7dec71e024..00000000000 --- a/features/main/overridden_operation.feature +++ /dev/null @@ -1,132 +0,0 @@ -Feature: Create-Retrieve-Update-Delete with a Overridden Operation context - In order to use an hypermedia API - As a client software developer - I need to be able to retrieve, create, update and delete JSON-LD encoded resources. - - @createSchema - Scenario: Create a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/overridden_operation_dummies" with body: - """ - { - "name": "My Overridden Operation Dummy", - "description" : "Gerard", - "alias": "notWritable" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/OverriddenOperationDummy", - "@id": "/overridden_operation_dummies/1", - "@type": "OverriddenOperationDummy", - "name": "My Overridden Operation Dummy", - "alias": null, - "description": "Gerard" - } - """ - - Scenario: Get a resource - When I send a "GET" request to "/overridden_operation_dummies/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/OverriddenOperationDummy", - "@id": "/overridden_operation_dummies/1", - "@type": "OverriddenOperationDummy", - "name": "My Overridden Operation Dummy", - "alias": null, - "description": "Gerard" - } - """ - - Scenario: Get a resource in XML - When I add "Accept" header equal to "application/xml" - And I send a "GET" request to "/overridden_operation_dummies/1" - Then the response status code should be 200 - And the header "Content-Type" should be equal to "application/xml; charset=utf-8" - And the response should be equal to - """ - - My Overridden Operation DummyGerard - """ - - Scenario: Get a not found exception - When I send a "GET" request to "/overridden_operation_dummies/42" - Then the response status code should be 404 - - Scenario: Get a collection - When I send a "GET" request to "/overridden_operation_dummies" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/OverriddenOperationDummy", - "@id": "/overridden_operation_dummies", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/overridden_operation_dummies/1", - "@type": "OverriddenOperationDummy", - "name": "My Overridden Operation Dummy", - "alias": null, - "description": "Gerard" - } - ], - "hydra:totalItems": 1 - } - """ - - Scenario: Update a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/overridden_operation_dummies/1" with body: - """ - { - "@id": "/overridden_operation_dummies/1", - "name": "A nice dummy", - "alias": "Dummy" - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/OverriddenOperationDummy", - "@id": "/overridden_operation_dummies/1", - "@type": "OverriddenOperationDummy", - "alias": "Dummy", - "description": "Gerard" - } - """ - - Scenario: Get the final resource - When I send a "GET" request to "/overridden_operation_dummies/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/OverriddenOperationDummy", - "@id": "/overridden_operation_dummies/1", - "@type": "OverriddenOperationDummy", - "name": "My Overridden Operation Dummy", - "alias": "Dummy", - "description": "Gerard" - } - """ - - Scenario: Delete a resource - When I send a "DELETE" request to "/overridden_operation_dummies/1" - Then the response status code should be 204 - And the response should be empty diff --git a/features/main/relation.feature b/features/main/relation.feature deleted file mode 100644 index 46e385709e9..00000000000 --- a/features/main/relation.feature +++ /dev/null @@ -1,584 +0,0 @@ -Feature: Relations support - In order to use a hypermedia API - As a client software developer - I need to be able to update relations between resources - - @createSchema - Scenario: Create a third level - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/third_levels" with body: - """ - {"level": 3} - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/ThirdLevel", - "@id": "/third_levels/1", - "@type": "ThirdLevel", - "fourthLevel": null, - "id": 1, - "level": 3, - "test": true - } - """ - - Scenario: Create a dummy friend - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_friends" with body: - """ - {"name": "Zoidberg"} - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/DummyFriend", - "@id": "/dummy_friends/1", - "@type": "DummyFriend", - "id": 1, - "name": "Zoidberg" - } - """ - - Scenario: Create a related dummy - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/related_dummies" with body: - """ - {"thirdLevel": "/third_levels/1"} - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelatedDummy", - "@id": "/related_dummies/1", - "@type": "https://schema.org/Product", - "id": 1, - "name": null, - "symfony": "symfony", - "dummyDate": null, - "thirdLevel": { - "@id": "/third_levels/1", - "@type": "ThirdLevel", - "fourthLevel": null - }, - "relatedToDummyFriend": [], - "dummyBoolean": null, - "embeddedDummy": [], - "age": null - } - """ - - Scenario: Create a friend relationship - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/related_to_dummy_friends" with body: - """ - { - "name": "Friends relation", - "dummyFriend": "/dummy_friends/1", - "relatedDummy": "/related_dummies/1" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelatedToDummyFriend", - "@id": "/related_to_dummy_friends/dummyFriend=1;relatedDummy=1", - "@type": "RelatedToDummyFriend", - "name": "Friends relation", - "description": null, - "dummyFriend": { - "@id": "/dummy_friends/1", - "@type": "DummyFriend", - "name": "Zoidberg" - } - } - """ - - Scenario: Get the relationship - When I send a "GET" request to "/related_to_dummy_friends/dummyFriend=1;relatedDummy=1" - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelatedToDummyFriend", - "@id": "/related_to_dummy_friends/dummyFriend=1;relatedDummy=1", - "@type": "RelatedToDummyFriend", - "name": "Friends relation", - "description": null, - "dummyFriend": { - "@id": "/dummy_friends/1", - "@type": "DummyFriend", - "name": "Zoidberg" - } - } - """ - - Scenario: Create a dummy with relations - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Dummy with relations", - "relatedDummy": "http://example.com/related_dummies/1", - "relatedDummies": [ - "/related_dummies/1" - ], - "name_converted": null - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Dummy", - "@id": "/dummies/1", - "@type": "Dummy", - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "relatedDummy": "/related_dummies/1", - "relatedDummies": [ - "/related_dummies/1" - ], - "jsonData": [], - "arrayData": [], - "name_converted": null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id": 1, - "name": "Dummy with relations", - "alias": null, - "foo": null - } - """ - - Scenario: Filter on a relation - When I send a "GET" request to "/dummies?relatedDummy=%2Frelated_dummies%2F1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 1}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies/1$"} - } - }, - "maxItems": 1 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?relatedDummy=%2Frelated_dummies%2F1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Filter on a to-many relation - When I send a "GET" request to "/dummies?relatedDummies[]=%2Frelated_dummies%2F1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/Dummy$"}, - "@id": {"pattern": "^/dummies$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:totalItems": {"type":"number", "maximum": 1}, - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies/1$"} - } - }, - "maxItems": 1 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummies\\?relatedDummies%5B%5D=%2Frelated_dummies%2F1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Embed a relation in the parent object - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "related": "/related_dummies/1" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelationEmbedder", - "@id": "/relation_embedders/1", - "@type": "RelationEmbedder", - "krondstadt": "Krondstadt", - "anotherRelated": null, - "related": { - "@id": "/related_dummies/1", - "@type": "https://schema.org/Product", - "symfony": "symfony", - "thirdLevel": { - "@id": "/third_levels/1", - "@type": "ThirdLevel", - "level": 3, - "fourthLevel": null - } - } - } - """ - - Scenario: Create an existing relation - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "anotherRelated": { - "symfony": "laravel" - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelationEmbedder", - "@id": "/relation_embedders/2", - "@type": "RelationEmbedder", - "krondstadt": "Krondstadt", - "anotherRelated": { - "@id": "/related_dummies/2", - "@type": "https://schema.org/Product", - "symfony": "laravel", - "thirdLevel": null - }, - "related": null - } - """ - - Scenario: Update the relation with a new one - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/relation_embedders/2" with body: - """ - { - "anotherRelated": { - "symfony": "laravel2" - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelationEmbedder", - "@id": "/relation_embedders/2", - "@type": "RelationEmbedder", - "krondstadt": "Krondstadt", - "anotherRelated": { - "@id": "/related_dummies/3", - "@type": "https://schema.org/Product", - "symfony": "laravel2", - "thirdLevel": null - }, - "related": null - } - """ - - Scenario: Post a wrong relation - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "anotherRelated": { - "@id": "/related_dummies/123", - "@type": "https://schema.org/Product", - "symfony": "phalcon" - } - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - Scenario: Post a relation with a not existing IRI - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "related": "/related_dummies/123" - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - Scenario: Create a new relation (json) - When I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "anotherRelated": { - "symfony": "laravel" - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelationEmbedder", - "@id": "/relation_embedders/3", - "@type": "RelationEmbedder", - "krondstadt": "Krondstadt", - "anotherRelated": { - "@id": "/related_dummies/4", - "@type": "https://schema.org/Product", - "symfony": "laravel", - "thirdLevel": null - }, - "related": null - } - """ - - Scenario: Update the relation with a new one (json) - When I add "Content-Type" header equal to "application/json" - And I send a "PUT" request to "/relation_embedders/3" with body: - """ - { - "anotherRelated": { - "symfony": "laravel2" - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelationEmbedder", - "@id": "/relation_embedders/3", - "@type": "RelationEmbedder", - "krondstadt": "Krondstadt", - "anotherRelated": { - "@id": "/related_dummies/5", - "@type": "https://schema.org/Product", - "symfony": "laravel2", - "thirdLevel": null - }, - "related": null - } - """ - - Scenario: Update an embedded relation - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/relation_embedders/2" with body: - """ - { - "anotherRelated": { - "@id": "/related_dummies/2", - "symfony": "API Platform" - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelationEmbedder", - "@id": "/relation_embedders/2", - "@type": "RelationEmbedder", - "krondstadt": "Krondstadt", - "anotherRelated": { - "@id": "/related_dummies/2", - "@type": "https://schema.org/Product", - "symfony": "API Platform", - "thirdLevel": null - }, - "related": null - } - """ - - Scenario: Create a related dummy with a relation (json) - When I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/related_dummies" with body: - """ - {"thirdLevel": "1"} - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelatedDummy", - "@id": "/related_dummies/6", - "@type": "https://schema.org/Product", - "id": 6, - "name": null, - "symfony": "symfony", - "dummyDate": null, - "thirdLevel": { - "@id": "/third_levels/1", - "@type": "ThirdLevel", - "fourthLevel": null - }, - "relatedToDummyFriend": [], - "dummyBoolean": null, - "embeddedDummy": [], - "age": null - } - """ - - Scenario: Issue #1222 - Given there are people having pets - When I add "Content-Type" header equal to "application/ld+json" - And I send a "GET" request to "/people" - And the response status code should be 200 - And the response should be in JSON - And the JSON should be equal to: - """ - { - "@context": "/contexts/Person", - "@id": "/people", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/people/1", - "@type": "Person", - "name": "foo", - "pets": [ - { - "pet": { - "@id": "/pets/1", - "@type": "Pet", - "name": "bar" - } - } - ] - } - ], - "hydra:totalItems": 1 - } - """ - - Scenario: Passing a (valid) plain identifier on a relation - When I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/dummies" with body: - """ - { - "relatedDummy": "1", - "relatedDummies": ["1"], - "name": "Dummy with plain relations" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context":"/contexts/Dummy", - "@id":"/dummies/2", - "@type":"Dummy", - "description":null, - "dummy":null, - "dummyBoolean":null, - "dummyDate":null, - "dummyFloat":null, - "dummyPrice":null, - "relatedDummy":"/related_dummies/1", - "relatedDummies":["/related_dummies/1"], - "jsonData":[], - "arrayData":[], - "name_converted":null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id":2, - "name":"Dummy with plain relations", - "alias":null, - "foo":null - } - """ - - @dropSchema - Scenario: Passing an invalid IRI to a relation - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "related": "certainly not an iri and not a plain identifier" - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "hydra:description" should contain "Invalid value provided (invalid IRI?)." - - @dropSchema - Scenario: Passing an invalid type to a relation - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/relation_embedders" with body: - """ - { - "related": 8 - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "hydra:description" should contain "Invalid value provided (invalid IRI?)." diff --git a/features/main/serializable_item_data_provider.feature b/features/main/serializable_item_data_provider.feature deleted file mode 100644 index 11a111cf101..00000000000 --- a/features/main/serializable_item_data_provider.feature +++ /dev/null @@ -1,18 +0,0 @@ -Feature: Serializable item data provider - In order to call any external API - As a developer - I should be able to serialize the response directly from the ItemDataProvider. - - Scenario: Get a resource containing a raw object - When I send a "GET" request to "/serializable_resources/1" - Then the JSON should be equal to: - """ - { - "@context": "/contexts/SerializableResource", - "@id": "/serializable_resources/1", - "@type": "SerializableResource", - "id": 1, - "foo": "Lorem", - "bar": "Ipsum" - } - """ diff --git a/features/main/subresource.feature b/features/main/subresource.feature deleted file mode 100644 index 6761b90cb16..00000000000 --- a/features/main/subresource.feature +++ /dev/null @@ -1,367 +0,0 @@ -Feature: Subresource support - In order to use a hypermedia API - As a client software developer - I need to be able to retrieve embedded resources only as Subresources - - @createSchema - Scenario: Get subresource one to one relation - Given there is an answer "42" to the question "What's the answer to the Ultimate Question of Life, the Universe and Everything?" - When I send a "GET" request to "/questions/1/answer" - And the response status code should be 200 - And the response should be in JSON - And the JSON should be equal to: - """ - { - "@context": "/contexts/Answer", - "@id": "/answers/1", - "@type": "Answer", - "id": 1, - "content": "42", - "question": "/questions/1", - "relatedQuestions": [ - "/questions/1" - ] - } - """ - - Scenario: Get a non existant subresource - Given there is an answer "42" to the question "What's the answer to the Ultimate Question of Life, the Universe and Everything?" - When I send a "GET" request to "/questions/999999/answer" - And the response status code should be 404 - And the response should be in JSON - - Scenario: Get recursive subresource one to many relation - When I send a "GET" request to "/questions/1/answer/related_questions" - And the response status code should be 200 - And the response should be in JSON - And the JSON should be equal to: - """ - { - "@context": "/contexts/Question", - "@id": "/questions/1/answer/related_questions", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/questions/1", - "@type": "Question", - "content": "What's the answer to the Ultimate Question of Life, the Universe and Everything?", - "id": 1, - "answer": "/answers/1" - } - ], - "hydra:totalItems": 1 - } - """ - - Scenario: Get the subresource relation collection - Given there is a dummy object with a fourth level relation - When I send a "GET" request to "/dummies/1/related_dummies" - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelatedDummy", - "@id": "/dummies/1/related_dummies", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/related_dummies/1", - "@type": "https://schema.org/Product", - "id": 1, - "name": "Hello", - "symfony": "symfony", - "dummyDate": null, - "thirdLevel": { - "@id": "/third_levels/1", - "@type": "ThirdLevel", - "fourthLevel": "/fourth_levels/1" - }, - "relatedToDummyFriend": [], - "dummyBoolean": null, - "embeddedDummy": [], - "age": null - }, - { - "@id": "/related_dummies/2", - "@type": "https://schema.org/Product", - "id": 2, - "name": null, - "symfony": "symfony", - "dummyDate": null, - "thirdLevel": { - "@id": "/third_levels/1", - "@type": "ThirdLevel", - "fourthLevel": "/fourth_levels/1" - }, - "relatedToDummyFriend": [], - "dummyBoolean": null, - "embeddedDummy": [], - "age": null - } - ], - "hydra:totalItems": 2, - "hydra:search": { - "@type": "hydra:IriTemplate", - "hydra:template": "/dummies/1/related_dummies{?relatedToDummyFriend.dummyFriend,relatedToDummyFriend.dummyFriend[],name}", - "hydra:variableRepresentation": "BasicRepresentation", - "hydra:mapping": [ - { - "@type": "IriTemplateMapping", - "variable": "relatedToDummyFriend.dummyFriend", - "property": "relatedToDummyFriend.dummyFriend", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedToDummyFriend.dummyFriend[]", - "property": "relatedToDummyFriend.dummyFriend", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "name", - "property": "name", - "required": false - } - ] - } - } - """ - - Scenario: Get filtered embedded relation subresource collection - When I send a "GET" request to "/dummies/1/related_dummies?name=Hello" - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelatedDummy", - "@id": "/dummies/1/related_dummies", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/related_dummies/1", - "@type": "https://schema.org/Product", - "id": 1, - "name": "Hello", - "symfony": "symfony", - "dummyDate": null, - "thirdLevel": { - "@id": "/third_levels/1", - "@type": "ThirdLevel", - "fourthLevel": "/fourth_levels/1" - }, - "relatedToDummyFriend": [], - "dummyBoolean": null, - "embeddedDummy": [], - "age": null - } - ], - "hydra:totalItems": 1, - "hydra:view": { - "@id": "/dummies/1/related_dummies?name=Hello", - "@type": "hydra:PartialCollectionView" - }, - "hydra:search": { - "@type": "hydra:IriTemplate", - "hydra:template": "/dummies/1/related_dummies{?relatedToDummyFriend.dummyFriend,relatedToDummyFriend.dummyFriend[],name}", - "hydra:variableRepresentation": "BasicRepresentation", - "hydra:mapping": [ - { - "@type": "IriTemplateMapping", - "variable": "relatedToDummyFriend.dummyFriend", - "property": "relatedToDummyFriend.dummyFriend", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "relatedToDummyFriend.dummyFriend[]", - "property": "relatedToDummyFriend.dummyFriend", - "required": false - }, - { - "@type": "IriTemplateMapping", - "variable": "name", - "property": "name", - "required": false - } - ] - } - } - """ - - Scenario: Get the subresource relation item - When I send a "GET" request to "/dummies/1/related_dummies/2" - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/RelatedDummy", - "@id": "/related_dummies/2", - "@type": "https://schema.org/Product", - "id": 2, - "name": null, - "symfony": "symfony", - "dummyDate": null, - "thirdLevel": { - "@id": "/third_levels/1", - "@type": "ThirdLevel", - "fourthLevel": "/fourth_levels/1" - }, - "relatedToDummyFriend": [], - "dummyBoolean": null, - "embeddedDummy": [], - "age": null - } - """ - - Scenario: Create a dummy with a relation that is a subresource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Dummy with relations", - "relatedDummy": "/dummies/1/related_dummies/2" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - Scenario: Get the embedded relation subresource item at the third level - When I send a "GET" request to "/dummies/1/related_dummies/1/third_level" - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/ThirdLevel", - "@id": "/third_levels/1", - "@type": "ThirdLevel", - "fourthLevel": "/fourth_levels/1", - "id": 1, - "level": 3, - "test": true - } - """ - - Scenario: Get the embedded relation subresource item at the fourth level - When I send a "GET" request to "/dummies/1/related_dummies/1/third_level/fourth_level" - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/FourthLevel", - "@id": "/fourth_levels/1", - "@type": "FourthLevel", - "id": 1, - "level": 4 - } - """ - - Scenario: Get offers subresource from aggregate offers subresource - Given I have a product with offers - When I send a "GET" request to "/dummy_products/2/offers/1/offers" - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/DummyOffer", - "@id": "/dummy_products/2/offers/1/offers", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/dummy_offers/1", - "@type": "DummyOffer", - "id": 1, - "value": 2, - "aggregate": "/dummy_aggregate_offers/1" - } - ], - "hydra:totalItems": 1 - } - """ - - Scenario: Get offers subresource from aggregate offers subresource - When I send a "GET" request to "/dummy_aggregate_offers/1/offers" - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/DummyOffer", - "@id": "/dummy_aggregate_offers/1/offers", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/dummy_offers/1", - "@type": "DummyOffer", - "id": 1, - "value": 2, - "aggregate": "/dummy_aggregate_offers/1" - } - ], - "hydra:totalItems": 1 - } - """ - - Scenario: The recipient of the person's greetings should be empty - Given there is a person named "Alice" greeting with a "hello" message - When I send a "GET" request to "/people/1/sent_greetings" - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Greeting", - "@id": "/people/1/sent_greetings", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/greetings/1", - "@type": "Greeting", - "message": "hello", - "sender": "/people/1", - "recipient": null, - "id": 1 - } - ], - "hydra:totalItems": 1 - } - """ - - @dropSchema - Scenario: Recursive resource - When I send a "GET" request to "/dummy_products/2" - And the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/DummyProduct", - "@id": "/dummy_products/2", - "@type": "DummyProduct", - "offers": [ - "/dummy_aggregate_offers/1" - ], - "id": 2, - "name": "Dummy product", - "relatedProducts": [ - "/dummy_products/1" - ], - "parent": null - } - """ diff --git a/features/main/table_inheritance.feature b/features/main/table_inheritance.feature deleted file mode 100644 index a4bc49a9dcd..00000000000 --- a/features/main/table_inheritance.feature +++ /dev/null @@ -1,325 +0,0 @@ -Feature: Table inheritance - In order to use the api with Doctrine table inheritance - As a client software developer - I need to be able to create resources and fetch them on the upper entity - - @createSchema - Scenario: Create a table inherited resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_table_inheritance_children" with body: - """ - {"name": "foo", "nickname": "bar"} - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@type": { - "type": "string", - "pattern": "^DummyTableInheritanceChild$" - }, - "@context": { - "type": "string", - "pattern": "^/contexts/DummyTableInheritanceChild$" - }, - "@id": { - "type": "string", - "pattern": "^/dummy_table_inheritance_children/1$" - }, - "name": { - "type": "string", - "pattern": "^foo$", - "required": "true" - }, - "nickname": { - "type": "string", - "pattern": "^bar$", - "required": "true" - } - } - } - """ - - Scenario: Get the parent entity collection - When I send a "GET" request to "/dummy_table_inheritances" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@type": { - "type": "string", - "pattern": "^DummyTableInheritanceChild$" - }, - "name": { - "type": "string", - "required": "true" - }, - "nickname": { - "type": "string", - "required": "true" - } - } - }, - "minItems": 1 - } - }, - "required": ["hydra:member"] - } - """ - - @createSchema - Scenario: Create a table inherited resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_table_inheritance_children" with body: - """ - {"name": "foo", "nickname": "bar"} - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@type": { - "type": "string", - "pattern": "^DummyTableInheritanceChild$" - }, - "@context": { - "type": "string", - "pattern": "^/contexts/DummyTableInheritanceChild$" - }, - "@id": { - "type": "string", - "pattern": "^/dummy_table_inheritance_children/1$" - }, - "name": { - "type": "string", - "pattern": "^foo$", - "required": "true" - }, - "nickname": { - "type": "string", - "pattern": "^bar$", - "required": "true" - } - } - } - """ - - Scenario: Create a different table inherited resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_table_inheritance_different_children" with body: - """ - {"name": "foo", "email": "bar@localhost"} - """ - Then the response status code should be 201 - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@type": { - "type": "string", - "pattern": "^DummyTableInheritanceDifferentChild$" - }, - "@context": { - "type": "string", - "pattern": "^/contexts/DummyTableInheritanceDifferentChild$" - }, - "@id": { - "type": "string", - "pattern": "^/dummy_table_inheritance_different_children/2$" - }, - "name": { - "type": "string", - "pattern": "^foo$", - "required": "true" - }, - "email": { - "type": "string", - "pattern": "^bar\\@localhost$", - "required": "true" - } - } - } - """ - - Scenario: Get related entity with multiple inherited children types - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_table_inheritance_relateds" with body: - """ - { - "children": [ - "/dummy_table_inheritance_children/1", - "/dummy_table_inheritance_different_children/2" - ] - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@type": { - "type": "string", - "pattern": "^DummyTableInheritanceRelated$" - }, - "@context": { - "type": "string", - "pattern": "^/contexts/DummyTableInheritanceRelated$" - }, - "@id": { - "type": "string", - "pattern": "^/dummy_table_inheritance_relateds/1$" - }, - "children": { - "items": { - "type": "object", - "anyOf": [ - { - "properties": { - "@type": { - "type": "string", - "pattern": "^DummyTableInheritanceChild$" - }, - "name": { - "type": "string", - "required": "true" - }, - "nickname": { - "type": "string", - "required": "true" - } - } - }, - { - "properties": { - "@type": { - "type": "string", - "pattern": "^DummyTableInheritanceDifferentChild$" - }, - "name": { - "type": "string", - "required": "true" - }, - "email": { - "type": "string", - "required": "true" - } - } - } - ] - }, - "minItems": 2, - "maxItems": 2 - } - } - } - """ - - Scenario: Get the parent entity collection which contains multiple inherited children type - When I send a "GET" request to "/dummy_table_inheritances" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "anyOf": [ - { - "properties": { - "@type": { - "type": "string", - "pattern": "^DummyTableInheritanceChild$" - }, - "name": { - "type": "string", - "required": "true" - }, - "nickname": { - "type": "string", - "required": "true" - } - } - }, - { - "properties": { - "@type": { - "type": "string", - "pattern": "^DummyTableInheritanceDifferentChild$" - }, - "name": { - "type": "string", - "required": "true" - }, - "email": { - "type": "string", - "required": "true" - } - } - } - ] - }, - "minItems": 2 - } - }, - "required": ["hydra:member"] - } - """ - - Scenario: Get the parent interface collection - When I send a "GET" request to "/resource_interfaces" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "hydra:member": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@type": { - "type": "string", - "pattern": "^ResourceInterface$" - }, - "foo": { - "type": "string", - "required": "true" - } - } - }, - "minItems": 1 - } - }, - "required": ["hydra:member"] - } - """ diff --git a/features/main/uuid.feature b/features/main/uuid.feature deleted file mode 100644 index 2a599e49cd8..00000000000 --- a/features/main/uuid.feature +++ /dev/null @@ -1,129 +0,0 @@ -Feature: Using uuid identifier on resource - In order to use an hypermedia API - As a client software developer - I need to be able to user other identifier than id in resource and set it via API call on POST / PUT. - - @createSchema - Scenario: Create a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/uuid_identifier_dummies" with body: - """ - { - "name": "My Dummy", - "uuid": "41b29566-144b-11e6-a148-3e1d05defe78" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78" - And the header "Location" should be equal to "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78" - - Scenario: Get a resource - When I send a "GET" request to "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/UuidIdentifierDummy", - "@id": "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78", - "@type": "UuidIdentifierDummy", - "uuid": "41b29566-144b-11e6-a148-3e1d05defe78", - "name": "My Dummy" - } - """ - - Scenario: Get a collection - When I send a "GET" request to "/uuid_identifier_dummies" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/UuidIdentifierDummy", - "@id": "/uuid_identifier_dummies", - "@type": "hydra:Collection", - "hydra:member": [ - { - "@id": "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78", - "@type": "UuidIdentifierDummy", - "uuid": "41b29566-144b-11e6-a148-3e1d05defe78", - "name": "My Dummy" - } - ], - "hydra:totalItems": 1 - } - """ - - Scenario: Update a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78" with body: - """ - { - "name": "My Dummy modified" - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78" - And the JSON should be equal to: - """ - { - "@context": "/contexts/UuidIdentifierDummy", - "@id": "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78", - "@type": "UuidIdentifierDummy", - "uuid": "41b29566-144b-11e6-a148-3e1d05defe78", - "name": "My Dummy modified" - } - """ - - Scenario: Create a resource with custom id generator - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/custom_generated_identifiers" with body: - """ - {} - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "Content-Location" should be equal to "/custom_generated_identifiers/foo" - And the header "Location" should be equal to "/custom_generated_identifiers/foo" - And the JSON should be equal to: - """ - { - "@context": "/contexts/CustomGeneratedIdentifier", - "@id": "/custom_generated_identifiers/foo", - "@type": "CustomGeneratedIdentifier", - "id": "foo" - } - """ - - Scenario: Delete a resource - When I send a "DELETE" request to "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78" - Then the response status code should be 204 - And the response should be empty - - @createSchema - Scenario: Retrieve a resource identified by Ramsey\Uuid\Uuid - Given there is a ramsey identified resource with uuid "41B29566-144B-11E6-A148-3E1D05DEFE78" - When I add "Content-Type" header equal to "application/ld+json" - And I send a "GET" request to "/ramsey_uuid_dummies/41B29566-144B-11E6-A148-3E1D05DEFE78" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - Scenario: Delete a resource identified by a Ramsey\Uuid\Uuid - When I send a "DELETE" request to "/ramsey_uuid_dummies/41B29566-144B-11E6-A148-3E1D05DEFE78" - Then the response status code should be 204 - And the response should be empty - - Scenario: Retrieve a resource identified by a bad Ramsey\Uuid\Uuid - When I add "Content-Type" header equal to "application/ld+json" - And I send a "GET" request to "/ramsey_uuid_dummies/41B29566-144B-E1D05DEFE78" - Then the response status code should be 404 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" diff --git a/features/main/validation.feature b/features/main/validation.feature deleted file mode 100644 index f2694a3e90d..00000000000 --- a/features/main/validation.feature +++ /dev/null @@ -1,75 +0,0 @@ -Feature: Using validations groups - As a client software developer - I need to be able to use validation groups - - @createSchema - @dropSchema - Scenario: Create a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_validation" with body: - """ - { - "code": "My Dummy" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - @createSchema - @dropSchema - Scenario: Create a resource with validation - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_validation/validation_groups" with body: - """ - { - "code": "My Dummy" - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/ConstraintViolationList", - "@type": "ConstraintViolationList", - "hydra:title": "An error occurred", - "hydra:description": "name: This value should not be null.", - "violations": [ - { - "propertyPath": "name", - "message": "This value should not be null." - } - ] - } - """ - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - @createSchema - @dropSchema - Scenario: Create a resource with validation group sequence - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_validation/validation_sequence" with body: - """ - { - "code": "My Dummy" - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/ConstraintViolationList", - "@type": "ConstraintViolationList", - "hydra:title": "An error occurred", - "hydra:description": "title: This value should not be null.", - "violations": [ - { - "propertyPath": "title", - "message": "This value should not be null." - } - ] - } - """ - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" diff --git a/features/security/README.md b/features/security/README.md deleted file mode 100644 index ba1803ac3f3..00000000000 --- a/features/security/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Security tests - -This directory contains a list of tests proving that API Platform enforces [OWASP's recommendations for REST APIs](https://www.owasp.org/index.php/REST_Security_Cheat_Sheet). -If you find a vulnerability in API Platform, please report it according to the procedure detailed in the [CONTRIBUTING.md](../../CONTRIBUTING.md) -file. - -## Authentication and session management - -Authentication and session management is delegated to the [Symfony Security component](http://symfony.com/doc/current/components/security.html). -This component has its own test suite. - -## Authorization - -Authorization is delegated to the [Symfony Security component](http://symfony.com/doc/current/components/security.html). -This component has its own test suite. - -## Input validation - -### Input validation 101 - -Input validation is delegated to the [Symfony Validator component](http://symfony.com/doc/current/components/validator.html) -(an implementation of the [JSR-303 Bean Validation specification](https://jcp.org/en/jsr/detail?id=303). -This component has its own test suite. - -### Secure parsing - -Parsing is delegated to the [Symfony Serializer component](http://symfony.com/doc/current/components/serializer.html). -This component has its own test suite. - -### Strong typing - -Strong typing is ensured by [our "strong typing" functional test suite](strong_typing.feature) and [the unit tests of the `AbstractItemNormalizer` -class](../../tests/Serializer/AbstractItemNormalizerTest.php). - -You might also be interested to see [how extra attributes are ignored](unknown_attributes.feature). - -### Validate incoming content-types - -Incoming content-types validation is ensured by [our "validate incoming content-types" functional test suite](validate_incoming_content-types.feature) and [the unit tests of the `DeserializeListener` -class](../../tests/EventListener/DeserializeListenerTest.php). - -### Validate response types - -Response type validation is ensured by [our "validate response types" functional test suite](validate_response_types.feature) -and [the unit tests of the `AddFormatListener` class](../../tests/EventListener/AddFormatListenerTest.php). - -### XML input validation - -XML parsing is delegated to the [Symfony Serializer component](http://symfony.com/doc/current/components/serializer.html). -This component has its own test suite. - -### Framework-Provided validation - -API Platform is shipped with the [Symfony Validator component](http://symfony.com/doc/current/components/validator.html), -one of the most popular framework validation in the world. - -## Output encoding - -### Send security headers - -The sending of security headers is ensured by [our "send security headers" functional test suite](send_security_headers.feature) -and the unit tests of the [`RespondListener`](../../tests/EventListener/RespondListenerTest.php), [`ExceptionAction`](../../tests/Action/ExceptionActionTest.php) -and [`ValidationExceptionListener`](../../tests/Bridge/Symfony/Validation/EventListener/ValidationExceptionListenerTest.php). - -### JSON encoding - -API Platform relies on the [Symfony Serializer component](http://symfony.com/doc/current/components/serializer.html), to -encode JSON. -This component has its own test suite. - -### XML encoding - -API Platform relies on the [Symfony Serializer component](http://symfony.com/doc/current/components/serializer.html), to -encode XML. -This component has its own test suite. - -## Cryptography - -Cryptography for transit and storage should be enabled and properly configured on your servers depending of the nature of -you application. -API Platform natively supports both HTTPS (always recommended) and HTTP (for read-only public data only). - -## Message Integrity - -API Platform relies on the [LexikJWTAuthenticationBundle](https://github.com/lexik/LexikJWTAuthenticationBundle), -for JWT support. -This bundle and the underlying [JSON Object Signing and Encryption library for PHP](https://github.com/namshi/jose) library have their own test suites. - -## HTTP Return Code - -Setting proper HTTP return codes is delegated to the [Symfony Security component](http://symfony.com/doc/current/components/security.html). -This component has its own test suite. diff --git a/features/security/send_security_headers.feature b/features/security/send_security_headers.feature deleted file mode 100644 index d4b91f77491..00000000000 --- a/features/security/send_security_headers.feature +++ /dev/null @@ -1,33 +0,0 @@ -Feature: Send security header - In order to have secure API - As a client software developer - The API must send correct HTTP headers - - @createSchema - Scenario: API responses must always contain security headers - When I send a "GET" request to "/dummies" - Then the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "X-Content-Type-Options" should be equal to "nosniff" - And the header "X-Frame-Options" should be equal to "deny" - - Scenario: Exceptions responses must always contain security headers - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - {"name": 1} - """ - Then the response status code should be 400 - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "X-Content-Type-Options" should be equal to "nosniff" - And the header "X-Frame-Options" should be equal to "deny" - - Scenario: Error validation responses must always contain security headers - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - {"name": ""} - """ - Then the response status code should be 400 - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the header "X-Content-Type-Options" should be equal to "nosniff" - And the header "X-Frame-Options" should be equal to "deny" diff --git a/features/security/strong_typing.feature b/features/security/strong_typing.feature deleted file mode 100644 index ac5b8976ff9..00000000000 --- a/features/security/strong_typing.feature +++ /dev/null @@ -1,138 +0,0 @@ -Feature: Handle properly invalid data submitted to the API - In order to have robust API - As a client software developer - The API must enforce strong typing - - @createSchema - Scenario: Create a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Not existing", - "unsupported": true - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Dummy", - "@id": "/dummies/1", - "@type": "Dummy", - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "relatedDummy": null, - "relatedDummies": [], - "jsonData": [], - "arrayData": [], - "name_converted": null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id": 1, - "name": "Not existing", - "alias": null, - "foo": null - } - """ - - Scenario: Create a resource with wrong value type for relation - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Foo", - "relatedDummy": "1" - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "@context" should be equal to "/contexts/Error" - And the JSON node "@type" should be equal to "hydra:Error" - And the JSON node "hydra:title" should be equal to "An error occurred" - And the JSON node "hydra:description" should be equal to 'Expected IRI or nested document for attribute "relatedDummy", "string" given.' - And the JSON node "trace" should exist - - Scenario: Ignore invalid dates - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Invalid date", - "dummyDate": "Invalid" - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - Scenario: Send non-array data when an array is expected - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Invalid", - "relatedDummies": "hello" - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "@context" should be equal to "/contexts/Error" - And the JSON node "@type" should be equal to "hydra:Error" - And the JSON node "hydra:title" should be equal to "An error occurred" - And the JSON node "hydra:description" should be equal to 'The type of the "relatedDummies" attribute must be "array", "string" given.' - And the JSON node "trace" should exist - - Scenario: Send an object where an array is expected - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Invalid", - "relatedDummies": {"a": {}, "b": {}} - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "@context" should be equal to "/contexts/Error" - And the JSON node "@type" should be equal to "hydra:Error" - And the JSON node "hydra:title" should be equal to "An error occurred" - And the JSON node "hydra:description" should be equal to 'The type of the key "a" must be "int", "string" given.' - - Scenario: Send a scalar having the bad type - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": 42 - } - """ - Then the response status code should be 400 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "@context" should be equal to "/contexts/Error" - And the JSON node "@type" should be equal to "hydra:Error" - And the JSON node "hydra:title" should be equal to "An error occurred" - And the JSON node "hydra:description" should be equal to 'The type of the "name" attribute must be "string", "integer" given.' - - Scenario: According to the JSON spec, allow numbers without explicit floating point for JSON formats - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "foo", - "dummyFloat": 42 - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" diff --git a/features/security/unknown_attributes.feature b/features/security/unknown_attributes.feature deleted file mode 100644 index efe7b954c0a..00000000000 --- a/features/security/unknown_attributes.feature +++ /dev/null @@ -1,43 +0,0 @@ -Feature: Ignore unknown attributes - In order to be robust - As a client software developer - I can send unsupported attributes that will be ignored - - @createSchema - Scenario: Create a resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Not existing", - "unsupported": true - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/Dummy", - "@id": "/dummies/1", - "@type": "Dummy", - "description": null, - "dummy": null, - "dummyBoolean": null, - "dummyDate": null, - "dummyFloat": null, - "dummyPrice": null, - "relatedDummy": null, - "relatedDummies": [], - "jsonData": [], - "arrayData": [], - "name_converted": null, - "relatedOwnedDummy": null, - "relatedOwningDummy": null, - "id": 1, - "name": "Not existing", - "alias": null, - "foo": null - } - """ diff --git a/features/security/validate_incoming_content-types.feature b/features/security/validate_incoming_content-types.feature deleted file mode 100644 index 71d981c089f..00000000000 --- a/features/security/validate_incoming_content-types.feature +++ /dev/null @@ -1,16 +0,0 @@ -Feature: Validate incoming content type - In order to have robust API - As a client software developer - The API must check incoming the content-type - - # It's not possible to omit the Content-Type with Behat. A unit test enforce that a 406 error code is returned in such case. - - Scenario: Send a document with a not supported content-type - When I add "Content-Type" header equal to "text/plain" - And I send a "POST" request to "/dummies" with body: - """ - something - """ - Then the response status code should be 406 - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON node "hydra:description" should be equal to 'The content-type "text/plain" is not supported. Supported MIME types are "application/ld+json", "application/hal+json", "application/vnd.api+json", "application/xml", "text/xml", "application/json", "text/html".' diff --git a/features/security/validate_response_types.feature b/features/security/validate_response_types.feature deleted file mode 100644 index de2ab033ead..00000000000 --- a/features/security/validate_response_types.feature +++ /dev/null @@ -1,38 +0,0 @@ -Feature: Validate response types - In order to have robust API - As a client software developer - The API must check the requested response type - - Scenario: Send a document without content-type - When I add "Accept" header equal to "text/plain" - And I send a "GET" request to "/dummies" - Then the response status code should be 406 - And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8" - And the JSON node "detail" should be equal to 'Requested format "text/plain" is not supported. Supported MIME types are "application/ld+json", "application/hal+json", "application/vnd.api+json", "application/xml", "text/xml", "application/json", "text/html".' - - Scenario: Requesting a different format in the Accept header and in the URL should error - When I add "Accept" header equal to "text/xml" - And I send a "GET" request to "/dummies/1.json" - Then the response status code should be 406 - And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8" - And the JSON node "detail" should be equal to 'Requested format "text/xml" is not supported. Supported MIME types are "application/json".' - - Scenario: Sending an invalid Accept header should error - When I add "Accept" header equal to "invalid" - And I send a "GET" request to "/dummies/1" - Then the response status code should be 406 - And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8" - And the JSON node "detail" should be equal to 'Requested format "invalid" is not supported. Supported MIME types are "application/ld+json", "application/hal+json", "application/vnd.api+json", "application/xml", "text/xml", "application/json", "text/html".' - - Scenario: Requesting an invalid format in the URL should throw an error - And I send a "GET" request to "/dummies/1.invalid" - Then the response status code should be 404 - And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8" - And the JSON node "detail" should be equal to 'Format "invalid" is not supported' - - Scenario: Requesting an invalid format in the Accept header and in the URL should throw an error - When I add "Accept" header equal to "text/invalid" - And I send a "GET" request to "/dummies/1.invalid" - Then the response status code should be 404 - And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8" - And the JSON node "detail" should be equal to 'Format "invalid" is not supported' diff --git a/features/serializer/deserialize_objects_using_constructor.feature b/features/serializer/deserialize_objects_using_constructor.feature deleted file mode 100644 index 624590f5ba2..00000000000 --- a/features/serializer/deserialize_objects_using_constructor.feature +++ /dev/null @@ -1,31 +0,0 @@ -Feature: Resource with constructor deserializable - In order to build non anemic resource object - As a developer - I should be able to deserialize data into objects with constructors - - @createSchema - Scenario: post a resource built with constructor - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_entity_with_constructors" with body: - """ - { - "foo": "hello", - "bar": "world" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/DummyEntityWithConstructor", - "@id": "/dummy_entity_with_constructors/1", - "@type": "DummyEntityWithConstructor", - "id": 1, - "foo": "hello", - "bar": "world", - "baz": null - } - """ - diff --git a/features/serializer/group_filter.feature b/features/serializer/group_filter.feature deleted file mode 100644 index 12dce07c0f1..00000000000 --- a/features/serializer/group_filter.feature +++ /dev/null @@ -1,977 +0,0 @@ -Feature: Filter with serialization groups on items and collections - In order to retrieve, create and update resources or large collections of resources - As a client software developer - I need to retrieve, create and update resources or collections of resources with serialization groups - - @createSchema - Scenario: Get a collection of resources by group dummy_foo without overriding - Given there are 10 dummy group objects - When I send a "GET" request to "/dummy_groups?groups[]=dummy_foo" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_groups\\?groups%5B%5D=dummy_foo&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection of resources by group dummy_foo with overriding - When I send a "GET" request to "/dummy_groups?override_groups[]=dummy_foo" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_groups\\?override_groups%5B%5D=dummy_foo&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection of resources by groups dummy_foo, dummy_qux and without overriding - When I send a "GET" request to "/dummy_groups?groups[]=dummy_foo&groups[]=dummy_qux" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {}, - "qux": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz", "qux"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {}, - "qux": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz", "qux"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {}, - "qux": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz", "qux"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_groups\\?groups%5B%5D=dummy_foo&groups%5B%5D=dummy_qux&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection of resources by groups dummy_foo, dummy_qux and with overriding - When I send a "GET" request to "/dummy_groups?override_groups[]=dummy_foo&override_groups[]=dummy_qux" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {}, - "qux": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo", "qux"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {}, - "qux": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo", "qux"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {}, - "qux": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo", "qux"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_groups\\?override_groups%5B%5D=dummy_foo&override_groups%5B%5D=dummy_qux&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - - Scenario: Get a collection of resources by groups dummy_foo, dummy_qux, without overriding and with whitelist - When I send a "GET" request to "/dummy_groups?whitelisted_groups[]=dummy_foo&whitelisted_groups[]=dummy_qux" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_groups\\?whitelisted_groups%5B%5D=dummy_foo&whitelisted_groups%5B%5D=dummy_qux&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection of resources by groups dummy_foo, dummy_qux with overriding and with whitelist - When I send a "GET" request to "/dummy_groups?override_whitelisted_groups[]=dummy_foo&override_whitelisted_groups[]=dummy_qux" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_groups\\?override_whitelisted_groups%5B%5D=dummy_foo&override_whitelisted_groups%5B%5D=dummy_qux&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection of resources by group empty and without overriding - When I send a "GET" request to "/dummy_groups?groups[]=" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar", "baz"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_groups\\?groups%5B%5D=&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection of resources by group empty and with overriding - When I send a "GET" request to "/dummy_groups?override_groups[]=" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {} - }, - "additionalProperties": false, - "required": ["@id", "@type"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {} - }, - "additionalProperties": false, - "required": ["@id", "@type"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {} - }, - "additionalProperties": false, - "required": ["@id", "@type"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_groups\\?override_groups%5B%5D=&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a resource by group dummy_foo without overriding - When I send a "GET" request to "/dummy_groups/1?groups[]=dummy_foo" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups/1$"}, - "@type": {"pattern": "^DummyGroup$"}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@context", "@id", "@type", "id", "foo", "bar", "baz"] - } - """ - - Scenario: Get a resource by group dummy_foo with overriding - When I send a "GET" request to "/dummy_groups/1?override_groups[]=dummy_foo" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups/1$"}, - "@type": {"pattern": "^DummyGroup$"}, - "foo": {} - }, - "additionalProperties": false, - "required": ["@context", "@id", "@type", "foo"] - } - """ - - Scenario: Get a resource by groups dummy_foo, dummy_qux and without overriding - When I send a "GET" request to "/dummy_groups/1?groups[]=dummy_foo&groups[]=dummy_qux" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups/1$"}, - "@type": {"pattern": "^DummyGroup$"}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {}, - "qux": {} - }, - "additionalProperties": false, - "required": ["@context", "@id", "@type", "id", "foo", "bar", "baz", "qux"] - } - """ - - Scenario: Get a resource by groups dummy_foo, dummy_qux and with overriding - When I send a "GET" request to "/dummy_groups/1?override_groups[]=dummy_foo&override_groups[]=dummy_qux" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups/1$"}, - "@type": {"pattern": "^DummyGroup$"}, - "foo": {}, - "qux": {} - }, - "additionalProperties": false, - "required": ["@context", "@id", "@type", "foo", "qux"] - } - """ - - Scenario: Get a resource by groups dummy_foo, dummy_qux and without overriding and with whitelist - When I send a "GET" request to "/dummy_groups/1?whitelisted_groups[]=dummy_foo&whitelisted_groups[]=dummy_qux" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups/1$"}, - "@type": {"pattern": "^DummyGroup$"}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@context", "@id", "@type", "id", "foo", "bar", "baz"] - } - """ - - Scenario: Get a resource by groups dummy_foo, dummy_qux and with overriding and with whitelist - When I send a "GET" request to "/dummy_groups/1?override_whitelisted_groups[]=dummy_foo&override_whitelisted_groups[]=dummy_qux" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups/1$"}, - "@type": {"pattern": "^DummyGroup$"}, - "foo": {} - }, - "additionalProperties": false, - "required": ["@context", "@id", "@type", "foo"] - } - """ - - Scenario: Get a resource by group empty and without overriding - When I send a "GET" request to "/dummy_groups/1?groups[]=" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups/1$"}, - "@type": {"pattern": "^DummyGroup$"}, - "id": {}, - "foo": {}, - "bar": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@context", "@id", "@type", "id", "foo", "bar", "baz"] - } - """ - - Scenario: Get a resource by group empty and with overriding - When I send a "GET" request to "/dummy_groups/1?override_groups[]=" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyGroup$"}, - "@id": {"pattern": "^/dummy_groups/1$"}, - "@type": {"pattern": "^DummyGroup$"} - }, - "additionalProperties": false, - "required": ["@context", "@id", "@type"] - } - """ - - Scenario: Create a resource by group dummy_foo and without overriding - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_groups?groups[]=dummy_foo" with body: - """ - { - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyGroup", - "@id": "\/dummy_groups\/11", - "@type": "DummyGroup", - "id": 11, - "foo": "Foo", - "bar": "Bar", - "baz": null - } - """ - - Scenario: Create a resource by group dummy_foo and with overriding - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_groups?override_groups[]=dummy_foo" with body: - """ - { - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyGroup", - "@id": "\/dummy_groups\/12", - "@type": "DummyGroup", - "foo": "Foo" - } - """ - - Scenario: Create a resource by groups dummy_foo, dummy_baz, dummy_qux and without overriding - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_groups?groups[]=dummy_foo&groups[]=dummy_baz&groups[]=dummy_qux" with body: - """ - { - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyGroup", - "@id": "\/dummy_groups\/13", - "@type": "DummyGroup", - "id": 13, - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - - Scenario: Create a resource by groups dummy_foo, dummy_baz, dummy_qux and with overriding - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_groups?override_groups[]=dummy_foo&override_groups[]=dummy_baz&override_groups[]=dummy_qux" with body: - """ - { - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyGroup", - "@id": "\/dummy_groups\/14", - "@type": "DummyGroup", - "foo": "Foo", - "baz": "Baz", - "qux": "Qux" - } - """ - - Scenario: Create a resource by groups dummy, dummy_baz, without overriding - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_groups?groups[]=dummy&groups[]=dummy_baz" with body: - """ - { - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyGroup", - "@id": "\/dummy_groups\/15", - "@type": "DummyGroup", - "id": 15, - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - - Scenario: Create a resource by groups dummy, dummy_baz and with overriding - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_groups?override_groups[]=dummy&override_groups[]=dummy_baz" with body: - """ - { - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyGroup", - "@id": "\/dummy_groups\/16", - "@type": "DummyGroup", - "id": 16, - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - - Scenario: Create a resource by group empty and without overriding - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_groups?groups[]=" with body: - """ - { - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyGroup", - "@id": "\/dummy_groups\/17", - "@type": "DummyGroup", - "id": 17, - "foo": "Foo", - "bar": "Bar", - "baz": null - } - """ - - Scenario: Create a resource by group empty and with overriding - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_groups?override_groups[]=" with body: - """ - { - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyGroup", - "@id": "\/dummy_groups\/18", - "@type": "DummyGroup" - } - """ - - Scenario: Create a resource by groups dummy, dummy_baz, without overriding and with whitelist - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_groups?whitelisted_groups[]=dummy&whitelisted_groups[]=dummy_baz" with body: - """ - { - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyGroup", - "@id": "\/dummy_groups\/19", - "@type": "DummyGroup", - "id": 19, - "foo": "Foo", - "bar": "Bar", - "baz": "Baz" - } - """ - - Scenario: Create a resource by groups dummy, dummy_baz, with overriding and with whitelist - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_groups?override_whitelisted_groups[]=dummy&override_whitelisted_groups[]=dummy_baz" with body: - """ - { - "foo": "Foo", - "bar": "Bar", - "baz": "Baz", - "qux": "Qux" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyGroup", - "@id": "\/dummy_groups\/20", - "@type": "DummyGroup", - "baz": "Baz" - } - """ diff --git a/features/serializer/property_filter.feature b/features/serializer/property_filter.feature deleted file mode 100644 index b8b1926e991..00000000000 --- a/features/serializer/property_filter.feature +++ /dev/null @@ -1,606 +0,0 @@ -Feature: Filter with serialization attributes on items and collections - In order to retrieve, create and update resources or large collection of resources - As a client software developer - I need to retrieve, create and update resources or collections of resources with serialization attributes - - @createSchema - Scenario: Get a collection of resources by attributes id, foo and bar - Given there are 10 dummy property objects - When I send a "GET" request to "/dummy_properties?properties[]=id&properties[]=foo&properties[]=bar" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyProperty$"}, - "@id": {"pattern": "^/dummy_properties$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "id": {}, - "foo": {}, - "bar": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "id", "foo", "bar"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_properties\\?properties%5B%5D=id&properties%5B%5D=foo&properties%5B%5D=bar&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection of resources by attributes foo, bar, group.baz and group.qux - When I send a "GET" request to "/dummy_properties?properties[]=foo&properties[]=bar&properties[group][]=baz&properties[group][]=qux" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyProperty$"}, - "@id": {"pattern": "^/dummy_properties$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {}, - "bar": {}, - "group": { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "baz"] - } - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo", "bar"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {}, - "bar": {}, - "group": { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "baz"] - } - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo", "bar"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {}, - "bar": {}, - "group": { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "baz"] - } - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo", "bar"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_properties\\?properties%5B%5D=foo&properties%5B%5D=bar&properties%5Bgroup%5D%5B%5D=baz&properties%5Bgroup%5D%5B%5D=qux&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection of resources by attributes foo, bar - When I send a "GET" request to "/dummy_properties?whitelisted_properties[]=foo&whitelisted_properties[]=bar" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyProperty$"}, - "@id": {"pattern": "^/dummy_properties$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_properties\\?whitelisted_properties%5B%5D=foo&whitelisted_properties%5B%5D=bar&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection of resources by attributes foo, bar, group.baz and group.qux - When I send a "GET" request to "/dummy_properties?whitelisted_nested_properties[]=foo&whitelisted_nested_properties[]=bar&whitelisted_nested_properties[group][]=baz" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyProperty$"}, - "@id": {"pattern": "^/dummy_properties$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {}, - "group": { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "baz"] - } - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {}, - "group": { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "baz"] - } - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "foo": {}, - "group": { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type"] - } - }, - "additionalProperties": false, - "required": ["@id", "@type", "foo"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_properties\\?whitelisted_nested_properties%5B%5D=foo&whitelisted_nested_properties%5B%5D=bar&whitelisted_nested_properties%5Bgroup%5D%5B%5D=baz&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection of resources by attributes bar not allowed - When I send a "GET" request to "/dummy_properties?whitelisted_properties[]=bar" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyProperty$"}, - "@id": {"pattern": "^/dummy_properties$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {} - }, - "additionalProperties": false, - "required": ["@id", "@type"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {} - }, - "additionalProperties": false, - "required": ["@id", "@type"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {} - }, - "additionalProperties": false, - "required": ["@id", "@type"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_properties\\?whitelisted_properties%5B%5D=bar&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a collection of resources by attributes empty - When I send a "GET" request to "/dummy_properties?properties[]=&properties[group][]=" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyProperty$"}, - "@id": {"pattern": "^/dummy_properties$"}, - "@type": {"pattern": "^hydra:Collection$"}, - "hydra:member": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "group": { - "type": "object", - "properties": { - "@id": {}, - "@type": {} - }, - "additionalProperties": false, - "required": ["@id", "@type"] - } - }, - "additionalProperties": false, - "required": ["@id", "@type", "group"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "group": { - "type": "object", - "properties": { - "@id": {}, - "@type": {} - }, - "additionalProperties": false, - "required": ["@id", "@type"] - } - }, - "additionalProperties": false, - "required": ["@id", "@type", "group"] - }, - { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "group": { - "type": "object", - "properties": { - "@id": {}, - "@type": {} - }, - "additionalProperties": false, - "required": ["@id", "@type"] - } - }, - "additionalProperties": false, - "required": ["@id", "@type", "group"] - } - ], - "additionalItems": false, - "maxItems": 3, - "minItems": 3 - }, - "hydra:view": { - "type": "object", - "properties": { - "@id": {"pattern": "^/dummy_properties\\?properties%5B%5D=&properties%5Bgroup%5D%5B%5D=&page=1$"}, - "@type": {"pattern": "^hydra:PartialCollectionView$"} - } - } - } - } - """ - - Scenario: Get a resource by attributes id, foo and bar - When I send a "GET" request to "/dummy_properties/1?properties[]=id&properties[]=foo&properties[]=bar" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyProperty$"}, - "@id": {"pattern": "^/dummy_properties/1$"}, - "@type": {"pattern": "^DummyProperty$"}, - "id": {}, - "foo": {}, - "bar": {} - }, - "additionalProperties": false, - "required": ["@context", "@id", "@type", "id", "foo", "bar"] - } - """ - - Scenario: Get a resource by attributes foo, bar, group.baz and group.qux - When I send a "GET" request to "/dummy_properties/1?properties[]=foo&properties[]=bar&properties[group][]=baz&properties[group][]=qux" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyProperty$"}, - "@id": {"pattern": "^/dummy_properties/1$"}, - "@type": {"pattern": "^DummyProperty$"}, - "foo": {}, - "bar": {}, - "group": { - "type": "object", - "properties": { - "@id": {}, - "@type": {}, - "baz": {} - }, - "additionalProperties": false, - "required": ["@id", "@type", "baz"] - } - }, - "additionalProperties": false, - "required": ["@context", "@id", "@type", "foo", "bar", "group"] - } - """ - - Scenario: Get a resource by attributes empty - When I send a "GET" request to "/dummy_properties/1?properties[]=&properties[group][]=" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": {"pattern": "^/contexts/DummyProperty$"}, - "@id": {"pattern": "^/dummy_properties/1$"}, - "@type": {"pattern": "^DummyProperty$"}, - "group": { - "type": "object", - "properties": { - "@id": {}, - "@type": {} - }, - "additionalProperties": false, - "required": ["@id", "@type"] - } - }, - "additionalProperties": false, - "required": ["@context", "@id", "@type", "group"] - } - """ - - Scenario: Create a resource by attributes foo and bar - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_properties?properties[]=foo&properties[]=bar" with body: - """ - { - "foo": "Foo", - "bar": "Bar" - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyProperty", - "@id": "\/dummy_properties\/11", - "@type": "DummyProperty", - "foo": "Foo", - "bar": "Bar" - } - """ - - Scenario: Create a resource by attributes foo, bar, group.foo, group.baz and group.qux - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummy_properties?properties[]=foo&properties[]=bar&properties[group][]=foo&properties[group][]=baz&properties[group][]=qux" with body: - """ - { - "foo": "Foo", - "bar": "Bar", - "group": { - "foo": "Foo", - "baz": "Baz", - "qux": "Qux" - } - } - """ - Then the response status code should be 201 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "\/contexts\/DummyProperty", - "@id": "\/dummy_properties\/12", - "@type": "DummyProperty", - "foo": "Foo", - "bar": "Bar", - "group": { - "@id": "\/dummy_groups\/11", - "@type": "DummyGroup", - "foo": "Foo", - "baz": null - } - } - """ diff --git a/features/serializer/vo_relations.feature b/features/serializer/vo_relations.feature deleted file mode 100644 index ad22a1c6763..00000000000 --- a/features/serializer/vo_relations.feature +++ /dev/null @@ -1,187 +0,0 @@ -Feature: Value object as ApiResource - In order to keep ApiResource immutable - As a client software developer - I need to be able to use class without setters as ApiResource - - @createSchema - Scenario: Create Value object resource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/vo_dummy_cars" with body: - """ - { - "mileage": 1500, - "bodyType": "suv", - "make": "CustomCar", - "insuranceCompany": { - "name": "Safe Drive Company" - }, - "drivers": [ - { - "firstName": "John", - "lastName": "Doe" - } - ] - } - """ - Then the response status code should be 201 - And the JSON should be equal to: - """ - { - "@context": "/contexts/VoDummyCar", - "@id": "/vo_dummy_cars/1", - "@type": "VoDummyCar", - "mileage": 1500, - "bodyType": "suv", - "inspections": [], - "make": "CustomCar", - "insuranceCompany": { - "@id": "/vo_dummy_insurance_companies/1", - "@type": "VoDummyInsuranceCompany", - "name": "Safe Drive Company" - }, - "drivers": [ - { - "@id": "/vo_dummy_drivers/1", - "@type": "VoDummyDriver", - "firstName": "John", - "lastName": "Doe" - } - ] - } - """ - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - Scenario: Create Value object with IRI and nullable parameter - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/vo_dummy_inspections" with body: - """ - { - "accepted": true, - "car": "/vo_dummy_cars/1" - } - """ - Then the response status code should be 201 - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "required": ["accepted", "performed", "car"], - "properties": { - "accepted": { - "enum":[true] - }, - "performed": { - "format": "date-time" - }, - "car": { - "enum": ["/vo_dummy_cars/1"] - } - } - } - """ - - Scenario: Update Value object with writable and non writable property - When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "/vo_dummy_inspections/1" with body: - """ - { - "performed": "2018-08-24 00:00:00", - "accepted": false - } - """ - Then the response status code should be 200 - And the JSON should be equal to: - """ - { - "@context": "/contexts/VoDummyInspection", - "@id": "/vo_dummy_inspections/1", - "@type": "VoDummyInspection", - "accepted": true, - "car": "/vo_dummy_cars/1", - "performed": "2018-08-24T00:00:00+00:00", - "id": 1 - } - """ - - @createSchema - Scenario: Create Value object without required params - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/vo_dummy_cars" with body: - """ - { - "mileage": 1500, - "make": "CustomCar", - "insuranceCompany": { - "name": "Safe Drive Company" - } - } - """ - Then the response status code should be 400 - And the JSON should be valid according to this schema: - """ - { - "type": "object", - "properties": { - "@context": { - "enum": ["/contexts/Error"] - }, - "type": { - "enum": ["hydra:Error"] - }, - "hydra:title": { - "enum": ["An error occurred"] - }, - "hydra:description": { - "enum": ["Cannot create an instance of ApiPlatform\\Core\\Tests\\Fixtures\\TestBundle\\Entity\\VoDummyCar from serialized data because its constructor requires parameter \"drivers\" to be present."] - } - } - } - """ - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - - @createSchema - Scenario: Create Value object without default param - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/vo_dummy_cars" with body: - """ - { - "mileage": 1500, - "make": "CustomCar", - "insuranceCompany": { - "name": "Safe Drive Company" - }, - "drivers": [ - { - "firstName": "John", - "lastName": "Doe" - } - ] - } - """ - Then the response status code should be 201 - And the JSON should be equal to: - """ - { - "@context": "/contexts/VoDummyCar", - "@id": "/vo_dummy_cars/1", - "@type": "VoDummyCar", - "mileage": 1500, - "bodyType": "coupe", - "inspections": [], - "make": "CustomCar", - "insuranceCompany": { - "@id": "/vo_dummy_insurance_companies/1", - "@type": "VoDummyInsuranceCompany", - "name": "Safe Drive Company" - }, - "drivers": [ - { - "@id": "/vo_dummy_drivers/1", - "@type": "VoDummyDriver", - "firstName": "John", - "lastName": "Doe" - } - ] - } - """ - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" diff --git a/features/swagger/docs.feature b/features/swagger/docs.feature deleted file mode 100644 index d6c8e44585a..00000000000 --- a/features/swagger/docs.feature +++ /dev/null @@ -1,100 +0,0 @@ -Feature: Documentation support - In order to build an auto-discoverable API - As a client software developer - I need to know Swagger specifications of objects I send and receive - - @createSchema - Scenario: Retrieve the Swagger/OpenAPI documentation - Given I send a "GET" request to "/docs.json" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json; charset=utf-8" - # Context - And the JSON node "swagger" should be equal to "2.0" - # Root properties - And the JSON node "info.title" should be equal to "My Dummy API" - And the JSON node "info.description" should be equal to "This is a test API." - # Supported classes - And the Swagger class "AbstractDummy" exists - And the Swagger class "CircularReference" exists - And the Swagger class "CircularReference-circular" exists - And the Swagger class "CompositeItem" exists - And the Swagger class "CompositeLabel" exists - And the Swagger class "ConcreteDummy" exists - And the Swagger class "CustomIdentifierDummy" exists - And the Swagger class "CustomNormalizedDummy-input" exists - And the Swagger class "CustomNormalizedDummy-output" exists - And the Swagger class "CustomWritableIdentifierDummy" exists - And the Swagger class "Dummy" exists - And the Swagger class "RelatedDummy" exists - And the Swagger class "DummyTableInheritance" exists - And the Swagger class "DummyTableInheritanceChild" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_get" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_put" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_read" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_write" exists - And the Swagger class "RelatedDummy" exists - And the Swagger class "NoCollectionDummy" exists - And the Swagger class "RelatedToDummyFriend" exists - And the Swagger class "RelatedToDummyFriend-fakemanytomany" exists - And the Swagger class "DummyFriend" exists - And the Swagger class "RelationEmbedder-barcelona" exists - And the Swagger class "RelationEmbedder-chicago" exists - And the Swagger class "User-user_user-read" exists - And the Swagger class "User-user_user-write" exists - And the Swagger class "UuidIdentifierDummy" exists - And the Swagger class "ThirdLevel" exists - And the Swagger class "ParentDummy" doesn't exist - And the Swagger class "UnknownDummy" doesn't exist - And the Swagger path "/relation_embedders/{id}/custom" exists - And the Swagger path "/override/swagger" exists - And the Swagger path "/api/custom-call/{id}" exists - And the JSON node "paths./api/custom-call/{id}.get" should exist - And the JSON node "paths./api/custom-call/{id}.put" should exist - # Properties - And "id" property exists for the Swagger class "Dummy" - And "name" property is required for Swagger class "Dummy" - # Filters - And the JSON node "paths./dummies.get.parameters[0].name" should be equal to "dummyBoolean" - And the JSON node "paths./dummies.get.parameters[0].in" should be equal to "query" - And the JSON node "paths./dummies.get.parameters[0].required" should be false - And the JSON node "paths./dummies.get.parameters[0].type" should be equal to "boolean" - - # Subcollection - check filter on subResource - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[0].name" should be equal to "id" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[0].in" should be equal to "path" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[0].required" should be true - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[0].type" should be equal to "string" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[1].name" should be equal to "name" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[1].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[1].required" should be false - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[1].type" should be equal to "string" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[2].name" should be equal to "description" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[2].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[2].required" should be false - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[2].type" should be equal to "string" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters" should have 3 element - - # Subcollection - check schema - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.responses.200.schema.items.$ref" should be equal to "#/definitions/RelatedToDummyFriend-fakemanytomany" - - # Deprecations - And the JSON node "paths./dummies.get.deprecated" should not exist - And the JSON node "paths./deprecated_resources.get.deprecated" should be true - And the JSON node "paths./deprecated_resources.post.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.get.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.delete.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.put.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.patch.deprecated" should be true - - Scenario: Swagger UI is enabled for docs endpoint - Given I add "Accept" header equal to "text/html" - And I send a "GET" request to "/docs" - Then the response status code should be 200 - And I should see text matching "My Dummy API" - - Scenario: Swagger UI is enabled for an arbitrary endpoint - Given I add "Accept" header equal to "text/html" - And I send a "GET" request to "/dummies" - Then the response status code should be 200 - And I should see text matching "My Dummy API" diff --git a/src/Api/IriConverterInterface.php b/src/Api/IriConverterInterface.php index 5cf4be34a2d..95bc03c18a8 100644 --- a/src/Api/IriConverterInterface.php +++ b/src/Api/IriConverterInterface.php @@ -43,7 +43,7 @@ public function getItemFromIri(string $iri, array $context = []); * @throws InvalidArgumentException * @throws RuntimeException */ - public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface::ABS_PATH): string; + public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface::ABS_PATH, $context = []): string; /** * Gets the IRI associated with the given resource collection. diff --git a/src/Bridge/FosUser/EventListener.php b/src/Bridge/FosUser/EventListener.php deleted file mode 100644 index 75c88ea2bc4..00000000000 --- a/src/Bridge/FosUser/EventListener.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\FosUser; - -use ApiPlatform\Core\Util\RequestAttributesExtractor; -use FOS\UserBundle\Model\UserInterface; -use FOS\UserBundle\Model\UserManagerInterface; -use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; - -/** - * Bridges between FOSUserBundle and API Platform Core. - * - * @author Kévin Dunglas - * @author Théo FIDRY - */ -final class EventListener -{ - private $userManager; - - public function __construct(UserManagerInterface $userManager) - { - $this->userManager = $userManager; - } - - /** - * Persists, updates or delete data return by the controller if applicable. - */ - public function onKernelView(GetResponseForControllerResultEvent $event) - { - $request = $event->getRequest(); - if (!RequestAttributesExtractor::extractAttributes($request)) { - return; - } - - $user = $event->getControllerResult(); - if (!$user instanceof UserInterface || $request->isMethodSafe(false)) { - return; - } - - if ('DELETE' === $request->getMethod()) { - $this->userManager->deleteUser($user); - $event->setControllerResult(null); - } else { - $this->userManager->updateUser($user); - } - } -} diff --git a/src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php b/src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php index b83da3c5434..ed5490bc11f 100644 --- a/src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php +++ b/src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php @@ -16,7 +16,7 @@ use ApiPlatform\Core\Api\FilterCollection; use ApiPlatform\Core\Api\FilterLocatorTrait; use ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser; -use ApiPlatform\Core\Bridge\Symfony\Routing\OperationMethodResolverInterface; +use Videni\Bundle\RestBundle\Routing\OperationMethodResolverInterface; use ApiPlatform\Core\Documentation\Documentation; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; diff --git a/src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php b/src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php deleted file mode 100644 index 669fc1eedd4..00000000000 --- a/src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php +++ /dev/null @@ -1,164 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Action; - -use ApiPlatform\Core\Api\FormatsProviderInterface; -use ApiPlatform\Core\Documentation\Documentation; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Core\Util\RequestAttributesExtractor; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * Displays the documentation in Swagger UI. - * - * @author Kévin Dunglas - */ -final class SwaggerUiAction -{ - private $resourceNameCollectionFactory; - private $resourceMetadataFactory; - private $normalizer; - private $twig; - private $urlGenerator; - private $title; - private $description; - private $version; - private $formats = []; - private $oauthEnabled; - private $oauthClientId; - private $oauthClientSecret; - private $oauthType; - private $oauthFlow; - private $oauthTokenUrl; - private $oauthAuthorizationUrl; - private $oauthScopes; - private $formatsProvider; - - /** - * @throws InvalidArgumentException - */ - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, NormalizerInterface $normalizer, \Twig_Environment $twig, UrlGeneratorInterface $urlGenerator, string $title = '', string $description = '', string $version = '', /* FormatsProviderInterface */ $formatsProvider = [], $oauthEnabled = false, $oauthClientId = '', $oauthClientSecret = '', $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = []) - { - $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->normalizer = $normalizer; - $this->twig = $twig; - $this->urlGenerator = $urlGenerator; - $this->title = $title; - $this->description = $description; - $this->version = $version; - $this->oauthEnabled = $oauthEnabled; - $this->oauthClientId = $oauthClientId; - $this->oauthClientSecret = $oauthClientSecret; - $this->oauthType = $oauthType; - $this->oauthFlow = $oauthFlow; - $this->oauthTokenUrl = $oauthTokenUrl; - $this->oauthAuthorizationUrl = $oauthAuthorizationUrl; - $this->oauthScopes = $oauthScopes; - - if (\is_array($formatsProvider)) { - if ($formatsProvider) { - // Only trigger notification for non-default argument - @trigger_error('Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3', E_USER_DEPRECATED); - } - $this->formats = $formatsProvider; - - return; - } - if (!$formatsProvider instanceof FormatsProviderInterface) { - throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class)); - } - - $this->formatsProvider = $formatsProvider; - } - - public function __invoke(Request $request) - { - // BC check to be removed in 3.0 - if (null !== $this->formatsProvider) { - $this->formats = $this->formatsProvider->getFormatsFromAttributes(RequestAttributesExtractor::extractAttributes($request)); - } - - $documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version, $this->formats); - - return new Response($this->twig->render('@ApiPlatform/SwaggerUi/index.html.twig', $this->getContext($request, $documentation))); - } - - /** - * Gets the base Twig context. - */ - private function getContext(Request $request, Documentation $documentation): array - { - $context = [ - 'title' => $this->title, - 'description' => $this->description, - 'formats' => $this->formats, - ]; - - $swaggerData = [ - 'url' => $this->urlGenerator->generate('api_doc', ['format' => 'json']), - 'spec' => $this->normalizer->normalize($documentation, 'json', ['base_url' => $request->getBaseUrl()]), - ]; - - $swaggerData['oauth'] = [ - 'enabled' => $this->oauthEnabled, - 'clientId' => $this->oauthClientId, - 'clientSecret' => $this->oauthClientSecret, - 'type' => $this->oauthType, - 'flow' => $this->oauthFlow, - 'tokenUrl' => $this->oauthTokenUrl, - 'authorizationUrl' => $this->oauthAuthorizationUrl, - 'scopes' => $this->oauthScopes, - ]; - - if ($request->isMethodSafe(false) && null !== $resourceClass = $request->attributes->get('_api_resource_class')) { - $swaggerData['id'] = $request->attributes->get('id'); - $swaggerData['queryParameters'] = $request->query->all(); - - $metadata = $this->resourceMetadataFactory->create($resourceClass); - $swaggerData['shortName'] = $metadata->getShortName(); - - if (null !== $collectionOperationName = $request->attributes->get('_api_collection_operation_name')) { - $swaggerData['operationId'] = sprintf('%s%sCollection', $collectionOperationName, $swaggerData['shortName']); - } elseif (null !== $itemOperationName = $request->attributes->get('_api_item_operation_name')) { - $swaggerData['operationId'] = sprintf('%s%sItem', $itemOperationName, $swaggerData['shortName']); - } elseif (null !== $subresourceOperationContext = $request->attributes->get('_api_subresource_context')) { - $swaggerData['operationId'] = $subresourceOperationContext['operationId']; - } - - list($swaggerData['path'], $swaggerData['method']) = $this->getPathAndMethod($swaggerData); - } - - return $context + ['swagger_data' => $swaggerData]; - } - - private function getPathAndMethod(array $swaggerData): array - { - foreach ($swaggerData['spec']['paths'] as $path => $operations) { - foreach ($operations as $method => $operation) { - if ($operation['operationId'] === $swaggerData['operationId']) { - return [$path, $method]; - } - } - } - - throw new RuntimeException(sprintf('The operation "%s" cannot be found in the Swagger specification.', $swaggerData['operationId'])); - } -} diff --git a/src/Bridge/Symfony/Bundle/ApiPlatformBundle.php b/src/Bridge/Symfony/Bundle/ApiPlatformBundle.php deleted file mode 100644 index 94171ff0fda..00000000000 --- a/src/Bridge/Symfony/Bundle/ApiPlatformBundle.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\Symfony\Bundle; - -use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\AnnotationFilterPass; -use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass; -use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\FilterPass; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\HttpKernel\Bundle\Bundle; - -/** - * The Symfony bundle. - * - * @author Kévin Dunglas - */ -final class ApiPlatformBundle extends Bundle -{ - /** - * {@inheritdoc} - */ - public function build(ContainerBuilder $container) - { - parent::build($container); - - $container->addCompilerPass(new DataProviderPass()); - $container->addCompilerPass(new AnnotationFilterPass()); - $container->addCompilerPass(new FilterPass()); - } -} diff --git a/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php b/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php deleted file mode 100644 index 2373d089ee7..00000000000 --- a/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php +++ /dev/null @@ -1,81 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Command; - -use ApiPlatform\Core\Documentation\Documentation; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -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\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Yaml\Yaml; - -/** - * Console command to dump Swagger API documentations. - * - * @author Amrouche Hamza - */ -final class SwaggerCommand extends Command -{ - private $documentationNormalizer; - private $resourceNameCollectionFactory; - private $apiTitle; - private $apiDescription; - private $apiVersion; - private $apiFormats; - - public function __construct(NormalizerInterface $documentationNormalizer, ResourceNameCollectionFactoryInterface $resourceNameCollection, string $apiTitle, string $apiDescription, string $apiVersion, array $apiFormats) - { - parent::__construct(); - - $this->documentationNormalizer = $documentationNormalizer; - $this->resourceNameCollectionFactory = $resourceNameCollection; - $this->apiTitle = $apiTitle; - $this->apiDescription = $apiDescription; - $this->apiVersion = $apiVersion; - $this->apiFormats = $apiFormats; - } - - /** - * {@inheritdoc} - */ - protected function configure() - { - $this - ->setName('api:swagger:export') - ->setDescription('Dump the Swagger 2.0 (OpenAPI) documentation') - ->addOption('yaml', 'y', InputOption::VALUE_NONE, 'Dump the documentation in YAML') - ->addOption('output', 'o', InputOption::VALUE_OPTIONAL, 'Write output to file'); - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->apiTitle, $this->apiDescription, $this->apiVersion, $this->apiFormats); - $data = $this->documentationNormalizer->normalize($documentation); - $content = $input->getOption('yaml') ? Yaml::dump($data, Yaml::DUMP_OBJECT_AS_MAP) : json_encode($data, JSON_PRETTY_PRINT); - - if (!empty($input->getOption('output'))) { - file_put_contents($input->getOption('output'), $content); - $output->writeln( - sprintf('Data written to %s', $input->getOption('output')) - ); - } else { - $output->writeln($content); - } - } -} diff --git a/src/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php b/src/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php deleted file mode 100644 index 05826b7f17e..00000000000 --- a/src/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php +++ /dev/null @@ -1,112 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\Symfony\Bundle\DataCollector; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Util\RequestAttributesExtractor; -use Psr\Container\ContainerInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\DataCollector\DataCollector; - -/** - * @author Julien DENIAU - */ -final class RequestDataCollector extends DataCollector -{ - private $metadataFactory; - private $filterLocator; - - public function __construct(ResourceMetadataFactoryInterface $metadataFactory, ContainerInterface $filterLocator) - { - $this->metadataFactory = $metadataFactory; - $this->filterLocator = $filterLocator; - } - - /** - * {@inheritdoc} - */ - public function collect(Request $request, Response $response, \Exception $exception = null) - { - $counters = ['ignored_filters' => 0]; - $resourceClass = $request->attributes->get('_api_resource_class'); - $resourceMetadata = $resourceClass ? $this->metadataFactory->create($resourceClass) : null; - - $filters = []; - foreach ($resourceMetadata ? $resourceMetadata->getAttribute('filters', []) : [] as $id) { - if ($this->filterLocator->has($id)) { - $filters[$id] = \get_class($this->filterLocator->get($id)); - continue; - } - - $filters[$id] = null; - ++$counters['ignored_filters']; - } - - $this->data = [ - 'resource_class' => $resourceClass, - 'resource_metadata' => $resourceMetadata ? $this->cloneVar($resourceMetadata) : null, - 'acceptable_content_types' => $request->getAcceptableContentTypes(), - 'request_attributes' => RequestAttributesExtractor::extractAttributes($request), - 'filters' => $filters, - 'counters' => $counters, - ]; - } - - public function getAcceptableContentTypes(): array - { - return $this->data['acceptable_content_types'] ?? []; - } - - public function getResourceClass() - { - return $this->data['resource_class'] ?? null; - } - - public function getResourceMetadata() - { - return $this->data['resource_metadata'] ?? null; - } - - public function getRequestAttributes(): array - { - return $this->data['request_attributes'] ?? []; - } - - public function getFilters(): array - { - return $this->data['filters'] ?? []; - } - - public function getCounters(): array - { - return $this->data['counters'] ?? []; - } - - /** - * {@inheritdoc} - */ - public function getName(): string - { - return 'api_platform.data_collector.request'; - } - - /** - * {@inheritdoc} - */ - public function reset() - { - $this->data = []; - } -} diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php deleted file mode 100644 index 2adfd410ba5..00000000000 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ /dev/null @@ -1,523 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection; - -use ApiPlatform\Core\Api\FilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\EagerLoadingExtension; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\FilterEagerLoadingExtension; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface; -use ApiPlatform\Core\DataPersister\DataPersisterInterface; -use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Core\Exception\RuntimeException; -use Doctrine\Common\Annotations\Annotation; -use Doctrine\ORM\Version; -use phpDocumentor\Reflection\DocBlockFactoryInterface; -use Ramsey\Uuid\Uuid; -use Symfony\Component\Cache\Adapter\ArrayAdapter; -use Symfony\Component\Config\FileLocator; -use Symfony\Component\Config\Resource\DirectoryResource; -use Symfony\Component\DependencyInjection\ChildDefinition; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; -use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -use Symfony\Component\Finder\Finder; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\Validator\Validator\ValidatorInterface; -use Symfony\Component\Yaml\Yaml; - -/** - * The extension of this bundle. - * - * @author Kévin Dunglas - */ -final class ApiPlatformExtension extends Extension implements PrependExtensionInterface -{ - /** - * {@inheritdoc} - */ - public function prepend(ContainerBuilder $container) - { - if (!$frameworkConfiguration = $container->getExtensionConfig('framework')) { - return; - } - - foreach ($frameworkConfiguration as $frameworkParameters) { - if (isset($frameworkParameters['serializer'])) { - $serializerConfig = $serializerConfig ?? $frameworkParameters['serializer']; - } - - if (isset($frameworkParameters['property_info'])) { - $propertyInfoConfig = $propertyInfoConfig ?? $frameworkParameters['property_info']; - } - } - - if (!isset($serializerConfig['enabled'])) { - $container->prependExtensionConfig('framework', ['serializer' => ['enabled' => true]]); - } - - if (!isset($propertyInfoConfig['enabled'])) { - $container->prependExtensionConfig('framework', ['property_info' => ['enabled' => true]]); - } - - if (isset($serializerConfig['name_converter'])) { - $container->prependExtensionConfig('api_platform', ['name_converter' => $serializerConfig['name_converter']]); - } - } - - /** - * {@inheritdoc} - */ - public function load(array $configs, ContainerBuilder $container) - { - $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); - $formats = $this->getFormats($config['formats']); - $errorFormats = $this->getFormats($config['error_formats']); - $this->handleConfig($container, $config, $formats, $errorFormats); - - $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('api.xml'); - $loader->load('data_persister.xml'); - $loader->load('data_provider.xml'); - $loader->load('filter.xml'); - - $container->registerForAutoconfiguration(DataPersisterInterface::class) - ->addTag('api_platform.data_persister'); - $container->registerForAutoconfiguration(ItemDataProviderInterface::class) - ->addTag('api_platform.item_data_provider'); - $container->registerForAutoconfiguration(CollectionDataProviderInterface::class) - ->addTag('api_platform.collection_data_provider'); - $container->registerForAutoconfiguration(SubresourceDataProviderInterface::class) - ->addTag('api_platform.subresource_data_provider'); - $container->registerForAutoconfiguration(QueryItemExtensionInterface::class) - ->addTag('api_platform.doctrine.orm.query_extension.item'); - $container->registerForAutoconfiguration(QueryCollectionExtensionInterface::class) - ->addTag('api_platform.doctrine.orm.query_extension.collection'); - $container->registerForAutoconfiguration(FilterInterface::class) - ->addTag('api_platform.filter'); - - if (interface_exists(ValidatorInterface::class)) { - $loader->load('validator.xml'); - } - - $bundles = $container->getParameter('kernel.bundles'); - if (isset($bundles['SecurityBundle'])) { - if (class_exists(ExpressionLanguage::class)) { - $loader->load('security_expression_language.xml'); - } - $loader->load('security.xml'); - } - - if (class_exists(Uuid::class)) { - $loader->load('ramsey_uuid.xml'); - } - - $useDoctrine = isset($bundles['DoctrineBundle']) && class_exists(Version::class); - - $this->registerMetadataConfiguration($container, $config, $loader); - $this->registerOAuthConfiguration($container, $config, $loader); - $this->registerApiKeysConfiguration($container, $config, $loader); - $this->registerSwaggerConfiguration($container, $config, $loader); - $this->registerJsonApiConfiguration($formats, $loader); - $this->registerJsonLdConfiguration($container, $formats, $loader, $config['enable_docs']); - $this->registerJsonHalConfiguration($formats, $loader); - $this->registerJsonProblemConfiguration($errorFormats, $loader); - $this->registerGraphqlConfiguration($container, $config, $loader); - $this->registerBundlesConfiguration($bundles, $config, $loader, $useDoctrine); - $this->registerCacheConfiguration($container); - $this->registerDoctrineExtensionConfiguration($container, $config, $useDoctrine); - $this->registerHttpCache($container, $config, $loader, $useDoctrine); - $this->registerValidatorConfiguration($container, $config, $loader); - $this->registerDataCollector($container, $config, $loader); - } - - /** - * Handles configuration. - */ - private function handleConfig(ContainerBuilder $container, array $config, array $formats, array $errorFormats) - { - $container->setParameter('api_platform.enable_entrypoint', $config['enable_entrypoint']); - $container->setParameter('api_platform.enable_docs', $config['enable_docs']); - $container->setParameter('api_platform.title', $config['title']); - $container->setParameter('api_platform.description', $config['description']); - $container->setParameter('api_platform.version', $config['version']); - $container->setParameter('api_platform.exception_to_status', $config['exception_to_status']); - $container->setParameter('api_platform.formats', $formats); - $container->setParameter('api_platform.error_formats', $errorFormats); - $container->setParameter('api_platform.allow_plain_identifiers', $config['allow_plain_identifiers']); - $container->setParameter('api_platform.eager_loading.enabled', $config['eager_loading']['enabled']); - $container->setParameter('api_platform.eager_loading.max_joins', $config['eager_loading']['max_joins']); - $container->setParameter('api_platform.eager_loading.fetch_partial', $config['eager_loading']['fetch_partial']); - $container->setParameter('api_platform.eager_loading.force_eager', $config['eager_loading']['force_eager']); - $container->setParameter('api_platform.collection.order', $config['collection']['order']); - $container->setParameter('api_platform.collection.order_parameter_name', $config['collection']['order_parameter_name']); - $container->setParameter('api_platform.collection.pagination.enabled', $config['collection']['pagination']['enabled']); - $container->setParameter('api_platform.collection.pagination.partial', $config['collection']['pagination']['partial']); - $container->setParameter('api_platform.collection.pagination.client_enabled', $config['collection']['pagination']['client_enabled']); - $container->setParameter('api_platform.collection.pagination.client_items_per_page', $config['collection']['pagination']['client_items_per_page']); - $container->setParameter('api_platform.collection.pagination.client_partial', $config['collection']['pagination']['client_partial']); - $container->setParameter('api_platform.collection.pagination.items_per_page', $config['collection']['pagination']['items_per_page']); - $container->setParameter('api_platform.collection.pagination.maximum_items_per_page', $config['collection']['pagination']['maximum_items_per_page']); - $container->setParameter('api_platform.collection.pagination.page_parameter_name', $config['collection']['pagination']['page_parameter_name']); - $container->setParameter('api_platform.collection.pagination.enabled_parameter_name', $config['collection']['pagination']['enabled_parameter_name']); - $container->setParameter('api_platform.collection.pagination.items_per_page_parameter_name', $config['collection']['pagination']['items_per_page_parameter_name']); - $container->setParameter('api_platform.collection.pagination.partial_parameter_name', $config['collection']['pagination']['partial_parameter_name']); - $container->setParameter('api_platform.http_cache.etag', $config['http_cache']['etag']); - $container->setParameter('api_platform.http_cache.max_age', $config['http_cache']['max_age']); - $container->setParameter('api_platform.http_cache.shared_max_age', $config['http_cache']['shared_max_age']); - $container->setParameter('api_platform.http_cache.vary', $config['http_cache']['vary']); - $container->setParameter('api_platform.http_cache.public', $config['http_cache']['public']); - - $container->setAlias('api_platform.operation_path_resolver.default', $config['default_operation_path_resolver']); - $container->setAlias('api_platform.path_segment_name_generator', $config['path_segment_name_generator']); - - if ($config['name_converter']) { - $container->setAlias('api_platform.name_converter', $config['name_converter']); - } - } - - /** - * Registers metadata configuration. - */ - private function registerMetadataConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader) - { - $loader->load('metadata/metadata.xml'); - $loader->load('metadata/xml.xml'); - - list($xmlResources, $yamlResources) = $this->getResourcesToWatch($container, $config['mapping']['paths']); - - if (!empty($config['resource_class_directories'])) { - $container->setParameter('api_platform.resource_class_directories', array_merge( - $config['resource_class_directories'], $container->getParameter('api_platform.resource_class_directories') - )); - } - - $container->getDefinition('api_platform.metadata.extractor.xml')->addArgument($xmlResources); - - if (class_exists(Annotation::class)) { - $loader->load('metadata/annotation.xml'); - } - - if (class_exists(Yaml::class)) { - $loader->load('metadata/yaml.xml'); - $container->getDefinition('api_platform.metadata.extractor.yaml')->addArgument($yamlResources); - } - - if (interface_exists(DocBlockFactoryInterface::class)) { - $loader->load('metadata/php_doc.xml'); - } - } - - private function getBundlesResourcesPaths(ContainerBuilder $container): array - { - $bundlesResourcesPaths = []; - - foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) { - $paths = []; - $dirname = $bundle['path']; - foreach (['.yaml', '.yml', '.xml', ''] as $extension) { - $paths[] = "$dirname/Resources/config/api_resources$extension"; - } - $paths[] = "$dirname/Entity"; - - foreach ($paths as $path) { - if ($container->fileExists($path, false)) { - $bundlesResourcesPaths[] = $path; - } - } - } - - return $bundlesResourcesPaths; - } - - private function getResourcesToWatch(ContainerBuilder $container, array $resourcesPaths): array - { - $paths = array_unique(array_merge($resourcesPaths, $this->getBundlesResourcesPaths($container))); - - // Flex structure (only if nothing specified) - $projectDir = $container->getParameter('kernel.project_dir'); - if (!$paths && is_dir($dir = "$projectDir/config/api_platform")) { - $paths = [$dir]; - } - - $resources = ['yml' => [], 'xml' => [], 'dir' => []]; - - foreach ($paths as $path) { - if (is_dir($path)) { - foreach (Finder::create()->followLinks()->files()->in($path)->name('/\.(xml|ya?ml)$/') as $file) { - $resources['yaml' === ($extension = $file->getExtension()) ? 'yml' : $extension][] = $file->getRealPath(); - } - - $resources['dir'][] = $path; - $container->addResource(new DirectoryResource($path, '/\.(xml|ya?ml|php)$/')); - - continue; - } - - if ($container->fileExists($path, false)) { - if (!preg_match('/\.(xml|ya?ml)$/', $path, $matches)) { - throw new RuntimeException(sprintf('Unsupported mapping type in "%s", supported types are XML & Yaml.', $path)); - } - - $resources['yaml' === $matches[1] ? 'yml' : $matches[1]][] = $path; - - continue; - } - - throw new RuntimeException(sprintf('Could not open file or directory "%s".', $path)); - } - - $container->setParameter('api_platform.resource_class_directories', $resources['dir']); - - return [$resources['xml'], $resources['yml']]; - } - - /** - * Registers the OAuth configuration. - */ - private function registerOAuthConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader) - { - if (!$config['oauth']) { - return; - } - - $container->setParameter('api_platform.oauth.enabled', $config['oauth']['enabled']); - $container->setParameter('api_platform.oauth.clientId', $config['oauth']['clientId']); - $container->setParameter('api_platform.oauth.clientSecret', $config['oauth']['clientSecret']); - $container->setParameter('api_platform.oauth.type', $config['oauth']['type']); - $container->setParameter('api_platform.oauth.flow', $config['oauth']['flow']); - $container->setParameter('api_platform.oauth.tokenUrl', $config['oauth']['tokenUrl']); - $container->setParameter('api_platform.oauth.authorizationUrl', $config['oauth']['authorizationUrl']); - $container->setParameter('api_platform.oauth.scopes', $config['oauth']['scopes']); - } - - /** - * Registers the api keys configuration. - */ - private function registerApiKeysConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader) - { - $container->setParameter('api_platform.swagger.api_keys', $config['swagger']['api_keys']); - } - - /** - * Registers the Swagger and Swagger UI configuration. - */ - private function registerSwaggerConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader) - { - if (!$config['enable_swagger']) { - return; - } - - $loader->load('swagger.xml'); - - if ($config['enable_swagger_ui']) { - $loader->load('swagger-ui.xml'); - $container->setParameter('api_platform.enable_swagger_ui', $config['enable_swagger_ui']); - } - - $container->setParameter('api_platform.enable_swagger', $config['enable_swagger']); - } - - /** - * Registers the JsonApi configuration. - */ - private function registerJsonApiConfiguration(array $formats, XmlFileLoader $loader) - { - if (!isset($formats['jsonapi'])) { - return; - } - - $loader->load('jsonapi.xml'); - } - - /** - * Registers the JSON-LD and Hydra configuration. - */ - private function registerJsonLdConfiguration(ContainerBuilder $container, array $formats, XmlFileLoader $loader, bool $docEnabled) - { - if (!isset($formats['jsonld'])) { - return; - } - - $loader->load('jsonld.xml'); - $loader->load('hydra.xml'); - - if (!$docEnabled) { - $container->removeDefinition('api_platform.hydra.listener.response.add_link_header'); - } - } - - /** - * Registers the HAL configuration. - */ - private function registerJsonHalConfiguration(array $formats, XmlFileLoader $loader) - { - if (!isset($formats['jsonhal'])) { - return; - } - - $loader->load('hal.xml'); - } - - /** - * Registers the JSON Problem configuration. - */ - private function registerJsonProblemConfiguration(array $errorFormats, XmlFileLoader $loader) - { - if (!isset($errorFormats['jsonproblem'])) { - return; - } - - $loader->load('problem.xml'); - } - - /** - * Registers the GraphQL configuration. - */ - private function registerGraphqlConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader) - { - if (!$config['graphql']) { - return; - } - - $container->setParameter('api_platform.graphql.enabled', $config['graphql']['enabled']); - $container->setParameter('api_platform.graphql.graphiql.enabled', $config['graphql']['graphiql']['enabled']); - - $loader->load('graphql.xml'); - } - - /** - * Registers configuration for integration with third-party bundles. - * - * @param string[] $bundles - */ - private function registerBundlesConfiguration(array $bundles, array $config, XmlFileLoader $loader, bool $useDoctrine) - { - // Doctrine ORM support - if ($useDoctrine) { - $loader->load('doctrine_orm.xml'); - } - - // FOSUser support - if (isset($bundles['FOSUserBundle']) && $config['enable_fos_user']) { - $loader->load('fos_user.xml'); - } - - // NelmioApiDoc support - if (isset($bundles['NelmioApiDocBundle']) && $config['enable_nelmio_api_doc']) { - $loader->load('nelmio_api_doc.xml'); - } - } - - /** - * Registers the cache configuration. - */ - private function registerCacheConfiguration(ContainerBuilder $container) - { - // Don't use system cache pool in dev - if ($container->hasParameter('api_platform.metadata_cache') ? $container->getParameter('api_platform.metadata_cache') : !$container->getParameter('kernel.debug')) { - return; - } - - $container->register('api_platform.cache.metadata.property', ArrayAdapter::class); - $container->register('api_platform.cache.metadata.resource', ArrayAdapter::class); - $container->register('api_platform.cache.route_name_resolver', ArrayAdapter::class); - $container->register('api_platform.cache.identifiers_extractor', ArrayAdapter::class); - $container->register('api_platform.cache.subresource_operation_factory', ArrayAdapter::class); - } - - /** - * Manipulate doctrine extension services according to the configuration. - */ - private function registerDoctrineExtensionConfiguration(ContainerBuilder $container, array $config, bool $useDoctrine) - { - if (!$useDoctrine || $config['eager_loading']['enabled']) { - return; - } - - $container->removeAlias(EagerLoadingExtension::class); - $container->removeDefinition('api_platform.doctrine.orm.query_extension.eager_loading'); - $container->removeAlias(FilterEagerLoadingExtension::class); - $container->removeDefinition('api_platform.doctrine.orm.query_extension.filter_eager_loading'); - } - - private function registerHttpCache(ContainerBuilder $container, array $config, XmlFileLoader $loader, bool $useDoctrine) - { - $loader->load('http_cache.xml'); - - if (!$config['http_cache']['invalidation']['enabled']) { - return; - } - - if ($useDoctrine) { - $loader->load('doctrine_orm_http_cache_purger.xml'); - } - - $loader->load('http_cache_tags.xml'); - - $definitions = []; - foreach ($config['http_cache']['invalidation']['varnish_urls'] as $key => $url) { - $definition = new ChildDefinition('api_platform.http_cache.purger.varnish_client'); - $definition->addArgument(['base_uri' => $url] + $config['http_cache']['invalidation']['request_options']); - - $definitions[] = $definition; - } - - $container->getDefinition('api_platform.http_cache.purger.varnish')->addArgument($definitions); - $container->setAlias('api_platform.http_cache.purger', 'api_platform.http_cache.purger.varnish'); - } - - /** - * Normalizes the format from config to the one accepted by Symfony HttpFoundation. - */ - private function getFormats(array $configFormats): array - { - $formats = []; - foreach ($configFormats as $format => $value) { - foreach ($value['mime_types'] as $mimeType) { - $formats[$format][] = $mimeType; - } - } - - return $formats; - } - - /** - * Registers the Validator configuration. - */ - private function registerValidatorConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader) - { - if (!$config['validator']) { - return; - } - - $container->setParameter('api_platform.validator.serialize_payload_fields', $config['validator']['serialize_payload_fields']); - } - - /** - * Registers the DataCollector configuration. - */ - private function registerDataCollector(ContainerBuilder $container, array $config, XmlFileLoader $loader) - { - if (!$config['enable_profiler']) { - return; - } - - $loader->load('data_collector.xml'); - } -} diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php deleted file mode 100644 index f447c86c570..00000000000 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler; - -use ApiPlatform\Core\Util\AnnotationFilterExtractorTrait; -use ApiPlatform\Core\Util\ReflectionClassRecursiveIterator; -use Doctrine\Common\Annotations\Reader; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; - -/** - * Injects filters. - * - * @internal - * - * @author Antoine Bluchet - */ -final class AnnotationFilterPass implements CompilerPassInterface -{ - use AnnotationFilterExtractorTrait; - - const TAG_FILTER_NAME = 'api_platform.filter'; - - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) - { - $resourceClassDirectories = $container->getParameter('api_platform.resource_class_directories'); - /** - * @var Reader - */ - $reader = $container->get('annotation_reader'); - - foreach (ReflectionClassRecursiveIterator::getReflectionClassesFromDirectories($resourceClassDirectories) as $className => $reflectionClass) { - $this->createFilterDefinitions($reflectionClass, $reader, $container); - } - } - - private function createFilterDefinitions(\ReflectionClass $reflectionClass, Reader $reader, ContainerBuilder $container) - { - foreach ($this->readFilterAnnotations($reflectionClass, $reader) as $id => list($arguments, $filterClass)) { - if ($container->hasDefinition($id)) { - continue; - } - - $definition = new Definition(); - $definition->setClass($filterClass); - $definition->addTag(self::TAG_FILTER_NAME); - $definition->setAutowired(true); - - foreach ($arguments as $key => $value) { - $definition->setArgument("$$key", $value); - } - - $container->setDefinition($id, $definition); - } - } -} diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php deleted file mode 100644 index 5ae351d727e..00000000000 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\DataProvider\SerializerAwareDataProviderInterface; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; - -/** - * Registers data providers. - * - * @internal - * - * @author Kévin Dunglas - * @author Vincent Chalamon - */ -final class DataProviderPass implements CompilerPassInterface -{ - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) - { - foreach (OperationType::TYPES as $type) { - $this->addSerializerLocator($container, $type); - } - } - - private function addSerializerLocator(ContainerBuilder $container, string $type) - { - $services = $container->findTaggedServiceIds("api_platform.{$type}_data_provider", true); - - foreach ($services as $id => $tags) { - $definition = $container->getDefinition((string) $id); - if (is_a($definition->getClass(), SerializerAwareDataProviderInterface::class, true)) { - $definition->addMethodCall('setSerializerLocator', [new Reference('api_platform.serializer_locator')]); - } - } - } -} diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php deleted file mode 100644 index 9ad49e05db4..00000000000 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler; - -use ApiPlatform\Core\Exception\RuntimeException; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; - -/** - * Injects filters. - * - * @internal - * - * @author Kévin Dunglas - */ -final class FilterPass implements CompilerPassInterface -{ - /** - * {@inheritdoc} - * - * @throws RuntimeException - */ - public function process(ContainerBuilder $container) - { - $filters = []; - foreach ($container->findTaggedServiceIds('api_platform.filter', true) as $serviceId => $tags) { - foreach ($tags as $tag) { - if (!isset($tag['id'])) { - $tag['id'] = $serviceId; - } - - $filters[$tag['id']] = new Reference($serviceId); - } - } - - $container->getDefinition('api_platform.filter_locator')->addArgument($filters); - $container->getDefinition('api_platform.filter_collection_factory')->addArgument(array_keys($filters)); - } -} diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php deleted file mode 100644 index b1dddf97fe3..00000000000 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ /dev/null @@ -1,325 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection; - -use ApiPlatform\Core\Exception\FilterValidationException; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use FOS\UserBundle\FOSUserBundle; -use GraphQL\GraphQL; -use Symfony\Bundle\TwigBundle\TwigBundle; -use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; -use Symfony\Component\Config\Definition\Builder\TreeBuilder; -use Symfony\Component\Config\Definition\ConfigurationInterface; -use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Serializer\Exception\ExceptionInterface; - -/** - * The configuration of the bundle. - * - * @author Kévin Dunglas - * @author Baptiste Meyer - */ -final class Configuration implements ConfigurationInterface -{ - /** - * {@inheritdoc} - */ - public function getConfigTreeBuilder() - { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('api_platform'); - - $rootNode - ->children() - ->scalarNode('title') - ->info('The title of the API.') - ->cannotBeEmpty() - ->defaultValue('') - ->end() - ->scalarNode('description') - ->info('The description of the API.') - ->cannotBeEmpty() - ->defaultValue('') - ->end() - ->scalarNode('version') - ->info('The version of the API.') - ->cannotBeEmpty() - ->defaultValue('0.0.0') - ->end() - ->scalarNode('default_operation_path_resolver') - ->defaultValue('api_platform.operation_path_resolver.underscore') - ->setDeprecated('The use of the `default_operation_path_resolver` has been deprecated in 2.1 and will be removed in 3.0. Use `path_segment_name_generator` instead.') - ->info('Specify the default operation path resolver to use for generating resources operations path.') - ->end() - ->scalarNode('name_converter')->defaultNull()->info('Specify a name converter to use.')->end() - ->scalarNode('path_segment_name_generator')->defaultValue('api_platform.path_segment_name_generator.underscore')->info('Specify a path name generator to use.')->end() - ->booleanNode('allow_plain_identifiers')->defaultFalse()->info('Allow plain identifiers, for example "id" instead of "@id" when denormalizing a relation.')->end() - ->arrayNode('validator') - ->addDefaultsIfNotSet() - ->children() - ->variableNode('serialize_payload_fields')->defaultValue([])->info('Enable the serialization of payload fields when a validation error is thrown.')->end() - ->end() - ->end() - ->arrayNode('eager_loading') - ->canBeDisabled() - ->addDefaultsIfNotSet() - ->children() - ->booleanNode('enabled')->defaultTrue()->info('To enable or disable eager loading')->end() - ->booleanNode('fetch_partial')->defaultFalse()->info('Fetch only partial data according to serialization groups. If enabled, Doctrine ORM entities will not work as expected if any of the other fields are used.')->end() - ->integerNode('max_joins')->defaultValue(30)->info('Max number of joined relations before EagerLoading throws a RuntimeException')->end() - ->booleanNode('force_eager')->defaultTrue()->info('Force join on every relation. If disabled, it will only join relations having the EAGER fetch mode.')->end() - ->end() - ->end() - ->booleanNode('enable_fos_user')->defaultValue(class_exists(FOSUserBundle::class))->info('Enable the FOSUserBundle integration.')->end() - ->booleanNode('enable_nelmio_api_doc') - ->defaultValue(false) - ->setDeprecated('Enabling the NelmioApiDocBundle integration has been deprecated in 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.') - ->info('Enable the NelmioApiDocBundle integration.') - ->end() - ->booleanNode('enable_swagger')->defaultValue(true)->info('Enable the Swagger documentation and export.')->end() - ->booleanNode('enable_swagger_ui')->defaultValue(class_exists(TwigBundle::class))->info('Enable Swagger ui.')->end() - ->booleanNode('enable_entrypoint')->defaultTrue()->info('Enable the entrypoint')->end() - ->booleanNode('enable_docs')->defaultTrue()->info('Enable the docs')->end() - ->booleanNode('enable_profiler')->defaultTrue()->info('Enable the data collector and the WebProfilerBundle integration.')->end() - - ->arrayNode('oauth') - ->canBeEnabled() - ->addDefaultsIfNotSet() - ->children() - ->booleanNode('enabled')->defaultFalse()->info('To enable or disable oauth')->end() - ->scalarNode('clientId')->defaultValue('')->info('The oauth client id.')->end() - ->scalarNode('clientSecret')->defaultValue('')->info('The oauth client secret.')->end() - ->scalarNode('type')->defaultValue('oauth2')->info('The oauth client secret.')->end() - ->scalarNode('flow')->defaultValue('application')->info('The oauth flow grant type.')->end() - ->scalarNode('tokenUrl')->defaultValue('/oauth/v2/token')->info('The oauth token url.')->end() - ->scalarNode('authorizationUrl')->defaultValue('/oauth/v2/auth')->info('The oauth authentication url.')->end() - ->arrayNode('scopes') - ->prototype('scalar')->end() - ->end() - ->end() - ->end() - - ->arrayNode('graphql') - ->canBeEnabled() - ->addDefaultsIfNotSet() - ->children() - ->booleanNode('enabled')->defaultValue(class_exists(GraphQL::class))->info('To enable or disable GraphQL.')->end() - ->arrayNode('graphiql') - ->canBeEnabled() - ->addDefaultsIfNotSet() - ->children() - ->booleanNode('enabled')->defaultValue(class_exists(GraphQL::class) && class_exists(TwigBundle::class))->info('To enable or disable GraphiQL.')->end() - ->end() - ->end() - ->end() - ->end() - - ->arrayNode('swagger') - ->addDefaultsIfNotSet() - ->children() - ->arrayNode('api_keys') - ->prototype('array') - ->children() - ->scalarNode('name') - ->info('The name of the header or query parameter containing the api key.') - ->end() - ->enumNode('type') - ->info('Whether the api key should be a query parameter or a header.') - ->values(['query', 'header']) - ->end() - ->end() - ->end() - ->end() - ->end() - ->end() - - ->arrayNode('collection') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('order')->defaultValue('ASC')->info('The default order of results.')->end() // Default ORDER is required for postgresql and mysql >= 5.7 when using LIMIT/OFFSET request - ->scalarNode('order_parameter_name')->defaultValue('order')->cannotBeEmpty()->info('The name of the query parameter to order results.')->end() - ->arrayNode('pagination') - ->canBeDisabled() - ->addDefaultsIfNotSet() - ->children() - ->booleanNode('enabled')->defaultTrue()->info('To enable or disable pagination for all resource collections by default.')->end() - ->booleanNode('partial')->defaultFalse()->info('To enable or disable partial pagination for all resource collections by default when pagination is enabled.')->end() - ->booleanNode('client_enabled')->defaultFalse()->info('To allow the client to enable or disable the pagination.')->end() - ->booleanNode('client_items_per_page')->defaultFalse()->info('To allow the client to set the number of items per page.')->end() - ->booleanNode('client_partial')->defaultFalse()->info('To allow the client to enable or disable partial pagination.')->end() - ->integerNode('items_per_page')->defaultValue(30)->info('The default number of items per page.')->end() - ->integerNode('maximum_items_per_page')->defaultNull()->info('The maximum number of items per page.')->end() - ->scalarNode('page_parameter_name')->defaultValue('page')->cannotBeEmpty()->info('The default name of the parameter handling the page number.')->end() - ->scalarNode('enabled_parameter_name')->defaultValue('pagination')->cannotBeEmpty()->info('The name of the query parameter to enable or disable pagination.')->end() - ->scalarNode('items_per_page_parameter_name')->defaultValue('itemsPerPage')->cannotBeEmpty()->info('The name of the query parameter to set the number of items per page.')->end() - ->scalarNode('partial_parameter_name')->defaultValue('partial')->cannotBeEmpty()->info('The name of the query parameter to enable or disable partial pagination.')->end() - ->end() - ->end() - ->end() - ->end() - - ->arrayNode('mapping') - ->addDefaultsIfNotSet() - ->children() - ->arrayNode('paths') - ->prototype('scalar')->end() - ->end() - ->end() - ->end() - - ->arrayNode('resource_class_directories') - ->prototype('scalar')->end() - ->end() - - ->arrayNode('http_cache') - ->addDefaultsIfNotSet() - ->children() - ->booleanNode('etag')->defaultTrue()->info('Automatically generate etags for API responses.')->end() - ->integerNode('max_age')->defaultNull()->info('Default value for the response max age.')->end() - ->integerNode('shared_max_age')->defaultNull()->info('Default value for the response shared (proxy) max age.')->end() - ->arrayNode('vary') - ->defaultValue(['Accept']) - ->prototype('scalar')->end() - ->info('Default values of the "Vary" HTTP header.') - ->end() - ->booleanNode('public')->defaultNull()->info('To make all responses public by default.')->end() - ->arrayNode('invalidation') - ->info('Enable the tags-based cache invalidation system.') - ->canBeEnabled() - ->children() - ->arrayNode('varnish_urls') - ->defaultValue([]) - ->prototype('scalar')->end() - ->info('URLs of the Varnish servers to purge using cache tags when a resource is updated.') - ->end() - ->variableNode('request_options') - ->defaultValue([]) - ->validate() - ->ifTrue(function ($v) { return false === \is_array($v); }) - ->thenInvalid('The request_options parameter must be an array.') - ->end() - ->info('To pass options to the client charged with the request.') - ->end() - ->end() - ->end() - ->end() - ->end() - - ->end(); - - $this->addExceptionToStatusSection($rootNode); - - $this->addFormatSection($rootNode, 'formats', [ - 'jsonld' => ['mime_types' => ['application/ld+json']], - 'json' => ['mime_types' => ['application/json']], // Swagger support - 'html' => ['mime_types' => ['text/html']], // Swagger UI support - ]); - $this->addFormatSection($rootNode, 'error_formats', [ - 'jsonproblem' => ['mime_types' => ['application/problem+json']], - 'jsonld' => ['mime_types' => ['application/ld+json']], - ]); - - return $treeBuilder; - } - - /** - * Adds an exception to status section. - * - * - * @throws InvalidConfigurationException - */ - private function addExceptionToStatusSection(ArrayNodeDefinition $rootNode) - { - $rootNode - ->children() - ->arrayNode('exception_to_status') - ->defaultValue([ - ExceptionInterface::class => Response::HTTP_BAD_REQUEST, - InvalidArgumentException::class => Response::HTTP_BAD_REQUEST, - FilterValidationException::class => Response::HTTP_BAD_REQUEST, - ]) - ->info('The list of exceptions mapped to their HTTP status code.') - ->normalizeKeys(false) - ->useAttributeAsKey('exception_class') - ->beforeNormalization() - ->ifArray() - ->then(function (array $exceptionToStatus) { - foreach ($exceptionToStatus as &$httpStatusCode) { - if (\is_int($httpStatusCode)) { - continue; - } - - if (\defined($httpStatusCodeConstant = sprintf('%s::%s', Response::class, $httpStatusCode))) { - @trigger_error(sprintf('Using a string "%s" as a constant of the "%s" class is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3. Use the Symfony\'s custom YAML extension for PHP constants instead (i.e. "!php/const:%s").', $httpStatusCode, Response::class, $httpStatusCodeConstant), E_USER_DEPRECATED); - - $httpStatusCode = \constant($httpStatusCodeConstant); - } - } - - return $exceptionToStatus; - }) - ->end() - ->prototype('integer')->end() - ->validate() - ->ifArray() - ->then(function (array $exceptionToStatus) { - foreach ($exceptionToStatus as $httpStatusCode) { - if ($httpStatusCode < 100 || $httpStatusCode >= 600) { - throw new InvalidConfigurationException(sprintf('The HTTP status code "%s" is not valid.', $httpStatusCode)); - } - } - - return $exceptionToStatus; - }) - ->end() - ->end() - ->end(); - } - - /** - * Adds a format section. - */ - private function addFormatSection(ArrayNodeDefinition $rootNode, string $key, array $defaultValue) - { - $rootNode - ->children() - ->arrayNode($key) - ->defaultValue($defaultValue) - ->info('The list of enabled formats. The first one will be the default.') - ->normalizeKeys(false) - ->useAttributeAsKey('format') - ->beforeNormalization() - ->ifArray() - ->then(function ($v) { - foreach ($v as $format => $value) { - if (isset($value['mime_types'])) { - continue; - } - - $v[$format] = ['mime_types' => $value]; - } - - return $v; - }) - ->end() - ->prototype('array') - ->children() - ->arrayNode('mime_types')->prototype('scalar')->end()->end() - ->end() - ->end() - ->end() - ->end(); - } -} diff --git a/src/Bridge/Symfony/Bundle/EventListener/SwaggerUiListener.php b/src/Bridge/Symfony/Bundle/EventListener/SwaggerUiListener.php deleted file mode 100644 index 32c9320813b..00000000000 --- a/src/Bridge/Symfony/Bundle/EventListener/SwaggerUiListener.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\Symfony\Bundle\EventListener; - -use Symfony\Component\HttpKernel\Event\GetResponseEvent; - -final class SwaggerUiListener -{ - /** - * Sets SwaggerUiAction as controller if the requested format is HTML. - */ - public function onKernelRequest(GetResponseEvent $event) - { - $request = $event->getRequest(); - if ( - 'html' !== $request->getRequestFormat('') || - (!$request->attributes->has('_api_resource_class') && !$request->attributes->has('_api_respond')) - ) { - return; - } - - $request->attributes->set('_controller', 'api_platform.swagger.action.ui'); - } -} diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml deleted file mode 100644 index 4dd1eab8217..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ /dev/null @@ -1,282 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.formats% - %api_platform.resource_class_directories% - - %api_platform.graphql.enabled% - %api_platform.enable_entrypoint% - %api_platform.enable_docs% - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.formats% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.allow_plain_identifiers% - - - - - - - - - - - - - - - - - - - - - - - - The "%service_id%" service is deprecated since API Platform 2.1 and will be removed in 3.0. Use "api_platform.path_segment_name_generator.underscore" instead. - - - - The "%service_id%" service is deprecated since API Platform 2.1 and will be removed in 3.0. Use "api_platform.path_segment_name_generator.dash" instead. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.error_formats% - - - - - - api_platform.action.exception - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.title% - %api_platform.description% - %api_platform.version% - - - - - - %api_platform.error_formats% - %api_platform.exception_to_status% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/data_collector.xml b/src/Bridge/Symfony/Bundle/Resources/config/data_collector.xml deleted file mode 100644 index 2cbf4b3b5f2..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/data_collector.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/data_persister.xml b/src/Bridge/Symfony/Bundle/Resources/config/data_persister.xml deleted file mode 100644 index aa61dc3565a..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/data_persister.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml b/src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml deleted file mode 100644 index 2ed2f84d7d4..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml deleted file mode 100644 index d8d5b661c6f..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - null - - - - - - - - null - %api_platform.collection.order_parameter_name% - - - - - - null - - - - - - null - - - - - - null - - - - - - null - - - - - - null - - - - - - - - - - - - - - - - - %api_platform.eager_loading.max_joins% - %api_platform.eager_loading.force_eager% - null - null - %api_platform.eager_loading.fetch_partial% - - - - - - - - - - - - - - - - - - - - %api_platform.eager_loading.force_eager% - - - - - - - - - - %api_platform.collection.pagination.enabled% - %api_platform.collection.pagination.client_enabled% - %api_platform.collection.pagination.client_items_per_page% - %api_platform.collection.pagination.items_per_page% - %api_platform.collection.pagination.page_parameter_name% - %api_platform.collection.pagination.enabled_parameter_name% - %api_platform.collection.pagination.items_per_page_parameter_name% - %api_platform.collection.pagination.maximum_items_per_page% - %api_platform.collection.pagination.partial% - %api_platform.collection.pagination.client_partial% - %api_platform.collection.pagination.partial_parameter_name% - - - - - - - %api_platform.collection.order% - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_http_cache_purger.xml b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_http_cache_purger.xml deleted file mode 100644 index 6c7f3c62f3b..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_http_cache_purger.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/filter.xml b/src/Bridge/Symfony/Bundle/Resources/config/filter.xml deleted file mode 100644 index 61fce0c2d38..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/filter.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - The "%service_id%" service is deprecated since 2.1 and will be removed in 3.0. Use the "api_platform.filter_locator" service instead. - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/fos_user.xml b/src/Bridge/Symfony/Bundle/Resources/config/fos_user.xml deleted file mode 100644 index 30c0141e64a..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/fos_user.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml b/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml deleted file mode 100644 index bdc491d1b38..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - %api_platform.collection.pagination.enabled% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.collection.pagination.enabled% - - - - - - - - - %kernel.debug% - %api_platform.graphql.graphiql.enabled% - %api_platform.title% - - - - - - - - - - - - - - %api_platform.allow_plain_identifiers% - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/hal.xml b/src/Bridge/Symfony/Bundle/Resources/config/hal.xml deleted file mode 100644 index 9e6a3f5d358..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/hal.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - jsonhal - - - - - - - - - - - - - - - %api_platform.collection.pagination.page_parameter_name% - - - - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/http_cache.xml b/src/Bridge/Symfony/Bundle/Resources/config/http_cache.xml deleted file mode 100644 index c7f16d278d8..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/http_cache.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - %api_platform.http_cache.etag% - %api_platform.http_cache.max_age% - %api_platform.http_cache.shared_max_age% - %api_platform.http_cache.vary% - %api_platform.http_cache.public% - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/http_cache_tags.xml b/src/Bridge/Symfony/Bundle/Resources/config/http_cache_tags.xml deleted file mode 100644 index 0e2224f1ba8..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/http_cache_tags.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml b/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml deleted file mode 100644 index b4b4dd3b094..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.validator.serialize_payload_fields% - - - - - - - - - - - - - - - - %kernel.debug% - - - - - - - - - - - - - - - %api_platform.collection.pagination.page_parameter_name% - %api_platform.collection.pagination.enabled_parameter_name% - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml b/src/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml deleted file mode 100644 index ccebf5ceb3c..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - jsonapi - - - - - - - - - - - - - - - - - - - %api_platform.collection.pagination.page_parameter_name% - - - - - - - - - - - - - - - - - - - - - - - - - - %kernel.debug% - - - - - - - - - - - - %api_platform.collection.order_parameter_name% - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml b/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml deleted file mode 100644 index 999ce4ff92b..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsonld - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/annotation.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata/annotation.xml deleted file mode 100644 index 70556d4f8bc..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/annotation.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - %api_platform.resource_class_directories% - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml deleted file mode 100644 index 8b8386c6775..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - %api_platform.formats% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/php_doc.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata/php_doc.xml deleted file mode 100644 index 3520a386648..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/php_doc.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/xml.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata/xml.xml deleted file mode 100644 index 2662d22ba22..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/xml.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml deleted file mode 100644 index bf172a11930..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/nelmio_api_doc.xml b/src/Bridge/Symfony/Bundle/Resources/config/nelmio_api_doc.xml deleted file mode 100644 index fdc8d00f79f..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/nelmio_api_doc.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - The "%service_id%" service is deprecated since API Platform 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - - - - - - - - - - - - The "%service_id%" service is deprecated since API Platform 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/problem.xml b/src/Bridge/Symfony/Bundle/Resources/config/problem.xml deleted file mode 100644 index dee6616a9fa..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/problem.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - jsonproblem - - - - - - %api_platform.validator.serialize_payload_fields% - - - - - - - %kernel.debug% - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/ramsey_uuid.xml b/src/Bridge/Symfony/Bundle/Resources/config/ramsey_uuid.xml deleted file mode 100644 index a5d0626ab06..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/ramsey_uuid.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/routing/api.xml deleted file mode 100644 index 0a04cf423c8..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/routing/api.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - api_platform.action.entrypoint - - 1 - index - index - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/docs.xml b/src/Bridge/Symfony/Bundle/Resources/config/routing/docs.xml deleted file mode 100644 index ba2c7887fad..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/routing/docs.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - api_platform.action.documentation - 1 - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/graphql.xml b/src/Bridge/Symfony/Bundle/Resources/config/routing/graphql.xml deleted file mode 100644 index faa6db883df..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/routing/graphql.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - api_platform.graphql.action.entrypoint - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml b/src/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml deleted file mode 100644 index 158b2a675aa..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - api_platform.jsonld.action.context - 1 - jsonld - - .+ - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/security.xml b/src/Bridge/Symfony/Bundle/Resources/config/security.xml deleted file mode 100644 index 530132e0a52..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/security.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/security_expression_language.xml b/src/Bridge/Symfony/Bundle/Resources/config/security_expression_language.xml deleted file mode 100644 index cc82ec3b261..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/security_expression_language.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml b/src/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml deleted file mode 100644 index 050ffd88f0e..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml b/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml deleted file mode 100644 index 13daa4757ee..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - null - - - %api_platform.oauth.enabled% - %api_platform.oauth.type% - %api_platform.oauth.flow% - %api_platform.oauth.tokenUrl% - %api_platform.oauth.authorizationUrl% - %api_platform.oauth.scopes% - %api_platform.swagger.api_keys% - - %api_platform.collection.pagination.enabled% - %api_platform.collection.pagination.page_parameter_name% - %api_platform.collection.pagination.client_items_per_page% - %api_platform.collection.pagination.items_per_page_parameter_name% - %api_platform.collection.pagination.client_enabled% - %api_platform.collection.pagination.enabled_parameter_name% - - - - - - - - - - - - %api_platform.title% - %api_platform.description% - %api_platform.version% - %api_platform.formats% - - - - - - - - - - - - - %api_platform.title% - %api_platform.description% - %api_platform.version% - - %api_platform.oauth.enabled% - %api_platform.oauth.clientId% - %api_platform.oauth.clientSecret% - %api_platform.oauth.type% - %api_platform.oauth.flow% - %api_platform.oauth.tokenUrl% - %api_platform.oauth.authorizationUrl% - %api_platform.oauth.scopes% - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/validator.xml b/src/Bridge/Symfony/Bundle/Resources/config/validator.xml deleted file mode 100644 index d34dcd8cfe6..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/validator.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/public/es6-promise/es6-promise.auto.min.js b/src/Bridge/Symfony/Bundle/Resources/public/es6-promise/es6-promise.auto.min.js deleted file mode 100644 index fdf8bff2676..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/public/es6-promise/es6-promise.auto.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.ES6Promise=e()}(this,function(){"use strict";function t(t){var e=typeof t;return null!==t&&("object"===e||"function"===e)}function e(t){return"function"==typeof t}function n(t){B=t}function r(t){G=t}function o(){return function(){return process.nextTick(a)}}function i(){return"undefined"!=typeof z?function(){z(a)}:c()}function s(){var t=0,e=new J(a),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function u(){var t=new MessageChannel;return t.port1.onmessage=a,function(){return t.port2.postMessage(0)}}function c(){var t=setTimeout;return function(){return t(a,1)}}function a(){for(var t=0;t this.readyToRead) { - push = USE_ALLOC ? Buffer.alloc(this.readyToRead, '', 'binary') : new Buffer(this.readyToRead, 'binary'); - this.responseBuffer.copy(push, 0, 0, this.readyToRead); - restSize = this.responseBuffer.length - this.readyToRead; - rest = USE_ALLOC ? Buffer.alloc(restSize, '', 'binary') : new Buffer(restSize, 'binary'); - this.responseBuffer.copy(rest, 0, this.readyToRead); - } else { - push = this.responseBuffer; - rest = USE_ALLOC ? Buffer.alloc(0, '', 'binary') : new Buffer(0, 'binary'); - } - this.responseBuffer = rest; - this.readyToRead = 0; - if (this.options.encoding) { - this.push(push, this.options.encoding); - } else { - this.push(push); - } -}; - -FetchStream.prototype.destroy = function (ex) { - this.emit('destroy', ex); -}; - -FetchStream.prototype.normalizeOptions = function () { - - // cookiejar - this.cookieJar = this.options.cookieJar || new CookieJar(); - - // default redirects - 10 - // if disableRedirect is set, then 0 - if (!this.options.disableRedirect && typeof this.options.maxRedirects !== 'number' && - !(this.options.maxRedirects instanceof Number)) { - this.options.maxRedirects = 10; - } else if (this.options.disableRedirects) { - this.options.maxRedirects = 0; - } - - // normalize header keys - // HTTP and HTTPS takes in key names in case insensitive but to find - // an exact value from an object key name needs to be case sensitive - // so we're just lowercasing all input keys - this.options.headers = this.options.headers || {}; - - var keys = Object.keys(this.options.headers); - var newheaders = {}; - var i; - - for (i = keys.length - 1; i >= 0; i--) { - newheaders[keys[i].toLowerCase().trim()] = this.options.headers[keys[i]]; - } - - this.options.headers = newheaders; - - if (!this.options.headers['user-agent']) { - this.options.headers['user-agent'] = this.userAgent; - } - - if (!this.options.headers.pragma) { - this.options.headers.pragma = 'no-cache'; - } - - if (!this.options.headers['cache-control']) { - this.options.headers['cache-control'] = 'no-cache'; - } - - if (!this.options.disableGzip) { - this.options.headers['accept-encoding'] = 'gzip, deflate'; - } else { - delete this.options.headers['accept-encoding']; - } - - // max length for the response, - // if not set, default is Infinity - if (!this.options.maxResponseLength) { - this.options.maxResponseLength = Infinity; - } - - // method: - // defaults to GET, or when payload present to POST - if (!this.options.method) { - this.options.method = this.options.payload || this.options.payloadSize ? 'POST' : 'GET'; - } - - // set cookies - // takes full cookie definition strings as params - if (this.options.cookies) { - for (i = 0; i < this.options.cookies.length; i++) { - this.cookieJar.setCookie(this.options.cookies[i], this.url); - } - } - - // rejectUnauthorized - if (typeof this.options.rejectUnauthorized === 'undefined') { - this.options.rejectUnauthorized = true; - } -}; - -FetchStream.prototype.parseUrl = function (url) { - var urlparts = urllib.parse(url, false, true), - transport, - urloptions = { - host: urlparts.hostname || urlparts.host, - port: urlparts.port, - path: urlparts.pathname + (urlparts.search || '') || '/', - method: this.options.method, - rejectUnauthorized: this.options.rejectUnauthorized - }; - - switch (urlparts.protocol) { - case 'https:': - transport = https; - break; - case 'http:': - default: - transport = http; - break; - } - - if (transport === https) { - if('agentHttps' in this.options){ - urloptions.agent = this.options.agentHttps; - } - if('agent' in this.options){ - urloptions.agent = this.options.agent; - } - } else { - if('agentHttp' in this.options){ - urloptions.agent = this.options.agentHttp; - } - if('agent' in this.options){ - urloptions.agent = this.options.agent; - } - } - - if (!urloptions.port) { - switch (urlparts.protocol) { - case 'https:': - urloptions.port = 443; - break; - case 'http:': - default: - urloptions.port = 80; - break; - } - } - - urloptions.headers = this.options.headers || {}; - - if (urlparts.auth) { - var buf = USE_ALLOC ? Buffer.alloc(Buffer.byteLength(urlparts.auth), urlparts.auth) : new Buffer(urlparts.auth); - urloptions.headers.Authorization = 'Basic ' + buf.toString('base64'); - } - - return { - urloptions: urloptions, - transport: transport - }; -}; - -FetchStream.prototype.setEncoding = function (encoding) { - this.options.encoding = encoding; -}; - -FetchStream.prototype.runStream = function (url) { - var url_data = this.parseUrl(url), - cookies = this.cookieJar.getCookies(url); - - if (cookies) { - url_data.urloptions.headers.cookie = cookies; - } else { - delete url_data.urloptions.headers.cookie; - } - - if (this.options.payload) { - url_data.urloptions.headers['content-length'] = Buffer.byteLength(this.options.payload || '', 'utf-8'); - } - - if (this.options.payloadSize) { - url_data.urloptions.headers['content-length'] = this.options.payloadSize; - } - - if (this.options.asyncDnsLoookup) { - var dnsCallback = (function (err, addresses) { - if (err) { - this.emit('error', err); - return; - } - - url_data.urloptions.headers.host = url_data.urloptions.hostname || url_data.urloptions.host; - url_data.urloptions.hostname = addresses[0]; - url_data.urloptions.host = url_data.urloptions.headers.host + (url_data.urloptions.port ? ':' + url_data.urloptions.port : ''); - - this._runStream(url_data, url); - }).bind(this); - - if (net.isIP(url_data.urloptions.host)) { - dnsCallback(null, [url_data.urloptions.host]); - } else { - dns.resolve4(url_data.urloptions.host, dnsCallback); - } - } else { - this._runStream(url_data, url); - } -}; - -FetchStream.prototype._runStream = function (url_data, url) { - - var req = url_data.transport.request(url_data.urloptions, (function (res) { - - // catch new cookies before potential redirect - if (Array.isArray(res.headers['set-cookie'])) { - for (var i = 0; i < res.headers['set-cookie'].length; i++) { - this.cookieJar.setCookie(res.headers['set-cookie'][i], url); - } - } - - if ([301, 302, 303, 307, 308].indexOf(res.statusCode) >= 0) { - if (!this.options.disableRedirects && this.options.maxRedirects > this._redirect_count && res.headers.location) { - this._redirect_count++; - req.destroy(); - this.runStream(urllib.resolve(url, res.headers.location)); - return; - } - } - - this.meta = { - status: res.statusCode, - responseHeaders: res.headers, - finalUrl: url, - redirectCount: this._redirect_count, - cookieJar: this.cookieJar - }; - - var curlen = 0, - maxlen, - - receive = (function (chunk) { - if (curlen + chunk.length > this.options.maxResponseLength) { - maxlen = this.options.maxResponseLength - curlen; - } else { - maxlen = chunk.length; - } - - if (maxlen <= 0) { - return; - } - - curlen += Math.min(maxlen, chunk.length); - if (maxlen >= chunk.length) { - if (this.responseBuffer.length === 0) { - this.responseBuffer = chunk; - } else { - this.responseBuffer = Buffer.concat([this.responseBuffer, chunk]); - } - } else { - this.responseBuffer = Buffer.concat([this.responseBuffer, chunk], this.responseBuffer.length + maxlen); - } - this.drainBuffer(); - }).bind(this), - - error = (function (e) { - this.ended = true; - this.emit('error', e); - this.drainBuffer(); - }).bind(this), - - end = (function () { - this.ended = true; - if (this.responseBuffer.length === 0) { - this.push(null); - } - }).bind(this), - - unpack = (function (type, res) { - var z = zlib['create' + type](); - z.on('data', receive); - z.on('error', error); - z.on('end', end); - res.pipe(z); - }).bind(this); - - this.emit('meta', this.meta); - - if (res.headers['content-encoding']) { - switch (res.headers['content-encoding'].toLowerCase().trim()) { - case 'gzip': - return unpack('Gunzip', res); - case 'deflate': - return unpack('InflateRaw', res); - } - } - - res.on('data', receive); - res.on('end', end); - - }).bind(this)); - - req.on('error', (function (e) { - this.emit('error', e); - }).bind(this)); - - if (this.options.timeout) { - req.setTimeout(this.options.timeout, req.abort.bind(req)); - } - this.on('destroy', req.abort.bind(req)); - - if (this.options.payload) { - req.end(this.options.payload); - } else if (this.options.payloadStream) { - this.options.payloadStream.pipe(req); - this.options.payloadStream.resume(); - } else { - req.end(); - } -}; - -function fetchUrl(url, options, callback) { - if (!callback && typeof options === 'function') { - callback = options; - options = undefined; - } - options = options || {}; - - var fetchstream = new FetchStream(url, options), - response_data, chunks = [], - length = 0, - curpos = 0, - buffer, - content_type, - callbackFired = false; - - fetchstream.on('meta', function (meta) { - response_data = meta; - content_type = _parseContentType(meta.responseHeaders['content-type']); - }); - - fetchstream.on('data', function (chunk) { - if (chunk) { - chunks.push(chunk); - length += chunk.length; - } - }); - - fetchstream.on('error', function (error) { - if (error && error.code === 'HPE_INVALID_CONSTANT') { - // skip invalid formatting errors - return; - } - if (callbackFired) { - return; - } - callbackFired = true; - callback(error); - }); - - fetchstream.on('end', function () { - if (callbackFired) { - return; - } - callbackFired = true; - - buffer = USE_ALLOC ? Buffer.alloc(length) : new Buffer(length); - for (var i = 0, len = chunks.length; i < len; i++) { - chunks[i].copy(buffer, curpos); - curpos += chunks[i].length; - } - - if (content_type.mimeType === 'text/html') { - content_type.charset = _findHTMLCharset(buffer) || content_type.charset; - } - - content_type.charset = (options.overrideCharset || content_type.charset || 'utf-8').trim().toLowerCase(); - - - if (!options.disableDecoding && !content_type.charset.match(/^utf-?8$/i)) { - buffer = encodinglib.convert(buffer, 'UTF-8', content_type.charset); - } - - if (options.outputEncoding) { - return callback(null, response_data, buffer.toString(options.outputEncoding)); - } else { - return callback(null, response_data, buffer); - } - - }); -} - -function _parseContentType(str) { - if (!str) { - return {}; - } - var parts = str.split(';'), - mimeType = parts.shift(), - charset, chparts; - - for (var i = 0, len = parts.length; i < len; i++) { - chparts = parts[i].split('='); - if (chparts.length > 1) { - if (chparts[0].trim().toLowerCase() === 'charset') { - charset = chparts[1]; - } - } - } - - return { - mimeType: (mimeType || '').trim().toLowerCase(), - charset: (charset || 'UTF-8').trim().toLowerCase() // defaults to UTF-8 - }; -} - -function _findHTMLCharset(htmlbuffer) { - - var body = htmlbuffer.toString('ascii'), - input, meta, charset; - - if ((meta = body.match(/]*?>/i))) { - input = meta[0]; - } - - if (input) { - charset = input.match(/charset\s?=\s?([a-zA-Z\-0-9]*);?/); - if (charset) { - charset = (charset[1] || '').trim().toLowerCase(); - } - } - - if (!charset && (meta = body.match(/ * { - margin: 0; -} - -.graphiql-container .toolbar-button-group > *:not(:last-child) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.graphiql-container .toolbar-button-group > *:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - margin-left: -1px; -} - -.graphiql-container .execute-button-wrap { - height: 34px; - margin: 0 14px 0 28px; - position: relative; -} - -.graphiql-container .execute-button { - background: -webkit-gradient(linear, left top, left bottom, from(#fdfdfd), to(#d2d3d6)); - background: linear-gradient(#fdfdfd, #d2d3d6); - border-radius: 17px; - border: 1px solid rgba(0,0,0,0.25); - -webkit-box-shadow: 0 1px 0 #fff; - box-shadow: 0 1px 0 #fff; - cursor: pointer; - fill: #444; - height: 34px; - margin: 0; - padding: 0; - width: 34px; -} - -.graphiql-container .execute-button svg { - pointer-events: none; -} - -.graphiql-container .execute-button:active { - background: -webkit-gradient(linear, left top, left bottom, from(#e6e6e6), to(#c3c3c3)); - background: linear-gradient(#e6e6e6, #c3c3c3); - -webkit-box-shadow: - 0 1px 0 #fff, - inset 0 0 2px rgba(0, 0, 0, 0.2), - inset 0 0 6px rgba(0, 0, 0, 0.1); - box-shadow: - 0 1px 0 #fff, - inset 0 0 2px rgba(0, 0, 0, 0.2), - inset 0 0 6px rgba(0, 0, 0, 0.1); -} - -.graphiql-container .execute-button:focus { - outline: 0; -} - -.graphiql-container .toolbar-menu, -.graphiql-container .toolbar-select { - position: relative; -} - -.graphiql-container .execute-options, -.graphiql-container .toolbar-menu-items, -.graphiql-container .toolbar-select-options { - background: #fff; - -webkit-box-shadow: - 0 0 0 1px rgba(0,0,0,0.1), - 0 2px 4px rgba(0,0,0,0.25); - box-shadow: - 0 0 0 1px rgba(0,0,0,0.1), - 0 2px 4px rgba(0,0,0,0.25); - margin: 0; - padding: 6px 0; - position: absolute; - z-index: 100; -} - -.graphiql-container .execute-options { - min-width: 100px; - top: 37px; - left: -1px; -} - -.graphiql-container .toolbar-menu-items { - left: 1px; - margin-top: -1px; - min-width: 110%; - top: 100%; - visibility: hidden; -} - -.graphiql-container .toolbar-menu-items.open { - visibility: visible; -} - -.graphiql-container .toolbar-select-options { - left: 0; - min-width: 100%; - top: -5px; - visibility: hidden; -} - -.graphiql-container .toolbar-select-options.open { - visibility: visible; -} - -.graphiql-container .execute-options > li, -.graphiql-container .toolbar-menu-items > li, -.graphiql-container .toolbar-select-options > li { - cursor: pointer; - display: block; - margin: none; - max-width: 300px; - overflow: hidden; - padding: 2px 20px 4px 11px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.graphiql-container .execute-options > li.selected, -.graphiql-container .toolbar-menu-items > li.hover, -.graphiql-container .toolbar-menu-items > li:active, -.graphiql-container .toolbar-menu-items > li:hover, -.graphiql-container .toolbar-select-options > li.hover, -.graphiql-container .toolbar-select-options > li:active, -.graphiql-container .toolbar-select-options > li:hover, -.graphiql-container .history-contents > p:hover, -.graphiql-container .history-contents > p:active { - background: #e10098; - color: #fff; -} - -.graphiql-container .toolbar-select-options > li > svg { - display: inline; - fill: #666; - margin: 0 -6px 0 6px; - pointer-events: none; - vertical-align: middle; -} - -.graphiql-container .toolbar-select-options > li.hover > svg, -.graphiql-container .toolbar-select-options > li:active > svg, -.graphiql-container .toolbar-select-options > li:hover > svg { - fill: #fff; -} - -.graphiql-container .CodeMirror-scroll { - overflow-scrolling: touch; -} - -.graphiql-container .CodeMirror { - color: #141823; - font-family: - 'Consolas', - 'Inconsolata', - 'Droid Sans Mono', - 'Monaco', - monospace; - font-size: 13px; - height: 100%; - left: 0; - position: absolute; - top: 0; - width: 100%; -} - -.graphiql-container .CodeMirror-lines { - padding: 20px 0; -} - -.CodeMirror-hint-information .content { - box-orient: vertical; - color: #141823; - display: -webkit-box; - display: -ms-flexbox; - display: flex; - font-family: system, -apple-system, 'San Francisco', '.SFNSDisplay-Regular', 'Segoe UI', Segoe, 'Segoe WP', 'Helvetica Neue', helvetica, 'Lucida Grande', arial, sans-serif; - font-size: 13px; - line-clamp: 3; - line-height: 16px; - max-height: 48px; - overflow: hidden; - text-overflow: -o-ellipsis-lastline; -} - -.CodeMirror-hint-information .content p:first-child { - margin-top: 0; -} - -.CodeMirror-hint-information .content p:last-child { - margin-bottom: 0; -} - -.CodeMirror-hint-information .infoType { - color: #CA9800; - cursor: pointer; - display: inline; - margin-right: 0.5em; -} - -.autoInsertedLeaf.cm-property { - -webkit-animation-duration: 6s; - animation-duration: 6s; - -webkit-animation-name: insertionFade; - animation-name: insertionFade; - border-bottom: 2px solid rgba(255, 255, 255, 0); - border-radius: 2px; - margin: -2px -4px -1px; - padding: 2px 4px 1px; -} - -@-webkit-keyframes insertionFade { - from, to { - background: rgba(255, 255, 255, 0); - border-color: rgba(255, 255, 255, 0); - } - - 15%, 85% { - background: #fbffc9; - border-color: #f0f3c0; - } -} - -@keyframes insertionFade { - from, to { - background: rgba(255, 255, 255, 0); - border-color: rgba(255, 255, 255, 0); - } - - 15%, 85% { - background: #fbffc9; - border-color: #f0f3c0; - } -} - -div.CodeMirror-lint-tooltip { - background-color: white; - border-radius: 2px; - border: 0; - color: #141823; - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); - font-family: - system, - -apple-system, - 'San Francisco', - '.SFNSDisplay-Regular', - 'Segoe UI', - Segoe, - 'Segoe WP', - 'Helvetica Neue', - helvetica, - 'Lucida Grande', - arial, - sans-serif; - font-size: 13px; - line-height: 16px; - max-width: 430px; - opacity: 0; - padding: 8px 10px; - -webkit-transition: opacity 0.15s; - transition: opacity 0.15s; - white-space: pre-wrap; -} - -div.CodeMirror-lint-tooltip > * { - padding-left: 23px; -} - -div.CodeMirror-lint-tooltip > * + * { - margin-top: 12px; -} - -/* COLORS */ - -.graphiql-container .CodeMirror-foldmarker { - border-radius: 4px; - background: #08f; - background: -webkit-gradient(linear, left top, left bottom, from(#43A8FF), to(#0F83E8)); - background: linear-gradient(#43A8FF, #0F83E8); - -webkit-box-shadow: - 0 1px 1px rgba(0, 0, 0, 0.2), - inset 0 0 0 1px rgba(0, 0, 0, 0.1); - box-shadow: - 0 1px 1px rgba(0, 0, 0, 0.2), - inset 0 0 0 1px rgba(0, 0, 0, 0.1); - color: white; - font-family: arial; - font-size: 12px; - line-height: 0; - margin: 0 3px; - padding: 0px 4px 1px; - text-shadow: 0 -1px rgba(0, 0, 0, 0.1); -} - -.graphiql-container div.CodeMirror span.CodeMirror-matchingbracket { - color: #555; - text-decoration: underline; -} - -.graphiql-container div.CodeMirror span.CodeMirror-nonmatchingbracket { - color: #f00; -} - -/* Comment */ -.cm-comment { - color: #999; -} - -/* Punctuation */ -.cm-punctuation { - color: #555; -} - -/* Keyword */ -.cm-keyword { - color: #B11A04; -} - -/* OperationName, FragmentName */ -.cm-def { - color: #D2054E; -} - -/* FieldName */ -.cm-property { - color: #1F61A0; -} - -/* FieldAlias */ -.cm-qualifier { - color: #1C92A9; -} - -/* ArgumentName and ObjectFieldName */ -.cm-attribute { - color: #8B2BB9; -} - -/* Number */ -.cm-number { - color: #2882F9; -} - -/* String */ -.cm-string { - color: #D64292; -} - -/* Boolean */ -.cm-builtin { - color: #D47509; -} - -/* EnumValue */ -.cm-string-2 { - color: #0B7FC7; -} - -/* Variable */ -.cm-variable { - color: #397D13; -} - -/* Directive */ -.cm-meta { - color: #B33086; -} - -/* Type */ -.cm-atom { - color: #CA9800; -} -/* BASICS */ - -.CodeMirror { - /* Set height, width, borders, and global font properties here */ - color: black; - font-family: monospace; - height: 300px; -} - -/* PADDING */ - -.CodeMirror-lines { - padding: 4px 0; /* Vertical padding around content */ -} -.CodeMirror pre { - padding: 0 4px; /* Horizontal padding of content */ -} - -.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - background-color: white; /* The little square between H and V scrollbars */ -} - -/* GUTTER */ - -.CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; - white-space: nowrap; -} -.CodeMirror-linenumbers {} -.CodeMirror-linenumber { - color: #999; - min-width: 20px; - padding: 0 3px 0 5px; - text-align: right; - white-space: nowrap; -} - -.CodeMirror-guttermarker { color: black; } -.CodeMirror-guttermarker-subtle { color: #999; } - -/* CURSOR */ - -.CodeMirror .CodeMirror-cursor { - border-left: 1px solid black; -} -/* Shown when moving in bi-directional text */ -.CodeMirror div.CodeMirror-secondarycursor { - border-left: 1px solid silver; -} -.CodeMirror.cm-fat-cursor div.CodeMirror-cursor { - background: #7e7; - border: 0; - width: auto; -} -.CodeMirror.cm-fat-cursor div.CodeMirror-cursors { - z-index: 1; -} - -.cm-animate-fat-cursor { - -webkit-animation: blink 1.06s steps(1) infinite; - animation: blink 1.06s steps(1) infinite; - border: 0; - width: auto; -} -@-webkit-keyframes blink { - 0% { background: #7e7; } - 50% { background: none; } - 100% { background: #7e7; } -} -@keyframes blink { - 0% { background: #7e7; } - 50% { background: none; } - 100% { background: #7e7; } -} - -/* Can style cursor different in overwrite (non-insert) mode */ -div.CodeMirror-overwrite div.CodeMirror-cursor {} - -.cm-tab { display: inline-block; text-decoration: inherit; } - -.CodeMirror-ruler { - border-left: 1px solid #ccc; - position: absolute; -} - -/* DEFAULT THEME */ - -.cm-s-default .cm-keyword {color: #708;} -.cm-s-default .cm-atom {color: #219;} -.cm-s-default .cm-number {color: #164;} -.cm-s-default .cm-def {color: #00f;} -.cm-s-default .cm-variable, -.cm-s-default .cm-punctuation, -.cm-s-default .cm-property, -.cm-s-default .cm-operator {} -.cm-s-default .cm-variable-2 {color: #05a;} -.cm-s-default .cm-variable-3 {color: #085;} -.cm-s-default .cm-comment {color: #a50;} -.cm-s-default .cm-string {color: #a11;} -.cm-s-default .cm-string-2 {color: #f50;} -.cm-s-default .cm-meta {color: #555;} -.cm-s-default .cm-qualifier {color: #555;} -.cm-s-default .cm-builtin {color: #30a;} -.cm-s-default .cm-bracket {color: #997;} -.cm-s-default .cm-tag {color: #170;} -.cm-s-default .cm-attribute {color: #00c;} -.cm-s-default .cm-header {color: blue;} -.cm-s-default .cm-quote {color: #090;} -.cm-s-default .cm-hr {color: #999;} -.cm-s-default .cm-link {color: #00c;} - -.cm-negative {color: #d44;} -.cm-positive {color: #292;} -.cm-header, .cm-strong {font-weight: bold;} -.cm-em {font-style: italic;} -.cm-link {text-decoration: underline;} -.cm-strikethrough {text-decoration: line-through;} - -.cm-s-default .cm-error {color: #f00;} -.cm-invalidchar {color: #f00;} - -.CodeMirror-composing { border-bottom: 2px solid; } - -/* Default styles for common addons */ - -div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} -div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} -.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } -.CodeMirror-activeline-background {background: #e8f2ff;} - -/* STOP */ - -/* The rest of this file contains styles related to the mechanics of - the editor. You probably shouldn't touch them. */ - -.CodeMirror { - background: white; - overflow: hidden; - position: relative; -} - -.CodeMirror-scroll { - height: 100%; - /* 30px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror */ - margin-bottom: -30px; margin-right: -30px; - outline: none; /* Prevent dragging from highlighting the element */ - overflow: scroll !important; /* Things will break if this is overridden */ - padding-bottom: 30px; - position: relative; -} -.CodeMirror-sizer { - border-right: 30px solid transparent; - position: relative; -} - -/* The fake, visible scrollbars. Used to force redraw during scrolling - before actual scrolling happens, thus preventing shaking and - flickering artifacts. */ -.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - display: none; - position: absolute; - z-index: 6; -} -.CodeMirror-vscrollbar { - overflow-x: hidden; - overflow-y: scroll; - right: 0; top: 0; -} -.CodeMirror-hscrollbar { - bottom: 0; left: 0; - overflow-x: scroll; - overflow-y: hidden; -} -.CodeMirror-scrollbar-filler { - right: 0; bottom: 0; -} -.CodeMirror-gutter-filler { - left: 0; bottom: 0; -} - -.CodeMirror-gutters { - min-height: 100%; - position: absolute; left: 0; top: 0; - z-index: 3; -} -.CodeMirror-gutter { - display: inline-block; - height: 100%; - margin-bottom: -30px; - vertical-align: top; - white-space: normal; - /* Hack to make IE7 behave */ - *zoom:1; - *display:inline; -} -.CodeMirror-gutter-wrapper { - background: none !important; - border: none !important; - position: absolute; - z-index: 4; -} -.CodeMirror-gutter-background { - position: absolute; - top: 0; bottom: 0; - z-index: 4; -} -.CodeMirror-gutter-elt { - cursor: default; - position: absolute; - z-index: 4; -} -.CodeMirror-gutter-wrapper { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.CodeMirror-lines { - cursor: text; - min-height: 1px; /* prevents collapsing before first draw */ -} -.CodeMirror pre { - -webkit-tap-highlight-color: transparent; - /* Reset some styles that the rest of the page might have set */ - background: transparent; - border-radius: 0; - border-width: 0; - color: inherit; - font-family: inherit; - font-size: inherit; - -webkit-font-variant-ligatures: none; - font-variant-ligatures: none; - line-height: inherit; - margin: 0; - overflow: visible; - position: relative; - white-space: pre; - word-wrap: normal; - z-index: 2; -} -.CodeMirror-wrap pre { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; -} - -.CodeMirror-linebackground { - position: absolute; - left: 0; right: 0; top: 0; bottom: 0; - z-index: 0; -} - -.CodeMirror-linewidget { - overflow: auto; - position: relative; - z-index: 2; -} - -.CodeMirror-widget {} - -.CodeMirror-code { - outline: none; -} - -/* Force content-box sizing for the elements where we expect it */ -.CodeMirror-scroll, -.CodeMirror-sizer, -.CodeMirror-gutter, -.CodeMirror-gutters, -.CodeMirror-linenumber { - -webkit-box-sizing: content-box; - box-sizing: content-box; -} - -.CodeMirror-measure { - height: 0; - overflow: hidden; - position: absolute; - visibility: hidden; - width: 100%; -} - -.CodeMirror-cursor { position: absolute; } -.CodeMirror-measure pre { position: static; } - -div.CodeMirror-cursors { - position: relative; - visibility: hidden; - z-index: 3; -} -div.CodeMirror-dragcursors { - visibility: visible; -} - -.CodeMirror-focused div.CodeMirror-cursors { - visibility: visible; -} - -.CodeMirror-selected { background: #d9d9d9; } -.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } -.CodeMirror-crosshair { cursor: crosshair; } -.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } -.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } -.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } - -.cm-searching { - background: #ffa; - background: rgba(255, 255, 0, .4); -} - -/* IE7 hack to prevent it from returning funny offsetTops on the spans */ -.CodeMirror span { *vertical-align: text-bottom; } - -/* Used to force a border model for a node */ -.cm-force-border { padding-right: .1px; } - -@media print { - /* Hide the cursor when printing */ - .CodeMirror div.CodeMirror-cursors { - visibility: hidden; - } -} - -/* See issue #2901 */ -.cm-tab-wrap-hack:after { content: ''; } - -/* Help users use markselection to safely style text background */ -span.CodeMirror-selectedtext { background: none; } - -.CodeMirror-dialog { - background: inherit; - color: inherit; - left: 0; right: 0; - overflow: hidden; - padding: .1em .8em; - position: absolute; - z-index: 15; -} - -.CodeMirror-dialog-top { - border-bottom: 1px solid #eee; - top: 0; -} - -.CodeMirror-dialog-bottom { - border-top: 1px solid #eee; - bottom: 0; -} - -.CodeMirror-dialog input { - background: transparent; - border: 1px solid #d3d6db; - color: inherit; - font-family: monospace; - outline: none; - width: 20em; -} - -.CodeMirror-dialog button { - font-size: 70%; -} -.graphiql-container .doc-explorer { - background: white; -} - -.graphiql-container .doc-explorer-title-bar, -.graphiql-container .history-title-bar { - cursor: default; - display: -webkit-box; - display: -ms-flexbox; - display: flex; - height: 34px; - line-height: 14px; - padding: 8px 8px 5px; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.graphiql-container .doc-explorer-title, -.graphiql-container .history-title { - -webkit-box-flex: 1; - -ms-flex: 1; - flex: 1; - font-weight: bold; - overflow-x: hidden; - padding: 10px 0 10px 10px; - text-align: center; - text-overflow: ellipsis; - -webkit-user-select: initial; - -moz-user-select: initial; - -ms-user-select: initial; - user-select: initial; - white-space: nowrap; -} - -.graphiql-container .doc-explorer-back { - color: #3B5998; - cursor: pointer; - margin: -7px 0 -6px -8px; - overflow-x: hidden; - padding: 17px 12px 16px 16px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.doc-explorer-narrow .doc-explorer-back { - width: 0; -} - -.graphiql-container .doc-explorer-back:before { - border-left: 2px solid #3B5998; - border-top: 2px solid #3B5998; - content: ''; - display: inline-block; - height: 9px; - margin: 0 3px -1px 0; - position: relative; - -webkit-transform: rotate(-45deg); - transform: rotate(-45deg); - width: 9px; -} - -.graphiql-container .doc-explorer-rhs { - position: relative; -} - -.graphiql-container .doc-explorer-contents, -.graphiql-container .history-contents { - background-color: #ffffff; - border-top: 1px solid #d6d6d6; - bottom: 0; - left: 0; - overflow-y: auto; - padding: 20px 15px; - position: absolute; - right: 0; - top: 47px; -} - -.graphiql-container .doc-explorer-contents { - min-width: 300px; -} - -.graphiql-container .doc-type-description p:first-child , -.graphiql-container .doc-type-description blockquote:first-child { - margin-top: 0; -} - -.graphiql-container .doc-explorer-contents a { - cursor: pointer; - text-decoration: none; -} - -.graphiql-container .doc-explorer-contents a:hover { - text-decoration: underline; -} - -.graphiql-container .doc-value-description > :first-child { - margin-top: 4px; -} - -.graphiql-container .doc-value-description > :last-child { - margin-bottom: 4px; -} - -.graphiql-container .doc-category { - margin: 20px 0; -} - -.graphiql-container .doc-category-title { - border-bottom: 1px solid #e0e0e0; - color: #777; - cursor: default; - font-size: 14px; - font-variant: small-caps; - font-weight: bold; - letter-spacing: 1px; - margin: 0 -15px 10px 0; - padding: 10px 0; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.graphiql-container .doc-category-item { - margin: 12px 0; - color: #555; -} - -.graphiql-container .keyword { - color: #B11A04; -} - -.graphiql-container .type-name { - color: #CA9800; -} - -.graphiql-container .field-name { - color: #1F61A0; -} - -.graphiql-container .field-short-description { - color: #999; - margin-left: 5px; - overflow: hidden; - text-overflow: ellipsis; -} - -.graphiql-container .enum-value { - color: #0B7FC7; -} - -.graphiql-container .arg-name { - color: #8B2BB9; -} - -.graphiql-container .arg { - display: block; - margin-left: 1em; -} - -.graphiql-container .arg:first-child:last-child, -.graphiql-container .arg:first-child:nth-last-child(2), -.graphiql-container .arg:first-child:nth-last-child(2) ~ .arg { - display: inherit; - margin: inherit; -} - -.graphiql-container .arg:first-child:nth-last-child(2):after { - content: ', '; -} - -.graphiql-container .arg-default-value { - color: #43A047; -} - -.graphiql-container .doc-deprecation { - background: #fffae8; - -webkit-box-shadow: inset 0 0 1px #bfb063; - box-shadow: inset 0 0 1px #bfb063; - color: #867F70; - line-height: 16px; - margin: 8px -8px; - max-height: 80px; - overflow: hidden; - padding: 8px; - border-radius: 3px; -} - -.graphiql-container .doc-deprecation:before { - content: 'Deprecated:'; - color: #c79b2e; - cursor: default; - display: block; - font-size: 9px; - font-weight: bold; - letter-spacing: 1px; - line-height: 1; - padding-bottom: 5px; - text-transform: uppercase; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.graphiql-container .doc-deprecation > :first-child { - margin-top: 0; -} - -.graphiql-container .doc-deprecation > :last-child { - margin-bottom: 0; -} - -.graphiql-container .show-btn { - -webkit-appearance: initial; - display: block; - border-radius: 3px; - border: solid 1px #ccc; - text-align: center; - padding: 8px 12px 10px; - width: 100%; - -webkit-box-sizing: border-box; - box-sizing: border-box; - background: #fbfcfc; - color: #555; - cursor: pointer; -} - -.graphiql-container .search-box { - border-bottom: 1px solid #d3d6db; - display: block; - font-size: 14px; - margin: -15px -15px 12px 0; - position: relative; -} - -.graphiql-container .search-box:before { - content: '\26b2'; - cursor: pointer; - display: block; - font-size: 24px; - position: absolute; - top: -2px; - -webkit-transform: rotate(-45deg); - transform: rotate(-45deg); - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.graphiql-container .search-box .search-box-clear { - background-color: #d0d0d0; - border-radius: 12px; - color: #fff; - cursor: pointer; - font-size: 11px; - padding: 1px 5px 2px; - position: absolute; - right: 3px; - top: 8px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.graphiql-container .search-box .search-box-clear:hover { - background-color: #b9b9b9; -} - -.graphiql-container .search-box > input { - border: none; - -webkit-box-sizing: border-box; - box-sizing: border-box; - font-size: 14px; - outline: none; - padding: 6px 24px 8px 20px; - width: 100%; -} - -.graphiql-container .error-container { - font-weight: bold; - left: 0; - letter-spacing: 1px; - opacity: 0.5; - position: absolute; - right: 0; - text-align: center; - text-transform: uppercase; - top: 50%; - -webkit-transform: translate(0, -50%); - transform: translate(0, -50%); -} -.CodeMirror-foldmarker { - color: blue; - cursor: pointer; - font-family: arial; - line-height: .3; - text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; -} -.CodeMirror-foldgutter { - width: .7em; -} -.CodeMirror-foldgutter-open, -.CodeMirror-foldgutter-folded { - cursor: pointer; -} -.CodeMirror-foldgutter-open:after { - content: "\25BE"; -} -.CodeMirror-foldgutter-folded:after { - content: "\25B8"; -} -.graphiql-container .history-contents { - font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace; - padding: 0; -} - -.graphiql-container .history-contents p { - font-size: 12px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin: 0; - padding: 8px; - border-bottom: 1px solid #e0e0e0; -} - -.graphiql-container .history-contents p:hover { - cursor: pointer; -} -.CodeMirror-info { - background: white; - border-radius: 2px; - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); - -webkit-box-sizing: border-box; - box-sizing: border-box; - color: #555; - font-family: - system, - -apple-system, - 'San Francisco', - '.SFNSDisplay-Regular', - 'Segoe UI', - Segoe, - 'Segoe WP', - 'Helvetica Neue', - helvetica, - 'Lucida Grande', - arial, - sans-serif; - font-size: 13px; - line-height: 16px; - margin: 8px -8px; - max-width: 400px; - opacity: 0; - overflow: hidden; - padding: 8px 8px; - position: fixed; - -webkit-transition: opacity 0.15s; - transition: opacity 0.15s; - z-index: 50; -} - -.CodeMirror-info :first-child { - margin-top: 0; -} - -.CodeMirror-info :last-child { - margin-bottom: 0; -} - -.CodeMirror-info p { - margin: 1em 0; -} - -.CodeMirror-info .info-description { - color: #777; - line-height: 16px; - margin-top: 1em; - max-height: 80px; - overflow: hidden; -} - -.CodeMirror-info .info-deprecation { - background: #fffae8; - -webkit-box-shadow: inset 0 1px 1px -1px #bfb063; - box-shadow: inset 0 1px 1px -1px #bfb063; - color: #867F70; - line-height: 16px; - margin: -8px; - margin-top: 8px; - max-height: 80px; - overflow: hidden; - padding: 8px; -} - -.CodeMirror-info .info-deprecation-label { - color: #c79b2e; - cursor: default; - display: block; - font-size: 9px; - font-weight: bold; - letter-spacing: 1px; - line-height: 1; - padding-bottom: 5px; - text-transform: uppercase; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.CodeMirror-info .info-deprecation-label + * { - margin-top: 0; -} - -.CodeMirror-info a { - text-decoration: none; -} - -.CodeMirror-info a:hover { - text-decoration: underline; -} - -.CodeMirror-info .type-name { - color: #CA9800; -} - -.CodeMirror-info .field-name { - color: #1F61A0; -} - -.CodeMirror-info .enum-value { - color: #0B7FC7; -} - -.CodeMirror-info .arg-name { - color: #8B2BB9; -} - -.CodeMirror-info .directive-name { - color: #B33086; -} -.CodeMirror-jump-token { - text-decoration: underline; - cursor: pointer; -} -/* The lint marker gutter */ -.CodeMirror-lint-markers { - width: 16px; -} - -.CodeMirror-lint-tooltip { - background-color: infobackground; - border-radius: 4px 4px 4px 4px; - border: 1px solid black; - color: infotext; - font-family: monospace; - font-size: 10pt; - max-width: 600px; - opacity: 0; - overflow: hidden; - padding: 2px 5px; - position: fixed; - -webkit-transition: opacity .4s; - transition: opacity .4s; - white-space: pre-wrap; - z-index: 100; -} - -.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { - background-position: left bottom; - background-repeat: repeat-x; -} - -.CodeMirror-lint-mark-error { - background-image: - url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==") - ; -} - -.CodeMirror-lint-mark-warning { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); -} - -.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning { - background-position: center center; - background-repeat: no-repeat; - cursor: pointer; - display: inline-block; - height: 16px; - position: relative; - vertical-align: middle; - width: 16px; -} - -.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning { - background-position: top left; - background-repeat: no-repeat; - padding-left: 18px; -} - -.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII="); -} - -.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); -} - -.CodeMirror-lint-marker-multiple { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"); - background-position: right bottom; - background-repeat: no-repeat; - width: 100%; height: 100%; -} -.graphiql-container .spinner-container { - height: 36px; - left: 50%; - position: absolute; - top: 50%; - -webkit-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); - width: 36px; - z-index: 10; -} - -.graphiql-container .spinner { - -webkit-animation: rotation .6s infinite linear; - animation: rotation .6s infinite linear; - border-bottom: 6px solid rgba(150, 150, 150, .15); - border-left: 6px solid rgba(150, 150, 150, .15); - border-radius: 100%; - border-right: 6px solid rgba(150, 150, 150, .15); - border-top: 6px solid rgba(150, 150, 150, .8); - display: inline-block; - height: 24px; - position: absolute; - vertical-align: middle; - width: 24px; -} - -@-webkit-keyframes rotation { - from { -webkit-transform: rotate(0deg); transform: rotate(0deg); } - to { -webkit-transform: rotate(359deg); transform: rotate(359deg); } -} - -@keyframes rotation { - from { -webkit-transform: rotate(0deg); transform: rotate(0deg); } - to { -webkit-transform: rotate(359deg); transform: rotate(359deg); } -} -.CodeMirror-hints { - background: white; - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); - font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace; - font-size: 13px; - list-style: none; - margin-left: -6px; - margin: 0; - max-height: 14.5em; - overflow-y: auto; - overflow: hidden; - padding: 0; - position: absolute; - z-index: 10; -} - -.CodeMirror-hint { - border-top: solid 1px #f7f7f7; - color: #141823; - cursor: pointer; - margin: 0; - max-width: 300px; - overflow: hidden; - padding: 2px 6px; - white-space: pre; -} - -li.CodeMirror-hint-active { - background-color: #08f; - border-top-color: white; - color: white; -} - -.CodeMirror-hint-information { - border-top: solid 1px #c0c0c0; - max-width: 300px; - padding: 4px 6px; - position: relative; - z-index: 1; -} - -.CodeMirror-hint-information:first-child { - border-bottom: solid 1px #c0c0c0; - border-top: none; - margin-bottom: -1px; -} - -.CodeMirror-hint-deprecation { - background: #fffae8; - -webkit-box-shadow: inset 0 1px 1px -1px #bfb063; - box-shadow: inset 0 1px 1px -1px #bfb063; - color: #867F70; - font-family: - system, - -apple-system, - 'San Francisco', - '.SFNSDisplay-Regular', - 'Segoe UI', - Segoe, - 'Segoe WP', - 'Helvetica Neue', - helvetica, - 'Lucida Grande', - arial, - sans-serif; - font-size: 13px; - line-height: 16px; - margin-top: 4px; - max-height: 80px; - overflow: hidden; - padding: 6px; -} - -.CodeMirror-hint-deprecation .deprecation-label { - color: #c79b2e; - cursor: default; - display: block; - font-size: 9px; - font-weight: bold; - letter-spacing: 1px; - line-height: 1; - padding-bottom: 5px; - text-transform: uppercase; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.CodeMirror-hint-deprecation .deprecation-label + * { - margin-top: 0; -} - -.CodeMirror-hint-deprecation :last-child { - margin-bottom: 0; -} diff --git a/src/Bridge/Symfony/Bundle/Resources/public/graphiql/graphiql.min.js b/src/Bridge/Symfony/Bundle/Resources/public/graphiql/graphiql.min.js deleted file mode 100644 index fd7b1585d89..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/public/graphiql/graphiql.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).GraphiQL=f()}}(function(){return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n||e)},l,l.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o1&&e.setState({navStack:e.state.navStack.slice(0,-1)})},e.handleClickTypeOrField=function(t){e.showDoc(t)},e.handleSearch=function(t){e.showSearch(t)},e.state={navStack:[initialNav]},e}return _inherits(t,e),_createClass(t,[{key:"shouldComponentUpdate",value:function(e,t){return this.props.schema!==e.schema||this.state.navStack!==t.navStack}},{key:"render",value:function(){var e=this.props.schema,t=this.state.navStack,a=t[t.length-1],r=void 0;r=void 0===e?_react2.default.createElement("div",{className:"spinner-container"},_react2.default.createElement("div",{className:"spinner"})):e?a.search?_react2.default.createElement(_SearchResults2.default,{searchValue:a.search,withinType:a.def,schema:e,onClickType:this.handleClickTypeOrField,onClickField:this.handleClickTypeOrField}):1===t.length?_react2.default.createElement(_SchemaDoc2.default,{schema:e,onClickType:this.handleClickTypeOrField}):(0,_graphql.isType)(a.def)?_react2.default.createElement(_TypeDoc2.default,{schema:e,type:a.def,onClickType:this.handleClickTypeOrField,onClickField:this.handleClickTypeOrField}):_react2.default.createElement(_FieldDoc2.default,{field:a.def,onClickType:this.handleClickTypeOrField}):_react2.default.createElement("div",{className:"error-container"},"No Schema Available");var c=1===t.length||(0,_graphql.isType)(a.def)&&a.def.getFields,l=void 0;return t.length>1&&(l=t[t.length-2].name),_react2.default.createElement("div",{className:"doc-explorer",key:a.name},_react2.default.createElement("div",{className:"doc-explorer-title-bar"},l&&_react2.default.createElement("div",{className:"doc-explorer-back",onClick:this.handleNavBackClick},l),_react2.default.createElement("div",{className:"doc-explorer-title"},a.title||a.name),_react2.default.createElement("div",{className:"doc-explorer-rhs"},this.props.children)),_react2.default.createElement("div",{className:"doc-explorer-contents"},c&&_react2.default.createElement(_SearchBox2.default,{value:a.search,placeholder:"Search "+a.name+"...",onSearch:this.handleSearch}),r))}},{key:"showDoc",value:function(e){var t=this.state.navStack;t[t.length-1].def!==e&&this.setState({navStack:t.concat([{name:e.name,def:e}])})}},{key:"showDocForReference",value:function(e){"Type"===e.kind?this.showDoc(e.type):"Field"===e.kind?this.showDoc(e.field):"Argument"===e.kind&&e.field?this.showDoc(e.field):"EnumValue"===e.kind&&e.type&&this.showDoc(e.type)}},{key:"showSearch",value:function(e){var t=this.state.navStack.slice(),a=t[t.length-1];t[t.length-1]=_extends({},a,{search:e}),this.setState({navStack:t})}},{key:"reset",value:function(){this.setState({navStack:[initialNav]})}}]),t}(_react2.default.Component)).propTypes={schema:_propTypes2.default.instanceOf(_graphql.GraphQLSchema)}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./DocExplorer/FieldDoc":4,"./DocExplorer/SchemaDoc":6,"./DocExplorer/SearchBox":7,"./DocExplorer/SearchResults":8,"./DocExplorer/TypeDoc":9,graphql:95,"prop-types":233}],2:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function Argument(e){var t=e.arg,r=e.onClickType,a=e.showDefaultValue;return _react2.default.createElement("span",{className:"arg"},_react2.default.createElement("span",{className:"arg-name"},t.name),": ",_react2.default.createElement(_TypeLink2.default,{type:t.type,onClick:r}),!1!==a&&_react2.default.createElement(_DefaultValue2.default,{field:t}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=Argument;var _react2=_interopRequireDefault("undefined"!=typeof window?window.React:void 0!==global?global.React:null),_propTypes2=_interopRequireDefault(require("prop-types")),_TypeLink2=_interopRequireDefault(require("./TypeLink")),_DefaultValue2=_interopRequireDefault(require("./DefaultValue"));Argument.propTypes={arg:_propTypes2.default.object.isRequired,onClickType:_propTypes2.default.func.isRequired,showDefaultValue:_propTypes2.default.bool}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./DefaultValue":3,"./TypeLink":10,"prop-types":233}],3:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function DefaultValue(e){var r=e.field,t=r.type,a=r.defaultValue;return void 0!==a?_react2.default.createElement("span",null," = ",_react2.default.createElement("span",{className:"arg-default-value"},(0,_graphql.print)((0,_graphql.astFromValue)(a,t)))):null}Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=DefaultValue;var _react2=_interopRequireDefault("undefined"!=typeof window?window.React:void 0!==global?global.React:null),_propTypes2=_interopRequireDefault(require("prop-types")),_graphql=require("graphql");DefaultValue.propTypes={field:_propTypes2.default.object.isRequired}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{graphql:95,"prop-types":233}],4:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var r=0;r0&&(r=_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"arguments"),t.args.map(function(t){return _react2.default.createElement("div",{key:t.name,className:"doc-category-item"},_react2.default.createElement("div",null,_react2.default.createElement(_Argument2.default,{arg:t,onClickType:e.props.onClickType})),_react2.default.createElement(_MarkdownContent2.default,{className:"doc-value-description",markdown:t.description}))}))),_react2.default.createElement("div",null,_react2.default.createElement(_MarkdownContent2.default,{className:"doc-type-description",markdown:t.description||"No Description"}),t.deprecationReason&&_react2.default.createElement(_MarkdownContent2.default,{className:"doc-deprecation",markdown:t.deprecationReason}),_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"type"),_react2.default.createElement(_TypeLink2.default,{type:t.type,onClick:this.props.onClickType})),r)}}]),t}(_react2.default.Component);FieldDoc.propTypes={field:_propTypes2.default.object,onClickType:_propTypes2.default.func},exports.default=FieldDoc}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./Argument":2,"./MarkdownContent":5,"./TypeLink":10,"prop-types":233}],5:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var r=0;r=100)return"break";var i=c[r];if(t!==i&&isMatch(r,e)&&l.push(_react2.default.createElement("div",{className:"doc-category-item",key:r},_react2.default.createElement(_TypeLink2.default,{type:i,onClick:n}))),i.getFields){var s=i.getFields();Object.keys(s).forEach(function(l){var c=s[l],p=void 0;if(!isMatch(l,e)){if(!c.args||!c.args.length)return;if(0===(p=c.args.filter(function(t){return isMatch(t.name,e)})).length)return}var f=_react2.default.createElement("div",{className:"doc-category-item",key:r+"."+l},t!==i&&[_react2.default.createElement(_TypeLink2.default,{key:"type",type:i,onClick:n}),"."],_react2.default.createElement("a",{className:"field-name",onClick:function(e){return a(c,i,e)}},c.name),p&&["(",_react2.default.createElement("span",{key:"args"},p.map(function(e){return _react2.default.createElement(_Argument2.default,{key:e.name,arg:e,onClickType:n,showDefaultValue:!1})})),")"]);t===i?o.push(f):u.push(f)})}}();s=!0);}catch(e){p=!0,f=e}finally{try{!s&&_.return&&_.return()}finally{if(p)throw f}}return o.length+l.length+u.length===0?_react2.default.createElement("span",{className:"doc-alert-text"},"No results found."):t&&l.length+u.length>0?_react2.default.createElement("div",null,o,_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"other results"),l,u)):_react2.default.createElement("div",null,o,l,u)}}]),t}(_react2.default.Component);SearchResults.propTypes={schema:_propTypes2.default.object,withinType:_propTypes2.default.object,searchValue:_propTypes2.default.string,onClickType:_propTypes2.default.func,onClickField:_propTypes2.default.func},exports.default=SearchResults}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./Argument":2,"./TypeLink":10,"prop-types":233}],9:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function Field(e){var t=e.type,a=e.field,r=e.onClickType,n=e.onClickField;return _react2.default.createElement("div",{className:"doc-category-item"},_react2.default.createElement("a",{className:"field-name",onClick:function(e){return n(a,t,e)}},a.name),a.args&&a.args.length>0&&["(",_react2.default.createElement("span",{key:"args"},a.args.map(function(e){return _react2.default.createElement(_Argument2.default,{key:e.name,arg:e,onClickType:r})})),")"],": ",_react2.default.createElement(_TypeLink2.default,{type:a.type,onClick:r}),_react2.default.createElement(_DefaultValue2.default,{field:a}),a.description&&_react2.default.createElement(_MarkdownContent2.default,{className:"field-short-description",markdown:a.description}),a.deprecationReason&&_react2.default.createElement(_MarkdownContent2.default,{className:"doc-deprecation",markdown:a.deprecationReason}))}function EnumValue(e){var t=e.value;return _react2.default.createElement("div",{className:"doc-category-item"},_react2.default.createElement("div",{className:"enum-value"},t.name),_react2.default.createElement(_MarkdownContent2.default,{className:"doc-value-description",markdown:t.description}),t.deprecationReason&&_react2.default.createElement(_MarkdownContent2.default,{className:"doc-deprecation",markdown:t.deprecationReason}))}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var a=0;a0&&(l=_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},n),c.map(function(e){return _react2.default.createElement("div",{key:e.name,className:"doc-category-item"},_react2.default.createElement(_TypeLink2.default,{type:e,onClick:a}))})));var o=void 0,i=void 0;if(t.getFields){var u=t.getFields(),p=Object.keys(u).map(function(e){return u[e]});o=_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"fields"),p.filter(function(e){return!e.isDeprecated}).map(function(e){return _react2.default.createElement(Field,{key:e.name,type:t,field:e,onClickType:a,onClickField:r})}));var s=p.filter(function(e){return e.isDeprecated});s.length>0&&(i=_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"deprecated fields"),this.state.showDeprecated?s.map(function(e){return _react2.default.createElement(Field,{key:e.name,type:t,field:e,onClickType:a,onClickField:r})}):_react2.default.createElement("button",{className:"show-btn",onClick:this.handleShowDeprecated},"Show deprecated fields...")))}var d=void 0,f=void 0;if(t instanceof _graphql.GraphQLEnumType){var m=t.getValues();d=_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"values"),m.filter(function(e){return!e.isDeprecated}).map(function(e){return _react2.default.createElement(EnumValue,{key:e.name,value:e})}));var _=m.filter(function(e){return e.isDeprecated});_.length>0&&(f=_react2.default.createElement("div",{className:"doc-category"},_react2.default.createElement("div",{className:"doc-category-title"},"deprecated values"),this.state.showDeprecated?_.map(function(e){return _react2.default.createElement(EnumValue,{key:e.name,value:e})}):_react2.default.createElement("button",{className:"show-btn",onClick:this.handleShowDeprecated},"Show deprecated values...")))}return _react2.default.createElement("div",null,_react2.default.createElement(_MarkdownContent2.default,{className:"doc-type-description",markdown:t.description||"No Description"}),t instanceof _graphql.GraphQLObjectType&&l,o,i,d,f,!(t instanceof _graphql.GraphQLObjectType)&&l)}}]),t}(_react2.default.Component);TypeDoc.propTypes={schema:_propTypes2.default.instanceOf(_graphql.GraphQLSchema),type:_propTypes2.default.object,onClickType:_propTypes2.default.func,onClickField:_propTypes2.default.func},exports.default=TypeDoc,Field.propTypes={type:_propTypes2.default.object,field:_propTypes2.default.object,onClickType:_propTypes2.default.func,onClickField:_propTypes2.default.func},EnumValue.propTypes={value:_propTypes2.default.object}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./Argument":2,"./DefaultValue":3,"./MarkdownContent":5,"./TypeLink":10,graphql:95,"prop-types":233}],10:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function renderType(e,t){return e instanceof _graphql.GraphQLNonNull?_react2.default.createElement("span",null,renderType(e.ofType,t),"!"):e instanceof _graphql.GraphQLList?_react2.default.createElement("span",null,"[",renderType(e.ofType,t),"]"):_react2.default.createElement("a",{className:"type-name",onClick:function(r){return t(e,r)}},e.name)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var r=0;r1,r=null;if(o&&n){var u=this.state.highlight;r=_react2.default.createElement("ul",{className:"execute-options"},t.map(function(t){return _react2.default.createElement("li",{key:t.name?t.name.value:"*",className:t===u&&"selected"||null,onMouseOver:function(){return e.setState({highlight:t})},onMouseOut:function(){return e.setState({highlight:null})},onMouseUp:function(){return e._onOptionSelected(t)}},t.name?t.name.value:"")}))}var a=void 0;!this.props.isRunning&&o||(a=this._onClick);var i=void 0;this.props.isRunning||!o||n||(i=this._onOptionsOpen);var s=this.props.isRunning?_react2.default.createElement("path",{d:"M 10 10 L 23 10 L 23 23 L 10 23 z"}):_react2.default.createElement("path",{d:"M 11 9 L 24 16 L 11 23 z"});return _react2.default.createElement("div",{className:"execute-button-wrap"},_react2.default.createElement("button",{type:"button",className:"execute-button",onMouseDown:i,onClick:a,title:"Execute Query (Ctrl-Enter)"},_react2.default.createElement("svg",{width:"34",height:"34"},s)),r)}}]),t}(_react2.default.Component)).propTypes={onRun:_propTypes2.default.func,onStop:_propTypes2.default.func,isRunning:_propTypes2.default.bool,operations:_propTypes2.default.array}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"prop-types":233}],12:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function isPromise(e){return"object"===(void 0===e?"undefined":_typeof(e))&&"function"==typeof e.then}function observableToPromise(e){return isObservable(e)?new Promise(function(t,r){var o=e.subscribe(function(e){t(e),o.unsubscribe()},r,function(){r(new Error("no value resolved"))})}):e}function isObservable(e){return"object"===(void 0===e?"undefined":_typeof(e))&&"function"==typeof e.subscribe}Object.defineProperty(exports,"__esModule",{value:!0}),exports.GraphiQL=void 0;var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},_extends=Object.assign||function(e){for(var t=1;t0){var o=this.getQueryEditor();o.operation(function(){var e=o.getCursor(),i=o.indexFromPos(e);o.setValue(r);var n=0,a=t.map(function(e){var t=e.index,r=e.string;return o.markText(o.posFromIndex(t+n),o.posFromIndex(t+(n+=r.length)),{className:"autoInsertedLeaf",clearOnEnter:!0,title:"Automatically added leaf fields"})});setTimeout(function(){return a.forEach(function(e){return e.clear()})},7e3);var s=i;t.forEach(function(e){var t=e.index,r=e.string;t=i){e=a.name&&a.name.value;break}}}this.handleRunQuery(e)}}},{key:"_didClickDragBar",value:function(e){if(0!==e.button||e.ctrlKey)return!1;var t=e.target;if(0!==t.className.indexOf("CodeMirror-gutter"))return!1;for(var r=_reactDom2.default.findDOMNode(this.resultComponent);t;){if(t===r)return!0;t=t.parentNode}return!1}}]),t}(_react2.default.Component);GraphiQL.propTypes={fetcher:_propTypes2.default.func.isRequired,schema:_propTypes2.default.instanceOf(_graphql.GraphQLSchema),query:_propTypes2.default.string,variables:_propTypes2.default.string,operationName:_propTypes2.default.string,response:_propTypes2.default.string,storage:_propTypes2.default.shape({getItem:_propTypes2.default.func,setItem:_propTypes2.default.func,removeItem:_propTypes2.default.func}),defaultQuery:_propTypes2.default.string,onEditQuery:_propTypes2.default.func,onEditVariables:_propTypes2.default.func,onEditOperationName:_propTypes2.default.func,onToggleDocs:_propTypes2.default.func,getDefaultFieldNames:_propTypes2.default.func,editorTheme:_propTypes2.default.string,onToggleHistory:_propTypes2.default.func,ResultsTooltip:_propTypes2.default.any};var _initialiseProps=function(){var e=this;this.handleClickReference=function(t){e.setState({docExplorerOpen:!0},function(){e.docExplorerComponent.showDocForReference(t)})},this.handleRunQuery=function(t){var r=++e._editorQueryID,o=e.autoCompleteLeafs()||e.state.query,i=e.state.variables,n=e.state.operationName;t&&t!==n&&(n=t,e.handleEditOperationName(n));try{e.setState({isWaitingForResponse:!0,response:null,operationName:n});var a=e._fetchQuery(o,i,n,function(t){r===e._editorQueryID&&e.setState({isWaitingForResponse:!1,response:JSON.stringify(t,null,2)})});e.setState({subscription:a})}catch(t){e.setState({isWaitingForResponse:!1,response:t.message})}},this.handleStopQuery=function(){var t=e.state.subscription;e.setState({isWaitingForResponse:!1,subscription:null}),t&&t.unsubscribe()},this.handlePrettifyQuery=function(){var t=e.getQueryEditor();t.setValue((0,_graphql.print)((0,_graphql.parse)(t.getValue())))},this.handleEditQuery=(0,_debounce2.default)(100,function(t){var r=e._updateQueryFacts(t,e.state.operationName,e.state.operations,e.state.schema);if(e.setState(_extends({query:t},r)),e.props.onEditQuery)return e.props.onEditQuery(t)}),this._updateQueryFacts=function(t,r,o,i){var n=(0,_getQueryFacts2.default)(i,t);if(n){var a=(0,_getSelectedOperationName2.default)(o,r,n.operations),s=e.props.onEditOperationName;return s&&r!==a&&s(a),_extends({operationName:a},n)}},this.handleEditVariables=function(t){e.setState({variables:t}),e.props.onEditVariables&&e.props.onEditVariables(t)},this.handleEditOperationName=function(t){var r=e.props.onEditOperationName;r&&r(t)},this.handleHintInformationRender=function(t){t.addEventListener("click",e._onClickHintInformation);var r=void 0;t.addEventListener("DOMNodeRemoved",r=function(){t.removeEventListener("DOMNodeRemoved",r),t.removeEventListener("click",e._onClickHintInformation)})},this.handleEditorRunQuery=function(){e._runQueryAtCursor()},this._onClickHintInformation=function(t){if("typeName"===t.target.className){var r=t.target.innerHTML,o=e.state.schema;if(o){var i=o.getType(r);i&&e.setState({docExplorerOpen:!0},function(){e.docExplorerComponent.showDoc(i)})}}},this.handleToggleDocs=function(){"function"==typeof e.props.onToggleDocs&&e.props.onToggleDocs(!e.state.docExplorerOpen),e.setState({docExplorerOpen:!e.state.docExplorerOpen})},this.handleToggleHistory=function(){"function"==typeof e.props.onToggleHistory&&e.props.onToggleHistory(!e.state.historyPaneOpen),e.setState({historyPaneOpen:!e.state.historyPaneOpen})},this.handleSelectHistoryQuery=function(t,r,o){e.handleEditQuery(t),e.handleEditVariables(r),e.handleEditOperationName(o)},this.handleResizeStart=function(t){if(e._didClickDragBar(t)){t.preventDefault();var r=t.clientX-(0,_elementPosition.getLeft)(t.target),o=function(t){if(0===t.buttons)return i();var o=_reactDom2.default.findDOMNode(e.editorBarComponent),n=t.clientX-(0,_elementPosition.getLeft)(o)-r,a=o.clientWidth-n;e.setState({editorFlex:n/a})},i=function(e){function t(){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(){document.removeEventListener("mousemove",o),document.removeEventListener("mouseup",i),o=null,i=null});document.addEventListener("mousemove",o),document.addEventListener("mouseup",i)}},this.handleResetResize=function(){e.setState({editorFlex:1})},this.handleDocsResizeStart=function(t){t.preventDefault();var r=e.state.docExplorerWidth,o=t.clientX-(0,_elementPosition.getLeft)(t.target),i=function(t){if(0===t.buttons)return n();var r=_reactDom2.default.findDOMNode(e),i=t.clientX-(0,_elementPosition.getLeft)(r)-o,a=r.clientWidth-i;a<100?e.setState({docExplorerOpen:!1}):e.setState({docExplorerOpen:!0,docExplorerWidth:Math.min(a,650)})},n=function(e){function t(){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(){e.state.docExplorerOpen||e.setState({docExplorerWidth:r}),document.removeEventListener("mousemove",i),document.removeEventListener("mouseup",n),i=null,n=null});document.addEventListener("mousemove",i),document.addEventListener("mouseup",n)},this.handleDocsResetResize=function(){e.setState({docExplorerWidth:DEFAULT_DOC_EXPLORER_WIDTH})},this.handleVariableResizeStart=function(t){t.preventDefault();var r=!1,o=e.state.variableEditorOpen,i=e.state.variableEditorHeight,n=t.clientY-(0,_elementPosition.getTop)(t.target),a=function(t){if(0===t.buttons)return s();r=!0;var o=_reactDom2.default.findDOMNode(e.editorBarComponent),a=t.clientY-(0,_elementPosition.getTop)(o)-n,l=o.clientHeight-a;l<60?e.setState({variableEditorOpen:!1,variableEditorHeight:i}):e.setState({variableEditorOpen:!0,variableEditorHeight:l})},s=function(e){function t(){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(){r||e.setState({variableEditorOpen:!o}),document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",s),a=null,s=null});document.addEventListener("mousemove",a),document.addEventListener("mouseup",s)}};GraphiQL.Logo=function(e){return _react2.default.createElement("div",{className:"title"},e.children||_react2.default.createElement("span",null,"Graph",_react2.default.createElement("em",null,"i"),"QL"))},GraphiQL.Toolbar=function(e){return _react2.default.createElement("div",{className:"toolbar"},e.children)},GraphiQL.QueryEditor=_QueryEditor.QueryEditor,GraphiQL.VariableEditor=_VariableEditor.VariableEditor,GraphiQL.ResultViewer=_ResultViewer.ResultViewer,GraphiQL.Button=_ToolbarButton.ToolbarButton,GraphiQL.ToolbarButton=_ToolbarButton.ToolbarButton,GraphiQL.Group=_ToolbarGroup.ToolbarGroup,GraphiQL.Menu=_ToolbarMenu.ToolbarMenu,GraphiQL.MenuItem=_ToolbarMenu.ToolbarMenuItem,GraphiQL.Select=_ToolbarSelect.ToolbarSelect,GraphiQL.SelectOption=_ToolbarSelect.ToolbarSelectOption,GraphiQL.Footer=function(e){return _react2.default.createElement("div",{className:"footer"},e.children)};var defaultQuery='# Welcome to GraphiQL\n#\n# GraphiQL is an in-browser tool for writing, validating, and\n# testing GraphQL queries.\n#\n# Type queries into this side of the screen, and you will see intelligent\n# typeaheads aware of the current GraphQL type schema and live syntax and\n# validation errors highlighted within the text.\n#\n# GraphQL queries typically start with a "{" character. Lines that starts\n# with a # are ignored.\n#\n# An example GraphQL query might look like:\n#\n# {\n# field(arg: "value") {\n# subField\n# }\n# }\n#\n# Keyboard shortcuts:\n#\n# Prettify Query: Shift-Ctrl-P (or press the prettify button above)\n#\n# Run Query: Ctrl-Enter (or press the play button above)\n#\n# Auto Complete: Ctrl-Space (or just start typing)\n#\n\n'}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../utility/CodeMirrorSizer":23,"../utility/StorageAPI":25,"../utility/debounce":26,"../utility/elementPosition":27,"../utility/fillLeafs":28,"../utility/find":29,"../utility/getQueryFacts":30,"../utility/getSelectedOperationName":31,"../utility/introspectionQueries":32,"./DocExplorer":1,"./ExecuteButton":11,"./QueryEditor":14,"./QueryHistory":15,"./ResultViewer":16,"./ToolbarButton":17,"./ToolbarGroup":18,"./ToolbarMenu":19,"./ToolbarSelect":20,"./VariableEditor":21,graphql:95,"prop-types":233}],13:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,t){for(var r=0;r20&&this.historyStore.shift();var r=this.historyStore.items,o=this.favoriteStore.items,i=r.concat(o);this.setState({queries:i})}}},{key:"render",value:function(){var e=this,t=this.state.queries.slice().reverse().map(function(t,r){return _react2.default.createElement(_HistoryQuery2.default,_extends({handleToggleFavorite:e.toggleFavorite,key:r,onSelect:e.props.onSelectQuery},t))});return _react2.default.createElement("div",null,_react2.default.createElement("div",{className:"history-title-bar"},_react2.default.createElement("div",{className:"history-title"},"History"),_react2.default.createElement("div",{className:"doc-explorer-rhs"},this.props.children)),_react2.default.createElement("div",{className:"history-contents"},t))}}]),t}(_react2.default.Component)).propTypes={query:_propTypes2.default.string,variables:_propTypes2.default.string,operationName:_propTypes2.default.string,queryID:_propTypes2.default.number,onSelectQuery:_propTypes2.default.func,storage:_propTypes2.default.object};var _initialiseProps=function(){var e=this;this.toggleFavorite=function(t,r,o,i){var a={query:t,variables:r,operationName:o};e.favoriteStore.contains(a)?i&&(a.favorite=!1,e.favoriteStore.delete(a)):(a.favorite=!0,e.favoriteStore.push(a));var s=e.historyStore.items,n=e.favoriteStore.items,u=s.concat(n);e.setState({queries:u})}}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../utility/QueryStore":24,"./HistoryQuery":13,graphql:95,"prop-types":233}],16:[function(require,module,exports){(function(global){"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,r){if(!(e instanceof r))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,r){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!r||"object"!=typeof r&&"function"!=typeof r?e:r}function _inherits(e,r){if("function"!=typeof r&&null!==r)throw new TypeError("Super expression must either be null or a function, not "+typeof r);e.prototype=Object.create(r&&r.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),r&&(Object.setPrototypeOf?Object.setPrototypeOf(e,r):e.__proto__=r)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.ResultViewer=void 0;var _createClass=function(){function e(e,r){for(var t=0;t=65&&t<=90||!r.shiftKey&&t>=48&&t<=57||r.shiftKey&&189===t||r.shiftKey&&222===t)&&o.editor.execCommand("autocomplete")},o._onEdit=function(){o.ignoreChangeEvent||(o.cachedValue=o.editor.getValue(),o.props.onEdit&&o.props.onEdit(o.cachedValue))},o._onHasCompletion=function(e,r){(0,_onHasCompletion2.default)(e,r,o.props.onHintInformationRender)},o.cachedValue=e.value||"",o}return _inherits(r,e),_createClass(r,[{key:"componentDidMount",value:function(){var e=this,r=require("codemirror");require("codemirror/addon/hint/show-hint"),require("codemirror/addon/edit/matchbrackets"),require("codemirror/addon/edit/closebrackets"),require("codemirror/addon/fold/brace-fold"),require("codemirror/addon/fold/foldgutter"),require("codemirror/addon/lint/lint"),require("codemirror/addon/search/searchcursor"),require("codemirror/addon/search/jump-to-line"),require("codemirror/addon/dialog/dialog"),require("codemirror/keymap/sublime"),require("codemirror-graphql/variables/hint"),require("codemirror-graphql/variables/lint"),require("codemirror-graphql/variables/mode"),this.editor=r(this._node,{value:this.props.value||"",lineNumbers:!0,tabSize:2,mode:"graphql-variables",theme:this.props.editorTheme||"graphiql",keyMap:"sublime",autoCloseBrackets:!0,matchBrackets:!0,showCursorWhenSelecting:!0,readOnly:!!this.props.readOnly&&"nocursor",foldGutter:{minFoldSize:4},lint:{variableToType:this.props.variableToType},hintOptions:{variableToType:this.props.variableToType,closeOnUnfocus:!1,completeSingle:!1},gutters:["CodeMirror-linenumbers","CodeMirror-foldgutter"],extraKeys:{"Cmd-Space":function(){return e.editor.showHint({completeSingle:!1})},"Ctrl-Space":function(){return e.editor.showHint({completeSingle:!1})},"Alt-Space":function(){return e.editor.showHint({completeSingle:!1})},"Shift-Space":function(){return e.editor.showHint({completeSingle:!1})},"Cmd-Enter":function(){e.props.onRunQuery&&e.props.onRunQuery()},"Ctrl-Enter":function(){e.props.onRunQuery&&e.props.onRunQuery()},"Shift-Ctrl-P":function(){e.props.onPrettifyQuery&&e.props.onPrettifyQuery()},"Cmd-F":"findPersistent","Ctrl-F":"findPersistent","Ctrl-Left":"goSubwordLeft","Ctrl-Right":"goSubwordRight","Alt-Left":"goGroupLeft","Alt-Right":"goGroupRight"}}),this.editor.on("change",this._onEdit),this.editor.on("keyup",this._onKeyUp),this.editor.on("hasCompletion",this._onHasCompletion)}},{key:"componentDidUpdate",value:function(e){var r=require("codemirror");this.ignoreChangeEvent=!0,this.props.variableToType!==e.variableToType&&(this.editor.options.lint.variableToType=this.props.variableToType,this.editor.options.hintOptions.variableToType=this.props.variableToType,r.signal(this.editor,"change",this.editor)),this.props.value!==e.value&&this.props.value!==this.cachedValue&&(this.cachedValue=this.props.value,this.editor.setValue(this.props.value)),this.ignoreChangeEvent=!1}},{key:"componentWillUnmount",value:function(){this.editor.off("change",this._onEdit),this.editor.off("keyup",this._onKeyUp),this.editor.off("hasCompletion",this._onHasCompletion),this.editor=null}},{key:"render",value:function(){var e=this;return _react2.default.createElement("div",{className:"codemirrorWrap",ref:function(r){e._node=r}})}},{key:"getCodeMirror",value:function(){return this.editor}},{key:"getClientHeight",value:function(){return this._node&&this._node.clientHeight}}]),r}(_react2.default.Component)).propTypes={variableToType:_propTypes2.default.object,value:_propTypes2.default.string,onEdit:_propTypes2.default.func,readOnly:_propTypes2.default.bool,onHintInformationRender:_propTypes2.default.func,onPrettifyQuery:_propTypes2.default.func,onRunQuery:_propTypes2.default.func,editorTheme:_propTypes2.default.string}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../utility/onHasCompletion":34,codemirror:65,"codemirror-graphql/variables/hint":49,"codemirror-graphql/variables/lint":50,"codemirror-graphql/variables/mode":51,"codemirror/addon/dialog/dialog":53,"codemirror/addon/edit/closebrackets":54,"codemirror/addon/edit/matchbrackets":55,"codemirror/addon/fold/brace-fold":56,"codemirror/addon/fold/foldgutter":58,"codemirror/addon/hint/show-hint":59,"codemirror/addon/lint/lint":60,"codemirror/addon/search/jump-to-line":61,"codemirror/addon/search/searchcursor":63,"codemirror/keymap/sublime":64,"prop-types":233}],22:[function(require,module,exports){"use strict";module.exports=require("./components/GraphiQL").GraphiQL},{"./components/GraphiQL":12}],23:[function(require,module,exports){"use strict";function _classCallCheck(e,r){if(!(e instanceof r))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(exports,"__esModule",{value:!0});var _createClass=function(){function e(e,r){for(var t=0;t'+e.name+""}Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=function(e,n,r){var t=void 0,a=void 0;require("codemirror").on(n,"select",function(e,n){if(!t){var o=n.parentNode;(t=document.createElement("div")).className="CodeMirror-hint-information",o.appendChild(t),(a=document.createElement("div")).className="CodeMirror-hint-deprecation",o.appendChild(a);var i=void 0;o.addEventListener("DOMNodeRemoved",i=function(e){e.target===o&&(o.removeEventListener("DOMNodeRemoved",i),t=null,a=null,i=null)})}var d=e.description?md.render(e.description):"Self descriptive.",p=e.type?''+renderType(e.type)+"":"";if(t.innerHTML='
'+("

"===d.slice(0,3)?"

"+p+d.slice(3):p+d)+"

",e.isDeprecated){var l=e.deprecationReason?md.render(e.deprecationReason):"";a.innerHTML='Deprecated'+l,a.style.display="block"}else a.style.display="none";r&&r(t)})};var _graphql=require("graphql"),md=new(function(e){return e&&e.__esModule?e:{default:e}}(require("markdown-it")).default)},{codemirror:65,graphql:95,"markdown-it":172}],35:[function(require,module,exports){(function(global){"use strict";function compare(a,b){if(a===b)return 0;for(var x=a.length,y=b.length,i=0,len=Math.min(x,y);i=0;i--)if(ka[i]!==kb[i])return!1;for(i=ka.length-1;i>=0;i--)if(key=ka[i],!_deepEqual(a[key],b[key],strict,actualVisitedObjects))return!1;return!0}function notDeepStrictEqual(actual,expected,message){_deepEqual(actual,expected,!0)&&fail(actual,expected,message,"notDeepStrictEqual",notDeepStrictEqual)}function expectedException(actual,expected){if(!actual||!expected)return!1;if("[object RegExp]"==Object.prototype.toString.call(expected))return expected.test(actual);try{if(actual instanceof expected)return!0}catch(e){}return!Error.isPrototypeOf(expected)&&!0===expected.call({},actual)}function _tryBlock(block){var error;try{block()}catch(e){error=e}return error}function _throws(shouldThrow,block,expected,message){var actual;if("function"!=typeof block)throw new TypeError('"block" argument must be a function');"string"==typeof expected&&(message=expected,expected=null),actual=_tryBlock(block),message=(expected&&expected.name?" ("+expected.name+").":".")+(message?" "+message:"."),shouldThrow&&!actual&&fail(actual,expected,"Missing expected exception"+message);var userProvidedMessage="string"==typeof message,isUnwantedException=!shouldThrow&&util.isError(actual),isUnexpectedException=!shouldThrow&&actual&&!expected;if((isUnwantedException&&userProvidedMessage&&expectedException(actual,expected)||isUnexpectedException)&&fail(actual,expected,"Got unwanted exception"+message),shouldThrow&&actual&&expected&&!expectedException(actual,expected)||!shouldThrow&&actual)throw actual}var util=require("util/"),hasOwn=Object.prototype.hasOwnProperty,pSlice=Array.prototype.slice,functionsHaveNames="foo"===function(){}.name,assert=module.exports=ok,regex=/\s*function\s+([^\(\s]*)\s*/;assert.AssertionError=function(options){this.name="AssertionError",this.actual=options.actual,this.expected=options.expected,this.operator=options.operator,options.message?(this.message=options.message,this.generatedMessage=!1):(this.message=getMessage(this),this.generatedMessage=!0);var stackStartFunction=options.stackStartFunction||fail;if(Error.captureStackTrace)Error.captureStackTrace(this,stackStartFunction);else{var err=new Error;if(err.stack){var out=err.stack,fn_name=getName(stackStartFunction),idx=out.indexOf("\n"+fn_name);if(idx>=0){var next_line=out.indexOf("\n",idx+1);out=out.substring(next_line+1)}this.stack=out}}},util.inherits(assert.AssertionError,Error),assert.fail=fail,assert.ok=ok,assert.equal=function(actual,expected,message){actual!=expected&&fail(actual,expected,message,"==",assert.equal)},assert.notEqual=function(actual,expected,message){actual==expected&&fail(actual,expected,message,"!=",assert.notEqual)},assert.deepEqual=function(actual,expected,message){_deepEqual(actual,expected,!1)||fail(actual,expected,message,"deepEqual",assert.deepEqual)},assert.deepStrictEqual=function(actual,expected,message){_deepEqual(actual,expected,!0)||fail(actual,expected,message,"deepStrictEqual",assert.deepStrictEqual)},assert.notDeepEqual=function(actual,expected,message){_deepEqual(actual,expected,!1)&&fail(actual,expected,message,"notDeepEqual",assert.notDeepEqual)},assert.notDeepStrictEqual=notDeepStrictEqual,assert.strictEqual=function(actual,expected,message){actual!==expected&&fail(actual,expected,message,"===",assert.strictEqual)},assert.notStrictEqual=function(actual,expected,message){actual===expected&&fail(actual,expected,message,"!==",assert.notStrictEqual)},assert.throws=function(block,error,message){_throws(!0,block,error,message)},assert.doesNotThrow=function(block,error,message){_throws(!1,block,error,message)},assert.ifError=function(err){if(err)throw err};var objectKeys=Object.keys||function(obj){var keys=[];for(var key in obj)hasOwn.call(obj,key)&&keys.push(key);return keys}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"util/":244}],36:[function(require,module,exports){"use strict";var _codemirror2=function(obj){return obj&&obj.__esModule?obj:{default:obj}}(require("codemirror")),_graphqlLanguageServiceInterface=require("graphql-language-service-interface");_codemirror2.default.registerHelper("hint","graphql",function(editor,options){var schema=options.schema;if(schema){var cur=editor.getCursor(),token=editor.getTokenAt(cur),rawResults=(0,_graphqlLanguageServiceInterface.getAutocompleteSuggestions)(schema,editor.getValue(),cur,token),tokenStart=null!==token.type&&/"|\w/.test(token.string[0])?token.start:token.end,results={list:rawResults.map(function(item){return{text:item.label,type:schema.getType(item.detail),description:item.documentation,isDeprecated:item.isDeprecated,deprecationReason:item.deprecationReason}}),from:{line:cur.line,column:tokenStart},to:{line:cur.line,column:token.end}};return results&&results.list&&results.list.length>0&&(results.from=_codemirror2.default.Pos(results.from.line,results.from.column),results.to=_codemirror2.default.Pos(results.to.line,results.to.column),_codemirror2.default.signal(editor,"hasCompletion",editor,results,token)),results}})},{codemirror:65,"graphql-language-service-interface":76}],37:[function(require,module,exports){"use strict";function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function renderField(into,typeInfo,options){renderQualifiedField(into,typeInfo,options),renderTypeAnnotation(into,typeInfo,options,typeInfo.type)}function renderQualifiedField(into,typeInfo,options){var fieldName=typeInfo.fieldDef.name;"__"!==fieldName.slice(0,2)&&(renderType(into,typeInfo,options,typeInfo.parentType),text(into,".")),text(into,fieldName,"field-name",options,(0,_SchemaReference.getFieldReference)(typeInfo))}function renderDirective(into,typeInfo,options){text(into,"@"+typeInfo.directiveDef.name,"directive-name",options,(0,_SchemaReference.getDirectiveReference)(typeInfo))}function renderArg(into,typeInfo,options){typeInfo.directiveDef?renderDirective(into,typeInfo,options):typeInfo.fieldDef&&renderQualifiedField(into,typeInfo,options);var name=typeInfo.argDef.name;text(into,"("),text(into,name,"arg-name",options,(0,_SchemaReference.getArgumentReference)(typeInfo)),renderTypeAnnotation(into,typeInfo,options,typeInfo.inputType),text(into,")")}function renderTypeAnnotation(into,typeInfo,options,t){text(into,": "),renderType(into,typeInfo,options,t)}function renderEnumValue(into,typeInfo,options){var name=typeInfo.enumValue.name;renderType(into,typeInfo,options,typeInfo.inputType),text(into,"."),text(into,name,"enum-value",options,(0,_SchemaReference.getEnumValueReference)(typeInfo))}function renderType(into,typeInfo,options,t){t instanceof _graphql.GraphQLNonNull?(renderType(into,typeInfo,options,t.ofType),text(into,"!")):t instanceof _graphql.GraphQLList?(text(into,"["),renderType(into,typeInfo,options,t.ofType),text(into,"]")):text(into,t.name,"type-name",options,(0,_SchemaReference.getTypeReference)(typeInfo,t))}function renderDescription(into,options,def){var description=def.description;if(description){var descriptionDiv=document.createElement("div");descriptionDiv.className="info-description",options.renderDescription?descriptionDiv.innerHTML=options.renderDescription(description):descriptionDiv.appendChild(document.createTextNode(description)),into.appendChild(descriptionDiv)}renderDeprecation(into,options,def)}function renderDeprecation(into,options,def){var reason=def.deprecationReason;if(reason){var deprecationDiv=document.createElement("div");deprecationDiv.className="info-deprecation",options.renderDescription?deprecationDiv.innerHTML=options.renderDescription(reason):deprecationDiv.appendChild(document.createTextNode(reason));var label=document.createElement("span");label.className="info-deprecation-label",label.appendChild(document.createTextNode("Deprecated: ")),deprecationDiv.insertBefore(label,deprecationDiv.firstChild),into.appendChild(deprecationDiv)}}function text(into,content,className,options,ref){if(className){var onClick=options.onClick,node=document.createElement(onClick?"a":"span");onClick&&(node.href="javascript:void 0",node.addEventListener("click",function(e){onClick(ref,e)})),node.className=className,node.appendChild(document.createTextNode(content)),into.appendChild(node)}else into.appendChild(document.createTextNode(content))}var _graphql=require("graphql"),_codemirror2=_interopRequireDefault(require("codemirror")),_getTypeInfo2=_interopRequireDefault(require("./utils/getTypeInfo")),_SchemaReference=require("./utils/SchemaReference");require("./utils/info-addon"),_codemirror2.default.registerHelper("info","graphql",function(token,options){if(options.schema&&token.state){var state=token.state,kind=state.kind,step=state.step,typeInfo=(0,_getTypeInfo2.default)(options.schema,token.state);if("Field"===kind&&0===step&&typeInfo.fieldDef||"AliasedField"===kind&&2===step&&typeInfo.fieldDef){var into=document.createElement("div");return renderField(into,typeInfo,options),renderDescription(into,options,typeInfo.fieldDef),into}if("Directive"===kind&&1===step&&typeInfo.directiveDef){var _into=document.createElement("div");return renderDirective(_into,typeInfo,options),renderDescription(_into,options,typeInfo.directiveDef),_into}if("Argument"===kind&&0===step&&typeInfo.argDef){var _into2=document.createElement("div");return renderArg(_into2,typeInfo,options),renderDescription(_into2,options,typeInfo.argDef),_into2}if("EnumValue"===kind&&typeInfo.enumValue&&typeInfo.enumValue.description){var _into3=document.createElement("div");return renderEnumValue(_into3,typeInfo,options),renderDescription(_into3,options,typeInfo.enumValue),_into3}if("NamedType"===kind&&typeInfo.type&&typeInfo.type.description){var _into4=document.createElement("div");return renderType(_into4,typeInfo,options,typeInfo.type),renderDescription(_into4,options,typeInfo.type),_into4}}})},{"./utils/SchemaReference":42,"./utils/getTypeInfo":44,"./utils/info-addon":46,codemirror:65,graphql:95}],38:[function(require,module,exports){"use strict";function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}var _codemirror2=_interopRequireDefault(require("codemirror")),_getTypeInfo2=_interopRequireDefault(require("./utils/getTypeInfo")),_SchemaReference=require("./utils/SchemaReference");require("./utils/jump-addon"),_codemirror2.default.registerHelper("jump","graphql",function(token,options){if(options.schema&&options.onClick&&token.state){var state=token.state,kind=state.kind,step=state.step,typeInfo=(0,_getTypeInfo2.default)(options.schema,state);return"Field"===kind&&0===step&&typeInfo.fieldDef||"AliasedField"===kind&&2===step&&typeInfo.fieldDef?(0,_SchemaReference.getFieldReference)(typeInfo):"Directive"===kind&&1===step&&typeInfo.directiveDef?(0,_SchemaReference.getDirectiveReference)(typeInfo):"Argument"===kind&&0===step&&typeInfo.argDef?(0,_SchemaReference.getArgumentReference)(typeInfo):"EnumValue"===kind&&typeInfo.enumValue?(0,_SchemaReference.getEnumValueReference)(typeInfo):"NamedType"===kind&&typeInfo.type?(0,_SchemaReference.getTypeReference)(typeInfo):void 0}})},{"./utils/SchemaReference":42,"./utils/getTypeInfo":44,"./utils/jump-addon":48,codemirror:65}],39:[function(require,module,exports){"use strict";var _codemirror2=function(obj){return obj&&obj.__esModule?obj:{default:obj}}(require("codemirror")),_graphqlLanguageServiceInterface=require("graphql-language-service-interface"),SEVERITY=["error","warning","information","hint"],TYPE={"GraphQL: Validation":"validation","GraphQL: Deprecation":"deprecation","GraphQL: Syntax":"syntax"};_codemirror2.default.registerHelper("lint","graphql",function(text,options){var schema=options.schema;return(0,_graphqlLanguageServiceInterface.getDiagnostics)(text,schema).map(function(error){return{message:error.message,severity:SEVERITY[error.severity-1],type:TYPE[error.source],from:_codemirror2.default.Pos(error.range.start.line,error.range.start.character),to:_codemirror2.default.Pos(error.range.end.line,error.range.end.character)}})})},{codemirror:65,"graphql-language-service-interface":76}],40:[function(require,module,exports){"use strict";function indent(state,textAfter){var levels=state.levels;return(levels&&0!==levels.length?levels[levels.length-1]-(this.electricInput.test(textAfter)?1:0):state.indentLevel)*this.config.indentUnit}var _codemirror2=function(obj){return obj&&obj.__esModule?obj:{default:obj}}(require("codemirror")),_graphqlLanguageServiceParser=require("graphql-language-service-parser");_codemirror2.default.defineMode("graphql",function(config){var parser=(0,_graphqlLanguageServiceParser.onlineParser)({eatWhitespace:function(stream){return stream.eatWhile(_graphqlLanguageServiceParser.isIgnored)},lexRules:_graphqlLanguageServiceParser.LexRules,parseRules:_graphqlLanguageServiceParser.ParseRules,editorConfig:{tabSize:config.tabSize}});return{config:config,startState:parser.startState,token:parser.token,indent:indent,electricInput:/^\s*[})\]]/,fold:"brace",lineComment:"#",closeBrackets:{pairs:'()[]{}""',explode:"()[]{}"}}})},{codemirror:65,"graphql-language-service-parser":80}],41:[function(require,module,exports){"use strict";function indent(state,textAfter){var levels=state.levels;return(levels&&0!==levels.length?levels[levels.length-1]-(this.electricInput.test(textAfter)?1:0):state.indentLevel)*this.config.indentUnit}var _codemirror2=function(obj){return obj&&obj.__esModule?obj:{default:obj}}(require("codemirror")),_graphqlLanguageServiceParser=require("graphql-language-service-parser");_codemirror2.default.defineMode("graphql-results",function(config){var parser=(0,_graphqlLanguageServiceParser.onlineParser)({eatWhitespace:function(stream){return stream.eatSpace()},lexRules:LexRules,parseRules:ParseRules,editorConfig:{tabSize:config.tabSize}});return{config:config,startState:parser.startState,token:parser.token,indent:indent,electricInput:/^\s*[}\]]/,fold:"brace",closeBrackets:{pairs:'[]{}""',explode:"[]{}"}}});var LexRules={Punctuation:/^\[|]|\{|\}|:|,/,Number:/^-?(?:0|(?:[1-9][0-9]*))(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?/,String:/^"(?:[^"\\]|\\(?:"|\/|\\|b|f|n|r|t|u[0-9a-fA-F]{4}))*"?/,Keyword:/^true|false|null/},ParseRules={Document:[(0,_graphqlLanguageServiceParser.p)("{"),(0,_graphqlLanguageServiceParser.list)("Entry",(0,_graphqlLanguageServiceParser.p)(",")),(0,_graphqlLanguageServiceParser.p)("}")],Entry:[(0,_graphqlLanguageServiceParser.t)("String","def"),(0,_graphqlLanguageServiceParser.p)(":"),"Value"],Value:function(token){switch(token.kind){case"Number":return"NumberValue";case"String":return"StringValue";case"Punctuation":switch(token.value){case"[":return"ListValue";case"{":return"ObjectValue"}return null;case"Keyword":switch(token.value){case"true":case"false":return"BooleanValue";case"null":return"NullValue"}return null}},NumberValue:[(0,_graphqlLanguageServiceParser.t)("Number","number")],StringValue:[(0,_graphqlLanguageServiceParser.t)("String","string")],BooleanValue:[(0,_graphqlLanguageServiceParser.t)("Keyword","builtin")],NullValue:[(0,_graphqlLanguageServiceParser.t)("Keyword","keyword")],ListValue:[(0,_graphqlLanguageServiceParser.p)("["),(0,_graphqlLanguageServiceParser.list)("Value",(0,_graphqlLanguageServiceParser.p)(",")),(0,_graphqlLanguageServiceParser.p)("]")],ObjectValue:[(0,_graphqlLanguageServiceParser.p)("{"),(0,_graphqlLanguageServiceParser.list)("ObjectField",(0,_graphqlLanguageServiceParser.p)(",")),(0,_graphqlLanguageServiceParser.p)("}")],ObjectField:[(0,_graphqlLanguageServiceParser.t)("String","property"),(0,_graphqlLanguageServiceParser.p)(":"),"Value"]}},{codemirror:65,"graphql-language-service-parser":80}],42:[function(require,module,exports){"use strict";function isMetaField(fieldDef){return"__"===fieldDef.name.slice(0,2)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getFieldReference=function(typeInfo){return{kind:"Field",schema:typeInfo.schema,field:typeInfo.fieldDef,type:isMetaField(typeInfo.fieldDef)?null:typeInfo.parentType}},exports.getDirectiveReference=function(typeInfo){return{kind:"Directive",schema:typeInfo.schema,directive:typeInfo.directiveDef}},exports.getArgumentReference=function(typeInfo){return typeInfo.directiveDef?{kind:"Argument",schema:typeInfo.schema,argument:typeInfo.argDef,directive:typeInfo.directiveDef}:{kind:"Argument",schema:typeInfo.schema,argument:typeInfo.argDef,field:typeInfo.fieldDef,type:isMetaField(typeInfo.fieldDef)?null:typeInfo.parentType}},exports.getEnumValueReference=function(typeInfo){return{kind:"EnumValue",value:typeInfo.enumValue,type:(0,_graphql.getNamedType)(typeInfo.inputType)}},exports.getTypeReference=function(typeInfo,type){return{kind:"Type",schema:typeInfo.schema,type:type||typeInfo.type}};var _graphql=require("graphql")},{graphql:95}],43:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=function(stack,fn){for(var reverseStateStack=[],state=stack;state&&state.kind;)reverseStateStack.push(state),state=state.prevState;for(var i=reverseStateStack.length-1;i>=0;i--)fn(reverseStateStack[i])}},{}],44:[function(require,module,exports){"use strict";function getFieldDef(schema,type,fieldName){return fieldName===_introspection.SchemaMetaFieldDef.name&&schema.getQueryType()===type?_introspection.SchemaMetaFieldDef:fieldName===_introspection.TypeMetaFieldDef.name&&schema.getQueryType()===type?_introspection.TypeMetaFieldDef:fieldName===_introspection.TypeNameMetaFieldDef.name&&(0,_graphql.isCompositeType)(type)?_introspection.TypeNameMetaFieldDef:type.getFields?type.getFields()[fieldName]:void 0}function find(array,predicate){for(var i=0;itext.length&&(proximity-=suggestion.length-text.length-1,proximity+=0===suggestion.indexOf(text)?0:.5),proximity}function lexicalDistance(a,b){var i=void 0,j=void 0,d=[],aLength=a.length,bLength=b.length;for(i=0;i<=aLength;i++)d[i]=[i];for(j=1;j<=bLength;j++)d[0][j]=j;for(i=1;i<=aLength;i++)for(j=1;j<=bLength;j++){var cost=a[i-1]===b[j-1]?0:1;d[i][j]=Math.min(d[i-1][j]+1,d[i][j-1]+1,d[i-1][j-1]+cost),i>1&&j>1&&a[i-1]===b[j-2]&&a[i-2]===b[j-1]&&(d[i][j]=Math.min(d[i][j],d[i-2][j-2]+cost))}return d[aLength][bLength]}Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=function(cursor,token,list){var hints=filterAndSortList(list,normalizeText(token.string));if(hints){var tokenStart=null!==token.type&&/"|\w/.test(token.string[0])?token.start:token.end;return{list:hints,from:{line:cursor.line,column:tokenStart},to:{line:cursor.line,column:token.end}}}}},{}],46:[function(require,module,exports){"use strict";function createState(options){return{options:options instanceof Function?{render:options}:!0===options?{}:options}}function getHoverTime(cm){var options=cm.state.info.options;return options&&options.hoverTime||500}function onMouseOver(cm,e){var state=cm.state.info,target=e.target||e.srcElement;if("SPAN"===target.nodeName&&void 0===state.hoverTimeout){var box=target.getBoundingClientRect(),hoverTime=getHoverTime(cm);state.hoverTimeout=setTimeout(onHover,hoverTime);var onMouseMove=function(){clearTimeout(state.hoverTimeout),state.hoverTimeout=setTimeout(onHover,hoverTime)},onMouseOut=function onMouseOut(){_codemirror2.default.off(document,"mousemove",onMouseMove),_codemirror2.default.off(cm.getWrapperElement(),"mouseout",onMouseOut),clearTimeout(state.hoverTimeout),state.hoverTimeout=void 0},onHover=function(){_codemirror2.default.off(document,"mousemove",onMouseMove),_codemirror2.default.off(cm.getWrapperElement(),"mouseout",onMouseOut),state.hoverTimeout=void 0,onMouseHover(cm,box)};_codemirror2.default.on(document,"mousemove",onMouseMove),_codemirror2.default.on(cm.getWrapperElement(),"mouseout",onMouseOut)}}function onMouseHover(cm,box){var pos=cm.coordsChar({left:(box.left+box.right)/2,top:(box.top+box.bottom)/2}),options=cm.state.info.options,render=options.render||cm.getHelper(pos,"info");if(render){var token=cm.getTokenAt(pos,!0);if(token){var info=render(token,options,cm);info&&showPopup(cm,box,info)}}}function showPopup(cm,box,info){var popup=document.createElement("div");popup.className="CodeMirror-info",popup.appendChild(info),document.body.appendChild(popup);var popupBox=popup.getBoundingClientRect(),popupStyle=popup.currentStyle||window.getComputedStyle(popup),popupWidth=popupBox.right-popupBox.left+parseFloat(popupStyle.marginLeft)+parseFloat(popupStyle.marginRight),popupHeight=popupBox.bottom-popupBox.top+parseFloat(popupStyle.marginTop)+parseFloat(popupStyle.marginBottom),topPos=box.bottom;popupHeight>window.innerHeight-box.bottom-15&&box.top>window.innerHeight-box.bottom&&(topPos=box.top-popupHeight),topPos<0&&(topPos=box.bottom);var leftPos=Math.max(0,window.innerWidth-popupWidth-15);leftPos>box.left&&(leftPos=box.left),popup.style.opacity=1,popup.style.top=topPos+"px",popup.style.left=leftPos+"px";var popupTimeout=void 0,onMouseOverPopup=function(){clearTimeout(popupTimeout)},onMouseOut=function(){clearTimeout(popupTimeout),popupTimeout=setTimeout(hidePopup,200)},hidePopup=function(){_codemirror2.default.off(popup,"mouseover",onMouseOverPopup),_codemirror2.default.off(popup,"mouseout",onMouseOut),_codemirror2.default.off(cm.getWrapperElement(),"mouseout",onMouseOut),popup.style.opacity?(popup.style.opacity=0,setTimeout(function(){popup.parentNode&&popup.parentNode.removeChild(popup)},600)):popup.parentNode&&popup.parentNode.removeChild(popup)};_codemirror2.default.on(popup,"mouseover",onMouseOverPopup),_codemirror2.default.on(popup,"mouseout",onMouseOut),_codemirror2.default.on(cm.getWrapperElement(),"mouseout",onMouseOut)}var _codemirror2=function(obj){return obj&&obj.__esModule?obj:{default:obj}}(require("codemirror"));_codemirror2.default.defineOption("info",!1,function(cm,options,old){if(old&&old!==_codemirror2.default.Init){var oldOnMouseOver=cm.state.info.onMouseOver;_codemirror2.default.off(cm.getWrapperElement(),"mouseover",oldOnMouseOver),clearTimeout(cm.state.info.hoverTimeout),delete cm.state.info}if(options){var state=cm.state.info=createState(options);state.onMouseOver=onMouseOver.bind(null,cm),_codemirror2.default.on(cm.getWrapperElement(),"mouseover",state.onMouseOver)}})},{codemirror:65}],47:[function(require,module,exports){"use strict";function parseObj(){var nodeStart=start,members=[];if(expect("{"),!skip("}")){do{members.push(parseMember())}while(skip(","));expect("}")}return{kind:"Object",start:nodeStart,end:lastEnd,members:members}}function parseMember(){var nodeStart=start,key="String"===kind?curToken():null;expect("String"),expect(":");var value=parseVal();return{kind:"Member",start:nodeStart,end:lastEnd,key:key,value:value}}function parseArr(){var nodeStart=start,values=[];if(expect("["),!skip("]")){do{values.push(parseVal())}while(skip(","));expect("]")}return{kind:"Array",start:nodeStart,end:lastEnd,values:values}}function parseVal(){switch(kind){case"[":return parseArr();case"{":return parseObj();case"String":case"Number":case"Boolean":case"Null":var token=curToken();return lex(),token}return expect("Value")}function curToken(){return{kind:kind,start:start,end:end,value:JSON.parse(string.slice(start,end))}}function expect(str){if(kind!==str){var found=void 0;if("EOF"===kind)found="[end of file]";else if(end-start>1)found="`"+string.slice(start,end)+"`";else{var match=string.slice(start).match(/^.+?\b/);found="`"+(match?match[0]:string[start])+"`"}throw syntaxError("Expected "+str+" but found "+found+".")}lex()}function syntaxError(message){return{message:message,start:start,end:end}}function skip(k){if(kind===k)return lex(),!0}function ch(){end31;)if(92===code)switch(ch(),code){case 34:case 47:case 92:case 98:case 102:case 110:case 114:case 116:ch();break;case 117:ch(),readHex(),readHex(),readHex(),readHex();break;default:throw syntaxError("Bad character escape sequence.")}else{if(end===strLen)throw syntaxError("Unterminated string.");ch()}if(34!==code)throw syntaxError("Unterminated string.");ch()}function readHex(){if(code>=48&&code<=57||code>=65&&code<=70||code>=97&&code<=102)return ch();throw syntaxError("Expected hexadecimal digit.")}function readNumber(){45===code&&ch(),48===code?ch():readDigits(),46===code&&(ch(),readDigits()),69!==code&&101!==code||(ch(),43!==code&&45!==code||ch(),readDigits())}function readDigits(){if(code<48||code>57)throw syntaxError("Expected decimal digit.");do{ch()}while(code>=48&&code<=57)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=function(str){string=str,strLen=str.length,start=end=lastEnd=-1,ch(),lex();var ast=parseObj();return expect("EOF"),ast};var string=void 0,strLen=void 0,start=void 0,end=void 0,lastEnd=void 0,code=void 0,kind=void 0},{}],48:[function(require,module,exports){"use strict";function onMouseOver(cm,event){var target=event.target||event.srcElement;if("SPAN"===target.nodeName){var box=target.getBoundingClientRect(),cursor={left:(box.left+box.right)/2,top:(box.top+box.bottom)/2};cm.state.jump.cursor=cursor,cm.state.jump.isHoldingModifier&&enableJumpMode(cm)}}function onMouseOut(cm){cm.state.jump.isHoldingModifier||!cm.state.jump.cursor?cm.state.jump.isHoldingModifier&&cm.state.jump.marker&&disableJumpMode(cm):cm.state.jump.cursor=null}function onKeyDown(cm,event){if(!cm.state.jump.isHoldingModifier&&isJumpModifier(event.key)){cm.state.jump.isHoldingModifier=!0,cm.state.jump.cursor&&enableJumpMode(cm);var onClick=function(clickEvent){var destination=cm.state.jump.destination;destination&&cm.state.jump.options.onClick(destination,clickEvent)},onMouseDown=function(_,downEvent){cm.state.jump.destination&&(downEvent.codemirrorIgnore=!0)};_codemirror2.default.on(document,"keyup",function onKeyUp(upEvent){upEvent.code===event.code&&(cm.state.jump.isHoldingModifier=!1,cm.state.jump.marker&&disableJumpMode(cm),_codemirror2.default.off(document,"keyup",onKeyUp),_codemirror2.default.off(document,"click",onClick),cm.off("mousedown",onMouseDown))}),_codemirror2.default.on(document,"click",onClick),cm.on("mousedown",onMouseDown)}}function isJumpModifier(key){return key===(isMac?"Meta":"Control")}function enableJumpMode(cm){if(!cm.state.jump.marker){var cursor=cm.state.jump.cursor,pos=cm.coordsChar(cursor),token=cm.getTokenAt(pos,!0),options=cm.state.jump.options,getDestination=options.getDestination||cm.getHelper(pos,"jump");if(getDestination){var destination=getDestination(token,options,cm);if(destination){var marker=cm.markText({line:pos.line,ch:token.start},{line:pos.line,ch:token.end},{className:"CodeMirror-jump-token"});cm.state.jump.marker=marker,cm.state.jump.destination=destination}}}}function disableJumpMode(cm){var marker=cm.state.jump.marker;cm.state.jump.marker=null,cm.state.jump.destination=null,marker.clear()}var _codemirror2=function(obj){return obj&&obj.__esModule?obj:{default:obj}}(require("codemirror"));_codemirror2.default.defineOption("jump",!1,function(cm,options,old){if(old&&old!==_codemirror2.default.Init){var oldOnMouseOver=cm.state.jump.onMouseOver;_codemirror2.default.off(cm.getWrapperElement(),"mouseover",oldOnMouseOver);var oldOnMouseOut=cm.state.jump.onMouseOut;_codemirror2.default.off(cm.getWrapperElement(),"mouseout",oldOnMouseOut),_codemirror2.default.off(document,"keydown",cm.state.jump.onKeyDown),delete cm.state.jump}if(options){var state=cm.state.jump={options:options,onMouseOver:onMouseOver.bind(null,cm),onMouseOut:onMouseOut.bind(null,cm),onKeyDown:onKeyDown.bind(null,cm)};_codemirror2.default.on(cm.getWrapperElement(),"mouseover",state.onMouseOver),_codemirror2.default.on(cm.getWrapperElement(),"mouseout",state.onMouseOut),_codemirror2.default.on(document,"keydown",state.onKeyDown)}});var isMac=navigator&&-1!==navigator.appVersion.indexOf("Mac")},{codemirror:65}],49:[function(require,module,exports){"use strict";function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function getVariablesHint(cur,token,options){var state="Invalid"===token.state.kind?token.state.prevState:token.state,kind=state.kind,step=state.step;if("Document"===kind&&0===step)return(0,_hintList2.default)(cur,token,[{text:"{"}]);var variableToType=options.variableToType;if(variableToType){var typeInfo=getTypeInfo(variableToType,token.state);if("Document"===kind||"Variable"===kind&&0===step){var variableNames=Object.keys(variableToType);return(0,_hintList2.default)(cur,token,variableNames.map(function(name){return{text:'"'+name+'": ',type:variableToType[name]}}))}if(("ObjectValue"===kind||"ObjectField"===kind&&0===step)&&typeInfo.fields){var inputFields=Object.keys(typeInfo.fields).map(function(fieldName){return typeInfo.fields[fieldName]});return(0,_hintList2.default)(cur,token,inputFields.map(function(field){return{text:'"'+field.name+'": ',type:field.type,description:field.description}}))}if("StringValue"===kind||"NumberValue"===kind||"BooleanValue"===kind||"NullValue"===kind||"ListValue"===kind&&1===step||"ObjectField"===kind&&2===step||"Variable"===kind&&2===step){var namedInputType=(0,_graphql.getNamedType)(typeInfo.type);if(namedInputType instanceof _graphql.GraphQLInputObjectType)return(0,_hintList2.default)(cur,token,[{text:"{"}]);if(namedInputType instanceof _graphql.GraphQLEnumType){var valueMap=namedInputType.getValues(),values=Object.keys(valueMap).map(function(name){return valueMap[name]});return(0,_hintList2.default)(cur,token,values.map(function(value){return{text:'"'+value.name+'"',type:namedInputType,description:value.description}}))}if(namedInputType===_graphql.GraphQLBoolean)return(0,_hintList2.default)(cur,token,[{text:"true",type:_graphql.GraphQLBoolean,description:"Not false."},{text:"false",type:_graphql.GraphQLBoolean,description:"Not true."}])}}}function getTypeInfo(variableToType,tokenState){var info={type:null,fields:null};return(0,_forEachState2.default)(tokenState,function(state){if("Variable"===state.kind)info.type=variableToType[state.name];else if("ListValue"===state.kind){var nullableType=(0,_graphql.getNullableType)(info.type);info.type=nullableType instanceof _graphql.GraphQLList?nullableType.ofType:null}else if("ObjectValue"===state.kind){var objectType=(0,_graphql.getNamedType)(info.type);info.fields=objectType instanceof _graphql.GraphQLInputObjectType?objectType.getFields():null}else if("ObjectField"===state.kind){var objectField=state.name&&info.fields?info.fields[state.name]:null;info.type=objectField&&objectField.type}}),info}var _codemirror2=_interopRequireDefault(require("codemirror")),_graphql=require("graphql"),_forEachState2=_interopRequireDefault(require("../utils/forEachState")),_hintList2=_interopRequireDefault(require("../utils/hintList"));_codemirror2.default.registerHelper("hint","graphql-variables",function(editor,options){var cur=editor.getCursor(),token=editor.getTokenAt(cur),results=getVariablesHint(cur,token,options);return results&&results.list&&results.list.length>0&&(results.from=_codemirror2.default.Pos(results.from.line,results.from.column),results.to=_codemirror2.default.Pos(results.to.line,results.to.column),_codemirror2.default.signal(editor,"hasCompletion",editor,results,token)),results})},{"../utils/forEachState":43,"../utils/hintList":45,codemirror:65,graphql:95}],50:[function(require,module,exports){"use strict";function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function validateVariables(editor,variableToType,variablesAST){var errors=[];return variablesAST.members.forEach(function(member){var variableName=member.key.value,type=variableToType[variableName];type?validateValue(type,member.value).forEach(function(_ref){var node=_ref[0],message=_ref[1];errors.push(lintError(editor,node,message))}):errors.push(lintError(editor,member.key,'Variable "$'+variableName+'" does not appear in any GraphQL query.'))}),errors}function validateValue(type,valueAST){if(type instanceof _graphql.GraphQLNonNull)return"Null"===valueAST.kind?[[valueAST,'Type "'+type+'" is non-nullable and cannot be null.']]:validateValue(type.ofType,valueAST);if("Null"===valueAST.kind)return[];if(type instanceof _graphql.GraphQLList){var itemType=type.ofType;return"Array"===valueAST.kind?mapCat(valueAST.values,function(item){return validateValue(itemType,item)}):validateValue(itemType,valueAST)}if(type instanceof _graphql.GraphQLInputObjectType){if("Object"!==valueAST.kind)return[[valueAST,'Type "'+type+'" must be an Object.']];var providedFields=Object.create(null),fieldErrors=mapCat(valueAST.members,function(member){var fieldName=member.key.value;providedFields[fieldName]=!0;var inputField=type.getFields()[fieldName];return inputField?validateValue(inputField?inputField.type:void 0,member.value):[[member.key,'Type "'+type+'" does not have a field "'+fieldName+'".']]});return Object.keys(type.getFields()).forEach(function(fieldName){providedFields[fieldName]||type.getFields()[fieldName].type instanceof _graphql.GraphQLNonNull&&fieldErrors.push([valueAST,'Object of type "'+type+'" is missing required field "'+fieldName+'".'])}),fieldErrors}return"Boolean"===type.name&&"Boolean"!==valueAST.kind||"String"===type.name&&"String"!==valueAST.kind||"ID"===type.name&&"Number"!==valueAST.kind&&"String"!==valueAST.kind||"Float"===type.name&&"Number"!==valueAST.kind||"Int"===type.name&&("Number"!==valueAST.kind||(0|valueAST.value)!==valueAST.value)?[[valueAST,'Expected value of type "'+type+'".']]:(type instanceof _graphql.GraphQLEnumType||type instanceof _graphql.GraphQLScalarType)&&("String"!==valueAST.kind&&"Number"!==valueAST.kind&&"Boolean"!==valueAST.kind&&"Null"!==valueAST.kind||isNullish(type.parseValue(valueAST.value)))?[[valueAST,'Expected value of type "'+type+'".']]:[]}function lintError(editor,node,message){return{message:message,severity:"error",type:"validation",from:editor.posFromIndex(node.start),to:editor.posFromIndex(node.end)}}function isNullish(value){return null===value||void 0===value||value!==value}function mapCat(array,mapper){return Array.prototype.concat.apply([],array.map(mapper))}var _codemirror2=_interopRequireDefault(require("codemirror")),_graphql=require("graphql"),_jsonParse2=_interopRequireDefault(require("../utils/jsonParse"));_codemirror2.default.registerHelper("lint","graphql-variables",function(text,options,editor){if(!text)return[];var ast=void 0;try{ast=(0,_jsonParse2.default)(text)}catch(syntaxError){if(syntaxError.stack)throw syntaxError;return[lintError(editor,syntaxError,syntaxError.message)]}var variableToType=options.variableToType;return variableToType?validateVariables(editor,variableToType,ast):[]})},{"../utils/jsonParse":47,codemirror:65,graphql:95}],51:[function(require,module,exports){"use strict";function indent(state,textAfter){var levels=state.levels;return(levels&&0!==levels.length?levels[levels.length-1]-(this.electricInput.test(textAfter)?1:0):state.indentLevel)*this.config.indentUnit}function namedKey(style){return{style:style,match:function(token){return"String"===token.kind},update:function(state,token){state.name=token.value.slice(1,-1)}}}var _codemirror2=function(obj){return obj&&obj.__esModule?obj:{default:obj}}(require("codemirror")),_graphqlLanguageServiceParser=require("graphql-language-service-parser");_codemirror2.default.defineMode("graphql-variables",function(config){var parser=(0,_graphqlLanguageServiceParser.onlineParser)({eatWhitespace:function(stream){return stream.eatSpace()},lexRules:LexRules,parseRules:ParseRules,editorConfig:{tabSize:config.tabSize}});return{config:config,startState:parser.startState,token:parser.token,indent:indent,electricInput:/^\s*[}\]]/,fold:"brace",closeBrackets:{pairs:'[]{}""',explode:"[]{}"}}});var LexRules={Punctuation:/^\[|]|\{|\}|:|,/,Number:/^-?(?:0|(?:[1-9][0-9]*))(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?/,String:/^"(?:[^"\\]|\\(?:"|\/|\\|b|f|n|r|t|u[0-9a-fA-F]{4}))*"?/,Keyword:/^true|false|null/},ParseRules={Document:[(0,_graphqlLanguageServiceParser.p)("{"),(0,_graphqlLanguageServiceParser.list)("Variable",(0,_graphqlLanguageServiceParser.opt)((0,_graphqlLanguageServiceParser.p)(","))),(0,_graphqlLanguageServiceParser.p)("}")],Variable:[namedKey("variable"),(0,_graphqlLanguageServiceParser.p)(":"),"Value"],Value:function(token){switch(token.kind){case"Number":return"NumberValue";case"String":return"StringValue";case"Punctuation":switch(token.value){case"[":return"ListValue";case"{":return"ObjectValue"}return null;case"Keyword":switch(token.value){case"true":case"false":return"BooleanValue";case"null":return"NullValue"}return null}},NumberValue:[(0,_graphqlLanguageServiceParser.t)("Number","number")],StringValue:[(0,_graphqlLanguageServiceParser.t)("String","string")],BooleanValue:[(0,_graphqlLanguageServiceParser.t)("Keyword","builtin")],NullValue:[(0,_graphqlLanguageServiceParser.t)("Keyword","keyword")],ListValue:[(0,_graphqlLanguageServiceParser.p)("["),(0,_graphqlLanguageServiceParser.list)("Value",(0,_graphqlLanguageServiceParser.opt)((0,_graphqlLanguageServiceParser.p)(","))),(0,_graphqlLanguageServiceParser.p)("]")],ObjectValue:[(0,_graphqlLanguageServiceParser.p)("{"),(0,_graphqlLanguageServiceParser.list)("ObjectField",(0,_graphqlLanguageServiceParser.opt)((0,_graphqlLanguageServiceParser.p)(","))),(0,_graphqlLanguageServiceParser.p)("}")],ObjectField:[namedKey("attribute"),(0,_graphqlLanguageServiceParser.p)(":"),"Value"]}},{codemirror:65,"graphql-language-service-parser":80}],52:[function(require,module,exports){!function(mod){mod("object"==typeof exports&&"object"==typeof module?require("../../lib/codemirror"):CodeMirror)}(function(CodeMirror){"use strict";function firstNonWS(str){var found=str.search(nonWS);return-1==found?0:found}function probablyInsideString(cm,pos,line){return/\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line,0)))&&!/^[\'\"\`]/.test(line)}function getMode(cm,pos){var mode=cm.getMode();return!1!==mode.useInnerComments&&mode.innerMode?cm.getModeAt(pos):mode}var noOptions={},nonWS=/[^\s\u00a0]/,Pos=CodeMirror.Pos;CodeMirror.commands.toggleComment=function(cm){cm.toggleComment()},CodeMirror.defineExtension("toggleComment",function(options){options||(options=noOptions);for(var cm=this,minLine=1/0,ranges=this.listSelections(),mode=null,i=ranges.length-1;i>=0;i--){var from=ranges[i].from(),to=ranges[i].to();from.line>=minLine||(to.line>=minLine&&(to=Pos(minLine,0)),minLine=from.line,null==mode?cm.uncomment(from,to,options)?mode="un":(cm.lineComment(from,to,options),mode="line"):"un"==mode?cm.uncomment(from,to,options):cm.lineComment(from,to,options))}}),CodeMirror.defineExtension("lineComment",function(from,to,options){options||(options=noOptions);var self=this,mode=getMode(self,from),firstLine=self.getLine(from.line);if(null!=firstLine&&!probablyInsideString(self,from,firstLine)){var commentString=options.lineComment||mode.lineComment;if(commentString){var end=Math.min(0!=to.ch||to.line==from.line?to.line+1:to.line,self.lastLine()+1),pad=null==options.padding?" ":options.padding,blankLines=options.commentBlankLines||from.line==to.line;self.operation(function(){if(options.indent){for(var baseString=null,i=from.line;iwhitespace.length)&&(baseString=whitespace)}for(i=from.line;iend||self.operation(function(){if(0!=options.fullLines){var lastLineHasText=nonWS.test(self.getLine(end));self.replaceRange(pad+endString,Pos(end)),self.replaceRange(startString+pad,Pos(from.line,0));var lead=options.blockCommentLead||mode.blockCommentLead;if(null!=lead)for(var i=from.line+1;i<=end;++i)(i!=end||lastLineHasText)&&self.replaceRange(lead+pad,Pos(i,0))}else self.replaceRange(endString,to),self.replaceRange(startString,from)})}}else(options.lineComment||mode.lineComment)&&0!=options.fullLines&&self.lineComment(from,to,options)}),CodeMirror.defineExtension("uncomment",function(from,to,options){options||(options=noOptions);var didSomething,self=this,mode=getMode(self,from),end=Math.min(0!=to.ch||to.line==from.line?to.line:to.line-1,self.lastLine()),start=Math.min(from.line,end),lineString=options.lineComment||mode.lineComment,lines=[],pad=null==options.padding?" ":options.padding;lineComment:if(lineString){for(var i=start;i<=end;++i){var line=self.getLine(i),found=line.indexOf(lineString);if(found>-1&&!/comment/.test(self.getTokenTypeAt(Pos(i,found+1)))&&(found=-1),-1==found&&nonWS.test(line))break lineComment;if(found>-1&&nonWS.test(line.slice(0,found)))break lineComment;lines.push(line)}if(self.operation(function(){for(var i=start;i<=end;++i){var line=lines[i-start],pos=line.indexOf(lineString),endPos=pos+lineString.length;pos<0||(line.slice(endPos,endPos+pad.length)==pad&&(endPos+=pad.length),didSomething=!0,self.replaceRange("",Pos(i,pos),Pos(i,endPos)))}}),didSomething)return!0}var startString=options.blockCommentStart||mode.blockCommentStart,endString=options.blockCommentEnd||mode.blockCommentEnd;if(!startString||!endString)return!1;var lead=options.blockCommentLead||mode.blockCommentLead,startLine=self.getLine(start),open=startLine.indexOf(startString);if(-1==open)return!1;var endLine=end==start?startLine:self.getLine(end),close=endLine.indexOf(endString,end==start?open+startString.length:0);-1==close&&start!=end&&(endLine=self.getLine(--end),close=endLine.indexOf(endString));var insideStart=Pos(start,open+1),insideEnd=Pos(end,close+1);if(-1==close||!/comment/.test(self.getTokenTypeAt(insideStart))||!/comment/.test(self.getTokenTypeAt(insideEnd))||self.getRange(insideStart,insideEnd,"\n").indexOf(endString)>-1)return!1;var lastStart=startLine.lastIndexOf(startString,from.ch),firstEnd=-1==lastStart?-1:startLine.slice(0,from.ch).indexOf(endString,lastStart+startString.length);if(-1!=lastStart&&-1!=firstEnd&&firstEnd+endString.length!=from.ch)return!1;firstEnd=endLine.indexOf(endString,to.ch);var almostLastStart=endLine.slice(to.ch).lastIndexOf(startString,firstEnd-to.ch);return lastStart=-1==firstEnd||-1==almostLastStart?-1:to.ch+almostLastStart,(-1==firstEnd||-1==lastStart||lastStart==to.ch)&&(self.operation(function(){self.replaceRange("",Pos(end,close-(pad&&endLine.slice(close-pad.length,close)==pad?pad.length:0)),Pos(end,close+endString.length));var openEnd=open+startString.length;if(pad&&startLine.slice(openEnd,openEnd+pad.length)==pad&&(openEnd+=pad.length),self.replaceRange("",Pos(start,open),Pos(start,openEnd)),lead)for(var i=start+1;i<=end;++i){var line=self.getLine(i),found=line.indexOf(lead);if(-1!=found&&!nonWS.test(line.slice(0,found))){var foundEnd=found+lead.length;pad&&line.slice(foundEnd,foundEnd+pad.length)==pad&&(foundEnd+=pad.length),self.replaceRange("",Pos(i,found),Pos(i,foundEnd))}}}),!0)})})},{"../../lib/codemirror":65}],53:[function(require,module,exports){!function(mod){mod("object"==typeof exports&&"object"==typeof module?require("../../lib/codemirror"):CodeMirror)}(function(CodeMirror){function dialogDiv(cm,template,bottom){var dialog;return dialog=cm.getWrapperElement().appendChild(document.createElement("div")),dialog.className=bottom?"CodeMirror-dialog CodeMirror-dialog-bottom":"CodeMirror-dialog CodeMirror-dialog-top","string"==typeof template?dialog.innerHTML=template:dialog.appendChild(template),dialog}function closeNotification(cm,newVal){cm.state.currentNotificationClose&&cm.state.currentNotificationClose(),cm.state.currentNotificationClose=newVal}CodeMirror.defineExtension("openDialog",function(template,callback,options){function close(newVal){if("string"==typeof newVal)inp.value=newVal;else{if(closed)return;closed=!0,dialog.parentNode.removeChild(dialog),me.focus(),options.onClose&&options.onClose(dialog)}}options||(options={}),closeNotification(this,null);var button,dialog=dialogDiv(this,template,options.bottom),closed=!1,me=this,inp=dialog.getElementsByTagName("input")[0];return inp?(inp.focus(),options.value&&(inp.value=options.value,!1!==options.selectValueOnOpen&&inp.select()),options.onInput&&CodeMirror.on(inp,"input",function(e){options.onInput(e,inp.value,close)}),options.onKeyUp&&CodeMirror.on(inp,"keyup",function(e){options.onKeyUp(e,inp.value,close)}),CodeMirror.on(inp,"keydown",function(e){options&&options.onKeyDown&&options.onKeyDown(e,inp.value,close)||((27==e.keyCode||!1!==options.closeOnEnter&&13==e.keyCode)&&(inp.blur(),CodeMirror.e_stop(e),close()),13==e.keyCode&&callback(inp.value,e))}),!1!==options.closeOnBlur&&CodeMirror.on(inp,"blur",close)):(button=dialog.getElementsByTagName("button")[0])&&(CodeMirror.on(button,"click",function(){close(),me.focus()}),!1!==options.closeOnBlur&&CodeMirror.on(button,"blur",close),button.focus()),close}),CodeMirror.defineExtension("openConfirm",function(template,callbacks,options){function close(){closed||(closed=!0,dialog.parentNode.removeChild(dialog),me.focus())}closeNotification(this,null);var dialog=dialogDiv(this,template,options&&options.bottom),buttons=dialog.getElementsByTagName("button"),closed=!1,me=this,blurring=1;buttons[0].focus();for(var i=0;i0;return{anchor:new Pos(sel.anchor.line,sel.anchor.ch+(inverted?-1:1)),head:new Pos(sel.head.line,sel.head.ch+(inverted?1:-1))}}function handleChar(cm,ch){var conf=getConfig(cm);if(!conf||cm.getOption("disableInput"))return CodeMirror.Pass;var pairs=getOption(conf,"pairs"),pos=pairs.indexOf(ch);if(-1==pos)return CodeMirror.Pass;for(var type,triples=getOption(conf,"triples"),identical=pairs.charAt(pos+1)==ch,ranges=cm.listSelections(),opening=pos%2==0,i=0;i1&&triples.indexOf(ch)>=0&&cm.getRange(Pos(cur.line,cur.ch-2),cur)==ch+ch&&(cur.ch<=2||cm.getRange(Pos(cur.line,cur.ch-3),Pos(cur.line,cur.ch-2))!=ch))curType="addFour";else if(identical){if(CodeMirror.isWordChar(next)||!enteringString(cm,cur,ch))return CodeMirror.Pass;curType="both"}else{if(!opening||cm.getLine(cur.line).length!=cur.ch&&!isClosingBracket(next,pairs)&&!/\s/.test(next))return CodeMirror.Pass;curType="both"}else curType=identical&&stringStartsAfter(cm,cur)?"both":triples.indexOf(ch)>=0&&cm.getRange(cur,Pos(cur.line,cur.ch+3))==ch+ch+ch?"skipThree":"skip";if(type){if(type!=curType)return CodeMirror.Pass}else type=curType}var left=pos%2?pairs.charAt(pos-1):ch,right=pos%2?ch:pairs.charAt(pos+1);cm.operation(function(){if("skip"==type)cm.execCommand("goCharRight");else if("skipThree"==type)for(i=0;i<3;i++)cm.execCommand("goCharRight");else if("surround"==type){for(var sels=cm.getSelections(),i=0;i-1&&pos%2==1}function charsAround(cm,pos){var str=cm.getRange(Pos(pos.line,pos.ch-1),Pos(pos.line,pos.ch+1));return 2==str.length?str:null}function enteringString(cm,pos,ch){var line=cm.getLine(pos.line),token=cm.getTokenAt(pos);if(/\bstring2?\b/.test(token.type)||stringStartsAfter(cm,pos))return!1;var stream=new CodeMirror.StringStream(line.slice(0,pos.ch)+ch+line.slice(pos.ch),4);for(stream.pos=stream.start=token.start;;){var type1=cm.getMode().token(stream,token.state);if(stream.pos>=pos.ch+1)return/\bstring2?\b/.test(type1);stream.start=stream.pos}}function stringStartsAfter(cm,pos){var token=cm.getTokenAt(Pos(pos.line,pos.ch+1));return/\bstring/.test(token.type)&&token.start==pos.ch}var defaults={pairs:"()[]{}''\"\"",triples:"",explode:"[]{}"},Pos=CodeMirror.Pos;CodeMirror.defineOption("autoCloseBrackets",!1,function(cm,val,old){old&&old!=CodeMirror.Init&&(cm.removeKeyMap(keyMap),cm.state.closeBrackets=null),val&&(cm.state.closeBrackets=val,cm.addKeyMap(keyMap))});for(var bind=defaults.pairs+"`",keyMap={Backspace:function(cm){var conf=getConfig(cm);if(!conf||cm.getOption("disableInput"))return CodeMirror.Pass;for(var pairs=getOption(conf,"pairs"),ranges=cm.listSelections(),i=0;i=0;i--){var cur=ranges[i].head;cm.replaceRange("",Pos(cur.line,cur.ch-1),Pos(cur.line,cur.ch+1),"+delete")}},Enter:function(cm){var conf=getConfig(cm),explode=conf&&getOption(conf,"explode");if(!explode||cm.getOption("disableInput"))return CodeMirror.Pass;for(var ranges=cm.listSelections(),i=0;i=0&&matching[line.text.charAt(pos)]||matching[line.text.charAt(++pos)];if(!match)return null;var dir=">"==match.charAt(1)?1:-1;if(config&&config.strict&&dir>0!=(pos==where.ch))return null;var style=cm.getTokenTypeAt(Pos(where.line,pos+1)),found=scanForBracket(cm,Pos(where.line,pos+(dir>0?1:0)),dir,style||null,config);return null==found?null:{from:Pos(where.line,pos),to:found&&found.pos,match:found&&found.ch==match.charAt(0),forward:dir>0}}function scanForBracket(cm,where,dir,style,config){for(var maxScanLen=config&&config.maxScanLineLength||1e4,maxScanLines=config&&config.maxScanLines||1e3,stack=[],re=config&&config.bracketRegex?config.bracketRegex:/[(){}[\]]/,lineEnd=dir>0?Math.min(where.line+maxScanLines,cm.lastLine()+1):Math.max(cm.firstLine()-1,where.line-maxScanLines),lineNo=where.line;lineNo!=lineEnd;lineNo+=dir){var line=cm.getLine(lineNo);if(line){var pos=dir>0?0:line.length-1,end=dir>0?line.length:-1;if(!(line.length>maxScanLen))for(lineNo==where.line&&(pos=where.ch-(dir<0?1:0));pos!=end;pos+=dir){var ch=line.charAt(pos);if(re.test(ch)&&(void 0===style||cm.getTokenTypeAt(Pos(lineNo,pos+1))==style))if(">"==matching[ch].charAt(1)==dir>0)stack.push(ch);else{if(!stack.length)return{pos:Pos(lineNo,pos),ch:ch};stack.pop()}}}}return lineNo-dir!=(dir>0?cm.lastLine():cm.firstLine())&&null}function matchBrackets(cm,autoclear,config){for(var maxHighlightLen=cm.state.matchBrackets.maxHighlightLineLength||1e3,marks=[],ranges=cm.listSelections(),i=0;i",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<"},currentlyHighlighted=null;CodeMirror.defineOption("matchBrackets",!1,function(cm,val,old){old&&old!=CodeMirror.Init&&(cm.off("cursorActivity",doMatchBrackets),currentlyHighlighted&&(currentlyHighlighted(),currentlyHighlighted=null)),val&&(cm.state.matchBrackets="object"==typeof val?val:{},cm.on("cursorActivity",doMatchBrackets))}),CodeMirror.defineExtension("matchBrackets",function(){matchBrackets(this,!0)}),CodeMirror.defineExtension("findMatchingBracket",function(pos,config,oldConfig){return(oldConfig||"boolean"==typeof config)&&(oldConfig?(oldConfig.strict=config,config=oldConfig):config=config?{strict:!0}:null),findMatchingBracket(this,pos,config)}),CodeMirror.defineExtension("scanForBracket",function(pos,dir,style,config){return scanForBracket(this,pos,dir,style,config)})})},{"../../lib/codemirror":65}],56:[function(require,module,exports){!function(mod){mod("object"==typeof exports&&"object"==typeof module?require("../../lib/codemirror"):CodeMirror)}(function(CodeMirror){"use strict";CodeMirror.registerHelper("fold","brace",function(cm,start){function findOpening(openCh){for(var at=start.ch,pass=0;;){var found=at<=0?-1:lineText.lastIndexOf(openCh,at-1);if(-1!=found){if(1==pass&&foundcm.lastLine())return null;var start=cm.getTokenAt(CodeMirror.Pos(line,1));if(/\S/.test(start.string)||(start=cm.getTokenAt(CodeMirror.Pos(line,start.end+1))),"keyword"!=start.type||"import"!=start.string)return null;for(var i=line,e=Math.min(cm.lastLine(),line+10);i<=e;++i){var semi=cm.getLine(i).indexOf(";");if(-1!=semi)return{startCh:start.end,end:CodeMirror.Pos(i,semi)}}}var prev,startLine=start.line,has=hasImport(startLine);if(!has||hasImport(startLine-1)||(prev=hasImport(startLine-2))&&prev.end.line==startLine-1)return null;for(var end=has.end;;){var next=hasImport(end.line+1);if(null==next)break;end=next.end}return{from:cm.clipPos(CodeMirror.Pos(startLine,has.startCh+1)),to:end}}),CodeMirror.registerHelper("fold","include",function(cm,start){function hasInclude(line){if(linecm.lastLine())return null;var start=cm.getTokenAt(CodeMirror.Pos(line,1));return/\S/.test(start.string)||(start=cm.getTokenAt(CodeMirror.Pos(line,start.end+1))),"meta"==start.type&&"#include"==start.string.slice(0,8)?start.start+8:void 0}var startLine=start.line,has=hasInclude(startLine);if(null==has||null!=hasInclude(startLine-1))return null;for(var end=startLine;null!=hasInclude(end+1);)++end;return{from:CodeMirror.Pos(startLine,has+1),to:cm.clipPos(CodeMirror.Pos(end))}})})},{"../../lib/codemirror":65}],57:[function(require,module,exports){!function(mod){mod("object"==typeof exports&&"object"==typeof module?require("../../lib/codemirror"):CodeMirror)}(function(CodeMirror){"use strict";function doFold(cm,pos,options,force){function getRange(allowFolded){var range=finder(cm,pos);if(!range||range.to.line-range.from.linecm.firstLine();)pos=CodeMirror.Pos(pos.line-1,0),range=getRange(!1);if(range&&!range.cleared&&"unfold"!==force){var myWidget=makeWidget(cm,options);CodeMirror.on(myWidget,"mousedown",function(e){myRange.clear(),CodeMirror.e_preventDefault(e)});var myRange=cm.markText(range.from,range.to,{replacedWith:myWidget,clearOnEnter:getOption(cm,options,"clearOnEnter"),__isFold:!0});myRange.on("clear",function(from,to){CodeMirror.signal(cm,"unfold",cm,from,to)}),CodeMirror.signal(cm,"fold",cm,range.from,range.to)}}function makeWidget(cm,options){var widget=getOption(cm,options,"widget");if("string"==typeof widget){var text=document.createTextNode(widget);(widget=document.createElement("span")).appendChild(text),widget.className="CodeMirror-foldmarker"}return widget}function getOption(cm,options,name){if(options&&void 0!==options[name])return options[name];var editorOptions=cm.options.foldOptions;return editorOptions&&void 0!==editorOptions[name]?editorOptions[name]:defaultOptions[name]}CodeMirror.newFoldFunction=function(rangeFinder,widget){return function(cm,pos){doFold(cm,pos,{rangeFinder:rangeFinder,widget:widget})}},CodeMirror.defineExtension("foldCode",function(pos,options,force){doFold(this,pos,options,force)}),CodeMirror.defineExtension("isFolded",function(pos){for(var marks=this.findMarksAt(pos),i=0;i=minSize&&(mark=marker(opts.indicatorOpen))}cm.setGutterMarker(line,opts.gutter,mark),++cur})}function updateInViewport(cm){var vp=cm.getViewport(),state=cm.state.foldGutter;state&&(cm.operation(function(){updateFoldInfo(cm,vp.from,vp.to)}),state.from=vp.from,state.to=vp.to)}function onGutterClick(cm,line,gutter){var state=cm.state.foldGutter;if(state){var opts=state.options;if(gutter==opts.gutter){var folded=isFolded(cm,line);folded?folded.clear():cm.foldCode(Pos(line,0),opts.rangeFinder)}}}function onChange(cm){var state=cm.state.foldGutter;if(state){var opts=state.options;state.from=state.to=0,clearTimeout(state.changeUpdate),state.changeUpdate=setTimeout(function(){updateInViewport(cm)},opts.foldOnChangeTimeSpan||600)}}function onViewportChange(cm){var state=cm.state.foldGutter;if(state){var opts=state.options;clearTimeout(state.changeUpdate),state.changeUpdate=setTimeout(function(){var vp=cm.getViewport();state.from==state.to||vp.from-state.to>20||state.from-vp.to>20?updateInViewport(cm):cm.operation(function(){vp.fromstate.to&&(updateFoldInfo(cm,state.to,vp.to),state.to=vp.to)})},opts.updateViewportTimeSpan||400)}}function onFold(cm,from){var state=cm.state.foldGutter;if(state){var line=from.line;line>=state.from&&line0&&old.to.ch-old.from.ch!=nw.to.ch-nw.from.ch}function parseOptions(cm,pos,options){var editor=cm.options.hintOptions,out={};for(var prop in defaultOptions)out[prop]=defaultOptions[prop];if(editor)for(var prop in editor)void 0!==editor[prop]&&(out[prop]=editor[prop]);if(options)for(var prop in options)void 0!==options[prop]&&(out[prop]=options[prop]);return out.hint.resolve&&(out.hint=out.hint.resolve(cm,pos)),out}function getText(completion){return"string"==typeof completion?completion:completion.text}function buildKeyMap(completion,handle){function addBinding(key,val){var bound;bound="string"!=typeof val?function(cm){return val(cm,handle)}:baseMap.hasOwnProperty(val)?baseMap[val]:val,ourMap[key]=bound}var baseMap={Up:function(){handle.moveFocus(-1)},Down:function(){handle.moveFocus(1)},PageUp:function(){handle.moveFocus(1-handle.menuSize(),!0)},PageDown:function(){handle.moveFocus(handle.menuSize()-1,!0)},Home:function(){handle.setFocus(0)},End:function(){handle.setFocus(handle.length-1)},Enter:handle.pick,Tab:handle.pick,Esc:handle.close},custom=completion.options.customKeys,ourMap=custom?{}:baseMap;if(custom)for(var key in custom)custom.hasOwnProperty(key)&&addBinding(key,custom[key]);var extra=completion.options.extraKeys;if(extra)for(var key in extra)extra.hasOwnProperty(key)&&addBinding(key,extra[key]);return ourMap}function getHintElement(hintsElement,el){for(;el&&el!=hintsElement;){if("LI"===el.nodeName.toUpperCase()&&el.parentNode==hintsElement)return el;el=el.parentNode}}function Widget(completion,data){this.completion=completion,this.data=data,this.picked=!1;var widget=this,cm=completion.cm,hints=this.hints=document.createElement("ul");hints.className="CodeMirror-hints",this.selectedHint=data.selectedHint||0;for(var completions=data.list,i=0;ihints.clientHeight+1,startScroll=cm.getScrollInfo();if(overlapY>0){var height=box.bottom-box.top;if(pos.top-(pos.bottom-box.top)-height>0)hints.style.top=(top=pos.top-height)+"px",below=!1;else if(height>winH){hints.style.height=winH-5+"px",hints.style.top=(top=pos.bottom-box.top)+"px";var cursor=cm.getCursor();data.from.ch!=cursor.ch&&(pos=cm.cursorCoords(cursor),hints.style.left=(left=pos.left)+"px",box=hints.getBoundingClientRect())}}var overlapX=box.right-winW;if(overlapX>0&&(box.right-box.left>winW&&(hints.style.width=winW-5+"px",overlapX-=box.right-box.left-winW),hints.style.left=(left=pos.left-overlapX)+"px"),scrolls)for(var node=hints.firstChild;node;node=node.nextSibling)node.style.paddingRight=cm.display.nativeBarWidth+"px";if(cm.addKeyMap(this.keyMap=buildKeyMap(completion,{moveFocus:function(n,avoidWrap){widget.changeActive(widget.selectedHint+n,avoidWrap)},setFocus:function(n){widget.changeActive(n)},menuSize:function(){return widget.screenAmount()},length:completions.length,close:function(){completion.close()},pick:function(){widget.pick()},data:data})),completion.options.closeOnUnfocus){var closingOnBlur;cm.on("blur",this.onBlur=function(){closingOnBlur=setTimeout(function(){completion.close()},100)}),cm.on("focus",this.onFocus=function(){clearTimeout(closingOnBlur)})}return cm.on("scroll",this.onScroll=function(){var curScroll=cm.getScrollInfo(),editor=cm.getWrapperElement().getBoundingClientRect(),newTop=top+startScroll.top-curScroll.top,point=newTop-(window.pageYOffset||(document.documentElement||document.body).scrollTop);if(below||(point+=hints.offsetHeight),point<=editor.top||point>=editor.bottom)return completion.close();hints.style.top=newTop+"px",hints.style.left=left+startScroll.left-curScroll.left+"px"}),CodeMirror.on(hints,"dblclick",function(e){var t=getHintElement(hints,e.target||e.srcElement);t&&null!=t.hintId&&(widget.changeActive(t.hintId),widget.pick())}),CodeMirror.on(hints,"click",function(e){var t=getHintElement(hints,e.target||e.srcElement);t&&null!=t.hintId&&(widget.changeActive(t.hintId),completion.options.completeOnSingleClick&&widget.pick())}),CodeMirror.on(hints,"mousedown",function(){setTimeout(function(){cm.focus()},20)}),CodeMirror.signal(data,"select",completions[0],hints.firstChild),!0}function applicableHelpers(cm,helpers){if(!cm.somethingSelected())return helpers;for(var result=[],i=0;i1)){if(this.somethingSelected()){if(!options.hint.supportsSelection)return;for(var i=0;i=this.data.list.length?i=avoidWrap?this.data.list.length-1:0:i<0&&(i=avoidWrap?0:this.data.list.length-1),this.selectedHint!=i){var node=this.hints.childNodes[this.selectedHint];node.className=node.className.replace(" "+ACTIVE_HINT_ELEMENT_CLASS,""),(node=this.hints.childNodes[this.selectedHint=i]).className+=" "+ACTIVE_HINT_ELEMENT_CLASS,node.offsetTopthis.hints.scrollTop+this.hints.clientHeight&&(this.hints.scrollTop=node.offsetTop+node.offsetHeight-this.hints.clientHeight+3),CodeMirror.signal(this.data,"select",this.data.list[this.selectedHint],node)}},screenAmount:function(){return Math.floor(this.hints.clientHeight/this.hints.firstChild.offsetHeight)||1}},CodeMirror.registerHelper("hint","auto",{resolve:function(cm,pos){var words,helpers=cm.getHelpers(pos,"hint");if(helpers.length){var resolved=function(cm,callback,options){function run(i){if(i==app.length)return callback(null);fetchHints(app[i],cm,options,function(result){result&&result.list.length>0?callback(result):run(i+1)})}var app=applicableHelpers(cm,helpers);run(0)};return resolved.async=!0,resolved.supportsSelection=!0,resolved}return(words=cm.getHelper(cm.getCursor(),"hintWords"))?function(cm){return CodeMirror.hint.fromList(cm,{words:words})}:CodeMirror.hint.anyword?function(cm,options){return CodeMirror.hint.anyword(cm,options)}:function(){}}}),CodeMirror.registerHelper("hint","fromList",function(cm,options){var cur=cm.getCursor(),token=cm.getTokenAt(cur),to=CodeMirror.Pos(cur.line,token.end);if(token.string&&/\w/.test(token.string[token.string.length-1]))var term=token.string,from=CodeMirror.Pos(cur.line,token.start);else var term="",from=to;for(var found=[],i=0;i,]/,closeOnUnfocus:!0,completeOnSingleClick:!0,container:null,customKeys:null,extraKeys:null};CodeMirror.defineOption("hintOptions",null)})},{"../../lib/codemirror":65}],60:[function(require,module,exports){!function(mod){mod("object"==typeof exports&&"object"==typeof module?require("../../lib/codemirror"):CodeMirror)}(function(CodeMirror){"use strict";function showTooltip(e,content){function position(e){if(!tt.parentNode)return CodeMirror.off(document,"mousemove",position);tt.style.top=Math.max(0,e.clientY-tt.offsetHeight-5)+"px",tt.style.left=e.clientX+5+"px"}var tt=document.createElement("div");return tt.className="CodeMirror-lint-tooltip",tt.appendChild(content.cloneNode(!0)),document.body.appendChild(tt),CodeMirror.on(document,"mousemove",position),position(e),null!=tt.style.opacity&&(tt.style.opacity=1),tt}function rm(elt){elt.parentNode&&elt.parentNode.removeChild(elt)}function hideTooltip(tt){tt.parentNode&&(null==tt.style.opacity&&rm(tt),tt.style.opacity=0,setTimeout(function(){rm(tt)},600))}function showTooltipFor(e,content,node){function hide(){CodeMirror.off(node,"mouseout",hide),tooltip&&(hideTooltip(tooltip),tooltip=null)}var tooltip=showTooltip(e,content),poll=setInterval(function(){if(tooltip)for(var n=node;;n=n.parentNode){if(n&&11==n.nodeType&&(n=n.host),n==document.body)return;if(!n){hide();break}}if(!tooltip)return clearInterval(poll)},400);CodeMirror.on(node,"mouseout",hide)}function LintState(cm,options,hasGutter){this.marked=[],this.options=options,this.timeout=null,this.hasGutter=hasGutter,this.onMouseOver=function(e){onMouseOver(cm,e)},this.waitingFor=0}function parseOptions(_cm,options){return options instanceof Function?{getAnnotations:options}:(options&&!0!==options||(options={}),options)}function clearMarks(cm){var state=cm.state.lint;state.hasGutter&&cm.clearGutter(GUTTER_ID);for(var i=0;i1,state.options.tooltips))}}options.onUpdateLinting&&options.onUpdateLinting(annotationsNotSorted,annotations,cm)}function onChange(cm){var state=cm.state.lint;state&&(clearTimeout(state.timeout),state.timeout=setTimeout(function(){startLinting(cm)},state.options.delay||500))}function popupTooltips(annotations,e){for(var target=e.target||e.srcElement,tooltip=document.createDocumentFragment(),i=0;i (Use line:column or scroll% syntax)',"Jump to line:",cur.line+1+":"+cur.ch,function(posStr){if(posStr){var match;if(match=/^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(posStr))cm.setCursor(interpretLine(cm,match[1]),Number(match[2]));else if(match=/^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(posStr)){var line=Math.round(cm.lineCount()*Number(match[1])/100);/^[-+]/.test(match[1])&&(line=cur.line+line+1),cm.setCursor(line-1,cur.ch)}else(match=/^\s*\:?\s*([\+\-]?\d+)\s*/.exec(posStr))&&cm.setCursor(interpretLine(cm,match[1]),cur.ch)}})},CodeMirror.keyMap.default["Alt-G"]="jumpToLine"})},{"../../lib/codemirror":65,"../dialog/dialog":53}],62:[function(require,module,exports){!function(mod){"object"==typeof exports&&"object"==typeof module?mod(require("../../lib/codemirror"),require("./searchcursor"),require("../dialog/dialog")):mod(CodeMirror)}(function(CodeMirror){"use strict";function searchOverlay(query,caseInsensitive){return"string"==typeof query?query=new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&"),caseInsensitive?"gi":"g"):query.global||(query=new RegExp(query.source,query.ignoreCase?"gi":"g")),{token:function(stream){query.lastIndex=stream.pos;var match=query.exec(stream.string);if(match&&match.index==stream.pos)return stream.pos+=match[0].length||1,"searching";match?stream.pos=match.index:stream.skipToEnd()}}}function SearchState(){this.posFrom=this.posTo=this.lastQuery=this.query=null,this.overlay=null}function getSearchState(cm){return cm.state.search||(cm.state.search=new SearchState)}function queryCaseInsensitive(query){return"string"==typeof query&&query==query.toLowerCase()}function getSearchCursor(cm,query,pos){return cm.getSearchCursor(query,pos,{caseFold:queryCaseInsensitive(query),multiline:!0})}function persistentDialog(cm,text,deflt,onEnter,onKeyDown){cm.openDialog(text,onEnter,{value:deflt,selectValueOnOpen:!0,closeOnEnter:!1,onClose:function(){clearSearch(cm)},onKeyDown:onKeyDown})}function dialog(cm,text,shortText,deflt,f){cm.openDialog?cm.openDialog(text,f,{value:deflt,selectValueOnOpen:!0}):f(prompt(shortText,deflt))}function confirmDialog(cm,text,shortText,fs){cm.openConfirm?cm.openConfirm(text,fs):confirm(shortText)&&fs[0]()}function parseString(string){return string.replace(/\\(.)/g,function(_,ch){return"n"==ch?"\n":"r"==ch?"\r":ch})}function parseQuery(query){var isRE=query.match(/^\/(.*)\/([a-z]*)$/);if(isRE)try{query=new RegExp(isRE[1],-1==isRE[2].indexOf("i")?"":"i")}catch(e){}else query=parseString(query);return("string"==typeof query?""==query:query.test(""))&&(query=/x^/),query}function startSearch(cm,state,query){state.queryText=query,state.query=parseQuery(query),cm.removeOverlay(state.overlay,queryCaseInsensitive(state.query)),state.overlay=searchOverlay(state.query,queryCaseInsensitive(state.query)),cm.addOverlay(state.overlay),cm.showMatchesOnScrollbar&&(state.annotate&&(state.annotate.clear(),state.annotate=null),state.annotate=cm.showMatchesOnScrollbar(state.query,queryCaseInsensitive(state.query)))}function doSearch(cm,rev,persistent,immediate){var state=getSearchState(cm);if(state.query)return findNext(cm,rev);var q=cm.getSelection()||state.lastQuery;if(persistent&&cm.openDialog){var hiding=null,searchNext=function(query,event){CodeMirror.e_stop(event),query&&(query!=state.queryText&&(startSearch(cm,state,query),state.posFrom=state.posTo=cm.getCursor()),hiding&&(hiding.style.opacity=1),findNext(cm,event.shiftKey,function(_,to){var dialog;to.line<3&&document.querySelector&&(dialog=cm.display.wrapper.querySelector(".CodeMirror-dialog"))&&dialog.getBoundingClientRect().bottom-4>cm.cursorCoords(to,"window").top&&((hiding=dialog).style.opacity=.4)}))};persistentDialog(cm,queryDialog,q,searchNext,function(event,query){var keyName=CodeMirror.keyName(event),cmd=CodeMirror.keyMap[cm.getOption("keyMap")][keyName];cmd||(cmd=cm.getOption("extraKeys")[keyName]),"findNext"==cmd||"findPrev"==cmd||"findPersistentNext"==cmd||"findPersistentPrev"==cmd?(CodeMirror.e_stop(event),startSearch(cm,getSearchState(cm),query),cm.execCommand(cmd)):"find"!=cmd&&"findPersistent"!=cmd||(CodeMirror.e_stop(event),searchNext(query,event))}),immediate&&q&&(startSearch(cm,state,q),findNext(cm,rev))}else dialog(cm,queryDialog,"Search for:",q,function(query){query&&!state.query&&cm.operation(function(){startSearch(cm,state,query),state.posFrom=state.posTo=cm.getCursor(),findNext(cm,rev)})})}function findNext(cm,rev,callback){cm.operation(function(){var state=getSearchState(cm),cursor=getSearchCursor(cm,state.query,rev?state.posFrom:state.posTo);(cursor.find(rev)||(cursor=getSearchCursor(cm,state.query,rev?CodeMirror.Pos(cm.lastLine()):CodeMirror.Pos(cm.firstLine(),0))).find(rev))&&(cm.setSelection(cursor.from(),cursor.to()),cm.scrollIntoView({from:cursor.from(),to:cursor.to()},20),state.posFrom=cursor.from(),state.posTo=cursor.to(),callback&&callback(cursor.from(),cursor.to()))})}function clearSearch(cm){cm.operation(function(){var state=getSearchState(cm);state.lastQuery=state.query,state.query&&(state.query=state.queryText=null,cm.removeOverlay(state.overlay),state.annotate&&(state.annotate.clear(),state.annotate=null))})}function replaceAll(cm,query,text){cm.operation(function(){for(var cursor=getSearchCursor(cm,query);cursor.findNext();)if("string"!=typeof query){var match=cm.getRange(cursor.from(),cursor.to()).match(query);cursor.replace(text.replace(/\$(\d)/g,function(_,i){return match[i]}))}else cursor.replace(text)})}function replace(cm,all){if(!cm.getOption("readOnly")){var query=cm.getSelection()||getSearchState(cm).lastQuery,dialogText=''+(all?"Replace all:":"Replace:")+"";dialog(cm,dialogText+replaceQueryDialog,dialogText,query,function(query){query&&(query=parseQuery(query),dialog(cm,replacementQueryDialog,"Replace with:","",function(text){if(text=parseString(text),all)replaceAll(cm,query,text);else{clearSearch(cm);var cursor=getSearchCursor(cm,query,cm.getCursor("from")),advance=function(){var match,start=cursor.from();!(match=cursor.findNext())&&(cursor=getSearchCursor(cm,query),!(match=cursor.findNext())||start&&cursor.from().line==start.line&&cursor.from().ch==start.ch)||(cm.setSelection(cursor.from(),cursor.to()),cm.scrollIntoView({from:cursor.from(),to:cursor.to()}),confirmDialog(cm,doReplaceConfirm,"Replace?",[function(){doReplace(match)},advance,function(){replaceAll(cm,query,text)}]))},doReplace=function(match){cursor.replace("string"==typeof query?text:text.replace(/\$(\d)/g,function(_,i){return match[i]})),advance()};advance()}}))})}}var queryDialog='Search: (Use /re/ syntax for regexp search)',replaceQueryDialog=' (Use /re/ syntax for regexp search)',replacementQueryDialog='With: ',doReplaceConfirm='Replace? ';CodeMirror.commands.find=function(cm){clearSearch(cm),doSearch(cm)},CodeMirror.commands.findPersistent=function(cm){clearSearch(cm),doSearch(cm,!1,!0)},CodeMirror.commands.findPersistentNext=function(cm){doSearch(cm,!1,!0,!0)},CodeMirror.commands.findPersistentPrev=function(cm){doSearch(cm,!0,!0,!0)},CodeMirror.commands.findNext=doSearch,CodeMirror.commands.findPrev=function(cm){doSearch(cm,!0)},CodeMirror.commands.clearSearch=clearSearch,CodeMirror.commands.replace=replace,CodeMirror.commands.replaceAll=function(cm){replace(cm,!0)}})},{"../../lib/codemirror":65,"../dialog/dialog":53,"./searchcursor":63}],63:[function(require,module,exports){!function(mod){mod("object"==typeof exports&&"object"==typeof module?require("../../lib/codemirror"):CodeMirror)}(function(CodeMirror){"use strict";function regexpFlags(regexp){var flags=regexp.flags;return null!=flags?flags:(regexp.ignoreCase?"i":"")+(regexp.global?"g":"")+(regexp.multiline?"m":"")}function ensureGlobal(regexp){return regexp.global?regexp:new RegExp(regexp.source,regexpFlags(regexp)+"g")}function maybeMultiline(regexp){return/\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)}function searchRegexpForward(doc,regexp,start){regexp=ensureGlobal(regexp);for(var line=start.line,ch=start.ch,last=doc.lastLine();line<=last;line++,ch=0){regexp.lastIndex=ch;var string=doc.getLine(line),match=regexp.exec(string);if(match)return{from:Pos(line,match.index),to:Pos(line,match.index+match[0].length),match:match}}}function searchRegexpForwardMultiline(doc,regexp,start){if(!maybeMultiline(regexp))return searchRegexpForward(doc,regexp,start);regexp=ensureGlobal(regexp);for(var string,chunk=1,line=start.line,last=doc.lastLine();line<=last;){for(var i=0;i=first;line--,ch=-1){var string=doc.getLine(line);ch>-1&&(string=string.slice(0,ch));var match=lastMatchIn(string,regexp);if(match)return{from:Pos(line,match.index),to:Pos(line,match.index+match[0].length),match:match}}}function searchRegexpBackwardMultiline(doc,regexp,start){regexp=ensureGlobal(regexp);for(var string,chunk=1,line=start.line,first=doc.firstLine();line>=first;){for(var i=0;ipos))return pos1;--pos1}}}function searchStringForward(doc,query,start,caseFold){if(!query.length)return null;var fold=caseFold?doFold:noFold,lines=fold(query).split(/\r|\n\r?/);search:for(var line=start.line,ch=start.ch,last=doc.lastLine()+1-lines.length;line<=last;line++,ch=0){var orig=doc.getLine(line).slice(ch),string=fold(orig);if(1==lines.length){var found=string.indexOf(lines[0]);if(-1==found)continue search;var start=adjustPos(orig,string,found)+ch;return{from:Pos(line,adjustPos(orig,string,found)+ch),to:Pos(line,adjustPos(orig,string,found+lines[0].length)+ch)}}var cutFrom=string.length-lines[0].length;if(string.slice(cutFrom)==lines[0]){for(var i=1;i=first;line--,ch=-1){var orig=doc.getLine(line);ch>-1&&(orig=orig.slice(0,ch));var string=fold(orig);if(1==lines.length){var found=string.lastIndexOf(lines[0]);if(-1==found)continue search;return{from:Pos(line,adjustPos(orig,string,found)),to:Pos(line,adjustPos(orig,string,found+lines[0].length))}}var lastLine=lines[lines.length-1];if(string.slice(0,lastLine.length)==lastLine){for(var i=1,start=line-lines.length+1;i0);)ranges.push({anchor:cur.from(),head:cur.to()});ranges.length&&this.setSelections(ranges,0)})})},{"../../lib/codemirror":65}],64:[function(require,module,exports){!function(mod){"object"==typeof exports&&"object"==typeof module?mod(require("../lib/codemirror"),require("../addon/search/searchcursor"),require("../addon/edit/matchbrackets")):mod(CodeMirror)}(function(CodeMirror){"use strict";function findPosSubword(doc,start,dir){if(dir<0&&0==start.ch)return doc.clipPos(Pos(start.line-1));var line=doc.getLine(start.line);if(dir>0&&start.ch>=line.length)return doc.clipPos(Pos(start.line+1,0));for(var type,state="start",pos=start.ch,e=dir<0?0:line.length,i=0;pos!=e;pos+=dir,i++){var next=line.charAt(dir<0?pos-1:pos),cat="_"!=next&&CodeMirror.isWordChar(next)?"w":"o";if("w"==cat&&next.toUpperCase()==next&&(cat="W"),"start"==state)"o"!=cat&&(state="in",type=cat);else if("in"==state&&type!=cat){if("w"==type&&"W"==cat&&dir<0&&pos--,"W"==type&&"w"==cat&&dir>0){type="w";continue}break}}return Pos(start.line,pos)}function moveSubword(cm,dir){cm.extendSelectionsBy(function(range){return cm.display.shift||cm.doc.extend||range.empty()?findPosSubword(cm.doc,range.head,dir):dir<0?range.from():range.to()})}function insertLine(cm,above){if(cm.isReadOnly())return CodeMirror.Pass;cm.operation(function(){for(var len=cm.listSelections().length,newSelection=[],last=-1,i=0;i=0;i--){var range=ranges[indices[i]];if(!(at&&CodeMirror.cmpPos(range.head,at)>0)){var word=wordAt(cm,range.head);at=word.from,cm.replaceRange(mod(word.word),word.from,word.to)}}})}function getTarget(cm){var from=cm.getCursor("from"),to=cm.getCursor("to");if(0==CodeMirror.cmpPos(from,to)){var word=wordAt(cm,from);if(!word.word)return;from=word.from,to=word.to}return{from:from,to:to,query:cm.getRange(from,to),word:word}}function findAndGoTo(cm,forward){var target=getTarget(cm);if(target){var query=target.query,cur=cm.getSearchCursor(query,forward?target.to:target.from);(forward?cur.findNext():cur.findPrevious())?cm.setSelection(cur.from(),cur.to()):(cur=cm.getSearchCursor(query,forward?Pos(cm.firstLine(),0):cm.clipPos(Pos(cm.lastLine()))),(forward?cur.findNext():cur.findPrevious())?cm.setSelection(cur.from(),cur.to()):target.word&&cm.setSelection(target.from,target.to))}}var map=CodeMirror.keyMap.sublime={fallthrough:"default"},cmds=CodeMirror.commands,Pos=CodeMirror.Pos,mac=CodeMirror.keyMap.default==CodeMirror.keyMap.macDefault,ctrl=mac?"Cmd-":"Ctrl-",goSubwordCombo=mac?"Ctrl-":"Alt-";cmds[map[goSubwordCombo+"Left"]="goSubwordLeft"]=function(cm){moveSubword(cm,-1)},cmds[map[goSubwordCombo+"Right"]="goSubwordRight"]=function(cm){moveSubword(cm,1)},mac&&(map["Cmd-Left"]="goLineStartSmart");var scrollLineCombo=mac?"Ctrl-Alt-":"Ctrl-";cmds[map[scrollLineCombo+"Up"]="scrollLineUp"]=function(cm){var info=cm.getScrollInfo();if(!cm.somethingSelected()){var visibleBottomLine=cm.lineAtHeight(info.top+info.clientHeight,"local");cm.getCursor().line>=visibleBottomLine&&cm.execCommand("goLineUp")}cm.scrollTo(null,info.top-cm.defaultTextHeight())},cmds[map[scrollLineCombo+"Down"]="scrollLineDown"]=function(cm){var info=cm.getScrollInfo();if(!cm.somethingSelected()){var visibleTopLine=cm.lineAtHeight(info.top,"local")+1;cm.getCursor().line<=visibleTopLine&&cm.execCommand("goLineDown")}cm.scrollTo(null,info.top+cm.defaultTextHeight())},cmds[map["Shift-"+ctrl+"L"]="splitSelectionByLine"]=function(cm){for(var ranges=cm.listSelections(),lineRanges=[],i=0;ifrom.line&&line==to.line&&0==to.ch||lineRanges.push({anchor:line==from.line?from:Pos(line,0),head:line==to.line?to:Pos(line)});cm.setSelections(lineRanges,0)},map["Shift-Tab"]="indentLess",cmds[map.Esc="singleSelectionTop"]=function(cm){var range=cm.listSelections()[0];cm.setSelection(range.anchor,range.head,{scroll:!1})},cmds[map[ctrl+"L"]="selectLine"]=function(cm){for(var ranges=cm.listSelections(),extended=[],i=0;iat?linesToMove.push(from,to):linesToMove.length&&(linesToMove[linesToMove.length-1]=to),at=to}cm.operation(function(){for(var i=0;icm.lastLine()?cm.replaceRange("\n"+line,Pos(cm.lastLine()),null,"+swapLine"):cm.replaceRange(line+"\n",Pos(to,0),null,"+swapLine")}cm.setSelections(newSels),cm.scrollIntoView()})},cmds[map[swapLineCombo+"Down"]="swapLineDown"]=function(cm){if(cm.isReadOnly())return CodeMirror.Pass;for(var ranges=cm.listSelections(),linesToMove=[],at=cm.lastLine()+1,i=ranges.length-1;i>=0;i--){var range=ranges[i],from=range.to().line+1,to=range.from().line;0!=range.to().ch||range.empty()||from--,from=0;i-=2){var from=linesToMove[i],to=linesToMove[i+1],line=cm.getLine(from);from==cm.lastLine()?cm.replaceRange("",Pos(from-1),Pos(from),"+swapLine"):cm.replaceRange("",Pos(from,0),Pos(from+1,0),"+swapLine"),cm.replaceRange(line+"\n",Pos(to,0),null,"+swapLine")}cm.scrollIntoView()})},cmds[map[ctrl+"/"]="toggleCommentIndented"]=function(cm){cm.toggleComment({indent:!0})},cmds[map[ctrl+"J"]="joinLines"]=function(cm){for(var ranges=cm.listSelections(),joined=[],i=0;i=0;i--){var cursor=cursors[i].head,toStartOfLine=cm.getRange({line:cursor.line,ch:0},cursor),column=CodeMirror.countColumn(toStartOfLine,null,cm.getOption("tabSize")),deletePos=cm.findPosH(cursor,-1,"char",!1);if(toStartOfLine&&!/\S/.test(toStartOfLine)&&column%indentUnit==0){var prevIndent=new Pos(cursor.line,CodeMirror.findColumn(toStartOfLine,column-indentUnit,indentUnit));prevIndent.ch!=cursor.ch&&(deletePos=prevIndent)}cm.replaceRange("",deletePos,cursor,"+delete")}})},cmds[map[cK+ctrl+"K"]="delLineRight"]=function(cm){cm.operation(function(){for(var ranges=cm.listSelections(),i=ranges.length-1;i>=0;i--)cm.replaceRange("",ranges[i].anchor,Pos(ranges[i].to().line),"+delete");cm.scrollIntoView()})},cmds[map[cK+ctrl+"U"]="upcaseAtCursor"]=function(cm){modifyWordOrSelection(cm,function(str){return str.toUpperCase()})},cmds[map[cK+ctrl+"L"]="downcaseAtCursor"]=function(cm){modifyWordOrSelection(cm,function(str){return str.toLowerCase()})},cmds[map[cK+ctrl+"Space"]="setSublimeMark"]=function(cm){cm.state.sublimeMark&&cm.state.sublimeMark.clear(),cm.state.sublimeMark=cm.setBookmark(cm.getCursor())},cmds[map[cK+ctrl+"A"]="selectToSublimeMark"]=function(cm){var found=cm.state.sublimeMark&&cm.state.sublimeMark.find();found&&cm.setSelection(cm.getCursor(),found)},cmds[map[cK+ctrl+"W"]="deleteToSublimeMark"]=function(cm){var found=cm.state.sublimeMark&&cm.state.sublimeMark.find();if(found){var from=cm.getCursor(),to=found;if(CodeMirror.cmpPos(from,to)>0){var tmp=to;to=from,from=tmp}cm.state.sublimeKilled=cm.getRange(from,to),cm.replaceRange("",from,to)}},cmds[map[cK+ctrl+"X"]="swapWithSublimeMark"]=function(cm){var found=cm.state.sublimeMark&&cm.state.sublimeMark.find();found&&(cm.state.sublimeMark.clear(),cm.state.sublimeMark=cm.setBookmark(cm.getCursor()),cm.setCursor(found))},cmds[map[cK+ctrl+"Y"]="sublimeYank"]=function(cm){null!=cm.state.sublimeKilled&&cm.replaceSelection(cm.state.sublimeKilled,null,"paste")},map[cK+ctrl+"G"]="clearBookmarks",cmds[map[cK+ctrl+"C"]="showInCenter"]=function(cm){var pos=cm.cursorCoords(null,"local");cm.scrollTo(null,(pos.top+pos.bottom)/2-cm.getScrollInfo().clientHeight/2)};var selectLinesCombo=mac?"Ctrl-Shift-":"Ctrl-Alt-";cmds[map[selectLinesCombo+"Up"]="selectLinesUpward"]=function(cm){cm.operation(function(){for(var ranges=cm.listSelections(),i=0;icm.firstLine()&&cm.addSelection(Pos(range.head.line-1,range.head.ch))}})},cmds[map[selectLinesCombo+"Down"]="selectLinesDownward"]=function(cm){cm.operation(function(){for(var ranges=cm.listSelections(),i=0;i0;--count)e.removeChild(e.firstChild);return e}function removeChildrenAndAdd(parent,e){return removeChildren(parent).appendChild(e)}function elt(tag,content,className,style){var e=document.createElement(tag);if(className&&(e.className=className),style&&(e.style.cssText=style),"string"==typeof content)e.appendChild(document.createTextNode(content));else if(content)for(var i=0;i=end)return n+(end-i);n+=nextTab-i,n+=tabSize-n%tabSize,i=nextTab+1}}function indexOf(array,elt){for(var i=0;i=goal)return pos+Math.min(skipped,goal-col);if(col+=nextTab-pos,col+=tabSize-col%tabSize,pos=nextTab+1,col>=goal)return pos}}function spaceStr(n){for(;spaceStrs.length<=n;)spaceStrs.push(lst(spaceStrs)+" ");return spaceStrs[n]}function lst(arr){return arr[arr.length-1]}function map(array,f){for(var out=[],i=0;i"€"&&(ch.toUpperCase()!=ch.toLowerCase()||nonASCIISingleCaseWordChar.test(ch))}function isWordChar(ch,helper){return helper?!!(helper.source.indexOf("\\w")>-1&&isWordCharBasic(ch))||helper.test(ch):isWordCharBasic(ch)}function isEmpty(obj){for(var n in obj)if(obj.hasOwnProperty(n)&&obj[n])return!1;return!0}function isExtendingChar(ch){return ch.charCodeAt(0)>=768&&extendingChars.test(ch)}function skipExtendingChars(str,pos,dir){for(;(dir<0?pos>0:pos=doc.size)throw new Error("There is no line "+(n+doc.first)+" in the document.");for(var chunk=doc;!chunk.lines;)for(var i=0;;++i){var child=chunk.children[i],sz=child.chunkSize();if(n=doc.first&&llast?Pos(last,getLine(doc,last).text.length):clipToLen(pos,getLine(doc,pos.line).text.length)}function clipToLen(pos,linelen){var ch=pos.ch;return null==ch||ch>linelen?Pos(pos.line,linelen):ch<0?Pos(pos.line,0):pos}function clipPosArray(doc,array){for(var out=[],i=0;i=startCh:span.to>startCh);(nw||(nw=[])).push(new MarkedSpan(marker,span.from,endsAfter?null:span.to))}}return nw}function markedSpansAfter(old,endCh,isInsert){var nw;if(old)for(var i=0;i=endCh:span.to>endCh)||span.from==endCh&&"bookmark"==marker.type&&(!isInsert||span.marker.insertLeft)){var startsBefore=null==span.from||(marker.inclusiveLeft?span.from<=endCh:span.from0&&first)for(var i$2=0;i$20)){var newParts=[j,1],dfrom=cmp(p.from,m.from),dto=cmp(p.to,m.to);(dfrom<0||!mk.inclusiveLeft&&!dfrom)&&newParts.push({from:p.from,to:m.from}),(dto>0||!mk.inclusiveRight&&!dto)&&newParts.push({from:m.to,to:p.to}),parts.splice.apply(parts,newParts),j+=newParts.length-3}}return parts}function detachMarkedSpans(line){var spans=line.markedSpans;if(spans){for(var i=0;i=0&&toCmp<=0||fromCmp<=0&&toCmp>=0)&&(fromCmp<=0&&(sp.marker.inclusiveRight&&marker.inclusiveLeft?cmp(found.to,from)>=0:cmp(found.to,from)>0)||fromCmp>=0&&(sp.marker.inclusiveRight&&marker.inclusiveLeft?cmp(found.from,to)<=0:cmp(found.from,to)<0)))return!0}}}function visualLine(line){for(var merged;merged=collapsedSpanAtStart(line);)line=merged.find(-1,!0).line;return line}function visualLineEnd(line){for(var merged;merged=collapsedSpanAtEnd(line);)line=merged.find(1,!0).line;return line}function visualLineContinued(line){for(var merged,lines;merged=collapsedSpanAtEnd(line);)line=merged.find(1,!0).line,(lines||(lines=[])).push(line);return lines}function visualLineNo(doc,lineN){var line=getLine(doc,lineN),vis=visualLine(line);return line==vis?lineN:lineNo(vis)}function visualLineEndNo(doc,lineN){if(lineN>doc.lastLine())return lineN;var merged,line=getLine(doc,lineN);if(!lineIsHidden(doc,line))return lineN;for(;merged=collapsedSpanAtEnd(line);)line=merged.find(1,!0).line;return lineNo(line)+1}function lineIsHidden(doc,line){var sps=sawCollapsedSpans&&line.markedSpans;if(sps)for(var sp=void 0,i=0;id.maxLineLength&&(d.maxLineLength=len,d.maxLine=line)})}function iterateBidiSections(order,from,to,f){if(!order)return f(from,to,"ltr");for(var found=!1,i=0;ifrom||from==to&&part.to==from)&&(f(Math.max(part.from,from),Math.min(part.to,to),1==part.level?"rtl":"ltr"),found=!0)}found||f(from,to,"ltr")}function getBidiPartAt(order,ch,sticky){var found;bidiOther=null;for(var i=0;ich)return i;cur.to==ch&&(cur.from!=cur.to&&"before"==sticky?found=i:bidiOther=i),cur.from==ch&&(cur.from!=cur.to&&"before"!=sticky?found=i:bidiOther=i)}return null!=found?found:bidiOther}function getOrder(line,direction){var order=line.order;return null==order&&(order=line.order=bidiOrdering(line.text,direction)),order}function moveCharLogically(line,ch,dir){var target=skipExtendingChars(line.text,ch+dir,dir);return target<0||target>line.text.length?null:target}function moveLogically(line,start,dir){var ch=moveCharLogically(line,start.ch,dir);return null==ch?null:new Pos(start.line,ch,dir<0?"after":"before")}function endOfLine(visually,cm,lineObj,lineNo,dir){if(visually){var order=getOrder(lineObj,cm.doc.direction);if(order){var ch,part=dir<0?lst(order):order[0],sticky=dir<0==(1==part.level)?"after":"before";if(part.level>0){var prep=prepareMeasureForLine(cm,lineObj);ch=dir<0?lineObj.text.length-1:0;var targetTop=measureCharPrepared(cm,prep,ch).top;ch=findFirst(function(ch){return measureCharPrepared(cm,prep,ch).top==targetTop},dir<0==(1==part.level)?part.from:part.to-1,ch),"before"==sticky&&(ch=moveCharLogically(lineObj,ch,1))}else ch=dir<0?part.to:part.from;return new Pos(lineNo,ch,sticky)}}return new Pos(lineNo,dir<0?lineObj.text.length:0,dir<0?"before":"after")}function moveVisually(cm,line,start,dir){var bidi=getOrder(line,cm.doc.direction);if(!bidi)return moveLogically(line,start,dir);start.ch>=line.text.length?(start.ch=line.text.length,start.sticky="before"):start.ch<=0&&(start.ch=0,start.sticky="after");var partPos=getBidiPartAt(bidi,start.ch,start.sticky),part=bidi[partPos];if("ltr"==cm.doc.direction&&part.level%2==0&&(dir>0?part.to>start.ch:part.from=part.from&&ch>=wrappedLineExtent.begin)){var sticky=moveInStorageOrder?"before":"after";return new Pos(start.line,ch,sticky)}}var searchInVisualLine=function(partPos,dir,wrappedLineExtent){for(var getRes=function(ch,moveInStorageOrder){return moveInStorageOrder?new Pos(start.line,mv(ch,1),"before"):new Pos(start.line,ch,"after")};partPos>=0&&partPos0==(1!=part.level),ch=moveInStorageOrder?wrappedLineExtent.begin:mv(wrappedLineExtent.end,-1);if(part.from<=ch&&ch0?wrappedLineExtent.end:mv(wrappedLineExtent.begin,-1);return null==nextCh||dir>0&&nextCh==line.text.length||!(res=searchInVisualLine(dir>0?0:bidi.length-1,dir,getWrappedLineExtent(nextCh)))?null:res}function getHandlers(emitter,type){return emitter._handlers&&emitter._handlers[type]||noHandlers}function off(emitter,type,f){if(emitter.removeEventListener)emitter.removeEventListener(type,f,!1);else if(emitter.detachEvent)emitter.detachEvent("on"+type,f);else{var map$$1=emitter._handlers,arr=map$$1&&map$$1[type];if(arr){var index=indexOf(arr,f);index>-1&&(map$$1[type]=arr.slice(0,index).concat(arr.slice(index+1)))}}}function signal(emitter,type){var handlers=getHandlers(emitter,type);if(handlers.length)for(var args=Array.prototype.slice.call(arguments,2),i=0;i0}function eventMixin(ctor){ctor.prototype.on=function(type,f){on(this,type,f)},ctor.prototype.off=function(type,f){off(this,type,f)}}function e_preventDefault(e){e.preventDefault?e.preventDefault():e.returnValue=!1}function e_stopPropagation(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}function e_defaultPrevented(e){return null!=e.defaultPrevented?e.defaultPrevented:0==e.returnValue}function e_stop(e){e_preventDefault(e),e_stopPropagation(e)}function e_target(e){return e.target||e.srcElement}function e_button(e){var b=e.which;return null==b&&(1&e.button?b=1:2&e.button?b=3:4&e.button&&(b=2)),mac&&e.ctrlKey&&1==b&&(b=3),b}function zeroWidthElement(measure){if(null==zwspSupported){var test=elt("span","​");removeChildrenAndAdd(measure,elt("span",[test,document.createTextNode("x")])),0!=measure.firstChild.offsetHeight&&(zwspSupported=test.offsetWidth<=1&&test.offsetHeight>2&&!(ie&&ie_version<8))}var node=zwspSupported?elt("span","​"):elt("span"," ",null,"display: inline-block; width: 1px; margin-right: -1px");return node.setAttribute("cm-text",""),node}function hasBadBidiRects(measure){if(null!=badBidiRects)return badBidiRects;var txt=removeChildrenAndAdd(measure,document.createTextNode("AخA")),r0=range(txt,0,1).getBoundingClientRect(),r1=range(txt,1,2).getBoundingClientRect();return removeChildren(measure),!(!r0||r0.left==r0.right)&&(badBidiRects=r1.right-r0.right<3)}function hasBadZoomedRects(measure){if(null!=badZoomedRects)return badZoomedRects;var node=removeChildrenAndAdd(measure,elt("span","x")),normal=node.getBoundingClientRect(),fromRange=range(node,0,1).getBoundingClientRect();return badZoomedRects=Math.abs(normal.left-fromRange.left)>1}function defineMode(name,mode){arguments.length>2&&(mode.dependencies=Array.prototype.slice.call(arguments,2)),modes[name]=mode}function resolveMode(spec){if("string"==typeof spec&&mimeModes.hasOwnProperty(spec))spec=mimeModes[spec];else if(spec&&"string"==typeof spec.name&&mimeModes.hasOwnProperty(spec.name)){var found=mimeModes[spec.name];"string"==typeof found&&(found={name:found}),(spec=createObj(found,spec)).name=found.name}else{if("string"==typeof spec&&/^[\w\-]+\/[\w\-]+\+xml$/.test(spec))return resolveMode("application/xml");if("string"==typeof spec&&/^[\w\-]+\/[\w\-]+\+json$/.test(spec))return resolveMode("application/json")}return"string"==typeof spec?{name:spec}:spec||{name:"null"}}function getMode(options,spec){spec=resolveMode(spec);var mfactory=modes[spec.name];if(!mfactory)return getMode(options,"text/plain");var modeObj=mfactory(options,spec);if(modeExtensions.hasOwnProperty(spec.name)){var exts=modeExtensions[spec.name];for(var prop in exts)exts.hasOwnProperty(prop)&&(modeObj.hasOwnProperty(prop)&&(modeObj["_"+prop]=modeObj[prop]),modeObj[prop]=exts[prop])}if(modeObj.name=spec.name,spec.helperType&&(modeObj.helperType=spec.helperType),spec.modeProps)for(var prop$1 in spec.modeProps)modeObj[prop$1]=spec.modeProps[prop$1];return modeObj}function extendMode(mode,properties){copyObj(properties,modeExtensions.hasOwnProperty(mode)?modeExtensions[mode]:modeExtensions[mode]={})}function copyState(mode,state){if(!0===state)return state;if(mode.copyState)return mode.copyState(state);var nstate={};for(var n in state){var val=state[n];val instanceof Array&&(val=val.concat([])),nstate[n]=val}return nstate}function innerMode(mode,state){for(var info;mode.innerMode&&(info=mode.innerMode(state))&&info.mode!=mode;)state=info.state,mode=info.mode;return info||{mode:mode,state:state}}function startState(mode,a1,a2){return!mode.startState||mode.startState(a1,a2)}function highlightLine(cm,line,context,forceToEnd){var st=[cm.state.modeGen],lineClasses={};runMode(cm,line.text,cm.doc.mode,context,function(end,style){return st.push(end,style)},lineClasses,forceToEnd);for(var state=context.state,o=0;oend&&st.splice(i,1,end,st[i+1],i_end),i+=2,at=Math.min(end,i_end)}if(style)if(overlay.opaque)st.splice(start,i-start,end,"overlay "+style),i=start+2;else for(;startcm.options.maxHighlightLength&©State(cm.doc.mode,context.state),result=highlightLine(cm,line,context);resetState&&(context.state=resetState),line.stateAfter=context.save(!resetState),line.styles=result.styles,result.classes?line.styleClasses=result.classes:line.styleClasses&&(line.styleClasses=null),updateFrontier===cm.doc.highlightFrontier&&(cm.doc.modeFrontier=Math.max(cm.doc.modeFrontier,++cm.doc.highlightFrontier))}return line.styles}function getContextBefore(cm,n,precise){var doc=cm.doc,display=cm.display;if(!doc.mode.startState)return new Context(doc,!0,n);var start=findStartLine(cm,n,precise),saved=start>doc.first&&getLine(doc,start-1).stateAfter,context=saved?Context.fromSaved(doc,saved,start):new Context(doc,startState(doc.mode),start);return doc.iter(start,n,function(line){processLine(cm,line.text,context);var pos=context.line;line.stateAfter=pos==n-1||pos%5==0||pos>=display.viewFrom&&posstream.start)return style}throw new Error("Mode "+mode.name+" failed to advance stream.")}function takeToken(cm,pos,precise,asArray){var style,tokens,doc=cm.doc,mode=doc.mode,line=getLine(doc,(pos=clipPos(doc,pos)).line),context=getContextBefore(cm,pos.line,precise),stream=new StringStream(line.text,cm.options.tabSize,context);for(asArray&&(tokens=[]);(asArray||stream.poscm.options.maxHighlightLength?(flattenSpans=!1,forceToEnd&&processLine(cm,text,context,stream.pos),stream.pos=text.length,style=null):style=extractLineClasses(readToken(mode,stream,context.state,inner),lineClasses),inner){var mName=inner[0].name;mName&&(style="m-"+(style?mName+" "+style:mName))}if(!flattenSpans||curStyle!=style){for(;curStartlim;--search){if(search<=doc.first)return doc.first;var line=getLine(doc,search-1),after=line.stateAfter;if(after&&(!precise||search+(after instanceof SavedContext?after.lookAhead:0)<=doc.modeFrontier))return search;var indented=countColumn(line.text,null,cm.options.tabSize);(null==minline||minindent>indented)&&(minline=search-1,minindent=indented)}return minline}function retreatFrontier(doc,n){if(doc.modeFrontier=Math.min(doc.modeFrontier,n),!(doc.highlightFrontierstart;line--){var saved=getLine(doc,line).stateAfter;if(saved&&(!(saved instanceof SavedContext)||line+saved.lookAhead1&&!/ /.test(text))return text;for(var spaceBefore=trailingBefore,result="",i=0;istart&&part.from<=start);i++);if(part.to>=end)return inner(builder,text,style,startStyle,endStyle,title,css);inner(builder,text.slice(0,part.to-start),style,startStyle,null,title,css),startStyle=null,text=text.slice(part.to-start),start=part.to}}}function buildCollapsedSpan(builder,size,marker,ignoreWidget){var widget=!ignoreWidget&&marker.widgetNode;widget&&builder.map.push(builder.pos,builder.pos+size,widget),!ignoreWidget&&builder.cm.display.input.needsContentAttribute&&(widget||(widget=builder.content.appendChild(document.createElement("span"))),widget.setAttribute("cm-marker",marker.id)),widget&&(builder.cm.display.input.setUneditable(widget),builder.content.appendChild(widget)),builder.pos+=size,builder.trailingSpace=!1}function insertLineContent(line,builder,styles){var spans=line.markedSpans,allText=line.text,at=0;if(spans)for(var style,css,spanStyle,spanEndStyle,spanStartStyle,title,collapsed,len=allText.length,pos=0,i=1,text="",nextChange=0;;){if(nextChange==pos){spanStyle=spanEndStyle=spanStartStyle=title=css="",collapsed=null,nextChange=1/0;for(var foundBookmarks=[],endStyles=void 0,j=0;jpos||m.collapsed&&sp.to==pos&&sp.from==pos)?(null!=sp.to&&sp.to!=pos&&nextChange>sp.to&&(nextChange=sp.to,spanEndStyle=""),m.className&&(spanStyle+=" "+m.className),m.css&&(css=(css?css+";":"")+m.css),m.startStyle&&sp.from==pos&&(spanStartStyle+=" "+m.startStyle),m.endStyle&&sp.to==nextChange&&(endStyles||(endStyles=[])).push(m.endStyle,sp.to),m.title&&!title&&(title=m.title),m.collapsed&&(!collapsed||compareCollapsedMarkers(collapsed.marker,m)<0)&&(collapsed=sp)):sp.from>pos&&nextChange>sp.from&&(nextChange=sp.from)}if(endStyles)for(var j$1=0;j$1=len)break;for(var upto=Math.min(len,nextChange);;){if(text){var end=pos+text.length;if(!collapsed){var tokenText=end>upto?text.slice(0,upto-pos):text;builder.addToken(builder,tokenText,style?style+spanStyle:spanStyle,spanStartStyle,pos+tokenText.length==nextChange?spanEndStyle:"",title,css)}if(end>=upto){text=text.slice(upto-pos),pos=upto;break}pos=end,spanStartStyle=""}text=allText.slice(at,at=styles[i++]),style=interpretTokenStyle(styles[i++],builder.cm.options)}}else for(var i$1=1;i$12&&heights.push((cur.bottom+next.top)/2-rect.top)}}heights.push(rect.bottom-rect.top)}}function mapFromLineView(lineView,line,lineN){if(lineView.line==line)return{map:lineView.measure.map,cache:lineView.measure.cache};for(var i=0;ilineN)return{map:lineView.measure.maps[i$1],cache:lineView.measure.caches[i$1],before:!0}}function updateExternalMeasurement(cm,line){var lineN=lineNo(line=visualLine(line)),view=cm.display.externalMeasured=new LineView(cm.doc,line,lineN);view.lineN=lineN;var built=view.built=buildLineContent(cm,view);return view.text=built.pre,removeChildrenAndAdd(cm.display.lineMeasure,built.pre),view}function measureChar(cm,line,ch,bias){return measureCharPrepared(cm,prepareMeasureForLine(cm,line),ch,bias)}function findViewForLine(cm,lineN){if(lineN>=cm.display.viewFrom&&lineN=ext.lineN&&lineNch)&&(start=(end=mEnd-mStart)-1,ch>=mEnd&&(collapse="right")),null!=start){if(node=map$$1[i+2],mStart==mEnd&&bias==(node.insertLeft?"left":"right")&&(collapse=bias),"left"==bias&&0==start)for(;i&&map$$1[i-2]==map$$1[i-3]&&map$$1[i-1].insertLeft;)node=map$$1[2+(i-=3)],collapse="left";if("right"==bias&&start==mEnd-mStart)for(;i=0&&(rect=rects[i$1]).left==rect.right;i$1--);return rect}function measureCharInner(cm,prepared,ch,bias){var rect,place=nodeAndOffsetInLineMap(prepared.map,ch,bias),node=place.node,start=place.start,end=place.end,collapse=place.collapse;if(3==node.nodeType){for(var i$1=0;i$1<4;i$1++){for(;start&&isExtendingChar(prepared.line.text.charAt(place.coverStart+start));)--start;for(;place.coverStart+end0&&(collapse=bias="right");var rects;rect=cm.options.lineWrapping&&(rects=node.getClientRects()).length>1?rects["right"==bias?rects.length-1:0]:node.getBoundingClientRect()}if(ie&&ie_version<9&&!start&&(!rect||!rect.left&&!rect.right)){var rSpan=node.parentNode.getClientRects()[0];rect=rSpan?{left:rSpan.left,right:rSpan.left+charWidth(cm.display),top:rSpan.top,bottom:rSpan.bottom}:nullRect}for(var rtop=rect.top-prepared.rect.top,rbot=rect.bottom-prepared.rect.top,mid=(rtop+rbot)/2,heights=prepared.view.measure.heights,i=0;i=lineObj.text.length?(ch=lineObj.text.length,sticky="before"):ch<=0&&(ch=0,sticky="after"),!order)return get("before"==sticky?ch-1:ch,"before"==sticky);var partPos=getBidiPartAt(order,ch,sticky),other=bidiOther,val=getBidi(ch,partPos,"before"==sticky);return null!=other&&(val.other=getBidi(ch,other,"before"!=sticky)),val}function estimateCoords(cm,pos){var left=0;pos=clipPos(cm.doc,pos),cm.options.lineWrapping||(left=charWidth(cm.display)*pos.ch);var lineObj=getLine(cm.doc,pos.line),top=heightAtLine(lineObj)+paddingTop(cm.display);return{left:left,right:left,top:top,bottom:top+lineObj.height}}function PosWithInfo(line,ch,sticky,outside,xRel){var pos=Pos(line,ch,sticky);return pos.xRel=xRel,outside&&(pos.outside=!0),pos}function coordsChar(cm,x,y){var doc=cm.doc;if((y+=cm.display.viewOffset)<0)return PosWithInfo(doc.first,0,null,!0,-1);var lineN=lineAtHeight(doc,y),last=doc.first+doc.size-1;if(lineN>last)return PosWithInfo(doc.first+doc.size-1,getLine(doc,last).text.length,null,!0,1);x<0&&(x=0);for(var lineObj=getLine(doc,lineN);;){var found=coordsCharInner(cm,lineObj,lineN,x,y),merged=collapsedSpanAtEnd(lineObj),mergedPos=merged&&merged.find(0,!0);if(!merged||!(found.ch>mergedPos.from.ch||found.ch==mergedPos.from.ch&&found.xRel>0))return found;lineN=lineNo(lineObj=mergedPos.to.line)}}function wrappedLineExtent(cm,lineObj,preparedMeasure,y){var measure=function(ch){return intoCoordSystem(cm,lineObj,measureCharPrepared(cm,preparedMeasure,ch),"line")},end=lineObj.text.length,begin=findFirst(function(ch){return measure(ch-1).bottom<=y},end,0);return end=findFirst(function(ch){return measure(ch).top>y},begin,end),{begin:begin,end:end}}function wrappedLineExtentChar(cm,lineObj,preparedMeasure,target){return wrappedLineExtent(cm,lineObj,preparedMeasure,intoCoordSystem(cm,lineObj,measureCharPrepared(cm,preparedMeasure,target),"line").top)}function coordsCharInner(cm,lineObj,lineNo$$1,x,y){y-=heightAtLine(lineObj);var pos,begin=0,end=lineObj.text.length,preparedMeasure=prepareMeasureForLine(cm,lineObj);if(getOrder(lineObj,cm.doc.direction)){if(cm.options.lineWrapping){var assign;begin=(assign=wrappedLineExtent(cm,lineObj,preparedMeasure,y)).begin,end=assign.end}pos=new Pos(lineNo$$1,Math.floor(begin+(end-begin)/2));var prevDiff,prevPos,beginLeft=cursorCoords(cm,pos,"line",lineObj,preparedMeasure).left,dir=beginLeft1){var diff_change_per_step=Math.abs(diff-prevDiff)/steps;steps=Math.min(steps,Math.ceil(Math.abs(diff)/diff_change_per_step)),dir=diff<0?1:-1}}while(0!=diff&&(steps>1||dir<0!=diff<0&&Math.abs(diff)<=Math.abs(prevDiff)));if(Math.abs(diff)>Math.abs(prevDiff)){if(diff<0==prevDiff<0)throw new Error("Broke out of infinite loop in coordsCharInner");pos=prevPos}}else{var ch=findFirst(function(ch){var box=intoCoordSystem(cm,lineObj,measureCharPrepared(cm,preparedMeasure,ch),"line");return box.top>y?(end=Math.min(ch,end),!0):!(box.bottom<=y)&&(box.left>x||!(box.rightcoords.right?1:0,pos}function textHeight(display){if(null!=display.cachedTextHeight)return display.cachedTextHeight;if(null==measureText){measureText=elt("pre");for(var i=0;i<49;++i)measureText.appendChild(document.createTextNode("x")),measureText.appendChild(elt("br"));measureText.appendChild(document.createTextNode("x"))}removeChildrenAndAdd(display.measure,measureText);var height=measureText.offsetHeight/50;return height>3&&(display.cachedTextHeight=height),removeChildren(display.measure),height||1}function charWidth(display){if(null!=display.cachedCharWidth)return display.cachedCharWidth;var anchor=elt("span","xxxxxxxxxx"),pre=elt("pre",[anchor]);removeChildrenAndAdd(display.measure,pre);var rect=anchor.getBoundingClientRect(),width=(rect.right-rect.left)/10;return width>2&&(display.cachedCharWidth=width),width||10}function getDimensions(cm){for(var d=cm.display,left={},width={},gutterLeft=d.gutters.clientLeft,n=d.gutters.firstChild,i=0;n;n=n.nextSibling,++i)left[cm.options.gutters[i]]=n.offsetLeft+n.clientLeft+gutterLeft,width[cm.options.gutters[i]]=n.clientWidth;return{fixedPos:compensateForHScroll(d),gutterTotalWidth:d.gutters.offsetWidth,gutterLeft:left,gutterWidth:width,wrapperWidth:d.wrapper.clientWidth}}function compensateForHScroll(display){return display.scroller.getBoundingClientRect().left-display.sizer.getBoundingClientRect().left}function estimateHeight(cm){var th=textHeight(cm.display),wrapping=cm.options.lineWrapping,perLine=wrapping&&Math.max(5,cm.display.scroller.clientWidth/charWidth(cm.display)-3);return function(line){if(lineIsHidden(cm.doc,line))return 0;var widgetsHeight=0;if(line.widgets)for(var i=0;i=cm.display.viewTo)return null;if((n-=cm.display.viewFrom)<0)return null;for(var view=cm.display.view,i=0;i=cm.display.viewTo||range$$1.to().line3&&(add(left,leftPos.top,null,leftPos.bottom),left=leftSide,leftPos.bottomend.bottom||rightPos.bottom==end.bottom&&rightPos.right>end.right)&&(end=rightPos),left0?display.blinker=setInterval(function(){return display.cursorDiv.style.visibility=(on=!on)?"":"hidden"},cm.options.cursorBlinkRate):cm.options.cursorBlinkRate<0&&(display.cursorDiv.style.visibility="hidden")}}function ensureFocus(cm){cm.state.focused||(cm.display.input.focus(),onFocus(cm))}function delayBlurEvent(cm){cm.state.delayingBlurEvent=!0,setTimeout(function(){cm.state.delayingBlurEvent&&(cm.state.delayingBlurEvent=!1,onBlur(cm))},100)}function onFocus(cm,e){cm.state.delayingBlurEvent&&(cm.state.delayingBlurEvent=!1),"nocursor"!=cm.options.readOnly&&(cm.state.focused||(signal(cm,"focus",cm,e),cm.state.focused=!0,addClass(cm.display.wrapper,"CodeMirror-focused"),cm.curOp||cm.display.selForContextMenu==cm.doc.sel||(cm.display.input.reset(),webkit&&setTimeout(function(){return cm.display.input.reset(!0)},20)),cm.display.input.receivedFocus()),restartBlink(cm))}function onBlur(cm,e){cm.state.delayingBlurEvent||(cm.state.focused&&(signal(cm,"blur",cm,e),cm.state.focused=!1,rmClass(cm.display.wrapper,"CodeMirror-focused")),clearInterval(cm.display.blinker),setTimeout(function(){cm.state.focused||(cm.display.shift=!1)},150))}function updateHeightsInViewport(cm){for(var display=cm.display,prevBottom=display.lineDiv.offsetTop,i=0;i.001||diff<-.001)&&(updateLineHeight(cur.line,height),updateWidgetHeight(cur.line),cur.rest))for(var j=0;j=to&&(from=lineAtHeight(doc,heightAtLine(getLine(doc,ensureTo))-display.wrapper.clientHeight),to=ensureTo)}return{from:from,to:Math.max(to,from+1)}}function alignHorizontally(cm){var display=cm.display,view=display.view;if(display.alignWidgets||display.gutters.firstChild&&cm.options.fixedGutter){for(var comp=compensateForHScroll(display)-display.scroller.scrollLeft+cm.doc.scrollLeft,gutterW=display.gutters.offsetWidth,left=comp+"px",i=0;i(window.innerHeight||document.documentElement.clientHeight)&&(doScroll=!1),null!=doScroll&&!phantom){var scrollNode=elt("div","​",null,"position: absolute;\n top: "+(rect.top-display.viewOffset-paddingTop(cm.display))+"px;\n height: "+(rect.bottom-rect.top+scrollGap(cm)+display.barHeight)+"px;\n left: "+rect.left+"px; width: "+Math.max(2,rect.right-rect.left)+"px;");cm.display.lineSpace.appendChild(scrollNode),scrollNode.scrollIntoView(doScroll),cm.display.lineSpace.removeChild(scrollNode)}}}function scrollPosIntoView(cm,pos,end,margin){null==margin&&(margin=0);for(var rect,limit=0;limit<5;limit++){var changed=!1,coords=cursorCoords(cm,pos),endCoords=end&&end!=pos?cursorCoords(cm,end):coords,scrollPos=calculateScrollPos(cm,rect={left:Math.min(coords.left,endCoords.left),top:Math.min(coords.top,endCoords.top)-margin,right:Math.max(coords.left,endCoords.left),bottom:Math.max(coords.bottom,endCoords.bottom)+margin}),startTop=cm.doc.scrollTop,startLeft=cm.doc.scrollLeft;if(null!=scrollPos.scrollTop&&(updateScrollTop(cm,scrollPos.scrollTop),Math.abs(cm.doc.scrollTop-startTop)>1&&(changed=!0)),null!=scrollPos.scrollLeft&&(setScrollLeft(cm,scrollPos.scrollLeft),Math.abs(cm.doc.scrollLeft-startLeft)>1&&(changed=!0)),!changed)break}return rect}function scrollIntoView(cm,rect){var scrollPos=calculateScrollPos(cm,rect);null!=scrollPos.scrollTop&&updateScrollTop(cm,scrollPos.scrollTop),null!=scrollPos.scrollLeft&&setScrollLeft(cm,scrollPos.scrollLeft)}function calculateScrollPos(cm,rect){var display=cm.display,snapMargin=textHeight(cm.display);rect.top<0&&(rect.top=0);var screentop=cm.curOp&&null!=cm.curOp.scrollTop?cm.curOp.scrollTop:display.scroller.scrollTop,screen=displayHeight(cm),result={};rect.bottom-rect.top>screen&&(rect.bottom=rect.top+screen);var docBottom=cm.doc.height+paddingVert(display),atTop=rect.topdocBottom-snapMargin;if(rect.topscreentop+screen){var newTop=Math.min(rect.top,(atBottom?docBottom:rect.bottom)-screen);newTop!=screentop&&(result.scrollTop=newTop)}var screenleft=cm.curOp&&null!=cm.curOp.scrollLeft?cm.curOp.scrollLeft:display.scroller.scrollLeft,screenw=displayWidth(cm)-(cm.options.fixedGutter?display.gutters.offsetWidth:0),tooWide=rect.right-rect.left>screenw;return tooWide&&(rect.right=rect.left+screenw),rect.left<10?result.scrollLeft=0:rect.leftscreenw+screenleft-3&&(result.scrollLeft=rect.right+(tooWide?0:10)-screenw),result}function addToScrollTop(cm,top){null!=top&&(resolveScrollToPos(cm),cm.curOp.scrollTop=(null==cm.curOp.scrollTop?cm.doc.scrollTop:cm.curOp.scrollTop)+top)}function ensureCursorVisible(cm){resolveScrollToPos(cm);var cur=cm.getCursor(),from=cur,to=cur;cm.options.lineWrapping||(from=cur.ch?Pos(cur.line,cur.ch-1):cur,to=Pos(cur.line,cur.ch+1)),cm.curOp.scrollToPos={from:from,to:to,margin:cm.options.cursorScrollMargin}}function scrollToCoords(cm,x,y){null==x&&null==y||resolveScrollToPos(cm),null!=x&&(cm.curOp.scrollLeft=x),null!=y&&(cm.curOp.scrollTop=y)}function scrollToRange(cm,range$$1){resolveScrollToPos(cm),cm.curOp.scrollToPos=range$$1}function resolveScrollToPos(cm){var range$$1=cm.curOp.scrollToPos;range$$1&&(cm.curOp.scrollToPos=null,scrollToCoordsRange(cm,estimateCoords(cm,range$$1.from),estimateCoords(cm,range$$1.to),range$$1.margin))}function scrollToCoordsRange(cm,from,to,margin){var sPos=calculateScrollPos(cm,{left:Math.min(from.left,to.left),top:Math.min(from.top,to.top)-margin,right:Math.max(from.right,to.right),bottom:Math.max(from.bottom,to.bottom)+margin});scrollToCoords(cm,sPos.scrollLeft,sPos.scrollTop)}function updateScrollTop(cm,val){Math.abs(cm.doc.scrollTop-val)<2||(gecko||updateDisplaySimple(cm,{top:val}),setScrollTop(cm,val,!0),gecko&&updateDisplaySimple(cm),startWorker(cm,100))}function setScrollTop(cm,val,forceScroll){val=Math.min(cm.display.scroller.scrollHeight-cm.display.scroller.clientHeight,val),(cm.display.scroller.scrollTop!=val||forceScroll)&&(cm.doc.scrollTop=val,cm.display.scrollbars.setScrollTop(val),cm.display.scroller.scrollTop!=val&&(cm.display.scroller.scrollTop=val))}function setScrollLeft(cm,val,isScroller,forceScroll){val=Math.min(val,cm.display.scroller.scrollWidth-cm.display.scroller.clientWidth),(isScroller?val==cm.doc.scrollLeft:Math.abs(cm.doc.scrollLeft-val)<2)&&!forceScroll||(cm.doc.scrollLeft=val,alignHorizontally(cm),cm.display.scroller.scrollLeft!=val&&(cm.display.scroller.scrollLeft=val),cm.display.scrollbars.setScrollLeft(val))}function measureForScrollbars(cm){var d=cm.display,gutterW=d.gutters.offsetWidth,docH=Math.round(cm.doc.height+paddingVert(cm.display));return{clientHeight:d.scroller.clientHeight,viewHeight:d.wrapper.clientHeight,scrollWidth:d.scroller.scrollWidth,clientWidth:d.scroller.clientWidth,viewWidth:d.wrapper.clientWidth,barLeft:cm.options.fixedGutter?gutterW:0,docHeight:docH,scrollHeight:docH+scrollGap(cm)+d.barHeight,nativeBarWidth:d.nativeBarWidth,gutterWidth:gutterW}}function updateScrollbars(cm,measure){measure||(measure=measureForScrollbars(cm));var startWidth=cm.display.barWidth,startHeight=cm.display.barHeight;updateScrollbarsInner(cm,measure);for(var i=0;i<4&&startWidth!=cm.display.barWidth||startHeight!=cm.display.barHeight;i++)startWidth!=cm.display.barWidth&&cm.options.lineWrapping&&updateHeightsInViewport(cm),updateScrollbarsInner(cm,measureForScrollbars(cm)),startWidth=cm.display.barWidth,startHeight=cm.display.barHeight}function updateScrollbarsInner(cm,measure){var d=cm.display,sizes=d.scrollbars.update(measure);d.sizer.style.paddingRight=(d.barWidth=sizes.right)+"px",d.sizer.style.paddingBottom=(d.barHeight=sizes.bottom)+"px",d.heightForcer.style.borderBottom=sizes.bottom+"px solid transparent",sizes.right&&sizes.bottom?(d.scrollbarFiller.style.display="block",d.scrollbarFiller.style.height=sizes.bottom+"px",d.scrollbarFiller.style.width=sizes.right+"px"):d.scrollbarFiller.style.display="",sizes.bottom&&cm.options.coverGutterNextToScrollbar&&cm.options.fixedGutter?(d.gutterFiller.style.display="block",d.gutterFiller.style.height=sizes.bottom+"px",d.gutterFiller.style.width=measure.gutterWidth+"px"):d.gutterFiller.style.display=""}function initScrollbars(cm){cm.display.scrollbars&&(cm.display.scrollbars.clear(),cm.display.scrollbars.addClass&&rmClass(cm.display.wrapper,cm.display.scrollbars.addClass)),cm.display.scrollbars=new scrollbarModel[cm.options.scrollbarStyle](function(node){cm.display.wrapper.insertBefore(node,cm.display.scrollbarFiller),on(node,"mousedown",function(){cm.state.focused&&setTimeout(function(){return cm.display.input.focus()},0)}),node.setAttribute("cm-not-content","true")},function(pos,axis){"horizontal"==axis?setScrollLeft(cm,pos):updateScrollTop(cm,pos)},cm),cm.display.scrollbars.addClass&&addClass(cm.display.wrapper,cm.display.scrollbars.addClass)}function startOperation(cm){cm.curOp={cm:cm,viewChanged:!1,startHeight:cm.doc.height,forceUpdate:!1,updateInput:null,typing:!1,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:!1,updateMaxLine:!1,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:!1,id:++nextOpId},pushOperation(cm.curOp)}function endOperation(cm){finishOperation(cm.curOp,function(group){for(var i=0;i=display.viewTo)||display.maxLineChanged&&cm.options.lineWrapping,op.update=op.mustUpdate&&new DisplayUpdate(cm,op.mustUpdate&&{top:op.scrollTop,ensure:op.scrollToPos},op.forceUpdate)}function endOperation_W1(op){op.updatedDisplay=op.mustUpdate&&updateDisplayIfNeeded(op.cm,op.update)}function endOperation_R2(op){var cm=op.cm,display=cm.display;op.updatedDisplay&&updateHeightsInViewport(cm),op.barMeasure=measureForScrollbars(cm),display.maxLineChanged&&!cm.options.lineWrapping&&(op.adjustWidthTo=measureChar(cm,display.maxLine,display.maxLine.text.length).left+3,cm.display.sizerWidth=op.adjustWidthTo,op.barMeasure.scrollWidth=Math.max(display.scroller.clientWidth,display.sizer.offsetLeft+op.adjustWidthTo+scrollGap(cm)+cm.display.barWidth),op.maxScrollLeft=Math.max(0,display.sizer.offsetLeft+op.adjustWidthTo-displayWidth(cm))),(op.updatedDisplay||op.selectionChanged)&&(op.preparedSelection=display.input.prepareSelection(op.focus))}function endOperation_W2(op){var cm=op.cm;null!=op.adjustWidthTo&&(cm.display.sizer.style.minWidth=op.adjustWidthTo+"px",op.maxScrollLeft