8000 introspection was missing some deprecation filtering support by bbakerman · Pull Request #2825 · graphql-java/graphql-java · GitHub
[go: up one dir, main page]

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
17 changes: 14 additions & 3 deletions src/main/java/graphql/introspection/Introspection.java
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,11 @@ private static String printDefaultValue(InputValueWithState inputValueWithState,
static {
register(__Field, "args", environment -> {
Object type = environment.getSource();
return ((GraphQLFieldDefinition) type).getArguments();
GraphQLFieldDefinition fieldDef = (GraphQLFieldDefinition) type;
Boolean includeDeprecated = environment.getArgument("includeDeprecated");
return fieldDef.getArguments().stream()
.filter(arg -> includeDeprecated || !arg.isDeprecated())
.collect(Collectors.toList());
});
register(__Field, "isDeprecated", environment -> {
Object type = environment.getSource();
Expand Down Expand Up @@ -477,7 +481,11 @@ public enum DirectiveLocation {
.type(nonNull(list(nonNull(__DirectiveLocation)))))
.field(newFieldDefinition()
.name("args")
.type(nonNull(list(nonNull(__InputValue)))))
.type(nonNull(list(nonNull(__InputValue))))
.argument(newArgument()
.name("includeDeprecated")
.type(GraphQLBoolean)
.defaultValueProgrammatic(false)))
.field(newFieldDefinition()
.name("onOperation")
.type(GraphQLBoolean)
Expand All @@ -499,7 +507,10 @@ public enum DirectiveLocation {
});
register(__Directive, "args", environment -> {
GraphQLDirective directive = environment.getSource();
return directive.getArguments();
Boolean includeDeprecated = environment.getArgument("includeDeprecated");
return directive.getArguments().stream()
.filter(arg -> includeDeprecated || !arg.isDeprecated())
.collect(Collectors.toList());
});
register(__Directive, "name", nameDataFetcher);
register(__Directive, "description", descriptionDataFetcher);
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/graphql/introspection/IntrospectionQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public interface IntrospectionQuery {
" name\n" +
" description\n" +
" locations\n" +
" args {\n" +
" args(includeDeprecated: true) {\n" +
" ...InputValue\n" +
" }\n" +
" isRepeatable\n" +
Expand All @@ -33,7 +33,7 @@ public interface IntrospectionQuery {
" fields(includeDeprecated: true) {\n" +
" name\n" +
" description\n" +
" args {\n" +
" args(includeDeprecated: true) {\n" +
" ...InputValue\n" +
" }\n" +
" type {\n" +
Expand Down
10BC0
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
import graphql.language.Directive;
import graphql.language.DirectiveDefinition;
import graphql.language.InputValueDefinition;
import graphql.language.StringValue;
import graphql.language.Type;
import graphql.language.Value;
import graphql.schema.GraphQLAppliedDirectiveArgument;
import graphql.schema.GraphQLAppliedDirective;
import graphql.schema.GraphQLAppliedDirectiveArgument;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLDirective;
import graphql.schema.GraphQLInputType;
Expand All @@ -25,10 +26,13 @@
import java.util.Set;
import java.util.function.Function;

import static graphql.Directives.NO_LONGER_SUPPORTED;
import static graphql.collect.ImmutableKit.emptyList;
import static graphql.collect.ImmutableKit.map;
import static graphql.introspection.Introspection.DirectiveLocation.ARGUMENT_DEFINITION;
import static graphql.schema.idl.SchemaGeneratorHelper.buildDescription;
import static graphql.util.Pair.pair;
import static java.util.stream.Collectors.toMap;

/**
* This contains helper code to build out appliedm directives on schema element
Expand Down Expand Up @@ -244,18 +248,50 @@ private static List<Introspection.DirectiveLocation> buildLocations(DirectiveDef
dl -> Introspection.DirectiveLocation.valueOf(dl.getName().toUpperCase()));
}

static GraphQLArgument buildDirectiveArgumentDefinitionFromAst(SchemaGeneratorHelper.BuildContext buildCtx, InputValueDefinition arg, Function<Type<?>, GraphQLInputType> inputTypeFactory) {
GraphQLArgument.Builder builder = GraphQLArgument.newArgument()
.name(arg.getName())
.definition(buildCtx.isCaptureAstDefinitions() ? arg : null);
static GraphQLArgument buildDirectiveArgumentDefinitionFromAst(SchemaGeneratorHelper.BuildContext buildCtx, InputValueDefinition valueDefinition, Function<Type<?>, GraphQLInputType> inputTypeFactory) {
GraphQLArgument.Builder builder = GraphQLArgument.newArgument();
builder.definition(buildCtx.isCaptureAstDefinitions() ? valueDefinition : null);
builder.name(valueDefinition.getName());
builder.description(buildDescription(buildCtx, valueDefinition, valueDefinition.getDescription()));
builder.deprecate(buildDeprecationReason(valueDefinition.getDirectives()));
builder.comparatorRegistry(buildCtx.getComparatorRegistry());

GraphQLInputType inputType = inputTypeFactory.apply(arg.getType());
GraphQLInputType inputType = inputTypeFactory.apply(valueDefinition.getType());
builder.type(inputType);
if (arg.getDefaultValue() != null) {
builder.valueLiteral(arg.getDefaultValue());
builder.defaultValueLiteral(arg.getDefaultValue());
if (valueDefinition.getDefaultValue() != null) {
builder.valueLiteral(valueDefinition.getDefaultValue());
builder.defaultValueLiteral(valueDefinition.getDefaultValue());
}
builder.description(buildDescription(buildCtx, arg, arg.getDescription()));

Pair<List<GraphQLDirective>, List<GraphQLAppliedDirective>> appliedDirectives = buildAppliedDirectives(
buildCtx,
inputTypeFactory,
valueDefinition.getDirectives(),
emptyList(),
ARGUMENT_DEFINITION,
buildCtx.getDirectives(),
buildCtx.getComparatorRegistry());
buildAppliedDirectives(buildCtx, builder, appliedDirectives);

return builder.build();
}


static String buildDeprecationReason(List<Directive> directives) {
directives = Optional.ofNullable(directives).orElse(emptyList());
Optional<Directive> directive = directives.stream().filter(d -> "deprecated".equals(d.getName())).findFirst();
if (directive.isPresent()) {
Map<String, String> args = directive.get().getArguments().stream().collect(toMap(
Argument::getName, arg -> ((StringValue) arg.getValue()).getValue()
));
if (args.isEmpty()) {
return NO_LONGER_SUPPORTED; // default value from spec
} else {
// pre flight checks have ensured its valid
return args.get("reason");
}
}
return null;
}

}
6 changes: 2 additions & 4 deletions src/main/java/graphql/schema/idl/SchemaGeneratorHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@

import static graphql.Assert.assertNotNull;
import static graphql.Directives.DEPRECATED_DIRECTIVE_DEFINITION;
import static graphql.Directives.NO_LONGER_SUPPORTED;
import static graphql.Directives.SPECIFIED_BY_DIRECTIVE_DEFINITION;
import static graphql.Directives.SpecifiedByDirective;
import static graphql.collect.ImmutableKit.emptyList;
Expand All @@ -95,6 +96,7 @@
import static graphql.schema.GraphQLEnumValueDefinition.newEnumValueDefinition;
import static graphql.schema.GraphQLTypeReference.typeRef;
import static graphql.schema.idl.SchemaGeneratorAppliedDirectiveHelper.buildAp 3B0C pliedDirectives;
import static graphql.schema.idl.SchemaGeneratorAppliedDirectiveHelper.buildDeprecationReason;
import static graphql.schema.idl.SchemaGeneratorAppliedDirectiveHelper.buildDirectiveDefinitionFromAst;
import static java.lang.String.format;
import static java.util.stream.Collectors.toMap;
Expand Down Expand Up @@ -209,8 +211,6 @@ public boolean isCaptureAstDefinitions() {
}
}

static final String NO_LONGER_SUPPORTED = "No longer supported";

static String buildDescription(BuildContext buildContext, Node<?> node, Description description) {
if (description != null) {
return description.getContent();
Expand Down Expand Up @@ -330,8 +330,6 @@ private GraphQLInputObjectField buildInputField(BuildContext buildCtx, InputValu
fieldBuilder.deprecate(buildDeprecationReason(fieldDef.getDirectives()));
fieldBuilder.comparatorRegistry(buildCtx.getComparatorRegistry());

// currently the spec doesnt allow deprecations on InputValueDefinitions but it should!
//fieldBuilder.deprecate(buildDeprecationReason(fieldDef.getDirectives()));
GraphQLInputType inputType = buildInputType(buildCtx, fieldDef.getType());
fieldBuilder.type(inputType);
Value<?> defaultValue = fieldDef.getDefaultValue();
Expand Down
109 changes: 105 additions & 4 deletions src/test/groovy/graphql/introspection/IntrospectionTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ class IntrospectionTest extends Specification {

def "introspection for deprecated support"() {
def spec = '''

directive @someDirective(
deprecatedArg : String @deprecated
notDeprecatedArg : String
) on FIELD

type Query {
namedField(arg : InputType @deprecated ) : Enum @deprecated
notDeprecated(arg : InputType) : Enum
Expand All @@ -172,10 +178,10 @@ class IntrospectionTest extends Specification {

def types = executionResult.data['__schema']['types'] as List
def queryType = types.find { it['name'] == 'Query' }
def namedField = (queryType['fields'] as List).find({ it["name"] == "namedField"})
def namedField = (queryType['fields'] as List).find({ it["name"] == "namedField" })
namedField["isDeprecated"]

def notDeprecatedField = (queryType['fields'] as List).find({ it["name"] == "notDeprecated"})
def notDeprecatedField = (queryType['fields'] as List).find({ it["name"] == "notDeprecated" })
!notDeprecatedField["isDeprecated"]
notDeprecatedField["deprecationReason"] == null

Expand All @@ -189,13 +195,108 @@ class IntrospectionTest extends Specification {
inputField["isDeprecated"]
inputField["deprecationReason"] == "No longer supported"

def argument = (namedField["args"] as List).find({ it["name"] == "arg"})
def argument = (namedField["args"] as List).find({ it["name"] == "arg" })
argument["isDeprecated"]
argument["deprecationReason"] == "No longer supported"

def argument2 = (notDeprecatedField["args"] as List).find({ it["name"] == "arg"})
def argument2 = (notDeprecatedField["args"] as List).find({ it["name"] == "arg" })
!argument2["isDeprecated"]
argument2["deprecationReason"] == null


def directives = executionResult.data['__schema']['directives'] as List
def directive = direct 3B0C ives.find { it['name'] == "someDirective" }

def directiveArgs = directive["args"] as List
directiveArgs.collect({ it["name"] }).sort() == ["deprecatedArg", "notDeprecatedArg"]

def dArgument = directiveArgs.find({ it["name"] == "deprecatedArg" })
dArgument["isDeprecated"]
dArgument["deprecationReason"] == "No longer supported"

}

def "can filter out deprecated things in introspection"() {

def spec = '''

directive @someDirective(
deprecatedArg : String @deprecated
notDeprecatedArg : String
) on FIELD

type Query {
namedField(arg : InputType @deprecated, notDeprecatedArg : InputType ) : Enum @deprecated
notDeprecated(arg : InputType @deprecated, notDeprecatedArg : InputType) : Enum
}
enum Enum {
RED @deprecated
BLUE
}
input InputType {
inputField : String @deprecated
notDeprecatedInputField : String
}
'''

def graphQL = TestUtil.graphQL(spec).build()

when: "we dont include deprecated things"
def introspectionQueryWithoutDeprecated = IntrospectionQuery.INTROSPECTION_QUERY.replace("includeDeprecated: true", "includeDeprecated: false")

def executionResult = graphQL.execute(introspectionQueryWithoutDeprecated)

then:
executionResult.errors.isEmpty()

def types = executionResult.data['__schema']['types'] as List
def queryType = types.find { it['name'] == 'Query' }
def fields = (queryType['fields'] as List)
fields.size() == 1

def notDeprecatedField = fields[0]
notDeprecatedField["name"] == "notDeprecated"

def fieldArgs = notDeprecatedField["args"] as List
fieldArgs.size() == 1
fieldArgs[0]["name"] == "notDeprecatedArg"

def enumType = types.find { it['name'] == 'Enum' }
def enumValues = (enumType['enumValues'] as List)
enumValues.size() == 1
enumValues[0]["name"] == "BLUE"

def inputType = types.find { it['name'] == 'InputType' }
def inputFields = (inputType['inputFields'] as List)
inputFields.size() == 1
inputFields[0]["name"] == "notDeprecatedInputField"

when: "we DO include deprecated things"
executionResult = graphQL.execute(IntrospectionQuery.INTROSPECTION_QUERY)

types = executionResult.data['__schema']['types'] as List
queryType = types.find { it['name'] == 'Query' }
fields = (queryType['fields'] as List)

notDeprecatedField = fields.find { it["name"] == "notDeprecated" }
fieldArgs = notDeprecatedField["args"] as List

enumType = types.find { it['name'] == 'Enum' }
enumValues = (enumType['enumValues'] as List)

inputType = types.find { it['name'] == 'InputType' }
inputFields = (inputType['inputFields'] as List)

then:
executionResult.errors.isEmpty()

fields.size() == 2

fieldArgs.size() == 2

enumValues.size() == 2

inputFields.size() == 2
}

def "can change data fetchers for introspection types"() {
Expand Down
0