diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php index 2290a5cdd..ba698400a 100644 --- a/src/Utils/SchemaPrinter.php +++ b/src/Utils/SchemaPrinter.php @@ -5,6 +5,9 @@ namespace GraphQL\Utils; use GraphQL\Error\Error; +use GraphQL\Language\AST\ArgumentNode; +use GraphQL\Language\AST\DirectiveNode; +use GraphQL\Language\AST\NodeKind; use GraphQL\Language\Printer; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\EnumType; @@ -29,6 +32,7 @@ use function count; use function explode; use function implode; +use function iterator_to_array; use function ksort; use function mb_strlen; use function preg_match_all; @@ -371,7 +375,7 @@ public static function printType(Type $type, array $options = []): string */ protected static function printScalar(ScalarType $type, array $options): string { - return sprintf('%sscalar %s', static::printDescription($options, $type), $type->name); + return sprintf('%sscalar %s', static::printDescription($options, $type), $type->name) . static::printFieldOrTypeDirectives($type); } /** @@ -410,7 +414,7 @@ protected static function printFields(array $options, $type): string static function (FieldDefinition $f, int $i) use ($options): string { return static::printDescription($options, $f, ' ', $i === 0) . ' ' . $f->name . static::printArgs($options, $f->args, ' ') . ': ' . - (string) $f->getType() . static::printDeprecated($f); + (string) $f->getType() . static::printFieldOrTypeDirectives($f); }, $fields, array_keys($fields) @@ -418,6 +422,56 @@ static function (FieldDefinition $f, int $i) use ($options): string { ); } + /** + * @param FieldDefinition|ScalarType|EnumValueDefinition $fieldOrEnumVal + */ + protected static function printFieldOrTypeDirectives($fieldOrEnumVal): string + { + $serialized = ''; + + if (($fieldOrEnumVal instanceof FieldDefinition || $fieldOrEnumVal instanceof EnumValueDefinition) && $fieldOrEnumVal->deprecationReason !== null) { + $serialized .= static::printDeprecated($fieldOrEnumVal); + } + + if ($fieldOrEnumVal->astNode !== null) { + foreach ($fieldOrEnumVal->astNode->directives as $directive) { + /** @var DirectiveNode $directive */ + if ($directive->name->value === Directive::DEPRECATED_NAME && $fieldOrEnumVal->deprecationReason !== null) { + continue; + } + + $serialized .= ' @' . $directive->name->value; + + if ($directive->arguments->count() === 0) { + continue; + } + + $serialized .= '(' . implode(', ', array_map(static function (ArgumentNode $argument): string { + switch ($argument->value->kind) { + case NodeKind::INT: + $type = Type::int(); + break; + case NodeKind::FLOAT: + $type = Type::float(); + break; + case NodeKind::STRING: + $type = Type::string(); + break; + case NodeKind::BOOLEAN: + $type = Type::boolean(); + break; + default: + return ''; + } + + return $argument->name->value . ': ' . Printer::doPrint(AST::astFromValue($argument->value->value, $type)); + }, iterator_to_array($directive->arguments))) . ')'; + } + } + + return $serialized; + } + /** * @param FieldArgument|EnumValueDefinition $fieldOrEnumVal */ @@ -487,7 +541,7 @@ protected static function printEnumValues(array $values, array $options): string array_map( static function (EnumValueDefinition $value, int $i) use ($options): string { return static::printDescription($options, $value, ' ', $i === 0) . ' ' . - $value->name . static::printDeprecated($value); + $value->name . static::printFieldOrTypeDirectives($value); }, $values, array_keys($values) diff --git a/tests/Utils/SchemaPrinterTest.php b/tests/Utils/SchemaPrinterTest.php index 144b972f4..15136ef1d 100644 --- a/tests/Utils/SchemaPrinterTest.php +++ b/tests/Utils/SchemaPrinterTest.php @@ -6,6 +6,7 @@ use Generator; use GraphQL\Language\DirectiveLocation; +use GraphQL\Language\Parser; use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\EnumType; @@ -1370,4 +1371,25 @@ enum __TypeKind { EOT; self::assertEquals($introspectionSchema, $output); } + + public function testPrintParsedSDLIncludesCustomDirectives(): void + { + $SDL = <<<'EOT' +directive @customDirective(stringArgument: String!, intArgument: Int!, floatArgument: Float!, booleanArgument: Boolean!) on FIELD_DEFINITION + +directive @customDirectiveWithoutArguments on FIELD_DEFINITION + +type Query { + field1: Boolean @customDirective(stringArgument: "test", intArgument: 42, floatArgument: 0.1, booleanArgument: true) + field2: Boolean @customDirectiveWithoutArguments + field3: Boolean +} + +EOT; + $documentNode = Parser::parse($SDL); + $schema = BuildSchema::build($documentNode); + $output = SchemaPrinter::doPrint($schema); + + self::assertEquals($SDL, $output); + } }