Class OperationValidator

java.lang.Object
graphql.validation.OperationValidator
All Implemented Interfaces:
DocumentVisitor

@NullMarked public class OperationValidator extends Object implements DocumentVisitor
Consolidated operation validator that implements all GraphQL validation rules from the specification. Replaces the former 31 separate rule classes and the RulesVisitor dispatch layer.

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 UserFields are 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 visitedFragmentSpreads to prevent infinite loops during retraversal.
See Also: