Class OperationValidator
- All Implemented Interfaces:
DocumentVisitor
Traversal Model
This validator tracks two independent state variables during traversal:
fragmentRetraversalDepth- Tracks whether we are in the primary document traversal (== 0) or inside a manual re-traversal of a fragment via a spread (> 0).operationScope- Tracks whether we are currently inside an operation definition (true) or outside of any operation (false).
Traversal States
These two variables create four possible states, but only three actually occur:
┌────────────────────────────────────────┬────────────────────────────────────────────────┐ │ State │ Description │ ├────────────────────────────────────────┼────────────────────────────────────────────────┤ │ depth=0, operationScope=false │ PRIMARY TRAVERSAL, OUTSIDE OPERATION │ │ │ Visiting document root or fragment definitions │ │ │ after all operations have been processed. │ │ │ Example: FragmentDefinition at document level │ ├────────────────────────────────────────┼────────────────────────────────────────────────┤ │ depth=0, operationScope=true │ PRIMARY TRAVERSAL, INSIDE OPERATION │ │ │ Visiting nodes directly within an operation. │ │ │ Example: Field, InlineFragment in operation │ ├────────────────────────────────────────┼────────────────────────────────────────────────┤ │ depth>0, operationScope=true │ FRAGMENT RETRAVERSAL, INSIDE OPERATION │ │ │ Manually traversing into a fragment via spread.│ │ │ Example: Nodes reached via ...FragmentName │ ├────────────────────────────────────────┼────────────────────────────────────────────────┤ │ depth>0, operationScope=false │ NEVER OCCURS │ │ │ Retraversal only happens within an operation. │ └────────────────────────────────────────┴────────────────────────────────────────────────┘
Rule Categories
Rules are categorized by which states they should run in:
┌──────────────────────┬──────────────────────┬─────────────────────┬─────────────────────┐ │ Rule Category │ depth=0 │ depth=0 │ depth>0 │ │ │ operationScope=false │ operationScope=true │ operationScope=true │ ├──────────────────────┼──────────────────────┼─────────────────────┼─────────────────────┤ │ Document-Level Rules │ RUN │ RUN │ SKIP │ ├──────────────────────┼──────────────────────┼─────────────────────┼─────────────────────┤ │ Operation-Scoped │ SKIP │ RUN │ RUN │ │ Rules │ │ │ │ └──────────────────────┴──────────────────────┴─────────────────────┴─────────────────────┘
Document-Level Rules
Check: fragmentRetraversalDepth == 0 (via shouldRunDocumentLevelRules())
Purpose: Validate each AST node exactly once. Skip during fragment retraversal to avoid duplicate errors (the fragment was already validated at document level).
Examples: FieldsOnCorrectType, UniqueFragmentNames, ScalarLeaves
Operation-Scoped Rules
Check: operationScope == true (via shouldRunOperationScopedRules())
Purpose: Track state across an entire operation, including all fragments it references. These rules need to "follow" fragment spreads to see variable usages, defer directives, etc.
Examples: NoUndefinedVariables, NoUnusedVariables, VariableTypesMatch
Traversal Example
Consider this GraphQL document:
query GetUser($id: ID!) {
user(id: $id) {
...UserFields
}
}
fragment UserFields on User {
name
friends {
...UserFields # recursive spread
}
}
The traversal proceeds as follows:
STEP NODE depth operationScope DOC-LEVEL OP-SCOPED
──── ────────────────────────── ───── ────────────── ───────── ─────────
1 Document 0 false RUN SKIP
2 OperationDefinition 0 true RUN RUN
3 ├─ VariableDefinition $id 0 true RUN RUN
4 ├─ Field "user" 0 true RUN RUN
5 │ └─ FragmentSpread 0 true RUN RUN
│ ...UserFields
│ ┌─────────────────────────────────────────────────────────────┐
│ │ MANUAL RETRAVERSAL INTO FRAGMENT │
│ └─────────────────────────────────────────────────────────────┘
6 │ FragmentDefinition 1 true SKIP RUN
7 │ ├─ Field "name" 1 true SKIP RUN
8 │ ├─ Field "friends" 1 true SKIP RUN
9 │ │ └─ FragmentSpread 1 true SKIP RUN
│ │ ...UserFields
│ │ (already visited - skip to avoid infinite loop)
│ └─────────────────────────────────────────────────────────────┘
10 └─ (leave OperationDef) 0 false [finalize op-scoped rules]
11 FragmentDefinition 0 false RUN SKIP
"UserFields" (at doc level)
12 ├─ Field "name" 0 false RUN SKIP
13 ├─ Field "friends" 0 false RUN SKIP
14 │ └─ FragmentSpread 0 false RUN SKIP
Key Observations
- Steps 6-9: During retraversal, document-level rules SKIP because the fragment will be validated at steps 11-14. This prevents duplicate "field not found" errors.
- Steps 6-9: Operation-scoped rules RUN to track that variables used inside
UserFieldsare defined in the operation. - Steps 11-14: Operation-scoped rules SKIP because there's no operation context to track variables against.
- Step 9: Recursive fragment spreads are tracked via
visitedFragmentSpreadsto prevent infinite loops during retraversal.
- See Also:
-
Constructor Summary
ConstructorsConstructorDescriptionOperationValidator(ValidationContext validationContext, ValidationErrorCollector errorCollector, Predicate<OperationValidationRule> rulePredicate) -
Method Summary
-
Constructor Details
-
OperationValidator
public OperationValidator(ValidationContext validationContext, ValidationErrorCollector errorCollector, Predicate<OperationValidationRule> rulePredicate)
-
-
Method Details
-
enter
- Specified by:
enterin interfaceDocumentVisitor
-
leave
- Specified by:
leavein interfaceDocumentVisitor
-
toString
-