graphql.schema.GraphQLInterfaceType Maven / Gradle / Ivy
package graphql.schema;
import com.google.common.collect.ImmutableList;
import graphql.Assert;
import graphql.DirectivesUtil;
import graphql.Internal;
import graphql.PublicApi;
import graphql.language.InterfaceTypeDefinition;
import graphql.language.InterfaceTypeExtensionDefinition;
import graphql.util.FpKit;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertShouldNeverHappen;
import static graphql.Assert.assertValidName;
import static graphql.collect.ImmutableKit.emptyList;
import static graphql.schema.GraphqlTypeComparators.sortTypes;
import static graphql.util.FpKit.getByName;
import static graphql.util.FpKit.valuesToList;
/**
* In graphql, an interface is an abstract type that defines the set of fields that a type must include to
* implement that interface.
*
* At runtime a {@link TypeResolver} is used to take an interface object value and decide what {@link GraphQLObjectType}
* represents this interface type.
*
* See https://graphql.org/learn/schema/#interfaces for more details on the concept.
*/
@PublicApi
public class GraphQLInterfaceType implements GraphQLNamedType, GraphQLCompositeType, GraphQLUnmodifiedType, GraphQLNullableType, GraphQLDirectiveContainer, GraphQLImplementingType {
private final String name;
private final String description;
private final Map fieldDefinitionsByName;
private final TypeResolver typeResolver;
private final InterfaceTypeDefinition definition;
private final ImmutableList extensionDefinitions;
private final DirectivesUtil.DirectivesHolder directivesHolder;
private final ImmutableList originalInterfaces;
private final Comparator super GraphQLSchemaElement> interfaceComparator;
private ImmutableList replacedInterfaces;
public static final String CHILD_FIELD_DEFINITIONS = "fieldDefinitions";
public static final String CHILD_INTERFACES = "interfaces";
@Internal
private GraphQLInterfaceType(String name,
String description,
List fieldDefinitions,
TypeResolver typeResolver,
List directives,
List appliedDirectives,
InterfaceTypeDefinition definition,
List extensionDefinitions,
List interfaces,
Comparator super GraphQLSchemaElement> interfaceComparator) {
assertValidName(name);
assertNotNull(fieldDefinitions, () -> "fieldDefinitions can't null");
assertNotNull(directives, () -> "directives cannot be null");
this.name = name;
this.description = description;
this.typeResolver = typeResolver;
this.definition = definition;
this.interfaceComparator = interfaceComparator;
this.originalInterfaces = ImmutableList.copyOf(sortTypes(interfaceComparator, interfaces));
this.extensionDefinitions = ImmutableList.copyOf(extensionDefinitions);
this.directivesHolder = DirectivesUtil.DirectivesHolder.create(directives, appliedDirectives);
this.fieldDefinitionsByName = buildDefinitionMap(fieldDefinitions);
}
private Map buildDefinitionMap(List fieldDefinitions) {
return FpKit.getByName(fieldDefinitions, GraphQLFieldDefinition::getName,
(fld1, fld2) -> assertShouldNeverHappen("Duplicated definition for field '%s' in interface '%s'", fld1.getName(), this.name));
}
@Override
public GraphQLFieldDefinition getFieldDefinition(String name) {
return fieldDefinitionsByName.get(name);
}
@Override
public List getFieldDefinitions() {
return ImmutableList.copyOf(fieldDefinitionsByName.values());
}
@Override
public String getName() {
return name;
}
public String getDescription() {
return description;
}
// to be removed in a future version when all code is in the code registry
@Internal
@Deprecated(since = "2018-12-03")
TypeResolver getTypeResolver() {
return typeResolver;
}
public InterfaceTypeDefinition getDefinition() {
return definition;
}
public List getExtensionDefinitions() {
return extensionDefinitions;
}
@Override
public List getDirectives() {
return directivesHolder.getDirectives();
}
@Override
public Map getDirectivesByName() {
return directivesHolder.getDirectivesByName();
}
@Override
public Map> getAllDirectivesByName() {
return directivesHolder.getAllDirectivesByName();
}
@Override
public GraphQLDirective getDirective(String directiveName) {
return directivesHolder.getDirective(directiveName);
}
@Override
public List getAppliedDirectives() {
return directivesHolder.getAppliedDirectives();
}
@Override
public Map> getAllAppliedDirectivesByName() {
return directivesHolder.getAllAppliedDirectivesByName();
}
@Override
public GraphQLAppliedDirective getAppliedDirective(String directiveName) {
return directivesHolder.getAppliedDirective(directiveName);
}
@Override
public String toString() {
return "GraphQLInterfaceType{" +
"name='" + name + '\'' +
", description='" + description + '\'' +
", fieldDefinitionsByName=" + fieldDefinitionsByName.keySet() +
", typeResolver=" + typeResolver +
'}';
}
/**
* This helps you transform the current GraphQLInterfaceType into another one by starting a builder with all
* the current values and allows you to transform it how you want.
*
* @param builderConsumer the consumer code that will be given a builder to transform
*
* @return a new object based on calling build on that builder
*/
public GraphQLInterfaceType transform(Consumer builderConsumer) {
Builder builder = newInterface(this);
builderConsumer.accept(builder);
return builder.build();
}
@Override
public GraphQLSchemaElement copy() {
return newInterface(this).build();
}
@Override
public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) {
return visitor.visitGraphQLInterfaceType(this, context);
}
@Override
public List getChildren() {
List children = new ArrayList<>(fieldDefinitionsByName.values());
children.addAll(getInterfaces());
children.addAll(directivesHolder.getDirectives());
children.addAll(directivesHolder.getAppliedDirectives());
return children;
}
@Override
public SchemaElementChildrenContainer getChildrenWithTypeReferences() {
return SchemaElementChildrenContainer.newSchemaElementChildrenContainer()
.children(CHILD_FIELD_DEFINITIONS, fieldDefinitionsByName.values())
.children(CHILD_INTERFACES, originalInterfaces)
.children(CHILD_DIRECTIVES, directivesHolder.getDirectives())
.children(CHILD_APPLIED_DIRECTIVES, directivesHolder.getAppliedDirectives())
.build();
}
@Override
public GraphQLInterfaceType withNewChildren(SchemaElementChildrenContainer newChildren) {
return transform(builder ->
builder.replaceDirectives(newChildren.getChildren(CHILD_DIRECTIVES))
.replaceFields(newChildren.getChildren(CHILD_FIELD_DEFINITIONS))
.replaceInterfacesOrReferences(newChildren.getChildren(CHILD_INTERFACES))
.replaceDirectives(newChildren.getChildren(CHILD_DIRECTIVES))
.replaceAppliedDirectives(newChildren.getChildren(CHILD_APPLIED_DIRECTIVES))
);
}
@Override
public List getInterfaces() {
if (replacedInterfaces != null) {
return replacedInterfaces;
}
return originalInterfaces;
}
void replaceInterfaces(List interfaces) {
this.replacedInterfaces = ImmutableList.copyOf(sortTypes(interfaceComparator, interfaces));
}
/**
* {@inheritDoc}
*/
@Override
public final boolean equals(Object o) {
return super.equals(o);
}
/**
* {@inheritDoc}
*/
@Override
public final int hashCode() {
return super.hashCode();
}
public static Builder newInterface() {
return new Builder();
}
public static Builder newInterface(GraphQLInterfaceType existing) {
return new Builder(existing);
}
@PublicApi
public static class Builder extends GraphqlDirectivesContainerTypeBuilder {
private TypeResolver typeResolver;
private InterfaceTypeDefinition definition;
private List extensionDefinitions = emptyList();
private final Map fields = new LinkedHashMap<>();
private final Map interfaces = new LinkedHashMap<>();
public Builder() {
}
public Builder(GraphQLInterfaceType existing) {
this.name = existing.getName();
this.description = existing.getDescription();
this.typeResolver = existing.getTypeResolver();
this.definition = existing.getDefinition();
this.extensionDefinitions = existing.getExtensionDefinitions();
this.fields.putAll(getByName(existing.getFieldDefinitions(), GraphQLFieldDefinition::getName));
this.interfaces.putAll(getByName(existing.originalInterfaces, GraphQLNamedType::getName));
copyExistingDirectives(existing);
}
public Builder definition(InterfaceTypeDefinition definition) {
this.definition = definition;
return this;
}
public Builder extensionDefinitions(List extensionDefinitions) {
this.extensionDefinitions = extensionDefinitions;
return this;
}
public Builder field(GraphQLFieldDefinition fieldDefinition) {
assertNotNull(fieldDefinition, () -> "fieldDefinition can't be null");
this.fields.put(fieldDefinition.getName(), fieldDefinition);
return this;
}
/**
* Take a field builder in a function definition and apply. Can be used in a jdk8 lambda
* e.g.:
*
* {@code
* field(f -> f.name("fieldName"))
* }
*
*
* @param builderFunction a supplier for the builder impl
*
* @return this
*/
public Builder field(UnaryOperator builderFunction) {
assertNotNull(builderFunction, () -> "builderFunction can't be null");
GraphQLFieldDefinition.Builder builder = GraphQLFieldDefinition.newFieldDefinition();
builder = builderFunction.apply(builder);
return field(builder);
}
/**
* Same effect as the field(GraphQLFieldDefinition). Builder.build() is called
* from within
*
* @param builder an un-built/incomplete GraphQLFieldDefinition
*
* @return this
*/
public Builder field(GraphQLFieldDefinition.Builder builder) {
return field(builder.build());
}
public Builder fields(List fieldDefinitions) {
assertNotNull(fieldDefinitions, () -> "fieldDefinitions can't be null");
fieldDefinitions.forEach(this::field);
return this;
}
public Builder replaceFields(List fieldDefinitions) {
assertNotNull(fieldDefinitions, () -> "fieldDefinitions can't be null");
this.fields.clear();
fieldDefinitions.forEach(this::field);
return this;
}
public boolean hasField(String fieldName) {
return fields.containsKey(fieldName);
}
/**
* This is used to clear all the fields in the builder so far.
*
* @return the builder
*/
public Builder clearFields() {
fields.clear();
return this;
}
/**
* @param typeResolver the type resolver
*
* @return this builder
*
* @deprecated use {@link graphql.schema.GraphQLCodeRegistry.Builder#typeResolver(GraphQLInterfaceType, TypeResolver)} instead
*/
@Deprecated(since = "2018-12-03")
public Builder typeResolver(TypeResolver typeResolver) {
this.typeResolver = typeResolver;
return this;
}
public Builder replaceInterfaces(List interfaces) {
return replaceInterfacesOrReferences(interfaces);
}
public Builder replaceInterfacesOrReferences(List extends GraphQLNamedOutputType> interfacesOrReferences) {
assertNotNull(interfacesOrReferences, () -> "interfaces can't be null");
this.interfaces.clear();
for (GraphQLNamedOutputType schemaElement : interfacesOrReferences) {
if (schemaElement instanceof GraphQLInterfaceType || schemaElement instanceof GraphQLTypeReference) {
this.interfaces.put(schemaElement.getName(), schemaElement);
} else {
Assert.assertShouldNeverHappen("Unexpected type " + (schemaElement != null ? schemaElement.getClass() : "null"));
}
}
return this;
}
public Builder withInterface(GraphQLInterfaceType interfaceType) {
assertNotNull(interfaceType, () -> "interfaceType can't be null");
this.interfaces.put(interfaceType.getName(), interfaceType);
return this;
}
public Builder withInterface(GraphQLTypeReference reference) {
assertNotNull(reference, () -> "reference can't be null");
this.interfaces.put(reference.getName(), reference);
return this;
}
public Builder withInterfaces(GraphQLInterfaceType... interfaceType) {
for (GraphQLInterfaceType type : interfaceType) {
withInterface(type);
}
return this;
}
public Builder withInterfaces(GraphQLTypeReference... references) {
for (GraphQLTypeReference reference : references) {
withInterface(reference);
}
return this;
}
// -- the following are repeated to avoid a binary incompatibility problem --
@Override
public Builder replaceDirectives(List directives) {
return super.replaceDirectives(directives);
}
@Override
public Builder withDirectives(GraphQLDirective... directives) {
return super.withDirectives(directives);
}
@Override
public Builder withDirective(GraphQLDirective directive) {
return super.withDirective(directive);
}
@Override
public Builder withDirective(GraphQLDirective.Builder builder) {
return super.withDirective(builder);
}
@Override
public Builder clearDirectives() {
return super.clearDirectives();
}
@Override
public Builder name(String name) {
return super.name(name);
}
@Override
public Builder description(String description) {
return super.description(description);
}
public GraphQLInterfaceType build() {
return new GraphQLInterfaceType(
name,
description,
sort(fields, GraphQLInterfaceType.class, GraphQLFieldDefinition.class),
typeResolver,
sort(directives, GraphQLInterfaceType.class, GraphQLDirective.class),
sort(appliedDirectives, GraphQLScalarType.class, GraphQLAppliedDirective.class),
definition,
extensionDefinitions,
valuesToList(interfaces),
getComparator(GraphQLInterfaceType.class, GraphQLInterfaceType.class)
);
}
}
}