graphql.execution.ExecutionStepInfo Maven / Gradle / Ivy
package graphql.execution;
import graphql.PublicApi;
import graphql.collect.ImmutableMapWithNullValues;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLTypeUtil;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertTrue;
import static graphql.schema.GraphQLTypeUtil.isList;
/**
* As the graphql query executes, it forms a hierarchy from parent fields (and their type) to their child fields (and their type)
* until a scalar type is encountered; this class captures that execution type information.
*
* The static graphql type system (rightly) does not contain a hierarchy of child to parent types nor the nonnull ness of
* type instances, so this helper class adds this information during query execution.
*/
@PublicApi
public class ExecutionStepInfo {
/*
* An ExecutionStepInfo represent either a field or a list element inside a list of objects/interfaces/unions.
*
* A StepInfo never represent a Scalar/Enum inside a list (e.g. [String]) because GraphQL execution doesn't descend down
* scalar/enums lists.
*
*/
/**
* If this StepInfo represent a field the type is equal to fieldDefinition.getType()
*
* if this StepInfo is a list element this type is the actual current list element. For example:
* Query.pets: [[Pet]] with Pet either a Dog or Cat and the actual result is [[Dog1],[[Cat1]]
* Then the type is (for a query "{pets{name}}"):
* [[Pet]] for /pets (representing the field Query.pets, not a list element)
* [Pet] fot /pets[0]
* [Pet] for /pets[1]
* Dog for /pets[0][0]
* Cat for /pets[1][0]
* String for /pets[0][0]/name (representing the field Dog.name, not a list element)
* String for /pets[1][0]/name (representing the field Cat.name, not a list element)
*/
private final GraphQLOutputType type;
/**
* A list element is characterized by having a path ending with an index segment. (ResultPath.isListSegment())
*/
private final ResultPath path;
private final ExecutionStepInfo parent;
/**
* field, fieldDefinition, fieldContainer and arguments differ per field StepInfo.
*
* But for list StepInfos these properties are the same as the field returning the list.
*/
private final MergedField field;
private final GraphQLFieldDefinition fieldDefinition;
private final GraphQLObjectType fieldContainer;
private final Supplier> arguments;
private ExecutionStepInfo(Builder builder) {
this.fieldDefinition = builder.fieldDefinition;
this.field = builder.field;
this.path = builder.path;
this.parent = builder.parentInfo;
this.type = assertNotNull(builder.type, () -> "you must provide a graphql type");
this.arguments = builder.arguments;
this.fieldContainer = builder.fieldContainer;
}
/**
* @return the GraphQLObjectType defining the {@link #getFieldDefinition()}
*
* @see ExecutionStepInfo#getObjectType()
* @deprecated use {@link #getObjectType()} instead as it is named better
*/
@Deprecated(since = "2022-02-03")
public GraphQLObjectType getFieldContainer() {
return fieldContainer;
}
/**
* The GraphQLObjectType where fieldDefinition is defined.
* Note:
* For the Introspection field __typename the returned object type doesn't actually contain the fieldDefinition.
*
* @return the GraphQLObjectType defining the {@link #getFieldDefinition()}
*/
public GraphQLObjectType getObjectType() {
return fieldContainer;
}
/**
* This returns the type for the current step.
*
* @return the graphql type in question
*/
public GraphQLOutputType getType() {
return type;
}
/**
* This returns the type which is unwrapped if it was {@link GraphQLNonNull} wrapped
*
* @return the graphql type in question
*/
public GraphQLOutputType getUnwrappedNonNullType() {
return (GraphQLOutputType) GraphQLTypeUtil.unwrapNonNull(this.type);
}
/**
* This returns the field definition that is in play when this type info was created or null
* if the type is a root query type
*
* @return the field definition or null if there is not one
*/
public GraphQLFieldDefinition getFieldDefinition() {
return fieldDefinition;
}
/**
* This returns the AST fields that matches the {@link #getFieldDefinition()} during execution
*
* @return the merged fields
*/
public MergedField getField() {
return field;
}
/**
* @return the {@link ResultPath} to this info
*/
public ResultPath getPath() {
return path;
}
/**
* @return true if the type must be nonnull
*/
public boolean isNonNullType() {
return GraphQLTypeUtil.isNonNull(this.type);
}
/**
* @return true if the type is a list
*/
public boolean isListType() {
return isList(type);
}
/**
* @return the resolved arguments that have been passed to this field
*/
public Map getArguments() {
return arguments.get();
}
/**
* Returns the named argument
*
* @param name the name of the argument
* @param you decide what type it is
*
* @return the named argument or null if it's not present
*/
@SuppressWarnings("unchecked")
public T getArgument(String name) {
return (T) getArguments().get(name);
}
/**
* @return the parent type information
*/
public ExecutionStepInfo getParent() {
return parent;
}
/**
* @return true if the type has a parent (most do)
*/
public boolean hasParent() {
return parent != null;
}
/**
* This allows you to morph a type into a more specialized form yet return the same
* parent and non-null ness, for example taking a {@link GraphQLInterfaceType}
* and turning it into a specific {@link graphql.schema.GraphQLObjectType}
* after type resolution has occurred
*
* @param newType the new type to be
*
* @return a new type info with the same
*/
public ExecutionStepInfo changeTypeWithPreservedNonNull(GraphQLOutputType newType) {
assertTrue(!GraphQLTypeUtil.isNonNull(newType), () -> "newType can't be non null");
if (isNonNullType()) {
return newExecutionStepInfo(this).type(GraphQLNonNull.nonNull(newType)).build();
} else {
return newExecutionStepInfo(this).type(newType).build();
}
}
/**
* @return the type in graphql SDL format, eg [typeName!]!
*/
public String simplePrint() {
return GraphQLTypeUtil.simplePrint(type);
}
@Override
public String toString() {
return "ExecutionStepInfo{" +
" path=" + path +
", type=" + type +
", fieldDefinition=" + fieldDefinition +
'}';
}
public ExecutionStepInfo transform(Consumer builderConsumer) {
Builder builder = new Builder(this);
builderConsumer.accept(builder);
return builder.build();
}
public String getResultKey() {
return field.getResultKey();
}
/**
* @return a builder of type info
*/
public static ExecutionStepInfo.Builder newExecutionStepInfo() {
return new Builder();
}
public static ExecutionStepInfo.Builder newExecutionStepInfo(ExecutionStepInfo existing) {
return new Builder(existing);
}
public static class Builder {
GraphQLOutputType type;
ExecutionStepInfo parentInfo;
GraphQLFieldDefinition fieldDefinition;
GraphQLObjectType fieldContainer;
MergedField field;
ResultPath path;
Supplier> arguments;
/**
* @see ExecutionStepInfo#newExecutionStepInfo()
*/
private Builder() {
arguments = ImmutableMapWithNullValues::emptyMap;
}
private Builder(ExecutionStepInfo existing) {
this.type = existing.type;
this.parentInfo = existing.parent;
this.fieldDefinition = existing.fieldDefinition;
this.fieldContainer = existing.fieldContainer;
this.field = existing.field;
this.path = existing.path;
this.arguments = existing.arguments;
}
public Builder type(GraphQLOutputType type) {
this.type = type;
return this;
}
public Builder parentInfo(ExecutionStepInfo executionStepInfo) {
this.parentInfo = executionStepInfo;
return this;
}
public Builder fieldDefinition(GraphQLFieldDefinition fieldDefinition) {
this.fieldDefinition = fieldDefinition;
return this;
}
public Builder field(MergedField field) {
this.field = field;
return this;
}
public Builder path(ResultPath resultPath) {
this.path = resultPath;
return this;
}
public Builder arguments(Supplier> arguments) {
this.arguments = arguments;
return this;
}
public Builder fieldContainer(GraphQLObjectType fieldContainer) {
this.fieldContainer = fieldContainer;
return this;
}
public ExecutionStepInfo build() {
return new ExecutionStepInfo(this);
}
}
}