Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0dae6b5
A start on applied directives
bbakerman Sep 26, 2021
aca111e
More work on getting applied schemas duplicated
bbakerman Oct 4, 2021
7eafd24
Now with T extends T
bbakerman Oct 4, 2021
f00e244
Renamed to directivesHolder and also fixed up and extra SchemaElement
bbakerman Oct 4, 2021
8512b34
Merge remote-tracking branch 'origin/master' into applied_directives
bbakerman Dec 6, 2021
c9df4d0
Put back in the ease of use builder functions
bbakerman Dec 6, 2021
e3dd53b
Ooops - didnt know my own clever work
bbakerman Dec 6, 2021
975c8d2
Small tweak
bbakerman Dec 6, 2021
51fdc32
Merge remote-tracking branch 'origin/master' into applied_directives
bbakerman Dec 6, 2021
f17ba1c
SchemaGenerator work on getting applied directives in place
bbakerman Dec 6, 2021
fc843de
Added SchemaGeneratorAppliedDirectiveHelper.java to split code
bbakerman Dec 6, 2021
db2c7c2
Amde the applied directive to be consistent wirth GraphQLArgument
bbakerman Dec 7, 2021
b645ea7
more tests
bbakerman Dec 7, 2021
986cb86
tests in directive wiring
bbakerman Dec 7, 2021
e2cc582
Test for schema visiting and transform - not passing
bbakerman Dec 7, 2021
ae520e6
More work on schema printing
bbakerman Dec 9, 2021
e5f057b
More work on introspection
bbakerman Dec 10, 2021
39513a8
Added Anonymizer support
bbakerman Dec 12, 2021
b7377c8
Added QueryDirectives support
bbakerman Dec 13, 2021
00f6c09
Merge remote-tracking branch 'origin/master' into applied_directives
bbakerman Feb 1, 2022
033d796
Merge remote-tracking branch 'origin/master' into applied_directives
bbakerman Feb 16, 2022
e69cbd4
Renamed GraphQLAppliedArgument to GraphQLAppliedDirectiveArgument
bbakerman Feb 17, 2022
a6dd683
Put in deprecation notices
bbakerman Feb 17, 2022
ef4db61
Query applied directives added instead of re-using the scheme element…
bbakerman Feb 17, 2022
ade00b9
Removing schema repeated list methods on getDirectives - directive de…
bbakerman Feb 17, 2022
5a551be
deprecated value methods
bbakerman Feb 17, 2022
b83014b
Fix ups based on Andis final review
bbakerman Feb 18, 2022
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
84 changes: 82 additions & 2 deletions src/main/java/graphql/DirectivesUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@

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

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

import static graphql.Assert.assertNotNull;
import static graphql.collect.ImmutableKit.emptyList;
import static java.util.stream.Collectors.toSet;

