Skip to content

Add support for @specifiedBy directive#1913

Open
Copilot wants to merge 18 commits into
masterfrom
copilot/rebase-on-master-address-review-comment
Open

Add support for @specifiedBy directive#1913
Copilot wants to merge 18 commits into
masterfrom
copilot/rebase-on-master-address-review-comment

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 13, 2026

graphql-php parsed and validated @specifiedBy in SDL without ever doing anything with it. The URL was silently discarded, so custom scalar types had no way to carry their specification link through the library's own toolchain: it disappeared on introspection, on schema printing, on client-schema reconstruction, and on schema extension. Developers who relied on @specifiedBy to document their scalar types (e.g. scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122")) received no error and no warning—the information simply vanished, making schema round-trips lossy and introspection-based tooling blind to the spec link.

The GraphQL specification and the reference implementation (graphql-js) have shipped @specifiedBy for several years. Closing this gap matters because:

  • Interoperability — schemas authored in other tools or languages include @specifiedBy; importing them via SDL or introspection into graphql-php must not silently drop data.
  • Self-documenting APIs — clients and schema-explorer tools query __type { specifiedByURL } to link scalar types to their machine-readable specification. Without this, graphql-php APIs are less discoverable.
  • Correctness of utilitiesBuildSchema, BuildClientSchema, SchemaExtender, and SchemaPrinter are all supposed to preserve the full semantics of a schema; losing specifiedByURL was a silent correctness bug in each of them.

This PR brings every layer of the library into alignment: the type definition, introspection, SDL parsing, schema extension, and printing all now treat specifiedByURL as a first-class, preserved property—matching graphql-js behaviour and the specification.

A latent bug in QueryComplexity was found and fixed in scope: directiveExcludesField() returned early on the first unrecognised directive, so a field annotated @foo @skip(if: true) was incorrectly included in complexity calculations. Variable coercion is now lazy and cached so it only runs when @include or @skip is actually present.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class support for the GraphQL @specifiedBy directive across schema construction, extension, and printing, so custom scalars can carry/emit a specifiedByURL and the directive appears among built-in directives (including introspection schema output).

Changes:

  • Introduces Directive::specifiedByDirective() / constants and registers it as a built-in directive; ensures BuildSchema includes it by default.
  • Threads specifiedByURL through scalar type config, SDL building (ASTDefinitionBuilder), schema extension (SchemaExtender), and SDL printing (SchemaPrinter).
  • Updates tests to reflect the new built-in directive and enabled specifiedBy-related behaviors.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Type/Definition/Directive.php Adds built-in @specifiedBy directive definition and constants.
src/Type/Definition/ScalarType.php Adds specifiedByURL to scalar config + instance property.
src/Type/Definition/CustomScalarType.php Updates PHPDoc config types to include specifiedByURL.
src/Utils/ASTDefinitionBuilder.php Extracts specifiedByURL from scalar AST directives and passes into scalar config.
src/Utils/BuildSchema.php Ensures @specifiedBy is included when not explicitly declared in SDL.
src/Utils/SchemaExtender.php Propagates specifiedByURL when extending scalars (incl. reading from extension AST).
src/Utils/SchemaPrinter.php Prints @specifiedBy(url: ...) on scalar definitions when present.
src/Language/Printer.php Adjusts string printing to avoid escaping unicode (and slashes) in JSON encoding.
src/Validator/Rules/QueryComplexity.php Attempts to ignore @specifiedBy during directive-based exclusion logic.
tests/Type/IntrospectionTest.php Updates expected introspection result to include specifiedBy directive.
tests/Utils/BreakingChangesFinderTest.php Includes specifiedByDirective() in removed-directive detection test.
tests/Utils/BuildSchemaTest.php Updates directive counts and enables specifiedBy-related schema build tests.
tests/Utils/SchemaExtenderTest.php Enables and extends scalar extension tests for @specifiedBy.
tests/Utils/SchemaPrinterTest.php Updates expected introspection schema output to include @specifiedBy directive definition.
tests/Validator/ValidatorTestCase.php Ensures test schema includes all built-in/internal directives via getInternalDirectives().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Validator/Rules/QueryComplexity.php Outdated
Comment on lines +186 to +190
return false;
}

if ($directiveNode->name->value === Directive::SPECIFIED_BY_NAME) {
return false;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's cover correct functionality with a test case.

Comment thread src/Utils/ASTDefinitionBuilder.php Outdated
Comment on lines +419 to +426
* Given a collection of directives, returns the string value for the specifiedBy URL.
*
* @param ScalarTypeDefinitionNode $node
*
* @throws \Exception
* @throws InvariantViolation
*/
private function getSpecifiedByURL(Node $node): ?string
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, clean up.

…ExcludesField

Agent-Logs-Url: https://github.com/webonyx/graphql-php/sessions/67e190bd-9ad8-4bc7-ac58-927893b6f552

Co-authored-by: spawnia <12158000+spawnia@users.noreply.github.com>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.

Benchmark suite Current: ccb5788 Previous: 516401c Ratio
DeferredBench::benchManyNestedDeferreds 21.856 ms 12.8 ms 1.71

This comment was automatically generated by workflow using github-action-benchmark.

…f), descriptions (behavior not behaviour)

Agent-Logs-Url: https://github.com/webonyx/graphql-php/sessions/5536e145-b412-4cca-9f9a-847ccaa65158

Co-authored-by: spawnia <12158000+spawnia@users.noreply.github.com>
Copilot AI and others added 2 commits May 14, 2026 07:35
…URL option, BuildClientSchema round-trip, and lazy variable coercion

Agent-Logs-Url: https://github.com/webonyx/graphql-php/sessions/056e7e07-5cd1-4d0a-8278-3c0e03334605

Co-authored-by: spawnia <12158000+spawnia@users.noreply.github.com>
…cessary precondition check

Agent-Logs-Url: https://github.com/webonyx/graphql-php/sessions/056e7e07-5cd1-4d0a-8278-3c0e03334605

Co-authored-by: spawnia <12158000+spawnia@users.noreply.github.com>
Comment thread tests/Utils/BuildClientSchemaTest.php Outdated

public function testRoundTripPreservesSpecifiedByURL(): void
{
$scalarType = new \GraphQL\Type\Definition\CustomScalarType([
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use imports

Comment thread tests/Utils/BuildClientSchemaTest.php Outdated
$clientSchema = BuildClientSchema::build($introspection);

$uuidFromClient = $clientSchema->getType('UUID');
self::assertInstanceOf(\GraphQL\Type\Definition\ScalarType::class, $uuidFromClient);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, imports

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Comment thread src/Type/Definition/ScalarType.php Outdated
{
$this->name = $config['name'] ?? $this->inferName();
$this->description = $config['description'] ?? $this->description ?? null;
$this->specifiedByURL = $config['specifiedByURL'] ?? null;
Comment thread docs/type-definitions/directives.md Outdated
query in any way the server desires.

GraphQL specification includes two built-in directives:
GraphQL specification includes the following built-in directives:
Comment thread src/Language/Printer.php Outdated
}

return json_encode($node->value, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
// Do not escape unicode or slashes in order to keep URLs valid
…rammar, Printer comment

Agent-Logs-Url: https://github.com/webonyx/graphql-php/sessions/ec97a2ec-237f-4065-aa53-2871b3ca7e17

Co-authored-by: spawnia <12158000+spawnia@users.noreply.github.com>
@spawnia spawnia marked this pull request as ready for review May 14, 2026 09:05
@spawnia spawnia requested a review from ruudk May 14, 2026 09:07
Copilot AI requested a review from spawnia May 14, 2026 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants