Skip to content

Commit 40e8740

Browse files
committed
Now has the walking code up kin ValidationRules rather than in lower level rules. More efficient and more powerful for rules
1 parent 694857f commit 40e8740

17 files changed

+204
-128
lines changed

src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java

Lines changed: 7 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,14 @@
44
import graphql.GraphQLError;
55
import graphql.PublicSpi;
66
import graphql.Scalars;
7-
import graphql.execution.ExecutionPath;
87
import graphql.schema.GraphQLArgument;
98
import graphql.schema.GraphQLDirective;
10-
import graphql.schema.GraphQLDirectiveContainer;
119
import graphql.schema.GraphQLFieldDefinition;
1210
import graphql.schema.GraphQLFieldsContainer;
13-
import graphql.schema.GraphQLInputObjectField;
1411
import graphql.schema.GraphQLInputObjectType;
1512
import graphql.schema.GraphQLInputType;
16-
import graphql.schema.GraphQLList;
1713
import graphql.schema.GraphQLScalarType;
1814
import graphql.schema.GraphQLTypeUtil;
19-
import graphql.util.FpKit;
2015
import graphql.validation.rules.ValidationEnvironment;
2116
import graphql.validation.util.DirectivesAndTypeWalker;
2217
import graphql.validation.util.Util;
@@ -25,14 +20,12 @@
2520
import java.math.BigDecimal;
2621
import java.util.ArrayList;
2722
import java.util.Collection;
28-
import java.util.Collections;
2923
import java.util.LinkedHashMap;
3024
import java.util.List;
3125
import java.util.Map;
3226

3327
import static graphql.schema.GraphQLTypeUtil.isList;
3428
import static graphql.validation.rules.ValidationEnvironment.ValidatedElement.FIELD;
35-
import static graphql.validation.rules.ValidationEnvironment.ValidatedElement.INPUT_OBJECT_FIELD;
3629
import static java.util.Collections.singletonList;
3730

3831
@SuppressWarnings("UnnecessaryLocalVariable")
@@ -115,9 +108,7 @@ public List<GraphQLError> runValidation(ValidationEnvironment validationEnvironm
115108
return runFieldValidationImpl(validationEnvironment);
116109
}
117110

118-
GraphQLArgument argument = validationEnvironment.getArgument();
119111
Object validatedValue = validationEnvironment.getValidatedValue();
120-
List<GraphQLDirective> directives = argument == null ? Collections.emptyList() : argument.getDirectives();
121112

122113
//
123114
// all the directives validation code does NOT care for NULL ness since the graphql engine covers that.
@@ -126,42 +117,22 @@ public List<GraphQLError> runValidation(ValidationEnvironment validationEnvironm
126117
GraphQLInputType inputType = Util.unwrapNonNull(validationEnvironment.getValidatedType());
127118
validationEnvironment = validationEnvironment.transform(b -> b.validatedType(inputType));
128119

129-
return runValidationImpl(validationEnvironment, inputType, validatedValue, directives);
120+
return runValidationImpl(validationEnvironment, inputType, validatedValue);
130121
}
131122

132123
private List<GraphQLError> runFieldValidationImpl(ValidationEnvironment validationEnvironment) {
133-
return runConstraintOnDirectives(validationEnvironment, validationEnvironment.getFieldDefinition().getDirectives());
124+
return runConstraintOnDirectives(validationEnvironment);
134125
}
135126

