diff --git a/CHANGELOG.md b/CHANGELOG.md index 6037093..d9d7834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## v6.3.0 + +### Added + +- Add a new abstract scalar `IntRange` + ## v6.2.0 ### Added diff --git a/README.md b/README.md index 98a1092..db28538 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,9 @@ A collection of custom scalar types for usage with https://github.com/webonyx/gr ## Installation - composer require mll-lab/graphql-php-scalars +```sh +composer require mll-lab/graphql-php-scalars +``` ## Usage @@ -43,6 +45,27 @@ A datetime string with format `Y-m-d\TH:i:s.uP`, e.g. `2020-04-20T16:20:04+04:00 A [RFC 5321](https://tools.ietf.org/html/rfc5321) compliant email. +### [IntRange](src/IntRange.php) + +Allows defining numeric scalars where the values must lie between a defined minimum and maximum. + +```php +use MLL\GraphQLScalars\IntRange; + +final class UpToADozen extends IntRange +{ + protected function min(): int + { + return 1; + } + + protected function max(): int + { + return 12; + } +} +``` + ### [JSON](src/JSON.php) Arbitrary data encoded in JavaScript Object Notation. See https://www.json.org. @@ -131,13 +154,11 @@ use MLL\GraphQLScalars\Regex; // The name is implicitly set through the class name here class HexValue extends Regex { - /** - * The description that is used for schema introspection. - */ - public ?string $description = <<<'DESCRIPTION' -A hexadecimal color is specified with: `#RRGGBB`, where `RR` (red), `GG` (green) and `BB` (blue) -are hexadecimal integers between `00` and `FF` specifying the intensity of the color. -DESCRIPTION; + /** The description that is used for schema introspection. */ + public ?string $description = /** @lang Markdown */<<<'MARKDOWN' + A hexadecimal color is specified with: `#RRGGBB`, where `RR` (red), `GG` (green) and `BB` (blue) + are hexadecimal integers between `00` and `FF` specifying the intensity of the color. + MARKDOWN; public static function regex(): string { @@ -160,13 +181,11 @@ use MLL\GraphQLScalars\StringScalar; $coolName = StringScalar::make( 'CoolName', 'A name that is most definitely cool.', - static function (string $name): bool { - return in_array($name, [ - 'Vladar', - 'Benedikt', - 'Christopher', - ]); - } + static fn (string $name): bool => in_array($name, [ + 'Vladar', + 'Benedikt', + 'Christopher', + ]), ); ``` diff --git a/src/IntRange.php b/src/IntRange.php new file mode 100644 index 0000000..fbbd47c --- /dev/null +++ b/src/IntRange.php @@ -0,0 +1,59 @@ +isValueInExpectedRange($value)) { + return $value; + } + + $notInRange = Utils::printSafe($value); + throw new \InvalidArgumentException("Value not in range: {$notInRange}."); + } + + public function parseValue($value) + { + if (is_int($value) && $this->isValueInExpectedRange($value)) { + return $value; + } + + $notInRange = Utils::printSafe($value); + throw new Error("Value not in range: {$notInRange}."); + } + + public function parseLiteral(Node $valueNode, ?array $variables = null) + { + if ($valueNode instanceof IntValueNode) { + $value = (int) $valueNode->value; + if ($this->isValueInExpectedRange($value)) { + return $value; + } + } + + $notInRange = Printer::doPrint($valueNode); + throw new Error("Value not in range: {$notInRange}.", $valueNode); + } + + private function isValueInExpectedRange(int $value): bool + { + return $value <= static::max() && $value >= static::min(); + } +} diff --git a/tests/IntRangeTest.php b/tests/IntRangeTest.php new file mode 100644 index 0000000..c79a298 --- /dev/null +++ b/tests/IntRangeTest.php @@ -0,0 +1,48 @@ +expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Value not in range: "12".'); + + (new UpToADozen())->serialize('12'); + } + + public function testSerializeThrowsIfInvalid(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Value not in range: 13.'); + + (new UpToADozen())->serialize(13); + } + + public function testSerializePassesWhenValid(): void + { + $serializedResult = (new UpToADozen())->serialize(12); + + self::assertSame(12, $serializedResult); + } + + public function testParseValueThrowsIfInvalid(): void + { + $this->expectException(Error::class); + $this->expectExceptionMessage('Value not in range: 13.'); + + (new UpToADozen())->parseValue(13); + } + + public function testParseValuePassesIfValid(): void + { + self::assertSame( + 12, + (new UpToADozen())->parseValue(12) + ); + } +} diff --git a/tests/MixedScalarTest.php b/tests/MixedScalarTest.php index c2acd38..913d1f7 100644 --- a/tests/MixedScalarTest.php +++ b/tests/MixedScalarTest.php @@ -27,9 +27,7 @@ public function setUp(): void 'fields' => [ 'foo' => [ 'type' => $mixed, - 'resolve' => function ($root, array $args) { - return reset($args); - }, + 'resolve' => fn ($root, array $args) => reset($args), 'args' => [ 'bar' => $mixed, ], diff --git a/tests/NullScalarTest.php b/tests/NullScalarTest.php index 797f9a4..b86d213 100644 --- a/tests/NullScalarTest.php +++ b/tests/NullScalarTest.php @@ -30,18 +30,14 @@ public function setUp(): void 'fields' => [ 'foo' => [ 'type' => $null, - 'resolve' => function ($root, array $args) { - return reset($args); - }, + 'resolve' => fn ($root, array $args) => reset($args), 'args' => [ 'bar' => $null, ], ], 'mixed' => [ 'type' => $null, - 'resolve' => function () { - return $this->return; - }, + 'resolve' => fn () => $this->return, ], ], ]) diff --git a/tests/StringScalarTest.php b/tests/StringScalarTest.php index 5f72975..06deb76 100644 --- a/tests/StringScalarTest.php +++ b/tests/StringScalarTest.php @@ -46,9 +46,7 @@ protected function isValid(string $stringValue): bool StringScalar::make( 'MyStringScalar', 'Bar', - function (string $string) { - return $string === 'foo'; - } + fn (string $string) => $string === 'foo' ), ]; } diff --git a/tests/UpToADozen.php b/tests/UpToADozen.php new file mode 100644 index 0000000..9eb3232 --- /dev/null +++ b/tests/UpToADozen.php @@ -0,0 +1,18 @@ +