Skip to content

Commit 49149ba

Browse files
committed
The AbstractDirectiveConstraint now runs all the validation and type system walking
1 parent 4b9da1d commit 49149ba

24 files changed

+308
-262
lines changed

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

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,33 @@
77
import graphql.execution.ExecutionPath;
88
import graphql.schema.GraphQLArgument;
99
import graphql.schema.GraphQLDirective;
10+
import graphql.schema.GraphQLDirectiveContainer;
1011
import graphql.schema.GraphQLFieldDefinition;
1112
import graphql.schema.GraphQLFieldsContainer;
13+
import graphql.schema.GraphQLInputObjectField;
1214
import graphql.schema.GraphQLInputObjectType;
1315
import graphql.schema.GraphQLInputType;
16+
import graphql.schema.GraphQLList;
1417
import graphql.schema.GraphQLScalarType;
1518
import graphql.schema.GraphQLTypeUtil;
19+
import graphql.util.FpKit;
1620
import graphql.validation.rules.ValidationEnvironment;
1721
import graphql.validation.util.DirectivesAndTypeWalker;
1822
import graphql.validation.util.Util;
1923

2024
import java.lang.reflect.Array;
2125
import java.math.BigDecimal;
26+
import java.util.ArrayList;
2227
import java.util.Collection;
28+
import java.util.Collections;
2329
import java.util.LinkedHashMap;
2430
import java.util.List;
2531
import java.util.Map;
2632

2733
import static graphql.schema.GraphQLTypeUtil.isList;
2834
import static java.util.Collections.singletonList;
2935

