Skip to content

Commit c4a05f1

Browse files
committed
validate programmatic applied directive values
1 parent 77512e1 commit c4a05f1

7 files changed

Lines changed: 108 additions & 7 deletions

File tree

‎src/main/java/graphql/execution/ValuesResolver.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package graphql.execution;
22

33

4+
import com.google.common.collect.ImmutableList;
45
import graphql.AssertException;
56
import graphql.Internal;
67
import graphql.Scalars;
@@ -219,7 +220,7 @@ private ObjectValue externalValueToLiteralForObject(GraphqlFieldVisibility field
219220
List<GraphQLInputObjectField> fieldDefinitions = fieldVisibility.getFieldDefinitions(inputObjectType);
220221

221222

222-
List<ObjectField> objectFields = new ArrayList<>();
223+
ImmutableList.Builder<ObjectField> objectFields = ImmutableList.builder();
223224
for (GraphQLInputObjectField inputFieldDefinition : fieldDefinitions) {
224225
GraphQLInputType fieldType = inputFieldDefinition.getType();
225226
String fieldName = inputFieldDefinition.getName();
@@ -242,7 +243,7 @@ private ObjectValue externalValueToLiteralForObject(GraphqlFieldVisibility field
242243
// meaning no value was provided for this field
243244
}
244245
}
245-
return ObjectValue.newObjectValue().objectFields(objectFields).build();
246+
return ObjectValue.newObjectValue().objectFields(objectFields.build()).build();
246247
}
247248

248249

