From a830bb18b254a2ad4521215512dd53c743191ca9 Mon Sep 17 00:00:00 2001 From: Wei Xue Date: Wed, 7 Apr 2021 10:26:36 -0700 Subject: [PATCH] Added support for client defined required field --- src/execution/execute.js | 4 +++- src/language/ast.d.ts | 1 + src/language/parser.js | 42 ++++++++++++++++++++++++++-------------- src/language/printer.js | 5 +++-- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/execution/execute.js b/src/execution/execute.js index 35f440c720..e63049804c 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -38,6 +38,7 @@ import type { GraphQLResolveInfo, GraphQLTypeResolver, GraphQLList, + GraphQLNonNull, } from '../type/definition'; import { assertValidSchema } from '../type/validate'; import { @@ -640,7 +641,8 @@ function resolveField( return; } - const returnType = fieldDef.type; + const returnType = fieldNode.required ? new GraphQLNonNull(fieldDef.type) : fieldDef.type + const resolveFn = fieldDef.resolve ?? exeContext.fieldResolver; const info = buildResolveInfo( diff --git a/src/language/ast.d.ts b/src/language/ast.d.ts index 61cb9f4eb5..3fc507a46b 100644 --- a/src/language/ast.d.ts +++ b/src/language/ast.d.ts @@ -267,6 +267,7 @@ export interface FieldNode { readonly arguments?: ReadonlyArray; readonly directives?: ReadonlyArray; readonly selectionSet?: SelectionSetNode; + readonly required?: Boolean; } export interface ArgumentNode { diff --git a/src/language/parser.js b/src/language/parser.js index 5d7b16186d..693895e9c5 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -225,9 +225,10 @@ export class Parser { if (this.peek(TokenKind.NAME)) { switch (this._lexer.token.value) { case 'query': + return this.parseOperationDefinition(true); case 'mutation': case 'subscription': - return this.parseOperationDefinition(); + return this.parseOperationDefinition(false); case 'fragment': return this.parseFragmentDefinition(); case 'schema': @@ -243,7 +244,7 @@ export class Parser { return this.parseTypeSystemExtension(); } } else if (this.peek(TokenKind.BRACE_L)) { - return this.parseOperationDefinition(); + return this.parseOperationDefinition(true); } else if (this.peekDescription()) { return this.parseTypeSystemDefinition(); } @@ -258,7 +259,7 @@ export class Parser { * - SelectionSet * - OperationType Name? VariableDefinitions? Directives? SelectionSet */ - parseOperationDefinition(): OperationDefinitionNode { + parseOperationDefinition(allowRequiredField: boolean): OperationDefinitionNode { const start = this._lexer.token; if (this.peek(TokenKind.BRACE_L)) { return { @@ -267,7 +268,7 @@ export class Parser { name: undefined, variableDefinitions: [], directives: [], - selectionSet: this.parseSelectionSet(), + selectionSet: this.parseSelectionSet(allowRequiredField), loc: this.loc(start), }; } @@ -282,7 +283,7 @@ export class Parser { name, variableDefinitions: this.parseVariableDefinitions(), directives: this.parseDirectives(false), - selectionSet: this.parseSelectionSet(), + selectionSet: this.parseSelectionSet(allowRequiredField), loc: this.loc(start), }; } @@ -348,13 +349,18 @@ export class Parser { /** * SelectionSet : { Selection+ } */ - parseSelectionSet(): SelectionSetNode { + parseSelectionSet(allowRequiredField: boolean = false): SelectionSetNode { const start = this._lexer.token; + const _allowRequiredField = allowRequiredField + let parseSelectionFn: () => SelectionNode = function (): SelectionNode { + return this.parseSelection(_allowRequiredField) + } + return { kind: Kind.SELECTION_SET, selections: this.many( TokenKind.BRACE_L, - this.parseSelection, + parseSelectionFn, TokenKind.BRACE_R, ), loc: this.loc(start), @@ -367,10 +373,10 @@ export class Parser { * - FragmentSpread * - InlineFragment */ - parseSelection(): SelectionNode { + parseSelection(allowRequiredField: boolean): SelectionNode { return this.peek(TokenKind.SPREAD) ? this.parseFragment() - : this.parseField(); + : this.parseField(allowRequiredField); } /** @@ -378,12 +384,19 @@ export class Parser { * * Alias : Name : */ - parseField(): FieldNode { + parseField(allowRequiredField: boolean): FieldNode { const start = this._lexer.token; const nameOrAlias = this.parseName(); let alias; let name; + let required; + if (allowRequiredField && this.expectOptionalToken(TokenKind.BANG)) { + required = true + } else { + required = false + } + if (this.expectOptionalToken(TokenKind.COLON)) { alias = nameOrAlias; name = this.parseName(); @@ -398,8 +411,9 @@ export class Parser { arguments: this.parseArguments(false), directives: this.parseDirectives(false), selectionSet: this.peek(TokenKind.BRACE_L) - ? this.parseSelectionSet() + ? this.parseSelectionSet(allowRequiredField) : undefined, + required: required, loc: this.loc(start), }; } @@ -464,7 +478,7 @@ export class Parser { kind: Kind.INLINE_FRAGMENT, typeCondition: hasTypeCondition ? this.parseNamedType() : undefined, directives: this.parseDirectives(false), - selectionSet: this.parseSelectionSet(), + selectionSet: this.parseSelectionSet(true), loc: this.loc(start), }; } @@ -488,7 +502,7 @@ export class Parser { variableDefinitions: this.parseVariableDefinitions(), typeCondition: (this.expectKeyword('on'), this.parseNamedType()), directives: this.parseDirectives(false), - selectionSet: this.parseSelectionSet(), + selectionSet: this.parseSelectionSet(true), loc: this.loc(start), }; } @@ -497,7 +511,7 @@ export class Parser { name: this.parseFragmentName(), typeCondition: (this.expectKeyword('on'), this.parseNamedType()), directives: this.parseDirectives(false), - selectionSet: this.parseSelectionSet(), + selectionSet: this.parseSelectionSet(true), loc: this.loc(start), }; } diff --git a/src/language/printer.js b/src/language/printer.js index 8ca8fb2dad..c02711fdd9 100644 --- a/src/language/printer.js +++ b/src/language/printer.js @@ -43,8 +43,8 @@ const printDocASTReducer: any = { wrap(' ', join(directives, ' ')), SelectionSet: ({ selections }) => block(selections), - Field: ({ alias, name, arguments: args, directives, selectionSet }) => { - const prefix = wrap('', alias, ': ') + name; + Field: ({ alias, name, arguments: args, directives, selectionSet, required }) => { + const prefix = wrap('', alias, ': ') + name + (required ? '!' : ''); let argsLine = prefix + wrap('(', join(args, ', '), ')'); if (argsLine.length > MAX_LINE_LENGTH) { @@ -56,6 +56,7 @@ const printDocASTReducer: any = { Argument: ({ name, value }) => name + ': ' + value, + // Fragments FragmentSpread: ({ name, directives }) =>