11import { GraphQLError } from '../../error/GraphQLError.js' ;
22
3+ import type {
4+ FragmentDefinitionNode ,
5+ FragmentSpreadNode ,
6+ InlineFragmentNode ,
7+ OperationDefinitionNode ,
8+ OperationTypeNode ,
9+ SelectionSetNode ,
10+ } from '../../language/ast.js' ;
11+ import { Kind } from '../../language/kinds.js' ;
312import type { ASTVisitor } from '../../language/visitor.js' ;
413
514import {
615 GraphQLDeferDirective ,
716 GraphQLStreamDirective ,
817} from '../../type/directives.js' ;
18+ import type { GraphQLObjectType } from '../../type/index.js' ;
919
1020import type { ValidationContext } from '../ValidationContext.js' ;
1121
@@ -18,46 +28,112 @@ export function DeferStreamDirectiveOnRootFieldRule(
1828 context : ValidationContext ,
1929) : ASTVisitor {
2030 return {
21- Directive ( node ) {
22- const mutationType = context . getSchema ( ) . getMutationType ( ) ;
23- const subscriptionType = context . getSchema ( ) . getSubscriptionType ( ) ;
24- const parentType = context . getParentType ( ) ;
25- if ( parentType && node . name . value === GraphQLDeferDirective . name ) {
26- if ( mutationType && parentType === mutationType ) {
27- context . reportError (
28- new GraphQLError (
29- `Defer directive cannot be used on root mutation type "${ parentType } ".` ,
30- { nodes : node } ,
31- ) ,
32- ) ;
33- }
34- if ( subscriptionType && parentType === subscriptionType ) {
35- context . reportError (
36- new GraphQLError (
37- `Defer directive cannot be used on root subscription type "${ parentType } ".` ,
38- { nodes : node } ,
39- ) ,
40- ) ;
31+ OperationDefinition ( node : OperationDefinitionNode ) {
32+ const document = context . getDocument ( ) ;
33+ const fragments = new Map < string , FragmentDefinitionNode > ( ) ;
34+
35+ for ( const definition of document . definitions ) {
36+ if ( definition . kind === Kind . FRAGMENT_DEFINITION ) {
37+ fragments . set ( definition . name . value , definition ) ;
4138 }
4239 }
43- if ( parentType && node . name . value === GraphQLStreamDirective . name ) {
44- if ( mutationType && parentType === mutationType ) {
45- context . reportError (
46- new GraphQLError (
47- `Stream directive cannot be used on root mutation type "${ parentType } ".` ,
48- { nodes : node } ,
49- ) ,
50- ) ;
51- }
52- if ( subscriptionType && parentType === subscriptionType ) {
40+ if ( node . operation !== 'subscription' && node . operation !== 'mutation' ) {
41+ return ;
42+ }
43+ const schema = context . getSchema ( ) ;
44+ const rootType = schema . getRootType ( node . operation ) ;
45+ if ( rootType ) {
46+ collectRootFields ( {
47+ context,
48+ operationType : node . operation ,
49+ rootType,
50+ fragments,
51+ selectionSet : node . selectionSet ,
52+ visitedFragments : new Set ( ) ,
53+ } ) ;
54+ }
55+ } ,
56+ } ;
57+ }
58+
59+ function collectRootFields ( {
60+ context,
61+ operationType,
62+ rootType,
63+ fragments,
64+ selectionSet,
65+ visitedFragments,
66+ } : {
67+ context : ValidationContext ;
68+ operationType : OperationTypeNode ;
69+ rootType : GraphQLObjectType ;
70+ fragments : Map < string , FragmentDefinitionNode > ;
71+ selectionSet : SelectionSetNode ;
72+ visitedFragments : Set < string > ;
73+ } ) {
74+ for ( const selection of selectionSet . selections ) {
75+ if ( selection . kind === 'Field' ) {
76+ const stream = selection . directives ?. find (
77+ ( d ) => d . name . value === GraphQLStreamDirective . name ,
78+ ) ;
79+ if ( stream ) {
80+ context . reportError (
81+ new GraphQLError (
82+ `Stream directive cannot be used on root ${ operationType } type "${ rootType } ".` ,
83+ { nodes : stream } ,
84+ ) ,
85+ ) ;
86+ }
87+ } else if ( selection . kind === 'FragmentSpread' ) {
88+ const fragmentName = selection . name . value ;
89+ if ( visitedFragments . has ( fragmentName ) ) {
90+ continue ;
91+ }
92+ const fragment = fragments . get ( fragmentName ) ;
93+ if ( fragment ) {
94+ const defer = getDeferDirective ( selection ) ;
95+ if ( defer ) {
5396 context . reportError (
5497 new GraphQLError (
55- `Stream directive cannot be used on root subscription type "${ parentType } ".` ,
56- { nodes : node } ,
98+ `Defer directive cannot be used on root ${ operationType } type "${ rootType } ".` ,
99+ { nodes : defer } ,
57100 ) ,
58101 ) ;
59102 }
103+ collectRootFields ( {
104+ context,
105+ operationType,
106+ rootType,
107+ fragments,
108+ selectionSet : fragment . selectionSet ,
109+ visitedFragments,
110+ } ) ;
60111 }
61- } ,
62- } ;
112+ visitedFragments . add ( fragmentName ) ;
113+ } else if ( selection . kind === 'InlineFragment' ) {
114+ const defer = getDeferDirective ( selection ) ;
115+ if ( defer ) {
116+ context . reportError (
117+ new GraphQLError (
118+ `Defer directive cannot be used on root ${ operationType } type "${ rootType } ".` ,
119+ { nodes : defer } ,
120+ ) ,
121+ ) ;
122+ }
123+ collectRootFields ( {
124+ context,
125+ operationType,
126+ rootType,
127+ fragments,
128+ selectionSet : selection . selectionSet ,
129+ visitedFragments,
130+ } ) ;
131+ }
132+ }
133+ }
134+
135+ function getDeferDirective ( fragment : FragmentSpreadNode | InlineFragmentNode ) {
136+ return fragment . directives ?. find (
137+ ( d ) => d . name . value === GraphQLDeferDirective . name ,
138+ ) ;
63139}
0 commit comments