diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md index 7083a6b3da..e1df47e632 100644 --- a/.claude/commands/jspecify-annotate.md +++ b/.claude/commands/jspecify-annotate.md @@ -1,7 +1,11 @@ -The task is to annotate public API classes (marked with `@PublicAPI`) with JSpecify nullability annotations. +The task is to annotate public API classes (marked with `@PublicApi` or `@PublicSpi`) with JSpecify nullability annotations. Note that JSpecify is already used in this repository so it's already imported. +## Scope: Public API only + +Only annotate classes marked `@PublicApi` or `@PublicSpi`. Do **not** add JSpecify annotations to `@Internal` classes — these are implementation details and are intentionally left unannotated. + If you see a builder static class, you can label it `@NullUnmarked` and not need to do anymore for this static class in terms of annotations. Analyze this Java class and add JSpecify annotations based on: @@ -29,9 +33,11 @@ If you find NullAway errors, try and make the smallest possible change to fix th Do not make spacing or formatting changes. Avoid adjusting whitespace, line breaks, or other formatting when editing code. These changes make diffs messy and harder to review. Only make the minimal changes necessary to accomplish the task. ## Cleaning up -Finally, can you remove this class from the JSpecifyAnnotationsCheck as an exemption +Finally, remove this class from the exemption list in `JSpecifyAnnotationsCheck.groovy`. + +The exemption list contains fully-qualified class names. Remove **only** the class you just annotated — do not remove entries for `@Internal` classes or any other class you did not annotate. -You do not need to run the JSpecifyAnnotationsCheck. Removing the completed class is enough. +You do not need to run the JSpecifyAnnotationsCheck. Removing the completed class from the list is enough. Remember to delete all unused imports when you're done from the class you've just annotated. diff --git a/.claude/commands/jspecify-team-prompt.md b/.claude/commands/jspecify-team-prompt.md new file mode 100644 index 0000000000..fb5d3e28f5 --- /dev/null +++ b/.claude/commands/jspecify-team-prompt.md @@ -0,0 +1,394 @@ +# JSpecify Annotation Team Orchestrator Prompt + +You are an orchestrator agent responsible for coordinating a team of worker +agents to add JSpecify nullability annotations to public API classes in the +graphql-java project. + +## Your Job + +1. Read the current exemption list from: + `src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy` + Before building batch assignments, check which classes from the lists below + are still present in the exemption list. Only assign classes that remain. + This makes the orchestrator safe to re-run after interruption. + +2. Group the remaining classes into batches (see batches below). + +3. Launch worker agents in **waves of 3** to manage token usage. + After each wave completes, launch the reviewer before starting the next wave. + Only proceed to the next wave if the reviewer reports no NullAway errors. + +4. After all waves are complete, launch the validator. + +## Execution Flow + +### Wave 1 +Launch Workers 1, 2, 3 in parallel. Wait for all to complete. +Launch Reviewer for Wave 1. Wait for completion and NullAway confirmation. + +### Wave 2 +Launch Workers 5, 6, 7 in parallel. Wait for all to complete. +Launch Reviewer for Wave 2. Wait for completion and NullAway confirmation. + +### Wave 3 +Launch Workers 8, 9 in parallel. Wait for all to complete. +Launch Reviewer for Wave 3. Wait for completion and NullAway confirmation. + +### Final Step +Launch the validator to remove completed classes from +`JSpecifyAnnotationsCheck.groovy` and push the branch. + +--- + +## Batch Assignments + +### Worker 1 — graphql.analysis (16 classes) +``` +graphql.analysis.QueryComplexityCalculator +graphql.analysis.QueryComplexityInfo +graphql.analysis.QueryDepthInfo +graphql.analysis.QueryReducer +graphql.analysis.QueryTransformer +graphql.analysis.QueryTraversalOptions +graphql.analysis.QueryVisitor +graphql.analysis.QueryVisitorFieldArgumentEnvironment +graphql.analysis.QueryVisitorFieldArgumentInputValue +graphql.analysis.QueryVisitorFieldArgumentValueEnvironment +graphql.analysis.QueryVisitorFieldEnvironment +graphql.analysis.QueryVisitorFragmentDefinitionEnvironment +graphql.analysis.QueryVisitorFragmentSpreadEnvironment +graphql.analysis.QueryVisitorInlineFragmentEnvironment +graphql.analysis.QueryVisitorStub +graphql.analysis.values.ValueTraverser +``` + +### Worker 2 — graphql.execution core (26 classes) +``` +graphql.execution.AbortExecutionException +graphql.execution.AsyncExecutionStrategy +graphql.execution.AsyncSerialExecutionStrategy +graphql.execution.CoercedVariables +graphql.execution.DataFetcherExceptionHandlerParameters +graphql.execution.DataFetcherExceptionHandlerResult +graphql.execution.DefaultValueUnboxer +graphql.execution.ExecutionContext +graphql.execution.ExecutionId +graphql.execution.ExecutionStepInfo +graphql.execution.ExecutionStrategyParameters +graphql.execution.FetchedValue +graphql.execution.FieldValueInfo +graphql.execution.InputMapDefinesTooManyFieldsException +graphql.execution.MergedSelectionSet +graphql.execution.MissingRootTypeException +graphql.execution.NonNullableValueCoercedAsNullException +graphql.execution.NormalizedVariables +graphql.execution.OneOfNullValueException +graphql.execution.OneOfTooManyKeysException +graphql.execution.ResultNodesInfo +graphql.execution.ResultPath +graphql.execution.SimpleDataFetcherExceptionHandler +graphql.execution.SubscriptionExecutionStrategy +graphql.execution.UnknownOperationException +graphql.execution.UnresolvedTypeException +``` + +### Worker 3 — graphql.execution sub-packages (24 classes) +``` +graphql.execution.conditional.ConditionalNodeDecision +graphql.execution.directives.QueryAppliedDirective +graphql.execution.directives.QueryAppliedDirectiveArgument +graphql.execution.directives.QueryDirectives +graphql.execution.instrumentation.fieldvalidation.FieldValidationInstrumentation +graphql.execution.instrumentation.fieldvalidation.SimpleFieldValidation +graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters +graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters +graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters +graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters +graphql.execution.instrumentation.parameters.InstrumentationFieldCompleteParameters +graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters +graphql.execution.instrumentation.parameters.InstrumentationFieldParameters +graphql.execution.instrumentation.parameters.InstrumentationValidationParameters +graphql.execution.instrumentation.tracing.TracingInstrumentation +graphql.execution.instrumentation.tracing.TracingSupport +graphql.execution.preparsed.PreparsedDocumentEntry +graphql.execution.preparsed.persisted.ApolloPersistedQuerySupport +graphql.execution.preparsed.persisted.InMemoryPersistedQueryCache +graphql.execution.preparsed.persisted.PersistedQueryCacheMiss +graphql.execution.preparsed.persisted.PersistedQueryIdInvalid +graphql.execution.preparsed.persisted.PersistedQueryNotFound +graphql.execution.reactive.DelegatingSubscription +graphql.execution.reactive.SubscriptionPublisher +``` + +### Worker 5 — graphql.language (remaining) + graphql.normalized (23 classes) +``` +graphql.language.ScalarTypeDefinition +graphql.language.ScalarTypeExtensionDefinition +graphql.language.SchemaDefinition +graphql.language.SchemaExtensionDefinition +graphql.language.Selection +graphql.language.SelectionSet +graphql.language.SelectionSetContainer +graphql.language.TypeKind +graphql.language.TypeName +graphql.language.UnionTypeDefinition +graphql.language.UnionTypeExtensionDefinition +graphql.language.VariableDefinition +graphql.normalized.ExecutableNormalizedField +graphql.normalized.ExecutableNormalizedOperation +graphql.normalized.ExecutableNormalizedOperationFactory +graphql.normalized.ExecutableNormalizedOperationToAstCompiler +graphql.normalized.NormalizedInputValue +graphql.normalized.incremental.NormalizedDeferredExecution +graphql.normalized.nf.NormalizedDocument +graphql.normalized.nf.NormalizedDocumentFactory +graphql.normalized.nf.NormalizedField +graphql.normalized.nf.NormalizedOperation +graphql.normalized.nf.NormalizedOperationToAstCompiler +``` + +### Worker 6 — graphql.schema core types (38 classes) +``` +graphql.schema.AsyncDataFetcher +graphql.schema.CoercingParseLiteralException +graphql.schema.CoercingParseValueException +graphql.schema.CoercingSerializeException +graphql.schema.DataFetcherFactories +graphql.schema.DataFetcherFactoryEnvironment +graphql.schema.DataFetchingFieldSelectionSet +graphql.schema.DefaultGraphqlTypeComparatorRegistry +graphql.schema.DelegatingDataFetchingEnvironment +graphql.schema.FieldCoordinates +graphql.schema.GraphQLAppliedDirectiveArgument +graphql.schema.GraphQLArgument +graphql.schema.GraphQLCompositeType +graphql.schema.GraphQLDirective +graphql.schema.GraphQLDirectiveContainer +graphql.schema.GraphQLEnumValueDefinition +graphql.schema.GraphQLFieldDefinition +graphql.schema.GraphQLFieldsContainer +graphql.schema.GraphQLImplementingType +graphql.schema.GraphQLInputFieldsContainer +graphql.schema.GraphQLInputObjectField +graphql.schema.GraphQLInputObjectType +graphql.schema.GraphQLInputSchemaElement +graphql.schema.GraphQLInputType +graphql.schema.GraphQLInputValueDefinition +graphql.schema.GraphQLInterfaceType +graphql.schema.GraphQLModifiedType +graphql.schema.GraphQLNamedOutputType +graphql.schema.GraphQLNamedSchemaElement +graphql.schema.GraphQLNamedType +graphql.schema.GraphQLNonNull +graphql.schema.GraphQLNullableType +graphql.schema.GraphQLObjectType +graphql.schema.GraphQLOutputType +graphql.schema.GraphQLSchemaElement +graphql.schema.GraphQLTypeReference +graphql.schema.GraphQLTypeVisitor +graphql.schema.GraphQLTypeVisitorStub +graphql.schema.GraphQLUnmodifiedType +``` + +### Worker 7 — graphql.schema utilities + sub-packages (41 classes) +``` +graphql.schema.GraphqlElementParentTree +graphql.schema.GraphqlTypeComparatorEnvironment +graphql.schema.GraphqlTypeComparatorRegistry +graphql.schema.InputValueWithState +graphql.schema.SchemaElementChildrenContainer +graphql.schema.SchemaTransformer +graphql.schema.SchemaTraverser +graphql.schema.SelectedField +graphql.schema.StaticDataFetcher +graphql.schema.diff.DiffCategory +graphql.schema.diff.DiffEvent +graphql.schema.diff.DiffLevel +graphql.schema.diff.DiffSet +graphql.schema.diff.SchemaDiffSet +graphql.schema.diff.reporting.CapturingReporter +graphql.schema.diff.reporting.ChainedReporter +graphql.schema.diff.reporting.PrintStreamReporter +graphql.schema.idl.CombinedWiringFactory +graphql.schema.idl.MapEnumValuesProvider +graphql.schema.idl.NaturalEnumValuesProvider +graphql.schema.idl.RuntimeWiring +graphql.schema.idl.SchemaDirectiveWiring +graphql.schema.idl.SchemaDirectiveWiringEnvironment +graphql.schema.idl.SchemaGenerator +graphql.schema.idl.SchemaPrinter +graphql.schema.idl.TypeRuntimeWiring +graphql.schema.idl.errors.SchemaProblem +graphql.schema.idl.errors.StrictModeWiringException +graphql.schema.transform.FieldVisibilitySchemaTransformation +graphql.schema.transform.VisibleFieldPredicateEnvironment +graphql.schema.usage.SchemaUsage +graphql.schema.usage.SchemaUsageSupport +graphql.schema.validation.OneOfInputObjectRules +graphql.schema.validation.SchemaValidationErrorClassification +graphql.schema.visibility.BlockedFields +graphql.schema.visibility.DefaultGraphqlFieldVisibility +graphql.schema.visibility.GraphqlFieldVisibility +graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility +graphql.schema.visitor.GraphQLSchemaTraversalControl +``` + +### Worker 8 — graphql.extensions + graphql.incremental + graphql.introspection (15 classes) +``` +graphql.extensions.ExtensionsBuilder +graphql.incremental.DeferPayload +graphql.incremental.DelayedIncrementalPartialResult +graphql.incremental.DelayedIncrementalPartialResultImpl +graphql.incremental.IncrementalExecutionResult +graphql.incremental.IncrementalExecutionResultImpl +graphql.incremental.IncrementalPayload +graphql.incremental.StreamPayload +graphql.introspection.GoodFaithIntrospection +graphql.introspection.Introspection +graphql.introspection.IntrospectionQuery +graphql.introspection.IntrospectionQueryBuilder +graphql.introspection.IntrospectionResultToSchema +graphql.introspection.IntrospectionWithDirectivesSupport +graphql.introspection.IntrospectionWithDirectivesSupport$DirectivePredicateEnvironment +``` + +### Worker 9 — graphql.parser + graphql.util (18 classes) +``` +graphql.parser.InvalidSyntaxException +graphql.parser.MultiSourceReader +graphql.parser.Parser +graphql.parser.ParserEnvironment +graphql.parser.ParserOptions +graphql.util.CyclicSchemaAnalyzer +graphql.util.NodeAdapter +graphql.util.NodeLocation +graphql.util.NodeMultiZipper +graphql.util.NodeZipper +graphql.util.querygenerator.QueryGenerator +graphql.util.querygenerator.QueryGeneratorOptions +graphql.util.querygenerator.QueryGeneratorOptions$QueryGeneratorOptionsBuilder +graphql.util.querygenerator.QueryGeneratorResult +graphql.util.TraversalControl +graphql.util.TraverserContext +graphql.util.TreeTransformer +graphql.util.TreeTransformerUtil +``` + +--- + +## Instructions for Each Worker Agent + +Each worker agent must follow these rules for every class in its batch: + +### Per-class steps +1. Find the class at `src/main/java/.java` +2. Add `@NullMarked` at the class level +3. Remove redundant `@NonNull` annotations added by IntelliJ +4. Check Javadoc `@param` tags mentioning "null", "nullable", "may be null" +5. Check Javadoc `@return` tags mentioning "null", "optional", "if available" +6. Check method bodies that return null or check for null +7. Consult the GraphQL spec (https://spec.graphql.org/draft/) for the + relevant concept — prioritize spec nullability over IntelliJ inference +8. If the class has a static Builder inner class, annotate it `@NullUnmarked` + and skip further annotation of that inner class +9. Delete unused imports from the class after editing +10. Do NOT modify `JSpecifyAnnotationsCheck.groovy` + +### Generics rules +- `` does NOT allow `@Nullable` type arguments (`Box<@Nullable String>` illegal) +- `` DOES allow `@Nullable` type arguments +- Use `` only when callers genuinely need nullable types + +### After each class +- Commit with message: `"Add JSpecify annotations to ClassName"` +- `git pull` before starting the next class to pick up any concurrent changes + +### If a class has NullAway errors +- Make the smallest possible fix +- Use `Objects.requireNonNull(x, "message")` or `assertNotNull(x, "message")` + only as a last resort +- Do NOT run the full build — leave that for the reviewer + +--- + +## Reviewer Agent Instructions + +After each wave of workers completes, launch a single reviewer agent. +The reviewer must NOT read any worker agent output or context — it works +only from the git diff. + +### Step 1 — Get the diffs + +For each class annotated in this wave, get its diff: +``` +git diff origin/main...HEAD -- +``` + +### Step 2 — Review each diff independently + +For each changed file, check: + +**JSpecify correctness:** +- `@NullMarked` is present at the class level +- No redundant `@NonNull` annotations remain +- `@Nullable` is used where null is genuinely possible, not speculatively +- `` only used when callers truly need nullable + type arguments +- Builder inner classes are marked `@NullUnmarked` and nothing more + +**GraphQL spec compliance:** +Fetch https://spec.graphql.org/draft/ for the relevant concept. +`MUST` = non-null. Conditional/IF = nullable. +Does the annotation match the spec? + +**Unused imports:** +Are there leftover imports (e.g. `org.jetbrains.annotations.NotNull`)? + +### Step 3 — Fix any issues found + +Make the minimal fix directly in the file. +Do NOT revert the whole annotation — fix only the specific problem. +Do NOT modify `JSpecifyAnnotationsCheck.groovy`. + +If you make any fixes, commit with: +``` +"Review fixes: correct JSpecify annotations for ClassName" +``` + +### Step 4 — Run the compile check + +``` +./gradlew compileJava +``` + +If it passes, report success and stop. + +If it fails: +- Read the NullAway error output carefully +- Make the smallest possible fix (prefer `assertNotNull(x, "message")` + over restructuring code) +- Re-run `./gradlew compileJava` +- Repeat until it passes or you have tried 3 times +- If still failing after 3 attempts, report the error details to the + orchestrator so it can decide whether to continue to the next wave + +--- + +## Validator Agent Instructions + +All waves are complete and the reviewer has confirmed `compileJava` passes +for each wave. Your job is cleanup and push only. + +1. Read the current exemption list from `JSpecifyAnnotationsCheck.groovy` +2. For each class that now has `@NullMarked` in its source file, remove it + from the `JSPECIFY_EXEMPTION_LIST` in `JSpecifyAnnotationsCheck.groovy` +3. Run `./gradlew compileJava` one final time to confirm the full build passes +4. Commit: `"Remove annotated classes from JSpecify exemption list"` +5. Push: `git push -u origin claude/agent-team-jspecify-Frd74` + +--- + +## Repository Details +- Working directory: `/home/user/graphql-java` +- Branch: `claude/agent-team-jspecify-Frd74` +- NullAway compile check: `./gradlew compileJava` diff --git a/src/main/java/graphql/ExceptionWhileDataFetching.java b/src/main/java/graphql/ExceptionWhileDataFetching.java index d5d0c61379..89a13d9649 100644 --- a/src/main/java/graphql/ExceptionWhileDataFetching.java +++ b/src/main/java/graphql/ExceptionWhileDataFetching.java @@ -27,7 +27,7 @@ public class ExceptionWhileDataFetching implements GraphQLError { private final List locations; private final @Nullable Map extensions; - public ExceptionWhileDataFetching(ResultPath path, Throwable exception, SourceLocation sourceLocation) { + public ExceptionWhileDataFetching(ResultPath path, Throwable exception, @Nullable SourceLocation sourceLocation) { this.path = assertNotNull(path).toList(); this.exception = assertNotNull(exception); this.locations = Collections.singletonList(sourceLocation); diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index 1441238b89..ca79ca9a4d 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -547,7 +547,7 @@ private CompletableFuture parseValidateAndExecute(ExecutionInpu return CompletableFuture.completedFuture(new ExecutionResultImpl(preparsedDocumentEntry.getErrors())); } try { - return execute(Assert.assertNotNull(executionInputRef.get()), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState, engineRunningState, profiler); + return execute(Assert.assertNotNull(executionInputRef.get()), assertNotNull(preparsedDocumentEntry.getDocument(), "document must not be null"), graphQLSchema, instrumentationState, engineRunningState, profiler); } catch (AbortExecutionException e) { return CompletableFuture.completedFuture(e.toExecutionResult()); } diff --git a/src/main/java/graphql/UnresolvedTypeError.java b/src/main/java/graphql/UnresolvedTypeError.java index ba9bf333a5..60200df158 100644 --- a/src/main/java/graphql/UnresolvedTypeError.java +++ b/src/main/java/graphql/UnresolvedTypeError.java @@ -32,8 +32,8 @@ private String mkMessage(ResultPath path, UnresolvedTypeException exception, Exe return format("Can't resolve '%s'. Abstract type '%s' must resolve to an Object type at runtime for field '%s.%s'. %s", path, exception.getInterfaceOrUnionType().getName(), - simplePrint(info.getParent().getUnwrappedNonNullType()), - info.getFieldDefinition().getName(), + simplePrint(assertNotNull(info.getParent(), "executionStepInfo parent must not be null").getUnwrappedNonNullType()), + assertNotNull(info.getFieldDefinition(), "fieldDefinition must not be null").getName(), exception.getMessage()); } diff --git a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java index 1ac22227f8..6bab51da1a 100644 --- a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java +++ b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java @@ -9,6 +9,7 @@ import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.function.Function; @@ -84,7 +85,7 @@ QueryTraverser newQueryTraverser(ExecutionContext executionContext) { .build(); } - private int getPathLength(QueryVisitorFieldEnvironment path) { + private int getPathLength(@Nullable QueryVisitorFieldEnvironment path) { int length = 1; while (path != null) { path = path.getParentEnvironment(); diff --git a/src/main/java/graphql/analysis/QueryComplexityCalculator.java b/src/main/java/graphql/analysis/QueryComplexityCalculator.java index e16035cc7a..0ec297523d 100644 --- a/src/main/java/graphql/analysis/QueryComplexityCalculator.java +++ b/src/main/java/graphql/analysis/QueryComplexityCalculator.java @@ -4,6 +4,9 @@ import graphql.execution.CoercedVariables; import graphql.language.Document; import graphql.schema.GraphQLSchema; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import java.util.LinkedHashMap; import java.util.Map; @@ -16,12 +19,13 @@ * into it. */ @PublicApi +@NullMarked public class QueryComplexityCalculator { private final FieldComplexityCalculator fieldComplexityCalculator; private final GraphQLSchema schema; private final Document document; - private final String operationName; + private final @Nullable String operationName; private final CoercedVariables variables; public QueryComplexityCalculator(Builder builder) { @@ -95,6 +99,7 @@ public static Builder newCalculator() { return new Builder(); } + @NullUnmarked public static class Builder { private FieldComplexityCalculator fieldComplexityCalculator; private GraphQLSchema schema; diff --git a/src/main/java/graphql/analysis/QueryComplexityInfo.java b/src/main/java/graphql/analysis/QueryComplexityInfo.java index 29914e960a..83e9002050 100644 --- a/src/main/java/graphql/analysis/QueryComplexityInfo.java +++ b/src/main/java/graphql/analysis/QueryComplexityInfo.java @@ -3,17 +3,20 @@ import graphql.PublicApi; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.jspecify.annotations.NullUnmarked; /** * The query complexity info. */ @PublicApi +@NullMarked public class QueryComplexityInfo { private final int complexity; - private final InstrumentationValidationParameters instrumentationValidationParameters; - private final InstrumentationExecuteOperationParameters instrumentationExecuteOperationParameters; + private final @Nullable InstrumentationValidationParameters instrumentationValidationParameters; + private final @Nullable InstrumentationExecuteOperationParameters instrumentationExecuteOperationParameters; private QueryComplexityInfo(Builder builder) { this.complexity = builder.complexity; @@ -35,7 +38,7 @@ public int getComplexity() { * * @return the instrumentation validation parameters. */ - public InstrumentationValidationParameters getInstrumentationValidationParameters() { + public @Nullable InstrumentationValidationParameters getInstrumentationValidationParameters() { return instrumentationValidationParameters; } @@ -44,7 +47,7 @@ public InstrumentationValidationParameters getInstrumentationValidationParameter * * @return the instrumentation execute operation parameters. */ - public InstrumentationExecuteOperationParameters getInstrumentationExecuteOperationParameters() { + public @Nullable InstrumentationExecuteOperationParameters getInstrumentationExecuteOperationParameters() { return instrumentationExecuteOperationParameters; } diff --git a/src/main/java/graphql/analysis/QueryDepthInfo.java b/src/main/java/graphql/analysis/QueryDepthInfo.java index a8d4a0ac03..329c2dd49c 100644 --- a/src/main/java/graphql/analysis/QueryDepthInfo.java +++ b/src/main/java/graphql/analysis/QueryDepthInfo.java @@ -1,12 +1,14 @@ package graphql.analysis; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullUnmarked; /** * The query depth info. */ @PublicApi +@NullMarked public class QueryDepthInfo { private final int depth; diff --git a/src/main/java/graphql/analysis/QueryReducer.java b/src/main/java/graphql/analysis/QueryReducer.java index f19ee7fd93..e62aee4151 100644 --- a/src/main/java/graphql/analysis/QueryReducer.java +++ b/src/main/java/graphql/analysis/QueryReducer.java @@ -1,6 +1,7 @@ package graphql.analysis; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; /** * Used by {@link QueryTraverser} to reduce the fields of a Document (or part of it) to a single value. @@ -10,6 +11,7 @@ * See {@link QueryTraverser#reducePostOrder(QueryReducer, Object)} and {@link QueryTraverser#reducePreOrder(QueryReducer, Object)} */ @PublicApi +@NullMarked @FunctionalInterface public interface QueryReducer { diff --git a/src/main/java/graphql/analysis/QueryTransformer.java b/src/main/java/graphql/analysis/QueryTransformer.java index fbf5052a91..b1a8b63c20 100644 --- a/src/main/java/graphql/analysis/QueryTransformer.java +++ b/src/main/java/graphql/analysis/QueryTransformer.java @@ -13,6 +13,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullUnmarked; import static graphql.Assert.assertNotNull; @@ -31,6 +32,7 @@ * visitField calls. */ @PublicApi +@NullMarked public class QueryTransformer { private final Node root; diff --git a/src/main/java/graphql/analysis/QueryTraversalOptions.java b/src/main/java/graphql/analysis/QueryTraversalOptions.java index 7ce73f05ce..6c4a798da8 100644 --- a/src/main/java/graphql/analysis/QueryTraversalOptions.java +++ b/src/main/java/graphql/analysis/QueryTraversalOptions.java @@ -1,11 +1,13 @@ package graphql.analysis; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; /** * This options object controls how {@link QueryTraverser} works */ @PublicApi +@NullMarked public class QueryTraversalOptions { private final boolean coerceFieldArguments; diff --git a/src/main/java/graphql/analysis/QueryVisitor.java b/src/main/java/graphql/analysis/QueryVisitor.java index 69d86f2449..262c591619 100644 --- a/src/main/java/graphql/analysis/QueryVisitor.java +++ b/src/main/java/graphql/analysis/QueryVisitor.java @@ -2,6 +2,7 @@ import graphql.PublicApi; import graphql.util.TraversalControl; +import org.jspecify.annotations.NullMarked; /** * Used by {@link QueryTraverser} to visit the nodes of a Query. @@ -9,6 +10,7 @@ * How this happens in detail (pre vs post-order for example) is defined by {@link QueryTraverser}. */ @PublicApi +@NullMarked public interface QueryVisitor { void visitField(QueryVisitorFieldEnvironment queryVisitorFieldEnvironment); diff --git a/src/main/java/graphql/analysis/QueryVisitorFieldArgumentEnvironment.java b/src/main/java/graphql/analysis/QueryVisitorFieldArgumentEnvironment.java index adf31abe42..42037553a1 100644 --- a/src/main/java/graphql/analysis/QueryVisitorFieldArgumentEnvironment.java +++ b/src/main/java/graphql/analysis/QueryVisitorFieldArgumentEnvironment.java @@ -7,10 +7,13 @@ import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLSchema; import graphql.util.TraverserContext; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Map; @PublicApi +@NullMarked public interface QueryVisitorFieldArgumentEnvironment { GraphQLSchema getSchema(); @@ -21,7 +24,7 @@ public interface QueryVisitorFieldArgumentEnvironment { Argument getArgument(); - Object getArgumentValue(); + @Nullable Object getArgumentValue(); Map getVariables(); diff --git a/src/main/java/graphql/analysis/QueryVisitorFieldArgumentInputValue.java b/src/main/java/graphql/analysis/QueryVisitorFieldArgumentInputValue.java index 106e596ae0..01288c095d 100644 --- a/src/main/java/graphql/analysis/QueryVisitorFieldArgumentInputValue.java +++ b/src/main/java/graphql/analysis/QueryVisitorFieldArgumentInputValue.java @@ -4,6 +4,8 @@ import graphql.language.Value; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLInputValueDefinition; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * This describes the tree structure that forms from a argument input type, @@ -11,9 +13,10 @@ * types and hence form a tree of values. */ @PublicApi +@NullMarked public interface QueryVisitorFieldArgumentInputValue { - QueryVisitorFieldArgumentInputValue getParent(); + @Nullable QueryVisitorFieldArgumentInputValue getParent(); GraphQLInputValueDefinition getInputValueDefinition(); @@ -21,5 +24,5 @@ public interface QueryVisitorFieldArgumentInputValue { GraphQLInputType getInputType(); - Value getValue(); + @Nullable Value getValue(); } diff --git a/src/main/java/graphql/analysis/QueryVisitorFieldArgumentValueEnvironment.java b/src/main/java/graphql/analysis/QueryVisitorFieldArgumentValueEnvironment.java index 5da2d02e8a..cf07dd1d50 100644 --- a/src/main/java/graphql/analysis/QueryVisitorFieldArgumentValueEnvironment.java +++ b/src/main/java/graphql/analysis/QueryVisitorFieldArgumentValueEnvironment.java @@ -6,10 +6,12 @@ import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLSchema; import graphql.util.TraverserContext; +import org.jspecify.annotations.NullMarked; import java.util.Map; @PublicApi +@NullMarked public interface QueryVisitorFieldArgumentValueEnvironment { GraphQLSchema getSchema(); diff --git a/src/main/java/graphql/analysis/QueryVisitorFieldEnvironment.java b/src/main/java/graphql/analysis/QueryVisitorFieldEnvironment.java index 608c5205df..4e98b8794d 100644 --- a/src/main/java/graphql/analysis/QueryVisitorFieldEnvironment.java +++ b/src/main/java/graphql/analysis/QueryVisitorFieldEnvironment.java @@ -9,10 +9,13 @@ import graphql.schema.GraphQLOutputType; import graphql.schema.GraphQLSchema; import graphql.util.TraverserContext; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Map; @PublicApi +@NullMarked public interface QueryVisitorFieldEnvironment { /** @@ -46,11 +49,11 @@ public interface QueryVisitorFieldEnvironment { */ GraphQLFieldsContainer getFieldsContainer(); - QueryVisitorFieldEnvironment getParentEnvironment(); + @Nullable QueryVisitorFieldEnvironment getParentEnvironment(); Map getArguments(); - SelectionSetContainer getSelectionSetContainer(); + @Nullable SelectionSetContainer getSelectionSetContainer(); TraverserContext getTraverserContext(); } diff --git a/src/main/java/graphql/analysis/QueryVisitorFragmentDefinitionEnvironment.java b/src/main/java/graphql/analysis/QueryVisitorFragmentDefinitionEnvironment.java index da9ab15600..cf7a20af86 100644 --- a/src/main/java/graphql/analysis/QueryVisitorFragmentDefinitionEnvironment.java +++ b/src/main/java/graphql/analysis/QueryVisitorFragmentDefinitionEnvironment.java @@ -5,8 +5,10 @@ import graphql.language.Node; import graphql.schema.GraphQLSchema; import graphql.util.TraverserContext; +import org.jspecify.annotations.NullMarked; @PublicApi +@NullMarked public interface QueryVisitorFragmentDefinitionEnvironment { /** diff --git a/src/main/java/graphql/analysis/QueryVisitorFragmentSpreadEnvironment.java b/src/main/java/graphql/analysis/QueryVisitorFragmentSpreadEnvironment.java index 4d70f24753..d4318a392a 100644 --- a/src/main/java/graphql/analysis/QueryVisitorFragmentSpreadEnvironment.java +++ b/src/main/java/graphql/analysis/QueryVisitorFragmentSpreadEnvironment.java @@ -6,8 +6,10 @@ import graphql.language.Node; import graphql.schema.GraphQLSchema; import graphql.util.TraverserContext; +import org.jspecify.annotations.NullMarked; @PublicApi +@NullMarked public interface QueryVisitorFragmentSpreadEnvironment { /** diff --git a/src/main/java/graphql/analysis/QueryVisitorInlineFragmentEnvironment.java b/src/main/java/graphql/analysis/QueryVisitorInlineFragmentEnvironment.java index 699b5b453a..2295b635e2 100644 --- a/src/main/java/graphql/analysis/QueryVisitorInlineFragmentEnvironment.java +++ b/src/main/java/graphql/analysis/QueryVisitorInlineFragmentEnvironment.java @@ -5,8 +5,10 @@ import graphql.language.Node; import graphql.schema.GraphQLSchema; import graphql.util.TraverserContext; +import org.jspecify.annotations.NullMarked; @PublicApi +@NullMarked public interface QueryVisitorInlineFragmentEnvironment { /** diff --git a/src/main/java/graphql/analysis/QueryVisitorStub.java b/src/main/java/graphql/analysis/QueryVisitorStub.java index 72129f296e..0180e635ea 100644 --- a/src/main/java/graphql/analysis/QueryVisitorStub.java +++ b/src/main/java/graphql/analysis/QueryVisitorStub.java @@ -1,8 +1,10 @@ package graphql.analysis; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; @PublicApi +@NullMarked public class QueryVisitorStub implements QueryVisitor { diff --git a/src/main/java/graphql/analysis/values/ValueTraverser.java b/src/main/java/graphql/analysis/values/ValueTraverser.java index 664cda26be..0cc1c3d31b 100644 --- a/src/main/java/graphql/analysis/values/ValueTraverser.java +++ b/src/main/java/graphql/analysis/values/ValueTraverser.java @@ -19,11 +19,14 @@ import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLTypeUtil; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import static graphql.Assert.assertNotNull; import static graphql.Assert.assertShouldNeverHappen; import static graphql.Assert.assertTrue; import static graphql.analysis.values.ValueVisitor.ABSENCE_SENTINEL; @@ -46,13 +49,14 @@ * null values for non-nullable types say, so you need to be careful. */ @PublicApi +@NullMarked public class ValueTraverser { private static class InputElements implements ValueVisitor.InputElements { private final ImmutableList inputElements; private final List unwrappedInputElements; - private final GraphQLInputValueDefinition lastElement; + private final @Nullable GraphQLInputValueDefinition lastElement; private InputElements(GraphQLInputSchemaElement startElement) { this.inputElements = ImmutableList.of(startElement); @@ -88,7 +92,7 @@ public List getUnwrappedInputElements() { } @Override - public GraphQLInputValueDefinition getLastInputValueDefinition() { + public @Nullable GraphQLInputValueDefinition getLastInputValueDefinition() { return lastElement; } } @@ -161,7 +165,7 @@ public static Map visitPreOrder(Map coercedArgum * * @return the same value if nothing changes or a new value if the visitor changes anything */ - public static Object visitPreOrder(Object coercedArgumentValue, GraphQLArgument argument, ValueVisitor visitor) { + public static @Nullable Object visitPreOrder(@Nullable Object coercedArgumentValue, GraphQLArgument argument, ValueVisitor visitor) { InputElements inputElements = new InputElements(argument); Object newValue = visitor.visitArgumentValue(coercedArgumentValue, argument, inputElements); if (newValue == ABSENCE_SENTINEL) { @@ -185,7 +189,7 @@ public static Object visitPreOrder(Object coercedArgumentValue, GraphQLArgument * * @return the same value if nothing changes or a new value if the visitor changes anything */ - public static Object visitPreOrder(Object coercedArgumentValue, GraphQLAppliedDirectiveArgument argument, ValueVisitor visitor) { + public static @Nullable Object visitPreOrder(@Nullable Object coercedArgumentValue, GraphQLAppliedDirectiveArgument argument, ValueVisitor visitor) { InputElements inputElements = new InputElements(argument); Object newValue = visitor.visitAppliedDirectiveArgumentValue(coercedArgumentValue, argument, inputElements); if (newValue == ABSENCE_SENTINEL) { @@ -198,7 +202,7 @@ public static Object visitPreOrder(Object coercedArgumentValue, GraphQLAppliedDi return newValue; } - private static Object visitPreOrderImpl(Object coercedValue, GraphQLInputType startingInputType, InputElements containingElements, ValueVisitor visitor) { + private static @Nullable Object visitPreOrderImpl(@Nullable Object coercedValue, GraphQLInputType startingInputType, InputElements containingElements, ValueVisitor visitor) { if (startingInputType instanceof GraphQLNonNull) { containingElements = containingElements.push(startingInputType); } @@ -218,7 +222,7 @@ private static Object visitPreOrderImpl(Object coercedValue, GraphQLInputType st } } - private static Object visitObjectValue(Object coercedValue, GraphQLInputObjectType inputObjectType, InputElements containingElements, ValueVisitor visitor) { + private static @Nullable Object visitObjectValue(@Nullable Object coercedValue, GraphQLInputObjectType inputObjectType, InputElements containingElements, ValueVisitor visitor) { if (coercedValue != null) { assertTrue(coercedValue instanceof Map, "A input object type MUST have an Map value"); } @@ -263,7 +267,7 @@ private static Object visitObjectValue(Object coercedValue, GraphQLInputObjectTy } } - private static Object visitListValue(Object coercedValue, GraphQLList listInputType, InputElements containingElements, ValueVisitor visitor) { + private static @Nullable Object visitListValue(@Nullable Object coercedValue, GraphQLList listInputType, InputElements containingElements, ValueVisitor visitor) { if (coercedValue != null) { assertTrue(coercedValue instanceof List, "A list type MUST have an List value"); } @@ -281,7 +285,7 @@ private static Object visitListValue(Object coercedValue, GraphQLList listInputT Object newValue = visitPreOrderImpl(subValue, inputType, containingElements, visitor); if (copiedList != null) { if (newValue != ABSENCE_SENTINEL) { - copiedList.add(newValue); + copiedList.add(assertNotNull(newValue, "list element must not be null")); } } else if (hasChanged(newValue, subValue)) { // go into copy mode because something has changed @@ -291,7 +295,7 @@ private static Object visitListValue(Object coercedValue, GraphQLList listInputT copiedList.add(newList.get(j)); } if (newValue != ABSENCE_SENTINEL) { - copiedList.add(newValue); + copiedList.add(assertNotNull(newValue, "list element must not be null")); } } i++; @@ -306,11 +310,11 @@ private static Object visitListValue(Object coercedValue, GraphQLList listInputT } } - private static boolean hasChanged(Object newValue, Object oldValue) { + private static boolean hasChanged(@Nullable Object newValue, @Nullable Object oldValue) { return newValue != oldValue || newValue == ABSENCE_SENTINEL; } - private static void setNewValue(Map newMap, String key, Object newValue) { + private static void setNewValue(Map newMap, String key, @Nullable Object newValue) { if (newValue == ABSENCE_SENTINEL) { newMap.remove(key); } else { diff --git a/src/main/java/graphql/execution/AbortExecutionException.java b/src/main/java/graphql/execution/AbortExecutionException.java index 950dfde9a0..fcbfd4612e 100644 --- a/src/main/java/graphql/execution/AbortExecutionException.java +++ b/src/main/java/graphql/execution/AbortExecutionException.java @@ -7,6 +7,8 @@ import graphql.GraphQLException; import graphql.PublicApi; import graphql.language.SourceLocation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; @@ -19,6 +21,7 @@ * This Exception indicates that the current execution should be aborted. */ @PublicApi +@NullMarked public class AbortExecutionException extends GraphQLException implements GraphQLError { private final List underlyingErrors; @@ -47,7 +50,7 @@ public AbortExecutionException(Throwable cause) { } @Override - public List getLocations() { + public @Nullable List getLocations() { return null; } diff --git a/src/main/java/graphql/execution/AsyncExecutionStrategy.java b/src/main/java/graphql/execution/AsyncExecutionStrategy.java index 8d1d430581..325a5829a8 100644 --- a/src/main/java/graphql/execution/AsyncExecutionStrategy.java +++ b/src/main/java/graphql/execution/AsyncExecutionStrategy.java @@ -3,6 +3,7 @@ import graphql.ExecutionResult; import graphql.PublicApi; import graphql.execution.incremental.DeferredExecutionSupport; +import org.jspecify.annotations.NullMarked; import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext; import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters; @@ -17,6 +18,7 @@ * The standard graphql execution strategy that runs fields asynchronously non-blocking. */ @PublicApi +@NullMarked public class AsyncExecutionStrategy extends AbstractAsyncExecutionStrategy { /** diff --git a/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java b/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java index b5ff7cd5fa..6c865e05a0 100644 --- a/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java +++ b/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java @@ -4,6 +4,8 @@ import graphql.ExecutionResult; import graphql.PublicApi; import graphql.execution.instrumentation.Instrumentation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import graphql.execution.instrumentation.InstrumentationContext; import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters; import graphql.introspection.Introspection; @@ -12,6 +14,7 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; +import static graphql.Assert.assertNotNull; import static graphql.execution.instrumentation.SimpleInstrumentationContext.nonNullCtx; /** @@ -19,6 +22,7 @@ * See {@link AsyncExecutionStrategy} for a non-serial (parallel) execution of every field. */ @PublicApi +@NullMarked public class AsyncSerialExecutionStrategy extends AbstractAsyncExecutionStrategy { public AsyncSerialExecutionStrategy() { @@ -50,7 +54,7 @@ public CompletableFuture execute(ExecutionContext executionCont } CompletableFuture> resultsFuture = Async.eachSequentially(fieldNames, (fieldName, prevResults) -> { - MergedField currentField = fields.getSubField(fieldName); + MergedField currentField = assertNotNull(fields.getSubField(fieldName), "currentField must not be null"); ResultPath fieldPath = parameters.getPath().segment(mkNameForPath(currentField)); ExecutionStrategyParameters newParameters = parameters.transform(currentField, fieldPath); @@ -65,7 +69,7 @@ public CompletableFuture execute(ExecutionContext executionCont return overallResult; } - private Object resolveSerialField(ExecutionContext executionContext, + private @Nullable Object resolveSerialField(ExecutionContext executionContext, DataLoaderDispatchStrategy dataLoaderDispatcherStrategy, ExecutionStrategyParameters newParameters) { dataLoaderDispatcherStrategy.executionSerialStrategy(executionContext, newParameters); diff --git a/src/main/java/graphql/execution/CoercedVariables.java b/src/main/java/graphql/execution/CoercedVariables.java index 6123aeec82..b567b017e9 100644 --- a/src/main/java/graphql/execution/CoercedVariables.java +++ b/src/main/java/graphql/execution/CoercedVariables.java @@ -3,6 +3,8 @@ import graphql.PublicApi; import graphql.collect.ImmutableKit; import graphql.collect.ImmutableMapWithNullValues; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Map; @@ -10,6 +12,7 @@ * Holds coerced variables, that is their values are now in a canonical form. */ @PublicApi +@NullMarked public class CoercedVariables { private static final CoercedVariables EMPTY = CoercedVariables.of(ImmutableKit.emptyMap()); private final ImmutableMapWithNullValues coercedVariables; @@ -26,7 +29,7 @@ public boolean containsKey(String key) { return coercedVariables.containsKey(key); } - public Object get(String key) { + public @Nullable Object get(String key) { return coercedVariables.get(key); } diff --git a/src/main/java/graphql/execution/DataFetcherExceptionHandlerParameters.java b/src/main/java/graphql/execution/DataFetcherExceptionHandlerParameters.java index b17e3582b5..494f02cd56 100644 --- a/src/main/java/graphql/execution/DataFetcherExceptionHandlerParameters.java +++ b/src/main/java/graphql/execution/DataFetcherExceptionHandlerParameters.java @@ -4,6 +4,9 @@ import graphql.language.SourceLocation; import graphql.schema.DataFetchingEnvironment; import graphql.schema.GraphQLFieldDefinition; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import java.util.Map; @@ -11,6 +14,7 @@ * The parameters available to {@link DataFetcherExceptionHandler}s */ @PublicApi +@NullMarked public class DataFetcherExceptionHandlerParameters { private final DataFetchingEnvironment dataFetchingEnvironment; @@ -45,7 +49,7 @@ public Map getArgumentValues() { return dataFetchingEnvironment.getArguments(); } - public SourceLocation getSourceLocation() { + public @Nullable SourceLocation getSourceLocation() { return getField().getSingleField().getSourceLocation(); } @@ -53,6 +57,7 @@ public static Builder newExceptionParameters() { return new Builder(); } + @NullUnmarked public static class Builder { DataFetchingEnvironment dataFetchingEnvironment; Throwable exception; diff --git a/src/main/java/graphql/execution/DataFetcherExceptionHandlerResult.java b/src/main/java/graphql/execution/DataFetcherExceptionHandlerResult.java index 4059e91824..f05115e3ed 100644 --- a/src/main/java/graphql/execution/DataFetcherExceptionHandlerResult.java +++ b/src/main/java/graphql/execution/DataFetcherExceptionHandlerResult.java @@ -2,6 +2,8 @@ import graphql.GraphQLError; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; import java.util.ArrayList; import java.util.List; @@ -12,6 +14,7 @@ * The result object for {@link graphql.execution.DataFetcherExceptionHandler}s */ @PublicApi +@NullMarked public class DataFetcherExceptionHandlerResult { private final List errors; @@ -32,6 +35,7 @@ public static Builder newResult(GraphQLError error) { return new Builder().error(error); } + @NullUnmarked public static class Builder { private final List errors = new ArrayList<>(); diff --git a/src/main/java/graphql/execution/DefaultValueUnboxer.java b/src/main/java/graphql/execution/DefaultValueUnboxer.java index db03d1cfad..b763ce11af 100644 --- a/src/main/java/graphql/execution/DefaultValueUnboxer.java +++ b/src/main/java/graphql/execution/DefaultValueUnboxer.java @@ -2,6 +2,8 @@ import graphql.Internal; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Optional; import java.util.OptionalDouble; @@ -12,16 +14,17 @@ * Public API because it should be used as a delegate when implementing a custom {@link ValueUnboxer} */ @PublicApi +@NullMarked public class DefaultValueUnboxer implements ValueUnboxer { @Override - public Object unbox(final Object object) { + public @Nullable Object unbox(final Object object) { return unboxValue(object); } @Internal // used by next-gen at the moment - public static Object unboxValue(Object result) { + public static @Nullable Object unboxValue(Object result) { if (result instanceof Optional) { Optional optional = (Optional) result; return optional.orElse(null); diff --git a/src/main/java/graphql/execution/ExecutionContext.java b/src/main/java/graphql/execution/ExecutionContext.java index ac4b1a8b0d..b4e1b52cea 100644 --- a/src/main/java/graphql/execution/ExecutionContext.java +++ b/src/main/java/graphql/execution/ExecutionContext.java @@ -23,6 +23,7 @@ import graphql.util.FpKit; import graphql.util.LockKit; import org.dataloader.DataLoaderRegistry; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import java.util.HashSet; @@ -34,8 +35,11 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import static graphql.Assert.assertNotNull; + @SuppressWarnings("TypeParameterUnusedInFormals") @PublicApi +@NullMarked public class ExecutionContext { private final GraphQLSchema graphQLSchema; @@ -175,7 +179,7 @@ public T getRoot() { return (T) root; } - public FragmentDefinition getFragment(String name) { + public @Nullable FragmentDefinition getFragment(String name) { return fragmentsByName.get(name); } @@ -247,7 +251,7 @@ public void addError(GraphQLError error, ResultPath fieldPath) { if (!errorPaths.add(fieldPath)) { return; } - this.errors.set(ImmutableKit.addToList(this.errors.get(), error)); + this.errors.set(ImmutableKit.addToList(assertNotNull(this.errors.get(), "errors list must not be null"), error)); }); } @@ -266,7 +270,7 @@ public void addError(GraphQLError error) { ResultPath path = ResultPath.fromList(error.getPath()); this.errorPaths.add(path); } - this.errors.set(ImmutableKit.addToList(this.errors.get(), error)); + this.errors.set(ImmutableKit.addToList(assertNotNull(this.errors.get(), "errors list must not be null"), error)); }); } @@ -294,7 +298,7 @@ public void addErrors(List errors) { } } this.errorPaths.addAll(newErrorPaths); - this.errors.set(ImmutableKit.concatLists(this.errors.get(), errors)); + this.errors.set(ImmutableKit.concatLists(assertNotNull(this.errors.get(), "errors list must not be null"), errors)); }); } @@ -307,7 +311,7 @@ public ResponseMapFactory getResponseMapFactory() { * @return the total list of errors for this execution context */ public List getErrors() { - return errors.get(); + return assertNotNull(errors.get(), "errors list must not be null"); } public ExecutionStrategy getQueryStrategy() { diff --git a/src/main/java/graphql/execution/ExecutionId.java b/src/main/java/graphql/execution/ExecutionId.java index 40ffd3466f..4812c8edd7 100644 --- a/src/main/java/graphql/execution/ExecutionId.java +++ b/src/main/java/graphql/execution/ExecutionId.java @@ -3,11 +3,13 @@ import graphql.Assert; import graphql.PublicApi; import graphql.util.IdGenerator; +import org.jspecify.annotations.NullMarked; /** * This opaque identifier is used to identify a unique query execution */ @PublicApi +@NullMarked public class ExecutionId { /** diff --git a/src/main/java/graphql/execution/ExecutionStepInfo.java b/src/main/java/graphql/execution/ExecutionStepInfo.java index 26737cd127..0fecdfc491 100644 --- a/src/main/java/graphql/execution/ExecutionStepInfo.java +++ b/src/main/java/graphql/execution/ExecutionStepInfo.java @@ -9,6 +9,9 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; import graphql.schema.GraphQLTypeUtil; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import java.util.Map; import java.util.function.Consumer; @@ -26,6 +29,7 @@ * type instances, so this helper class adds this information during query execution. */ @PublicApi +@NullMarked public class ExecutionStepInfo { /* @@ -56,16 +60,16 @@ public class ExecutionStepInfo { * A list element is characterized by having a path ending with an index segment. (ResultPath.isListSegment()) */ private final ResultPath path; - private final ExecutionStepInfo parent; + private final @Nullable ExecutionStepInfo parent; /** * field, fieldDefinition, fieldContainer and arguments differ per field StepInfo. *

