Skip to content

Commit ebc98da

Browse files
authored
Add a command for printing a SDL schema (#2600)
1 parent aeecbbb commit ebc98da

File tree

6 files changed

+275
-0
lines changed

6 files changed

+275
-0
lines changed

behat.yml.dist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ default:
22
suites:
33
default:
44
contexts:
5+
- 'CommandContext'
56
- 'DoctrineContext':
67
doctrine: '@doctrine'
78
- 'HttpHeaderContext'
@@ -21,6 +22,7 @@ default:
2122
tags: '~@postgres&&~@mongodb&&~@elasticsearch'
2223
postgres:
2324
contexts:
25+
- 'CommandContext'
2426
- 'DoctrineContext':
2527
doctrine: '@doctrine'
2628
- 'HttpHeaderContext'
@@ -40,6 +42,7 @@ default:
4042
tags: '~@sqlite&&~@mongodb&&~@elasticsearch'
4143
mongodb:
4244
contexts:
45+
- 'CommandContext'
4346
- 'DoctrineContext':
4447
doctrine: '@doctrine_mongodb'
4548
- 'HttpHeaderContext'
@@ -89,6 +92,7 @@ coverage:
8992
suites:
9093
default:
9194
contexts:
95+
- 'CommandContext'
9296
- 'DoctrineContext':
9397
doctrine: '@doctrine'
9498
- 'HttpHeaderContext'

features/bootstrap/CommandContext.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
use Behat\Gherkin\Node\PyStringNode;
15+
use Behat\Gherkin\Node\TableNode;
16+
use Behat\Symfony2Extension\Context\KernelAwareContext;
17+
use PHPUnit\Framework\Assert;
18+
use Symfony\Bundle\FrameworkBundle\Console\Application;
19+
use Symfony\Component\Console\Command\Command;
20+
use Symfony\Component\Console\Tester\CommandTester;
21+
use Symfony\Component\HttpKernel\KernelInterface;
22+
23+
/**
24+
* Context for Symfony commands.
25+
*
26+
* @author Alan Poulain <[email protected]>
27+
*/
28+
final class CommandContext implements KernelAwareContext
29+
{
30+
/**
31+
* @var KernelInterface
32+
*/
33+
private $kernel;
34+
35+
/**
36+
* @var Application
37+
*/
38+
private $application;
39+
40+
/**
41+
* @var CommandTester
42+
*/
43+
private $commandTester;
44+
45+
/**
46+
* @When I run the command :command
47+
*/
48+
public function iRunTheCommand(string $command): void
49+
{
50+
$command = $this->getApplication()->find($command);
51+
52+
$this->getCommandTester($command)->execute([]);
53+
}
54+
55+
/**
56+
* @When I run the command :command with options:
57+
*/
58+
public function iRunTheCommandWithOptions(string $command, TableNode $options): void
59+
{
60+
$command = $this->getApplication()->find($command);
61+
62+
$this->getCommandTester($command)->execute($options->getRowsHash());
63+
}
64+
65+
/**
66+
* @Then the command output should be:
67+
*/
68+
public function theCommandOutputShouldBe(PyStringNode $expectedOutput): void
69+
{
70+
Assert::assertEquals($expectedOutput->getRaw(), $this->commandTester->getDisplay());
71+
}
72+
73+
/**
74+
* @Then the command output should contain:
75+
*/
76+
public function theCommandOutputShouldContain(PyStringNode $expectedOutput): void
77+
{
78+
$expectedOutput = str_replace('###', '"""', $expectedOutput->getRaw());
79+
80+
Assert::assertContains($expectedOutput, $this->commandTester->getDisplay());
81+
}
82+
83+
public function setKernel(KernelInterface $kernel): void
84+
{
85+
$this->kernel = $kernel;
86+
}
87+
88+
public function getApplication(): Application
89+
{
90+
if (null !== $this->application) {
91+
return $this->application;
92+
}
93+
94+
$this->application = new Application($this->kernel);
95+
96+
return $this->application;
97+
}
98+
99+
private function getCommandTester(Command $command): CommandTester
100+
{
101+
$this->commandTester = new CommandTester($command);
102+
103+
return $this->commandTester;
104+
}
105+
}

features/graphql/schema.feature

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
Feature: GraphQL schema-related features
2+
3+
@createSchema
4+
Scenario: Export the GraphQL schema in SDL
5+
When I run the command "api:graphql:export"
6+
Then the command output should contain:
7+
"""
8+
###Dummy Friend.###
9+
type DummyFriend implements Node {
10+
id: ID!
11+
12+
###The id###
13+
_id: Int!
14+
15+
###The dummy name###
16+
name: String!
17+
}
18+
19+
###Connection for DummyFriend.###
20+
type DummyFriendConnection {
21+
edges: [DummyFriendEdge]
22+
pageInfo: DummyFriendPageInfo!
23+
totalCount: Int!
24+
}
25+
26+
###Edge of DummyFriend.###
27+
type DummyFriendEdge {
28+
node: DummyFriend
29+
cursor: String!
30+
}
31+
32+
###Information about the current page.###
33+
type DummyFriendPageInfo {
34+
endCursor: String
35+
startCursor: String
36+
hasNextPage: Boolean!
37+
hasPreviousPage: Boolean!
38+
}
39+
"""
40+
41+
Scenario: Export the GraphQL schema in SDL with comment descriptions
42+
When I run the command "api:graphql:export" with options:
43+
| --comment-descriptions | true |
44+
Then the command output should contain:
45+
"""
46+
# Dummy Friend.
47+
type DummyFriend implements Node {
48+
id: ID!
49+
50+
# The id
51+
_id: Int!
52+
53+
# The dummy name
54+
name: String!
55+
}
56+
57+
# Connection for DummyFriend.
58+
type DummyFriendConnection {
59+
edges: [DummyFriendEdge]
60+
pageInfo: DummyFriendPageInfo!
61+
totalCount: Int!
62+
}
63+
64+
# Edge of DummyFriend.
65+
type DummyFriendEdge {
66+
node: DummyFriend
67+
cursor: String!
68+
}
69+
70+
# Information about the current page.
71+
type DummyFriendPageInfo {
72+
endCursor: String
73+
startCursor: String
74+
hasNextPage: Boolean!
75+
hasPreviousPage: Boolean!
76+
}
77+
"""
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Command;
15+
16+
use ApiPlatform\Core\GraphQl\Type\SchemaBuilder;
17+
use GraphQL\Utils\SchemaPrinter;
18+
use Symfony\Component\Console\Command\Command;
19+
use Symfony\Component\Console\Input\InputInterface;
20+
use Symfony\Component\Console\Input\InputOption;
21+
use Symfony\Component\Console\Output\OutputInterface;
22+
use Symfony\Component\Console\Style\SymfonyStyle;
23+
24+
/**
25+
* Export the GraphQL schema in Schema Definition Language (SDL).
26+
*
27+
* @experimental
28+
*
29+
* @author Alan Poulain <[email protected]>
30+
*/
31+
class GraphQlExportCommand extends Command
32+
{
33+
protected static $defaultName = 'api:graphql:export';
34+
35+
private $schemaBuilder;
36+
37+
public function __construct(SchemaBuilder $schemaBuilder)
38+
{
39+
$this->schemaBuilder = $schemaBuilder;
40+
41+
parent::__construct();
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
protected function configure(): void
48+
{
49+
$this
50+
->setDescription('Export the GraphQL schema in Schema Definition Language (SDL)')
51+
->addOption('comment-descriptions', null, InputOption::VALUE_NONE, 'Use preceding comments as the description')
52+
->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'Write output to file')
53+
;
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
protected function execute(InputInterface $input, OutputInterface $output)
60+
{
61+
$io = new SymfonyStyle($input, $output);
62+
63+
$options = [];
64+
65+
if ($input->getOption('comment-descriptions')) {
66+
$options['commentDescriptions'] = true;
67+
}
68+
69+
$schemaExport = SchemaPrinter::doPrint($this->schemaBuilder->getSchema(), $options);
70+
71+
$filename = $input->getOption('output');
72+
if (\is_string($filename)) {
73+
file_put_contents($filename, $schemaExport);
74+
$io->success(sprintf('Data written to %s.', $filename));
75+
} else {
76+
$output->writeln($schemaExport);
77+
}
78+
79+
return 0;
80+
}
81+
}

src/Bridge/Symfony/Bundle/Resources/config/graphql.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@
119119
<!-- After the DataUriNormalizer but before the ObjectNormalizer -->
120120
<tag name="serializer.normalizer" priority="-924" />
121121
</service>
122+
123+
<!-- Command -->
124+
125+
<service id="api_platform.graphql.command.export_command" class="ApiPlatform\Core\Bridge\Symfony\Bundle\Command\GraphQlExportCommand">
126+
<argument type="service" id="api_platform.graphql.schema_builder" />
127+
<tag name="console.command" />
128+
</service>
122129
</services>
123130

124131
</container>

tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,7 @@ private function getBaseContainerBuilderProphecy()
962962
'api_platform.graphql.types_factory',
963963
'api_platform.graphql.normalizer.item',
964964
'api_platform.graphql.normalizer.item.non_resource',
965+
'api_platform.graphql.command.export_command',
965966
'api_platform.hal.encoder',
966967
'api_platform.hal.normalizer.collection',
967968
'api_platform.hal.normalizer.entrypoint',

0 commit comments

Comments
 (0)