graphql.schema.usage.SchemaUsage Maven / Gradle / Ivy
package graphql.schema.usage;
import com.google.common.collect.ImmutableMap;
import graphql.Internal;
import graphql.PublicApi;
import graphql.introspection.Introspection;
import graphql.schema.GraphQLDirective;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLNamedOutputType;
import graphql.schema.GraphQLNamedSchemaElement;
import graphql.schema.GraphQLNamedType;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.DirectiveInfo;
import graphql.schema.idl.ScalarInfo;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Collections.emptySet;
/**
* This class shows schema usage information. There are two aspects to this. To be strongly referenced, a schema element
* (directive or type) must some how be connected back to the root query, mutation or subscription type. It's possible to define
* types that reference other types but if they do not get used by a field / interface or union that itself leads back to the root
* types then it is not considered strongly references.
*
* Its reference counts however might be non-zero yet not be strongly referenced. For example given `type A { f : B } type B { f : A }` both types A and B
* will have non-zero counts but if no other type points to A or B that leads back to the root types then they are not strongly referenced types.
*
* Such types could be removed from the schema because there is no way to ever consume these types in a query. A common use case for this class
* is to find the types and directives in a schema that could in theory be removed because they are not useful.
*/
@PublicApi
public class SchemaUsage {
private final Map fieldReferenceCounts;
private final Map inputFieldReferenceCounts;
private final Map outputFieldReferenceCounts;
private final Map argReferenceCount;
private final Map interfaceReferenceCount;
private final Map unionReferenceCount;
private final Map directiveReferenceCount;
private final Map> interfaceImplementors;
private final Map> elementBackReferences;
private final Map> unionReferences;
private SchemaUsage(Builder builder) {
this.fieldReferenceCounts = ImmutableMap.copyOf(builder.fieldReferenceCounts);
this.inputFieldReferenceCounts = ImmutableMap.copyOf(builder.inputFieldReferenceCounts);
this.outputFieldReferenceCounts = ImmutableMap.copyOf(builder.outputFieldReferenceCounts);
this.argReferenceCount = ImmutableMap.copyOf(builder.argReferenceCount);
this.interfaceReferenceCount = ImmutableMap.copyOf(builder.interfaceReferenceCount);
this.unionReferenceCount = ImmutableMap.copyOf(builder.unionReferenceCount);
this.directiveReferenceCount = ImmutableMap.copyOf(builder.directiveReferenceCount);
this.interfaceImplementors = ImmutableMap.copyOf(builder.interfaceImplementors);
this.elementBackReferences = ImmutableMap.copyOf(builder.elementBackReferences);
this.unionReferences = ImmutableMap.copyOf(builder.unionReferences);
}
/**
* This shows how many times a type is referenced by either an input or output field.
*
* @return a map of type name to field reference counts
*/
public Map getFieldReferenceCounts() {
return fieldReferenceCounts;
}
/**
* This shows how many times a type is referenced by an output field.
*
* @return a map of type name to output field reference counts
*/
public Map getOutputFieldReferenceCounts() {
return outputFieldReferenceCounts;
}
/**
* This shows how many times a type is referenced by an input field.
*
* @return a map of type name to input field reference counts
*/
public Map getInputFieldReferenceCounts() {
return inputFieldReferenceCounts;
}
/**
* This shows how many times a type is referenced by an argument.
*
* @return a map of type name to argument reference counts
*/
public Map getArgumentReferenceCounts() {
return argReferenceCount;
}
/**
* This shows how many times an interface type is referenced as a member in some other
* object or interface type.
*
* @return a map of interface type name to object or interface type reference counts
*/
public Map getInterfaceReferenceCounts() {
return interfaceReferenceCount;
}
/**
* This shows how many times an object type is referenced as a member in some other
* union type.
*
* @return a map of object type name to union membership reference counts
*/
public Map getUnionReferenceCounts() {
return unionReferenceCount;
}
/**
* This shows how many times a directive is applied on some other schema element.
*
* @return a map of directive name to applied directive counts
*/
public Map getDirectiveReferenceCounts() {
return directiveReferenceCount;
}
/**
* Returns true if the named element is strongly reference somewhere in the schema back to the root types such as the schema
* query, mutation or subscription types.
*
* Graphql specified scalar types, introspection types and directives are always counted as referenced, even if
* not used explicitly.
*
* Directives that are defined but never applied on any schema elements will not report as referenced.
*
* @param schema the schema that contains the name type
* @param elementName the element name to check
*
* @return true if the element could be referenced
*/
public boolean isStronglyReferenced(GraphQLSchema schema, String elementName) {
return isReferencedImpl(schema, elementName, new HashSet<>());
}
/**
* This returns all the unreferenced named elements in a schema.
*
* @param schema the schema to check
*
* @return a set of the named schema elements where {@link #isStronglyReferenced(GraphQLSchema, String)} returns false
*/
public Set getUnReferencedElements(GraphQLSchema schema) {
Set elements = new LinkedHashSet<>();
schema.getAllTypesAsList().forEach(type -> {
if (!isStronglyReferenced(schema, type.getName())) {
elements.add(type);
}
});
schema.getDirectives().forEach(directive -> {
if (!isStronglyReferenced(schema, directive.getName())) {
elements.add(directive);
}
});
return elements;
}
private boolean isReferencedImpl(GraphQLSchema schema, String elementName, Set pathSoFar) {
if (pathSoFar.contains(elementName)) {
return false; // circular reference to that element
}
pathSoFar.add(elementName);
if (ScalarInfo.isGraphqlSpecifiedScalar(elementName)) {
return true;
}
GraphQLDirective directive = schema.getDirective(elementName);
if (directive != null) {
String directiveName = directive.getName();
if (DirectiveInfo.isGraphqlSpecifiedDirective(directiveName)) {
return true;
}
if (isNamedElementReferenced(schema, directiveName, pathSoFar)) {
return true;
}
}
GraphQLNamedType type = schema.getTypeAs(elementName);
if (type == null) {
return false;
}
if (Introspection.isIntrospectionTypes(type)) {
return true;
}
if (type == schema.getQueryType()) {
return true;
}
if (type == schema.getMutationType()) {
return true;
}
if (type == schema.getSubscriptionType()) {
return true;
}
if (isNamedElementReferenced(schema, elementName, pathSoFar)) {
return true;
}
if (type instanceof GraphQLInterfaceType) {
Set implementors = interfaceImplementors.getOrDefault(type.getName(), emptySet());
for (String implementor : implementors) {
if (isReferencedImpl(schema, implementor, pathSoFar)) {
return true;
}
}
}
if (type instanceof GraphQLObjectType) {
List interfaces = ((GraphQLObjectType) type).getInterfaces();
for (GraphQLNamedOutputType memberInterface : interfaces) {
Set implementors = interfaceImplementors.getOrDefault(memberInterface.getName(), emptySet());
for (String implementor : implementors) {
if (isReferencedImpl(schema, implementor, pathSoFar)) {
return true;
}
}
}
Set unionContainers = unionReferences.getOrDefault(type.getName(), emptySet());
for (String unionContainer : unionContainers) {
if (isReferencedImpl(schema, unionContainer, pathSoFar)) {
return true;
}
}
}
return false;
}
private boolean isNamedElementReferenced(GraphQLSchema schema, String elementName, Set pathSoFar) {
Set references = elementBackReferences.getOrDefault(elementName, emptySet());
for (String reference : references) {
if (isReferencedImpl(schema, reference, pathSoFar)) {
return true;
}
}
return false;
}
@Internal
static class Builder {
Map fieldReferenceCounts = new LinkedHashMap<>();
Map inputFieldReferenceCounts = new LinkedHashMap<>();
Map outputFieldReferenceCounts = new LinkedHashMap<>();
Map argReferenceCount = new LinkedHashMap<>();
Map interfaceReferenceCount = new LinkedHashMap<>();
Map unionReferenceCount = new LinkedHashMap<>();
Map directiveReferenceCount = new LinkedHashMap<>();
Map> interfaceImplementors = new LinkedHashMap<>();
Map> unionReferences = new LinkedHashMap<>();
Map> elementBackReferences = new LinkedHashMap<>();
SchemaUsage build() {
return new SchemaUsage(this);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy