Skip to content

Commit fb629e1

Browse files
committed
Implement nullability operators in query language
The ["Nullability RFC" for GraphQL](graphql/graphql-wg#694) allows fields to individually be marked as optional or required in a query by the client-side. ([See Strawman Proposal](graphql/graphql-spec#867)) If a field is marked as optional then it's allowed to be missing and `null`, which can control where missing values cascade to: ```graphql query { me { name? } } ``` If a field is marked as required it may never be allowed to become `null` and must cascade if it otherwise would have been set to `null`: ```graphql query { me { name! } } ```
1 parent 1a5ff4f commit fb629e1

File tree

3 files changed

+24
-3
lines changed

3 files changed

+24
-3
lines changed

alias/language/__tests__/printer.test.js

+8
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ describe('Printer: Query document', () => {
5454
name
5555
}
5656
`);
57+
58+
const queryWithNullabilityFields = parse('query { id?, name! }');
59+
expect(print(queryWithNullabilityFields)).toBe(dedent`
60+
{
61+
id?
62+
name!
63+
}
64+
`);
5765
});
5866

5967
it('prints query with variable directives', () => {

alias/language/parser.mjs

+9
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,20 @@ const directives = match()`
147147
${directive}*
148148
`;
149149

150+
const nullability = match(null, (x) => {
151+
return x[0] === '?' ? 'optional' : 'required';
152+
})`
153+
:${ignored}?
154+
${/[?!]/}
155+
`;
156+
150157
const field = match(Kind.FIELD, (x) => {
151158
let i = 0;
152159
return {
153160
kind: x.tag,
154161
alias: x[1].kind === Kind.NAME ? x[i++] : undefined,
155162
name: x[i++],
163+
required: typeof x[i] === 'string' ? x[i++] : 'unset',
156164
arguments: x[i++],
157165
directives: x[i++],
158166
selectionSet: x[i++],
@@ -164,6 +172,7 @@ const field = match(Kind.FIELD, (x) => {
164172
(?: ${ignored}? ${':'} ${ignored}?)
165173
${name}
166174
)?
175+
${nullability}?
167176
${args}
168177
${directives}
169178
${() => selectionSet}?

alias/language/printer.mjs

+7-3
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@ export function print(node) {
3636
);
3737

3838
case 'Field':
39+
let prefix = wrap('', print(node.alias), ': ') + print(node.name);
40+
if (node.required === 'optional') {
41+
prefix += '?';
42+
} else if (node.required === 'required') {
43+
prefix += '!';
44+
}
3945
return join(
4046
[
41-
wrap('', print(node.alias), ': ') +
42-
print(node.name) +
43-
wrap('(', join(print(node.arguments), ', '), ')'),
47+
prefix + wrap('(', join(print(node.arguments), ', '), ')'),
4448
join(print(node.directives), ' '),
4549
print(node.selectionSet),
4650
],

0 commit comments

Comments
 (0)