Working Draft - June 2017
GraphQL Constraints Directives is a set of directives that allows you to annotate GraphQL types. The primary use case for these annotations is the validation of input parameters. However, it may be used by any tooling including documentation, GraphiQL, form-generation, etc. It is inspired by JSON Schema and reuses as much semantics as possible from it.
Note: Reference implementation is coming
The issues list for this draft can be found at https://github.com/apis-guru/graphql-constraints-spec/issues To provide feedback, use this issue tracker, or email the document editors.
All the examples below will be based on GraphQL IDL.
- Constraints Directive - is a group of related constraints represented as a GraphQL directive
- Constraint - Atomic assertion which is represented as arguments and input values of Constraints Directive
- Instance - actual non-null value of argument, field, input field that is interpreted by the directive.
NOTE: null values are handled by GraphQL natively. All the constraints don't affect nullability.
When a Constraint Directive has more than one constraint in it, they should be treated as logical AND between individual constraints.
TBD
In GraphQL there are types (String
, Int
, Float
, ID
, Boolean
and various user-defined types) and type wrappers (List
and Not-Null
).
Accordingly, this document describes two kinds of Constraints Directives: Type Constraints Directives
and Wrapper Constraints Directives
.
TBD
If applied before coercion, possible issue with ID
type since it accepts both integers and strings.
Type Constraints Directives can be applied to the following GraphQL entities:
- Object Field Definition
- Input Object Field Definition
- Object Field Arguments Definition
- Directive Argument Definition
- Scalar Definition
Note: Only one Type Constraint per entity is allowed.
If Type Constraint is applied to an entity of List
wrapper type it describes innermost values of the lists.
Type Constraints Directives do not override GraphQL standard scalars semantic and runtime behavior. Moreover, each Type Constraints Directive is compatible only with specific standard scalars
Directive\Type | Float | Int | Boolean | String | ID |
---|---|---|---|---|---|
@numberValue | + | + | - | - | - |
@stringValue | - | - | - | + | + |
Applying a directive to a field definition, an argument definition or an input field definition of an incompatible standard type should result in an error.
@numberValue
directive is used to describe possible numeric values.
Instance is valid if it is a numeric value according to the Serialization Format (e.g. JSON)
The value of multipleOf
MUST be a number, strictly greater than 0
. A numeric instance is valid only if division by this constraint's value results in an integer.
The value of max
MUST be a number, representing an inclusive upper limit for a numeric instance. A numeric instance is valid only if the instance is less than or exactly equal to max
.
The value of min
MUST be a number, representing an inclusive upper limit for a numeric instance. A numeric instance is valid only if the instance is greater than or exactly equal to min
.
The value of exclusiveMax
MUST be a number, representing an exclusive upper limit for a numeric instance. A numeric instance is valid only if it is strictly less than (not equal to) exclusiveMax
.
The value of exclusiveMin
MUST be a number, representing an exclusive upper limit for a numeric instance. A numeric instance is valid only if it has a value strictly greater than (not equal to) exclusiveMin
.
The value of this argument MUST be an array. This array SHOULD have at least one element. Elements in the array SHOULD be unique. An instance is valid only if its value is equal to one of the elements in this constraint's array value.
A numeric instance is valid only if its value is equal to the value of the constrain.
type Foo {
byte:Integer @numberValue(
min: 0
max: 255
)
}
Examples of valid values for byte
field: 155
, 255
, 0
Examples of invalid values for byte
field: "string"
, 256
, -1
type Foo {
bitMask: Integer @numberValue(
oneOf: [ 1, 2, 4, 8, 16, 32, 64, 128 ]
)
}
Examples of valid values for bitMask
field: 1
, 16
, 128
Examples of invalid values for bitMask
field: "string"
, 3
, 5
@stringValue
directive is used to describe possible string values.
Instance is valid if it is a string value according to the Serialization Format (e.g. JSON)
The value of this constraint MUST be a non-negative integer.
A string instance is valid against this constraint if its length is less than, or equal to maxLength
. The length of a string instance is defined as the number of its characters.
The value of this constraint MUST be a non-negative integer.
A string instance is valid against this constraint if its length is greater than, or equal to minLength
. The length of a string instance is defined as the number of its characters.
The value of this constraint MUST be a string. An instance is valid if it begins with the characters of the constraint's string.
The value of this constraint MUST be a string. An instance is valid if it ends with the characters of the constraint's string.
The value of this constraint MUST be a string. An instance is valid if constraint's value may be found within the instance string.
The value of this constraint MUST be a string. This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect. An instance is valid if the regular expression matches the instance successfully. Recall: regular expressions are not implicitly anchored.
The same as for oneOf for @numberValue
The same as for equals for @numberValue
scalar AlphaNumeric @stringValue(
regex: "^[0-9a-zA-Z]*$"
)
Examples of valid values: "foo1"
, "Apollo13"
, 123test
Examples of invalid values: 3
, "dash-dash"
, admin@example.com
Wrapper Constraints Directives can be applied to the following GraphQL entities:
- Object Field Definition
- Input Object Field Definition
- Object Field Arguments Definition
- Directive Argument Definition
NOTE: Wrapper Constraints Directives can't be applied to Scalar Definitions
Wrapper Constraints Directives do not override GraphQL standard wrappers semantics and runtime behavior. Moreover, each Wrapper Constraints Directive is compatible only with specific GraphQL wrapper.
Applying a directive to a field definition, an argument definition or an input field definition without matching a wrapper type should result in an error.
@list
directive is used to describe list values. Instance is valid if it is a list according to the Serialization Format (e.g. JSON)
The value of this constraint MUST be a non-negative integer. An instance is valid if only its size is less than, or equal to, the value of this directive.
The value of this constraint MUST be a non-negative integer.
An instance is valid against minItems
if its size is greater than, or equal to, the value of this constraint.
Omitting this constraint has the same behavior as a value of 0.
The value of this constraint MUST be a boolean.
If it has boolean value true
, the instance is valid if all of its elements are unique.
This constraint is used to describe constraints of the nested List. The value of this constraint MUST be an object. This may contain fields with the same name as arguments of @list
directive. Semantic of these fields is the same but applied to the inner list.
type Foo {
point3D: [Float] @list(
maxItems: 3,
minItems: 3
)
}
Examples of valid values for point3D
field: [1, 2, 3]
, [-10, 2.5, 100]
Examples of invalid values for point3D
field: [-1, 0]
, [-1, 0, 100, 0]
type Foo {
pointOnScreen: [Float] @list(
maxItems: 2,
minItems: 2
) @numberValue(min: 0.0)
}
Examples of valid values for pointOnScreen
field: [1, 2.5]
, [0, 100]
Examples of invalid values for pointOnScreen
field: [-10, 100]
, [100, -100]
, [0, 0, 0]
type ticTacToe {
board: [[String!]!] @list(
minItems: 3,
maxItems: 3
innerList: {
minItems: 3,
maxItems: 3
}
) @stringValue(oneOf: [" ","X", "O"])
}
Examples of valid value for board
field:
[
[" ", " ", " "],
[" ", "X", " "],
["O", " ", " "]
]
Examples of invalid values: []
, [[],[],[]]
, "Empty board"
,
[
[" ", " ", " "],
[" ", "Y", " "],
["N", " ", " "]
]
TBD
Some ideas:
Variant1: Allows to provide a single error for multiple constraints but not intuitive structure
age: Float @numberValue(
min: 0,
max: 100,
errors: [{
constraints: [min, max]
message: "Invalid age ${value}, should be between ${min} and ${max} years"
code: "AGE_OUT_OF_RANGE"
}]
)
Variant2: Simpler in terms of DX but some messages duplication
age: Integer @numberValue(
min: 0,
max: 100,
errors: {
min: {
message: Invalid age ${value}, should be greater than ${min} years",
code: "AGE_LT_MINIMUM"
},
max: {
message: "Invalid age ${value}, should be between less than {max} years",
code: "AGE_GT_MAXIMUM"
}
}
)
type Foo {
bar: [Int] @numberValue(
multipleOf: 0.01
) @list({
minItems: 1,
maxItems: 3,
uniqueItems: true
})
}
Examples of valid values for bar
field: [1, 2, 3]
, [0.01, 0.02]
, [0.99]
Examples of invalid values for bar
field: [0.999]
, []
, [1, 2, 3, 4]
, [1.001, 2]
, [1, 1]
type Query {
allPersons(
first: Integer @numberValue(min: 1, max: 25)
after: String
last: Integer @numberValue(min: 1, max: 25)
before: String
): [Foo!]
}
Examples of valid values for first
and last
input arguments: 1
, 25
, 10
Examples of invalid values for first
and last
input arguments: 0
, 30
TBD