Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion src/main/antlr/GraphqlCommon.g4
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ arguments : '(' argument+ ')';

argument : name ':' valueWithVariable;

baseName: NAME | FRAGMENT | QUERY | MUTATION | SUBSCRIPTION | SCHEMA | SCALAR | TYPE | INTERFACE | IMPLEMENTS | ENUM | UNION | INPUT | EXTEND | DIRECTIVE;
baseName: NAME | FRAGMENT | QUERY | MUTATION | SUBSCRIPTION | SCHEMA | SCALAR | TYPE | INTERFACE | IMPLEMENTS | ENUM | UNION | INPUT | EXTEND | DIRECTIVE | REPEATABLE;
fragmentName: baseName | BooleanValue | NullValue;
enumValueName: baseName | ON_KEYWORD;

Expand Down Expand Up @@ -88,6 +88,7 @@ INPUT: 'input';
EXTEND: 'extend';
DIRECTIVE: 'directive';
ON_KEYWORD: 'on';
REPEATABLE: 'repeatable';
NAME: [_A-Za-z][_0-9A-Za-z]*;


Expand Down
2 changes: 1 addition & 1 deletion src/main/antlr/GraphqlSDL.g4
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ inputObjectValueDefinitions : '{' inputValueDefinition* '}';
extensionInputObjectValueDefinitions : '{' inputValueDefinition+ '}';


directiveDefinition : description? DIRECTIVE '@' name argumentsDefinition? 'on' directiveLocations;
directiveDefinition : description? DIRECTIVE '@' name argumentsDefinition? REPEATABLE? ON_KEYWORD directiveLocations;

directiveLocation : name;

Expand Down
1 change: 1 addition & 0 deletions src/main/java/graphql/Directives.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public class Directives {
.build())
.build();
}

public static final GraphQLDirective IncludeDirective = GraphQLDirective.newDirective()
.name("include")
.description("Directs the executor to include this field or fragment only when the `if` argument is true")
Expand Down
137 changes: 127 additions & 10 deletions src/main/java/graphql/DirectivesUtil.java
Original file line number Diff line number Diff line change
@@ -1,35 +1,152 @@
package graphql;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLDirective;
import graphql.util.FpKit;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import static graphql.Assert.assertNotNull;
import static graphql.collect.ImmutableKit.emptyList;

