Skip to content

Commit

Permalink
Schema validation + tests (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
vladar committed Aug 13, 2017
1 parent d3580e9 commit 34eae0b
Show file tree
Hide file tree
Showing 27 changed files with 4,521 additions and 1,266 deletions.
13 changes: 0 additions & 13 deletions docs/type-system/enum-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,6 @@ $episodeEnum = new EnumType([

which is equivalent of:
```php
$episodeEnum = new EnumType([
'name' => 'Episode',
'description' => 'One of the films in the Star Wars Trilogy',
'values' => [
'NEWHOPE' => 'NEWHOPE',
'EMPIRE' => 'EMPIRE',
'JEDI' => 'JEDI'
]
]);
```

which is in turn equivalent of:
```php
$episodeEnum = new EnumType([
'name' => 'Episode',
'description' => 'One of the films in the Star Wars Trilogy',
Expand Down
17 changes: 15 additions & 2 deletions src/Error/Warning.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ final class Warning

static $warned = [];

static private $warningHandler;

public static function setWarningHandler(callable $warningHandler = null)
{
self::$warningHandler = $warningHandler;
}

static function suppress($suppress = true)
{
if (true === $suppress) {
Expand All @@ -40,15 +47,21 @@ public static function enable($enable = true)

static function warnOnce($errorMessage, $warningId)
{
if ((self::$enableWarnings & $warningId) > 0 && !isset(self::$warned[$warningId])) {
if (self::$warningHandler) {
$fn = self::$warningHandler;
$fn($errorMessage, $warningId);
} else if ((self::$enableWarnings & $warningId) > 0 && !isset(self::$warned[$warningId])) {
self::$warned[$warningId] = true;
trigger_error($errorMessage, E_USER_WARNING);
}
}

static function warn($errorMessage, $warningId)
{
if ((self::$enableWarnings & $warningId) > 0) {
if (self::$warningHandler) {
$fn = self::$warningHandler;
$fn($errorMessage, $warningId);
} else if ((self::$enableWarnings & $warningId) > 0) {
trigger_error($errorMessage, E_USER_WARNING);
}
}
Expand Down
33 changes: 31 additions & 2 deletions src/Type/Definition/CustomScalarType.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php
namespace GraphQL\Type\Definition;

use GraphQL\Utils\Utils;

/**
* Class CustomScalarType
* @package GraphQL\Type\Definition
Expand Down Expand Up @@ -38,7 +40,11 @@ public function serialize($value)
*/
public function parseValue($value)
{
return call_user_func($this->config['parseValue'], $value);
if (isset($this->config['parseValue'])) {
return call_user_func($this->config['parseValue'], $value);
} else {
return null;
}
}

/**
Expand All @@ -47,6 +53,29 @@ public function parseValue($value)
*/
public function parseLiteral(/* GraphQL\Language\AST\ValueNode */ $valueNode)
{
return call_user_func($this->config['parseLiteral'], $valueNode);
if (isset($this->config['parseLiteral'])) {
return call_user_func($this->config['parseLiteral'], $valueNode);
} else {
return null;
}
}

public function assertValid()
{
parent::assertValid();

Utils::invariant(
isset($this->config['serialize']) && is_callable($this->config['serialize']),
"{$this->name} must provide \"serialize\" function. If this custom Scalar " .
'is also used as an input type, ensure "parseValue" and "parseLiteral" ' .
'functions are also provided.'
);
if (isset($this->config['parseValue']) || isset($this->config['parseLiteral'])) {
Utils::invariant(
isset($this->config['parseValue']) && isset($this->config['parseLiteral']) &&
is_callable($this->config['parseValue']) && is_callable($this->config['parseLiteral']),
"{$this->name} must provide both \"parseValue\" and \"parseLiteral\" functions."
);
}
}
}
83 changes: 70 additions & 13 deletions src/Type/Definition/EnumType.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace GraphQL\Type\Definition;

use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Utils\MixedStore;
use GraphQL\Utils\Utils;
Expand All @@ -26,6 +27,11 @@ class EnumType extends Type implements InputType, OutputType, LeafType
*/
private $nameLookup;

/**
* @var array
*/
public $config;

public function __construct($config)
{
if (!isset($config['name'])) {
Expand All @@ -47,28 +53,41 @@ public function __construct($config)

$this->name = $config['name'];
$this->description = isset($config['description']) ? $config['description'] : null;
$this->values = [];
$this->config = $config;
}

/**
* @return EnumValueDefinition[]
*/
public function getValues()
{
if ($this->values === null) {
$this->values = [];
$config = $this->config;

if (!empty($config['values'])) {
foreach ($config['values'] as $name => $value) {
if (!is_array($value)) {
if (isset($config['values'])) {
if (!is_array($config['values'])) {
throw new InvariantViolation("{$this->name} values must be an array");
}
foreach ($config['values'] as $name => $value) {
if (is_string($name)) {
$value = ['name' => $name, 'value' => $value];
if (!is_array($value)) {
throw new InvariantViolation(
"{$this->name}.$name must refer to an associative array with a " .
'"value" key representing an internal value but got: ' . Utils::printSafe($value)
);
}
$value += ['name' => $name, 'value' => $name];
} else if (is_int($name) && is_string($value)) {
$value = ['name' => $value, 'value' => $value];
} else {
throw new InvariantViolation("{$this->name} values must be an array with value names as keys.");
}
$this->values[] = new EnumValueDefinition($value);
}
// value will be equal to name only if 'value' is not set in definition
$this->values[] = new EnumValueDefinition($value + ['name' => $name, 'value' => $name]);
}
}
}

/**
* @return EnumValueDefinition[]
*/
public function getValues()
{
return $this->values;
}

Expand Down Expand Up @@ -168,4 +187,42 @@ private function getNameLookup()
}
return $this->nameLookup;
}

/**
* @throws InvariantViolation
*/
public function assertValid()
{
parent::assertValid();

Utils::invariant(
isset($this->config['values']),
"{$this->name} values must be an array."
);

$values = $this->getValues();

Utils::invariant(
!empty($values),
"{$this->name} values must be not empty."
);
foreach ($values as $value) {
try {
Utils::assertValidName($value->name);
} catch (InvariantViolation $e) {
throw new InvariantViolation(
"{$this->name} has value with invalid name: " .
Utils::printSafe($value->name) . " ({$e->getMessage()})"
);
}
Utils::invariant(
!in_array($value->name, ['true', 'false', 'null']),
"{$this->name}: \"{$value->name}\" can not be used as an Enum value."
);
Utils::invariant(
!isset($value->config['isDeprecated']),
"{$this->name}.{$value->name} should provide \"deprecationReason\" instead of \"isDeprecated\"."
);
}
}
}
12 changes: 11 additions & 1 deletion src/Type/Definition/EnumValueDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,19 @@ class EnumValueDefinition
*/
public $description;

/**
* @var array
*/
public $config;

public function __construct(array $config)
{
Utils::assign($this, $config);
$this->name = isset($config['name']) ? $config['name'] : null;
$this->value = isset($config['value']) ? $config['value'] : null;
$this->deprecationReason = isset($config['deprecationReason']) ? $config['deprecationReason'] : null;
$this->description = isset($config['description']) ? $config['description'] : null;

$this->config = $config;
}

/**
Expand Down
28 changes: 28 additions & 0 deletions src/Type/Definition/FieldArgument.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<?php
namespace GraphQL\Type\Definition;

use GraphQL\Error\InvariantViolation;
use GraphQL\Utils\Utils;


/**
* Class FieldArgument
Expand Down Expand Up @@ -105,4 +108,29 @@ public function defaultValueExists()
{
return $this->defaultValueExists;
}

public function assertValid(FieldDefinition $parentField, Type $parentType)
{
try {
Utils::assertValidName($this->name);
} catch (InvariantViolation $e) {
throw new InvariantViolation(
"{$parentType->name}.{$parentField->name}({$this->name}:) {$e->getMessage()}")
;
}
$type = $this->type;
if ($type instanceof WrappingType) {
$type = $type->getWrappedType(true);
}
Utils::invariant(
$type instanceof InputType,
"{$parentType->name}.{$parentField->name}({$this->name}): argument type must be " .
"Input Type but got: " . Utils::printSafe($this->type)
);
Utils::invariant(
$this->description === null || is_string($this->description),
"{$parentType->name}.{$parentField->name}({$this->name}): argument description type must be " .
"string but got: " . Utils::printSafe($this->description)
);
}
}
Loading

0 comments on commit 34eae0b

Please sign in to comment.