Skip to content

Commit 73cbefe

Browse files
committed
Add InputUnionType
1 parent 90a61ab commit 73cbefe

File tree

2 files changed

+391
-0
lines changed

2 files changed

+391
-0
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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\InputObjectType;
19+
use GraphQL\Type\Definition\InputType;
20+
use GraphQL\Type\Definition\LeafType;
21+
use GraphQL\Type\Definition\Type;
22+
use GraphQL\Utils\Utils;
23+
24+
/**
25+
* Represents an union of other input types.
26+
*
27+
* @experimental
28+
*
29+
* @author Alan Poulain <[email protected]>
30+
*/
31+
final class InputUnionType extends Type implements InputType, LeafType
32+
{
33+
/**
34+
* @var InputObjectType[]
35+
*/
36+
private $types;
37+
38+
/**
39+
* @var array
40+
*/
41+
private $config;
42+
43+
/**
44+
* @throws InvariantViolation
45+
*/
46+
public function __construct(array $config)
47+
{
48+
if (!isset($config['name'])) {
49+
$config['name'] = $this->tryInferName();
50+
}
51+
52+
Utils::assertValidName($config['name']);
53+
54+
$this->name = $config['name'];
55+
$this->description = $config['description'] ?? null;
56+
$this->config = $config;
57+
}
58+
59+
/**
60+
* @throws InvariantViolation
61+
*
62+
* @return InputObjectType[]
63+
*/
64+
public function getTypes(): array
65+
{
66+
if (null !== $this->types) {
67+
return $this->types;
68+
}
69+
70+
if (($types = $this->config['types'] ?? null) && \is_callable($types)) {
71+
$types = \call_user_func($this->config['types']);
72+
}
73+
74+
if (!\is_array($types)) {
75+
throw new InvariantViolation(
76+
"{$this->name} types must be an Array or a callable which returns an Array."
77+
);
78+
}
79+
80+
return $this->types = $types;
81+
}
82+
83+
/**
84+
* @throws InvariantViolation
85+
*/
86+
public function assertValid()
87+
{
88+
parent::assertValid();
89+
90+
$types = $this->getTypes();
91+
Utils::invariant(\count($types) > 0, "{$this->name} types must not be empty");
92+
93+
$includedTypeNames = [];
94+
foreach ($types as $inputType) {
95+
Utils::invariant(
96+
$inputType instanceof InputType,
97+
"{$this->name} may only contain input types, it cannot contain: %s.",
98+
Utils::printSafe($inputType)
99+
);
100+
Utils::invariant(
101+
!isset($includedTypeNames[$inputType->name]),
102+
"{$this->name} can include {$inputType->name} type only once."
103+
);
104+
$includedTypeNames[$inputType->name] = true;
105+
}
106+
}
107+
108+
/**
109+
* {@inheritdoc}
110+
*/
111+
public function serialize($value)
112+
{
113+
foreach ($this->getTypes() as $type) {
114+
if ($type instanceof LeafType) {
115+
try {
116+
return $type->serialize($value);
117+
} catch (\Exception $e) {
118+
}
119+
}
120+
}
121+
122+
throw new InvariantViolation(sprintf('Types in union cannot represent value: %s', Utils::printSafe($value)));
123+
}
124+
125+
/**
126+
* {@inheritdoc}
127+
*/
128+
public function parseValue($value)
129+
{
130+
foreach ($this->getTypes() as $type) {
131+
if ($type instanceof LeafType) {
132+
try {
133+
return $type->parseValue($value);
134+
} catch (\Exception $e) {
135+
}
136+
}
137+
}
138+
139+
throw new Error(sprintf('Types in union cannot represent value: %s', Utils::printSafeJson($value)));
140+
}
141+
142+
/**
143+
* {@inheritdoc}
144+
*/
145+
public function parseLiteral($valueNode)
146+
{
147+
foreach ($this->getTypes() as $type) {
148+
if ($type instanceof LeafType && null !== $parsed = $type->parseLiteral($valueNode)) {
149+
return $parsed;
150+
}
151+
}
152+
153+
return null;
154+
}
155+
156+
/**
157+
* {@inheritdoc}
158+
*/
159+
public function isValidValue($value): bool
160+
{
161+
foreach ($this->getTypes() as $type) {
162+
if ($type instanceof LeafType && $type->isValidValue($value)) {
163+
return true;
164+
}
165+
}
166+
167+
return false;
168+
}
169+
170+
/**
171+
* {@inheritdoc}
172+
*/
173+
public function isValidLiteral($valueNode): bool
174+
{
175+
foreach ($this->getTypes() as $type) {
176+
if ($type instanceof LeafType && $type->isValidLiteral($valueNode)) {
177+
return true;
178+
}
179+
}
180+
181+
return false;
182+
}
183+
}
Lines changed: 208 additions & 0 deletions
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)