Consolidate validation rules into single OperationValidator#4228
Open
Consolidate validation rules into single OperationValidator#4228
Conversation
Replace the AbstractRule + RulesVisitor + 31 individual rule class architecture with a single OperationValidator class that implements DocumentVisitor directly. BREAKING CHANGE: The rule filtering predicate type changes from Predicate<Class<?>> to Predicate<OperationValidationRule> in Validator, ParseAndValidate, and GraphQL. Code that filters validation rules by class reference must migrate to the new OperationValidationRule enum. - Create OperationValidationRule enum with 31 values (one per rule) - Create OperationValidator implementing DocumentVisitor with all validation logic consolidated from the 31 rule classes - Simplify Validator to create OperationValidator directly - Update ParseAndValidate and GraphQL predicate types - Delete AbstractRule, RulesVisitor, and all 31 rule classes (keep VariablesTypesMatcher as utility) - Convert all test files from mock-based rule instantiation to integration tests or OperationValidator with rule predicates - Update JMH benchmarks Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Contributor
Test Results 335 files ±0 335 suites ±0 5m 5s ⏱️ ±0s Results for commit 23dd3db. ± Comparison against base commit 2f0dc5b. This pull request removes 449 and adds 420 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Add integration tests replacing removed mock-based tests: - Per-operation fragment visiting (RulesVisitorTest) - Invalid input object field, list, nested list, and simple list types (ArgumentsOfCorrectTypeTest) - Null value for non-null input object field (ArgumentsOfCorrectTypeTest) - Unknown type on inline fragment not triggering composite type error (FragmentsOnCompositeTypeTest) - Directive argument scope isolation (KnownArgumentNamesTest) - Defaulted non-null field and directive arguments (ProvidedNonNullArgumentsTest) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Member
Author
Test Coverage Analysis: All 15 Removed Tests Accounted ForThe consolidation commit removed 15 mock-based tests. The latest commit restores 10 as integration tests. Here's the full accounting: Restored (10 tests)
Already covered by existing integration tests (3 tests — no gap)
Not applicable to new architecture (2 tests — no gap)
15 removed, 15 accounted for. No remaining coverage gaps. |
Member
Author
Correction: Full Accounting of All 23 Removed TestsThe previous comment only covered 15 tests. Here's the complete breakdown of all 23 mock-based tests removed in the consolidation commit. Refactored in the same commit (8 tests — renamed/rewritten as integration tests)
Partially refactored (1 parameterized test — 2 of 3 cases replaced)
Restored by latest commit (10 test scenarios)
Already covered by existing integration tests (3 tests — no gap)
Not applicable to new architecture (2 tests — no gap)
Summary
23 removed, 23 accounted for. No remaining coverage gaps. |
…ckage to validation package The rules sub-package is no longer needed since all 31 rule classes were consolidated into OperationValidator. Move the remaining utility class (VariablesTypesMatcher) and all 32 test files into the parent graphql.validation package, then delete the empty rules directories. Also remove stale JSpecifyAnnotationsCheck entries for deleted rule classes and update comments referencing the old package. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename methods for clarity: - shouldRunNonFragmentSpreadChecks() -> shouldRunDocumentLevelRules() - shouldRunFragmentSpreadChecks() -> shouldRunOperationScopedRules() - fragmentSpreadVisitDepth -> fragmentRetraversalDepth - Add comprehensive class Javadoc explaining: - Two independent state variables and their three valid combinations - Visual matrices showing when each rule category runs - Step-by-step traversal example with state at each step - Add detailed Javadoc to OperationValidationRule enum categorizing all rules by their traversal behavior - Simplify code by removing duplicate checks and redundant comments Co-authored-by: Cursor <cursoragent@cursor.com>
These checks are always true in methods that are only called at document level (depth=0): - checkDocument(): Document node visited once at start - checkOperationDefinition(): Operations are never inside fragments - checkVariableDefinition(): Variable definitions only exist in operations - documentFinished(): Called when leaving Document Co-authored-by: Cursor <cursoragent@cursor.com>
…onValidator ValidationContext: - Add @NullMarked at class level - Add @nullable to methods that can return null based on traversal state: getFragment, getParentType, getInputType, getDefaultValue, getFieldDef, getDirective, getArgument, getOutputType, getQueryPath OperationValidator: - Add @NullMarked at class level - Add @nullable to methods that can return null: getQueryPath, requireSameNameAndArguments, findArgumentByName, requireSameOutputTypeShape - Add @nullable to parameters that can be null: addError (SourceLocation), mkNotSameTypeError (typeA, typeB), overlappingFieldsImpl, overlappingFields_collectFields, overlappingFields_collectFieldsForInlineFragment, overlappingFields_collectFieldsForField, sameType, sameArguments - Add @nullable to fields that can be null: variableDefinitionMap, FieldAndType.graphQLType Co-authored-by: Cursor <cursoragent@cursor.com>
- Add @NullMarked at class level - Add @nullable to fields: directive, argument - Add @nullable to public methods that can return null: getOutputType, getParentType, getInputType, getDefaultValue, getFieldDef, getQueryPath, getDirective, getArgument - Add @nullable to private methods that can return null: lastElement, find, getFieldDef(schema, parentType, field), getNullableType - Add @nullable to parameters that accept null: addOutputType, addParentType, addInputType, addDefaultValue, addFieldDef, getNullableType Co-authored-by: Cursor <cursoragent@cursor.com>
Fix NullAway errors from master's @NullMarked additions to GraphQLSchema and GraphQLTypeUtil. Add null checks in OperationValidator and TraversalContext for nullable getCodeRegistry(), unwrapAll(), and type comparison methods. Update JSpecifyAnnotationsCheck exemption list to match master's annotated classes, removing deleted DeferDirective rule entries. Add @NullMarked to ParseAndValidate to match master.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Breaking Change
The rule filtering predicate type has changed from
Predicate<Class<?>>toPredicate<OperationValidationRule>in the following public APIs:Validator.validateDocument(schema, document, rulePredicate, locale)ParseAndValidate.parseAndValidate(...)overloads accepting a predicateGraphQLinternal validation predicate hintCode that filters validation rules by class reference (e.g.,
rule -> rule != NoUnusedFragments.class) must migrate to the newOperationValidationRuleenum (e.g.,rule -> rule != OperationValidationRule.NO_UNUSED_FRAGMENTS).Additionally, the following
@Internalclasses have been removed:AbstractRuleRulesVisitorgraphql.validation.rules.*(exceptVariablesTypesMatcher)Summary
AbstractRule, andRulesVisitorinto a singleOperationValidatorclass that implementsDocumentVisitordirectlyOperationValidationRuleenum (@PublicApi) with one value per validation rule for type-safe rule filteringValidatorto create a singleOperationValidatorinstead of 31 rule instancesPerformance
JMH benchmark results comparing
mastervs this branch (ValidatorBenchmark, avg ms/op, lower is better):Environment: JDK 25.0.1 (OpenJDK, Amazon Corretto), 2 forks, 2 warmup iterations (5s), 3 measurement iterations (10s).
The single-traversal approach avoids redundant AST walks that the 31 separate rule classes were performing, resulting in consistent improvements across all scenarios.
Test plan
./gradlew test -x testWithJava11 -x testWithJava17 -x testWithJava21)./gradlew test --tests "graphql.validation.*")🤖 Generated with Claude Code