Skip to content

Commit 8671c57

Browse files
committed
Add Mixed type
1 parent 979b57a commit 8671c57

File tree

3 files changed

+301
-1
lines changed

3 files changed

+301
-1
lines changed

src/Email.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
class Email extends StringScalar
1111
{
1212
/** @var string */
13-
public $description = 'A valid RFC 5321 compliant email.';
13+
public $description = 'A RFC 5321 compliant email.';
1414

1515
/**
1616
* Check if the given string is a valid email.

src/Mixed.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MLL\GraphQLScalars;
6+
7+
use GraphQL\Language\AST\FloatValueNode;
8+
use GraphQL\Language\AST\IntValueNode;
9+
use GraphQL\Language\AST\ListValueNode;
10+
use GraphQL\Language\AST\ObjectFieldNode;
11+
use GraphQL\Language\AST\ObjectValueNode;
12+
use GraphQL\Language\AST\ValueNode;
13+
use GraphQL\Type\Definition\ScalarType;
14+
15+
class Mixed extends ScalarType
16+
{
17+
/** @var string */
18+
public $description = <<<EOT
19+
Loose type that allows any value. Be careful when passing in large Int or Float literals,
20+
as they may not be parsed correctly on the server side. Use String literals if you are
21+
dealing with really large numbers to be on the safe side.
22+
EOT;
23+
24+
/**
25+
* Serializes an internal value to include in a response.
26+
*
27+
* @param \mixed $value
28+
*
29+
* @return \mixed
30+
*/
31+
public function serialize($value)
32+
{
33+
return $value;
34+
}
35+
36+
/**
37+
* Parses an externally provided value (query variable) to use as an input
38+
*
39+
* In the case of an invalid value this method must throw an Exception
40+
*
41+
* @param \mixed $value
42+
*
43+
* @return \mixed
44+
*/
45+
public function parseValue($value)
46+
{
47+
return $value;
48+
}
49+
50+
/**
51+
* Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input.
52+
*
53+
* In the case of an invalid node or value this method must throw an Exception
54+
*
55+
* @param ValueNode $valueNode
56+
* @param array|null $variables
57+
*
58+
* @return \mixed
59+
*/
60+
public function parseLiteral($valueNode, array $variables = null)
61+
{
62+
if ($valueNode instanceof IntValueNode) {
63+
// This is a potentially lossy conversion as GraphQL Int literals
64+
// may be arbitrarily large, whereas PHP ints are limited in size
65+
$value = (int)$valueNode->value;
66+
}
67+
68+
if ($valueNode instanceof FloatValueNode) {
69+
// This is a potentially lossy conversion as GraphQL Float literals
70+
// may be arbitrarily large, whereas PHP floats are limited in size
71+
$value = (float)$valueNode->value;
72+
}
73+
74+
if ($valueNode instanceof ListValueNode) {
75+
$value = [];
76+
foreach ($valueNode->values as $singleValue) {
77+
$value [] = $this->parseLiteral($singleValue);
78+
}
79+
}
80+
81+
if ($valueNode instanceof ObjectValueNode) {
82+
$value = [];
83+
/** @var ObjectFieldNode $singleValue */
84+
foreach ($valueNode->fields as $singleValue) {
85+
$value[$singleValue->name->value] = $this->parseLiteral($singleValue->value);
86+
}
87+
}
88+
89+
return $value ?? $valueNode->value;
90+
}
91+
}

