Skip to content

Commit dc27ed1

Browse files
committed
Merge remote-tracking branch 'origin/master' into oneOf_support_branch
2 parents 8e8e8ef + ba589a8 commit dc27ed1

File tree

9 files changed

+337
-42
lines changed

9 files changed

+337
-42
lines changed

src/main/java/graphql/validation/TraversalContext.java

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import graphql.schema.GraphQLType;
3333
import graphql.schema.GraphQLUnionType;
3434
import graphql.schema.GraphQLUnmodifiedType;
35+
import graphql.schema.InputValueWithState;
3536

3637
import java.util.ArrayList;
3738
import java.util.List;
@@ -47,6 +48,7 @@ public class TraversalContext implements DocumentVisitor {
4748
private final List<GraphQLOutputType> outputTypeStack = new ArrayList<>();
4849
private final List<GraphQLCompositeType> parentTypeStack = new ArrayList<>();
4950
private final List<GraphQLInputType> inputTypeStack = new ArrayList<>();
51+
private final List<InputValueWithState> defaultValueStack = new ArrayList<>();
5052
private final List<GraphQLFieldDefinition> fieldDefStack = new ArrayList<>();
5153
private final List<String> nameStack = new ArrayList<>();
5254
private GraphQLDirective directive;
@@ -155,6 +157,7 @@ private void enterImpl(Argument argument) {
155157
}
156158

157159
addInputType(argumentType != null ? argumentType.getType() : null);
160+
addDefaultValue(argumentType != null ? argumentType.getArgumentDefaultValue() : null);
158161
this.argument = argumentType;
159162
}
160163

@@ -165,23 +168,30 @@ private void enterImpl(ArrayValue arrayValue) {
165168
inputType = (GraphQLInputType) unwrapOne(nullableType);
166169
}
167170
addInputType(inputType);
171+
// List positions never have a default value. See graphql-js impl for inspiration
172+
addDefaultValue(null);
168173
}
169174

170175
private void enterImpl(ObjectField objectField) {
171176
GraphQLUnmodifiedType objectType = unwrapAll(getInputType());
172177
GraphQLInputType inputType = null;
178+
GraphQLInputObjectField inputField = null;
173179
if (objectType instanceof GraphQLInputObjectType) {
174180
GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) objectType;
175-
GraphQLInputObjectField inputField = schema.getFieldVisibility().getFieldDefinition(inputObjectType, objectField.getName());
176-
if (inputField != null)
181+
inputField = schema.getFieldVisibility().getFieldDefinition(inputObjectType, objectField.getName());
182+
if (inputField != null) {
177183
inputType = inputField.getType();
184+
}
178185
}
179186
addInputType(inputType);
187+
addDefaultValue(inputField != null ? inputField.getInputFieldDefaultValue() : null);
180188
}
181189