@Internal
public class DirectivesUtil {

public static Map<String, GraphQLDirective> directivesByName(List<GraphQLDirective> directiveList) {
return FpKit.getByName(directiveList, GraphQLDirective::getName, FpKit.mergeFirst());

public static Map<String, GraphQLDirective> nonRepeatableDirectivesByName(List<GraphQLDirective> directives) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nonRepeatableDirectivesByName will travel directives three times. The code below seem to be faster:

    public static Map<String, GraphQLDirective> nonRepeatableDirectivesByName(List<GraphQLDirective> directives) {
        // filter the repeatable directives
        List<GraphQLDirective> singletonDirectives = directives.stream()
                .filter(d -> !d.isRepeatable()).collect(Collectors.toList());
        
        return FpKit.getByName(singletonDirectives, GraphQLDirective::getName);
    }

// filter the repeatable directives
List<GraphQLDirective> singletonDirectives = directives.stream()
.filter(d -> !d.isRepeatable()).collect(Collectors.toList());

return FpKit.getByName(singletonDirectives, GraphQLDirective::getName);
}

public static Optional<GraphQLDirective> directiveByName(List<GraphQLDirective> directives, String directiveName) {
for (GraphQLDirective directive : directives) {
if (directive.getName().equals(directiveName)) {
return Optional.of(directive);
}
public static Map<String, ImmutableList<GraphQLDirective>> allDirectivesByName(List<GraphQLDirective> directives) {

return ImmutableMap.copyOf(FpKit.groupingBy(directives, GraphQLDirective::getName));
}

public static GraphQLDirective nonRepeatedDirectiveByNameWithAssert(Map<String, List<GraphQLDirective>> directives, String directiveName) {
List<GraphQLDirective> directiveList = directives.get(directiveName);
if (directiveList == null || directiveList.isEmpty()) {
return null;
}
return Optional.empty();
Assert.assertTrue(isAllNonRepeatable(directiveList), () -> String.format("'%s' is a repeatable directive and you have used a non repeatable access method", directiveName));
return directiveList.get(0);
}

public static Optional<GraphQLArgument> directiveWithArg(List<GraphQLDirective> directiveList, String directiveName, String argumentName) {
GraphQLDirective directive = directiveByName(directiveList, directiveName).orElse(null);
public static Optional<GraphQLArgument> directiveWithArg(List<GraphQLDirective> directives, String directiveName, String argumentName) {
GraphQLDirective directive = nonRepeatableDirectivesByName(directives).get(directiveName);
GraphQLArgument argument = null;
if (directive != null) {
argument = directive.getArgument(argumentName);
}
return Optional.ofNullable(argument);
}


public static boolean isAllNonRepeatable(List<GraphQLDirective> directives) {
if (directives == null || directives.isEmpty()) {
return false;
}
for (GraphQLDirective graphQLDirective : directives) {
if (graphQLDirective.isRepeatable()) {
return false;
}
}
return true;
}

public static List<GraphQLDirective> enforceAdd(List<GraphQLDirective> targetList, GraphQLDirective newDirective) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this is an more efficient way to implement enforceAdd:

    public static List<GraphQLDirective> enforceAdd(List<GraphQLDirective> targetList, GraphQLDirective newDirective) {
        assertNotNull(targetList, () -> "directive list can't be null");
        assertNotNull(newDirective, () -> "directive can't be null");

        // check whether the newDirective is repeatable in advance, to avoid needless operations
        if(newDirective.isNonRepeatable()){
            Map<String, List<GraphQLDirective>> map = allDirectivesByName(targetList);
            assertNonRepeatable(newDirective, map);
        }
        targetList.add(newDirective);
        return targetList;
    }

assertNotNull(targetList, () -> "directive list can't be null");
assertNotNull(newDirective, () -> "directive can't be null");

// check whether the newDirective is repeatable in advance, to avoid needless operations
if (newDirective.isNonRepeatable()) {
Map<String, ImmutableList<GraphQLDirective>> map = allDirectivesByName(targetList);
assertNonRepeatable(newDirective, map);
}
targetList.add(newDirective);
return targetList;
}

public static List<GraphQLDirective> enforceAddAll(List<GraphQLDirective> targetList, List<GraphQLDirective> newDirectives) {
assertNotNull(targetList, () -> "directive list can't be null");
assertNotNull(newDirectives, () -> "directive list can't be null");
Map<String, ImmutableList<GraphQLDirective>> map = allDirectivesByName(targetList);
for (GraphQLDirective newDirective : newDirectives) {
assertNonRepeatable(newDirective, map);
targetList.add(newDirective);
}
return targetList;
}

private static void assertNonRepeatable(GraphQLDirective directive, Map<String, ImmutableList<GraphQLDirective>> mapOfDirectives) {
if (directive.isNonRepeatable()) {
List<GraphQLDirective> currentDirectives = mapOfDirectives.getOrDefault(directive.getName(), emptyList());
int currentSize = currentDirectives.size();
if (currentSize > 0) {
Assert.assertShouldNeverHappen("%s is a non repeatable directive but there is already one present in this list", directive.getName());
}
}
}

public static GraphQLDirective getFirstDirective(String name, Map<String, List<GraphQLDirective>> allDirectivesByName) {
List<GraphQLDirective> directives = allDirectivesByName.getOrDefault(name, emptyList());
if (directives.isEmpty()) {
return null;
}
return directives.get(0);
}

/**
* A holder class that breaks a list of directives into maps to be more easily accessible in using classes
*/
public static class DirectivesHolder {

private final ImmutableMap<String, List<GraphQLDirective>> allDirectivesByName;
private final ImmutableMap<String, GraphQLDirective> nonRepeatableDirectivesByName;
private final List<GraphQLDirective> allDirectives;

public DirectivesHolder(Collection<GraphQLDirective> allDirectives) {
this.allDirectives = ImmutableList.copyOf(allDirectives);
this.allDirectivesByName = ImmutableMap.copyOf(FpKit.groupingBy(allDirectives, GraphQLDirective::getName));
// filter out the repeatable directives
List<GraphQLDirective> nonRepeatableDirectives = allDirectives.stream()
.filter(d -> !d.isRepeatable()).collect(Collectors.toList());
this.nonRepeatableDirectivesByName = ImmutableMap.copyOf(FpKit.getByName(nonRepeatableDirectives, GraphQLDirective::getName));
}

public ImmutableMap<String, List<GraphQLDirective>> getAllDirectivesByName() {
return allDirectivesByName;
}

public ImmutableMap<String, GraphQLDirective> getDirectivesByName() {
return nonRepeatableDirectivesByName;
}

public List<GraphQLDirective> getDirectives() {
return allDirectives;
}

public GraphQLDirective getDirective(String directiveName) {
List<GraphQLDirective> directiveList = allDirectivesByName.get(directiveName);
if (directiveList == null || directiveList.isEmpty()) {
return null;
}
Assert.assertTrue(isAllNonRepeatable(directiveList), () -> String.format("'%s' is a repeatable directive and you have used a non repeatable access method", directiveName));
return directiveList.get(0);

}

public List<GraphQLDirective> getDirectives(String directiveName) {
return allDirectivesByName.getOrDefault(directiveName, emptyList());
}
}
}
20 changes: 9 additions & 11 deletions src/main/java/graphql/execution/ConditionalNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import graphql.Internal;
import graphql.VisibleForTesting;
import graphql.language.Directive;
import graphql.language.NodeUtil;

import java.util.List;
import java.util.Map;

import static graphql.Directives.IncludeDirective;
import static graphql.Directives.SkipDirective;
import static graphql.language.NodeUtil.directiveByName;
import static graphql.collect.ImmutableKit.emptyList;


@Internal
Expand All @@ -26,23 +27,20 @@ public boolean shouldInclude(Map<String, Object> variables, List<Directive> dire
return !skip && include;
}

private Directive getDirectiveByName(List<Directive> directives, String name) {
if (directives.isEmpty()) {
return null;
}
return directiveByName(directives, name).orElse(null);
}

private boolean getDirectiveResult(Map<String, Object> variables, List<Directive> directives, String directiveName, boolean defaultValue) {
Directive directive = getDirectiveByName(directives, directiveName);
if (directive != null) {
List<Directive> foundDirectives = getDirectiveByName(directives, directiveName);
if (!foundDirectives.isEmpty()) {
Directive directive = foundDirectives.get(0);
Map<String, Object> argumentValues = valuesResolver.getArgumentValues(SkipDirective.getArguments(), directive.getArguments(), variables);
Object flag = argumentValues.get("if");
Assert.assertTrue(flag instanceof Boolean, () -> String.format("The '%s' directive MUST have a value for the 'if' argument", directiveName));
return (Boolean) flag;
}

return defaultValue;
}

private List<Directive> getDirectiveByName(List<Directive> directives, String name) {
return NodeUtil.allDirectivesByName(directives).getOrDefault(name, emptyList());
}

}
8 changes: 6 additions & 2 deletions src/main/java/graphql/introspection/Introspection.java
Original file line number Diff line number Diff line change
Expand Up @@ -435,13 +435,17 @@ public enum DirectiveLocation {
.name("__Directive")
.field(newFieldDefinition()
.name("name")
.type(GraphQLString))
.description("The __Directive type represents a Directive that a server supports.")
.type(nonNull(GraphQLString)))
.field(newFieldDefinition()
.name("description")
.type(GraphQLString))
.field(newFieldDefinition()
.name("isRepeatable")
.type(nonNull(GraphQLBoolean)))
.field(newFieldDefinition()
.name("locations")
.type(list(nonNull(__DirectiveLocation))))
.type(nonNull(list(nonNull(__DirectiveLocation)))))
.field(newFieldDefinition()
.name("args")
.type(nonNull(list(nonNull(__InputValue)))))
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/graphql/language/AstPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ private NodePrinter<DirectiveDefinition> directiveDefinition() {
out.printf("%s", description(node));
String arguments = wrap("(", join(node.getInputValueDefinitions(), argSep), ")");
String locations = join(node.getDirectiveLocations(), " | ");
out.printf("directive @%s%s on %s", node.getName(), arguments, locations);
String repeatable = node.isRepeatable() ? "repeatable " : "";
out.printf("directive @%s%s %son %s", node.getName(), arguments, repeatable, locations);
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/graphql/language/Directive.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public Map<String, Argument> getArgumentsByName() {
}

public Argument getArgument(String argumentName) {
return getArgumentByName(arguments, argumentName).orElse(null);
return NodeUtil.getArgumentByName(arguments, argumentName);
}

@Override
Expand Down
25 changes: 23 additions & 2 deletions src/main/java/graphql/language/DirectiveDefinition.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
@PublicApi
public class DirectiveDefinition extends AbstractDescribedNode<DirectiveDefinition> implements SDLDefinition<DirectiveDefinition>, NamedNode<DirectiveDefinition> {
private final String name;
private final boolean repeatable;
private final ImmutableList<InputValueDefinition> inputValueDefinitions;
private final ImmutableList<DirectiveLocation> directiveLocations;

Expand All @@ -30,6 +31,7 @@ public class DirectiveDefinition extends AbstractDescribedNode<DirectiveDefiniti

@Internal
protected DirectiveDefinition(String name,
boolean repeatable,
Description description,
List<InputValueDefinition> inputValueDefinitions,
List<DirectiveLocation> directiveLocations,
Expand All @@ -39,6 +41,7 @@ protected DirectiveDefinition(String name,
Map<String, String> additionalData) {
super(sourceLocation, comments, ignoredChars, additionalData, description);
this.name = name;
this.repeatable = repeatable;
this.inputValueDefinitions = ImmutableList.copyOf(inputValueDefinitions);
this.directiveLocations = ImmutableList.copyOf(directiveLocations);
}
Expand All @@ -49,14 +52,24 @@ protected DirectiveDefinition(String name,
* @param name of the directive definition
*/
public DirectiveDefinition(String name) {
this(name, null, emptyList(), emptyList(), null, emptyList(), IgnoredChars.EMPTY, emptyMap());
this(name, false, null, emptyList(), emptyList(), null, emptyList(), IgnoredChars.EMPTY, emptyMap());
}

@Override
public String getName() {
return name;
}

/**
* An AST node can have multiple directives associated with it IF the directive definition allows
* repeatable directives.
*
* @return true if this directive definition allows repeatable directives
*/
public boolean isRepeatable() {
return repeatable;
}

public List<InputValueDefinition> getInputValueDefinitions() {
return inputValueDefinitions;
}
Expand Down Expand Up @@ -106,6 +119,7 @@ public boolean isEqualTo(Node o) {
@Override
public DirectiveDefinition deepCopy() {
return new DirectiveDefinition(name,
repeatable,
description,
deepCopy(inputValueDefinitions),
deepCopy(directiveLocations),
Expand Down Expand Up @@ -143,6 +157,7 @@ public static final class Builder implements NodeBuilder {
private SourceLocation sourceLocation;
private List<Comment> comments = new ArrayList<>();
private String name;
private boolean repeatable = false;
Comment thread
andimarek marked this conversation as resolved.
private Description description;
private List<InputValueDefinition> inputValueDefinitions = new ArrayList<>();
private List<DirectiveLocation> directiveLocations = new ArrayList<>();
Expand All @@ -156,6 +171,7 @@ private Builder(DirectiveDefinition existing) {
this.sourceLocation = existing.getSourceLocation();
this.comments = existing.getComments();
this.name = existing.getName();
this.repeatable = existing.isRepeatable();
this.description = existing.getDescription();
this.inputValueDefinitions = existing.getInputValueDefinitions();
this.directiveLocations = existing.getDirectiveLocations();
Expand All @@ -178,6 +194,11 @@ public Builder name(String name) {
return this;
}

public Builder repeatable(boolean repeatable) {
this.repeatable = repeatable;
return this;
}

public Builder description(Description description) {
this.description = description;
return this;
Expand Down Expand Up @@ -220,7 +241,7 @@ public Builder additionalData(String key, String value) {


public DirectiveDefinition build() {
return new DirectiveDefinition(name, description, inputValueDefinitions, directiveLocations, sourceLocation, comments, ignoredChars, additionalData);
return new DirectiveDefinition(name, repeatable, description, inputValueDefinitions, directiveLocations, sourceLocation, comments, ignoredChars, additionalData);
}
}
}
Loading