136127
@SuppressWarnings("unchecked")
137-
private List<GraphQLError> runValidationImpl(ValidationEnvironment validationEnvironment, GraphQLInputType inputType, Object validatedValue, List<GraphQLDirective> directives) {
138-
List<GraphQLError> errors = runConstraintOnDirectives(validationEnvironment, directives);
139-
if (validatedValue == null) {
140-
return errors;
141-
}
142-
143-
inputType = (GraphQLInputType) GraphQLTypeUtil.unwrapNonNull(inputType);
144-
145-
if (GraphQLTypeUtil.isList(inputType)) {
146-
List<Object> values = new ArrayList<>(FpKit.toCollection(validatedValue));
147-
List<GraphQLError> ruleErrors = walkListArg(validationEnvironment, (GraphQLList) inputType, values);
148-
errors.addAll(ruleErrors);
149-
}
150-
151-
if (inputType instanceof GraphQLInputObjectType) {
152-
if (validatedValue instanceof Map) {
153-
Map<String, Object> objectValue = (Map<String, Object>) validatedValue;
154-
List<GraphQLError> ruleErrors = walkObjectArg(validationEnvironment, (GraphQLInputObjectType) inputType, objectValue);
155-
errors.addAll(ruleErrors);
156-
} else {
157-
Assert.assertShouldNeverHappen("How can there be a `input` object type '%s' that does not have a matching Map java value", GraphQLTypeUtil.simplePrint(inputType));
158-
}
159-
}
160-
return errors;
128+
private List<GraphQLError> runValidationImpl(ValidationEnvironment validationEnvironment, GraphQLInputType inputType, Object validatedValue) {
129+
return runConstraintOnDirectives(validationEnvironment);
161130
}
162131

163-
private List<GraphQLError> runConstraintOnDirectives(ValidationEnvironment validationEnvironment, List<GraphQLDirective> directives) {
132+
private List<GraphQLError> runConstraintOnDirectives(ValidationEnvironment validationEnvironment) {
133+
164134
List<GraphQLError> errors = new ArrayList<>();
135+
List<GraphQLDirective> directives = validationEnvironment.getDirectives();
165136
directives = Util.sort(directives, GraphQLDirective::getName);
166137

167138
for (GraphQLDirective directive : directives) {
@@ -180,63 +151,6 @@ private List<GraphQLError> runConstraintOnDirectives(ValidationEnvironment valid
180151
return errors;
181152
}
182153

183-
private List<GraphQLError> walkObjectArg(ValidationEnvironment validationEnvironment, GraphQLInputObjectType argumentType, Map<String, Object> objectMap) {
184-
List<GraphQLError> errors = new ArrayList<>();
185-
186-
// run them in a stable order
187-
List<GraphQLInputObjectField> fieldDefinitions = Util.sort(argumentType.getFieldDefinitions(), GraphQLInputObjectField::getName);
188-
for (GraphQLInputObjectField inputField : fieldDefinitions) {
189-
190-
GraphQLInputType fieldType = inputField.getType();
191-
List<GraphQLDirective> directives = inputField.getDirectives();
192-
Object validatedValue = objectMap.getOrDefault(inputField.getName(), inputField.getDefaultValue());
193-
if (validatedValue == null) {
194-
continue;
195-
}
196-
197-
ExecutionPath newPath = validationEnvironment.getValidatedPath().segment(inputField.getName());
198-
199-
ValidationEnvironment newValidationEnvironment = validationEnvironment.transform(builder -> builder
200-
.validatedPath(newPath)
201-
.validatedValue(validatedValue)
202-
.validatedType(fieldType)
203-
.validatedElement(INPUT_OBJECT_FIELD)
204-
);
205-
206-
List<GraphQLError> ruleErrors = runValidationImpl(newValidationEnvironment, fieldType, validatedValue, directives);
207-
errors.addAll(ruleErrors);
208-
}
209-
return errors;
210-
}
211-
212-
private List<GraphQLError> walkListArg(ValidationEnvironment validationEnvironment, GraphQLList argumentType, List<Object> objectList) {
213-
List<GraphQLError> errors = new ArrayList<>();
214-
215-
GraphQLInputType listItemType = Util.unwrapOneAndAllNonNull(argumentType);
216-
List<GraphQLDirective> directives;
217-
if (!(listItemType instanceof GraphQLDirectiveContainer)) {
218-
directives = Collections.emptyList();
219-
} else {
220-
directives = ((GraphQLDirectiveContainer) listItemType).getDirectives();
221-
}
222-
int ix = 0;
223-
for (Object value : objectList) {
224-
225-
ExecutionPath newPath = validationEnvironment.getValidatedPath().segment(ix);
226-
227-
ValidationEnvironment newValidationEnvironment = validationEnvironment.transform(builder -> builder
228-
.validatedPath(newPath)
229-
.validatedValue(value)
230-
.validatedType(listItemType)
231-
);
232-
233-
List<GraphQLError> ruleErrors = runValidationImpl(newValidationEnvironment, listItemType, value, directives);
234-
errors.addAll(ruleErrors);
235-
ix++;
236-
}
237-
return errors;
238-
}
239-
240154

241155
/**
242156
* Returns true of the input type is one of the specified scalar types, regardless of non null ness

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
import graphql.language.SourceLocation;
66
import graphql.schema.DataFetchingEnvironment;
77
import graphql.schema.GraphQLArgument;
8+
import graphql.schema.GraphQLDirective;
89
import graphql.schema.GraphQLFieldDefinition;
910
import graphql.schema.GraphQLFieldsContainer;
1011
import graphql.schema.GraphQLInputType;
1112
import graphql.validation.interpolation.MessageInterpolator;
1213

1314
import java.util.Collections;
1415
import java.util.HashMap;
16+
import java.util.List;
1517
import java.util.Locale;
1618
import java.util.Map;
1719
import java.util.function.Consumer;
@@ -53,6 +55,7 @@ public enum ValidatedElement {
5355
private final Object validatedValue;
5456
private final GraphQLInputType validatedType;
5557
private final ValidatedElement validatedElement;
58+
private final List<GraphQLDirective> directives;
5659

5760
private ValidationEnvironment(Builder builder) {
5861
this.argument = builder.argument;
@@ -68,6 +71,7 @@ private ValidationEnvironment(Builder builder) {
6871
this.location = builder.location;
6972
this.validatedValue = builder.validatedValue;
7073
this.validatedElement = builder.validatedElement;
74+
this.directives = builder.directives;
7175
}
7276

7377
public static Builder newValidationEnvironment() {
@@ -127,6 +131,10 @@ public ValidatedElement getValidatedElement() {
127131
return validatedElement;
128132
}
129133

134+
public List<GraphQLDirective> getDirectives() {
135+
return directives;
136+
}
137+
130138
public ValidationEnvironment transform(Consumer<Builder> builderConsumer) {
131139
Builder builder = newValidationEnvironment().validationEnvironment(this);
132140
builderConsumer.accept(builder);
@@ -147,6 +155,7 @@ public static class Builder {
147155
private Object validatedValue;
148156
private GraphQLInputType validatedType;
149157
private ValidatedElement validatedElement;
158+
private List<GraphQLDirective> directives = Collections.emptyList();
150159

151160
public Builder validationEnvironment(ValidationEnvironment validationEnvironment) {
152161
this.argument = validationEnvironment.argument;
@@ -162,12 +171,14 @@ public Builder validationEnvironment(ValidationEnvironment validationEnvironment
162171
this.location = validationEnvironment.location;
163172
this.validatedValue = validationEnvironment.validatedValue;
164173
this.validatedElement = validationEnvironment.validatedElement;
174+
this.directives = validationEnvironment.directives;
165175
return this;
166176
}
167177

168178
public Builder dataFetchingEnvironment(DataFetchingEnvironment dataFetchingEnvironment) {
169179
fieldsContainer(dataFetchingEnvironment.getExecutionStepInfo().getFieldContainer());
170180
fieldDefinition(dataFetchingEnvironment.getFieldDefinition());
181+
directives(dataFetchingEnvironment.getFieldDefinition().getDirectives());
171182
executionPath(dataFetchingEnvironment.getExecutionStepInfo().getPath());
172183
validatedPath(dataFetchingEnvironment.getExecutionStepInfo().getPath());
173184
location(dataFetchingEnvironment.getField().getSourceLocation());
@@ -241,6 +252,11 @@ public Builder locale(Locale locale) {
241252
return this;
242253
}
243254

255+
public Builder directives(List<GraphQLDirective> directives) {
256+
this.directives = directives;
257+
return this;
258+
}
259+
244260
public ValidationEnvironment build() {
245261
return new ValidationEnvironment(this);
246262
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@ public interface ValidationRule {
3737
boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer);
3838

3939
/**
40-
* This is called to runs the rule
40+
* This is called to runs the rule. A rule maybe invoked MULTIPLE times per field. It will be invoked
41+
* once for the original field, then invoked for each of the arguments on a field and then if the input types are complex ones
42+
* such as {@link graphql.schema.GraphQLInputObjectType} then it will be invoked for each attribute of that
43+
* type.
44+
* <p>
45+
* A rule should consult {@link ValidationEnvironment#getValidatedElement()} to check
46+
* what element is being validated. If the rule does not handle that type of element,
47+
* simply return an empty list of errors.
4148
*
4249
* @param validationEnvironment the validation environment
4350
*

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

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
package graphql.validation.rules;
22

3+
import graphql.Assert;
34
import graphql.GraphQLError;
45
import graphql.PublicApi;
56
import graphql.execution.ExecutionPath;
67
import graphql.schema.DataFetchingEnvironment;
78
import graphql.schema.GraphQLArgument;
9+
import graphql.schema.GraphQLDirective;
10+
import graphql.schema.GraphQLDirectiveContainer;
811
import graphql.schema.GraphQLFieldDefinition;
12+
import graphql.schema.GraphQLInputObjectField;
13+
import graphql.schema.GraphQLInputObjectType;
14+
import graphql.schema.GraphQLInputType;
15+
import graphql.schema.GraphQLList;
916
import graphql.schema.GraphQLObjectType;
17+
import graphql.schema.GraphQLTypeUtil;
18+
import graphql.util.FpKit;
1019
import graphql.validation.interpolation.MessageInterpolator;
1120
import graphql.validation.util.Util;
1221

@@ -19,6 +28,7 @@
1928

2029
import static graphql.validation.rules.ValidationEnvironment.ValidatedElement.ARGUMENT;
2130
import static graphql.validation.rules.ValidationEnvironment.ValidatedElement.FIELD;
31+
import static graphql.validation.rules.ValidationEnvironment.ValidatedElement.INPUT_OBJECT_FIELD;
2232

2333
/**
2434
* ValidationRules is a holder of {@link graphql.validation.rules.ValidationRule}s against a specific
@@ -78,24 +88,113 @@ public List<GraphQLError> runValidationRules(DataFetchingEnvironment env, Messag
7888
}
7989

8090
Object argValue = env.getArgument(fieldArg.getName());
91+
GraphQLInputType inputType = fieldArg.getType();
8192

8293
ValidationEnvironment ruleEnvironment = ValidationEnvironment.newValidationEnvironment()
8394
.dataFetchingEnvironment(env)
8495
.argument(fieldArg)
8596
.validatedElement(ARGUMENT)
86-
.validatedType(fieldArg.getType())
97+
.validatedType(inputType)
8798
.validatedValue(argValue)
8899
.validatedPath(fieldPath.segment(fieldArg.getName()))
100+
.directives(fieldArg.getDirectives())
89101
.messageInterpolator(interpolator)
90102
.locale(locale)
91103
.build();
92104

93105
for (ValidationRule rule : rules) {
94-
List<GraphQLError> ruleErrors = rule.runValidation(ruleEnvironment);
106+
List<GraphQLError> ruleErrors = runValidationImpl(rule, ruleEnvironment, inputType, argValue);
107+
errors.addAll(ruleErrors);
108+
}
109+
}
110+
111+
return errors;
112+
}
113+
114+
@SuppressWarnings("unchecked")
115+
private List<GraphQLError> runValidationImpl(ValidationRule rule, ValidationEnvironment validationEnvironment, GraphQLInputType inputType, Object validatedValue) {
116+
List<GraphQLError> errors = rule.runValidation(validationEnvironment);
117+
if (validatedValue == null) {
118+
return errors;
119+
}
120+
121+
inputType = (GraphQLInputType) GraphQLTypeUtil.unwrapNonNull(inputType);
122+
123+
if (GraphQLTypeUtil.isList(inputType)) {
124+
List<Object> values = new ArrayList<>(FpKit.toCollection(validatedValue));
125+
List<GraphQLError> ruleErrors = walkListArg(rule, validationEnvironment, (GraphQLList) inputType, values);
126+
errors.addAll(ruleErrors);
127+
}
128+
129+
if (inputType instanceof GraphQLInputObjectType) {
130+
if (validatedValue instanceof Map) {
131+
Map<String, Object> objectValue = (Map<String, Object>) validatedValue;
132+
List<GraphQLError> ruleErrors = walkObjectArg(rule, validationEnvironment, (GraphQLInputObjectType) inputType, objectValue);
95133
errors.addAll(ruleErrors);
134+
} else {
135+
Assert.assertShouldNeverHappen("How can there be a `input` object type '%s' that does not have a matching Map java value", GraphQLTypeUtil.simplePrint(inputType));
96136
}
97137
}
138+
return errors;
139+
}
140+
141+
142+
private List<GraphQLError> walkObjectArg(ValidationRule rule, ValidationEnvironment validationEnvironment, GraphQLInputObjectType argumentType, Map<String, Object> objectMap) {
143+
List<GraphQLError> errors = new ArrayList<>();
98144

145+
// run them in a stable order
146+
List<GraphQLInputObjectField> fieldDefinitions = Util.sort(argumentType.getFieldDefinitions(), GraphQLInputObjectField::getName);
147+
for (GraphQLInputObjectField inputField : fieldDefinitions) {
148+
149+
GraphQLInputType fieldType = inputField.getType();
150+
List<GraphQLDirective> directives = inputField.getDirectives();
151+
Object validatedValue = objectMap.getOrDefault(inputField.getName(), inputField.getDefaultValue());
152+
if (validatedValue == null) {
153+
continue;
154+
}
155+
156+
ExecutionPath newPath = validationEnvironment.getValidatedPath().segment(inputField.getName());
157+
158+
ValidationEnvironment newValidationEnvironment = validationEnvironment.transform(builder -> builder
159+
.validatedPath(newPath)
160+
.validatedValue(validatedValue)
161+
.validatedType(fieldType)
162+
.directives(inputField.getDirectives())
163+
.validatedElement(INPUT_OBJECT_FIELD)
164+
);
165+
166+
List<GraphQLError> ruleErrors = runValidationImpl(rule, newValidationEnvironment, fieldType, validatedValue);
167+
errors.addAll(ruleErrors);
168+
}
169+
return errors;
170+
}
171+
172+
private List<GraphQLError> walkListArg(ValidationRule rule, ValidationEnvironment validationEnvironment, GraphQLList argumentType, List<Object> objectList) {
173+
List<GraphQLError> errors = new ArrayList<>();
174+
175+
GraphQLInputType listItemType = Util.unwrapOneAndAllNonNull(argumentType);
176+
List<GraphQLDirective> directives;
177+
if (!(listItemType instanceof GraphQLDirectiveContainer)) {
178+
directives = Collections.emptyList();
179+
} else {
180+
directives = ((GraphQLDirectiveContainer) listItemType).getDirectives();
181+
}
182+
int ix = 0;
183+
for (Object value : objectList) {
184+
185+
ExecutionPath newPath = validationEnvironment.getValidatedPath().segment(ix);
186+
187+
ValidationEnvironment newValidationEnvironment = validationEnvironment.transform(builder -> builder
188+
.validatedPath(newPath)
189+
.validatedValue(value)
190+
.validatedType(listItemType)
191+
.directives(directives)
192+
);
193+
194+
List<GraphQLError> ruleErrors = runValidationImpl(rule, newValidationEnvironment, listItemType, value);
195+
errors.addAll(ruleErrors);
196+
ix++;
197+
}
99198
return errors;
100199
}
101200

0 commit comments

Comments
 (0)