Skip to content

Commit

Permalink
Add JSON scalar
Browse files Browse the repository at this point in the history
  • Loading branch information
spawnia committed Mar 28, 2019
1 parent 1f8101d commit 86ccf59
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 1 deletion.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ This package comes with a bunch of scalars that are ready-to-use and just work o

A [RFC 5321](https://tools.ietf.org/html/rfc5321) compliant email.

### [JSON](src/JSON.php)

Arbitrary data encoded in JavaScript Object Notation. See https://www.json.org/.

### [Mixed](src/Mixed.php)

Loose type that allows any value. Be careful when passing in large `Int` or `Float` literals,
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"php": ">=7.1",
"webonyx/graphql-php": "^0.13",
"spatie/regex": "^1.3",
"egulias/email-validator": "^2.1"
"egulias/email-validator": "^2.1",
"thecodingmachine/safe": "^0.1.14"
},
"require-dev": {
"ext-json": "*",
Expand Down
95 changes: 95 additions & 0 deletions src/JSON.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace MLL\GraphQLScalars;

use Exception;
use GraphQL\Error\Error;
use GraphQL\Language\AST\Node;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
use Safe\Exceptions\JsonException;

class JSON extends ScalarType
{
/**
* The description that is used for schema introspection.
*
* @var string
*/
public $description = 'Arbitrary data encoded in JavaScript Object Notation. See https://www.json.org/.';

/**
* Serializes an internal value to include in a response.
*
* @param mixed $value
*
* @return string
*/
public function serialize($value): string
{
return \Safe\json_encode($value);
}

/**
* Parses an externally provided value (query variable) to use as an input
*
* In the case of an invalid value this method must throw an Exception
*
* @param mixed $value
*
* @return mixed
*
* @throws Error
*/
public function parseValue($value)
{
return $this->decodeJSON($value);
}

/**
* Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input
*
* In the case of an invalid node or value this method must throw an Exception
*
* @param Node $valueNode
* @param mixed[]|null $variables
*
* @return mixed
*
* @throws Exception
*/
public function parseLiteral($valueNode, ?array $variables = null)
{
if(!property_exists($valueNode, 'value')){
throw new Error(
'Can only parse literals that contain a value, got ' . Utils::printSafeJson($valueNode)
);
}

return $this->decodeJSON($valueNode->value);
}

/**
* Try to decode a user-given value into JSON.
*
* @param mixed $value
*
* @return mixed
*
* @throws Error
*/
protected function decodeJSON($value)
{
try {
$parsed = \Safe\json_decode($value);
} catch (JsonException $jsonException) {
throw new Error(
$jsonException->getMessage()
);
}

return $parsed;
}
}
67 changes: 67 additions & 0 deletions tests/JSONTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace Tests;

use GraphQL\Error\Error;
use GraphQL\Language\AST\StringValueNode;
use MLL\GraphQLScalars\JSON;
use PHPUnit\Framework\TestCase;
use Safe\Exceptions\JsonException;

class JSONTest extends TestCase
{
const INVALID_UTF8_SEQUENCE = "\xB1\x31";

public function testSerializeThrowsIfNonEncodableValueIsGiven(): void
{
$this->expectException(JsonException::class);

(new JSON())->serialize(self::INVALID_UTF8_SEQUENCE);
}

public function testSerializeThrowsIfJSONIsInvalid(): void
{
$this->expectException(JsonException::class);

(new JSON())->serialize(self::INVALID_UTF8_SEQUENCE);
}

public function testSerializePassesWhenJSONIsValid(): void
{
$serializedResult = (new JSON())->serialize(1);

$this->assertSame('1', $serializedResult);
}

public function testParseValueThrowsIfJSONIsInvalid(): void
{
$this->expectException(Error::class);

(new JSON())->parseValue('foo');
}

public function testParseValuePassesIfJSONIsValid(): void
{
$this->assertSame(
[1, 2],
(new JSON())->parseValue('[1, 2]')
);
}

public function testParseLiteralThrowsIfNotValidJSON(): void
{
$this->expectException(Error::class);

(new JSON())->parseLiteral(new StringValueNode(['value' => 'foo']));
}

public function testParseLiteralPassesIfJSONIsValid(): void
{
$this->assertSame(
'bar',
(new JSON())->parseLiteral(new StringValueNode(['value' => '{"foo": "bar"}']))->foo
);
}
}

0 comments on commit 86ccf59

Please sign in to comment.