36+
@SuppressWarnings("UnnecessaryLocalVariable")
3037
@PublicSpi
3138
public abstract class AbstractDirectiveConstraint implements DirectiveConstraint {
3239

@@ -60,7 +67,7 @@ public boolean appliesTo(GraphQLFieldDefinition fieldDefinition, GraphQLFieldsCo
6067
@Override
6168
public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer) {
6269

63-
return DirectivesAndTypeWalker.isSuitable(argument, (inputType, directive) -> {
70+
boolean suitable = DirectivesAndTypeWalker.isSuitable(argument, (inputType, directive) -> {
6471
boolean hasNamedDirective = directive.getName().equals(this.getName());
6572
if (hasNamedDirective) {
6673
inputType = Util.unwrapNonNull(inputType);
@@ -75,10 +82,132 @@ public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldD
7582
}
7683
return false;
7784
});
85+
return suitable;
7886
}
7987

8088
abstract protected boolean appliesToType(GraphQLInputType inputType);
8189

90+
abstract protected List<GraphQLError> runConstraint(ValidationEnvironment validationEnvironment);
91+
92+
93+
@SuppressWarnings("unchecked")
94+
@Override
95+
public List<GraphQLError> runValidation(ValidationEnvironment validationEnvironment) {
96+
97+
GraphQLArgument argument = validationEnvironment.getArgument();
98+
Object validatedValue = validationEnvironment.getValidatedValue();
99+
List<GraphQLDirective> directives = argument.getDirectives();
100+
101+
//
102+
// all the directives validation code does NOT care for NULL ness since the graphql engine covers that.
103+
// eg a @NonNull validation directive makes no sense in graphql like it might in Java
104+
//
105+
GraphQLInputType inputType = Util.unwrapNonNull(validationEnvironment.getFieldOrArgumentType());
106+
validationEnvironment = validationEnvironment.transform(b -> b.fieldOrArgumentType(inputType));
107+
108+
return runValidationImpl(validationEnvironment, inputType, validatedValue, directives);
109+
}
110+
111+
@SuppressWarnings("unchecked")
112+
private List<GraphQLError> runValidationImpl(ValidationEnvironment validationEnvironment, GraphQLInputType inputType, Object validatedValue, List<GraphQLDirective> directives) {
113+
List<GraphQLError> errors = new ArrayList<>();
114+
// run them in a stable order
115+
directives = Util.sort(directives, GraphQLDirective::getName);
116+
for (GraphQLDirective directive : directives) {
117+
// we get called for arguments and input field types which can have multiple directive constraints on them and hence no just for this one
118+
boolean isOurDirective = directive.getName().equals(this.getName());
119+
if (!isOurDirective) {
120+
continue;
121+
}
122+
123+
validationEnvironment = validationEnvironment.transform(b -> b.context(GraphQLDirective.class, directive));
124+
//
125+
// now run the directive rule with this directive instance
126+
List<GraphQLError> ruleErrors = this.runConstraint(validationEnvironment);
127+
errors.addAll(ruleErrors);
128+
}
129+
130+
if (validatedValue == null) {
131+
return errors;
132+
}
133+
134+
inputType = (GraphQLInputType) GraphQLTypeUtil.unwrapNonNull(inputType);
135+
136+
if (GraphQLTypeUtil.isList(inputType)) {
137+
List<Object> values = new ArrayList<>(FpKit.toCollection(validatedValue));
138+
List<GraphQLError> ruleErrors = walkListArg(validationEnvironment, (GraphQLList) inputType, values);
139+
errors.addAll(ruleErrors);
140+
}
141+
142+
if (inputType instanceof GraphQLInputObjectType) {
143+
if (validatedValue instanceof Map) {
144+
Map<String, Object> objectValue = (Map<String, Object>) validatedValue;
145+
List<GraphQLError> ruleErrors = walkObjectArg(validationEnvironment, (GraphQLInputObjectType) inputType, objectValue);
146+
errors.addAll(ruleErrors);
147+
} else {
148+
Assert.assertShouldNeverHappen("How can there be a `input` object type '%s' that does not have a matching Map java value", GraphQLTypeUtil.simplePrint(inputType));
149+
}
150+
}
151+
return errors;
152+
}
153+
154+
private List<GraphQLError> walkObjectArg(ValidationEnvironment validationEnvironment, GraphQLInputObjectType argumentType, Map<String, Object> objectMap) {
155+
List<GraphQLError> errors = new ArrayList<>();
156+
157+
// run them in a stable order
158+
List<GraphQLInputObjectField> fieldDefinitions = Util.sort(argumentType.getFieldDefinitions(), GraphQLInputObjectField::getName);
159+
for (GraphQLInputObjectField inputField : fieldDefinitions) {
160+
161+
GraphQLInputType fieldType = inputField.getType();
162+
List<GraphQLDirective> directives = inputField.getDirectives();
163+
Object validatedValue = objectMap.getOrDefault(inputField.getName(), inputField.getDefaultValue());
164+
if (validatedValue == null) {
165+
continue;
166+
}
167+
168+
ExecutionPath fieldOrArgPath = validationEnvironment.getFieldOrArgumentPath().segment(inputField.getName());
169+
170+
ValidationEnvironment newValidationEnvironment = validationEnvironment.transform(builder -> builder
171+
.fieldOrArgumentPath(fieldOrArgPath)
172+
.validatedValue(validatedValue)
173+
.fieldOrArgumentType(fieldType)
174+
);
175+
176+
List<GraphQLError> ruleErrors = runValidationImpl(newValidationEnvironment, fieldType, validatedValue, directives);
177+
errors.addAll(ruleErrors);
178+
}
179+
return errors;
180+
}
181+
182+
private List<GraphQLError> walkListArg(ValidationEnvironment validationEnvironment, GraphQLList argumentType, List<Object> objectList) {
183+
List<GraphQLError> errors = new ArrayList<>();
184+
185+
GraphQLInputType listItemType = Util.unwrapOneAndAllNonNull(argumentType);
186+
List<GraphQLDirective> directives;
187+
if (!(listItemType instanceof GraphQLDirectiveContainer)) {
188+
directives = Collections.emptyList();
189+
} else {
190+
directives = ((GraphQLDirectiveContainer) listItemType).getDirectives();
191+
}
192+
int ix = 0;
193+
for (Object value : objectList) {
194+
195+
ExecutionPath fieldOrArgPath = validationEnvironment.getFieldOrArgumentPath().segment(ix);
196+
197+
ValidationEnvironment newValidationEnvironment = validationEnvironment.transform(builder -> builder
198+
.fieldOrArgumentPath(fieldOrArgPath)
199+
.validatedValue(value)
200+
.fieldOrArgumentType(listItemType)
201+
);
202+
203+
List<GraphQLError> ruleErrors = runValidationImpl(newValidationEnvironment, listItemType, value, directives);
204+
errors.addAll(ruleErrors);
205+
ix++;
206+
}
207+
return errors;
208+
}
209+
210+
82211
/**
83212
* Returns true of the input type is one of the specified scalar types, regardless of non null ness
84213
*
@@ -208,6 +337,7 @@ protected Map<String, Object> mkMessageParams(Object validatedValue, ValidationE
208337
return params;
209338
}
210339

340+
211341
/**
212342
* Makes a map of the args
213343
*

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

Lines changed: 15 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
11
package graphql.validation.constraints;
22

3-
import graphql.Assert;
4-
import graphql.GraphQLError;
53
import graphql.PublicApi;
6-
import graphql.execution.ExecutionPath;
74
import graphql.schema.GraphQLArgument;
8-
import graphql.schema.GraphQLDirective;
9-
import graphql.schema.GraphQLDirectiveContainer;
105
import graphql.schema.GraphQLFieldDefinition;
116
import graphql.schema.GraphQLFieldsContainer;
12-
import graphql.schema.GraphQLInputObjectField;
13-
import graphql.schema.GraphQLInputObjectType;
14-
import graphql.schema.GraphQLInputType;
15-
import graphql.schema.GraphQLList;
16-
import graphql.schema.GraphQLTypeUtil;
17-
import graphql.util.FpKit;
187
import graphql.validation.constraints.standard.ArgumentsConstraint;
198
import graphql.validation.constraints.standard.AssertFalseConstraint;
209
import graphql.validation.constraints.standard.AssertTrueConstraint;
@@ -32,26 +21,24 @@
3221
import graphql.validation.constraints.standard.PositiveOrZeroConstraint;
3322
import graphql.validation.constraints.standard.RangeConstraint;
3423
import graphql.validation.constraints.standard.SizeConstraint;
35-
import graphql.validation.rules.ValidationEnvironment;
36-
import graphql.validation.rules.ValidationRule;
37-
import graphql.validation.util.Util;
3824

39-
import java.util.ArrayList;
4025
import java.util.Arrays;
4126
import java.util.Collections;
4227
import java.util.LinkedHashMap;
4328
import java.util.List;
4429
import java.util.Map;
4530

31+
import static java.util.stream.Collectors.toList;
32+
4633
/**
47-
* This contains a liszt of {@link graphql.validation.constraints.DirectiveConstraint}s and
48-
* runs them as a group on a field and its argument values.
34+
* This contains a map of {@link graphql.validation.constraints.DirectiveConstraint}s and helps
35+
* run them against a specific field or argument
4936
* <p>
5037
* This ships with a set of standard constraints via {@link #STANDARD_CONSTRAINTS} but you can
5138
* add your own implementations if you wish
5239
*/
5340
@PublicApi
54-
public class DirectiveConstraints implements ValidationRule {
41+
public class DirectiveConstraints {
5542

5643
/**
5744
* These are the standard directive rules that come with the system
@@ -98,141 +85,20 @@ public String getDirectivesSDL() {
9885
return sb.toString();
9986
}
10087

101-
@Override
102-
public boolean appliesTo(GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer) {
103-
for (DirectiveConstraint directiveRule : constraints.values()) {
104-
boolean applies = directiveRule.appliesTo(fieldDefinition, fieldsContainer);
105-
if (applies) {
106-
return true;
107-
}
108-
}
109-
return false;
110-
}
111-
112-
@Override
113-
public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer) {
114-
for (DirectiveConstraint directiveRule : constraints.values()) {
115-
boolean applies = directiveRule.appliesTo(argument, fieldDefinition, fieldsContainer);
116-
if (applies) {
117-
return true;
118-
}
119-
}
120-
return false;
121-
}
122-
123-
@SuppressWarnings("unchecked")
124-
@Override
125-
public List<GraphQLError> runValidation(ValidationEnvironment validationEnvironment) {
126-
127-
GraphQLArgument argument = validationEnvironment.getArgument();
128-
Object validatedValue = validationEnvironment.getValidatedValue();
129-
List<GraphQLDirective> directives = argument.getDirectives();
130-
131-
//
132-
// all the directives validation code does NOT care for NULL ness since the graphql engine covers that.
133-
// eg a @NonNull validation directive makes no sense in graphql like it might in Java
134-
//
135-
GraphQLInputType inputType = Util.unwrapNonNull(validationEnvironment.getFieldOrArgumentType());
136-
validationEnvironment = validationEnvironment.transform(b -> b.fieldOrArgumentType(inputType));
137-
138-
return runValidationImpl(validationEnvironment, inputType, validatedValue, directives);
139-
}
140-
141-
@SuppressWarnings("unchecked")
142-
private List<GraphQLError> runValidationImpl(ValidationEnvironment validationEnvironment, GraphQLInputType inputType, Object validatedValue, List<GraphQLDirective> directives) {
143-
List<GraphQLError> errors = new ArrayList<>();
144-
for (GraphQLDirective directive : directives) {
145-
DirectiveConstraint validationRule = constraints.get(directive.getName());
146-
if (validationRule == null) {
147-
continue;
148-
}
149-
150-
validationEnvironment = validationEnvironment.transform(b -> b.context(GraphQLDirective.class, directive));
151-
//
152-
// now run the directive rule with this directive instance
153-
List<GraphQLError> ruleErrors = validationRule.runValidation(validationEnvironment);
154-
errors.addAll(ruleErrors);
155-
}
156-
157-
if (validatedValue == null) {
158-
return errors;
159-
}
160-
161-
inputType = (GraphQLInputType) GraphQLTypeUtil.unwrapNonNull(inputType);
162-
163-
if (GraphQLTypeUtil.isList(inputType)) {
164-
List<Object> values = new ArrayList<>(FpKit.toCollection(validatedValue));
165-
List<GraphQLError> ruleErrors = walkListArg(validationEnvironment, (GraphQLList) inputType, values);
166-
errors.addAll(ruleErrors);
167-
}
168-
169-
if (inputType instanceof GraphQLInputObjectType) {
170-
if (validatedValue instanceof Map) {
171-
Map<String, Object> objectValue = (Map<String, Object>) validatedValue;
172-
List<GraphQLError> ruleErrors = walkObjectArg(validationEnvironment, (GraphQLInputObjectType) inputType, objectValue);
173-
errors.addAll(ruleErrors);
174-
} else {
175-
Assert.assertShouldNeverHappen("How can there be a `input` object type '%s' that does not have a matching Map java value", GraphQLTypeUtil.simplePrint(inputType));
176-
}
177-
}
178-
return errors;
179-
}
180-
181-
private List<GraphQLError> walkObjectArg(ValidationEnvironment validationEnvironment, GraphQLInputObjectType argumentType, Map<String, Object> objectMap) {
182-
List<GraphQLError> errors = new ArrayList<>();
183-
184-
for (GraphQLInputObjectField inputField : argumentType.getFieldDefinitions()) {
185-
186-
GraphQLInputType fieldType = inputField.getType();
187-
List<GraphQLDirective> directives = inputField.getDirectives();
188-
Object validatedValue = objectMap.getOrDefault(inputField.getName(), inputField.getDefaultValue());
189-
if (validatedValue == null) {
190-
continue;
191-
}
192-
193-
ExecutionPath fieldOrArgPath = validationEnvironment.getFieldOrArgumentPath().segment(inputField.getName());
194-
195-
ValidationEnvironment newValidationEnvironment = validationEnvironment.transform(builder -> builder
196-
.fieldOrArgumentPath(fieldOrArgPath)
197-
.validatedValue(validatedValue)
198-
.fieldOrArgumentType(fieldType)
199-
);
200-
201-
List<GraphQLError> ruleErrors = runValidationImpl(newValidationEnvironment, fieldType, validatedValue, directives);
202-
errors.addAll(ruleErrors);
203-
}
204-
return errors;
88+
public List<DirectiveConstraint> whichApplyTo(GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer) {
89+
return constraints.values()
90+
.stream()
91+
.filter(c -> c.appliesTo(fieldDefinition, fieldsContainer))
92+
.collect(toList());
20593
}
20694

207-
private List<GraphQLError> walkListArg(ValidationEnvironment validationEnvironment, GraphQLList argumentType, List<Object> objectList) {
208-
List<GraphQLError> errors = new ArrayList<>();
209-
210-
GraphQLInputType listItemType = Util.unwrapOneAndAllNonNull(argumentType);
211-
List<GraphQLDirective> directives;
212-
if (!(listItemType instanceof GraphQLDirectiveContainer)) {
213-
directives = Collections.emptyList();
214-
} else {
215-
directives = ((GraphQLDirectiveContainer) listItemType).getDirectives();
216-
}
217-
int ix = 0;
218-
for (Object value : objectList) {
219-
220-
ExecutionPath fieldOrArgPath = validationEnvironment.getFieldOrArgumentPath().segment(ix);
221-
222-
ValidationEnvironment newValidationEnvironment = validationEnvironment.transform(builder -> builder
223-
.fieldOrArgumentPath(fieldOrArgPath)
224-
.validatedValue(value)
225-
.fieldOrArgumentType(listItemType)
226-
);
227-
228-
List<GraphQLError> ruleErrors = runValidationImpl(newValidationEnvironment, listItemType, value, directives);
229-
errors.addAll(ruleErrors);
230-
ix++;
231-
}
232-
return errors;
95+
public List<DirectiveConstraint> whichApplyTo(GraphQLArgument argument, GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer) {
96+
return constraints.values()
97+
.stream()
98+
.filter(c -> c.appliesTo(argument, fieldDefinition, fieldsContainer))
99+
.collect(toList());
233100
}
234101

235-
236102
public static class Builder {
237103
private Map<String, DirectiveConstraint> directiveRules = new LinkedHashMap<>();
238104

src/main/java/graphql/validation/constraints/standard/AbstractAssertConstraint.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public boolean appliesToType(GraphQLInputType inputType) {
2626
}
2727

2828
@Override
29-
public List<GraphQLError> runValidation(ValidationEnvironment validationEnvironment) {
29+
protected List<GraphQLError> runConstraint(ValidationEnvironment validationEnvironment) {
3030
Object validatedValue = validationEnvironment.getValidatedValue();
3131
//null values are valid
3232
if (validatedValue == null) {

0 commit comments

Comments
 (0)