You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I found an interesting bug within SPQR concerning the combinations of Unions and visitors. I've got an Java interface in GraphQL which is purely used to indicate that any classes implementing the interface should be part of a Union, as the types do not share fields.
The issue is as follows: When creating a Union Type using the @GraphQLUnion annotation the union is created as expected. When creating a query that returns that Union Type the fields on members of that Union can be requested as normal. The fun begins when a Visitor is used to mutate one of the members of the Union: After the member has been changed the member does not return any data when queried, just an empty Map.
I've compared this to a plain GraphQL Java implementation where I've build my own field definitions and runtimewiring etc. When I mutate this schema using the same Visitor it still works as expected, whereas the schema generated with SPQR breaks.
I've created a code sample that demonstrates the issue. The full code is attached, but below I'll list the most relevant parts.
@GraphQLUnion(name = "Item", possibleTypeAutoDiscovery = true)
publicinterfaceItem {
}
@GraphQLTypepublicclassDogimplementsItem {
privateStringname = "Bello";
privateintpaws = 4;
publicStringgetName() {
returnname;
}
publicvoidsetName(Stringname) {
this.name = name;
}
// Ignoring getPaws here for demonstration's sake.@GraphQLIgnorepublicintgetPaws() {
returnpaws;
}
publicvoidsetPaws(intpaws) {
this.paws = paws;
}
}
@GraphQLTypepublicclassCarimplementsItem {
privateintnumberOfWheels = 4;
publicintgetNumberOfWheels() {
returnnumberOfWheels;
}
publicvoidsetNumberOfWheels(intnumberOfWheels) {
this.numberOfWheels = numberOfWheels;
}
}
publicclassDataService {
@GraphQLQuerypublicDoggetDog() {
returnnewDog();
}
@GraphQLQuerypublicCargetCar() {
returnnewCar();
}
@GraphQLQuerypublicList<Item> getItems() {
returnList.of(newDog(), newCar());
}
}
//Now to build the schema and execute a query against itExecutionInputexecutionInput = ExecutionInput
.newExecutionInput()
.query("query { items { ...on Dog { name } ... on Car { numberOfWheels }} }")
.build();
GraphQLSchemaGeneratorgenerator = newGraphQLSchemaGenerator();
generator.withOperationsFromSingleton(newDataService());
GraphQLSchemaspqrSchema = generator.generate();
GraphQLspqr = GraphQL.newGraphQL(spqrSchema).build();
ExecutionResultspqrResult = spqr.execute(executionInput);
// Here the spqrResult will correctly return a Dog object with a name field containing Bello and // a Car object with a numberOfWheels field containing 4.// Now let's mutate the schema using the following VisitorprivateGraphQLTypeVisitorStubgetGraphQLTypeVisitorStub() {
returnnewGraphQLTypeVisitorStub() {
@OverridepublicTraversalControlvisitGraphQLObjectType(GraphQLObjectTypenode, TraverserContext<GraphQLSchemaElement> context) {
if (node.getName().equals("Dog")) {
returnchangeNode(context, node.transform(builder -> builder
.field(GraphQLFieldDefinition.newFieldDefinition()
.name("paws")
.type(GraphQLInt)
.build()
).build()
));
}
returnTraversalControl.CONTINUE;
}
};
}
spqrSchema = SchemaTransformer.transform(spqrSchema, getGraphQLTypeVisitorStub);
// And repeat the queryExecutionResultspqrResultAfterVisitor = spqr.execute(executionInput);
// spqrResultAfterVisitor now contains an empty LinkedHashMap in the data field, see attached screenshots
The ExecutionResult when the Schema has not been visisted
The result when the Schema has been visited
The attached ZIP file contains a Java file, containing an HTTP Servlet, that generates a Schema based on both GraphQL SPQR and based on plain GraphQL Java, using the same classes. The line that transforms the SPQR generated schema is commented out, so in it's current state both calls return the correct data. You can uncomment line 57 to transform the spqr schema and see the same results I see in the screenshots above.
I've looked into the issue a bit myself: it seems that the Dog object is actually retrieved in both situations, it just the translation back to the API that seems to be broken. SimpleGraphQLServlet.java.zip
The text was updated successfully, but these errors were encountered:
Balf
changed the title
BUG: Using a Visitor on a member of GraphQLUnion defined via SPQR breaks the visisted member within the union
BUG: Using a Visitor on a member of GraphQLUnion defined via SPQR breaks the visited member within the union
Mar 26, 2024
Something I initially forgot to mention: This issue only occurs when querying the items query. When directly querying the dog query it works as expected. I've looked in it a bit further and I think that the issue is strongly related to the DelegatingTypeResolver.
//Check if the type is already unambiguousList<MappedType> mappedTypes = globalEnv.typeRegistry.getOutputTypes(abstractTypeName, resultType);
if (mappedTypes.isEmpty()) {
return (GraphQLObjectType) env.getSchema().getType(resultTypeName);
}
if (mappedTypes.size() == 1) {
returnmappedTypes.get(0).getAsObjectType();
}
In my example the code reaches the line:
returnmappedTypes.get(0).getAsObjectType();
When I debug the result of mappedTypes.get(0).getAsObjectType() I see that the paws field is missing from the FieldDefinition. I compared this to the UnionTypeResolver in the attached Java file, and I see that the field is present there.
So it seems that the globalEnv in the DelegatingTypeResolver is not properly updated when a Visitor is used.
Balf
changed the title
BUG: Using a Visitor on a member of GraphQLUnion defined via SPQR breaks the visited member within the union
BUG: The DelegatingTypeResolver breaks when updating the GraphQLSchema using a Visitor
Mar 27, 2024
Hi @kaqqao,
I found an interesting bug within SPQR concerning the combinations of Unions and visitors. I've got an Java interface in GraphQL which is purely used to indicate that any classes implementing the interface should be part of a Union, as the types do not share fields.
The issue is as follows: When creating a Union Type using the @GraphQLUnion annotation the union is created as expected. When creating a query that returns that Union Type the fields on members of that Union can be requested as normal. The fun begins when a Visitor is used to mutate one of the members of the Union: After the member has been changed the member does not return any data when queried, just an empty Map.
I've compared this to a plain GraphQL Java implementation where I've build my own field definitions and runtimewiring etc. When I mutate this schema using the same Visitor it still works as expected, whereas the schema generated with SPQR breaks.
I've created a code sample that demonstrates the issue. The full code is attached, but below I'll list the most relevant parts.
The ExecutionResult when the Schema has not been visisted
The result when the Schema has been visited
The attached ZIP file contains a Java file, containing an HTTP Servlet, that generates a Schema based on both GraphQL SPQR and based on plain GraphQL Java, using the same classes. The line that transforms the SPQR generated schema is commented out, so in it's current state both calls return the correct data. You can uncomment line 57 to transform the spqr schema and see the same results I see in the screenshots above.
I've looked into the issue a bit myself: it seems that the Dog object is actually retrieved in both situations, it just the translation back to the API that seems to be broken.
SimpleGraphQLServlet.java.zip
The text was updated successfully, but these errors were encountered: