Skip to content

Commit d9b4266

Browse files
committed
Added runRule to possible to make it easier to run in a data fetcher by hand
1 parent fa779a5 commit d9b4266

File tree

5 files changed

+258
-11
lines changed

5 files changed

+258
-11
lines changed

readme.md

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,17 @@ This library provides extended validation of fields and field arguments for [gra
55

66
# Status
77

8-
This code is currently under construction. There is no release artifact and not all parts of it are ready.
8+
This code is currently under construction. It is fairly complete in providing powerful validation
9+
but as it has NOT be consumed by a production like project then its API usefulness has not been tested
10+
and its code has not been battle tested.
911

1012
But the project welcomes all feedback and input on code design and validation requirements.
1113

12-
It currently passes MOST of its tests - but not all. This is a matter of attention and time.
13-
14-
It has NOT yet been consumed by a production like project and hence its API usefulness has not been tested
15-
16-
# Using the code
17-
18-
TODO
19-
2014

2115
# SDL @Directive constraints
2216

23-
This library a series of directives that can be applied to field arguments and input type fields which will constrain their allowable values.
17+
This library provides a series of directives that can be applied to field arguments and input type fields which will
18+
constrain their allowable values.
2419

2520
These names and semantics are inspired from the javax.validation annotations
2621

@@ -144,6 +139,68 @@ considered valid.
144139
The default strategy `OnValidationErrorStrategy.RETURN_NULL` will return null for the field input if it is not considered valid. You can
145140
write your own strategy if you want.
146141

142+
## Using the API direct in your own data fetchers
143+
144+
We recommend that you use the SDL schema directive wiring and @directives for the easiest way to get input type validation.
145+
146+
However there can be reasons why you cant use this approach and you have to manually use the API directly in your data fetching code.
147+
148+
```java
149+
150+
//
151+
// an example of writing your own custom validation rule
152+
//
153+
ValidationRule myCustomValidationRule = new ValidationRule() {
154+
@Override
155+
public boolean appliesTo(GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer) {
156+
return fieldDefinition.getName().equals("decide whether this rule applies here");
157+
}
158+
159+
@Override
160+
public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer) {
161+
return argument.getName().equals("decide whether this rule applies here to an argument");
162+
}
163+
164+
@Override
165+
public List<GraphQLError> runValidation(ValidationEnvironment validationEnvironment) {
166+
167+
List<GraphQLError> errors = new ArrayList<>();
168+
Map<String, Object> argumentValues = validationEnvironment.getArgumentValues();
169+
for (String argName : argumentValues.keySet()) {
170+
Object argValue = argumentValues.get(argName);
171+
GraphQLError error = runCodeThatValidatesInputHere(validationEnvironment, argName, argValue);
172+
if (error != null) {
173+
errors.add(error);
174+
}
175+
}
176+
return errors;
177+
}
178+
};
179+
180+
PossibleValidationRules possibleValidationRules = PossibleValidationRules
181+
.newPossibleRules()
182+
.addRule(myCustomValidationRule)
183+
.build();
184+
185+
DataFetcher dataFetcher = new DataFetcher() {
186+
@Override
187+
public Object get(DataFetchingEnvironment env) {
188+
189+
List<GraphQLError> errors = possibleValidationRules.runValidationRules(env);
190+
if (!errors.isEmpty()) {
191+
return DataFetcherResult.newResult().errors(errors).data(null).build();
192+
}
193+
194+
return normalDataFetchingCodeRunsNow(env);
195+
}
196+
};
197+
```
198+
199+
The above code shows a custom validation rule (with nonsense logic for demonstration purposes) and then a data fetcher
200+
that uses the `PossibleValidationRules` API to run validation rules. Its is called "possible" because it can contain more rules that may
201+
or may apply to a field, argument or parent field container.
202+
203+
147204
## The supplied @Directive constraints
148205

149206
<!-- generated by DocHelper on 2019-08-17T11:55:22.933Z -->

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package graphql.validation.rules;
22

3+
import graphql.GraphQLError;
34
import graphql.PublicApi;
5+
import graphql.schema.DataFetchingEnvironment;
46
import graphql.schema.GraphQLArgument;
57
import graphql.schema.GraphQLFieldDefinition;
68
import graphql.schema.GraphQLFieldsContainer;
@@ -87,6 +89,27 @@ public List<ValidationRule> getRulesFor(GraphQLFieldDefinition fieldDefinition,
8789
.collect(Collectors.toList());
8890
}
8991

92+
93+
/**
94+
* This helper method will run the validation rules that apply to the provided {@link graphql.schema.DataFetchingEnvironment}
95+
*
96+
* @param env the data fetching environment
97+
* @return a list of zero or more input data validation errors
98+
*/
99+
public List<GraphQLError> runValidationRules(DataFetchingEnvironment env) {
100+
GraphQLFieldsContainer fieldsContainer = env.getExecutionStepInfo().getFieldContainer();
101+
GraphQLFieldDefinition fieldDefinition = env.getFieldDefinition();
102+
103+
//
104+
// a future version of graphql-java will have the locale within the DataFetchingEnvironment
105+
Locale locale = this.getLocale();
106+
MessageInterpolator messageInterpolator = this.getMessageInterpolator();
107+
108+
ValidationRules rules = this.buildRulesFor(fieldDefinition, fieldsContainer);
109+
return rules.runValidationRules(env, messageInterpolator, locale);
110+
}
111+
112+
90113
public static class Builder {
91114
private Locale locale;
92115
private OnValidationErrorStrategy onValidationErrorStrategy = OnValidationErrorStrategy.RETURN_NULL;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import graphql.schema.GraphQLDirective;
1010
import graphql.schema.GraphQLDirectiveContainer;
1111
import graphql.schema.GraphQLFieldDefinition;
12+
import graphql.schema.GraphQLFieldsContainer;
1213
import graphql.schema.GraphQLInputObjectField;
1314
import graphql.schema.GraphQLInputObjectType;
1415
import graphql.schema.GraphQLInputType;
@@ -59,7 +60,7 @@ public boolean isEmpty() {
5960
* @param env the field being executed
6061
* @param interpolator the message interpolator to use
6162
* @param locale the locale in play
62-
* @return a list of zero or more data validation errors
63+
* @return a list of zero or more input data validation errors
6364
*/
6465
public List<GraphQLError> runValidationRules(DataFetchingEnvironment env, MessageInterpolator interpolator, Locale locale) {
6566

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package graphql.validation.rules
2+
3+
import graphql.GraphQL
4+
import graphql.execution.DataFetcherResult
5+
import graphql.schema.DataFetcher
6+
import graphql.schema.idl.RuntimeWiring
7+
import graphql.validation.TestUtil
8+
import graphql.validation.constraints.DirectiveConstraints
9+
import spock.lang.Specification
10+
11+
import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring
12+
13+
class PossibleValidationRulesTest extends Specification {
14+
15+
16+
def directiveRules = DirectiveConstraints.newDirectiveConstraints().build()
17+
18+
def sdl = '''
19+
20+
''' + directiveRules.directivesSDL + '''
21+
22+
type Car {
23+
model : String
24+
make : String
25+
}
26+
27+
input CarFilter {
28+
model : String @Size(max : 10)
29+
make : String
30+
age : Int @Range(max : 5) @Expression(value : "${validatedValue==20}")
31+
}
32+
33+
34+
type Query {
35+
cars(filter : CarFilter) : [Car] @Expression(value : "${false}")
36+
}
37+
'''
38+
39+
def "run rules for data fetcher environment direct from possible rules"() {
40+
41+
42+
DataFetcher carsDF = { env ->
43+
PossibleValidationRules possibleRules = PossibleValidationRules
44+
.newPossibleRules().build()
45+
def errors = possibleRules.runValidationRules(env)
46+
if (!errors.isEmpty()) {
47+
return DataFetcherResult.newResult().errors(errors).data(null).build()
48+
}
49+
return [
50+
[model: "Prado", make: "Toyota"]
51+
]
52+
}
53+
54+
def runtime = RuntimeWiring.newRuntimeWiring()
55+
.type(newTypeWiring("Query").dataFetcher("cars", carsDF))
56+
.build()
57+
def schema = TestUtil.schema(sdl, runtime)
58+
def graphQL = GraphQL.newGraphQL(schema).build()
59+
60+
when:
61+
def er = graphQL.execute('''
62+
{
63+
cars (filter : { model : "Ford OR Toyota", age : 20 }) {
64+
model
65+
make
66+
}
67+
}
68+
''')
69+
70+
then:
71+
def specification = er.toSpecification()
72+
specification != null
73+
74+
er.errors.size() == 3
75+
er.errors[0].message == "/cars expression must evaluate to true"
76+
er.errors[0].path == ["cars"]
77+
er.errors[1].message == "/cars/filter/age range must be between 0 and 5"
78+
er.errors[1].path == ["cars"]
79+
er.errors[2].message == "/cars/filter/model size must be between 0 and 10"
80+
er.errors[2].path == ["cars"]
81+
}
82+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package examples;
2+
3+
import graphql.GraphQLError;
4+
import graphql.execution.DataFetcherResult;
5+
import graphql.schema.DataFetcher;
6+
import graphql.schema.DataFetchingEnvironment;
7+
import graphql.schema.GraphQLArgument;
8+
import graphql.schema.GraphQLFieldDefinition;
9+
import graphql.schema.GraphQLFieldsContainer;
10+
import graphql.validation.rules.PossibleValidationRules;
11+
import graphql.validation.rules.ValidationEnvironment;
12+
import graphql.validation.rules.ValidationRule;
13+
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import java.util.Map;
17+
18+
@SuppressWarnings({"unused", "Convert2Lambda"})
19+
public class DataFetcherExample {
20+
21+
22+
public static void main(String[] args) {
23+
24+
//
25+
// an example of writing your own custom validation rule
26+
//
27+
ValidationRule myCustomValidationRule = new ValidationRule() {
28+
@Override
29+
public boolean appliesTo(GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer) {
30+
return fieldDefinition.getName().equals("decide whether this rule applies here");
31+
}
32+
33+
@Override
34+
public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer) {
35+
return argument.getName().equals("decide whether this rule applies here to an argument");
36+
}
37+
38+
@Override
39+
public List<GraphQLError> runValidation(ValidationEnvironment validationEnvironment) {
40+
41+
List<GraphQLError> errors = new ArrayList<>();
42+
Map<String, Object> argumentValues = validationEnvironment.getArgumentValues();
43+
for (String argName : argumentValues.keySet()) {
44+
Object argValue = argumentValues.get(argName);
45+
GraphQLError error = runCodeThatValidatesInputHere(validationEnvironment, argName, argValue);
46+
if (error != null) {
47+
errors.add(error);
48+
}
49+
}
50+
return errors;
51+
}
52+
};
53+
54+
PossibleValidationRules possibleValidationRules = PossibleValidationRules
55+
.newPossibleRules()
56+
.addRule(myCustomValidationRule)
57+
.build();
58+
59+
DataFetcher dataFetcher = new DataFetcher() {
60+
@Override
61+
public Object get(DataFetchingEnvironment env) {
62+
63+
List<GraphQLError> errors = possibleValidationRules.runValidationRules(env);
64+
if (!errors.isEmpty()) {
65+
return DataFetcherResult.newResult().errors(errors).data(null).build();
66+
}
67+
68+
return normalDataFetchingCodeRunsNow(env);
69+
}
70+
};
71+
72+
73+
}
74+
75+
private static GraphQLError runCodeThatValidatesInputHere(ValidationEnvironment validationEnvironment, String argName, Object argValue) {
76+
return null;
77+
}
78+
79+
private static Object normalDataFetchingCodeRunsNow(DataFetchingEnvironment env) {
80+
return null;
81+
}
82+
83+
84+
}

0 commit comments

Comments
 (0)