@Internal
public class DirectivesUtil {


@Deprecated // use GraphQLAppliedDirectives eventually
public static Map<String, GraphQLDirective> nonRepeatableDirectivesByName(List<GraphQLDirective> directives) {
// filter the repeatable directives
List<GraphQLDirective> singletonDirectives = directives.stream()
Expand All @@ -27,11 +33,13 @@ public static Map<String, GraphQLDirective> nonRepeatableDirectivesByName(List<G
return FpKit.getByName(singletonDirectives, GraphQLDirective::getName);
}

@Deprecated // use GraphQLAppliedDirectives eventually
public static Map<String, ImmutableList<GraphQLDirective>> allDirectivesByName(List<GraphQLDirective> directives) {

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

@Deprecated // use GraphQLAppliedDirectives eventually
public static Optional<GraphQLArgument> directiveWithArg(List<GraphQLDirective> directives, String directiveName, String argumentName) {
GraphQLDirective directive = nonRepeatableDirectivesByName(directives).get(directiveName);
GraphQLArgument argument = null;
Expand All @@ -42,6 +50,7 @@ public static Optional<GraphQLArgument> directiveWithArg(List<GraphQLDirective>
}


@Deprecated // use GraphQLAppliedDirectives eventually
public static boolean isAllNonRepeatable(List<GraphQLDirective> directives) {
if (directives == null || directives.isEmpty()) {
return false;
Expand All @@ -54,20 +63,23 @@ public static boolean isAllNonRepeatable(List<GraphQLDirective> directives) {
return true;
}

@Deprecated // use GraphQLAppliedDirectives eventually
public static List<GraphQLDirective> add(List<GraphQLDirective> targetList, GraphQLDirective newDirective) {
assertNotNull(targetList, () -> "directive list can't be null");
assertNotNull(newDirective, () -> "directive can't be null");
targetList.add(newDirective);
return targetList;
}

@Deprecated // use GraphQLAppliedDirectives eventually
public static List<GraphQLDirective> addAll(List<GraphQLDirective> targetList, List<GraphQLDirective> newDirectives) {
assertNotNull(targetList, () -> "directive list can't be null");
assertNotNull(newDirectives, () -> "directive list can't be null");
targetList.addAll(newDirectives);
return targetList;
}

@Deprecated // use GraphQLAppliedDirectives eventually
public static GraphQLDirective getFirstDirective(String name, Map<String, List<GraphQLDirective>> allDirectivesByName) {
List<GraphQLDirective> directives = allDirectivesByName.getOrDefault(name, emptyList());
if (directives.isEmpty()) {
Expand All @@ -76,6 +88,43 @@ public static GraphQLDirective getFirstDirective(String name, Map<String, List<G
return directives.get(0);
}

/**
* This can take a collection of legacy directives and turn them applied directives, and combine them with any applied directives. The applied
* directives collection takes precedence.
*
* @param directiveContainer the schema element holding applied directives
*
* @return a combined list unique by name
*/
public static List<GraphQLAppliedDirective> toAppliedDirectives(GraphQLDirectiveContainer directiveContainer) {
return toAppliedDirectives(directiveContainer.getAppliedDirectives(), directiveContainer.getDirectives());
}

/**
* This can take a collection of legacy directives and turn them applied directives, and combine them with any applied directives. The applied
* directives collection takes precedence.
*
* @param appliedDirectives the applied directives to use
* @param directives the legacy directives to use
*
* @return a combined list unique by name
*/
public static List<GraphQLAppliedDirective> toAppliedDirectives(Collection<GraphQLAppliedDirective> appliedDirectives, Collection<GraphQLDirective> directives) {
Set<String> named = appliedDirectives.stream()
.map(GraphQLAppliedDirective::getName).collect(toSet());

ImmutableList.Builder<GraphQLAppliedDirective> list = ImmutableList.<GraphQLAppliedDirective>builder()
.addAll(appliedDirectives);
// we only put in legacy directives if the list does not already contain them. We need this mechanism
// (and not a map) because of repeated directives
directives.forEach(directive -> {
if (!named.contains(directive.getName())) {
list.add(directive.toAppliedDirective());
}
});
return list.build();
}

/**
* A holder class that breaks a list of directives into maps to be more easily accessible in using classes
*/
Expand All @@ -85,13 +134,20 @@ public static class DirectivesHolder {
private final ImmutableMap<String, GraphQLDirective> nonRepeatableDirectivesByName;
private final List<GraphQLDirective> allDirectives;

public DirectivesHolder(Collection<GraphQLDirective> allDirectives) {
private final ImmutableMap<String, List<GraphQLAppliedDirective>> allAppliedDirectivesByName;
private final List<GraphQLAppliedDirective> allAppliedDirectives;

public DirectivesHolder(Collection<GraphQLDirective> allDirectives, Collection<GraphQLAppliedDirective> allAppliedDirectives) {
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));

this.allAppliedDirectives = ImmutableList.copyOf(allAppliedDirectives);
this.allAppliedDirectivesByName = ImmutableMap.copyOf(FpKit.groupingBy(allAppliedDirectives, GraphQLAppliedDirective::getName));

}

public ImmutableMap<String, List<GraphQLDirective>> getAllDirectivesByName() {
Expand All @@ -112,11 +168,35 @@ public GraphQLDirective getDirective(String directiveName) {
return null;
}
return directiveList.get(0);

}

public List<GraphQLDirective> getDirectives(String directiveName) {
return allDirectivesByName.getOrDefault(directiveName, emptyList());
}

public ImmutableMap<String, List<GraphQLAppliedDirective>> getAllAppliedDirectivesByName() {
return allAppliedDirectivesByName;
}

public List<GraphQLAppliedDirective> getAppliedDirectives() {
return allAppliedDirectives;
}

public List<GraphQLAppliedDirective> getAppliedDirectives(String directiveName) {
return allAppliedDirectivesByName.getOrDefault(directiveName, emptyList());
}

public GraphQLAppliedDirective getAppliedDirective(String directiveName) {
List<GraphQLAppliedDirective> list = allAppliedDirectivesByName.getOrDefault(directiveName, emptyList());
return list.isEmpty() ? null : list.get(0);
}

@Override
public String toString() {
return new StringJoiner(", ", DirectivesHolder.class.getSimpleName() + "[", "]")
.add("allDirectivesByName=" + String.join(",", allDirectivesByName.keySet()))
.add("allAppliedDirectivesByName=" + String.join(",", allAppliedDirectivesByName.keySet()))
.toString();
}
}
}
190 changes: 190 additions & 0 deletions src/main/java/graphql/execution/directives/QueryAppliedDirective.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package graphql.execution.directives;


import com.google.common.collect.ImmutableList;
import graphql.PublicApi;
import graphql.language.Directive;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLDirective;
import graphql.schema.GraphqlTypeBuilder;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertValidName;
import static graphql.util.FpKit.getByName;

/**
* An applied directive represents the instance of a directive that is applied to a query element such as a field or fragment.
* <p>
* Originally graphql-java re-used the {@link GraphQLDirective} and {@link GraphQLArgument}
* classes to do both purposes. This was a modelling mistake. New {@link QueryAppliedDirective} and {@link QueryAppliedDirectiveArgument}
* classes have been introduced to better model when a directive is applied to a query element,
* as opposed to its schema definition itself.
* <p>
* See http://graphql.org/learn/queries/#directives for more details on the concept.
*/
@PublicApi
public class QueryAppliedDirective {

private final String name;
private final ImmutableList<QueryAppliedDirectiveArgument> arguments;
private final Directive definition;

private QueryAppliedDirective(String name, Directive definition, Collection<QueryAppliedDirectiveArgument> arguments) {
assertValidName(name);
assertNotNull(arguments, () -> "arguments can't be null");
this.name = name;
this.arguments = ImmutableList.copyOf(arguments);
this.definition = definition;
}

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

@Nullable
public String getDescription() {
return null;
}

public List<QueryAppliedDirectiveArgument> getArguments() {
return arguments;
}

@Nullable
public QueryAppliedDirectiveArgument getArgument(String name) {
for (QueryAppliedDirectiveArgument argument : arguments) {
if (argument.getName().equals(name)) {
return argument;
}
}
return null;
}

@Nullable
public Directive getDefinition() {
return definition;
}

@Override
public String toString() {
return new StringJoiner(", ", QueryAppliedDirective.class.getSimpleName() + "[", "]")
.add("name='" + name + "'")
.add("arguments=" + arguments)
.add("definition=" + definition)
.toString();
}

/**
* This helps you transform the current GraphQLDirective 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 field based on calling build on that builder
*/
public QueryAppliedDirective transform(Consumer<Builder> builderConsumer) {
Builder builder = newDirective(this);
builderConsumer.accept(builder);
return builder.build();
}

public static Builder newDirective() {
return new Builder();
}

public static Builder newDirective(QueryAppliedDirective existing) {
return new Builder(existing);
}

public static class Builder extends GraphqlTypeBuilder<Builder> {

private final Map<String, QueryAppliedDirectiveArgument> arguments = new LinkedHashMap<>();
private Directive definition;

public Builder() {
}

public Builder(QueryAppliedDirective existing) {
this.name = existing.getName();
this.description = existing.getDescription();
this.arguments.putAll(getByName(existing.getArguments(), QueryAppliedDirectiveArgument::getName));
}

public Builder argument(QueryAppliedDirectiveArgument argument) {
assertNotNull(argument, () -> "argument must not be null");
arguments.put(argument.getName(), argument);
return this;
}

public Builder replaceArguments(List<QueryAppliedDirectiveArgument> arguments) {
assertNotNull(arguments, () -> "arguments must not be null");
this.arguments.clear();
for (QueryAppliedDirectiveArgument argument : arguments) {
this.arguments.put(argument.getName(), argument);
}
return this;
}

/**
* Take an argument builder in a function definition and apply. Can be used in a jdk8 lambda
* e.g.:
* <pre>
* {@code
* argument(a -> a.name("argumentName"))
* }
* </pre>
*
* @param builderFunction a supplier for the builder impl
*
* @return this
*/
public Builder argument(UnaryOperator<QueryAppliedDirectiveArgument.Builder> builderFunction) {
QueryAppliedDirectiveArgument.Builder builder = QueryAppliedDirectiveArgument.newArgument();
builder = builderFunction.apply(builder);
return argument(builder);
}

/**
* Same effect as the argument(GraphQLAppliedDirectiveArgument). Builder.build() is called
* from within
*
* @param builder an un-built/incomplete GraphQLAppliedDirectiveArgument
*
* @return this
*/
public Builder argument(QueryAppliedDirectiveArgument.Builder builder) {
return argument(builder.build());
}

/**
* This is used to clear all the arguments in the builder so far.
*
* @return the builder
*/
public Builder clearArguments() {
arguments.clear();
return this;
}


public Builder definition(Directive definition) {
this.definition = definition;
return this;
}

public QueryAppliedDirective build() {
return new QueryAppliedDirective(name, this.definition, arguments.values());
}
}
}
Loading