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
6 changes: 3 additions & 3 deletions src/main/antlr/GraphqlOperation.g4
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import GraphqlCommon;

operationDefinition:
selectionSet |
operationType name? variableDefinitions? directives? selectionSet;
description? operationType name? variableDefinitions? directives? selectionSet;

variableDefinitions : '(' variableDefinition+ ')';

variableDefinition : variable ':' type defaultValue? directives?;
variableDefinition : description? variable ':' type defaultValue? directives?;


selectionSet : '{' selection+ '}';
Expand All @@ -27,7 +27,7 @@ fragmentSpread : '...' fragmentName directives?;

inlineFragment : '...' typeCondition? directives? selectionSet;

fragmentDefinition : FRAGMENT fragmentName typeCondition directives? selectionSet;
fragmentDefinition : description? FRAGMENT fragmentName typeCondition directives? selectionSet;


typeCondition : ON_KEYWORD typeName;
47 changes: 37 additions & 10 deletions src/main/java/graphql/language/AstPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,13 @@ private static boolean hasDescription(Node<?> node) {
return false;
}

private static boolean hasDescription(List<? extends Node<?>> nodes) {
return nodes.stream().anyMatch(AstPrinter::hasDescription);
}

private NodePrinter<FragmentDefinition> fragmentDefinition() {
return (out, node) -> {
description(out, node);
out.append("fragment ");
out.append(node.getName());
out.append(" on ");
Expand Down Expand Up @@ -367,24 +372,17 @@ private NodePrinter<OperationDefinition> operationDefinition() {
String name = node.getName();
// Anonymous queries with no directives or variable definitions can use
// the query short form.
if (isEmpty(name) && isEmpty(node.getDirectives()) && isEmpty(node.getVariableDefinitions())
&& node.getOperation() == OperationDefinition.Operation.QUERY) {
if (canUseQueryShortForm(node)) {
node(out, node.getSelectionSet());
} else {
description(out, node);
OperationDefinition.Operation op = node.getOperation();
out.append(op.toString().toLowerCase());
if (!isEmpty(name)) {
out.append(' ');
out.append(name);
}
if (!isEmpty(node.getVariableDefinitions())) {
if (isEmpty(name)) {
out.append(' ');
}
out.append('(');
join(out, node.getVariableDefinitions(), argSep);
out.append(')');
}
variableDefinitions(out, node, argSep);
if (!isEmpty(node.getDirectives())) {
out.append(' ');
directives(out, node.getDirectives());
Expand All @@ -397,6 +395,34 @@ private NodePrinter<OperationDefinition> operationDefinition() {
};
}

private boolean canUseQueryShortForm(OperationDefinition node) {
return (compactMode || !hasDescription(node))
&& isEmpty(node.getName())
&& isEmpty(node.getDirectives())
&& isEmpty(node.getVariableDefinitions())
&& node.getOperation() == OperationDefinition.Operation.QUERY;
}

private void variableDefinitions(StringBuilder out, OperationDefinition node, String argSep) {
if (isEmpty(node.getVariableDefinitions())) {
return;
}
if (isEmpty(node.getName())) {
out.append(' ');
}
if (!compactMode && hasDescription(node.getVariableDefinitions())) {
int offset = out.length();
out.append("(\n");
join(out, node.getVariableDefinitions(), "\n");
indent(out, offset);
out.append("\n)");
return;
}
out.append('(');
join(out, node.getVariableDefinitions(), argSep);
out.append(')');
}

private NodePrinter<OperationTypeDefinition> operationTypeDefinition() {
String nameTypeSep = compactMode ? ":" : ": ";
return (out, node) -> {
Expand Down Expand Up @@ -546,6 +572,7 @@ private NodePrinter<VariableDefinition> variableDefinition() {
String nameTypeSep = compactMode ? ":" : ": ";
String defaultValueEquals = compactMode ? "=" : " = ";
return (out, node) -> {
description(out, node);
out.append('$');
out.append(node.getName());
out.append(nameTypeSep);
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/graphql/language/FragmentDefinition.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
*/
@PublicApi
@NullMarked
public class FragmentDefinition extends AbstractNode<FragmentDefinition> implements Definition<FragmentDefinition>, SelectionSetContainer<FragmentDefinition>, DirectivesContainer<FragmentDefinition>, NamedNode<FragmentDefinition> {
public class FragmentDefinition extends AbstractDescribedNode<FragmentDefinition> implements Definition<FragmentDefinition>, SelectionSetContainer<FragmentDefinition>, DirectivesContainer<FragmentDefinition>, NamedNode<FragmentDefinition> {

private final String name;
private final TypeName typeCondition;
Expand All @@ -43,11 +43,12 @@ protected FragmentDefinition(String name,
TypeName typeCondition,
List<Directive> directives,
SelectionSet selectionSet,
@Nullable Description description,
@Nullable SourceLocation sourceLocation,
List<Comment> comments,
IgnoredChars ignoredChars,
Map<String, String> additionalData) {
super(sourceLocation, comments, ignoredChars, additionalData);
super(sourceLocation, comments, ignoredChars, additionalData, description);
this.name = name;
this.typeCondition = typeCondition;
this.directives = NodeUtil.DirectivesHolder.of(directives);
Expand Down Expand Up @@ -136,6 +137,7 @@ public FragmentDefinition deepCopy() {
assertNotNull(deepCopy(typeCondition)),
assertNotNull(deepCopy(directives.getDirectives())),
assertNotNull(deepCopy(selectionSet)),
description,
getSourceLocation(),
getComments(),
getIgnoredChars(),
Expand Down Expand Up @@ -174,6 +176,7 @@ public static final class Builder implements NodeDirectivesBuilder {

private String name;
private TypeName typeCondition;
private Description description;
private ImmutableList<Directive> directives = emptyList();
private SelectionSet selectionSet;
private IgnoredChars ignoredChars = IgnoredChars.EMPTY;
Expand All @@ -187,6 +190,7 @@ private Builder(FragmentDefinition existing) {
this.comments = ImmutableList.copyOf(existing.getComments());
this.name = existing.getName();
this.typeCondition = existing.getTypeCondition();
this.description = existing.getDescription();
this.directives = ImmutableList.copyOf(existing.getDirectives());
this.selectionSet = existing.getSelectionSet();
this.ignoredChars = existing.getIgnoredChars();
Expand Down Expand Up @@ -214,6 +218,11 @@ public Builder typeCondition(TypeName typeCondition) {
return this;
}

public Builder description(Description description) {
this.description = description;
return this;
}

@Override
public Builder directives(List<Directive> directives) {
this.directives = ImmutableList.copyOf(directives);
Expand Down Expand Up @@ -247,7 +256,7 @@ public Builder additionalData(String key, String value) {


public FragmentDefinition build() {
return new FragmentDefinition(assertNotNull(name), assertNotNull(typeCondition), directives, assertNotNull(selectionSet), sourceLocation, comments, ignoredChars, additionalData);
return new FragmentDefinition(assertNotNull(name), assertNotNull(typeCondition), directives, assertNotNull(selectionSet), description, sourceLocation, comments, ignoredChars, additionalData);
}
}
}
14 changes: 12 additions & 2 deletions src/main/java/graphql/language/OperationDefinition.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

@PublicApi
@NullMarked
public class OperationDefinition extends AbstractNode<OperationDefinition> implements Definition<OperationDefinition>, SelectionSetContainer<OperationDefinition>, DirectivesContainer<OperationDefinition>, NamedNode<OperationDefinition> {
public class OperationDefinition extends AbstractDescribedNode<OperationDefinition> implements Definition<OperationDefinition>, SelectionSetContainer<OperationDefinition>, DirectivesContainer<OperationDefinition>, NamedNode<OperationDefinition> {

public enum Operation {
QUERY, MUTATION, SUBSCRIPTION
Expand All @@ -49,11 +49,12 @@ protected OperationDefinition(@Nullable String name,
List<VariableDefinition> variableDefinitions,
List<Directive> directives,
SelectionSet selectionSet,
@Nullable Description description,
@Nullable SourceLocation sourceLocation,
List<Comment> comments,
IgnoredChars ignoredChars,
Map<String, String> additionalData) {
super(sourceLocation, comments, ignoredChars, additionalData);
super(sourceLocation, comments, ignoredChars, additionalData, description);
this.name = name;
this.operation = operation;
this.variableDefinitions = ImmutableList.copyOf(variableDefinitions);
Expand Down Expand Up @@ -147,6 +148,7 @@ public OperationDefinition deepCopy() {
assertNotNull(deepCopy(variableDefinitions), "variableDefinitions deepCopy should not return null"),
assertNotNull(deepCopy(directives.getDirectives()), "directives deepCopy should not return null"),
assertNotNull(deepCopy(selectionSet), "selectionSet deepCopy should not return null"),
description,
getSourceLocation(),
getComments(),
getIgnoredChars(),
Expand Down Expand Up @@ -185,6 +187,7 @@ public static final class Builder implements NodeDirectivesBuilder {
private ImmutableList<Comment> comments = emptyList();
private String name;
private Operation operation = Operation.QUERY;
private Description description;
private ImmutableList<VariableDefinition> variableDefinitions = emptyList();
private ImmutableList<Directive> directives = emptyList();
private SelectionSet selectionSet;
Expand All @@ -199,6 +202,7 @@ private Builder(OperationDefinition existing) {
this.comments = ImmutableList.copyOf(existing.getComments());
this.name = existing.getName();
this.operation = existing.getOperation();
this.description = existing.getDescription();
this.variableDefinitions = ImmutableList.copyOf(existing.getVariableDefinitions());
this.directives = ImmutableList.copyOf(existing.getDirectives());
this.selectionSet = existing.getSelectionSet();
Expand Down Expand Up @@ -227,6 +231,11 @@ public Builder operation(Operation operation) {
return this;
}

public Builder description(Description description) {
this.description = description;
return this;
}

public Builder variableDefinitions(List<VariableDefinition> variableDefinitions) {
this.variableDefinitions = ImmutableList.copyOf(variableDefinitions);
return this;
Expand Down Expand Up @@ -275,6 +284,7 @@ public OperationDefinition build() {
variableDefinitions,
directives,
selectionSet,
description,
sourceLocation,
comments,
ignoredChars,
Expand Down
18 changes: 14 additions & 4 deletions src/main/java/graphql/language/VariableDefinition.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import static graphql.language.NodeChildrenContainer.newNodeChildrenContainer;

@PublicApi
public class VariableDefinition extends AbstractNode<VariableDefinition> implements DirectivesContainer<VariableDefinition>, NamedNode<VariableDefinition> {
public class VariableDefinition extends AbstractDescribedNode<VariableDefinition> implements DirectivesContainer<VariableDefinition>, NamedNode<VariableDefinition> {

private final String name;
private final Type type;
Expand All @@ -37,11 +37,12 @@ protected VariableDefinition(String name,
Type type,
Value defaultValue,
List<Directive> directives,
Description description,
SourceLocation sourceLocation,
List<Comment> comments,
IgnoredChars ignoredChars,
Map<String, String> additionalData) {
super(sourceLocation, comments, ignoredChars, additionalData);
super(sourceLocation, comments, ignoredChars, additionalData, description);
this.name = name;
this.type = type;
this.defaultValue = defaultValue;
Expand All @@ -58,7 +59,7 @@ protected VariableDefinition(String name,
public VariableDefinition(String name,
Type type,
Value defaultValue) {
this(name, type, defaultValue, emptyList(), null, emptyList(), IgnoredChars.EMPTY, emptyMap());
this(name, type, defaultValue, emptyList(), null, null, emptyList(), IgnoredChars.EMPTY, emptyMap());
}

/**
Expand All @@ -69,7 +70,7 @@ public VariableDefinition(String name,
*/
public VariableDefinition(String name,
Type type) {
this(name, type, null, emptyList(), null, emptyList(), IgnoredChars.EMPTY, emptyMap());
this(name, type, null, emptyList(), null, null, emptyList(), IgnoredChars.EMPTY, emptyMap());
}

public Value getDefaultValue() {
Expand Down Expand Up @@ -154,6 +155,7 @@ public VariableDefinition deepCopy() {
deepCopy(type),
deepCopy(defaultValue),
deepCopy(directives.getDirectives()),
description,
getSourceLocation(),
getComments(),
getIgnoredChars(),
Expand Down Expand Up @@ -204,6 +206,7 @@ public static final class Builder implements NodeDirectivesBuilder {
private ImmutableList<Comment> comments = emptyList();
private Type type;
private Value defaultValue;
private Description description;
private ImmutableList<Directive> directives = emptyList();
private IgnoredChars ignoredChars = IgnoredChars.EMPTY;
private Map<String, String> additionalData = new LinkedHashMap<>();
Expand All @@ -217,6 +220,7 @@ private Builder(VariableDefinition existing) {
this.name = existing.getName();
this.type = existing.getType();
this.defaultValue = existing.getDefaultValue();
this.description = existing.getDescription();
this.directives = ImmutableList.copyOf(existing.getDirectives());
this.ignoredChars = existing.getIgnoredChars();
this.additionalData = new LinkedHashMap<>(existing.getAdditionalData());
Expand Down Expand Up @@ -247,6 +251,11 @@ public Builder defaultValue(Value defaultValue) {
return this;
}

public Builder description(Description description) {
this.description = description;
return this;
}

@Override
public Builder directives(List<Directive> directives) {
this.directives = ImmutableList.copyOf(directives);
Expand Down Expand Up @@ -279,6 +288,7 @@ public VariableDefinition build() {
type,
defaultValue,
directives,
description,
sourceLocation,
comments,
ignoredChars,
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/graphql/parser/GraphqlAntlrToLanguage.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ protected OperationDefinition createOperationDefinition(GraphqlParser.OperationD
if (ctx.name() != null) {
operationDefinition.name(ctx.name().getText());
}
operationDefinition.description(newDescription(ctx.description()));
operationDefinition.variableDefinitions(createVariableDefinitions(ctx.variableDefinitions()));
operationDefinition.selectionSet(createSelectionSet(ctx.selectionSet()));
operationDefinition.directives(createDirectives(ctx.directives()));
Expand Down Expand Up @@ -178,6 +179,7 @@ protected List<VariableDefinition> createVariableDefinitions(GraphqlParser.Varia
protected VariableDefinition createVariableDefinition(GraphqlParser.VariableDefinitionContext ctx) {
VariableDefinition.Builder variableDefinition = VariableDefinition.newVariableDefinition();
addCommonData(variableDefinition, ctx);
variableDefinition.description(newDescription(ctx.description()));
variableDefinition.name(ctx.variable().name().getText());
if (ctx.defaultValue() != null) {
Value value = createValue(ctx.defaultValue().value());
Expand All @@ -192,6 +194,7 @@ protected VariableDefinition createVariableDefinition(GraphqlParser.VariableDefi
protected FragmentDefinition createFragmentDefinition(GraphqlParser.FragmentDefinitionContext ctx) {
FragmentDefinition.Builder fragmentDefinition = FragmentDefinition.newFragmentDefinition();
addCommonData(fragmentDefinition, ctx);
fragmentDefinition.description(newDescription(ctx.description()));
fragmentDefinition.name(ctx.fragmentName().getText());
fragmentDefinition.typeCondition(TypeName.newTypeName().name(ctx.typeCondition().typeName().getText()).build());
fragmentDefinition.directives(createDirectives(ctx.directives()));
Expand Down
27 changes: 27 additions & 0 deletions src/test/groovy/graphql/ParseAndValidateTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,33 @@ class ParseAndValidateTest extends Specification {
errors.isEmpty()
}

def "executable descriptions do not affect validation"() {
def input = ExecutionInput.newExecutionInput('''
"Fetches a hero"
query HeroName(
"The target episode"
$episode: Episode = JEDI
) {
hero(episode: $episode) {
...heroFields
}
}

"Reusable hero fields"
fragment heroFields on Character {
name
}
''').build()
def result = ParseAndValidate.parse(input)

when:
def errors = ParseAndValidate.validate(StarWarsSchema.starWarsSchema, result.getDocument(), input.getLocale())

then:
!result.isFailure()
errors.isEmpty()
}

def "will validate documents with actual problems"() {

def input = ExecutionInput.newExecutionInput("query { hero }").variables([var1: 1]).build()
Expand Down
Loading