Skip to content

Commit 2a27f50

Browse files
committed
Add InputUnionType
1 parent 7bdead7 commit 2a27f50

File tree

2 files changed

+398
-0
lines changed

2 files changed

+398
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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\GraphQl\Type\Definition;
15+
16+
use GraphQL\Error\Error;
17+
use GraphQL\Error\InvariantViolation;
18+
use GraphQL\Type\Definition\Config;
19+
use GraphQL\Type\Definition\InputObjectType;
20+
use GraphQL\Type\Definition\InputType;
21+
use GraphQL\Type\Definition\LeafType;
22+
use GraphQL\Type\Definition\Type;
23+
use GraphQL\Utils\Utils;
24+
25+
/**
26+
* Represents an union of other input types.
27+
*
28+
* @experimental
29+
*
30+
* @author Alan Poulain <[email protected]>
31+
*/
32+
final class InputUnionType extends Type implements InputType, LeafType
33+
{
34+
/**
35+
* @var InputObjectType[]
36+
*/
37+
private $types;
38+
39+
/**
40+
* @var array
41+
*/
42+
public $config;
43+
44+
/**
45+
* @throws InvariantViolation
46+
*/
47+
public function __construct(array $config)
48+
{
49+
if (!isset($config['name'])) {
50+
$config['name'] = $this->tryInferName();
51+
}
52+
53+
Utils::assertValidName($config['name']);
54+
55+
Config::validate($config, [
56+
'name' => Config::NAME | Config::REQUIRED,
57+
'types' => Config::arrayOf(Config::INPUT_TYPE, Config::MAYBE_THUNK | Config::REQUIRED),
58+
'description' => Config::STRING,
59+
]);
60+
61+
$this->name = $config['name'];
62+
$this->description = $config['description'] ?? null;
63+
$this->config = $config;
64+
}
65+
66+
/**
67+
* @throws InvariantViolation
68+
*
69+
* @return InputObjectType[]
70+
*/
71+
public function getTypes(): array
72+
{
73+
if (null !== $this->types) {
74+
return $this->types;
75+
}
76+
77+
if (($types = $this->config['types'] ?? null) && \is_callable($types)) {
78+
$types = \call_user_func($this->config['types']);
79+
}
80+
81+
if (!\is_array($types)) {
82+
throw new InvariantViolation(
83+
"{$this->name} types must be an Array or a callable which returns an Array."
84+
);
85+
}
86+
87+
return $this->types = $types;
88+
}
89+
90+
/**
91+
* @throws InvariantViolation
92+
*/
93+
public function assertValid()
94+
{
95+
parent::assertValid();
96+
97+
$types = $this->getTypes();
98+
Utils::invariant(!empty($types), "{$this->name} types must not be empty");
99+
100+
$includedTypeNames = [];
101+
foreach ($types as $inputType) {
102+
Utils::invariant(
103+
$inputType instanceof InputType,
104+
"{$this->name} may only contain input types, it cannot contain: %s.",
105+
Utils::printSafe($inputType)
106+
);
107+
Utils::invariant(
108+
!isset($includedTypeNames[$inputType->name]),
109+
"{$this->name} can include {$inputType->name} type only once."
110+
);
111+
$includedTypeNames[$inputType->name] = true;
112+
}
113+
}
114+
115+
/**
116+
* {@inheritdoc}
117+
*/
118+
public function serialize($value)
119+
{
120+
foreach ($this->getTypes() as $type) {
121+
if ($type instanceof LeafType) {
122+
try {
123+
return $type->serialize($value);
124+
} catch (\Exception $e) {
125+
}
126+
}
127+
}
128+
129+
throw new InvariantViolation(sprintf('Types in union cannot represent value: %s', Utils::printSafe($value)));
130+
}
131+
132+
/**
133+
* {@inheritdoc}
134+
*/
135+
public function parseValue($value)
136+
{
137+
foreach ($this->getTypes() as $type) {
138+
if ($type instanceof LeafType) {
139+
try {
140+
return $type->parseValue($value);
141+
} catch (\Exception $e) {
142+
}
143+
}
144+
}
145+
146+
throw new Error(sprintf('Types in union cannot represent value: %s', Utils::printSafeJson($value)));
147+
}
148+
149+
/**
150+
* {@inheritdoc}
151+
*/
152+
public function parseLiteral($valueNode)
153+
{
154+
foreach ($this->getTypes() as $type) {
155+
if ($type instanceof LeafType && null !== $parsed = $type->parseLiteral($valueNode)) {
156+
return $parsed;
157+
}
158+
}
159+
160+
return null;
161+
}
162+
163+
/**
164+
* {@inheritdoc}
165+
*/
166+
public function isValidValue($value): bool
167+
{
168+
foreach ($this->getTypes() as $type) {
169+
if ($type instanceof LeafType && $type->isValidValue($value)) {
170+
return true;
171+
}
172+
}
173+
174+
return false;
175+
}
176+
177+
/**
178+
* {@inheritdoc}
179+
*/
180+
public function isValidLiteral($valueNode): bool
181+
{
182+
foreach ($this->getTypes() as $type) {
183+
if ($type instanceof LeafType && $type->isValidLiteral($valueNode)) {
184+
return true;
185+
}
186+
}
187+
188+
return false;
189+
}
190+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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\Tests\GraphQl\Type\Definition;
15+
16+
use ApiPlatform\Core\GraphQl\Type\Definition\InputUnionType;
17+
use GraphQL\Error\Error;
18+
use GraphQL\Error\InvariantViolation;
19+
use GraphQL\Language\AST\StringValueNode;
20+
use GraphQL\Type\Definition\LeafType;
21+
use GraphQL\Type\Definition\ObjectType;
22+
use GraphQL\Type\Definition\StringType;
23+
use PHPUnit\Framework\TestCase;
24+
25+
/**
26+
* @author Alan Poulain <[email protected]>
27+
*/
28+
class InputUnionTypeTest extends TestCase
29+
{
30+
public function testGetTypesNotSet()
31+
{
32+
$inputUnionType = new InputUnionType([]);
33+
34+
$this->expectException(InvariantViolation::class);
35+
$this->expectExceptionMessage('InputUnion types must be an Array or a callable which returns an Array.');
36+
37+
$inputUnionType->getTypes();
38+
}
39+
40+
public function testGetTypesInvalid()
41+
{
42+
$inputUnionType = new InputUnionType(['types' => 1]);
43+
44+
$this->expectException(InvariantViolation::class);
45+
$this->expectExceptionMessage('InputUnion types must be an Array or a callable which returns an Array.');
46+
47+
$inputUnionType->getTypes();
48+
}
49+
50+
public function testGetTypesCallable()
51+
{
52+
$inputUnionType = new InputUnionType(['types' => function () {
53+
return ['foo'];
54+
}]);
55+
56+
$this->assertEquals(['foo'], $inputUnionType->getTypes());
57+
}
58+
59+
public function testGetTypes()
60+
{
61+
$inputUnionType = new InputUnionType(['types' => ['bar']]);
62+
63+
$this->assertEquals(['bar'], $inputUnionType->getTypes());
64+
}
65+
66+
public function testAssertValidEmptyTypes()
67+
{
68+
$inputUnionType = new InputUnionType(['types' => []]);
69+
70+
$this->expectException(InvariantViolation::class);
71+
$this->expectExceptionMessage('InputUnion types must not be empty');
72+
73+
$inputUnionType->assertValid();
74+
}
75+
76+
public function testAssertValidNotInputObjectTypes()
77+
{
78+
$inputUnionType = new InputUnionType(['types' => ['foo']]);
79+
80+
$this->expectException(InvariantViolation::class);
81+
$this->expectExceptionMessage('InputUnion may only contain input types, it cannot contain: "foo".');
82+
83+
$inputUnionType->assertValid();
84+
}
85+
86+
public function testAssertValidDuplicateTypes()
87+
{
88+
$type = $this->prophesize(StringType::class)->reveal();
89+
$inputUnionType = new InputUnionType(['types' => [$type, $type]]);
90+
91+
$this->expectException(InvariantViolation::class);
92+
$this->expectExceptionMessage('InputUnion can include String type only once.');
93+
94+
$inputUnionType->assertValid();
95+
}
96+
97+
public function testSerializeNotLeafType()
98+
{
99+
$type = $this->prophesize(ObjectType::class)->reveal();
100+
$inputUnionType = new InputUnionType(['types' => [$type]]);
101+
102+
$this->expectException(InvariantViolation::class);
103+
$this->expectExceptionMessage('Types in union cannot represent value: "foo"');
104+
105+
$inputUnionType->serialize('foo');
106+
}
107+
108+
public function testSerialize()
109+
{
110+
$type = $this->prophesize(LeafType::class);
111+
$type->serialize('foo')->shouldBeCalled();
112+
$inputUnionType = new InputUnionType(['types' => [$type->reveal()]]);
113+
114+
$inputUnionType->serialize('foo');
115+
}
116+
117+
public function testParseValueNotLeafType()
118+
{
119+
$type = $this->prophesize(ObjectType::class)->reveal();
120+
$inputUnionType = new InputUnionType(['types' => [$type]]);
121+
122+
$this->expectException(Error::class);
123+
$this->expectExceptionMessage('Types in union cannot represent value: "foo"');
124+
125+
$inputUnionType->parseValue('foo');
126+
}
127+
128+
public function testParseValue()
129+
{
130+
$type = $this->prophesize(LeafType::class);
131+
$type->parseValue('foo')->shouldBeCalled();
132+
$inputUnionType = new InputUnionType(['types' => [$type->reveal()]]);
133+
134+
$inputUnionType->parseValue('foo');
135+
}
136+
137+
public function testParseLiteralNotLeafType()
138+
{
139+
$type = $this->prophesize(ObjectType::class)->reveal();
140+
$inputUnionType = new InputUnionType(['types' => [$type]]);
141+
142+
$this->assertNull($inputUnionType->parseLiteral(new StringValueNode(['value' => 'foo'])));
143+
}
144+
145+
public function testParseLiteral()
146+
{
147+
$type = $this->prophesize(LeafType::class);
148+
$node = new StringValueNode(['value' => 'foo']);
149+
$type->parseLiteral($node)->shouldBeCalled();
150+
$inputUnionType = new InputUnionType(['types' => [$type->reveal()]]);
151+
152+
$inputUnionType->parseLiteral($node);
153+
}
154+
155+
public function testIsValidValueNotLeafType()
156+
{
157+
$type = $this->prophesize(ObjectType::class)->reveal();
158+
$inputUnionType = new InputUnionType(['types' => [$type]]);
159+
160+
$this->assertFalse($inputUnionType->isValidValue('foo'));
161+
}
162+
163+
public function testIsValidValueInvalid()
164+
{
165+
$type = $this->prophesize(LeafType::class);
166+
$type->isValidValue('foo')->willReturn(false)->shouldBeCalled();
167+
$inputUnionType = new InputUnionType(['types' => [$type->reveal()]]);
168+
169+
$this->assertFalse($inputUnionType->isValidValue('foo'));
170+
}
171+
172+
public function testIsValidValue()
173+
{
174+
$type = $this->prophesize(LeafType::class);
175+
$type->isValidValue('foo')->willReturn(true)->shouldBeCalled();
176+
$inputUnionType = new InputUnionType(['types' => [$type->reveal()]]);
177+
178+
$this->assertTrue($inputUnionType->isValidValue('foo'));
179+
}
180+
181+
public function testIsValidLiteralNotLeafType()
182+
{
183+
$type = $this->prophesize(ObjectType::class)->reveal();
184+
$inputUnionType = new InputUnionType(['types' => [$type]]);
185+
186+
$this->assertFalse($inputUnionType->isValidLiteral(new StringValueNode(['value' => 'foo'])));
187+
}
188+
189+
public function testIsValidLiteralInvalid()
190+
{
191+
$type = $this->prophesize(LeafType::class);
192+
$node = new StringValueNode(['value' => 'foo']);
193+
$type->isValidLiteral($node)->willReturn(false)->shouldBeCalled();
194+
$inputUnionType = new InputUnionType(['types' => [$type->reveal()]]);
195+
196+
$this->assertFalse($inputUnionType->isValidLiteral($node));
197+
}
198+
199+
public function testIsValidLiteral()
200+
{
201+
$type = $this->prophesize(LeafType::class);
202+
$node = new StringValueNode(['value' => 'foo']);
203+
$type->isValidLiteral($node)->willReturn(true)->shouldBeCalled();
204+
$inputUnionType = new InputUnionType(['types' => [$type->reveal()]]);
205+
206+
$this->assertTrue($inputUnionType->isValidLiteral($node));
207+
}
208+
}

0 commit comments

Comments
 (0)