diff --git a/composer.json b/composer.json index e65ae2d8..39f0a7dd 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ }, "autoload": { "files": [ - "./src/Type/functions.php", + "./src/Type/definition.php", + "./src/Type/scalars.php", "./src/Util/functions.php" ], "psr-4": { @@ -26,7 +27,8 @@ }, "autoload-dev": { "files": [ - "./src/Type/functions.php", + "./src/Type/definition.php", + "./src/Type/scalars.php", "./src/Util/functions.php" ], "psr-4": { diff --git a/src/Type/Definition/Scalar/BooleanType.php b/src/Type/Definition/Scalar/BooleanType.php deleted file mode 100644 index 4da4edbb..00000000 --- a/src/Type/Definition/Scalar/BooleanType.php +++ /dev/null @@ -1,43 +0,0 @@ -setName(TypeEnum::BOOLEAN); - $this->setDescription('The `Boolean` scalar type represents `true` or `false`.'); - $this->setSerialize(function ($value) { - return $this->coerceValue($value); - }); - $this->setParseValue(function ($value) { - return $this->coerceValue($value); - }); - $this->setParseLiteral(function (NodeInterface $astNode) { - return $astNode->getKind() === KindEnum::BOOLEAN ? $astNode->getValue() : null; - }); - } - - /** - * @param $value - * @return bool - * @throws \TypeError - */ - private function coerceValue($value): bool - { - if (!is_scalar($value)) { - throw new \TypeError(sprintf('Boolean cannot represent a non-scalar value: %s', $value)); - } - - return (bool)$value; - } -} diff --git a/src/Type/Definition/Scalar/FloatType.php b/src/Type/Definition/Scalar/FloatType.php deleted file mode 100644 index e53e0a14..00000000 --- a/src/Type/Definition/Scalar/FloatType.php +++ /dev/null @@ -1,51 +0,0 @@ -setName(TypeEnum::FLOAT); - $this->setDescription( - 'The `Float` scalar type represents signed double-precision fractional ' . - 'values as specified by ' . - '[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).' - ); - $this->setSerialize(function ($value) { - return $this->coerceValue($value); - }); - $this->setParseValue(function ($value) { - return $this->coerceValue($value); - }); - $this->setParseLiteral(function (NodeInterface $astNode) { - return in_array($astNode->getKind(), [KindEnum::FLOAT, KindEnum::INT]) ? $astNode->getValue() : null; - }); - } - - /** - * @param $value - * @return float - * @throws \TypeError - */ - private function coerceValue($value): float - { - if ($value === '') { - throw new \TypeError('Float cannot represent non numeric value: (empty string)'); - } - - if (is_numeric($value) || \is_bool($value)) { - return (float)$value; - } - - throw new \TypeError(sprintf('Float cannot represent non numeric value: %s', $value)); - } -} diff --git a/src/Type/Definition/Scalar/IDType.php b/src/Type/Definition/Scalar/IDType.php deleted file mode 100644 index 605e0733..00000000 --- a/src/Type/Definition/Scalar/IDType.php +++ /dev/null @@ -1,24 +0,0 @@ -setName(TypeEnum::ID); - $this->setDescription( - 'The `ID` scalar type represents a unique identifier, often used to ' . - 'refetch an object or as key for a cache. The ID type appears in a JSON ' . - 'response as a String; however, it is not intended to be human-readable. ' . - 'When expected as an input type, any string (such as `"4"`) or integer ' . - '(such as `4`) input value will be accepted as an ID.' - ); - } -} diff --git a/src/Type/Definition/Scalar/IntType.php b/src/Type/Definition/Scalar/IntType.php deleted file mode 100644 index 53848523..00000000 --- a/src/Type/Definition/Scalar/IntType.php +++ /dev/null @@ -1,64 +0,0 @@ -setName(TypeEnum::INT); - $this->setDescription( - 'The `Int` scalar type represents non-fractional signed whole numeric ' . - 'values. Int can represent values between -(2^31) and 2^31 - 1.' - ); - $this->setSerialize(function ($value) { - return $this->coerceValue($value); - }); - $this->setParseValue(function ($value) { - return $this->coerceValue($value); - }); - $this->setParseLiteral(function (NodeInterface $astNode) { - return $astNode->getKind() === KindEnum::INT ? $astNode->getValue() : null; - }); - } - - /** - * @param $value - * @return int - * @throws \TypeError - */ - private function coerceValue($value) - { - if ($value === '') { - throw new \TypeError('Int cannot represent non 32-bit signed integer value: (empty string)'); - } - - if (\is_bool($value)) { - $value = (int)$value; - } - - if (!\is_int($value) || $value > self::MAX_INT || $value < self::MIN_INT) { - throw new \TypeError(sprintf('Int cannot represent non 32-bit signed integer value: %s', $value)); - } - - $intValue = (int)$value; - $floatValue = (float)$value; - - if ($floatValue != $intValue || floor($floatValue) !== $floatValue) { - throw new \TypeError(sprintf('Int cannot represent non-integer value: %s', $value)); - } - - return $intValue; - } -} diff --git a/src/Type/Definition/Scalar/StringType.php b/src/Type/Definition/Scalar/StringType.php deleted file mode 100644 index 45bd745a..00000000 --- a/src/Type/Definition/Scalar/StringType.php +++ /dev/null @@ -1,60 +0,0 @@ -setName(TypeEnum::STRING); - $this->setDescription( - 'The `String` scalar type represents textual data, represented as UTF-8 ' . - 'character sequences. The String type is most often used by GraphQL to ' . - 'represent free-form human-readable text.' - ); - $this->setSerialize(function ($value) { - return $this->coerceValue($value); - }); - $this->setParseValue(function ($value) { - return $this->coerceValue($value); - }); - $this->setParseLiteral(function (NodeInterface $astNode) { - return $astNode->getKind() === KindEnum::STRING ? $astNode->getValue() : null; - }); - } - - /** - * @param $value - * @return string - * @throws \TypeError - */ - private function coerceValue($value): string - { - if ($value === null) { - return 'null'; - } - - if ($value === true) { - return 'true'; - } - - if ($value === false) { - return 'false'; - } - - if (!is_scalar($value)) { - throw new \TypeError('String cannot represent a non-scalar value'); - } - - return (string)$value; - } -} diff --git a/src/Type/Definition/Scalar/ScalarType.php b/src/Type/Definition/ScalarType.php similarity index 98% rename from src/Type/Definition/Scalar/ScalarType.php rename to src/Type/Definition/ScalarType.php index 3c7b40b8..50d2b95b 100644 --- a/src/Type/Definition/Scalar/ScalarType.php +++ b/src/Type/Definition/ScalarType.php @@ -1,6 +1,6 @@ TypeEnum::BOOLEAN, + 'description' => 'The `Boolean` scalar type represents `true` or `false`.', + 'serialize' => function ($value) { + return coerceBoolean($value); + }, + 'parseValue' => function ($value) { + return coerceBoolean($value); + }, + 'parseLiteral' => function (NodeInterface $astNode) { + return $astNode->getKind() === KindEnum::BOOLEAN ? $astNode->getValue() : null; + }, + ]); + } + + return $instance; +} + +/** + * @param $value + * @return float + * @throws \TypeError + */ +function coerceFloat($value): float +{ + if ($value === '') { + throw new \TypeError('Float cannot represent non numeric value: (empty string)'); + } + + if (is_numeric($value) || \is_bool($value)) { + return (float)$value; + } + + throw new \TypeError(sprintf('Float cannot represent non numeric value: %s', $value)); +} + +/** + * @return ScalarType + */ +function GraphQLFloat(): ScalarType +{ + static $instance = null; + + if (!$instance) { + $instance = GraphQLScalarType([ + 'name' => TypeEnum::FLOAT, + 'description' => + 'The `Float` scalar type represents signed double-precision fractional ' . + 'values as specified by ' . + '[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).', + 'serialize' => function ($value) { + return coerceFloat($value); + }, + 'parseValue' => function ($value) { + return coerceFloat($value); + }, + 'parseLiteral' => function (NodeInterface $astNode) { + return in_array($astNode->getKind(), [KindEnum::FLOAT, KindEnum::INT]) ? $astNode->getValue() : null; + }, + ]); + } + + return $instance; +} + +/** + * @param $value + * @return int + * @throws \TypeError + */ +function coerceInt($value) +{ + if ($value === '') { + throw new \TypeError('Int cannot represent non 32-bit signed integer value: (empty string)'); + } + + if (\is_bool($value)) { + $value = (int)$value; + } + + if (!\is_int($value) || $value > MAX_INT || $value < MIN_INT) { + throw new \TypeError(sprintf('Int cannot represent non 32-bit signed integer value: %s', $value)); + } + + $intValue = (int)$value; + $floatValue = (float)$value; + + if ($floatValue != $intValue || floor($floatValue) !== $floatValue) { + throw new \TypeError(sprintf('Int cannot represent non-integer value: %s', $value)); + } + + return $intValue; +} + +/** + * @return ScalarType + */ +function GraphQLInt(): ScalarType +{ + static $instance = null; + + if (!$instance) { + $instance = GraphQLScalarType([ + 'name' => TypeEnum::INT, + 'description' => + 'The `Int` scalar type represents non-fractional signed whole numeric ' . + 'values. Int can represent values between -(2^31) and 2^31 - 1.', + 'serialize' => function ($value) { + return coerceInt($value); + }, + 'parseValue' => function ($value) { + return coerceInt($value); + }, + 'parseLiteral' => function (NodeInterface $astNode) { + return $astNode->getKind() === KindEnum::INT ? $astNode->getValue() : null; + }, + ]); + } + + return $instance; +} + +/** + * @param $value + * @return string + * @throws \TypeError + */ +function coerceString($value): string +{ + if ($value === null) { + return 'null'; + } + + if ($value === true) { + return 'true'; + } + + if ($value === false) { + return 'false'; + } + + if (!is_scalar($value)) { + throw new \TypeError('String cannot represent a non-scalar value'); + } + + return (string)$value; +} + +/** + * @return ScalarType + */ +function GraphQLID(): ScalarType +{ + static $instance = null; + + if (!$instance) { + $instance = GraphQLScalarType([ + 'name' => TypeEnum::ID, + 'description' => + 'The `ID` scalar type represents a unique identifier, often used to ' . + 'refetch an object or as key for a cache. The ID type appears in a JSON ' . + 'response as a String; however, it is not intended to be human-readable. ' . + 'When expected as an input type, any string (such as `"4"`) or integer ' . + '(such as `4`) input value will be accepted as an ID.', + 'serialize' => function ($value) { + return coerceString($value); + }, + 'parseValue' => function ($value) { + return coerceString($value); + }, + 'parseLiteral' => function (NodeInterface $astNode) { + return in_array($astNode->getKind(), [KindEnum::STRING, KindEnum::INT]) ? $astNode->getValue() : null; + }, + ]); + } + + return $instance; +} + +/** + * @return ScalarType + */ +function GraphQLString(): ScalarType +{ + static $instance = null; + + if (!$instance) { + $instance = GraphQLScalarType([ + 'name' => TypeEnum::STRING, + 'description' => + 'The `String` scalar type represents textual data, represented as UTF-8 ' . + 'character sequences. The String type is most often used by GraphQL to ' . + 'represent free-form human-readable text.', + 'serialize' => function ($value) { + return coerceString($value); + }, + 'parseValue' => function ($value) { + return coerceString($value); + }, + 'parseLiteral' => function (NodeInterface $astNode) { + return $astNode->getKind() === KindEnum::STRING ? $astNode->getValue() : null; + }, + ]); + } + + return $instance; +} + +/** + * @param TypeInterface $type + * @return bool + */ +function isSpecifiedScalarType(TypeInterface $type): bool +{ + return \in_array($type->getName(), [ + TypeEnum::BOOLEAN, + TypeEnum::FLOAT, + TypeEnum::ID, + TypeEnum::INT, + TypeEnum::STRING, + ], true); +} diff --git a/tests/Functional/Type/DefinitionTest.php b/tests/Functional/Type/DefinitionTest.php index a89a4c2c..5264b6d1 100644 --- a/tests/Functional/Type/DefinitionTest.php +++ b/tests/Functional/Type/DefinitionTest.php @@ -10,8 +10,7 @@ use Digia\GraphQL\Type\Definition\InterfaceType; use Digia\GraphQL\Type\Definition\ListType; use Digia\GraphQL\Type\Definition\ObjectType; -use Digia\GraphQL\Type\Definition\Scalar\ScalarType; -use Digia\GraphQL\Type\Definition\Scalar\StringType; +use Digia\GraphQL\Type\Definition\ScalarType; use Digia\GraphQL\Type\Definition\TypeEnum; use Digia\GraphQL\Type\Definition\UnionType; use Digia\GraphQL\Type\Schema\Schema; @@ -239,7 +238,7 @@ public function testQuerySchema() $titleField = $articleFieldType->getFields()['title']; $this->assertEquals('title', $titleField->getName()); - $this->assertInstanceOf(StringType::class, $titleField->getType()); + $this->assertEquals(GraphQLString(), $titleField->getType()); $this->assertEquals('String', $titleField->getType()->getName()); $authorField = $articleFieldType->getFields()['author']; @@ -333,7 +332,7 @@ public function testObjectWithDeprecatedField() $field = $typeWithDeprecatedField->getFields()['bar']; - $this->assertInstanceOf(StringType::class, $field->getType()); + $this->assertEquals(GraphQLString(), $field->getType()); $this->assertEquals('A terrible reason', $field->getDeprecationReason()); $this->assertTrue($field->isDeprecated()); $this->assertEquals('bar', $field->getName()); @@ -529,9 +528,9 @@ public function testDoesNotMutatePassedFieldDefinitions() $this->assertEquals($testInputObject2->getFields(), $testInputObject1->getFields()); - $this->assertInstanceOf(StringType::class, $fields['field1']['type']); - $this->assertInstanceOf(StringType::class, $fields['field2']['type']); - $this->assertInstanceOf(StringType::class, $fields['field2']['args']['id']['type']); + $this->assertEquals(GraphQLString(), $fields['field1']['type']); + $this->assertEquals(GraphQLString(), $fields['field2']['type']); + $this->assertEquals(GraphQLString(), $fields['field2']['args']['id']['type']); } // TODO: Assess if we want to test "accepts an Object type with a field function". @@ -1122,7 +1121,7 @@ public function testAcceptsAnInputObjectTypeWithFields() ]); $field = $inputObjectType->getFields()['f']; - $this->assertInstanceOf(StringType::class, !$field ?: $field->getType()); + $this->assertEquals(GraphQLString(), $field->getType()); } /** @@ -1138,7 +1137,7 @@ public function testAcceptsAnInputObjectTypeWithAFieldFunction() ]); $field = $inputObjectType->getFields()['f']; - $this->assertInstanceOf(StringType::class, !$field ?: $field->getType()); + $this->assertEquals(GraphQLString(), $field->getType()); } /**