Skip to content

Commit 1ef99fa

Browse files
committed
Merge origin/master into java25
Resolve conflicts in workflow files: - Keep testWithJava21 test results - Keep javadoc job, updated to use JDK 25
2 parents 30b14e3 + d1798b1 commit 1ef99fa

21 files changed

+1111
-142
lines changed

.github/workflows/master.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ jobs:
3131
**/build/test-results/testWithJava11/TEST-*.xml
3232
**/build/test-results/testWithJava17/TEST-*.xml
3333
**/build/test-results/testWithJava21/TEST-*.xml
34+
javadoc:
35+
runs-on: ubuntu-latest
36+
steps:
37+
- uses: actions/checkout@v6
38+
- uses: gradle/actions/wrapper-validation@v5
39+
- name: Set up JDK 25
40+
uses: actions/setup-java@v5
41+
with:
42+
java-version: '25'
43+
distribution: 'corretto'
44+
- name: Verify Javadoc
45+
run: ./gradlew javadoc --info --stacktrace
3446
publishToMavenCentral:
3547
needs: buildAndTest
3648
runs-on: ubuntu-latest

.github/workflows/pull_request.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,16 @@ jobs:
3939
**/build/test-results/test/TEST-*.xml
4040
**/build/test-results/testWithJava11/TEST-*.xml
4141
**/build/test-results/testWithJava17/TEST-*.xml
42-
**/build/test-results/testWithJava21/TEST-*.xml
42+
**/build/test-results/testWithJava21/TEST-*.xml
43+
javadoc:
44+
runs-on: ubuntu-latest
45+
steps:
46+
- uses: actions/checkout@v6
47+
- uses: gradle/actions/wrapper-validation@v5
48+
- name: Set up JDK 25
49+
uses: actions/setup-java@v5
50+
with:
51+
java-version: '25'
52+
distribution: 'corretto'
53+
- name: Verify Javadoc
54+
run: ./gradlew javadoc --info --stacktrace

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ docs/_build/
1515
\.settings/
1616
/.nb-gradle/
1717
gen
18-
.DS_Store
18+
.DS_Store
19+
.vscode

AGENTS.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# AI Agent Context for graphql-java
2+
3+
This file provides context for AI assistants working with this codebase.
4+
5+
## Test Execution
6+
7+
When running tests, exclude the Java version-specific test tasks to avoid failures:
8+
9+
```bash
10+
./gradlew test -x testWithJava17 -x testWithJava11 -x testng
11+
```

