Skip to content

Commit 736f604

Browse files
committed
Moar tests an some documentation
1 parent 5e27f88 commit 736f604

38 files changed

+704
-38
lines changed

TODO.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
3+
# add the ability to do inter argument validation
4+
5+
eg imagine
6+
7+
field( first : Int, after : ID, last : Int, before : ID)
8+
9+
can we do a validation rule where we require `first`+`after` or `last` + `before` but not all 4 together??
10+
11+
Currently the rules are single argument specific - we would need field specific rules
12+

build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ dependencies {
4141
compile "com.graphql-java:graphql-java:12.0"
4242
compile "javax.validation:validation-api:2.0.1.Final"
4343
compile "org.hibernate.validator:hibernate-validator:6.0.10.Final"
44-
45-
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
44+
compile "javax.el:javax.el-api:3.0.0"
45+
compile "org.glassfish.web:javax.el:2.2.6"
46+
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
4647
testCompile 'org.codehaus.groovy:groovy-all:2.5.7'
4748
}
4849

readme.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,155 @@
22

33
This library provides extended validation for [graphql-java](https://github.com/graphql-java/graphql-java)
44

5+
6+
# Status
7+
8+
This code is current under construction. There is no release and not all parts of it are ready.
9+
10+
But the project welcomes all feedback and input on code design and validation requirements.
11+
12+
# Using the code
13+
14+
TODO
15+
16+
# SDL @Directive validation
17+
18+
This library a series of directives that can be applied to field arguments and input type fields which will constrain the values
19+
20+
These names and semantics are inspired from the javax.validation annotations
21+
22+
https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/package-summary.html
23+
24+
## SDL directives
25+
26+
You can add these onto arguments or input types in your graphql SDL.
27+
28+
For example
29+
30+
```graphql
31+
32+
#
33+
# this declares the directive as being possible on arguments and input fields
34+
#
35+
directive @Size(min : Int = 0, max : Int = 2147483647, message : String = "graphql.validation.Size.message")
36+
on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
37+
38+
input Application {
39+
name : String @Size(min : 3, max : 100)
40+
}
41+
42+
type Query {
43+
hired (applications : [Application!] @Size(max : 10)) : [Boolean]
44+
}
45+
```
46+
47+
In the example above, we have a `applications` argument that takes at most 10 applications and within each `Application` input object
48+
the `name` field must be at least 3 characters long and no more than 100 characters long to be considered valid.
49+
50+
51+
## The library supplied directives
52+
53+
### AssertFalse
54+
55+
The boolean value must be false.
56+
57+
- SDL : `directive @AssertFalse(message : String = "graphql.validation.AssertFalse.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
58+
- Applies to : `Boolean`
59+
60+
### AssertTrue
61+
62+
The boolean value must be true.
63+
64+
- SDL : `directive @AssertTrue(message : String = "graphql.validation.AssertTrue.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
65+
- Applies to : `Boolean`
66+
67+
### DecimalMax
68+
69+
The element must be a number whose value must be less than or equal to the specified maximum.
70+
71+
- SDL : `directive @DecimalMax(value : String!, inclusive : Boolean! = true, message : String = "graphql.validation.DecimalMax.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
72+
- Applies to : `String`, `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`
73+
74+
### DecimalMin
75+
76+
The element must be a number whose value must be greater than or equal to the specified minimum.
77+
78+
- SDL : `directive @DecimalMin(value : String!, inclusive : Boolean! = true, message : String = "graphql.validation.DecimalMin.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
79+
- Applies to : `String`, `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`
80+
81+
### Digits
82+
83+
The element must be a number inside the specified `integer` and `fraction` range.
84+
85+
- SDL : `directive @Digits(integer : Int!, fraction : Int!, message : String = "graphql.validation.Digits.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
86+
- Applies to : `String`, `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`
87+
88+
### Max
89+
90+
The element must be a number whose value must be less than or equal to the specified maximum.
91+
92+
- SDL : `directive @Max(value : Int! = 2147483647, message : String = "graphql.validation.Max.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
93+
- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`
94+
95+
### Min
96+
97+
The element must be a number whose value must be greater than or equal to the specified minimum.
98+
99+
- SDL : `directive @Min(value : Int! = 0, message : String = "graphql.validation.Min.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
100+
- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`
101+
102+
### Negative
103+
104+
The element must be a negative number.
105+
106+
- SDL : `directive @Negative(message : String = "graphql.validation.Negative.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
107+
- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`
108+
109+
### NegativeOrZero
110+
111+
The element must be a negative number or zero.
112+
113+
- SDL : `directive @NegativeOrZero(message : String = "graphql.validation.NegativeOrZero.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
114+
- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`
115+
116+
### NotBlank
117+
118+
The String must contain at least one non-whitespace character, according to Java's Character.isWhitespace().
119+
120+
- SDL : `directive @NotBlank(message : String = "graphql.validation.NotBlank.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
121+
- Applies to : `String`
122+
123+
### NotEmpty
124+
125+
The element must have a non zero size.
126+
127+
- SDL : `directive @NotEmpty(message : String = "graphql.validation.NotEmpty.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
128+
- Applies to : `String`, `Lists`, `Input Objects`
129+
130+
### Pattern
131+
132+
The String must match the specified regular expression, which follows the Java regular expression conventions.
133+
134+
- SDL : `directive @Pattern(pattern : String! =".*", message : String = "graphql.validation.Pattern.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
135+
- Applies to : `String`
136+
137+
### Positive
138+
139+
The element must be a positive number.
140+
141+
- SDL : `directive @Positive(message : String = "graphql.validation.Positive.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
142+
- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`
143+
144+
### PositiveOrZero
145+
146+
The element must be a positive number or zero.
147+
148+
- SDL : `directive @PositiveOrZero(message : String = "graphql.validation.PositiveOrZero.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
149+
- Applies to : `Byte`, `Short`, `Int`, `Long`, `BigDecimal`, `BigInteger`, `Float`
150+
151+
### Size
152+
153+
The element size must be between the specified `min` and `max` boundaries (inclusive).
154+
155+
- SDL : `directive @Size(min : Int = 0, max : Int = 2147483647, message : String = "graphql.validation.Size.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION`
156+
- Applies to : `String`, `Lists`, `Input Objects`
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
package graphql.validation;
22

3+
import graphql.PublicApi;
4+
5+
@PublicApi
36
public class ExtendedValidation {
47
}

src/main/java/graphql/validation/directives/AbstractDirectiveValidationRule.java

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import graphql.Assert;
44
import graphql.GraphQLError;
5+
import graphql.PublicSpi;
56
import graphql.Scalars;
67
import graphql.schema.GraphQLArgument;
78
import graphql.schema.GraphQLDirective;
@@ -23,6 +24,7 @@
2324
import static graphql.schema.GraphQLTypeUtil.isList;
2425
import static java.util.Collections.singletonList;
2526

27+
@PublicSpi
2628
public abstract class AbstractDirectiveValidationRule implements DirectiveValidationRule {
2729

2830
private final String name;
@@ -41,6 +43,14 @@ public boolean appliesToType(GraphQLArgument argument, GraphQLFieldDefinition fi
4143
return appliesToType(Util.unwrapNonNull(argument.getType()));
4244
}
4345

46+
/**
47+
* Returns true of the input type is one of the specified scalar types, regardless of non null ness
48+
*
49+
* @param inputType the type to check
50+
* @param scalarTypes the array of scalar types
51+
*
52+
* @return true ifits oneof them
53+
*/
4454
protected boolean isOneOfTheseTypes(GraphQLInputType inputType, GraphQLScalarType... scalarTypes) {
4555
GraphQLInputType unwrappedType = Util.unwrapNonNull(inputType);
4656
for (GraphQLScalarType scalarType : scalarTypes) {
@@ -51,6 +61,14 @@ protected boolean isOneOfTheseTypes(GraphQLInputType inputType, GraphQLScalarTyp
5161
return false;
5262
}
5363

64+
/**
65+
* Returns an integer argument from a directive (or its default) and throws an assertion of the argument is null
66+
*
67+
* @param directive the directive to check
68+
* @param argName the argument name
69+
*
70+
* @return a non null value
71+
*/
5472
protected int getIntArg(GraphQLDirective directive, String argName) {
5573
GraphQLArgument argument = directive.getArgument(argName);
5674
if (argument == null) {
@@ -66,6 +84,14 @@ protected int getIntArg(GraphQLDirective directive, String argName) {
6684
return value.intValue();
6785
}
6886

87+
/**
88+
* Returns an String argument from a directive (or its default) and throws an assertion of the argument is null
89+
*
90+
* @param directive the directive to check
91+
* @param argName the argument name
92+
*
93+
* @return a non null value
94+
*/
6995
protected String getStrArg(GraphQLDirective directive, String argName) {
7096
GraphQLArgument argument = directive.getArgument(argName);
7197
if (argument == null) {
@@ -81,6 +107,14 @@ protected String getStrArg(GraphQLDirective directive, String argName) {
81107
return value;
82108
}
83109

110+
/**
111+
* Returns an boolean argument from a directive (or its default) and throws an assertion of the argument is null
112+
*
113+
* @param directive the directive to check
114+
* @param argName the argument name
115+
*
116+
* @return a non null value
117+
*/
84118
protected boolean getBoolArg(GraphQLDirective directive, String argName) {
85119
GraphQLArgument argument = directive.getArgument(argName);
86120
if (argument == null) {
@@ -96,6 +130,14 @@ protected boolean getBoolArg(GraphQLDirective directive, String argName) {
96130
return Boolean.parseBoolean(String.valueOf(value));
97131
}
98132

133+
/**
134+
* Returns the "message : String" argument from a directive or makes up one
135+
* called "graphql.validation.{name}.message"
136+
*
137+
* @param directive the directive to check
138+
*
139+
* @return a non null value
140+
*/
99141
protected String getMessageTemplate(GraphQLDirective directive) {
100142
String msg = null;
101143
GraphQLArgument arg = directive.getArgument("message");
@@ -111,6 +153,13 @@ protected String getMessageTemplate(GraphQLDirective directive) {
111153
return msg;
112154
}
113155

156+
/**
157+
* Creates a map of named parameters for message interpolation
158+
*
159+
* @param args must be even with a String as even params and values as odd params
160+
*
161+
* @return a map of message parameters
162+
*/
114163
protected Map<String, Object> mkMessageParams(Object... args) {
115164
Assert.assertTrue(args.length % 2 == 0, "You MUST pass in an even number of arguments");
116165
Map<String, Object> params = new LinkedHashMap<>();
@@ -124,25 +173,51 @@ protected Map<String, Object> mkMessageParams(Object... args) {
124173
return params;
125174
}
126175

176+
/**
177+
* Creates a new {@link graphql.GraphQLError}
178+
*
179+
* @param ruleEnvironment the current rules environment
180+
* @param directive the directive being run
181+
* @param msgParams the map of parameters
182+
*
183+
* @return a list of a single error
184+
*/
127185
protected List<GraphQLError> mkError(ValidationRuleEnvironment ruleEnvironment, GraphQLDirective directive, Map<String, Object> msgParams) {
128186
String messageTemplate = getMessageTemplate(directive);
129187
GraphQLError error = ruleEnvironment.getInterpolator().interpolate(messageTemplate, msgParams, ruleEnvironment);
130188
return singletonList(error);
131189
}
132190

133-
protected boolean isStringOrListOrMap(GraphQLInputType argumentType) {
134-
GraphQLInputType unwrappedType = Util.unwrapOneAndAllNonNull(argumentType);
191+
/**
192+
* Return true if the type is a String or List type or {@link graphql.schema.GraphQLInputObjectType}, regardless of non null ness
193+
*
194+
* @param inputType the type to check
195+
*
196+
* @return true if one of the above
197+
*/
198+
protected boolean isStringOrListOrMap(GraphQLInputType inputType) {
199+
GraphQLInputType unwrappedType = Util.unwrapOneAndAllNonNull(inputType);
135200
return Scalars.GraphQLString.equals(unwrappedType) ||
136-
isList(argumentType) ||
201+
isList(inputType) ||
137202
(unwrappedType instanceof GraphQLInputObjectType);
138203
}
139204

205+
/**
206+
* Casts the object as a Map with an assertion of it is not one
207+
*
208+
* @return a Map
209+
*/
140210
@SuppressWarnings("ConstantConditions")
141211
protected Map asMap(Object value) {
142212
Assert.assertTrue(value instanceof Map, "The argument value MUST be a Map value");
143213
return (Map) value;
144214
}
145215

216+
/**
217+
* Makes the object a BigDecimal with an assertion if we have no conversion of it
218+
*
219+
* @return a BigDecimal
220+
*/
146221
protected BigDecimal asBigDecimal(Object value) throws NumberFormatException {
147222
if (value == null) {
148223
return Assert.assertShouldNeverHappen("Validation cant handle null objects BigDecimals");
@@ -161,6 +236,11 @@ protected BigDecimal asBigDecimal(Object value) throws NumberFormatException {
161236
return new BigDecimal(bdStr);
162237
}
163238

239+
/**
240+
* Makes the object a boolean with an assertion if we have no conversion of it
241+
*
242+
* @return a boolean
243+
*/
164244
protected boolean asBoolean(Object value) {
165245
if (value == null) {
166246
return Assert.assertShouldNeverHappen("Validation cant handle null objects Booleans");
@@ -172,16 +252,24 @@ protected boolean asBoolean(Object value) {
172252
}
173253
}
174254

175-
protected int getStringOrObjectOrMapLength(GraphQLInputType inputType, Object argumentValue) {
255+
/**
256+
* Returns the length of a String of the size of a list or size of a Map
257+
*
258+
* @param inputType the input type
259+
* @param value the value
260+
*
261+
* @return the length of a String or Map or List
262+
*/
263+
protected int getStringOrObjectOrMapLength(GraphQLInputType inputType, Object value) {
176264
int valLen;
177-
if (argumentValue == null) {
265+
if (value == null) {
178266
valLen = 0;
179267
} else if (Scalars.GraphQLString.equals(Util.unwrapNonNull(inputType))) {
180-
valLen = String.valueOf(argumentValue).length();
268+
valLen = String.valueOf(value).length();
181269
} else if (isList(inputType)) {
182-
valLen = getListLength(argumentValue);
270+
valLen = getListLength(value);
183271
} else {
184-
valLen = getObjectLen(argumentValue);
272+
valLen = getObjectLen(value);
185273
}
186274
return valLen;
187275
}

0 commit comments

Comments
 (0)