Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -607,11 +607,13 @@ tasks.register('markGeneratedEqualsHashCode') {
def jacocoDir = layout.buildDirectory.dir('classes-jacoco/java/main')

inputs.dir(originalDir)
inputs.property("generatedAnnotationTaskVersion", 2)
outputs.dir(jacocoDir)

doLast {
def src = originalDir.get().asFile
def dest = jacocoDir.get().asFile
project.delete(dest)
if (!src.exists()) return

// Copy all class files to a separate directory for JaCoCo
Expand All @@ -635,8 +637,10 @@ tasks.register('markGeneratedEqualsHashCode') {
if (method.invisibleAnnotations == null) {
method.invisibleAnnotations = []
}
method.invisibleAnnotations.add(new org.objectweb.asm.tree.AnnotationNode(ANNOTATION))
modified = true
if (!method.invisibleAnnotations.any { it.desc == ANNOTATION }) {
method.invisibleAnnotations.add(new org.objectweb.asm.tree.AnnotationNode(ANNOTATION))
modified = true
}
}
}

Expand Down Expand Up @@ -799,4 +803,3 @@ tasks.withType(GenerateModuleMetadata) {
}



Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Supplier;

import static graphql.schema.GraphQLTypeUtil.unwrapAllAs;
import static graphql.util.TraversalControl.CONTINUE;
Expand All @@ -43,8 +42,9 @@
* themselves are not collected - only concrete type instances are stored in the result map.
* <p>
* Because type references are not followed, this visitor also tracks "indirect strong references"
* - types that are directly referenced (not via type reference) by fields, arguments, and input
* fields. This handles edge cases where schema transformations replace type references with
* - types that are directly referenced (not via type reference) by fields, arguments,
* input fields, implemented interfaces, and union members. This handles edge cases where
* schema transformations replace type references with
* actual types, which would otherwise be missed during traversal.
*
* @see SchemaUtil#visitPartiallySchema
Expand Down Expand Up @@ -77,6 +77,7 @@ public TraversalControl visitGraphQLScalarType(GraphQLScalarType node, Traverser
public TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext<GraphQLSchemaElement> context) {
assertTypeUniqueness(node, result);
save(node.getName(), node);
saveIndirectStrongReferences(node.getInterfaces());
return CONTINUE;
}

Expand All @@ -91,47 +92,55 @@ public TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType node,
public TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, TraverserContext<GraphQLSchemaElement> context) {
assertTypeUniqueness(node, result);
save(node.getName(), node);
saveIndirectStrongReferences(node.getInterfaces());
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLUnionType(GraphQLUnionType node, TraverserContext<GraphQLSchemaElement> context) {
assertTypeUniqueness(node, result);
save(node.getName(), node);
saveIndirectStrongReferences(node.getTypes());
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext<GraphQLSchemaElement> context) {
saveIndirectStrongReference(node::getType);
saveIndirectStrongReference(node.getType());
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField node, TraverserContext<GraphQLSchemaElement> context) {
saveIndirectStrongReference(node::getType);
saveIndirectStrongReference(node.getType());
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLArgument(GraphQLArgument node, TraverserContext<GraphQLSchemaElement> context) {
saveIndirectStrongReference(node::getType);
saveIndirectStrongReference(node.getType());
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLAppliedDirectiveArgument(GraphQLAppliedDirectiveArgument node, TraverserContext<GraphQLSchemaElement> context) {
saveIndirectStrongReference(node::getType);
saveIndirectStrongReference(node.getType());
return CONTINUE;
}

private void saveIndirectStrongReference(Supplier<GraphQLType> typeSupplier) {
GraphQLNamedType type = unwrapAllAs(typeSupplier.get());
private void saveIndirectStrongReference(GraphQLType graphQLType) {
GraphQLNamedType type = unwrapAllAs(graphQLType);
if (!(type instanceof GraphQLTypeReference)) {
indirectStrongReferences.put(type.getName(), type);
}
}

private void saveIndirectStrongReferences(List<? extends GraphQLType> types) {
for (GraphQLType type : types) {
saveIndirectStrongReference(type);
}
}

private void save(String name, GraphQLNamedType type) {
result.put(name, type);
}
Expand Down
163 changes: 163 additions & 0 deletions src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ import graphql.NestedInputSchema
import graphql.introspection.Introspection
import graphql.schema.GraphQLAppliedDirectiveArgument
import graphql.schema.GraphQLArgument
import graphql.schema.GraphQLCodeRegistry
import graphql.schema.GraphQLFieldDefinition
import graphql.schema.GraphQLInputObjectType
import graphql.schema.GraphQLObjectType
import graphql.schema.GraphQLSchema
import graphql.schema.GraphQLSchemaElement
import graphql.schema.GraphQLType
import graphql.schema.GraphQLTypeVisitorStub
import graphql.schema.GraphQLTypeReference
import graphql.schema.GraphQLUnionType
import graphql.schema.SchemaTransformer
import graphql.util.TraversalControl
import graphql.util.TraverserContext
import spock.lang.Specification

import static graphql.Scalars.GraphQLBoolean
Expand Down Expand Up @@ -41,6 +48,7 @@ import static graphql.schema.GraphQLArgument.newArgument
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition
import static graphql.schema.GraphQLInputObjectField.newInputObjectField
import static graphql.schema.GraphQLInputObjectType.newInputObject
import static graphql.schema.GraphQLInterfaceType.newInterface
import static graphql.schema.GraphQLList.list
import static graphql.schema.GraphQLObjectType.newObject
import static graphql.schema.GraphQLSchema.newSchema
Expand Down Expand Up @@ -190,6 +198,161 @@ class SchemaUtilTest extends Specification {
!(cacheEnabled.getType() instanceof GraphQLTypeReference)
}

def "can rebuild schema after removing root type that made an implemented interface reachable"() {
given:
def schema = schemaWithObjectImplementingInterfaceThroughTypeReference()
def node = schema.getType("Node")

when:
def rebuiltSchema = newSchema(schema)
.mutation((GraphQLObjectType) null)
.build()

then:
rebuiltSchema.getMutationType() == null
rebuiltSchema.getType("Node") == node
rebuiltSchema.getObjectType("Person").interfaces == [node]
}

def "can transform schema after deleting root type that made an implemented interface reachable"() {
given:
def schema = schemaWithObjectImplementingInterfaceThroughTypeReference()
def node = schema.getType("Node")

when:
def transformedSchema = SchemaTransformer.transformSchemaWithDeletes(schema, new GraphQLTypeVisitorStub() {
@Override
TraversalControl visitGraphQLObjectType(GraphQLObjectType graphQLObjectType, TraverserContext<GraphQLSchemaElement> context) {
if (graphQLObjectType.name == "Mutation") {
return deleteNode(context)
}
return TraversalControl.CONTINUE
}
})

then:
transformedSchema.getMutationType() == null
transformedSchema.getType("Node") == node
transformedSchema.getObjectType("Person").interfaces == [node]
}

def "can rebuild schema after removing root type that made a union member reachable"() {
given:
def schema = schemaWithUnionMemberThroughTypeReference()
def cat = schema.getObjectType("Cat")

when:
def rebuiltSchema = newSchema(schema)
.mutation((GraphQLObjectType) null)
.build()

then:
rebuiltSchema.getMutationType() == null
rebuiltSchema.getType("Cat") == cat
rebuiltSchema.getType("Pet").types == [cat]
}

def "can rebuild schema after removing root type that made an interface implemented by another interface reachable"() {
given:
def schema = schemaWithInterfaceImplementingInterfaceThroughTypeReference()
def node = schema.getType("Node")

when:
def rebuiltSchema = newSchema(schema)
.mutation((GraphQLObjectType) null)
.build()

then:
rebuiltSchema.getMutationType() == null
rebuiltSchema.getType("Node") == node
rebuiltSchema.getType("NamedNode").interfaces == [node]
}

private GraphQLSchema schemaWithObjectImplementingInterfaceThroughTypeReference() {
def node = newInterface()
.name("Node")
.field(newFieldDefinition().name("id").type(GraphQLString))
.build()
def person = newObject()
.name("Person")
.withInterface(typeRef("Node"))
.field(newFieldDefinition().name("id").type(GraphQLString))
.build()
def query = newObject()
.name("Query")
.field(newFieldDefinition().name("person").type(person))
.build()
def mutation = newObject()
.name("Mutation")
.field(newFieldDefinition().name("node").type(node))
.build()
def codeRegistry = GraphQLCodeRegistry.newCodeRegistry()
.typeResolver(node, { env -> person })
.build()
return newSchema()
.query(query)
.mutation(mutation)
.codeRegistry(codeRegistry)
.build()
}

private GraphQLSchema schemaWithUnionMemberThroughTypeReference() {
def cat = newObject()
.name("Cat")
.field(newFieldDefinition().name("name").type(GraphQLString))
.build()
def pet = GraphQLUnionType.newUnionType()
.name("Pet")
.possibleType(typeRef("Cat"))
.build()
def query = newObject()
.name("Query")
.field(newFieldDefinition().name("pet").type(pet))
.build()
def mutation = newObject()
.name("Mutation")
.field(newFieldDefinition().name("cat").type(cat))
.build()
def codeRegistry = GraphQLCodeRegistry.newCodeRegistry()
.typeResolver(pet, { env -> cat })
.build()
return newSchema()
.query(query)
.mutation(mutation)
.codeRegistry(codeRegistry)
.build()
}

private GraphQLSchema schemaWithInterfaceImplementingInterfaceThroughTypeReference() {
def node = newInterface()
.name("Node")
.field(newFieldDefinition().name("id").type(GraphQLString))
.build()
def namedNode = newInterface()
.name("NamedNode")
.withInterface(typeRef("Node"))
.field(newFieldDefinition().name("id").type(GraphQLString))
.field(newFieldDefinition().name("name").type(GraphQLString))
.build()
def query = newObject()
.name("Query")
.field(newFieldDefinition().name("node").type(namedNode))
.build()
def mutation = newObject()
.name("Mutation")
.field(newFieldDefinition().name("node").type(node))
.build()
def codeRegistry = GraphQLCodeRegistry.newCodeRegistry()
.typeResolver(node, { env -> null })
.typeResolver(namedNode, { env -> null })
.build()
return newSchema()
.query(query)
.mutation(mutation)
.codeRegistry(codeRegistry)
.build()
}

def "redefined types are caught"() {
when:
final GraphQLInputObjectType attributeListInputObjectType = newInputObject().name("attributes")
Expand Down