src/main/java/graphql/execution/ExecutionStrategy.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -489,12 +489,17 @@ private Object fetchField(GraphQLFieldDefinition fieldDef, ExecutionContext exec
489489

490490
CompletableFuture<CompletableFuture<Object>> handleCF = engineRunningState.handle(fetchedValue, (result, exception) -> {
491491
// because we added an artificial CF, we need to unwrap the exception
492-
fetchCtx.onCompleted(result, exception);
493-
exception = engineRunningState.possibleCancellation(exception);
494-
495-
if (exception != null) {
496-
return handleFetchingException(dataFetchingEnvironment.get(), parameters, exception);
492+
Throwable possibleWrappedException = engineRunningState.possibleCancellation(exception);
493+
494+
if (possibleWrappedException != null) {
495+
CompletableFuture<DataFetcherResult<Object>> handledExceptionResult = handleFetchingException(dataFetchingEnvironment.get(), parameters, possibleWrappedException);
496+
return handledExceptionResult.thenApply( handledResult -> {
497+
fetchCtx.onExceptionHandled(handledResult);
498+
fetchCtx.onCompleted(result, exception);
499+
return handledResult;
500+
});
497501
} else {
502+
fetchCtx.onCompleted(result, exception);
498503
// we can simply return the fetched value CF and avoid a allocation
499504
return fetchedValue;
500505
}
@@ -578,7 +583,7 @@ private void addExtensionsIfPresent(ExecutionContext executionContext, DataFetch
578583
}
579584
}
580585

581-
protected <T> CompletableFuture<T> handleFetchingException(
586+
protected <T> CompletableFuture<DataFetcherResult<T>> handleFetchingException(
582587
DataFetchingEnvironment environment,
583588
ExecutionStrategyParameters parameters,
584589
Throwable e
@@ -599,10 +604,10 @@ protected <T> CompletableFuture<T> handleFetchingException(
599604
}
600605
}
601606

602-
private <T> CompletableFuture<T> asyncHandleException(DataFetcherExceptionHandler handler, DataFetcherExceptionHandlerParameters handlerParameters) {
607+
private <T> CompletableFuture<DataFetcherResult<T>> asyncHandleException(DataFetcherExceptionHandler handler, DataFetcherExceptionHandlerParameters handlerParameters) {
603608
//noinspection unchecked
604609
return handler.handleException(handlerParameters).thenApply(
605-
handlerResult -> (T) DataFetcherResult.newResult().errors(handlerResult.getErrors()).build()
610+
handlerResult -> (DataFetcherResult<T>) DataFetcherResult.newResult().errors(handlerResult.getErrors()).build()
606611
);
607612
}
608613

src/main/java/graphql/execution/instrumentation/ChainedInstrumentation.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import graphql.ExperimentalApi;
77
import graphql.PublicApi;
88
import graphql.execution.Async;
9+
import graphql.execution.DataFetcherResult;
910
import graphql.execution.ExecutionContext;
1011
import graphql.execution.FieldValueInfo;
1112
import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters;
@@ -380,6 +381,11 @@ public void onFetchedValue(Object fetchedValue) {
380381
contexts.forEach(context -> context.onFetchedValue(fetchedValue));
381382
}
382383

384+
@Override
385+
public void onExceptionHandled(DataFetcherResult<Object> dataFetcherResult) {
386+
contexts.forEach(context -> context.onExceptionHandled(dataFetcherResult));
387+
}
388+
383389
@Override
384390
public void onCompleted(Object result, Throwable t) {
385391
contexts.forEach(context -> context.onCompleted(result, t));

src/main/java/graphql/execution/instrumentation/FieldFetchingInstrumentationContext.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import graphql.Internal;
44
import graphql.PublicSpi;
5+
import graphql.execution.DataFetcherResult;
56
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
67
import org.jspecify.annotations.NonNull;
78
import org.jspecify.annotations.Nullable;
@@ -26,6 +27,15 @@ public interface FieldFetchingInstrumentationContext extends InstrumentationCont
2627
default void onFetchedValue(Object fetchedValue) {
2728
}
2829

30+
/**
31+
* This is called back after any {@link graphql.execution.DataFetcherExceptionHandler}) has run on any exception raised
32+
* during a {@link graphql.schema.DataFetcher} invocation. This allows to see the final {@link DataFetcherResult}
33+
* that will be used when performing the complete step.
34+
* @param dataFetcherResult the final {@link DataFetcherResult} after the exception handler has run
35+
*/
36+
default void onExceptionHandled(DataFetcherResult<Object> dataFetcherResult) {
37+
}
38+
2939
@Internal
3040
FieldFetchingInstrumentationContext NOOP = new FieldFetchingInstrumentationContext() {
3141
@Override

src/main/java/graphql/schema/GraphQLSchema.java

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public class GraphQLSchema {
5252
private final GraphQLObjectType mutationType;
5353
private final GraphQLObjectType subscriptionType;
5454
private final GraphQLObjectType introspectionSchemaType;
55-
private final ImmutableSet<GraphQLType> additionalTypes;
55+
private final ImmutableSet<GraphQLNamedType> additionalTypes;
5656
private final GraphQLFieldDefinition introspectionSchemaField;
5757
private final GraphQLFieldDefinition introspectionTypeField;
5858
// we don't allow modification of "__typename" - it's a scalar
@@ -219,7 +219,66 @@ public GraphQLObjectType getIntrospectionSchemaType() {
219219
return introspectionSchemaType;
220220
}
221221

222-
public Set<GraphQLType> getAdditionalTypes() {
222+
/**
223+
* Returns the set of "additional types" that were provided when building the schema.
224+
* <p>
225+
* During schema construction, types are discovered by traversing the schema from multiple roots:
226+
* <ul>
227+
* <li>Root operation types (Query, Mutation, Subscription)</li>
228+
* <li>Directive argument types</li>
229+
* <li>Introspection types</li>
230+
* <li>Types explicitly added via {@link Builder#additionalType(GraphQLNamedType)}</li>
231+
* </ul>
232+
* <p>
233+
* Additional types are types that are not reachable via any of the automatic traversal paths
234+
* but still need to be part of the schema. The most common use case is for interface
235+
* implementations that are not directly referenced elsewhere.
236+
* <p>
237+
* <b>Types that do NOT need to be added as additional types:</b>
238+
* <ul>
239+
* <li>Types reachable from Query, Mutation, or Subscription fields</li>
240+
* <li>Types used as directive arguments (these are discovered via directive traversal)</li>
241+
* </ul>
242+
* <p>
243+
* <b>When additional types ARE typically needed:</b>
244+
* <ul>
245+
* <li><b>Interface implementations:</b> When an interface is used as a field's return type,
246+
* implementing object types are not automatically discovered because interfaces do not
247+
* reference their implementors. These need to be added so they can be resolved at runtime
248+
* and appear in introspection.</li>
249+
* <li><b>SDL-defined schemas:</b> When building from SDL, the {@link graphql.schema.idl.SchemaGenerator}
250+
* automatically detects types not connected to any root and adds them as additional types.</li>
251+
* <li><b>Programmatic schemas with type references:</b> When using {@link GraphQLTypeReference}
252+
* to break circular dependencies, the actual type implementations may need to be provided
253+
* as additional types.</li>
254+
* </ul>
255+
* <p>
256+
* <b>Example - Interface implementation not directly referenced:</b>
257+
* <pre>{@code
258+
* // Given this schema:
259+
* // type Query { node: Node }
260+
* // interface Node { id: ID! }
261+
* // type User implements Node { id: ID!, name: String }
262+
* //
263+
* // User is not directly referenced from Query, so it needs to be added:
264+
* GraphQLSchema.newSchema()
265+
* .query(queryType)
266+
* .additionalType(GraphQLObjectType.newObject().name("User")...)
267+
* .build();
268+
* }</pre>
269+
* <p>
270+
* <b>Note:</b> There are no restrictions on what types can be added via this mechanism.
271+
* Types that are already reachable from other roots can also be added without causing
272+
* errors - they will simply be present in both the type map (via traversal) and this set.
273+
* After schema construction, use {@link #getTypeMap()} or {@link #getAllTypesAsList()} to get
274+
* all types in the schema regardless of how they were discovered.
275+
*
276+
* @return an immutable set of types that were explicitly added as additional types
277+
*
278+
* @see Builder#additionalType(GraphQLNamedType)
279+
* @see Builder#additionalTypes(Set)
280+
*/
281+
public Set<GraphQLNamedType> getAdditionalTypes() {
223282
return additionalTypes;
224283
}
225284

@@ -686,7 +745,7 @@ public static class Builder {
686745
private final Set<GraphQLDirective> additionalDirectives = new LinkedHashSet<>(
687746
asList(Directives.IncludeDirective, Directives.SkipDirective)
688747
);
689-
private final Set<GraphQLType> additionalTypes = new LinkedHashSet<>();
748+
private final Set<GraphQLNamedType> additionalTypes = new LinkedHashSet<>();
690749
private final List<GraphQLDirective> schemaDirectives = new ArrayList<>();
691750
private final List<GraphQLAppliedDirective> schemaAppliedDirectives = new ArrayList<>();
692751

@@ -722,16 +781,72 @@ public Builder codeRegistry(GraphQLCodeRegistry codeRegistry) {
722781
return this;
723782
}
724783

725-
public Builder additionalTypes(Set<GraphQLType> additionalTypes) {
784+
/**
785+
* Adds multiple types to the set of additional types.
786+
* <p>
787+
* Additional types are types that may not be directly reachable by traversing the schema
788+
* from the root operation types (Query, Mutation, Subscription), but still need to be
789+
* included in the schema. The most common use case is for object types that implement
790+
* an interface but are not directly referenced as field return types.
791+
* <p>
792+
* <b>Example - Adding interface implementations:</b>
793+
* <pre>{@code
794+
* // If Node interface is used but User/Post types aren't directly referenced:
795+
* builder.additionalTypes(Set.of(
796+
* GraphQLObjectType.newObject().name("User").withInterface(nodeInterface)...,
797+
* GraphQLObjectType.newObject().name("Post").withInterface(nodeInterface)...
798+
* ));
799+
* }</pre>
800+
* <p>
801+
* <b>Note:</b> There are no restrictions on what types can be added. Types already
802+
* reachable from root operations can be added without causing errors - they will
803+
* simply exist in both the traversed type map and this set.
804+
*
805+
* @param additionalTypes the types to add
806+
*
807+
* @return this builder
808+
*
809+
* @see GraphQLSchema#getAdditionalTypes()
810+
*/
811+
public Builder additionalTypes(Set<? extends GraphQLNamedType> additionalTypes) {
726812
this.additionalTypes.addAll(additionalTypes);
727813
return this;
728814
}
729815

730-
public Builder additionalType(GraphQLType additionalType) {
816+
/**
817+
* Adds a single type to the set of additional types.
818+
* <p>
819+
* Additional types are types that may not be directly reachable by traversing the schema
820+
* from the root operation types (Query, Mutation, Subscription), but still need to be
821+
* included in the schema. The most common use case is for object types that implement
822+
* an interface but are not directly referenced as field return types.
823+
* <p>
824+
* <b>Note:</b> There are no restrictions on what types can be added. Types already
825+
* reachable from root operations can be added without causing errors.
826+
*
827+
* @param additionalType the type to add
828+
*
829+
* @return this builder
830+
*
831+
* @see GraphQLSchema#getAdditionalTypes()
832+
* @see #additionalTypes(Set)
833+
*/
834+
public Builder additionalType(GraphQLNamedType additionalType) {
731835
this.additionalTypes.add(additionalType);
732836
return this;
733837
}
734838

839+
/**
840+
* Clears all additional types that have been added to this builder.
841+
* <p>
842+
* This is useful when transforming an existing schema and you want to
843+
* rebuild the additional types set from scratch.
844+
*
845+
* @return this builder
846+
*
847+
* @see #additionalType(GraphQLNamedType)
848+
* @see #additionalTypes(Set)
849+
*/
735850
public Builder clearAdditionalTypes() {
736851
this.additionalTypes.clear();
737852
return this;

0 commit comments

Comments
 (0)