* But for list StepInfos these properties are the same as the field returning the list. */ - private final MergedField field; - private final GraphQLFieldDefinition fieldDefinition; - private final GraphQLObjectType fieldContainer; + private final @Nullable MergedField field; + private final @Nullable GraphQLFieldDefinition fieldDefinition; + private final @Nullable GraphQLObjectType fieldContainer; private final Supplier> arguments; private ExecutionStepInfo(Builder builder) { @@ -83,10 +87,10 @@ private ExecutionStepInfo(Builder builder) { */ private ExecutionStepInfo(GraphQLOutputType type, ResultPath path, - ExecutionStepInfo parent, - MergedField field, - GraphQLFieldDefinition fieldDefinition, - GraphQLObjectType fieldContainer, + @Nullable ExecutionStepInfo parent, + @Nullable MergedField field, + @Nullable GraphQLFieldDefinition fieldDefinition, + @Nullable GraphQLObjectType fieldContainer, Supplier> arguments) { this.type = assertNotNull(type, "you must provide a graphql type"); this.path = path; @@ -104,7 +108,7 @@ private ExecutionStepInfo(GraphQLOutputType type, * * @return the GraphQLObjectType defining the {@link #getFieldDefinition()} */ - public GraphQLObjectType getObjectType() { + public @Nullable GraphQLObjectType getObjectType() { return fieldContainer; } @@ -144,7 +148,7 @@ public T getUnwrappedNonNullTypeAs() { * * @return the field definition or null if there is not one */ - public GraphQLFieldDefinition getFieldDefinition() { + public @Nullable GraphQLFieldDefinition getFieldDefinition() { return fieldDefinition; } @@ -153,7 +157,7 @@ public GraphQLFieldDefinition getFieldDefinition() { * * @return the merged fields */ - public MergedField getField() { + public @Nullable MergedField getField() { return field; } @@ -194,14 +198,14 @@ public Map getArguments() { * @return the named argument or null if it's not present */ @SuppressWarnings("unchecked") - public T getArgument(String name) { + public @Nullable T getArgument(String name) { return (T) getArguments().get(name); } /** * @return the parent type information */ - public ExecutionStepInfo getParent() { + public @Nullable ExecutionStepInfo getParent() { return parent; } @@ -264,7 +268,7 @@ public ExecutionStepInfo transform(Consumer builderConsumer) { } public String getResultKey() { - return field.getResultKey(); + return assertNotNull(field, "field must not be null").getResultKey(); } /** @@ -278,6 +282,7 @@ public static ExecutionStepInfo.Builder newExecutionStepInfo(ExecutionStepInfo e return new Builder(existing); } + @NullUnmarked public static class Builder { GraphQLOutputType type; ExecutionStepInfo parentInfo; diff --git a/src/main/java/graphql/execution/ExecutionStepInfoFactory.java b/src/main/java/graphql/execution/ExecutionStepInfoFactory.java index 286106a7dc..fc382c2052 100644 --- a/src/main/java/graphql/execution/ExecutionStepInfoFactory.java +++ b/src/main/java/graphql/execution/ExecutionStepInfoFactory.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.function.Supplier; +import static graphql.Assert.assertNotNull; import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo; @Internal @@ -44,7 +45,7 @@ public ExecutionStepInfo createExecutionStepInfo(ExecutionContext executionConte ExecutionStrategyParameters parameters, GraphQLFieldDefinition fieldDefinition, @Nullable GraphQLObjectType fieldContainer) { - MergedField field = parameters.getField(); + MergedField field = assertNotNull(parameters.getField(), "field must not be null"); ExecutionStepInfo parentStepInfo = parameters.getExecutionStepInfo(); GraphQLOutputType fieldType = fieldDefinition.getType(); List fieldArgDefs = fieldDefinition.getArguments(); diff --git a/src/main/java/graphql/execution/ExecutionStrategyParameters.java b/src/main/java/graphql/execution/ExecutionStrategyParameters.java index 71d7fd9f8b..21b828b7d5 100644 --- a/src/main/java/graphql/execution/ExecutionStrategyParameters.java +++ b/src/main/java/graphql/execution/ExecutionStrategyParameters.java @@ -3,6 +3,8 @@ import graphql.Internal; import graphql.PublicApi; import graphql.execution.incremental.AlternativeCallContext; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import java.util.function.Consumer; @@ -13,26 +15,27 @@ * The parameters that are passed to execution strategies */ @PublicApi +@NullMarked public class ExecutionStrategyParameters { private final ExecutionStepInfo executionStepInfo; - private final Object source; - private final Object localContext; + private final @Nullable Object source; + private final @Nullable Object localContext; private final MergedSelectionSet fields; private final NonNullableFieldValidator nonNullableFieldValidator; private final ResultPath path; - private final MergedField currentField; - private final ExecutionStrategyParameters parent; - private final AlternativeCallContext alternativeCallContext; + private final @Nullable MergedField currentField; + private final @Nullable ExecutionStrategyParameters parent; + private final @Nullable AlternativeCallContext alternativeCallContext; private ExecutionStrategyParameters(ExecutionStepInfo executionStepInfo, - Object source, - Object localContext, + @Nullable Object source, + @Nullable Object localContext, MergedSelectionSet fields, NonNullableFieldValidator nonNullableFieldValidator, ResultPath path, - MergedField currentField, - ExecutionStrategyParameters parent, - AlternativeCallContext alternativeCallContext) { + @Nullable MergedField currentField, + @Nullable ExecutionStrategyParameters parent, + @Nullable AlternativeCallContext alternativeCallContext) { this.executionStepInfo = assertNotNull(executionStepInfo, "executionStepInfo is null"); this.localContext = localContext; @@ -49,7 +52,7 @@ public ExecutionStepInfo getExecutionStepInfo() { return executionStepInfo; } - public Object getSource() { + public @Nullable Object getSource() { return source; } @@ -65,11 +68,11 @@ public ResultPath getPath() { return path; } - public Object getLocalContext() { + public @Nullable Object getLocalContext() { return localContext; } - public ExecutionStrategyParameters getParent() { + public @Nullable ExecutionStrategyParameters getParent() { return parent; } @@ -113,7 +116,7 @@ public boolean isInDeferredContext() { * * @return the current merged fields */ - public MergedField getField() { + public @Nullable MergedField getField() { return currentField; } @@ -134,7 +137,7 @@ ExecutionStrategyParameters transform(MergedField currentField, @Internal ExecutionStrategyParameters transform(ExecutionStepInfo executionStepInfo, MergedSelectionSet fields, - Object source) { + @Nullable Object source) { return new ExecutionStrategyParameters(executionStepInfo, source, localContext, @@ -149,8 +152,8 @@ ExecutionStrategyParameters transform(ExecutionStepInfo executionStepInfo, @Internal ExecutionStrategyParameters transform(ExecutionStepInfo executionStepInfo, ResultPath path, - Object localContext, - Object source) { + @Nullable Object localContext, + @Nullable Object source) { return new ExecutionStrategyParameters(executionStepInfo, source, localContext, @@ -164,8 +167,8 @@ ExecutionStrategyParameters transform(ExecutionStepInfo executionStepInfo, @Internal ExecutionStrategyParameters transform(ExecutionStepInfo executionStepInfo, - Object localContext, - Object source) { + @Nullable Object localContext, + @Nullable Object source) { return new ExecutionStrategyParameters(executionStepInfo, source, localContext, @@ -212,6 +215,7 @@ public static Builder newParameters(ExecutionStrategyParameters oldParameters) { return new Builder(oldParameters); } + @NullUnmarked public static class Builder { ExecutionStepInfo executionStepInfo; Object source; diff --git a/src/main/java/graphql/execution/FetchedValue.java b/src/main/java/graphql/execution/FetchedValue.java index fe56fa0a10..b4b5f03875 100644 --- a/src/main/java/graphql/execution/FetchedValue.java +++ b/src/main/java/graphql/execution/FetchedValue.java @@ -4,6 +4,8 @@ import graphql.GraphQLError; import graphql.PublicApi; import graphql.execution.instrumentation.parameters.InstrumentationFieldCompleteParameters; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.List; @@ -12,9 +14,10 @@ * and therefore part of the public despite never used in a method signature. */ @PublicApi +@NullMarked public class FetchedValue { - private final Object fetchedValue; - private final Object localContext; + private final @Nullable Object fetchedValue; + private final @Nullable Object localContext; private final ImmutableList errors; /** @@ -25,7 +28,7 @@ public class FetchedValue { * * @return the {@link FetchedValue#getFetchedValue()} if its wrapped otherwise the source value itself */ - public static Object getFetchedValue(Object sourceValue) { + public static @Nullable Object getFetchedValue(@Nullable Object sourceValue) { if (sourceValue instanceof FetchedValue) { return ((FetchedValue) sourceValue).fetchedValue; } else { @@ -42,7 +45,7 @@ public static Object getFetchedValue(Object sourceValue) { * * @return the {@link FetchedValue#getFetchedValue()} if its wrapped otherwise the default local context */ - public static Object getLocalContext(Object sourceValue, Object defaultLocalContext) { + public static @Nullable Object getLocalContext(@Nullable Object sourceValue, @Nullable Object defaultLocalContext) { if (sourceValue instanceof FetchedValue) { return ((FetchedValue) sourceValue).localContext; } else { @@ -50,7 +53,7 @@ public static Object getLocalContext(Object sourceValue, Object defaultLocalCont } } - public FetchedValue(Object fetchedValue, List errors, Object localContext) { + public FetchedValue(@Nullable Object fetchedValue, List errors, @Nullable Object localContext) { this.fetchedValue = fetchedValue; this.errors = ImmutableList.copyOf(errors); this.localContext = localContext; @@ -59,7 +62,7 @@ public FetchedValue(Object fetchedValue, List errors, Object local /* * the unboxed value meaning not Optional, not DataFetcherResult etc */ - public Object getFetchedValue() { + public @Nullable Object getFetchedValue() { return fetchedValue; } @@ -67,7 +70,7 @@ public List getErrors() { return errors; } - public Object getLocalContext() { + public @Nullable Object getLocalContext() { return localContext; } diff --git a/src/main/java/graphql/execution/FieldValueInfo.java b/src/main/java/graphql/execution/FieldValueInfo.java index 2839a8cd5b..0108503bca 100644 --- a/src/main/java/graphql/execution/FieldValueInfo.java +++ b/src/main/java/graphql/execution/FieldValueInfo.java @@ -2,6 +2,8 @@ import com.google.common.collect.ImmutableList; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -19,6 +21,7 @@ * values might need a call to a database or other systems will tend to be {@link CompletableFuture} promises. */ @PublicApi +@NullMarked public class FieldValueInfo { public enum CompleteValueType { @@ -30,14 +33,14 @@ public enum CompleteValueType { } private final CompleteValueType completeValueType; - private final Object /* CompletableFuture | Object */ fieldValueObject; + private final @Nullable Object /* CompletableFuture | Object */ fieldValueObject; private final List fieldValueInfos; - public FieldValueInfo(CompleteValueType completeValueType, Object fieldValueObject) { + public FieldValueInfo(CompleteValueType completeValueType, @Nullable Object fieldValueObject) { this(completeValueType, fieldValueObject, ImmutableList.of()); } - public FieldValueInfo(CompleteValueType completeValueType, Object fieldValueObject, List fieldValueInfos) { + public FieldValueInfo(CompleteValueType completeValueType, @Nullable Object fieldValueObject, List fieldValueInfos) { assertNotNull(fieldValueInfos, "fieldValueInfos can't be null"); this.completeValueType = completeValueType; this.fieldValueObject = fieldValueObject; @@ -58,7 +61,7 @@ public CompleteValueType getCompleteValueType() { * * @return either an object that is materialized or a {@link CompletableFuture} promise to a value */ - public Object /* CompletableFuture | Object */ getFieldValueObject() { + public @Nullable Object /* CompletableFuture | Object */ getFieldValueObject() { return fieldValueObject; } diff --git a/src/main/java/graphql/execution/InputMapDefinesTooManyFieldsException.java b/src/main/java/graphql/execution/InputMapDefinesTooManyFieldsException.java index 654c069c34..e4aef7e2c1 100644 --- a/src/main/java/graphql/execution/InputMapDefinesTooManyFieldsException.java +++ b/src/main/java/graphql/execution/InputMapDefinesTooManyFieldsException.java @@ -7,6 +7,8 @@ import graphql.language.SourceLocation; import graphql.schema.GraphQLType; import graphql.schema.GraphQLTypeUtil; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.List; @@ -16,6 +18,7 @@ * - This unordered map should not contain any entries with names not defined by a field of this input object type, otherwise an error should be thrown. */ @PublicApi +@NullMarked public class InputMapDefinesTooManyFieldsException extends GraphQLException implements GraphQLError { public InputMapDefinesTooManyFieldsException(GraphQLType graphQLType, String fieldName) { @@ -23,7 +26,7 @@ public InputMapDefinesTooManyFieldsException(GraphQLType graphQLType, String fie } @Override - public List getLocations() { + public @Nullable List getLocations() { return null; } diff --git a/src/main/java/graphql/execution/MergedSelectionSet.java b/src/main/java/graphql/execution/MergedSelectionSet.java index 434b8da4f5..75b7df45ae 100644 --- a/src/main/java/graphql/execution/MergedSelectionSet.java +++ b/src/main/java/graphql/execution/MergedSelectionSet.java @@ -3,6 +3,9 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import java.util.List; import java.util.Map; @@ -10,6 +13,7 @@ @PublicApi +@NullMarked public class MergedSelectionSet { private final Map subFields; @@ -36,7 +40,7 @@ public Set keySet() { return subFields.keySet(); } - public MergedField getSubField(String key) { + public @Nullable MergedField getSubField(String key) { return subFields.get(key); } @@ -52,6 +56,7 @@ public static Builder newMergedSelectionSet() { return new Builder(); } + @NullUnmarked public static class Builder { private Map subFields; diff --git a/src/main/java/graphql/execution/MissingRootTypeException.java b/src/main/java/graphql/execution/MissingRootTypeException.java index 9662f69ec0..7ae68cbb31 100644 --- a/src/main/java/graphql/execution/MissingRootTypeException.java +++ b/src/main/java/graphql/execution/MissingRootTypeException.java @@ -8,21 +8,24 @@ import graphql.GraphQLException; import graphql.PublicApi; import graphql.language.SourceLocation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * This is thrown if a query is attempting to perform an operation not defined in the GraphQL schema */ @PublicApi +@NullMarked public class MissingRootTypeException extends GraphQLException implements GraphQLError { - private List sourceLocations; + private @Nullable List sourceLocations; - public MissingRootTypeException(String message, SourceLocation sourceLocation) { + public MissingRootTypeException(String message, @Nullable SourceLocation sourceLocation) { super(message); this.sourceLocations = sourceLocation == null ? null : Collections.singletonList(sourceLocation); } @Override - public List getLocations() { + public @Nullable List getLocations() { return sourceLocations; } diff --git a/src/main/java/graphql/execution/NonNullableValueCoercedAsNullException.java b/src/main/java/graphql/execution/NonNullableValueCoercedAsNullException.java index 4dfb680815..9192d50802 100644 --- a/src/main/java/graphql/execution/NonNullableValueCoercedAsNullException.java +++ b/src/main/java/graphql/execution/NonNullableValueCoercedAsNullException.java @@ -10,6 +10,8 @@ import graphql.schema.GraphQLInputObjectField; import graphql.schema.GraphQLType; import graphql.schema.GraphQLTypeUtil; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Collections; import java.util.List; @@ -20,9 +22,10 @@ * This is thrown if a non nullable value is coerced to a null value */ @PublicApi +@NullMarked public class NonNullableValueCoercedAsNullException extends GraphQLException implements GraphQLError { - private List sourceLocations; - private List path; + private @Nullable List sourceLocations; + private @Nullable List path; public NonNullableValueCoercedAsNullException(VariableDefinition variableDefinition, GraphQLType graphQLType) { super(format("Variable '%s' has coerced Null value for NonNull type '%s'", @@ -74,12 +77,12 @@ public NonNullableValueCoercedAsNullException(GraphQLArgument graphQLArgument) { } @Override - public List getLocations() { + public @Nullable List getLocations() { return sourceLocations; } @Override - public List getPath() { + public @Nullable List getPath() { return path; } diff --git a/src/main/java/graphql/execution/NormalizedVariables.java b/src/main/java/graphql/execution/NormalizedVariables.java index ef16fec3cf..59b1d905b3 100644 --- a/src/main/java/graphql/execution/NormalizedVariables.java +++ b/src/main/java/graphql/execution/NormalizedVariables.java @@ -4,6 +4,8 @@ import graphql.collect.ImmutableKit; import graphql.collect.ImmutableMapWithNullValues; import graphql.normalized.NormalizedInputValue; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Map; @@ -11,6 +13,7 @@ * Holds coerced variables, that is their values are now in a normalized {@link graphql.normalized.NormalizedInputValue} form. */ @PublicApi +@NullMarked public class NormalizedVariables { private final ImmutableMapWithNullValues normalisedVariables; @@ -26,7 +29,7 @@ public boolean containsKey(String key) { return normalisedVariables.containsKey(key); } - public Object get(String key) { + public @Nullable Object get(String key) { return normalisedVariables.get(key); } diff --git a/src/main/java/graphql/execution/OneOfNullValueException.java b/src/main/java/graphql/execution/OneOfNullValueException.java index 40e5bbccae..cc9d3da824 100644 --- a/src/main/java/graphql/execution/OneOfNullValueException.java +++ b/src/main/java/graphql/execution/OneOfNullValueException.java @@ -5,6 +5,8 @@ import graphql.GraphQLException; import graphql.PublicApi; import graphql.language.SourceLocation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.List; @@ -12,6 +14,7 @@ * The input map to One Of Input Types MUST only have 1 entry with a non null value */ @PublicApi +@NullMarked public class OneOfNullValueException extends GraphQLException implements GraphQLError { public OneOfNullValueException(String message) { @@ -19,7 +22,7 @@ public OneOfNullValueException(String message) { } @Override - public List getLocations() { + public @Nullable List getLocations() { return null; } diff --git a/src/main/java/graphql/execution/OneOfTooManyKeysException.java b/src/main/java/graphql/execution/OneOfTooManyKeysException.java index f8d3c0053a..50ff017b15 100644 --- a/src/main/java/graphql/execution/OneOfTooManyKeysException.java +++ b/src/main/java/graphql/execution/OneOfTooManyKeysException.java @@ -5,6 +5,8 @@ import graphql.GraphQLException; import graphql.PublicApi; import graphql.language.SourceLocation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.List; @@ -12,6 +14,7 @@ * The input map to One Of Input Types MUST only have 1 entry */ @PublicApi +@NullMarked public class OneOfTooManyKeysException extends GraphQLException implements GraphQLError { public OneOfTooManyKeysException(String message) { @@ -19,7 +22,7 @@ public OneOfTooManyKeysException(String message) { } @Override - public List getLocations() { + public @Nullable List getLocations() { return null; } diff --git a/src/main/java/graphql/execution/ResultNodesInfo.java b/src/main/java/graphql/execution/ResultNodesInfo.java index afc366f6be..9e8b3cfec5 100644 --- a/src/main/java/graphql/execution/ResultNodesInfo.java +++ b/src/main/java/graphql/execution/ResultNodesInfo.java @@ -2,6 +2,7 @@ import graphql.Internal; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; import java.util.concurrent.atomic.AtomicInteger; @@ -14,6 +15,7 @@ *

*/ @PublicApi +@NullMarked public class ResultNodesInfo { public static final String MAX_RESULT_NODES = "__MAX_RESULT_NODES"; diff --git a/src/main/java/graphql/execution/ResultPath.java b/src/main/java/graphql/execution/ResultPath.java index 20d13f1399..daa56bb411 100644 --- a/src/main/java/graphql/execution/ResultPath.java +++ b/src/main/java/graphql/execution/ResultPath.java @@ -4,6 +4,8 @@ import graphql.AssertException; import graphql.PublicApi; import graphql.collect.ImmutableKit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.LinkedList; @@ -21,6 +23,7 @@ * class represents that path as a series of segments. */ @PublicApi +@NullMarked public class ResultPath { private static final ResultPath ROOT_PATH = new ResultPath(); @@ -33,13 +36,13 @@ public static ResultPath rootPath() { return ROOT_PATH; } - private final ResultPath parent; - private final Object segment; + private final @Nullable ResultPath parent; + private final @Nullable Object segment; // hash is effective immutable but lazily initialized similar to the hash code of java.lang.String private int hash; // lazily initialized similar to hash - computed on first toString() call - private String toStringValue; + private @Nullable String toStringValue; private final int level; private ResultPath() { @@ -72,7 +75,7 @@ public int getLevel() { return level; } - public ResultPath getPathWithoutListEnd() { + public @Nullable ResultPath getPathWithoutListEnd() { if (ROOT_PATH.equals(this)) { return ROOT_PATH; } @@ -98,18 +101,18 @@ public boolean isNamedSegment() { public String getSegmentName() { - return (String) segment; + return (String) assertNotNull(segment); } public int getSegmentIndex() { - return (int) segment; + return (int) assertNotNull(segment); } - public Object getSegmentValue() { + public @Nullable Object getSegmentValue() { return segment; } - public ResultPath getParent() { + public @Nullable ResultPath getParent() { return parent; } @@ -120,7 +123,7 @@ public ResultPath getParent() { * * @return a parsed execution path */ - public static ResultPath parse(String pathString) { + public static ResultPath parse(@Nullable String pathString) { pathString = pathString == null ? "" : pathString; String finalPathString = pathString.trim(); StringTokenizer st = new StringTokenizer(finalPathString, "/[]", true); @@ -195,7 +198,7 @@ public ResultPath segment(int segment) { * * @return a new path with the last segment dropped off */ - public ResultPath dropSegment() { + public @Nullable ResultPath dropSegment() { if (this == rootPath()) { return null; } @@ -212,7 +215,7 @@ public ResultPath dropSegment() { */ public ResultPath replaceSegment(int segment) { assertTrue(!ROOT_PATH.equals(this), "You MUST not call this with the root path"); - return new ResultPath(parent, segment); + return new ResultPath(assertNotNull(parent, "parent must not be null for non-root path"), segment); } /** @@ -225,7 +228,7 @@ public ResultPath replaceSegment(int segment) { */ public ResultPath replaceSegment(String segment) { assertTrue(!ROOT_PATH.equals(this), "You MUST not call this with the root path"); - return new ResultPath(parent, segment); + return new ResultPath(assertNotNull(parent, "parent must not be null for non-root path"), segment); } @@ -252,12 +255,12 @@ public ResultPath append(ResultPath path) { public ResultPath sibling(String siblingField) { assertTrue(!ROOT_PATH.equals(this), "You MUST not call this with the root path"); - return new ResultPath(this.parent, siblingField); + return new ResultPath(assertNotNull(this.parent, "parent must not be null for non-root path"), siblingField); } public ResultPath sibling(int siblingField) { assertTrue(!ROOT_PATH.equals(this), "You MUST not call this with the root path"); - return new ResultPath(this.parent, siblingField); + return new ResultPath(assertNotNull(this.parent, "parent must not be null for non-root path"), siblingField); } /** @@ -271,7 +274,7 @@ public List toList() { ResultPath p = this; while (p.segment != null) { list.addFirst(p.segment); - p = p.parent; + p = assertNotNull(p.parent, "non-root ResultPath must have a non-null parent"); } return ImmutableList.copyOf(list); } @@ -289,7 +292,7 @@ public List getKeysOnly() { if (p.segment instanceof String) { list.addFirst((String) p.segment); } - p = p.parent; + p = assertNotNull(p.parent, "non-root ResultPath must have a non-null parent"); } return list; } @@ -331,8 +334,8 @@ public boolean equals(Object o) { if (!Objects.equals(self.segment, that.segment)) { return false; } - self = self.parent; - that = that.parent; + self = assertNotNull(self.parent, "non-root ResultPath must have a non-null parent"); + that = assertNotNull(that.parent, "non-root ResultPath must have a non-null parent"); } return self.isRootPath() && that.isRootPath(); diff --git a/src/main/java/graphql/execution/SimpleDataFetcherExceptionHandler.java b/src/main/java/graphql/execution/SimpleDataFetcherExceptionHandler.java index 79e201a333..adfb4d0786 100644 --- a/src/main/java/graphql/execution/SimpleDataFetcherExceptionHandler.java +++ b/src/main/java/graphql/execution/SimpleDataFetcherExceptionHandler.java @@ -3,6 +3,7 @@ import graphql.ExceptionWhileDataFetching; import graphql.PublicApi; import graphql.language.SourceLocation; +import org.jspecify.annotations.NullMarked; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -12,6 +13,7 @@ * into the error collection */ @PublicApi +@NullMarked public class SimpleDataFetcherExceptionHandler implements DataFetcherExceptionHandler { static final SimpleDataFetcherExceptionHandler defaultImpl = new SimpleDataFetcherExceptionHandler(); diff --git a/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java b/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java index f1e1a9a919..89c77e967a 100644 --- a/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java +++ b/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java @@ -5,6 +5,8 @@ import graphql.ExecutionResultImpl; import graphql.GraphQLContext; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import graphql.execution.incremental.AlternativeCallContext; import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext; import graphql.execution.instrumentation.Instrumentation; @@ -25,6 +27,7 @@ import java.util.concurrent.Flow; import java.util.function.Function; +import static graphql.Assert.assertNotNull; import static graphql.execution.instrumentation.SimpleInstrumentationContext.nonNullCtx; import static java.util.Collections.singletonMap; @@ -40,6 +43,7 @@ * See https://www.reactive-streams.org/ */ @PublicApi +@NullMarked public class SubscriptionExecutionStrategy extends ExecutionStrategy { /** @@ -132,7 +136,7 @@ private CompletableFuture> createSourceEventStream(ExecutionCo * @return a reactive streams {@link Publisher} always */ @SuppressWarnings("unchecked") - private static Publisher mkReactivePublisher(Object publisherObj) { + private static @Nullable Publisher mkReactivePublisher(@Nullable Object publisherObj) { if (publisherObj != null) { if (publisherObj instanceof Publisher) { return (Publisher) publisherObj; @@ -182,7 +186,7 @@ private CompletableFuture executeSubscriptionEvent(ExecutionCon executionContext.getDataLoaderDispatcherStrategy().subscriptionEventCompletionDone(newParameters.getDeferredCallContext()); CompletableFuture overallResult = fieldValueInfo .getFieldValueFuture() - .thenApply(val -> new ExecutionResultImpl(val, newParameters.getDeferredCallContext().getErrors())) + .thenApply(val -> new ExecutionResultImpl(val, assertNotNull(newParameters.getDeferredCallContext(), "deferredCallContext must not be null").getErrors())) .thenApply(executionResult -> wrapWithRootFieldName(newParameters, executionResult)); // dispatch instrumentation so they can know about each subscription event @@ -206,7 +210,7 @@ private ExecutionResult wrapWithRootFieldName(ExecutionStrategyParameters parame } private String getRootFieldName(ExecutionStrategyParameters parameters) { - Field rootField = parameters.getField().getSingleField(); + Field rootField = assertNotNull(parameters.getField(), "field must not be null").getSingleField(); return rootField.getResultKey(); } @@ -214,7 +218,7 @@ private ExecutionStrategyParameters firstFieldOfSubscriptionSelection(ExecutionC ExecutionStrategyParameters parameters, boolean newCallContext) { MergedSelectionSet fields = parameters.getFields(); - MergedField firstField = fields.getSubField(fields.getKeys().get(0)); + MergedField firstField = assertNotNull(fields.getSubField(fields.getKeys().get(0)), "firstField must not be null"); ResultPath fieldPath = parameters.getPath().segment(mkNameForPath(firstField.getSingleField())); NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext); @@ -234,7 +238,7 @@ private ExecutionStrategyParameters firstFieldOfSubscriptionSelection(ExecutionC private ExecutionStepInfo createSubscribedFieldStepInfo(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { - Field field = parameters.getField().getSingleField(); + Field field = assertNotNull(parameters.getField(), "field must not be null").getSingleField(); GraphQLObjectType parentType = parameters.getExecutionStepInfo().getUnwrappedNonNullTypeAs(); GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field); return createExecutionStepInfo(executionContext, parameters, fieldDef, parentType); diff --git a/src/main/java/graphql/execution/UnknownOperationException.java b/src/main/java/graphql/execution/UnknownOperationException.java index 6ef523c9b1..13f427dff6 100644 --- a/src/main/java/graphql/execution/UnknownOperationException.java +++ b/src/main/java/graphql/execution/UnknownOperationException.java @@ -6,6 +6,8 @@ import graphql.GraphQLException; import graphql.PublicApi; import graphql.language.SourceLocation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.List; @@ -15,13 +17,14 @@ * contained in the GraphQL query. */ @PublicApi +@NullMarked public class UnknownOperationException extends GraphQLException implements GraphQLError { public UnknownOperationException(String message) { super(message); } @Override - public List getLocations() { + public @Nullable List getLocations() { return null; } diff --git a/src/main/java/graphql/execution/UnresolvedTypeException.java b/src/main/java/graphql/execution/UnresolvedTypeException.java index 4a6aad47d6..f964affb26 100644 --- a/src/main/java/graphql/execution/UnresolvedTypeException.java +++ b/src/main/java/graphql/execution/UnresolvedTypeException.java @@ -5,12 +5,14 @@ import graphql.schema.GraphQLNamedOutputType; import graphql.schema.GraphQLType; import graphql.schema.GraphQLTypeUtil; +import org.jspecify.annotations.NullMarked; /** * This is thrown if a {@link graphql.schema.TypeResolver} fails to give back a concrete type * or provides a type that doesn't implement the given interface or union. */ @PublicApi +@NullMarked public class UnresolvedTypeException extends GraphQLException { private final GraphQLNamedOutputType interfaceOrUnionType; diff --git a/src/main/java/graphql/execution/conditional/ConditionalNodeDecision.java b/src/main/java/graphql/execution/conditional/ConditionalNodeDecision.java index 69afc6bbc2..14056d70e9 100644 --- a/src/main/java/graphql/execution/conditional/ConditionalNodeDecision.java +++ b/src/main/java/graphql/execution/conditional/ConditionalNodeDecision.java @@ -1,6 +1,7 @@ package graphql.execution.conditional; import graphql.ExperimentalApi; +import org.jspecify.annotations.NullMarked; /** * This callback interface allows custom implementations to decide if a field is included in a query or not. @@ -8,6 +9,7 @@ * The default `@skip / @include` is built in, but you can create your own implementations to allow you to make * decisions on whether fields are considered part of a query. */ +@NullMarked @ExperimentalApi public interface ConditionalNodeDecision { diff --git a/src/main/java/graphql/execution/directives/QueryAppliedDirective.java b/src/main/java/graphql/execution/directives/QueryAppliedDirective.java index 84492143fd..d4c7785b9b 100644 --- a/src/main/java/graphql/execution/directives/QueryAppliedDirective.java +++ b/src/main/java/graphql/execution/directives/QueryAppliedDirective.java @@ -7,7 +7,8 @@ import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLDirective; import graphql.schema.GraphqlTypeBuilder; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import java.util.Collection; @@ -32,6 +33,7 @@ *

* See https://graphql.org/learn/queries/#directives for more details on the concept. */ +@NullMarked @PublicApi public class QueryAppliedDirective { @@ -47,7 +49,6 @@ private QueryAppliedDirective(String name, Directive definition, Collection { private final Map arguments = new LinkedHashMap<>(); diff --git a/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java b/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java index 8b772e91f7..98cc68be15 100644 --- a/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java +++ b/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java @@ -10,7 +10,8 @@ import graphql.schema.GraphQLInputType; import graphql.schema.GraphqlTypeBuilder; import graphql.schema.InputValueWithState; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import java.util.Locale; @@ -25,6 +26,7 @@ *

* You can think of them as 'instances' of {@link GraphQLArgument}, when applied to a directive on a query element */ +@NullMarked @PublicApi public class QueryAppliedDirectiveArgument { @@ -47,12 +49,10 @@ private QueryAppliedDirectiveArgument(String name, this.definition = definition; } - @NonNull public String getName() { return name; } - @NonNull public GraphQLInputType getType() { return originalType; } @@ -64,7 +64,7 @@ public boolean hasSetValue() { /** * @return an input value with state for an applied directive argument */ - public @NonNull InputValueWithState getArgumentValue() { + public InputValueWithState getArgumentValue() { return value; } @@ -134,6 +134,7 @@ public String toString() { '}'; } + @NullUnmarked public static class Builder extends GraphqlTypeBuilder { private InputValueWithState value = InputValueWithState.NOT_SET; @@ -166,7 +167,7 @@ public Builder definition(Argument definition) { * * @return this builder */ - public Builder valueLiteral(@NonNull Value value) { + public Builder valueLiteral(Value value) { this.value = InputValueWithState.newLiteralValue(value); return this; } @@ -181,7 +182,7 @@ public Builder valueProgrammatic(@Nullable Object value) { return this; } - public Builder inputValueWithState(@NonNull InputValueWithState value) { + public Builder inputValueWithState(InputValueWithState value) { this.value = Assert.assertNotNull(value); return this; } diff --git a/src/main/java/graphql/execution/directives/QueryDirectives.java b/src/main/java/graphql/execution/directives/QueryDirectives.java index 6eee9f9323..ff7baf6869 100644 --- a/src/main/java/graphql/execution/directives/QueryDirectives.java +++ b/src/main/java/graphql/execution/directives/QueryDirectives.java @@ -2,6 +2,7 @@ import graphql.GraphQLContext; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; import graphql.execution.CoercedVariables; import graphql.execution.MergedField; import graphql.execution.NormalizedVariables; @@ -30,6 +31,7 @@ * * @see graphql.execution.MergedField */ +@NullMarked @PublicApi public interface QueryDirectives { diff --git a/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java b/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java index 408fd261be..bea5d52577 100644 --- a/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationInstrumentation.java @@ -8,6 +8,7 @@ import graphql.execution.instrumentation.InstrumentationState; import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import java.util.List; @@ -23,6 +24,7 @@ * * @see FieldValidation */ +@NullMarked @PublicApi public class FieldValidationInstrumentation extends SimplePerformantInstrumentation { diff --git a/src/main/java/graphql/execution/instrumentation/fieldvalidation/SimpleFieldValidation.java b/src/main/java/graphql/execution/instrumentation/fieldvalidation/SimpleFieldValidation.java index 9f0a340f19..cebbac5dd8 100644 --- a/src/main/java/graphql/execution/instrumentation/fieldvalidation/SimpleFieldValidation.java +++ b/src/main/java/graphql/execution/instrumentation/fieldvalidation/SimpleFieldValidation.java @@ -4,6 +4,7 @@ import graphql.GraphQLError; import graphql.PublicApi; import graphql.execution.ResultPath; +import org.jspecify.annotations.NullMarked; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -19,6 +20,7 @@ * Use {@link #addRule(ResultPath, java.util.function.BiFunction)} to supply the rule callbacks where * you implement your specific business logic */ +@NullMarked @PublicApi public class SimpleFieldValidation implements FieldValidation { diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationCreateStateParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationCreateStateParameters.java index da07ee2669..cc045ea1ce 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationCreateStateParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationCreateStateParameters.java @@ -3,10 +3,12 @@ import graphql.ExecutionInput; import graphql.PublicApi; import graphql.schema.GraphQLSchema; +import org.jspecify.annotations.NullMarked; /** * Parameters sent to {@link graphql.execution.instrumentation.Instrumentation} methods */ +@NullMarked @PublicApi public class InstrumentationCreateStateParameters { private final GraphQLSchema schema; diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters.java index 69587f3f44..0f8bb54c04 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecuteOperationParameters.java @@ -3,10 +3,12 @@ import graphql.PublicApi; import graphql.execution.ExecutionContext; import graphql.execution.instrumentation.Instrumentation; +import org.jspecify.annotations.NullMarked; /** * Parameters sent to {@link Instrumentation} methods */ +@NullMarked @SuppressWarnings("TypeParameterUnusedInFormals") @PublicApi public class InstrumentationExecuteOperationParameters { diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionParameters.java index 58ad4d5aee..285a3116bd 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionParameters.java @@ -6,18 +6,21 @@ import graphql.collect.ImmutableKit; import graphql.execution.instrumentation.Instrumentation; import graphql.schema.GraphQLSchema; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Map; /** * Parameters sent to {@link Instrumentation} methods */ +@NullMarked @PublicApi public class InstrumentationExecutionParameters { private final ExecutionInput executionInput; private final String query; - private final String operation; - private final Object context; + private final @Nullable String operation; + private final @Nullable Object context; private final GraphQLContext graphQLContext; private final Map variables; private final GraphQLSchema schema; @@ -41,6 +44,7 @@ public String getQuery() { return query; } + @Nullable public String getOperation() { return operation; } @@ -54,7 +58,7 @@ public String getOperation() { */ @Deprecated(since = "2021-07-05") @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) - public T getContext() { + public @Nullable T getContext() { return (T) context; } diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionStrategyParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionStrategyParameters.java index 9c93c84d42..f3503a0b32 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionStrategyParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationExecutionStrategyParameters.java @@ -3,10 +3,12 @@ import graphql.PublicApi; import graphql.execution.ExecutionContext; import graphql.execution.ExecutionStrategyParameters; +import org.jspecify.annotations.NullMarked; /** * Parameters sent to {@link graphql.execution.instrumentation.Instrumentation} methods */ +@NullMarked @PublicApi public class InstrumentationExecutionStrategyParameters { diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldCompleteParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldCompleteParameters.java index ac7e1b27e5..2a49c12644 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldCompleteParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldCompleteParameters.java @@ -5,20 +5,25 @@ import graphql.execution.ExecutionStepInfo; import graphql.execution.ExecutionStrategyParameters; import graphql.schema.GraphQLFieldDefinition; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.function.Supplier; +import static graphql.Assert.assertNotNull; + /** * Parameters sent to {@link graphql.execution.instrumentation.Instrumentation} methods */ +@NullMarked @PublicApi public class InstrumentationFieldCompleteParameters { private final ExecutionContext executionContext; private final Supplier executionStepInfo; - private final Object fetchedValue; + private final @Nullable Object fetchedValue; private final ExecutionStrategyParameters executionStrategyParameters; - public InstrumentationFieldCompleteParameters(ExecutionContext executionContext, ExecutionStrategyParameters executionStrategyParameters, Supplier executionStepInfo, Object fetchedValue) { + public InstrumentationFieldCompleteParameters(ExecutionContext executionContext, ExecutionStrategyParameters executionStrategyParameters, Supplier executionStepInfo, @Nullable Object fetchedValue) { this.executionContext = executionContext; this.executionStrategyParameters = executionStrategyParameters; this.executionStepInfo = executionStepInfo; @@ -36,7 +41,7 @@ public ExecutionStrategyParameters getExecutionStrategyParameters() { } public GraphQLFieldDefinition getField() { - return getExecutionStepInfo().getFieldDefinition(); + return assertNotNull(getExecutionStepInfo().getFieldDefinition(), "fieldDefinition must not be null"); } @Deprecated(since = "2020-09-08") @@ -54,6 +59,7 @@ public ExecutionStepInfo getExecutionStepInfo() { * * @return the object was fetched, ready to be completed as a value. */ + @Nullable public Object getFetchedObject() { return fetchedValue; } diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java index fda194d73e..5db11a737e 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters.java @@ -5,12 +5,14 @@ import graphql.execution.ExecutionStrategyParameters; import graphql.execution.instrumentation.Instrumentation; import graphql.schema.DataFetchingEnvironment; +import org.jspecify.annotations.NullMarked; import java.util.function.Supplier; /** * Parameters sent to {@link Instrumentation} methods */ +@NullMarked @PublicApi public class InstrumentationFieldFetchParameters extends InstrumentationFieldParameters { private final Supplier environment; diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldParameters.java index ebac32ffb1..5b7fcd18f6 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationFieldParameters.java @@ -5,12 +5,16 @@ import graphql.execution.ExecutionStepInfo; import graphql.execution.instrumentation.Instrumentation; import graphql.schema.GraphQLFieldDefinition; +import org.jspecify.annotations.NullMarked; import java.util.function.Supplier; +import static graphql.Assert.assertNotNull; + /** * Parameters sent to {@link Instrumentation} methods */ +@NullMarked @PublicApi public class InstrumentationFieldParameters { private final ExecutionContext executionContext; @@ -24,7 +28,7 @@ public ExecutionContext getExecutionContext() { } public GraphQLFieldDefinition getField() { - return executionStepInfo.get().getFieldDefinition(); + return assertNotNull(executionStepInfo.get().getFieldDefinition(), "fieldDefinition must not be null"); } public ExecutionStepInfo getExecutionStepInfo() { diff --git a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationValidationParameters.java b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationValidationParameters.java index c23a413941..21257df544 100644 --- a/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationValidationParameters.java +++ b/src/main/java/graphql/execution/instrumentation/parameters/InstrumentationValidationParameters.java @@ -5,10 +5,12 @@ import graphql.execution.instrumentation.Instrumentation; import graphql.language.Document; import graphql.schema.GraphQLSchema; +import org.jspecify.annotations.NullMarked; /** * Parameters sent to {@link Instrumentation} methods */ +@NullMarked @PublicApi public class InstrumentationValidationParameters extends InstrumentationExecutionParameters { private final Document document; diff --git a/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java b/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java index ed18f68ab8..f522ed1544 100644 --- a/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java +++ b/src/main/java/graphql/execution/instrumentation/tracing/TracingInstrumentation.java @@ -14,7 +14,7 @@ import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; import graphql.language.Document; import graphql.validation.ValidationError; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import java.util.LinkedHashMap; @@ -29,6 +29,7 @@ * This {@link Instrumentation} implementation uses {@link TracingSupport} to * capture tracing information and puts it into the {@link ExecutionResult} */ +@NullMarked @PublicApi public class TracingInstrumentation extends SimplePerformantInstrumentation { @@ -77,7 +78,7 @@ public TracingInstrumentation(Options options) { } @Override - public @NonNull CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters, InstrumentationState rawState) { + public CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters, InstrumentationState rawState) { Map currentExt = executionResult.getExtensions(); TracingSupport tracingSupport = ofState(rawState); diff --git a/src/main/java/graphql/execution/instrumentation/tracing/TracingSupport.java b/src/main/java/graphql/execution/instrumentation/tracing/TracingSupport.java index 4a0f97a15c..0aa5a9a934 100644 --- a/src/main/java/graphql/execution/instrumentation/tracing/TracingSupport.java +++ b/src/main/java/graphql/execution/instrumentation/tracing/TracingSupport.java @@ -5,6 +5,7 @@ import graphql.execution.ExecutionStepInfo; import graphql.execution.instrumentation.InstrumentationState; import graphql.schema.DataFetchingEnvironment; +import org.jspecify.annotations.NullMarked; import java.time.Instant; import java.time.format.DateTimeFormatter; @@ -13,6 +14,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; +import static graphql.Assert.assertNotNull; import static graphql.schema.GraphQLTypeUtil.simplePrint; /** @@ -22,6 +24,7 @@ * calls. It has been made a separate class so that you can compose this into existing * instrumentation code. */ +@NullMarked @PublicApi public class TracingSupport implements InstrumentationState { @@ -78,9 +81,9 @@ public TracingContext beginField(DataFetchingEnvironment dataFetchingEnvironment Map fetchMap = new LinkedHashMap<>(); fetchMap.put("path", executionStepInfo.getPath().toList()); - fetchMap.put("parentType", simplePrint(executionStepInfo.getParent().getUnwrappedNonNullType())); + fetchMap.put("parentType", simplePrint(assertNotNull(executionStepInfo.getParent(), "executionStepInfo parent must not be null").getUnwrappedNonNullType())); fetchMap.put("returnType", executionStepInfo.simplePrint()); - fetchMap.put("fieldName", executionStepInfo.getFieldDefinition().getName()); + fetchMap.put("fieldName", assertNotNull(executionStepInfo.getFieldDefinition(), "fieldDefinition must not be null").getName()); fetchMap.put("startOffset", startOffset); fetchMap.put("duration", duration); diff --git a/src/main/java/graphql/execution/preparsed/PreparsedDocumentEntry.java b/src/main/java/graphql/execution/preparsed/PreparsedDocumentEntry.java index 8c14cf9bf8..114ee62402 100644 --- a/src/main/java/graphql/execution/preparsed/PreparsedDocumentEntry.java +++ b/src/main/java/graphql/execution/preparsed/PreparsedDocumentEntry.java @@ -3,6 +3,8 @@ import graphql.GraphQLError; import graphql.PublicApi; import graphql.language.Document; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.io.Serializable; import java.util.List; @@ -20,10 +22,11 @@ * with times frames that cross graphql-java versions. While we don't change things unnecessarily, we may inadvertently break * the serialised compatibility across versions. */ +@NullMarked @PublicApi public class PreparsedDocumentEntry implements Serializable { - private final Document document; - private final List errors; + private final @Nullable Document document; + private final @Nullable List errors; public PreparsedDocumentEntry(Document document, List errors) { @@ -49,10 +52,12 @@ public PreparsedDocumentEntry(GraphQLError error) { this(singletonList(assertNotNull(error))); } + @Nullable public Document getDocument() { return document; } + @Nullable public List getErrors() { return errors; } diff --git a/src/main/java/graphql/execution/preparsed/persisted/ApolloPersistedQuerySupport.java b/src/main/java/graphql/execution/preparsed/persisted/ApolloPersistedQuerySupport.java index b7b1a15302..68162bbc2a 100644 --- a/src/main/java/graphql/execution/preparsed/persisted/ApolloPersistedQuerySupport.java +++ b/src/main/java/graphql/execution/preparsed/persisted/ApolloPersistedQuerySupport.java @@ -2,6 +2,7 @@ import graphql.ExecutionInput; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -33,6 +34,7 @@ * * @see graphql.ExecutionInput#getExtensions() */ +@NullMarked @PublicApi public class ApolloPersistedQuerySupport extends PersistedQuerySupport { diff --git a/src/main/java/graphql/execution/preparsed/persisted/InMemoryPersistedQueryCache.java b/src/main/java/graphql/execution/preparsed/persisted/InMemoryPersistedQueryCache.java index 5226332b85..18000e5090 100644 --- a/src/main/java/graphql/execution/preparsed/persisted/InMemoryPersistedQueryCache.java +++ b/src/main/java/graphql/execution/preparsed/persisted/InMemoryPersistedQueryCache.java @@ -4,6 +4,8 @@ import graphql.ExecutionInput; import graphql.PublicApi; import graphql.execution.preparsed.PreparsedDocumentEntry; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; import java.util.HashMap; import java.util.Map; @@ -13,6 +15,7 @@ /** * A PersistedQueryCache that is just an in memory map of known queries. */ +@NullMarked @PublicApi public class InMemoryPersistedQueryCache implements PersistedQueryCache { @@ -53,6 +56,7 @@ public static Builder newInMemoryPersistedQueryCache() { return new Builder(); } + @NullUnmarked public static class Builder { private final Map knownQueries = new HashMap<>(); diff --git a/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCacheMiss.java b/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCacheMiss.java index 6e9cc03a3d..285c0c78a7 100644 --- a/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCacheMiss.java +++ b/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryCacheMiss.java @@ -2,6 +2,7 @@ import graphql.PublicApi; import graphql.execution.preparsed.PreparsedDocumentEntry; +import org.jspecify.annotations.NullMarked; import java.util.function.Function; @@ -10,6 +11,7 @@ * by the graphql engine. If you get a cache miss in your {@link graphql.execution.preparsed.persisted.PersistedQueryCache} implementation * then you are required to call back on the provided instance of this interface */ +@NullMarked @PublicApi public interface PersistedQueryCacheMiss extends Function { /** diff --git a/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryIdInvalid.java b/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryIdInvalid.java index 2599069797..120022ceb6 100644 --- a/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryIdInvalid.java +++ b/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryIdInvalid.java @@ -1,10 +1,12 @@ package graphql.execution.preparsed.persisted; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; import java.util.LinkedHashMap; import java.util.Map; +@NullMarked @PublicApi public class PersistedQueryIdInvalid extends PersistedQueryError { private final Object persistedQueryId; diff --git a/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryNotFound.java b/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryNotFound.java index 22c38bf8e5..14ff2968c1 100644 --- a/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryNotFound.java +++ b/src/main/java/graphql/execution/preparsed/persisted/PersistedQueryNotFound.java @@ -1,6 +1,7 @@ package graphql.execution.preparsed.persisted; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; import java.util.LinkedHashMap; import java.util.Map; @@ -8,6 +9,7 @@ /** * An exception that indicates the query id is not valid and can be found ever in cache */ +@NullMarked @PublicApi public class PersistedQueryNotFound extends PersistedQueryError { private final Object persistedQueryId; diff --git a/src/main/java/graphql/execution/reactive/DelegatingSubscription.java b/src/main/java/graphql/execution/reactive/DelegatingSubscription.java index e8a3ff9df3..3f51f690c2 100644 --- a/src/main/java/graphql/execution/reactive/DelegatingSubscription.java +++ b/src/main/java/graphql/execution/reactive/DelegatingSubscription.java @@ -1,6 +1,7 @@ package graphql.execution.reactive; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; import org.reactivestreams.Subscription; import static graphql.Assert.assertNotNull; @@ -8,6 +9,7 @@ /** * A simple subscription that delegates to another */ +@NullMarked @PublicApi public class DelegatingSubscription implements Subscription { private final Subscription upstreamSubscription; diff --git a/src/main/java/graphql/execution/reactive/SubscriptionPublisher.java b/src/main/java/graphql/execution/reactive/SubscriptionPublisher.java index 4951b451df..e3fbf5bd96 100644 --- a/src/main/java/graphql/execution/reactive/SubscriptionPublisher.java +++ b/src/main/java/graphql/execution/reactive/SubscriptionPublisher.java @@ -3,6 +3,7 @@ import graphql.ExecutionResult; import graphql.Internal; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -21,6 +22,7 @@ * } * */ +@NullMarked @SuppressWarnings("ReactiveStreamsPublisherImplementation") @PublicApi public class SubscriptionPublisher implements Publisher { diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 1b6cdafa61..d0881fbb7d 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,53 +11,7 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.analysis.QueryComplexityCalculator", - "graphql.analysis.QueryComplexityInfo", - "graphql.analysis.QueryDepthInfo", - "graphql.analysis.QueryReducer", - "graphql.analysis.QueryTransformer", - "graphql.analysis.QueryTraversalOptions", "graphql.analysis.QueryTraverser", - "graphql.analysis.QueryVisitor", - "graphql.analysis.QueryVisitorFieldArgumentEnvironment", - "graphql.analysis.QueryVisitorFieldArgumentInputValue", - "graphql.analysis.QueryVisitorFieldArgumentValueEnvironment", - "graphql.analysis.QueryVisitorFieldEnvironment", - "graphql.analysis.QueryVisitorFragmentDefinitionEnvironment", - "graphql.analysis.QueryVisitorFragmentSpreadEnvironment", - "graphql.analysis.QueryVisitorInlineFragmentEnvironment", - "graphql.analysis.QueryVisitorStub", - "graphql.analysis.values.ValueTraverser", - "graphql.execution.AbortExecutionException", - "graphql.execution.AsyncExecutionStrategy", - "graphql.execution.AsyncSerialExecutionStrategy", - "graphql.execution.CoercedVariables", - "graphql.execution.DataFetcherExceptionHandlerParameters", - "graphql.execution.DataFetcherExceptionHandlerResult", - "graphql.execution.DefaultValueUnboxer", - "graphql.execution.ExecutionContext", - "graphql.execution.ExecutionId", - "graphql.execution.ExecutionStepInfo", - "graphql.execution.ExecutionStrategyParameters", - "graphql.execution.FetchedValue", - "graphql.execution.FieldValueInfo", - "graphql.execution.InputMapDefinesTooManyFieldsException", - "graphql.execution.MergedSelectionSet", - "graphql.execution.MissingRootTypeException", - "graphql.execution.NonNullableValueCoercedAsNullException", - "graphql.execution.NormalizedVariables", - "graphql.execution.OneOfNullValueException", - "graphql.execution.OneOfTooManyKeysException", - "graphql.execution.ResultNodesInfo", - "graphql.execution.ResultPath", - "graphql.execution.SimpleDataFetcherExceptionHandler", - "graphql.execution.SubscriptionExecutionStrategy", - "graphql.execution.UnknownOperationException", - "graphql.execution.UnresolvedTypeException", - "graphql.execution.conditional.ConditionalNodeDecision", - "graphql.execution.directives.QueryAppliedDirective", - "graphql.execution.directives.QueryAppliedDirectiveArgument", - "graphql.execution.directives.QueryDirectives", "graphql.execution.incremental.DeferredExecution", "graphql.execution.instrumentation.ChainedInstrumentation", "graphql.execution.instrumentation.DocumentAndVariables", @@ -68,26 +22,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.execution.instrumentation.SimplePerformantInstrumentation", "graphql.execution.instrumentation.fieldvalidation.FieldAndArguments", "graphql.execution.instrumentation.fieldvalidation.FieldValidationEnvironment", - "graphql.execution.instrumentation.fieldvalidation.FieldValidationInstrumentation", - "graphql.execution.instrumentation.fieldvalidation.SimpleFieldValidation", - "graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters", - "graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters", - "graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters", - "graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters", - "graphql.execution.instrumentation.parameters.InstrumentationFieldCompleteParameters", - "graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters", - "graphql.execution.instrumentation.parameters.InstrumentationFieldParameters", - "graphql.execution.instrumentation.parameters.InstrumentationValidationParameters", - "graphql.execution.instrumentation.tracing.TracingInstrumentation", - "graphql.execution.instrumentation.tracing.TracingSupport", - "graphql.execution.preparsed.PreparsedDocumentEntry", - "graphql.execution.preparsed.persisted.ApolloPersistedQuerySupport", - "graphql.execution.preparsed.persisted.InMemoryPersistedQueryCache", - "graphql.execution.preparsed.persisted.PersistedQueryCacheMiss", - "graphql.execution.preparsed.persisted.PersistedQueryIdInvalid", - "graphql.execution.preparsed.persisted.PersistedQueryNotFound", - "graphql.execution.reactive.DelegatingSubscription", - "graphql.execution.reactive.SubscriptionPublisher", "graphql.extensions.ExtensionsBuilder", "graphql.incremental.DeferPayload", "graphql.incremental.DelayedIncrementalPartialResult",