Skip to content

Commit 4890f7f

Browse files
Copilotandimarek
andcommitted
Inline OneOf validation rules into TypeAndFieldRule
Co-authored-by: andimarek <1706744+andimarek@users.noreply.github.com>
1 parent defeda1 commit 4890f7f

4 files changed

Lines changed: 59 additions & 89 deletions

File tree

src/main/java/graphql/schema/validation/OneOfInputObjectRules.java

Lines changed: 0 additions & 85 deletions
This file was deleted.

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public SchemaValidator() {
2525
rules.add(new AppliedDirectivesAreValid());
2626
rules.add(new AppliedDirectiveArgumentsAreValid());
2727
rules.add(new InputAndOutputTypesUsedAppropriately());
28-
rules.add(new OneOfInputObjectRules());
2928
rules.add(new DeprecatedInputObjectAndArgumentsAreValid());
3029
}
3130

src/main/java/graphql/schema/validation/TypeAndFieldRule.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
import graphql.schema.GraphQLTypeUtil;
2020
import graphql.schema.GraphQLTypeVisitorStub;
2121
import graphql.schema.GraphQLUnionType;
22+
import graphql.schema.GraphQLUnmodifiedType;
2223
import graphql.util.FpKit;
2324
import graphql.util.TraversalControl;
2425
import graphql.util.TraverserContext;
2526

2627
import java.util.HashSet;
28+
import java.util.LinkedHashSet;
2729
import java.util.List;
2830
import java.util.Set;
2931
import java.util.function.Predicate;
@@ -91,6 +93,35 @@ public TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType type,
9193
}
9294
SchemaValidationErrorCollector errorCollector = context.getVarFromParents(SchemaValidationErrorCollector.class);
9395
validateInputObject((GraphQLInputObjectType) type, errorCollector);
96+
97+
// OneOf validation: check if OneOf input types are inhabited
98+
if (type.isOneOf()) {
99+
if (!canBeProvidedAFiniteValue(type, new LinkedHashSet<>())) {
100+
String message = String.format("OneOf Input Object %s must be inhabited but all fields recursively reference only other OneOf Input Objects forming an unresolvable cycle.", type.getName());
101+
errorCollector.addError(new SchemaValidationError(SchemaValidationErrorType.OneOfNotInhabited, message));
102+
}
103+
}
104+
105+
return TraversalControl.CONTINUE;
106+
}
107+
108+
@Override
109+
public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField inputObjectField, TraverserContext<GraphQLSchemaElement> context) {
110+
GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) context.getParentNode();
111+
if (!inputObjectType.isOneOf()) {
112+
return TraversalControl.CONTINUE;
113+
}
114+
SchemaValidationErrorCollector errorCollector = context.getVarFromParents(SchemaValidationErrorCollector.class);
115+
// OneOf validation: error messages taken from the reference implementation
116+
if (inputObjectField.hasSetDefaultValue()) {
117+
String message = String.format("OneOf input field %s.%s cannot have a default value.", inputObjectType.getName(), inputObjectField.getName());
118+
errorCollector.addError(new SchemaValidationError(SchemaValidationErrorType.OneOfDefaultValueOnField, message));
119+
}
120+
121+
if (GraphQLTypeUtil.isNonNull(inputObjectField.getType())) {
122+
String message = String.format("OneOf input field %s.%s must be nullable.", inputObjectType.getName(), inputObjectField.getName());
123+
errorCollector.addError(new SchemaValidationError(SchemaValidationErrorType.OneOfNonNullableField, message));
124+
}
94125
return TraversalControl.CONTINUE;
95126
}
96127

@@ -121,6 +152,32 @@ private void validateInputObject(GraphQLInputObjectType type, SchemaValidationEr
121152
}
122153
}
123154

155+
private boolean canBeProvidedAFiniteValue(GraphQLInputObjectType oneOfInputObject, Set<GraphQLInputObjectType> visited) {
156+
if (visited.contains(oneOfInputObject)) {
157+
return false;
158+
}
159+
Set<GraphQLInputObjectType> nextVisited = new LinkedHashSet<>(visited);
160+
nextVisited.add(oneOfInputObject);
161+
for (GraphQLInputObjectField field : oneOfInputObject.getFieldDefinitions()) {
162+
GraphQLType fieldType = field.getType();
163+
if (GraphQLTypeUtil.isList(fieldType)) {
164+
return true;
165+
}
166+
GraphQLUnmodifiedType namedFieldType = GraphQLTypeUtil.unwrapAll(fieldType);
167+
if (!(namedFieldType instanceof GraphQLInputObjectType)) {
168+
return true;
169+
}
170+
GraphQLInputObjectType inputFieldType = (GraphQLInputObjectType) namedFieldType;
171+
if (!inputFieldType.isOneOf()) {
172+
return true;
173+
}
174+
if (canBeProvidedAFiniteValue(inputFieldType, nextVisited)) {
175+
return true;
176+
}
177+
}
178+
return false;
179+
}
180+
124181
private void validateUnion(GraphQLUnionType type, SchemaValidationErrorCollector errorCollector) {
125182
List<GraphQLNamedOutputType> memberTypes = type.getTypes();
126183
if (memberTypes.size() == 0) {

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ class SchemaValidatorTest extends Specification {
1111
def validator = new SchemaValidator()
1212
def rules = validator.rules
1313
then:
14-
rules.size() == 9
14+
rules.size() == 8
1515
rules[0] instanceof NoUnbrokenInputCycles
1616
rules[1] instanceof TypesImplementInterfaces
1717
rules[2] instanceof TypeAndFieldRule
1818
rules[3] instanceof DefaultValuesAreValid
1919
rules[4] instanceof AppliedDirectivesAreValid
2020
rules[5] instanceof AppliedDirectiveArgumentsAreValid
2121
rules[6] instanceof InputAndOutputTypesUsedAppropriately
22-
rules[7] instanceof OneOfInputObjectRules
23-
rules[8] instanceof DeprecatedInputObjectAndArgumentsAreValid
22+
rules[7] instanceof DeprecatedInputObjectAndArgumentsAreValid
2423
}
2524
}

0 commit comments

Comments
 (0)