‎src/main/java/graphql/parser/Parser.java‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ public static Value<?> parseValue(String input) {
3838
public Document parseDocument(String input) throws InvalidSyntaxException {
3939
return parseDocument(input, null);
4040
}
41-
// public Value<?> parseValue(String input) throws InvalidSyntaxException {
42-
// return parseDocument(input, null);
43-
// }
4441

4542
public Document parseDocument(String input, String sourceName) throws InvalidSyntaxException {
4643
MultiSourceReader multiSourceReader = MultiSourceReader.newMultiSourceReader()
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package graphql.schema.validation;
2+
3+
import graphql.execution.NonNullableValueCoercedAsNullException;
4+
import graphql.execution.ValuesResolver;
5+
import graphql.language.Value;
6+
import graphql.schema.CoercingParseValueException;
7+
import graphql.schema.GraphQLArgument;
8+
import graphql.schema.GraphQLInputType;
9+
import graphql.schema.GraphQLSchema;
10+
import graphql.schema.GraphQLSchemaElement;
11+
import graphql.schema.GraphQLTypeVisitorStub;
12+
import graphql.schema.ValueState;
13+
import graphql.util.TraversalControl;
14+
import graphql.util.TraverserContext;
15+
import graphql.validation.ValidationUtil;
16+
17+
import static graphql.schema.GraphQLTypeUtil.simplePrint;
18+
import static java.lang.String.format;
19+
20+
public class AppliedDirectiveArgumentsAreValid extends GraphQLTypeVisitorStub {
21+
22+
private ValidationUtil validationUtil = new ValidationUtil();
23+
24+
25+
public TraversalControl visitGraphQLArgument(GraphQLArgument argument, TraverserContext<GraphQLSchemaElement> context) {
26+
// a directive argument is represented as GraphQLArgument.value
27+
if (!argument.hasSetValue()) {
28+
return TraversalControl.CONTINUE;
29+
}
30+
GraphQLSchema schema = context.getVarFromParents(GraphQLSchema.class);
31+
SchemaValidationErrorCollector errorCollector = context.getVarFromParents(SchemaValidationErrorCollector.class);
32+
ValueState argumentState = argument.getValueState();
33+
boolean invalid = false;
34+
if (argumentState == ValueState.LITERAL &&
35+
!validationUtil.isValidLiteralValue((Value<?>) argument.getArgumentValue(), argument.getType(), schema)) {
36+
invalid = true;
37+
} else if (argumentState == ValueState.EXTERNAL_VALUE &&
38+
!isValidExternalValue(schema, argument.getArgumentValue(), argument.getType())) {
39+
invalid = true;
40+
}
41+
if (invalid) {
42+
String message = format("Invalid argument %s for type %s for applied directive", argument.getValueState(), simplePrint(argument.getType()));
43+
errorCollector.addError(new SchemaValidationError(SchemaValidationErrorType.InvalidAppliedDirectiveArgument, message));
44+
}
45+
return TraversalControl.CONTINUE;
46+
}
47+
48+
private boolean isValidExternalValue(GraphQLSchema schema, Object externalValue, GraphQLInputType type) {
49+
try {
50+
ValuesResolver.externalValueToInternalValue(schema.getCodeRegistry().getFieldVisibility(), externalValue, type);
51+
return true;
52+
} catch (CoercingParseValueException | NonNullableValueCoercedAsNullException e) {
53+
return false;
54+
}
55+
}
56+
}

‎src/main/java/graphql/schema/validation/SchemaValidationErrorType.java‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public enum SchemaValidationErrorType {
1515
InvalidCustomizedNameError,
1616
NonNullWrapNonNullError,
1717
RepetitiveElementError,
18-
InvalidDefaultValue
18+
InvalidDefaultValue,
19+
InvalidAppliedDirectiveArgument
1920

2021
}

‎src/main/java/graphql/schema/validation/SchemaValidator.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public SchemaValidator() {
2222
rules.add(new TypesImplementInterfaces());
2323
rules.add(new TypeAndFieldRule());
2424
rules.add(new DefaultValuesAreValid());
25+
rules.add(new AppliedDirectiveArgumentsAreValid());
2526
}
2627

2728
public List<GraphQLTypeVisitor> getRules() {

‎src/test/groovy/graphql/GraphQLTest.groovy‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package graphql
22

33
import graphql.analysis.MaxQueryComplexityInstrumentation
44
import graphql.analysis.MaxQueryDepthInstrumentation
5+
import graphql.collect.ImmutableKit
56
import graphql.execution.AsyncExecutionStrategy
67
import graphql.execution.DataFetcherExceptionHandler
78
import graphql.execution.DataFetcherResult
@@ -18,13 +19,16 @@ import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrume
1819
import graphql.language.SourceLocation
1920
import graphql.schema.DataFetcher
2021
import graphql.schema.DataFetchingEnvironment
22+
import graphql.schema.GraphQLDirective
2123
import graphql.schema.GraphQLEnumType
2224
import graphql.schema.GraphQLFieldDefinition
2325
import graphql.schema.GraphQLInterfaceType
2426
import graphql.schema.GraphQLNonNull
2527
import graphql.schema.GraphQLObjectType
2628
import graphql.schema.GraphQLSchema
2729
import graphql.schema.StaticDataFetcher
30+
import graphql.schema.idl.SchemaGenerator
31+
import graphql.schema.idl.errors.SchemaProblem
2832
import graphql.schema.validation.InvalidSchemaException
2933
import graphql.validation.ValidationError
3034
import graphql.validation.ValidationErrorType
@@ -1313,4 +1317,44 @@ many lines''']
13131317
def e = thrown(InvalidSchemaException)
13141318
e.message.contains("Invalid default value")
13151319
}
1320+
1321+
def "Applied schema directives arguments are validated for SDL"() {
1322+
given:
1323+
def sdl = '''
1324+
directive @cached(
1325+
key: String
1326+
) on FIELD_DEFINITION
1327+
1328+
type Query {
1329+
hello: String @cached(key: {foo: "bar"})
1330+
}
1331+
'''
1332+
when:
1333+
SchemaGenerator.createdMockedSchema(sdl)
1334+
then:
1335+
def e = thrown(SchemaProblem)
1336+
e.message.contains("an illegal value for the argument ")
1337+
}
1338+
1339+
def "Applied schema directives arguments are validated for programmatic schemas"() {
1340+
given:
1341+
def arg = newArgument().name("arg").type(GraphQLInt).valueProgrammatic(ImmutableKit.emptyMap()).build()
1342+
def directive = GraphQLDirective.newDirective().name("cached").argument(arg).build()
1343+
def field = newFieldDefinition()
1344+
.name("hello")
1345+
.type(GraphQLString)
1346+
.argument(arg)
1347+
.withDirective(directive)
1348+
.build()
1349+
when:
1350+
newSchema().query(
1351+
newObject()
1352+
.name("Query")
1353+
.field(field)
1354+
.build())
1355+
.build()
1356+
then:
1357+
def e = thrown(InvalidSchemaException)
1358+
e.message.contains("Invalid argument")
1359+
}
13161360
}

‎src/test/groovy/graphql/schema/validation/SchemaValidatorTest.groovy‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ class SchemaValidatorTest extends Specification {
1111
def validator = new SchemaValidator()
1212
def rules = validator.rules
1313
then:
14-
rules.size() == 4
14+
rules.size() == 5
1515
rules[0] instanceof NoUnbrokenInputCycles
1616
rules[1] instanceof TypesImplementInterfaces
1717
rules[2] instanceof TypeAndFieldRule
1818
rules[3] instanceof DefaultValuesAreValid
19+
rules[4] instanceof AppliedDirectiveArgumentsAreValid
1920
}
2021

2122
}

0 commit comments

Comments
 (0)