tests/MixedTest.php

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests;
6+
7+
use GraphQL\Executor\ExecutionResult;
8+
use GraphQL\GraphQL;
9+
use GraphQL\Type\Definition\ObjectType;
10+
use GraphQL\Type\Schema;
11+
use GraphQL\Type\SchemaConfig;
12+
use GraphQL\Utils\SchemaPrinter;
13+
use MLL\GraphQLScalars\Mixed;
14+
15+
class MixedTest extends \PHPUnit\Framework\TestCase
16+
{
17+
/** @var Schema */
18+
protected $schema;
19+
20+
public function setUp()
21+
{
22+
parent::setUp();
23+
24+
$mixed = new Mixed;
25+
26+
$schemaConfig = new SchemaConfig;
27+
$schemaConfig->setQuery(
28+
new ObjectType([
29+
'name' => 'Query',
30+
'fields' => [
31+
'foo' => [
32+
'type' => $mixed,
33+
'resolve' => function ($root, $args) {
34+
return reset($args);
35+
},
36+
'args' => [
37+
'bar' => $mixed,
38+
],
39+
],
40+
],
41+
])
42+
);
43+
44+
$this->schema = new Schema($schemaConfig);
45+
}
46+
47+
/**
48+
* @dataProvider singleValues
49+
*
50+
* @param $value
51+
*/
52+
public function testSerializePassesThroughAnything($value)
53+
{
54+
$this->assertSame(
55+
$value,
56+
(new Mixed())->serialize(
57+
$value
58+
)
59+
);
60+
}
61+
62+
/**
63+
* @dataProvider singleValues
64+
*
65+
* @param $value
66+
*/
67+
public function testParseValuePassesThroughAnything($value)
68+
{
69+
$this->assertSame(
70+
$value,
71+
(new Mixed())->serialize(
72+
$value
73+
)
74+
);
75+
}
76+
77+
public function singleValues()
78+
{
79+
return [
80+
[null],
81+
[new class {}],
82+
[[]],
83+
[function(){}],
84+
[[$this, 'singleValues']]
85+
];
86+
}
87+
88+
/**
89+
* @dataProvider literalToPhpMap
90+
*
91+
* @param string $graphQLLiteral
92+
* @param string $jsonLiteral
93+
* @param $expected
94+
*/
95+
public function testCastsValuesIntoAppropriatePhpValue(string $graphQLLiteral, string $jsonLiteral, $expected)
96+
{
97+
$graphqlResult = $this->executeQueryWithLiteral($graphQLLiteral);
98+
$jsonResult = $this->executeQueryWithJsonVariable($jsonLiteral);
99+
100+
$this->assertSame(
101+
$expected,
102+
$graphqlResult->data['foo']
103+
);
104+
105+
// Ensure that values provided as JSON have the same result as GraphQL literals
106+
$this->assertSame(
107+
$graphqlResult->data,
108+
$jsonResult->data
109+
);
110+
}
111+
112+
/**
113+
* Provides a GraphQL literal, a Json literal and the expected PHP value.
114+
*
115+
* @return array
116+
*/
117+
public function literalToPhpMap()
118+
{
119+
return [
120+
['1', '1', 1],
121+
['"asdf"', '"asdf"', 'asdf'],
122+
['true','true', true],
123+
['123.321', '123.321', 123.321],
124+
['null', 'null', null],
125+
['[1, 2]', '[1, 2]', [1, 2]],
126+
[
127+
'{a: 1}',
128+
'{"a": 1}',
129+
['a' => 1],
130+
],
131+
[
132+
'
133+
{
134+
a: [
135+
{
136+
b: "c"
137+
}
138+
]
139+
}',
140+
'
141+
{
142+
"a": [
143+
{
144+
"b": "c"
145+
}
146+
]
147+
}',
148+
[
149+
'a' => [
150+
[
151+
'b' => 'c',
152+
],
153+
],
154+
],
155+
],
156+
];
157+
}
158+
159+
/**
160+
* @param string $literal
161+
*
162+
* @return ExecutionResult
163+
*/
164+
protected function executeQueryWithLiteral(string $literal): ExecutionResult
165+
{
166+
$query = "
167+
{
168+
foo(bar: {$literal})
169+
}
170+
";
171+
var_dump(
172+
SchemaPrinter::doPrint($this->schema));
173+
174+
return GraphQL::executeQuery(
175+
$this->schema,
176+
$query
177+
);
178+
}
179+
180+
/**
181+
* @param string $jsonLiteral
182+
*
183+
* @return ExecutionResult
184+
*/
185+
protected function executeQueryWithJsonVariable(string $jsonLiteral): ExecutionResult
186+
{
187+
$query = '
188+
query Foo($var: Mixed) {
189+
foo(bar: $var)
190+
}
191+
';
192+
193+
$json = json_decode("
194+
{
195+
\"var\": $jsonLiteral
196+
}
197+
",
198+
true
199+
);
200+
201+
return GraphQL::executeQuery(
202+
$this->schema,
203+
$query,
204+
null,
205+
null,
206+
$json
207+
);
208+
}
209+
}

0 commit comments

Comments
 (0)