182190
private GraphQLArgument find(List<GraphQLArgument> arguments, String name) {
183191
for (GraphQLArgument argument : arguments) {
184-
if (argument.getName().equals(name)) return argument;
192+
if (argument.getName().equals(name)) {
193+
return argument;
194+
}
185195
}
186196
return null;
187197
}
@@ -190,29 +200,32 @@ private GraphQLArgument find(List<GraphQLArgument> arguments, String name) {
190200
@Override
191201
public void leave(Node node, List<Node> ancestors) {
192202
if (node instanceof OperationDefinition) {
193-
outputTypeStack.remove(outputTypeStack.size() - 1);
203+
pop(outputTypeStack);
194204
} else if (node instanceof SelectionSet) {
195-
parentTypeStack.remove(parentTypeStack.size() - 1);
205+
pop(parentTypeStack);
196206
} else if (node instanceof Field) {
197207
leaveName(((Field) node).getName());
198-
fieldDefStack.remove(fieldDefStack.size() - 1);
199-
outputTypeStack.remove(outputTypeStack.size() - 1);
208+
pop(fieldDefStack);
209+
pop(outputTypeStack);
200210
} else if (node instanceof Directive) {
201211
directive = null;
202212
} else if (node instanceof InlineFragment) {
203-
outputTypeStack.remove(outputTypeStack.size() - 1);
213+
pop(outputTypeStack);
204214
} else if (node instanceof FragmentDefinition) {
205215
leaveName(((FragmentDefinition) node).getName());
206-
outputTypeStack.remove(outputTypeStack.size() - 1);
216+
pop(outputTypeStack);
207217
} else if (node instanceof VariableDefinition) {
208218
inputTypeStack.remove(inputTypeStack.size() - 1);
209219
} else if (node instanceof Argument) {
210220
argument = null;
211-
inputTypeStack.remove(inputTypeStack.size() - 1);
221+
pop(inputTypeStack);
222+
pop(defaultValueStack);
212223
} else if (node instanceof ArrayValue) {
213-
inputTypeStack.remove(inputTypeStack.size() - 1);
224+
pop(inputTypeStack);
225+
pop(defaultValueStack);
214226
} else if (node instanceof ObjectField) {
215-
inputTypeStack.remove(inputTypeStack.size() - 1);
227+
pop(inputTypeStack);
228+
pop(defaultValueStack);
216229
}
217230
}
218231

@@ -249,10 +262,16 @@ private void addOutputType(GraphQLOutputType type) {
249262
}
250263

251264
private <T> T lastElement(List<T> list) {
252-
if (list.size() == 0) return null;
265+
if (list.isEmpty()) {
266+
return null;
267+
}
253268
return list.get(list.size() - 1);
254269
}
255270

271+
private <T> T pop(List<T> list) {
272+
return list.remove(list.size() - 1);
273+
}
274+
256275
/**
257276
* @return can be null if the parent is not a CompositeType
258277
*/
@@ -267,11 +286,18 @@ private void addParentType(GraphQLCompositeType compositeType) {
267286
public GraphQLInputType getInputType() {
268287
return lastElement(inputTypeStack);
269288
}
289+
public InputValueWithState getDefaultValue() {
290+
return lastElement(defaultValueStack);
291+
}
270292

271293
private void addInputType(GraphQLInputType graphQLInputType) {
272294
inputTypeStack.add(graphQLInputType);
273295
}
274296

297+
private void addDefaultValue(InputValueWithState defaultValue) {
298+
defaultValueStack.add(defaultValue);
299+
}
300+
275301
public GraphQLFieldDefinition getFieldDef() {
276302
return lastElement(fieldDefStack);
277303
}

src/main/java/graphql/validation/ValidationContext.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import graphql.schema.GraphQLInputType;
1515
import graphql.schema.GraphQLOutputType;
1616
import graphql.schema.GraphQLSchema;
17+
import graphql.schema.InputValueWithState;
1718

1819
import java.util.LinkedHashMap;
1920
import java.util.List;
@@ -41,8 +42,10 @@ public ValidationContext(GraphQLSchema schema, Document document, I18n i18n) {
4142
}
4243

4344
private void buildFragmentMap() {
44-
for (Definition definition : document.getDefinitions()) {
45-
if (!(definition instanceof FragmentDefinition)) continue;
45+
for (Definition<?> definition : document.getDefinitions()) {
46+
if (!(definition instanceof FragmentDefinition)) {
47+
continue;
48+
}
4649
FragmentDefinition fragmentDefinition = (FragmentDefinition) definition;
4750
fragmentDefinitionMap.put(fragmentDefinition.getName(), fragmentDefinition);
4851
}
@@ -72,6 +75,10 @@ public GraphQLInputType getInputType() {
7275
return traversalContext.getInputType();
7376
}
7477

78+
public InputValueWithState getDefaultValue() {
79+
return traversalContext.getDefaultValue();
80+
}
81+
7582
public GraphQLFieldDefinition getFieldDef() {
7683
return traversalContext.getFieldDef();
7784
}

src/main/java/graphql/validation/rules/VariableTypesMatch.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,24 +59,25 @@ public void checkVariable(VariableReference variableReference) {
5959
if (variableType == null) {
6060
return;
6161
}
62-
GraphQLInputType expectedType = getValidationContext().getInputType();
63-
Optional<InputValueWithState> schemaDefault = Optional.ofNullable(getValidationContext().getArgument()).map(v -> v.getArgumentDefaultValue());
64-
Value schemaDefaultValue = null;
65-
if (schemaDefault.isPresent() && schemaDefault.get().isLiteral()) {
66-
schemaDefaultValue = (Value) schemaDefault.get().getValue();
67-
} else if (schemaDefault.isPresent() && schemaDefault.get().isSet()) {
68-
schemaDefaultValue = ValuesResolver.valueToLiteral(schemaDefault.get(), expectedType, getValidationContext().getGraphQLContext(), getValidationContext().getI18n().getLocale());
69-
}
70-
if (expectedType == null) {
71-
// we must have a unknown variable say to not have a known type
62+
GraphQLInputType locationType = getValidationContext().getInputType();
63+
Optional<InputValueWithState> locationDefault = Optional.ofNullable(getValidationContext().getDefaultValue());
64+
if (locationType == null) {
65+
// we must have an unknown variable say to not have a known type
7266
return;
7367
}
74-
if (!variablesTypesMatcher.doesVariableTypesMatch(variableType, variableDefinition.getDefaultValue(), expectedType) &&
75-
!variablesTypesMatcher.doesVariableTypesMatch(variableType, schemaDefaultValue, expectedType)) {
68+
Value<?> locationDefaultValue = null;
69+
if (locationDefault.isPresent() && locationDefault.get().isLiteral()) {
70+
locationDefaultValue = (Value<?>) locationDefault.get().getValue();
71+
} else if (locationDefault.isPresent() && locationDefault.get().isSet()) {
72+
locationDefaultValue = ValuesResolver.valueToLiteral(locationDefault.get(), locationType, getValidationContext().getGraphQLContext(), getValidationContext().getI18n().getLocale());
73+
}
74+
boolean variableDefMatches = variablesTypesMatcher.doesVariableTypesMatch(variableType, variableDefinition.getDefaultValue(), locationType, locationDefaultValue);
75+
if (!variableDefMatches) {
7676
GraphQLType effectiveType = variablesTypesMatcher.effectiveType(variableType, variableDefinition.getDefaultValue());
7777
String message = i18n(VariableTypeMismatch, "VariableTypesMatchRule.unexpectedType",
78+
variableDefinition.getName(),
7879
GraphQLTypeUtil.simplePrint(effectiveType),
79-
GraphQLTypeUtil.simplePrint(expectedType));
80+
GraphQLTypeUtil.simplePrint(locationType));
8081
addError(VariableTypeMismatch, variableReference.getSourceLocation(), message);
8182
}
8283
}

src/main/java/graphql/validation/rules/VariablesTypesMatcher.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,38 @@
99
import static graphql.schema.GraphQLNonNull.nonNull;
1010
import static graphql.schema.GraphQLTypeUtil.isList;
1111
import static graphql.schema.GraphQLTypeUtil.isNonNull;
12+
import static graphql.schema.GraphQLTypeUtil.unwrapNonNull;
1213
import static graphql.schema.GraphQLTypeUtil.unwrapOne;
1314

1415
@Internal
1516
public class VariablesTypesMatcher {
1617

17-
public boolean doesVariableTypesMatch(GraphQLType variableType, Value variableDefaultValue, GraphQLType expectedType) {
18-
return checkType(effectiveType(variableType, variableDefaultValue), expectedType);
18+
/**
19+
* This method and variable naming was inspired from the reference graphql-js implementation
20+
*
21+
* @param varType the variable type
22+
* @param varDefaultValue the default value for the variable
23+
* @param locationType the location type where the variable was encountered
24+
* @param locationDefaultValue the default value for that location
25+
*
26+
* @return true if the variable matches ok
27+
*/
28+
public boolean doesVariableTypesMatch(GraphQLType varType, Value<?> varDefaultValue, GraphQLType locationType, Value<?> locationDefaultValue) {
29+
if (isNonNull(locationType) && !isNonNull(varType)) {
30+
boolean hasNonNullVariableDefaultValue =
31+
varDefaultValue != null && !(varDefaultValue instanceof NullValue);
32+
boolean hasLocationDefaultValue = locationDefaultValue != null;
33+
if (!hasNonNullVariableDefaultValue && !hasLocationDefaultValue) {
34+
return false;
35+
}
36+
GraphQLType nullableLocationType = unwrapNonNull(locationType);
37+
return checkType(varType, nullableLocationType);
38+
}
39+
return checkType(varType, locationType);
1940
}
2041

21-
public GraphQLType effectiveType(GraphQLType variableType, Value defaultValue) {
42+
43+
public GraphQLType effectiveType(GraphQLType variableType, Value<?> defaultValue) {
2244
if (defaultValue == null || defaultValue instanceof NullValue) {
2345
return variableType;
2446
}

src/main/resources/i18n/Validation.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ VariableDefaultValuesOfCorrectType.badDefault=Validation error ({0}) : Bad defau
7676
#
7777
VariablesAreInputTypes.wrongType=Validation error ({0}) : Input variable ''{1}'' type ''{2}'' is not an input type
7878
#
79-
VariableTypesMatchRule.unexpectedType=Validation error ({0}) : Variable type ''{1}'' does not match expected type ''{2}''
79+
VariableTypesMatchRule.unexpectedType=Validation error ({0}) : Variable ''{1}'' of type ''{2}'' used in position expecting type ''{3}''
8080
#
8181
UniqueObjectFieldName.duplicateFieldName=Validation Error ({0}) : There can be only one field named ''{1}''
8282
#

src/main/resources/i18n/Validation_de.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ VariableDefaultValuesOfCorrectType.badDefault=Validierungsfehler ({0}) : Ung
7676
#
7777
VariablesAreInputTypes.wrongType=Validierungsfehler ({0}) : Eingabevariable ''{1}'' Typ ''{2}'' ist kein Eingabetyp
7878
#
79-
VariableTypesMatchRule.unexpectedType=Validierungsfehler ({0}) : Der Variablentyp ''{1}'' stimmt nicht mit dem erwarteten Typ ''{2}'' überein
79+
VariableTypesMatchRule.unexpectedType=Validierungsfehler ({0}) : Variable ''{1}'' vom Typ ''{2}'' verwendet in Position, die Typ ''{3}'' erwartet
8080
#
8181
# These are used but IDEA cant find them easily as being called
8282
#

0 commit comments

Comments
 (0)